SkillAgentSearch skills...

Wax

Opinionated and portable globs that can be matched against paths and directory trees.

Install / Use

/learn @olson-sean-k/Wax
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<div align="center"> <img alt="Wax" src="https://raw.githubusercontent.com/olson-sean-k/wax/master/doc/wax.svg?sanitize=true" width="320"/> </div> <br/>

Wax is a Rust library that provides opinionated and portable globs that can be matched against file paths and directory trees. Globs use a familiar syntax and support expressive features with semantics that emphasize component boundaries.

GitHub docs.rs crates.io

Basic Usage

Match a path against a glob:

use wax::{Glob, Program};

let glob = Glob::new("*.png").unwrap();
assert!(glob.is_match("logo.png"));

Match a path against a glob with matched text (captures):

use wax::{CandidatePath, Glob, Program};

let glob = Glob::new("**/{*.{go,rs}}").unwrap();

let path = CandidatePath::from("src/main.go");
let matched = glob.matched(&path).unwrap();

assert_eq!("main.go", matched.get(2).unwrap());

Match a directory tree against a glob:

use wax::Glob;

let glob = Glob::new("**/*.{md,txt}").unwrap();
for entry in glob.walk("doc") {
    let entry = entry.unwrap();
    // ...
}

Match a directory tree against a glob with negations:

use wax::walk::{FileIterator, LinkBehavior};
use wax::Glob;

let glob = Glob::new("**/*.{md,txt}").unwrap();
for entry in glob
    .walk_with_behavior("doc", LinkBehavior::ReadTarget)
    .not("**/secret/**")
    .unwrap()
{
    let entry = entry.unwrap();
    // ...
}

Match a path against multiple globs:

use wax::{Glob, Program};

let any = wax::any([
    "src/**/*.rs",
    "tests/**/*.rs",
    "doc/**/*.md",
    "pkg/**/PKGBUILD",
]).unwrap();
assert!(any.is_match("src/token/mod.rs"));

See more details below.

Construction

Globs are encoded as UTF-8 strings called glob expressions that resemble Unix paths consisting of nominal components delimited by separators. The most fundamental type in the Wax API is Glob, which is constructed from a glob expression via inherent functions or standard conversion traits. Data is borrowed where possible in most APIs, but can be copied into owned instances using an into_owned method with most types.

use wax::Glob;

let glob = Glob::new("site/img/logo.svg").unwrap();

Not only are APIs designed for portability, but so too are glob expressions. Regardless of platform or operating system, globs support the same features and use the same syntax. Glob expressions are distinct from paths, which differ in syntax and features on each platform.

In glob expressions, forward slash / is the only path component separator and back slashes \ are forbidden (back slash is used for escape sequences, but the literal sequence \\ is not supported). This means that it is impossible to represent \ in nominal path components, but this character is generally forbidden as such and its disuse avoids confusion.

Globs enforce various rules regarding meta-characters, patterns, and component boundaries that reject nonsense expressions. While these rules can sometimes make glob expressions a bit more difficult to compose, they also make glob expressions more consistent, easier to reason about, and less prone to errors.

Patterns

Globs resemble Unix paths, but additionally support patterns that can be matched against paths and directory trees. Patterns use a syntax that resembles globbing in Unix shells and tools like git, though there are some important differences.

use wax::Glob;

let glob = Glob::new("**/*.{go,rs}").unwrap();
assert!(glob.is_match("src/lib.rs"));

Patterns form captures that can be used to extract matched text (as seen in many regular expression engines). In the above example, there are three patterns that can be queried for matched text: **/, *, and {go,rs}. Every glob expression has an implicit capture for the complete matched text.

Globs use a consistent and opinionated format and patterns are not configurable; the semantics of a particular glob are always the same. For example, * never matches across component boundaries. Components are an important part of paths and file system trees, and only the tree wildcard ** (see below) implicitly matches across them.

Wildcards

Wildcards match some amount of arbitrary text in paths and are the most fundamental pattern provided by globs (and likely the most familiar).

