A small pattern language for filtering lists of objects by their labels.
Visit Label Filter for more information.
| Implementation | Package | Conformance | Published |
|---|---|---|---|
| TypeScript | label-filter (npm) |
passes | not yet |
| Rust | label-filter (crates.io) |
passes | not yet |
| Python | label-filter (PyPI) |
passes | not yet |
| Go | module github.com/code-evolve/label-filter/go |
passes | yes |
| PHP | twelvetone/label-filter (Packagist) |
passes | not yet |
Every row above was established by running that implementation's suite against the shared fixture on 2026-09-23, as part of building this page. It is a report rather than a claim.
Next: Java port · The ports nobody has asked for yet: C#, Ruby, Swift, Kotlin
Five, and they are the same language rather than five similar ones.
| Language | Package | Requires |
|---|---|---|
| TypeScript | label-filter (npm) |
ESM, Node 18+ or any bundler |
| Rust | label-filter (crates.io) |
Rust 1.70+ |
| Python | label-filter (PyPI) |
Python 3.9+ |
| Go | module github.com/code-evolve/label-filter/go |
Go 1.12+ |
| PHP | twelvetone/label-filter (Packagist) |
PHP 7.4+ |
Some are not published yet. The names above are the intended ones; until a release happens the package is consumed locally, and a link here would be a 404 on the page whose job is to send you to it.
Every implementation has no runtime dependencies, and no dependencies in its test runner either.
import { compileLabelFilter } from 'label-filter'
const match = compileLabelFilter('invoice*|receipt*')
files.filter(match)
compileLabelFilter(pattern) returns match(label) => boolean. The other implementations follow the same shape under their own naming conventions — label_filter.compile_label_filter in Python, labelfilter.Compile in Go, label_filter::compile in Rust, LabelFilter::compile in PHP — and each answers the same two questions: why can this not be read, and does this label pass.
When the pattern cannot be read, error is a string and the matcher answers true for every label. A filter must never hide a row for a reason it cannot state, and "no results" is an answer a filter gives legitimately all the time — so an error that produced it would be invisible. This holds under \-\ as well: negation never inverts a refusal.
That gives a caller two different obligations, and the second is easy to miss:
error beside the list. Better still, keep the last good result and show the message — no flicker, no false empty, no false everything. Every pattern passes through unreadable states while it is typed: [0-9] is invalid at [, [0, [0- and [0-9.error sits on the matcher rather than the language throwing or returning an empty set: the caller has to answer what do I do when this cannot be read, and the answer differs for showing and for doing.| Pattern | Means |
|---|---|
apple |
contains, case-insensitive — the default |
apple*pie |
* is any run of characters, including none |
`apple |
anchored to the start |
apple` |
anchored to the end |
`apple` |
both — an exact match |
apple|pear |
either alternative |
[abc] |
one character from a set — [0-9], [a-z], [A-Za-z], [0-9A-F] |
\c\apple |
options prefix; c makes literals case-sensitive |
\* |
a backslash escapes a reserved character |
Six characters are reserved and nothing else is special: * | ` \ [ ].
foo[123]bar one character from 123 — ranges too: [0-9], [a-z], [A-Za-z], [0-9A-F]
`[123]bar beside an anchor, with nothing special required
*.||md|txt|| ||…|| groups alternatives, so a shared part is written once, not once each
`||a|b||` exactly a, or exactly b — an anchor reaches every branch of the group
\-\test not — hide every label the pattern finds
\b\app whole word — app, app-1, my app, app_data; not apple or snapple
\c-b\App options compose
A set is always case-sensitive, even in the default case-insensitive mode, because a set is the one construct that can say either case explicitly — [A-Za-z] — while nothing else could say this case only. \c\ governs literals.
A backtick is an anchor and nothing else: the first character, the last character, or a mistake.
A word boundary is the edge of the label or a neighbouring character that is not a letter or a number, so _ separates words — which a regular expression's own \b gets wrong for filenames.
\-\ is an exclusion filter: \-\a|b is NOT (a OR b). Apple but not test is deliberately two filters rather than an AND, because precedence would cost the language its first goal.
One fixture holds the whole contract: 73 cases lifted from numbered sections of the specification, 31 patterns that must be refused — with the refusal then obliged to match every label — and 43 fragments of junk that must not crash anything. 399 assertions, in one file, run by all of them.
A port is a translation of the reference implementation function for function, so the files read side by side; the fixture is what makes that claim checkable rather than aspirational. One command runs every implementation and prints one line each.
The reference implementation carries two suites the ports do not, because they test the language rather than an implementation of it: a property suite that walks every pattern up to a given length over the reserved alphabet and checks thousands of generated ones against an independently written oracle — a regular expression, which is precisely what the implementation refuses to be — and a performance suite with per-label ceilings, because a filter that is correct and slow is still broken.
They are the tie-breaker for every proposed change, and each one is recorded with what serves it and what strains it, so a future addition argues against the goals rather than against taste.