diff --git a/docs/content/functions/filepath.md b/docs/content/functions/filepath.md new file mode 100644 index 000000000..3a1bf8c37 --- /dev/null +++ b/docs/content/functions/filepath.md @@ -0,0 +1,402 @@ +--- +title: filepath functions +menu: + main: + parent: functions +--- + +gomplate's path functions are split into 2 namespaces: +- `path`, which is useful for manipulating slash-based (`/`) paths, such as in URLs +- `filepath`, which should be used for local filesystem paths, especially when Windows paths may be involved. + +This page documents the `filepath` namespace - see also the [`path`](../path) documentation. + +These functions are wrappers for Go's [`path/filepath`](https://golang.org/pkg/path/filepath/) package. + +## `filepath.Base` + + +Returns the last element of path. Trailing path separators are removed before extracting the last element. If the path is empty, Base returns `.`. If the path consists entirely of separators, Base returns a single separator. + +A wrapper for Go's [`filepath.Base`](https://golang.org/pkg/path/filepath/#Base) function. + + +### Usage +```go +filepath.Base path +``` + +```go +path | filepath.Base +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Base "/tmp/foo" }}' +foo +``` + +## `filepath.Clean` + + +Clean returns the shortest path name equivalent to path by purely lexical processing. + +A wrapper for Go's [`filepath.Clean`](https://golang.org/pkg/path/filepath/#Clean) function. + + +### Usage +```go +filepath.Clean path +``` + +```go +path | filepath.Clean +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Clean "/tmp//foo/../" }}' +/tmp +``` + +## `filepath.Dir` + + +Returns all but the last element of path, typically the path's directory. + +A wrapper for Go's [`filepath.Dir`](https://golang.org/pkg/path/filepath/#Dir) function. + + +### Usage +```go +filepath.Dir path +``` + +```go +path | filepath.Dir +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Dir "/tmp/foo" }}' +/tmp +``` + +## `filepath.Ext` + + +Returns the file name extension used by path. + +A wrapper for Go's [`filepath.Ext`](https://golang.org/pkg/path/filepath/#Ext) function. + + +### Usage +```go +filepath.Ext path +``` + +```go +path | filepath.Ext +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Ext "/tmp/foo.csv" }}' +.csv +``` + +## `filepath.FromSlash` + + +Returns the result of replacing each slash (`/`) character in the path with the platform's separator character. + +A wrapper for Go's [`filepath.FromSlash`](https://golang.org/pkg/path/filepath/#FromSlash) function. + + +### Usage +```go +filepath.FromSlash path +``` + +```go +path | filepath.FromSlash +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.FromSlash "/foo/bar" }}' +/foo/bar +C:\> gomplate.exe -i '{{ filepath.FromSlash "/foo/bar" }}' +C:\foo\bar +``` + +## `filepath.IsAbs` + + +Reports whether the path is absolute. + +A wrapper for Go's [`filepath.IsAbs`](https://golang.org/pkg/path/filepath/#IsAbs) function. + + +### Usage +```go +filepath.IsAbs path +``` + +```go +path | filepath.IsAbs +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i 'the path is {{ if (filepath.IsAbs "/tmp/foo.csv") }}absolute{{else}}relative{{end}}' +the path is absolute +$ gomplate -i 'the path is {{ if (filepath.IsAbs "../foo.csv") }}absolute{{else}}relative{{end}}' +the path is relative +``` + +## `filepath.Join` + + +Joins any number of path elements into a single path, adding a separator if necessary. + +A wrapper for Go's [`filepath.Join`](https://golang.org/pkg/path/filepath/#Join) function. + + +### Usage +```go +filepath.Join elem... +``` + + +### Arguments + +| name | description | +|------|-------------| +| `elem...` | _(required)_ The path elements to join (0 or more) | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Join "/tmp" "foo" "bar" }}' +/tmp/foo/bar +C:\> gomplate.exe -i '{{ filepath.Join "C:\tmp" "foo" "bar" }}' +C:\tmp\foo\bar +``` + +## `filepath.Match` + + +Reports whether name matches the shell file name pattern. + +A wrapper for Go's [`filepath.Match`](https://golang.org/pkg/path/filepath/#Match) function. + + +### Usage +```go +filepath.Match pattern path +``` + + +### Arguments + +| name | description | +|------|-------------| +| `pattern` | _(required)_ The pattern to match on | +| `path` | _(required)_ The path to match | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Match "*.csv" "foo.csv" }}' +true +``` + +## `filepath.Rel` + + +Returns a relative path that is lexically equivalent to targetpath when joined to basepath with an intervening separator. + +A wrapper for Go's [`filepath.Rel`](https://golang.org/pkg/path/filepath/#Rel) function. + + +### Usage +```go +filepath.Rel basepath targetpath +``` + + +### Arguments + +| name | description | +|------|-------------| +| `basepath` | _(required)_ The | +| `targetpath` | _(required)_ The | + + +### Examples + +```console +$ gomplate -i '{{ filepath.Rel "/a" "/a/b/c" }}' +b/c +``` + +## `filepath.Split` + + +Splits path immediately following the final path separator, separating it into a directory and file name component. + +The function returns an array with two values, the first being the diretory, and the second the file. + +A wrapper for Go's [`filepath.Split`](https://golang.org/pkg/path/filepath/#Split) function. + + +### Usage +```go +filepath.Split path +``` + +```go +path | filepath.Split +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ $p := filepath.Split "/tmp/foo" }}{{ $dir := index $p 0 }}{{ $file := index $p 1 }}dir is {{$dir}}, file is {{$file}}' +dir is /tmp/, file is foo +C:\> gomplate.exe -i '{{ $p := filepath.Split `C:\tmp\foo` }}{{ $dir := index $p 0 }}{{ $file := index $p 1 }}dir is {{$dir}}, file is {{$file}}' +dir is C:\tmp\, file is foo +``` + +## `filepath.ToSlash` + + +Returns the result of replacing each separator character in path with a slash (`/`) character. + +A wrapper for Go's [`filepath.ToSlash`](https://golang.org/pkg/path/filepath/#ToSlash) function. + + +### Usage +```go +filepath.ToSlash path +``` + +```go +path | filepath.ToSlash +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ filepath.ToSlash "/foo/bar" }}' +/foo/bar +C:\> gomplate.exe -i '{{ filepath.ToSlash `foo\bar\baz` }}' +foo/bar/baz +``` + +## `filepath.VolumeName` + + +Returns the leading volume name. Given `C:\foo\bar` it returns `C:` on Windows. Given a UNC like `\\host\share\foo` it returns `\\host\share`. On other platforms it returns an empty string. + +A wrapper for Go's [`filepath.VolumeName`](https://golang.org/pkg/path/filepath/#VolumeName) function. + + +### Usage +```go +filepath.VolumeName path +``` + +```go +path | filepath.VolumeName +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +C:\> gomplate.exe -i 'volume is {{ filepath.VolumeName "C:/foo/bar" }}' +volume is C: +$ gomplate -i 'volume is {{ filepath.VolumeName "/foo/bar" }}' +volume is +``` diff --git a/docs/content/functions/path.md b/docs/content/functions/path.md new file mode 100644 index 000000000..5437db3f7 --- /dev/null +++ b/docs/content/functions/path.md @@ -0,0 +1,267 @@ +--- +title: path functions +menu: + main: + parent: functions +--- + +The path functions are split into 2 namespaces: +- `path`, which is useful for manipulating slash-based (`/`) paths, such as in URLs +- `filepath`, which should be used for local filesystem paths, especially when Windows paths may be involved + +This page documents the `path` namespace - see also the [`filepath`](../filepath) documentation. + +These functions are wrappers for Go's [`path`](https://golang.org/pkg/path/) and [`path/filepath`](https://golang.org/pkg/path/filepath/) packages. + +## `path.Base` + + +Returns the last element of path. Trailing slashes are removed before extracting the last element. If the path is empty, Base returns `.`. If the path consists entirely of slashes, Base returns `/`. + +A wrapper for Go's [`path.Base`](https://golang.org/pkg/path/#Base) function. + + +### Usage +```go +path.Base path +``` + +```go +path | path.Base +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ path.Base "/tmp/foo" }}' +foo +``` + +## `path.Clean` + + +Clean returns the shortest path name equivalent to path by purely lexical processing. + +A wrapper for Go's [`path.Clean`](https://golang.org/pkg/path/#Clean) function. + + +### Usage +```go +path.Clean path +``` + +```go +path | path.Clean +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ path.Clean "/tmp//foo/../" }}' +/tmp +``` + +## `path.Dir` + + +Returns all but the last element of path, typically the path's directory. + +A wrapper for Go's [`path.Dir`](https://golang.org/pkg/path/#Dir) function. + + +### Usage +```go +path.Dir path +``` + +```go +path | path.Dir +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ path.Dir "/tmp/foo" }}' +/tmp +``` + +## `path.Ext` + + +Returns the file name extension used by path. + +A wrapper for Go's [`path.Ext`](https://golang.org/pkg/path/#Ext) function. + + +### Usage +```go +path.Ext path +``` + +```go +path | path.Ext +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ path.Ext "/tmp/foo.csv" }}' +.csv +``` + +## `path.IsAbs` + + +Reports whether the path is absolute. + +A wrapper for Go's [`path.IsAbs`](https://golang.org/pkg/path/#IsAbs) function. + + +### Usage +```go +path.IsAbs path +``` + +```go +path | path.IsAbs +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i 'the path is {{ if (path.IsAbs "/tmp/foo.csv") }}absolute{{else}}relative{{end}}' +the path is absolute +$ gomplate -i 'the path is {{ if (path.IsAbs "../foo.csv") }}absolute{{else}}relative{{end}}' +the path is relative +``` + +## `path.Join` + + +Joins any number of path elements into a single path, adding a separating slash if necessary. + +A wrapper for Go's [`path.Join`](https://golang.org/pkg/path/#Join) function. + + +### Usage +```go +path.Join elem... +``` + + +### Arguments + +| name | description | +|------|-------------| +| `elem...` | _(required)_ The path elements to join (0 or more) | + + +### Examples + +```console +$ gomplate -i '{{ path.Join "/tmp" "foo" "bar" }}' +/tmp/foo/bar +``` + +## `path.Match` + + +Reports whether name matches the shell file name pattern. + +A wrapper for Go's [`path.Match`](https://golang.org/pkg/path/#Match) function. + + +### Usage +```go +path.Match pattern path +``` + + +### Arguments + +| name | description | +|------|-------------| +| `pattern` | _(required)_ The pattern to match on | +| `path` | _(required)_ The path to match | + + +### Examples + +```console +$ gomplate -i '{{ path.Match "*.csv" "foo.csv" }}' +true +``` + +## `path.Split` + + +Splits path immediately following the final slash, separating it into a directory and file name component. + +The function returns an array with two values, the first being the diretory, and the second the file. + +A wrapper for Go's [`path.Split`](https://golang.org/pkg/path/#Split) function. + + +### Usage +```go +path.Split path +``` + +```go +path | path.Split +``` + + +### Arguments + +| name | description | +|------|-------------| +| `path` | _(required)_ The input path | + + +### Examples + +```console +$ gomplate -i '{{ $p := path.Split "/tmp/foo" }}{{ $dir := index $p 0 }}{{ $file := index $p 1 }}dir is {{$dir}}, file is {{$file}}' +dir is /tmp/, file is foo +``` diff --git a/funcs.go b/funcs.go index 16a61f0c8..1a0527f0c 100644 --- a/funcs.go +++ b/funcs.go @@ -22,6 +22,8 @@ func initFuncs(d *data.Data) template.FuncMap { funcs.AddMathFuncs(f) funcs.AddCryptoFuncs(f) funcs.AddFileFuncs(f) + funcs.AddFilePathFuncs(f) + funcs.AddPathFuncs(f) funcs.AddSockaddrFuncs(f) return f } diff --git a/funcs/filepath.go b/funcs/filepath.go new file mode 100644 index 000000000..8ee9dc1ff --- /dev/null +++ b/funcs/filepath.go @@ -0,0 +1,90 @@ +package funcs + +import ( + "path/filepath" + "sync" + + "github.com/hairyhenderson/gomplate/conv" +) + +var ( + fpf *FilePathFuncs + fpfInit sync.Once +) + +// FilePathNS - the Path namespace +func FilePathNS() *FilePathFuncs { + fpfInit.Do(func() { fpf = &FilePathFuncs{} }) + return fpf +} + +// AddFilePathFuncs - +func AddFilePathFuncs(f map[string]interface{}) { + f["filepath"] = FilePathNS +} + +// FilePathFuncs - +type FilePathFuncs struct { +} + +// Base - +func (f *FilePathFuncs) Base(in interface{}) string { + return filepath.Base(conv.ToString(in)) +} + +// Clean - +func (f *FilePathFuncs) Clean(in interface{}) string { + return filepath.Clean(conv.ToString(in)) +} + +// Dir - +func (f *FilePathFuncs) Dir(in interface{}) string { + return filepath.Dir(conv.ToString(in)) +} + +// Ext - +func (f *FilePathFuncs) Ext(in interface{}) string { + return filepath.Ext(conv.ToString(in)) +} + +// FromSlash - +func (f *FilePathFuncs) FromSlash(in interface{}) string { + return filepath.FromSlash(conv.ToString(in)) +} + +// IsAbs - +func (f *FilePathFuncs) IsAbs(in interface{}) bool { + return filepath.IsAbs(conv.ToString(in)) +} + +// Join - +func (f *FilePathFuncs) Join(elem ...interface{}) string { + s := conv.ToStrings(elem...) + return filepath.Join(s...) +} + +// Match - +func (f *FilePathFuncs) Match(pattern, name interface{}) (matched bool, err error) { + return filepath.Match(conv.ToString(pattern), conv.ToString(name)) +} + +// Rel - +func (f *FilePathFuncs) Rel(basepath, targpath interface{}) (string, error) { + return filepath.Rel(conv.ToString(basepath), conv.ToString(targpath)) +} + +// Split - +func (f *FilePathFuncs) Split(in interface{}) []string { + dir, file := filepath.Split(conv.ToString(in)) + return []string{dir, file} +} + +// ToSlash - +func (f *FilePathFuncs) ToSlash(in interface{}) string { + return filepath.ToSlash(conv.ToString(in)) +} + +// VolumeName - +func (f *FilePathFuncs) VolumeName(in interface{}) string { + return filepath.VolumeName(conv.ToString(in)) +} diff --git a/funcs/filepath_test.go b/funcs/filepath_test.go new file mode 100644 index 000000000..79b0a481c --- /dev/null +++ b/funcs/filepath_test.go @@ -0,0 +1,30 @@ +//+build !windows + +package funcs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilePathFuncs(t *testing.T) { + f := FilePathNS() + assert.Equal(t, "bar", f.Base("foo/bar")) + assert.Equal(t, "bar", f.Base("/foo/bar")) + + assert.Equal(t, "/foo/baz", f.Clean("/foo/bar/../baz")) + assert.Equal(t, "foo", f.Dir("foo/bar")) + assert.Equal(t, ".txt", f.Ext("/foo/bar/baz.txt")) + assert.False(t, f.IsAbs("foo/bar")) + assert.True(t, f.IsAbs("/foo/bar")) + assert.Equal(t, "foo/bar/qux", f.Join("foo", "bar", "baz", "..", "qux")) + m, _ := f.Match("*.txt", "foo.json") + assert.False(t, m) + m, _ = f.Match("*.txt", "foo.txt") + assert.True(t, m) + r, _ := f.Rel("/foo/bar", "/foo/bar/baz") + assert.Equal(t, "baz", r) + assert.Equal(t, []string{"/foo/bar/", "baz"}, f.Split("/foo/bar/baz")) + assert.Equal(t, "", f.VolumeName("/foo/bar")) +} diff --git a/funcs/filepath_windows_test.go b/funcs/filepath_windows_test.go new file mode 100644 index 000000000..26cba9489 --- /dev/null +++ b/funcs/filepath_windows_test.go @@ -0,0 +1,32 @@ +//+build windows + +package funcs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilePathFuncs(t *testing.T) { + f := FilePathNS() + assert.Equal(t, "bar", f.Base(`foo\bar`)) + assert.Equal(t, "bar", f.Base("C:/foo/bar")) + assert.Equal(t, "bar", f.Base(`C:\foo\bar`)) + + assert.Equal(t, `C:\foo\baz`, f.Clean(`C:\foo\bar\..\baz`)) + assert.Equal(t, "foo", f.Dir(`foo\bar`)) + assert.Equal(t, ".txt", f.Ext(`C:\foo\bar\baz.txt`)) + assert.False(t, f.IsAbs(`foo\bar`)) + assert.True(t, f.IsAbs(`C:\foo\bar`)) + assert.False(t, f.IsAbs(`\foo\bar`)) + assert.Equal(t, `foo\bar\qux`, f.Join("foo", "bar", "baz", "..", "qux")) + m, _ := f.Match("*.txt", "foo.json") + assert.False(t, m) + m, _ = f.Match("*.txt", "foo.txt") + assert.True(t, m) + r, _ := f.Rel(`C:\foo\bar`, `C:\foo\bar\baz`) + assert.Equal(t, "baz", r) + assert.Equal(t, []string{`C:\foo\bar\`, "baz"}, f.Split(`C:\foo\bar\baz`)) + assert.Equal(t, "D:", f.VolumeName(`D:\foo\bar`)) +} diff --git a/funcs/path.go b/funcs/path.go new file mode 100644 index 000000000..255acef94 --- /dev/null +++ b/funcs/path.go @@ -0,0 +1,70 @@ +package funcs + +import ( + "path" + "sync" + + "github.com/hairyhenderson/gomplate/conv" +) + +var ( + pf *PathFuncs + pfInit sync.Once +) + +// PathNS - the Path namespace +func PathNS() *PathFuncs { + pfInit.Do(func() { pf = &PathFuncs{} }) + return pf +} + +// AddPathFuncs - +func AddPathFuncs(f map[string]interface{}) { + f["path"] = PathNS +} + +// PathFuncs - +type PathFuncs struct { +} + +// Base - +func (f *PathFuncs) Base(in interface{}) string { + return path.Base(conv.ToString(in)) +} + +// Clean - +func (f *PathFuncs) Clean(in interface{}) string { + return path.Clean(conv.ToString(in)) +} + +// Dir - +func (f *PathFuncs) Dir(in interface{}) string { + return path.Dir(conv.ToString(in)) +} + +// Ext - +func (f *PathFuncs) Ext(in interface{}) string { + return path.Ext(conv.ToString(in)) +} + +// IsAbs - +func (f *PathFuncs) IsAbs(in interface{}) bool { + return path.IsAbs(conv.ToString(in)) +} + +// Join - +func (f *PathFuncs) Join(elem ...interface{}) string { + s := conv.ToStrings(elem...) + return path.Join(s...) +} + +// Match - +func (f *PathFuncs) Match(pattern, name interface{}) (matched bool, err error) { + return path.Match(conv.ToString(pattern), conv.ToString(name)) +} + +// Split - +func (f *PathFuncs) Split(in interface{}) []string { + dir, file := path.Split(conv.ToString(in)) + return []string{dir, file} +} diff --git a/funcs/path_test.go b/funcs/path_test.go new file mode 100644 index 000000000..0ccfc6480 --- /dev/null +++ b/funcs/path_test.go @@ -0,0 +1,25 @@ +package funcs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPathFuncs(t *testing.T) { + p := PathNS() + assert.Equal(t, "bar", p.Base("foo/bar")) + assert.Equal(t, "bar", p.Base("/foo/bar")) + + assert.Equal(t, "/foo/baz", p.Clean("/foo/bar/../baz")) + assert.Equal(t, "foo", p.Dir("foo/bar")) + assert.Equal(t, ".txt", p.Ext("/foo/bar/baz.txt")) + assert.False(t, false, p.IsAbs("foo/bar")) + assert.True(t, p.IsAbs("/foo/bar")) + assert.Equal(t, "foo/bar/qux", p.Join("foo", "bar", "baz", "..", "qux")) + m, _ := p.Match("*.txt", "foo.json") + assert.False(t, m) + m, _ = p.Match("*.txt", "foo.txt") + assert.True(t, m) + assert.Equal(t, []string{"/foo/bar/", "baz"}, p.Split("/foo/bar/baz")) +}