The zero-or-more wildcards * and $ match zero or more of any character within a component (never path separators). Zero-or-more wildcards cannot be adjacent to other zero-or-more wildcards. The * wildcard is eager and will match the longest possible text while the $ wildcard is lazy and will match the shortest possible text. When followed by a literal, * stops at the last occurrence of that literal while $ stops at the first occurence.

The exactly-one wildcard ? matches any single character within a component (never path separators). Exactly-one wildcards do not group automatically, so a pattern of contiguous wildcards such as ??? form distinct captures for each ? wildcard. An alternation can be used to group exactly-one wildcards into a single capture, such as {???}.

The tree wildcard ** matches any characters across zero or more components. This is the only pattern that implicitly matches across arbitrary component boundaries; all other patterns do not implicitly match across component boundaries. When a tree wildcard participates in a match and does not terminate the pattern, its captured text includes the trailing separator. If a tree wildcard does not participate in a match, then its captured text is an empty string.

Tree wildcards must be delimited by forward slashes or terminations (the beginning and/or end of an expression). Tree wildcards and path separators are distinct and any adjacent forward slashes that form a tree wildcard are parsed together. Rooting forward slashes in tree wildcards are meaningful and the glob expressions **/*.txt and /**/*.txt differ in that the former is relative (has no root) and the latter has a root.

If a glob expression consists solely of a tree wildcard, then it matches any and all paths and the complete contents of any and all directory trees, including the root.

Character Classes

Character classes match any single character from a group of literals and ranges within a component (never path separators). Classes are delimited by square brackets [...]. Individual character literals are specified as is, such as [ab] to match either a or b. Character ranges are formed from two characters separated by a hyphen, such as [x-z] to match x, y, or z. Character classes match characters exactly and are always case-sensitive, so the expressions [ab] and {a,b} are not necessarily the same.

Any number of character literals and ranges can be used within a single character class. For example, [qa-cX-Z] matches any of q, a, b, c, X, Y, or Z.

Character classes may be negated by including an exclamation mark ! at the beginning of the class pattern. For example, [!a] matches any character except for a. These are the only patterns that support negation.

It is possible to escape meta-characters like *, $, etc., using character classes though globs also support escaping via a backslash \. To match the control characters [, ], and - within a character class, they must be escaped via a backslash, such as [a\-] to match a or -.

Character classes have notable platform-specific behavior, because they match arbitrary characters in native paths but never match path separators. This means that if a character class consists of only path separators on a given platform, then the character class is considered empty and matches nothing. For example, in the expression a[/]b the character class [/] matches nothing on Unix and Windows. Such character classes are not rejected, because the role of arbitrary characters depends on the platform. In practice, this is rarely a concern, but such patterns should be avoided.

Character classes have limited utility on their own, but compose well with repetitions.

Alternations

Alternations match an arbitrary sequence of one or more comma separated sub-globs delimited by curly braces {...,...}. For example, {a?c,x?z,foo} matches any of the alternative globs a?c, x?z, or foo. Alternations may be arbitrarily nested and composed with repetitions.

Alternations form a single capture group regardless of the contents of their sub-globs. This capture is formed from the complete match of the sub-glob, so if the alternation {a?c,x?z} matches the path abc, then the captured text will be abc (not b). Alternations can be used to group captures using a single sub-glob, such as {*.{go,rs}} to capture an entire file name with a particular extension or {???} to group a sequence of exactly-one wildcards.

Alternations must consider adjacency rules and neighboring patterns. For example, *{a,b*} is allowed but *{a,*b} is not. Additionally, they may not contain a sub-glob consisting of a singular tree wildcard ** and cannot root a glob expression as this could cause the expression to match or walk overlapping trees.

Repetitions

Repetitions match a sub-glob a specified number of times. Repetitions are delimited by angle brackets with a separating colon <...:...> where a sub-glob precedes the colon and an optional bounds specification follows it. For example, <a*/:0,> matches the sub-glob a*/ zero or more

View on GitHub
GitHub Stars121
CategoryDevelopment
Updated6d ago
Forks12

Languages

Rust

Security Score

100/100

Audited on Mar 27, 2026

No findings