From 8c1f0842b0626fd215c55d35f884a9b96e7881c5 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 22 May 2018 15:04:49 -0700 Subject: [PATCH] implement basename function --- lang/funcs/filesystem.go | 24 +++++++++++++++++++ lang/funcs/filesystem_test.go | 45 +++++++++++++++++++++++++++++++++++ lang/functions.go | 2 +- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/lang/funcs/filesystem.go b/lang/funcs/filesystem.go index 785dac9ea..c54964daa 100644 --- a/lang/funcs/filesystem.go +++ b/lang/funcs/filesystem.go @@ -63,6 +63,20 @@ func MakeFileFunc(baseDir string, encBase64 bool) function.Function { }) } +// BasenameFunc constructs a function that takes a string containing a filesystem path and removes all except the last portion from it. +var BasenameFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "path", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.StringVal(filepath.Base(args[0].AsString())), nil + }, +}) + // File reads the contents of the file at the given path. // // The file must contain valid UTF-8 bytes, or this function will return an error. @@ -86,3 +100,13 @@ func FileBase64(baseDir string, path cty.Value) (cty.Value, error) { fn := MakeFileFunc(baseDir, true) return fn.Call([]cty.Value{path}) } + +// Basename takes a string containing a filesystem path and removes all except the last portion from it. +// +// The underlying function implementation works only with the path string and does not access the filesystem itself. +// It is therefore unable to take into account filesystem features such as symlinks. +// +// If the path is empty then the result is ".", representing the current working directory. +func Basename(path cty.Value) (cty.Value, error) { + return BasenameFunc.Call([]cty.Value{path}) +} diff --git a/lang/funcs/filesystem_test.go b/lang/funcs/filesystem_test.go index 2e7eaad50..213a7c4cb 100644 --- a/lang/funcs/filesystem_test.go +++ b/lang/funcs/filesystem_test.go @@ -96,3 +96,48 @@ func TestFileBase64(t *testing.T) { }) } } + +func TestBasename(t *testing.T) { + tests := []struct { + Path cty.Value + Want cty.Value + Err bool + }{ + { + cty.StringVal("testdata/hello.txt"), + cty.StringVal("hello.txt"), + false, + }, + { + cty.StringVal("hello.txt"), + cty.StringVal("hello.txt"), + false, + }, + { + cty.StringVal(""), + cty.StringVal("."), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("Basename(%#v)", test.Path), func(t *testing.T) { + got, err := Basename(test.Path) + + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + return + } else { + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + } + + if !got.RawEquals(test.Want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) + } + }) + } +} diff --git a/lang/functions.go b/lang/functions.go index 731fcd312..cd05fef60 100644 --- a/lang/functions.go +++ b/lang/functions.go @@ -29,7 +29,7 @@ func (s *Scope) Functions() map[string]function.Function { s.funcs = map[string]function.Function{ "abs": stdlib.AbsoluteFunc, - "basename": unimplFunc, // TODO + "basename": funcs.BasenameFunc, "base64decode": unimplFunc, // TODO "base64encode": unimplFunc, // TODO "base64gzip": unimplFunc, // TODO