App-mdee
view release on metacpan or search on metacpan
This allows selective markup inside headings (e.g., bold but not code).
Links are always processed before headings (in `@protect_steps`), so
OSC 8 hyperlinks in headings remain clickable regardless of
`heading_markup`.
The `colorize()` loop calls `$step->active` and `$step->run`:
```perl
for my $name (build_pipeline()) {
my $step = $colorize{$name};
$step->run if $step->active;
}
```
### Code & Emphasis Color Labels
Code-related and emphasis-related theme keys map directly to module labels:
| Theme Key | Module Label | Description |
|-----------|-------------|-------------|
| `code_mark` | `code_mark` | Fenced code block delimiters (```` ``` ````, `~~~`) |
| `code_tick` | `code_tick` | Inline code backticks (`` ` ``) |
| `code_info` | `code_info` | Fenced code block info string |
| `code_block` | `code_block` | Fenced code block body (with `;E`) |
| `code_inline` | `code_inline` | Inline code body (without `;E`) |
| `emphasis_mark` | `emphasis_mark` | Emphasis markers (`**`, `*`, `__`, `_`, `~~`) |
| - | `meta_mark` | Front matter delimiter lines (`L15/L10E` / `L10/L15E`) |
| - | `meta_block` | Front matter body (`L25/L12E` / `L00/L13E`) |
`emphasis_mark` is the default color for all emphasis markers. Per-type overrides
(`bold_mark`, `italic_mark`, `strike_mark`) can be defined via `--cm` and take
priority when present. This follows the same pattern as `code_tick` / `code_inline`.
```bash
# Light mode
[code_mark]='L20'
[code_tick]='L15/L23'
[code_info]='${base_name}=y70'
[code_block]='/L23;E'
[code_inline]='L00/L23'
[emphasis_mark]='L18'
# Dark mode
[code_mark]='L10'
[code_tick]='L15/L05'
[code_info]='L10'
[code_block]='/L05;E'
[code_inline]='L25/L05'
[emphasis_mark]='L10'
```
The `code_block` label includes `;E` (erase line) for full-width background on fenced code blocks. `code_tick` has background color matching `code_inline` for visual continuity, with dimmer foreground. `code_inline` has explicit foreground (`L00`/`L2...
Inline code backticks are displayed as `` `content´ `` using `code_tick` color for the markers. Multi-backtick delimiters (``` `` ```, ```` ``` ````, etc.) are collapsed to a single pair, with optional surrounding spaces stripped per CommonMark. The...
Regex patterns used by the md module:
Fenced code blocks ([CommonMark](https://spec.commonmark.org/0.31.2/#fenced-code-blocks)):
```
^ {0,3}(?<bt>(?<fc>[`~])\g{fc}{2,}+)(.*)\n((?s:.*?))^ {0,3}\g{bt}\g{fc}*+\h*$
```
The closing fence is the same character as the opening one and at least as long, followed only by spaces or tabs; it is shown as written. The `code_blocks` step uses the same rule with numbered captures. A fence that is never closed is not treated ...
ATX headings ([CommonMark](https://spec.commonmark.org/0.31.2/#atx-headings)), for each level `$hdr` from `######` down to `#`: `^( {0,3}$hdr(?:\h+.*)?)$`. Up to 3 spaces of indentation are allowed, and a line of `#`s alone is an empty heading; `#fo...
Inline code ([CommonMark Code Spans](https://spec.commonmark.org/0.31.2/#code-spans)) uses the `$CODE` pattern, which matches both single and multi-backtick spans. For multi-backtick (2+), optional leading/trailing spaces are stripped per CommonMark ...
### Front Matter
Front matter at the top of the file (`front_matter()`: TOML between `+++` lines, YAML from `---` to `---` or `...`, not with a blank line right after the opening `---`, as pandoc defines it) is shown as a fenced code block: the first step, `front_mat...
### Control Characters in the File
`begin()` first calls `remove_controls`, so that a Markdown file from someone else cannot send anything but colors to the terminal: OSC (title, clipboard, hyperlink), DCS, CSI other than SGR, other escape sequences, C0 controls except `\t \n \f \r`, ...
### The `--begin` Option of the md Module
`--begin FUNCTION` before `--` is an option of the md module (`deal_with`, `@opt_begin`), like `--cm`: `begin()` calls each function first, with the text in `$_`, before `refill`, `colorize` and `format_table`. The `--begin` of greple after `--` is t...
### The `widen` Function (experimental)
`App::Greple::md::widen::widen` is a function for `--begin` (`-t widen`; `chars=2460-24FF:3248-324F` as a parameter). The theme lists the characters in the array `widen_chars` (with `+=`, one entry per line with a comment) and passes them as `chars`,...
### Text Folding (md module)
Text folding is handled within the `App::Greple::md` module using `-Mtee` to pipe matched regions through `ansifold`. The md module defines `--fold-by` as a greple option in its `__DATA__` section, and `--fold` is dynamically defined in `finalize()` ...
#### Fold Architecture
- `fold` config parameter (default: 0) enables/disables folding
- `fold_width` config parameter (default: 80) controls the fold width
- `fold_runin`/`fold_runout` config parameters (default: 4, the ansifold default) are the run-in and run-out widths for the line breaking of folded lines; mdee passes its own `--runin`/`--runout` values (default 4 as well), so the folded lines run in...
- `finalize()` defines `--fold` as `--fold-by $fold_width $fold_runin $fold_runout` via `$mod->setopt()` (for direct command-line use); `--fold-by` takes the three values as `$<shift>` arguments
- When `fold=1` in config, `finalize()` adds the same `--fold-by ...` to the module's default option
- mdee passes `fold=1,fold_width=$width` in config when fold is enabled
- The `--fold-by` option in `__DATA__` uses `-Mtee "&ansifold"` with `--exclude` patterns for code blocks, HTML comments, and tables. The lines it folds are `{LIST_ITEM}`, `{DEFINITION}`, `{QUOTE_LINE}` (a line starting with up to three spaces and on...
- Formatted tables are excluded from folding by `--exclude &__PACKAGE__::table_regions`: `table_regions()` builds its pattern from the config at run time and matches three or more consecutive lines that start with a border/rule character, a dash, whi...
#### Definition List Pattern
`(?:\A|\G\n|\n\n).+\n\n?(:\h+.*\n)`
- `(?:\A|\G\n|\n\n)`: Start of file, or after previous match, or after blank line
- `.+\n`: Term line
- `\n?`: Optional blank line between term and definition
- `(:\h+.*\n)`: Capture group for definition line (only this part is processed)
#### Table Formatting in md Module
Table formatting is handled within the `App::Greple::md` module's `begin()` function, which orchestrates `colorize()` (syntax highlighting) followed by `format_table()`:
```perl
sub begin {
colorize() if $config->{colorize};
format_table() if $config->{table};
}
```
Stage execution is controlled by config flags: `colorize` (default: 1), `table` (default: 1). Fold is not controlled in `begin()` because it operates via greple's pattern matching pipeline (`-Mtee`), not text transformation in the `begin` hook.
### Paragraph Refill (md module)
`--refill` (`-r`; config `refill=1`; `default[refill]` in config.sh; off by default) joins the lines of each paragraph in the original text. mdee does not decide itself which lines form a paragraph: `refill()`, called first in `begin()`, asks `App::G...
- `cmark_blocks()` walks the CommonMark node tree and takes `get_start_line`/`get_end_line` of every paragraph node, with the block-quote depth. libcmark has no GFM tables and no definition lists, so it reports them as paragraphs; `refill_block` keep...
- `mdperl_blocks()` has no public API to lean on: Markdown::Perl renders HTML only. Its block parser pushes each paragraph line in `_do_paragraph` and closes the paragraph in `_finalize_paragraph`, and keeps the offset of the current line in `last_po...
- `pandoc_blocks()` collects every `data-pos`: pandoc wraps a block in a `Div` whose `data-pos` is `L:C-L:C` (several segments separated by `;` when it consumed markers, as for a list item's continuation lines; a table's segments are not in order), b...
- `refill()` takes the `Para`/`Plain` blocks from the bottom up (so the line numbers above stay valid) and replaces their lines with `refill_block(\@lines, $depth)`, which joins as ansifold's `fill_up` does (nothing after fullwidth punctuation or bet...
- The final line numbers of the joined blocks go to `@refill_lines`. `format_table` records `[start line, lines added]` in `@table_shift`, because wrapped cells and `table_style=simple` change the line count (colorize keeps it); the shift is applied ...
- `--autoindent` in `--fold-by` decides the prefix of the continuation lines, with `--keepindent=named` (App::ansifold 1.36): the quote markers are in the named group `(?<q>...)`, with the SGR sequences the `blockquotes` step puts around them, and ar...
- `refill_block` keeps the break on both sides of a table-row line and treats a CR before the line break (CRLF file) as part of the line break. `refill()` leaves front matter at the top alone (config `front_matter`, default 1; `0` joins its lines as ...
- When fold is off (filter, raw, `--no-fold`) a joined paragraph is one long line.
- Tests: `t/08_refill_join.t` (unit tests of `refill_block`, no parser needed), `t/10_blocks.t` (both parsers give the same lines on edge cases) and `t/09_refill.t` (greple runs, repeated for each parser that `available()` returns; `skip_all` without...
### Field Visibility with --show Option
The `--show` option controls field visibility using a hash type with callback:
```bash
declare -A show=()
declare -A OPTS=(
[ show | %! # field visibility ]=
)
show() {
local arg=$2 key val
if [[ $arg == *=* ]]; then
key=${arg%%=*} val=${arg#*=}
else
key=$arg val=1
fi
if [[ $key == all ]]; then
for k in "${show_fields[@]}"; do
show[$k]=$val
done
fi
}
```
- `%!`: Hash type with callback
- Callback receives `($optname, $arg)` - parse key=value from `$arg`
- `all` is special: sets all fields to the given value
- Order matters: `--show all= --show bold` disables all, then enables bold
- Individual key=value is automatically handled by getoptlong.sh
In `run_greple()`, show values are passed to the md module:
```bash
for name in "${!show[@]}"; do
[[ $name == all ]] && continue
md_opts+=(--show "${name}=${show[$name]}")
done
```
The md module's `active()` function checks show flags and skips regex substitutions entirely for disabled fields.
### Emphasis Patterns (CommonMark)
Bold/italic/strike patterns use `$SKIP_CODE` as first alternative to skip code spans. Named captures `(?<m>...)` (markers) and `(?<t>...)` (content) with `\g{m}` backreference prevent mixing (`**...__`).
- `(?<![\\\w])` / `(?!\w)`: Word boundaries for `_`/`__` (prevents `foo_bar_baz`)
- `(?<!\*)` / `(?!\*)`: Distinguishes `*italic*` from `**bold**`
- `flanking($delim)`: returns the opener and closer patterns of `*` or `**`, left-flanking and right-flanking as in CommonMark (so `L*=50, a*=68` is not italic, while `2*3*4` is), or by the markdown-cjk-friendly rules with `cjk_friendly=1`; see "Flan...
- `mark_color($type, $text)`: Uses `${type}_mark` if in colormap, falls back to `emphasis_mark`
- `/p` flag with `${^MATCH}`: Safe alternative to `$&` (used by `$SKIP_CODE`)
### OSC 8 Hyperlinks
Links are converted to OSC 8 terminal hyperlinks. Three types (in processing order): `image_link` (`[](url)`), `image` (``), `link` (`[text](url)`). Each is colored, wrapped in OSC 8, and protected. Disable with `config(osc8=0)`...
URL encoding: `uri_escape_utf8($url, "^\\x20-\\x7e")` â escapes only non-ASCII, preserving `:`, `/`, etc.
#### Code Span Pattern and `(*SKIP)(*FAIL)` Protection
`$CODE` matches code spans (named captures `_bt`, `_content`). `$SKIP_CODE = qr{$CODE (*SKIP)(*FAIL)}` is used as first alternative in link/emphasis substitutions to skip code spans. Named captures required â `$SKIP_CODE` consumes `$1`. Link patter...
#### Link Text Matching Pattern
`$LT = qr/(?:`[^`\n]*+`|\\.|[^`\\\n\]]++)+/` â three branches: backtick-span (allows `]` inside), backslash-escape (`\]`), any other char. Possessive quantifiers prevent backtracking.
### Mode Detection with [Getopt::EX::termcolor](https://metacpan.org/pod/Getopt::EX::termcolor)
Terminal background luminance (0-100) is detected via OSC 11 query. Luminance < 50 â dark mode, ⥠50 â light mode.
When `TERM_LUMINANCE` is set, `detect_terminal_mode` uses it and does not run the query. `t/01_mdee.t` sets it to 100, because the reply of a busy terminal can arrive after termcolor's 0.1 s timeout and then shows up as typed input (`^[]11;rgb:...`).
### [Getopt::Long::Bash](https://metacpan.org/pod/Getopt::Long::Bash) (getoptlong.sh)
Option parsing uses getoptlong.sh from Getopt::Long::Bash module.
#### OPTS Array Format
```bash
declare -A OPTS=(
[&REQUIRE]=0.7.1 [&USAGE]="$USAGE"
[ option | o : # comment ]=default
)
```
Key format: `option_name | short_opt spec # comment`
- `option_name`: Long option name (becomes variable `$option_name`)
- `short_opt`: Single character short option
- `spec`: Option specification
- `# comment`: Description for help
#### Option Specifications
|Spec|Type|Description|
|-|-|-|
|(empty)|Boolean|Flag, sets variable to 1. `--no-` prefix supported.|
|`!`|Callback|Calls function with same name as option|
|`+`|Incremental|Counter, increases with each use|
|`:`|Required arg|Requires argument value|
|`:=i`|Integer|Requires integer argument|
|`?!`|Optional+Callback|Optional argument with callback|
|`%`|Hash|Hash variable, `--opt key=value`|
|`%!`|Hash+Callback|Hash variable with callback|
|`:>array`|Array append|Appends `--option=value` to named array|
( run in 1.112 second using v1.01-cache-2.11-cpan-99fa8243a42 )