Skip to main content

Enhancing the eg command with custom substitutions

Turning eg into a cheat sheet documentation system that's both highly functional and visually refined

Why customise eg?

eg is billed as a tool for quick command-line examples—a faster, more focused alternative to man pages. However, I've found it equally valuable as a foundation for comprehensive CLI cheat sheets. The challenge? As these cheat sheets grow more verbose, they need enhanced styling to remain scannable and legible.

This article chronicles the journey of transforming eg's output through custom substitutions, creating a personalised documentation system that's both functional and visually refined.

eg in Kitty terminal

The setup

Our environment consists of:

  • eg - The examples tool
  • bat - Syntax-highlighted pager
  • Kitty - GPU-accelerated terminal with the Twilight theme

The Twilight theme provides a muted dark palette that we'll leverage for our styling decisions:

background #141414
foreground #feffd3
color0 #141414   # Black
color2 #afb979   # Green (our base for comments)
color8 #262626   # Bright black/grey
# ... additional colours

Understanding display order

Before diving into customisation, there's a critical behaviour to understand: eg does not provide a configuration option for controlling display order. Files are always shown in this sequence:

  1. Custom directory files (if they exist)
  2. Examples directory files (if they exist)

Knowing this, we can strategically organise our content:

[eg-config]
custom-dir = ~/dotfiles/eg/custom        # Public cheat sheets (shown first)
examples-dir = ~/dotfiles/_private/eg/examples  # Private notes (shown second)

This arrangement means generic, shareable cheat sheets appear first, followed by private, context-specific notes. To visually separate these sections, private files begin with a PRIVATE marker that we'll transform via substitution.

The critical insight: when substitutions run

Here's the key to understanding eg substitutions: they run after colourisation.

The processing pipeline is:

  1. Colour - ANSI escape sequences are added to the text
  2. Squeeze - Blank lines are reduced (if enabled)
  3. Substitutions - Your custom regex patterns are applied

This means if you want to match a heading like ## Basic Syntax, you can't use a simple pattern like ^## (.*)$. By the time your substitution runs, that text has been transformed into:

^[[38;5;245m##^[[0m^[[31m^[[1m Basic Syntax^[[0m

Your patterns must account for these embedded ANSI escape sequences.

Essential debugging: making the invisible visible

To see what you're actually matching, use this command:

eg kitty | cat -v | head -50

This reveals the escape sequences as visible text:

  • ^[[38;5;245m - Start colour 245 (grey)
  • ^[[0m - Reset to default
  • ^[[1m - Bold
  • ^[[3m - Italic

In regex patterns, ^[ becomes \x1b\\[ (the ESC character + literal bracket).

Building substitutions: a chronological journey

Step 1: cleaner comments

The problem: Comments like # Create new tab are visually cluttered. The hash symbol adds noise, and the colour (inherited from code blocks) doesn't distinguish comments from commands.

Inspecting the reality:

eg kitty | grep "# Create new tab" | cat -v

Output:

^[[36m^[[1m^[[0m^[[38;5;143m# Create new tab^[[0m

The text is wrapped in:

  • Cyan bold reset sequence
  • Code colour 143 (yellowish-green)
  • The comment text with hash
  • Reset

The solution:

format-standalone-comments = ['^(\\s+)\x1b\\[36m\x1b\\[1m\x1b\\[0m\x1b\\[38;5;143m# ([^\x1b]+)\x1b\\[0m$', '\\1\x1b[3m\x1b[38;5;245m\\2\x1b[0m', True]

This pattern:

  • Matches the leading whitespace and escape sequences
  • Captures the comment text after the hash (note: # with space)
  • Replaces with italic + grey colour (245) + the text

Result: # Create new tab becomes an italicised, grey Create new tab without the hash.

Step 2: inline comments

The problem: Command lines with trailing comments like C-S-t # New tab still show the hash symbol.

The solution:

format-comments-no-hash = ['(\S +)#( +)(.*?)$', '\\1\\2\x1b[3m\x1b[38;5;245m\\3\x1b[0m', True]

This works on the already-colourised text but targets a different pattern—text that has non-whitespace before the hash. It:

  • Captures everything before the hash
  • Captures the spaces after the hash
  • Applies italic grey to the comment portion

Note: The hash itself is in the pattern but not in a capture group, so it's removed.

Result: C-S-t # New tab becomes C-S-t New tab with the comment portion in grey italics.

Step 3: visual hierarchy with h2 separators

The problem: Long cheat sheets with multiple ## Section headings need visual breaks for scannability.

Inspecting a heading:

eg kitty | grep "^##" | cat -v

Output:

^[[38;5;245m##^[[0m^[[31m^[[1m Basic Syntax^[[0m

The solution:

separator-h2 = ['^\x1b\\[38;5;245m(##)\x1b\\[0m\x1b\\[31m\x1b\\[1m([^\x1b]+)\x1b\\[0m$', '\x1b[38;5;245m\\1\x1b[0m\x1b[31m\x1b[1m\\2\x1b[0m\n\x1b[38;5;245m···········································································\x1b[0m', True]

This pattern:

  • Captures the pound-coloured ##
  • Captures the red-bold heading text
  • Reconstructs the heading exactly as it was
  • Adds a newline and a grey separator line

Result: Each H2 heading gains an underline, creating clear visual sections.

Step 4: markdown bold to ansi bold

The problem: Markdown **text** appears literally rather than as bold text.

The solution:

markdown-bold = ['\*\*([^*]+)\*\*', '\x1b[1m\\1\x1b[0m', True]

This runs early in the alphabetical order (starts with 'm'), so it processes the raw markdown before most colourisation affects it:

  • \*\* - Matches literal ** (escaped asterisks)
  • ([^*]+) - Captures the text between asterisks
  • Replaces with ANSI bold codes

Result: **Always use quotes** becomes actual bold terminal text.

Step 5: visual breaks between sections

The problem: The transition from public to private cheat sheets needs emphasis.

The solution: Two complementary substitutions:

separator-dash = ['^---$', '\x1b[38;5;245m···········································································\x1b[0m', True]

separator-private = ['^PRIVATE$', '\n\n\x1b[38;5;245m╾───────────────────────────────────────────────────────╼ PRIVATE ╾───────────────────────────────────────────────────────╼\x1b[0m\n', True]

The first converts simple --- markers into subtle grey separators. The second transforms the PRIVATE marker into a prominent divider with Unicode box-drawing characters.

Step 6: enhanced backticks

The problem: Inline code wrapped in backticks should stand out more.

The solution: Modify the colour definition itself:

[color]
backticks = '\x1b[38;5;214m\x1b[1m'

Then remove the backticks with a substitution:

remove-backticks = ['`', '', False]

This two-step approach colours backticked text in bold orange (214), then removes the backtick characters themselves.

Substitution execution order

Substitutions run alphabetically by name. This matters when one substitution depends on another's output. Our current order:

  1. format-comments-no-hash - Processes inline comments
  2. format-standalone-comments - Processes standalone comment lines
  3. markdown-bold - Converts markdown bold
  4. remove-backticks - Strips backtick characters
  5. separator-dash - Converts --- to decorative lines
  6. separator-h2 - Adds underlines to H2 headings
  7. separator-private - Converts PRIVATE marker

Tip

Strategic naming ensures substitutions run in the intended sequence.

The complete configuration

Here's the final egrc:

[eg-config]
custom-dir = ~/dotfiles/eg/custom
examples-dir = ~/dotfiles/_private/eg/examples
pager-cmd = 'bat -l markdown --color=never --paging=always --style=plain'
squeeze = false

[color]
pound = '\x1b[38;5;245m'        # Grey for # symbols
code = '\x1b[38;5;143m'         # Yellow-green for code
backticks = '\x1b[38;5;214m\x1b[1m'  # Bold orange for inline code

[substitutions]
format-comments-no-hash = ['(\S +)#( +)(.*?)$', '\\1\\2\x1b[3m\x1b[38;5;245m\\3\x1b[0m', True]
format-standalone-comments = ['^(\\s+)\x1b\\[36m\x1b\\[1m\x1b\\[0m\x1b\\[38;5;143m# ([^\x1b]+)\x1b\\[0m$', '\\1\x1b[3m\x1b[38;5;245m\\2\x1b[0m', True]
markdown-bold = ['\*\*([^*]+)\*\*', '\x1b[1m\\1\x1b[0m', True]
remove-backticks = ['`', '', False]
separator-dash = ['^---$', '\x1b[38;5;245m···········································································\x1b[0m', True]
separator-h2 = ['^\x1b\\[38;5;245m(##)\x1b\\[0m\x1b\\[31m\x1b\\[1m([^\x1b]+)\x1b\\[0m$', '\x1b[38;5;245m\\1\x1b[0m\x1b[31m\x1b[1m\\2\x1b[0m\n\x1b[38;5;245m···········································································\x1b[0m', True]
separator-private = ['^PRIVATE$', '\n\n\x1b[38;5;245m╾───────────────────────────────────────────────────────╼ PRIVATE ╾───────────────────────────────────────────────────────╼\x1b[0m\n', True]

Design principles

The resulting output embodies several deliberate choices:

Visual hierarchy through colour

  • Grey (245) for secondary information (comments, separators)
  • Yellow-green (143) for primary content (commands)
  • Orange (214) for emphasis (inline code)
  • Red bold for section headings

Typography for scannability

  • Italics distinguish comments from commands
  • Bold highlights important terms
  • Underlines create clear section breaks

Noise reduction

  • Hash symbols removed from comments
  • Backticks removed from inline code
  • Clean, uncluttered presentation

Semantic consistency

  • Comments always appear in italic grey
  • Code blocks maintain consistent colouring
  • Separators use the same visual language

Pattern reference

For creating your own substitutions, here are the common patterns from our configuration:

Matching escape sequences:

\x1b\\[38;5;NNNm    # 256-colour N
\x1b\\[0m           # Reset
\x1b\\[1m           # Bold
\x1b\\[3m           # Italic

Capturing text between escapes:

([^\x1b]+)          # Any text until next escape sequence

Common capture patterns:

(\\s+)              # Whitespace
(\S+)               # Non-whitespace
(.*?)               # Any text (non-greedy)

Conclusion

What began as a quest for better comment styling evolved into a comprehensive customisation of eg's output. The key insights:

  1. Substitutions operate on colourised text, not raw markdown
  2. Use cat -v to reveal the actual text structure
  3. Alphabetical naming controls execution order
  4. Strategic colour choices create visual hierarchy

The result is a documentation system that's both highly functional and visually refined—proving that command-line tools can be as carefully designed as any GUI application.

For those wanting to dive deeper into the technical aspects of eg substitutions, including the complete processing pipeline and advanced pattern-matching techniques, a comprehensive guide will follow in a separate article.