Markdown for GitHub¶
A table printed by a CI job reads well in the terminal and badly in a pull
request comment, where its box-drawing characters land in a proportional
font. rich_ext::markdown_export writes renderables as GitHub-flavoured
Markdown instead, so GitHub draws the table, the list and the code block
itself. No feature flag is needed.
let mut table = Table::new().title("Checks");
table.add_column("check");
table.add_column_justify("time", Justify::Right);
table.add_column_justify("result", Justify::Center);
table.add_row(&["lint", "1.2 s", "[bold]pass[/]"]);
table.add_row(&["unit | integration", "48 s", "[s]flaky[/]"]);
let mut tree = Tree::new("[bold]changed[/]");
tree.add("crates/rich-ext")
.add("[code]markdown_export.rs[/]");
let mut doc = GfmDocument::new();
doc.push_heading(2, "CI report")
.push(&table)
.push(&Rule::line())
.push(&tree)
.push_code("SELECT count(*) FROM runs;", "sql");
let comment = fit_github_comment(&doc.finish());
gives
## CI report
**Checks**
| check | time | result |
| --- | ---: | :---: |
| lint | 1.2 s | **pass** |
| unit \| integration | 48 s | ~~flaky~~ |
---
- **changed**
- crates/rich-ext
- `markdown_export.rs`
```sql
SELECT count(*) FROM runs;
```
GfmDocument collects blocks and separates them with blank lines; push
takes anything that implements ToGfm, and GfmExporter::export writes a
single value. Each renderable becomes:
| renderable | Markdown |
|---|---|
Table |
a table: header row, an alignment row from each column's justify, \| escaped, cell styles as inline Markdown; a title in bold above, a caption in italics below, a footer as the last row |
Tree |
nested lists; a hidden root lists its children, a collapsed node hides them |
Panel |
a blockquote with the title in bold, or a <details> block (below) |
Rule |
---, with its title in bold after it |
Text, a markup string |
paragraphs: line breaks become hard breaks, blank lines paragraph breaks |
Syntax (feature syntax) |
a fenced block with its language |
Renderables (a group) |
each item as its own block |
Markdown (feature markdown) |
its source, as it is |
Columns, and anything else through Plain or push_plain |
the plain rendering in a ```text fence |
push_markdown takes Markdown source as it is, and push(&markdown) writes
a core Markdown as the source it was parsed from. push_code writes a
fence, and push_heading a heading from console markup.
Styles¶
Styles become inline Markdown where Markdown has an equivalent: bold **,
italic *, strike ~~ and links [text](url). Colour, dim, underline and
reverse have none, so they are dropped. Inline code comes from style names:
a span styled code, markdown.code or inline_code (change the list with
GfmOptions::code_styles) becomes `code`, with a longer backtick fence
when the code holds backticks.
Text that Markdown would read as syntax is escaped: *, _, `, [,
<, |, & and the rest anywhere, and #, -, + and 1. at the start
of a line. Leading spaces become no-break spaces, so an indented line does
not turn into a code block.
Panels¶
A panel is a blockquote by default. PanelStyle::Details makes it a
collapsible block, which suits long logs in a comment:
let panel = || Panel::new(Box::new(Text::new("3 warnings\n1 note"))).title("Lint");
let quote = GfmExporter::new().export(&panel());
let details =
GfmExporter::with_options(GfmOptions::new().panel(PanelStyle::Details { open: false }))
.export(&panel());
A panel's title and subtitle are read from the panel. Its body is
written as paragraphs when it is a Text (or prints as one); any other
body is rendered and read back, and a table or another drawing stays a
drawing in a ```text fence inside the block.
How content is read¶
Tables, panels, rules and Markdown are read through core's read-only
accessors, which mirror upstream's public attributes: Table::columns
(each column's header, footer, justify and style), Table::rows (each
row's cells and style), get_title, get_caption, get_show_header
and get_show_footer; Panel::get_title, get_subtitle and
renderable; Rule::get_title; Markdown::markup. So every table is
written exactly, whatever its box, width or wrapping: a grid, a table
without edges and a cell of several lines (joined with <br>) included.
The alignment row comes from each column's justify (left is GitHub's
default ---, right ---:, centred :---:).
Header cells are written without their style (GitHub bolds headers
itself); body cells keep their own styles over the column's and row's; a
footer is written as the last row, in the table's footer_style. A cell
that is a renderable with no text is its plain rendering.
Only renderables that expose nothing are still rendered and read back, or
written plain: Columns, a panel's body that is not text, and anything
passed to Plain.
GitHub's limit¶
A comment holds at most 65 536 characters (GITHUB_COMMENT_LIMIT).
fit_github_comment returns Markdown that fits unchanged; longer Markdown is
cut at a line break, any code fence or <details> block left open is
closed, and a note says how much was kept:
truncate_markdown(markdown, limit) does the same for another limit.