const Separator
Separator is the byte MatchPath treats as a path boundary.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package glob matches shell-style patterns against strings, in two modes: plain, where \`\*\` crosses everything, and ...
gno.land/p/moul/x/daily/glob/v0Shell-style pattern matching, with and without path awareness: Match,
MatchPath, Valid, Filter, FilterPath, Any, AnyPath.
1import "gno.land/p/moul/x/daily/glob/v0"
2
3glob.Match("*.gno", "a.gno") // true
4glob.Match("g1[a-z0-9]*", "g1manfred") // true
5glob.MatchPath("gno.land/r/moul/*", "gno.land/r/moul/home") // true
6glob.MatchPath("gno.land/r/moul/*", "gno.land/r/moul/x/daily") // false, '*' stops at '/'
7glob.MatchPath("gno.land/r/moul/**", "gno.land/r/moul/x/daily") // true
8
9allow := []string{"gno.land/r/moul/home", "gno.land/r/moul/x/daily/**"}
10glob.AnyPath(allow, "gno.land/r/moul/x/daily/globdemo") // true
11glob.AnyPath(allow, "gno.land/r/moul/homeless") // false
Live demo: r/moul/x/daily/globdemo.
strings.HasPrefixEvery realm that decides what a caller may touch eventually writes an
allowlist, and the first version of it is a prefix check. The prefix
gno.land/r/moul/home also matches gno.land/r/moul/homeless; the prefix a
caller gets by trimming /** off gno.land/r/moul/x/daily/** also matches
gno.land/r/moul/x/dailyevil/drain. Registering a sibling name one byte longer
than an allowed one is cheap, and the check cannot tell the difference, because
a prefix has no idea where a path segment ends.
Appending the separator by hand is not the fix, it is the other half of the
same bug: gno.land/r/moul/home/ no longer matches the one path the rule was
written to name, and gno.land/r/moul/ no longer matches gno.land/r/moul.
The boundary has to be something the matcher knows about, not something every
call site remembers.
MatchPath makes the boundary part of the grammar rather than something the
caller remembered to append. *, ? and a character class stay inside one
segment; only a ** that is a whole segment crosses, and it crosses as many as
it needs, including zero (a/**/b matches a/b).
? |
any single byte |
* |
any run of bytes, including none |
[abc] |
any one of the listed bytes |
[a-z] |
any byte in the range, both ends included |
[!abc] |
any byte not listed; [^abc] is the same thing |
\x |
the literal byte x, for any x |
** |
in MatchPath only, and only as a whole segment: zero or more segments |
A ] inside a class is written \]. There is no POSIX leading-] rule: one
way to say it beats two, and the one that survives also works for - and \.
A - at either end of a class is a literal dash, as POSIX has it.
Match has no error return, so the
choice is between inert and permissive, and an allowlist entry with a typo
that matches everything is the worse of the two. Valid is how a caller
finds out, and it should be called at write time, not at match time. Any
skips a bad entry and keeps evaluating the good ones, so one typo costs one
rule rather than the whole list.* and
is exponential in the number of them: a*a*a*a*a*a*b against two dozen as
is where it stops returning. This one walks forward and remembers a single
backtrack point, the most recent star, which is
O(len(pattern) * len(name)) in time and constant in space. On chain the
difference is not a slow page, it is a gas bomb a caller gets to aim, because
the pattern is usually what the realm stored and the subject is usually what
the caller supplied. There is no length cap for the same reason: the bound is
a product of two lengths the caller already pays for.? consumes one byte, so it takes two of
them to cover a single é, and a class range compares byte values. That is
fine for what this is for, package paths, bech32 addresses, handles and file
names, all ASCII. It is not the package for matching prose, and a
[a-z] class will not do what you want to a non-ASCII name.In MatchPath a / that is escaped (a\/b) or that sits inside a character
class ([/]) is part of its segment and does not split the pattern. Splitting
there would cut an escape pair or a bracket expression in half and hand the
matcher a piece it would read past the end of, which is an abort out of a
Render that can never be redeployed. Such a pattern matches nothing, because
a name segment can never contain a separator, and it says so by returning
false. A negated class is the exception worth knowing: [!/] is a perfectly
ordinary one-segment pattern meaning any byte but a separator.
Not a regular expression engine, and not a path cleaner: it compares the
strings it is handed. a//b has an empty segment and is not a/b, a trailing
/ leaves a trailing empty segment, and . and .. mean nothing. Normalise
before matching if your input can contain any of those.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Package glob matches shell-style patterns against strings, in two modes: plain, where `*` crosses everything, and path, where `/` is a boundary that only `**` crosses.
The reason to have it on chain is the allowlist. A realm that decides what a caller may touch writes the rule as a pattern (gno.land/r/moul/**), and the obvious alternative, strings.HasPrefix, is wrong in a way that only shows up in production: the prefix gno.land/r/moul/home also matches gno.land/r/moul/homeless, and the prefix a caller gets by trimming /** off the rule above, gno.land/r/moul, also matches gno.land/r/moulier/drain, a different namespace owned by a different person. A separator-aware matcher cannot make either mistake, because a boundary is part of the grammar rather than something the caller remembered to append. Remembering to append it is no answer either: gno.land/r/moul/home with a slash on the end stops being a rule that matches the path it names.
The matcher is iterative with a single backtrack point, so it runs in O(len(pattern) * len(name)) time and constant space. That is not a style preference. The recursive one-liner every glob tutorial shows is exponential on a pattern like a*a*a*a*a*a*b, and a realm that accepts a pattern from a caller and runs an exponential matcher on it has handed out a gas bomb.
Matching is byte-oriented, deliberately: `?` matches one byte and a class range compares bytes, so this package is for ASCII subjects (package paths, bech32 addresses, handles, file names) and not for matching prose. See the package README for what that rules out.
A live demo of this package is at r/moul/x/daily/globdemo(/r/moul/x/daily/globdemo/v0).
Any reports whether name matches at least one of patterns, using Match.
An invalid entry is skipped rather than failing the whole list: one typo in an allowlist should cost that rule and no other.
Filter returns the elements of names matching pattern, in their original order, using Match.
The result is never nil, so a caller can range and len it with no guard.
Match reports whether name matches pattern.
1? any single byte
2* any sequence of bytes, including none
3[abc] any one of the listed bytes
4[a-z] any byte in the range, ends inclusive
5[!abc] any byte NOT listed; [^abc] is the same thing
6\x the literal byte x, for any x
7anything else itself
`*` crosses every byte, including Separator. Use MatchPath when the subject is a path and a wildcard must stay inside one segment.
An invalid pattern matches nothing. There is no error return, so the choice is between inert and permissive, and a bad allowlist entry that matches everything is the worse of the two. Ask Valid when it matters.
MatchPath reports whether name matches pattern, with Separator as a boundary neither side may cross by accident.
Both are split into segments, and then:
`**` only crosses as a whole segment. Glued to anything else, as in `a**b`, it is an ordinary star confined to its segment.
A Separator that is escaped (`a\/b`) or inside a class (`[/]`) belongs to its segment and does not split the pattern. Such a pattern matches nothing, because a name segment can never contain a separator, and it says so by returning false rather than by aborting.
An invalid pattern matches nothing, as in Match.
Valid reports whether pattern is well formed.
Three things make it malformed, and each of them is a typo a caller makes rather than a construct anyone writes on purpose: an unterminated class (`[a-z`), an empty one (`[]`, `[!]`), and a trailing backslash with nothing to escape (`abc\`).