// GRCRS — ENGINEERING REPORT

Faithful Rust port of grc (Generic Colouriser 1.13) · v1.13.0 · two binaries (grc + grcat) · fancy-regex engine · XDG-aware config search · 83 bundled colourfiles

>_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.

1,020
Production Rust Lines
31
Tests (Verified Passing)
1.13.0
Version (tracks grc 1.13)
2
Binaries (grc+grcat)
2
Direct Dependencies
83
Bundled Colourfiles
6
Git Commits

Source distribution — 1,380 Rust lines total

1,020 production / 360 test · 26.1% test code

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)381grc launcher — getopt parser, grc.conf matching, stdout/stderr redirection, command spawn, grcat piping, pty mode, SIGINT handling
src/grcrs.rs (tests)607Option parsing, regexp translation
src/grcatrs.rs (prod)639grcat 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)11611Colour table, unescape, config parsing, backrefs
tests/cli.rs18413End-to-end — spawn the built binaries, pipe stdin, assert on the coloured output and exit status
TOTAL Rust1,38031All 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.

CrateVersionRoleWhy
fancy-regex0.14Regexp engineBacktracking engine with lookahead/lookbehind and backreferences — grc colourfiles use constructs the standard regex crate rejects. Drives both grc.conf matching and grcat line colouring.
libc0.2System callsisatty (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 pattern
  • colours — per-group colour list
  • count — loop directive
  • command — run sh -c on match
  • skip — drop the line
  • replace / concat
  • trend / key / metric — grcrs delta colour

Loop directives

count= controls iteration + cross-line state.

  • more — keep matching (default)
  • once — first match only
  • stop — halt all patterns
  • block/unblock — whole-line block colour
  • previous — inherit prior count

Colour tokens

Resolved through the static ANSI table.

  • Attributes: bold, underline, italic, reverse, dark, strikethrough
  • FG/BG: redwhite, on_black
  • Bright: bright_*, on_bright_*
  • Streaming: previous, unchanged

Process orchestration

Faithful stdio wiring.

  • Pipe stdout and/or stderr to separate grcat children
  • SIGINT ignored in the wrapper; Ctrl-C reaches the child
  • Child exit status propagated
  • grcat located next to grc or 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.

SuiteFileTestsCoverage
Launcher unitsrc/grcrs.rs7Option parsing (stops at command, long forms, attached/separate -c, clustered shorts + --pty, -- terminator, --colour), regexp translation
Colouriser unitsrc/grcatrs.rs11Octal/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-endtests/cli.rs13Match-only colouring, lookahead colouring, per-group layering, unchanged, count=stop, block across lines, replace-then-colour, quoted literals, angle escapes, skip, missing-config error, grcgrcat piping, exit-status propagation
TOTALcargo test31CI 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:

HashSubject
52d2edbaadtests
741696a2bagrcat: fix clippy lints (is_some_and, if-let, collapse newline strip)
6e57ee508bCI
d41ec22817init
be525cfb2dadd vendor

Full history: github.com/MenkeTechnologies/grcrs/commits/main


>>> PIPE IT IN. MATCH THE PATTERN. PAINT THE STREAM. <<<