filepath

package
v0.2.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 3 Imported by: 0

README

filepath

import "github.com/gechr/x/filepath"

Package filepath provides path helpers: symlink resolution and containment checks.

Index

func Expand

func Expand(path string) string

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.

_ = os.Setenv("PROJECT_ROOT", "/srv/app")
fmt.Println(xfilepath.Expand("$PROJECT_ROOT/config.toml"))

Output:

/srv/app/config.toml

func IsWithin

func IsWithin(base string, targets ...string) bool

IsWithin reports whether all target paths are equal to or contained within base. Returns false when no targets are provided.

Example
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.

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
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.

fmt.Println(xfilepath.Merge([]string{"a", "./a", "a/"}))

Output:

[a]

func Resolve

func Resolve(path string) (string, error)

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.

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

func ResolveLenient(path string) (string, error)

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.

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

type MergeOption

MergeOption configures Merge.

type MergeOption func(*mergeConfig)

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.

Documentation

Overview

Package filepath provides path helpers: symlink resolution and containment checks.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Expand

func Expand(path string) string

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

func IsWithin(base string, targets ...string) bool

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 Resolve

func Resolve(path string) (string, error)

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

func ResolveLenient(path string) (string, error)

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

Types

type MergeOption

type MergeOption func(*mergeConfig)

MergeOption configures Merge.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL