Documentation
¶
Overview ¶
Package filepath provides path helpers: symlink resolution and containment checks.
Index ¶
- func Expand(path string) string
- func IsWithin(base string, targets ...string) bool
- func Merge(paths []string, opts ...MergeOption) []string
- func Rebase(dir, path string) string
- func Resolve(path string) (string, error)
- func ResolveLenient(path string) (string, error)
- func SplitPath(list string) []string
- type MergeOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Expand ¶
Expand expands a leading ~ to the user's home directory and resolves environment variables via os.ExpandEnv. It is purely lexical: the result is not checked for existence or resolved against the filesystem (use Resolve or ResolveLenient for that).
Example ¶
Expand also expands a leading ~ to the user's home directory.
package main
import (
"fmt"
"os"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
_ = os.Setenv("PROJECT_ROOT", "/srv/app")
fmt.Println(xfilepath.Expand("$PROJECT_ROOT/config.toml"))
}
Output: /srv/app/config.toml
func IsWithin ¶
IsWithin reports whether all target paths are equal to or contained within `base`. Returns false when no `targets` are provided.
Example:
IsWithin("src", "src/foo.go") // true
IsWithin(".", "src/foo.go", "lib/bar.go") // true
IsWithin("src", "lib/foo.go") // false
Example ¶
package main
import (
"fmt"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
fmt.Println(xfilepath.IsWithin("src", "src/foo.go"))
fmt.Println(xfilepath.IsWithin("src", "src"))
fmt.Println(xfilepath.IsWithin("src", "lib/foo.go"))
fmt.Println(xfilepath.IsWithin("src"))
}
Output: true true false false
Example (MultipleTargets) ¶
IsWithin only reports `true` when every target is contained within the base.
package main
import (
"fmt"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
fmt.Println(xfilepath.IsWithin(".", "src/foo.go", "lib/bar.go"))
fmt.Println(xfilepath.IsWithin("src", "src/foo.go", "lib/bar.go"))
}
Output: true false
func Merge ¶
func Merge(paths []string, opts ...MergeOption) []string
Merge reduces `paths` to the minimal set covering the same locations: comparing them as cleaned absolute paths, it drops any that duplicate or are nested within another, so a later walk visits each file once. Survivors keep their original form and first-seen order; a path whose absolute form cannot be computed is compared by its cleaned form.
The comparison is lexical by default; pass WithResolveSymlinks to compare resolved physical locations instead.
Example:
Merge([]string{"a", "a"}) // ["a"]
Merge([]string{".", "./sub"}) // ["."]
Merge([]string{"a/b", "a"}) // ["a"]
Merge([]string{"a", "b"}) // ["a", "b"]
Example ¶
package main
import (
"fmt"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
fmt.Println(xfilepath.Merge([]string{".", "./sub"}))
fmt.Println(xfilepath.Merge([]string{"a/b", "a"}))
fmt.Println(xfilepath.Merge([]string{"a", "b"}))
}
Output: [.] [a] [a b]
Example (Duplicates) ¶
Exact duplicates are merged; the first occurrence survives in its original spelling.
package main
import (
"fmt"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
fmt.Println(xfilepath.Merge([]string{"a", "./a", "a/"}))
}
Output: [a]
func Rebase ¶ added in v0.4.5
Rebase anchors a relative `path` to the base directory `dir`, returning `filepath.Join(dir, path)`. An empty or already-absolute `path` is returned unchanged, so a caller-supplied override always wins over the default base. It is purely lexical and touches neither the filesystem nor the current working directory; the result is absolute only when `dir` is.
Example:
Rebase("/etc/app", "conf.d") // "/etc/app/conf.d"
Rebase("/etc/app", "/abs.cfg") // "/abs.cfg"
Rebase("/etc/app", "") // ""
Example ¶
Rebase anchors a relative path to a base directory. (ToSlash keeps the output identical across operating systems.)
package main
import (
"fmt"
"path/filepath"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
base := filepath.Join("etc", "app")
fmt.Println(filepath.ToSlash(xfilepath.Rebase(base, "conf.d")))
}
Output: etc/app/conf.d
func Resolve ¶
Resolve recursively follows every symlink along `path` and returns the fully resolved absolute path. On any error (missing component, cycle, permission) the input path is returned alongside the error so callers can choose whether to handle it or fall back.
Example ¶
Resolve follows symlinks, so a link and its target resolve to the same path.
package main
import (
"fmt"
"os"
"path/filepath"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
dir, _ := os.MkdirTemp("", "example")
defer func() { _ = os.RemoveAll(dir) }()
target := filepath.Join(dir, "target.txt")
_ = os.WriteFile(target, []byte("hello"), 0o600)
link := filepath.Join(dir, "link.txt")
_ = os.Symlink(target, link)
resolvedLink, _ := xfilepath.Resolve(link)
resolvedTarget, _ := xfilepath.Resolve(target)
fmt.Println(resolvedLink == resolvedTarget)
}
Output: true
func ResolveLenient ¶
ResolveLenient returns an absolute path with symlinks resolved where possible. If `path` itself cannot be resolved, it resolves the parent directory and rejoins the original base name. If neither can be resolved, it returns the absolute path.
Example ¶
ResolveLenient succeeds where Resolve fails: a missing file resolves via its parent directory, keeping the original base name.
package main
import (
"fmt"
"os"
"path/filepath"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
dir, _ := os.MkdirTemp("", "example")
defer func() { _ = os.RemoveAll(dir) }()
missing := filepath.Join(dir, "missing.txt")
_, err := xfilepath.Resolve(missing)
fmt.Println(err != nil)
resolved, _ := xfilepath.ResolveLenient(missing)
fmt.Println(filepath.Base(resolved))
}
Output: true missing.txt
func SplitPath ¶ added in v0.4.4
SplitPath splits a PATH-style list (such as $PATH or $GOPATH) on the OS-specific list separator (os.PathListSeparator), dropping the empty entries produced by leading, trailing, or doubled separators - an empty entry otherwise resolves to the current directory when joined. On Windows it honours the same quoting rules as filepath.SplitList.
Example:
SplitPath("/usr/bin:/bin:") // ["/usr/bin", "/bin"] (Unix)
SplitPath("") // []
Example ¶
SplitPath splits a PATH-style list and drops the empty entry left by the trailing separator.
package main
import (
"fmt"
"os"
xfilepath "github.com/gechr/x/filepath"
)
func main() {
sep := string(os.PathListSeparator)
fmt.Println(xfilepath.SplitPath("/usr/bin" + sep + "/bin" + sep))
}
Output: [/usr/bin /bin]
Types ¶
type MergeOption ¶
type MergeOption func(*mergeConfig)
MergeOption configures Merge.
func WithResolveSymlinks ¶
func WithResolveSymlinks() MergeOption
WithResolveSymlinks makes Merge compare paths by their resolved physical location (via ResolveLenient) rather than lexically, so two spellings that reach the same target through a symlink are merged. It touches the filesystem; without it Merge is pure and lexical.