Skip to content

dwisiswant0/richglob

Repository files navigation

richglob

Go Reference tests

Brings Bash-style globbing to Go for filesystem matching like recursive **, extended patterns, case-insensitive searches, ignore rules, and more. All opt-in for flexibility.

Install

go get go.dw1.io/richglob

Quick start

package main

import (
	"fmt"
	"path/filepath"

	"go.dw1.io/richglob"
)

func main() {
	matched, err := richglob.Match(
		filepath.FromSlash("src/**/main.go"),
		filepath.FromSlash("src/cmd/tool/main.go"),
		richglob.WithGlobStar(),
	)
	if err != nil {
		panic(err)
	}

	println(matched)
}
matches, err := richglob.Glob("src/**/*.go", richglob.WithGlobStar())
if err != nil {
	panic(err)
}

for _, match := range matches {
	println(match)
}
for match, err := range richglob.GlobSeq2("src/**/*.go", richglob.WithGlobStar()) {
	if err != nil {
		panic(err)
	}

	println(match)
}

Pattern syntax

By default, richglob supports the usual glob operators:

  • * matches any sequence of non-separator characters
  • ? matches any single non-separator character
  • [abc] matches a character class
  • [^abc] matches any character outside a class
  • \ escapes metacharacters on non-Windows platforms

Patterns are path-aware. * and ? do not cross path separators.

Options

Note

Behavior notes

  • Match checks a single path or path segment string against a pattern.
  • Glob walks the filesystem and returns matching paths.
  • GlobSeq2 walks the filesystem lazily and yields (path, error) pairs.
  • Glob sorts results lexicographically by default.
  • GlobSeq2 yields matches in traversal order and does not globally sort before yielding.
  • If Glob finds nothing, it returns nil, nil by default.
  • GlobSeq2 yields a terminal error pair for malformed patterns, invalid ignore patterns, or WithFailGlob().
  • WithFailGlob() takes precedence over WithNullGlob().
  • Hidden files are included by default. When Bash-style pathname rules are enabled, hidden entries are excluded unless WithDotGlob() is also set. WithGlobIgnore(...) opts into Bash-style hidden-file handling on its own, while WithGitIgnore() keeps hidden entries filtered unless WithDotGlob() is also enabled.
  • When WithGitIgnore() and WithGlobIgnore(...) are both set, .gitignore rules apply first during traversal and WithGlobIgnore(...) acts as an additional result filter.

Examples

Case-insensitive matching:

ok, err := richglob.Match("*.GO", "main.go", richglob.WithNoCaseGlob())

Recursive search with **:

matches, err := richglob.Glob("src/**/*.go", richglob.WithGlobStar())

Lazy recursive search:

for match, err := range richglob.GlobSeq2("src/**/*.go", richglob.WithGlobStar()) {
	if err != nil {
		panic(err)
	}

	println(match)
}

Extglob alternation:

ok, err := richglob.Match("@(main|util).go", "util.go", richglob.WithExtGlob())

Ignore generated files:

matches, err := richglob.Glob(
	"src/**/*.go",
	richglob.WithGlobStar(),
	richglob.WithGlobIgnore("src/**/*_generated.go"),
)

Respect nested .gitignore files while walking:

matches, err := richglob.Glob(
	"src/**/*.go",
	richglob.WithGlobStar(),
	richglob.WithGitIgnore(),
)

Benchmarks

Compared against the doublestar and the std library's filepath.{Match,Glob}.

benchstat
goos: linux
goarch: amd64
pkg: benchmarks
cpu: AMD EPYC 7763 64-Core Processor                
                  │  richglob   │              doublestar              │                  std                   │
                  │   sec/op    │    sec/op     vs base                │    sec/op     vs base                  │
Match-4             80.05n ± 3%   108.70n ± 0%  +35.80% (p=0.000 n=10)   106.65n ± 0%  +33.24% (p=0.000 n=10)
Match/recursive-4   129.4n ± 0%    105.7n ± 0%  -18.28% (p=0.000 n=10)
Glob-4              13.98µ ± 1%    13.74µ ± 1%   -1.70% (p=0.000 n=10)    17.55µ ± 1%  +25.54% (p=0.000 n=10)
Glob/recursive-4    90.50µ ± 1%   162.24µ ± 1%  +79.26% (p=0.000 n=10)
geomean             1.902µ         2.250µ       +18.25%                   1.368µ       +29.33%                ¹
¹ benchmark set differs from baseline; geomeans may not be comparable

                  │    richglob    │               doublestar                │                   std                    │
                  │      B/op      │     B/op       vs base                  │     B/op      vs base                    │
Match-4               0.000 ± 0%        0.000 ± 0%        ~ (p=1.000 n=10) ¹     0.000 ± 0%        ~ (p=1.000 n=10) ¹
Match/recursive-4     0.000 ± 0%        0.000 ± 0%        ~ (p=1.000 n=10) ¹
Glob-4              1.495Ki ± 0%      1.872Ki ± 0%  +25.21% (p=0.000 n=10)     1.026Ki ± 0%  -31.35% (p=0.000 n=10)
Glob/recursive-4    8.115Ki ± 0%     11.178Ki ± 0%  +37.74% (p=0.000 n=10)
geomean                          ²                  +14.60%                ²                 -17.15%                ³ ²
¹ all samples are equal
² summaries must be >0 to compute geomean
³ benchmark set differs from baseline; geomeans may not be comparable

                  │   richglob   │              doublestar              │                  std                   │
                  │  allocs/op   │ allocs/op   vs base                  │ allocs/op   vs base                    │
Match-4             0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=10) ¹   0.000 ± 0%        ~ (p=1.000 n=10) ¹
Match/recursive-4   0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=10) ¹
Glob-4              31.00 ± 0%     53.00 ± 0%  +70.97% (p=0.000 n=10)     24.00 ± 0%  -22.58% (p=0.000 n=10)
Glob/recursive-4    152.0 ± 0%     273.0 ± 0%  +79.61% (p=0.000 n=10)
geomean                        ²               +32.38%                ²               -12.01%                ³ ²
¹ all samples are equal
² summaries must be >0 to compute geomean
³ benchmark set differs from baseline; geomeans may not be comparable

Highlights:

  • richglob outperforms doublestar by 35-36% in Match ops and is 25% faster than the standard library's Glob.
  • For recursive globbing (**), richglob is 79% faster than doublestar.
  • richglob uses fewer memory allocs (31 vs 53 for doublestar) and less memory in Glob ops.
  • Overall geomean perf shows richglob is 18% faster than doublestar and 29% faster than std.

Run benchmarks yourself:

make -C benchmarks/

License

richglob is released with ♡ by @dwisiswant0 under the Apache 2.0 license. See LICENSE.

Contributors