diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d401597f..474e5c0756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ internal API changes are not present. Main (unreleased) ----------------- +### Features + +- Add the function `path_join` to the stdlib. (@wildum) + ### Bugfixes - Update yet-another-cloudwatch-exporter from v0.60.0 vo v0.61.0: (@morremeyer) diff --git a/docs/sources/reference/stdlib/file.md b/docs/sources/reference/stdlib/file.md new file mode 100644 index 0000000000..5a8ae0efb0 --- /dev/null +++ b/docs/sources/reference/stdlib/file.md @@ -0,0 +1,24 @@ +--- +canonical: https://grafana.com/docs/alloy/latest/reference/stdlib/file/ +description: Learn about file functions +menuTitle: file +title: file +--- + +# file + +The `file` namespace contains functions related to files. + +## file.path_join + +The `file.path_join` function joins any number of path elements into a single path, separating them with an OS-specific separator. + +### Examples + +``` +> file.path_join() +"" + +> file.path_join("this/is", "a/path") +"this/is/a/path" +``` diff --git a/syntax/internal/stdlib/stdlib.go b/syntax/internal/stdlib/stdlib.go index a30fd4cd82..995e538d4b 100644 --- a/syntax/internal/stdlib/stdlib.go +++ b/syntax/internal/stdlib/stdlib.go @@ -7,6 +7,7 @@ import ( "fmt" "maps" "os" + "path/filepath" "strings" "github.com/grafana/alloy/syntax/alloytypes" @@ -53,6 +54,7 @@ var Identifiers = map[string]interface{}{ "array": array, "encoding": encoding, "string": str, + "file": file, } func init() { @@ -60,6 +62,10 @@ func init() { maps.Copy(Identifiers, DeprecatedIdentifiers) } +var file = map[string]interface{}{ + "path_join": filepath.Join, +} + var encoding = map[string]interface{}{ "from_json": jsonDecode, "from_yaml": yamlDecode, diff --git a/syntax/vm/vm_stdlib_test.go b/syntax/vm/vm_stdlib_test.go index 45b1137050..4454a31d89 100644 --- a/syntax/vm/vm_stdlib_test.go +++ b/syntax/vm/vm_stdlib_test.go @@ -215,6 +215,30 @@ func TestStdlib_StringFunc(t *testing.T) { } } +func TestStdlibFileFunc(t *testing.T) { + tt := []struct { + name string + input string + expect interface{} + }{ + {"file.path_join", `file.path_join("this/is", "a/path")`, "this/is/a/path"}, + {"file.path_join empty", `file.path_join()`, ""}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + expr, err := parser.ParseExpression(tc.input) + require.NoError(t, err) + + eval := vm.New(expr) + + rv := reflect.New(reflect.TypeOf(tc.expect)) + require.NoError(t, eval.Evaluate(nil, rv.Interface())) + require.Equal(t, tc.expect, rv.Elem().Interface()) + }) + } +} + func BenchmarkConcat(b *testing.B) { // There's a bit of setup work to do here: we want to create a scope holding // a slice of the Person type, which has a fair amount of data in it.