>_EXECUTIVE SUMMARY
grcrs is a faithful Rust port of grc (Generic Colouriser 1.13). It ships the same two binaries as the original: grc, the launcher that matches a command line against grc.conf and pipes the command's output through the colouriser; and grcat, the colouriser that applies regexp/colour blocks to stdin line by line. The port keeps grc's config grammar (regexp / colours / count / command / skip / replace / concat) — plus a grcrs-only trend / key / metric extension that colours a numeric group by its delta across the stream — its loop directives (more, once, stop, block, unblock, previous), its XDG-aware search order, and its cross-line streaming-colour behaviour. Python-re idioms are translated where the dialects differ: \</\> become literal angle brackets, and \N replacement backrefs become ${N}. 1,020 production Rust lines + 360 test lines back the surface, with 31 tests (18 unit + 13 end-to-end) all green in CI on Linux and macOS.
grc+grcat)Source distribution — 1,380 Rust lines total
Production source: src/grcrs.rs lines 1–381 (the grc launcher) + src/grcatrs.rs lines 1–639 (the grcat colouriser). Test source: inline #[cfg(test)] modules in src/grcrs.rs (382–441) and src/grcatrs.rs (640–755) + the integration suite in tests/cli.rs (184 lines).
~SOURCE LAYOUT
Two Cargo binary targets, one integration test crate. The launcher is a small option-parser + process-orchestrator; the colouriser holds the config parser, the ANSI colour table, and the per-line painting engine.
| File | Lines | Tests | Role |
|---|---|---|---|
| src/grcrs.rs (prod) | 381 | — | grc launcher — getopt parser, grc.conf matching, stdout/stderr redirection, command spawn, grcat piping, pty mode, SIGINT handling |
| src/grcrs.rs (tests) | 60 | 7 | Option parsing, regexp translation |
| src/grcatrs.rs (prod) | 639 | — | grcat colouriser — ANSI colour table, Python-string unescape, config-block parser, backref conversion, per-line matching + painting engine with cross-line state |
| src/grcatrs.rs (tests) | 116 | 11 | Colour table, unescape, config parsing, backrefs |
| tests/cli.rs | 184 | 13 | End-to-end — spawn the built binaries, pipe stdin, assert on the coloured output and exit status |
| TOTAL Rust | 1,380 | 31 | All cargo test functions pass on Linux + macOS in CI |
Runtime config is not Rust: the vendor/grc submodule provides grc.conf and 83 colourfiles/conf.* files, installed to the search paths by the Homebrew formula.
$DEPENDENCIES
Two direct dependencies. The lock file resolves to 9 crates total. Selection criteria: the regexp engine has to support the lookahead/lookbehind that grc colourfiles use, and the process/pty/signal plumbing needs raw libc.
| Crate | Version | Role | Why |
|---|---|---|---|
| fancy-regex | 0.14 | Regexp engine | Backtracking engine with lookahead/lookbehind and backreferences — grc colourfiles use constructs the standard regex crate rejects. Drives both grc.conf matching and grcat line colouring. |
| libc | 0.2 | System calls | isatty (colour=auto), openpty (--pty), signal/SIG_IGN (forward Ctrl-C to the child), dup (stderr routing), strerror (grc-format error strings). |
&CONFIG RESOLUTION
Both binaries resolve config the way grc 1.13 does. grc finds grc.conf and reads regexp/colourfile pairs; grcat resolves a colourfile by name against a search path. XDG variables default to ~/.config and ~/.local/share when unset; the first existing, non-directory file wins.
grc.conf search
/etc → /usr/local/etc → /opt/homebrew/etc → $XDG_CONFIG_HOME/grc → ~/.grc. Each line is a regexp; the next line names the colourfile.
colourfile search
Empty prefix (honour absolute/relative -c names as-is) → $XDG_DATA_HOME/grc → $XDG_CONFIG_HOME/grc → ~/.grc → /usr/local/share/grc → /usr/share/grc → /opt/homebrew/share/grc.
Command-line join
grc joins argv with spaces into one string and matches it against each grc.conf regexp top to bottom — so patterns can key off subcommands and flags, not just the binary name.
Redirection resolution
Default: colourise stdout. -e switches to stderr and stops auto-redirecting stdout; -s forces stdout back on even with -e. Both can be piped through separate grcat children.
Colour gating
--colour=on|off|auto; auto = isatty(1). When colour is off or no colourfile matched, the command runs uncoloured with inherited stdio and its exit status is propagated verbatim.
grcat location
grc prefers a grcat sitting next to its own executable, falling back to grcat on PATH — so a source build and an installed build both find the right colouriser.
%PORT MAPPING — PYTHON → RUST
grc is Python; grcrs is Rust. The behaviour is preserved function-for-function, with translations only where the two regexp dialects and string models differ.
Regexp dialect
translate_regex rewrites Python's \</\> (literal angle brackets in Python re) to literals, because fancy-regex would otherwise read them as GNU word boundaries. Present in both binaries.
Colour literals
get_colour maps named tokens through the static ANSI table; a "..." token is unescaped exactly as a Python string literal — octal \033, hex \xNN, and the single-char escapes.
Replacement backrefs
convert_backrefs turns Python \1 into ${1} and escapes literal $ to $$, so replace= directives rewrite lines identically.
Config block parsing
parse_config mirrors grcat: blocks of keyword=value, terminated by a blank/comment/non-letter line; colors/color/colour fold to colours; unknown keywords error.
Per-line engine
The main loop reads bytes leniently (from_utf8_lossy — command output is not guaranteed UTF-8), applies each pattern with the count loop semantics, then flattens overlapping colour spans into a single ANSI-annotated line.
Cross-line state
prevcolour/prevcount/blockflag/blockcolour carry between lines so previous, block, and unblock behave as in grcat — multi-line constructs inherit the opening line's colour.
@CAPABILITY SURFACE
The launcher options and the colourfile grammar, grouped by role.
Launcher options
getopt-style; scanning stops at the command.
-e / --stderr— colourise stderr-s / --stdout— force stdout on-c NAME / --config=NAME— explicit colourfile--colour=on|off|auto--pty— run under a pseudo-terminal (experimental)
Colourfile keywords
One block per pattern.
regexp— the patterncolours— per-group colour listcount— loop directivecommand— runsh -con matchskip— drop the linereplace/concattrend/key/metric— grcrs delta colour
Loop directives
count= controls iteration + cross-line state.
more— keep matching (default)once— first match onlystop— halt all patternsblock/unblock— whole-line block colourprevious— inherit prior count
Colour tokens
Resolved through the static ANSI table.
- Attributes:
bold,underline,italic,reverse,dark,strikethrough… - FG/BG:
red…white,on_black… - Bright:
bright_*,on_bright_* - Streaming:
previous,unchanged
Process orchestration
Faithful stdio wiring.
- Pipe stdout and/or stderr to separate
grcatchildren - SIGINT ignored in the wrapper; Ctrl-C reaches the child
- Child exit status propagated
- grcat located next to
grcor on PATH
Encoding safety
Robust to non-UTF-8 command output.
- Lines read as bytes, decoded lossily
- Byte offsets mapped to char indices for span painting
- EPIPE on a closed downstream ends the stream cleanly
!TEST POSTURE
Three layers: launcher unit tests, colouriser unit tests, and end-to-end tests that spawn the real binaries. Aggregate: 31 tests pass under cargo test, green on Linux and macOS in CI.
| Suite | File | Tests | Coverage |
|---|---|---|---|
| Launcher unit | src/grcrs.rs | 7 | Option parsing (stops at command, long forms, attached/separate -c, clustered shorts + --pty, -- terminator, --colour), regexp translation |
| Colouriser unit | src/grcatrs.rs | 11 | Octal/hex/named unescape, angle-bracket translation, backref conversion, named/quoted/invalid colours, multibyte byte→char mapping, config parsing (colours+count+regex, comma groups, lookahead + US spelling, blocks without regexp, bad keyword) |
| End-to-end | tests/cli.rs | 13 | Match-only colouring, lookahead colouring, per-group layering, unchanged, count=stop, block across lines, replace-then-colour, quoted literals, angle escapes, skip, missing-config error, grc→grcat piping, exit-status propagation |
| TOTAL | cargo test | 31 | CI gate on every push to main & every PR (ubuntu-latest + macos-latest) |
#SHIP SURFACE
What you get from the Homebrew tap or a source build.
Binaries
grc (launcher) and grcat (colouriser) — two Cargo targets built from src/grcrs.rs and src/grcatrs.rs. Release profile: LTO + codegen-units = 1.
Runtime config
grc.conf + 83 colourfiles/conf.* from the vendor/grc submodule. The formula installs grc.conf to $(brew --prefix)/etc and the colourfiles to $(brew --prefix)/share/grc.
CI workflow
.github/workflows/ci.yml — fmt, clippy (-D warnings), doc (-D warnings), build + test on ubuntu + macos, and a binary smoke test. Runnable via workflow dispatch.
Release workflow
.github/workflows/release.yml — builds release artifacts.
Compatibility
Tracks grc 1.13: same option surface, same config grammar, same search order, same colour semantics. Existing grc colourfiles work unchanged.
License
GPL-2.0-or-later — the same license as upstream grc.
vVERSION HISTORY
Current version: grcrs 1.13.0 (tracking grc 1.13). 6 commits on main. Last five:
| Hash | Subject |
|---|---|
| 52d2edbaad | tests |
| 741696a2ba | grcat: fix clippy lints (is_some_and, if-let, collapse newline strip) |
| 6e57ee508b | CI |
| d41ec22817 | init |
| be525cfb2d | add vendor |
Full history: github.com/MenkeTechnologies/grcrs/commits/main
>>> PIPE IT IN. MATCH THE PATTERN. PAINT THE STREAM. <<<