red_quilt 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +38 -0
- data/docs/api.md +11 -0
- data/lib/red_quilt/arena.rb +96 -0
- data/lib/red_quilt/block_parser.rb +22 -384
- data/lib/red_quilt/cli.rb +8 -2
- data/lib/red_quilt/code_block.rb +139 -0
- data/lib/red_quilt/document.rb +18 -4
- data/lib/red_quilt/footnote_anchors.rb +24 -0
- data/lib/red_quilt/footnote_pass.rb +6 -2
- data/lib/red_quilt/frontmatter.rb +54 -0
- data/lib/red_quilt/html_block.rb +161 -0
- data/lib/red_quilt/indentation.rb +35 -0
- data/lib/red_quilt/inline/builder.rb +9 -186
- data/lib/red_quilt/inline/emphasis_resolver.rb +184 -0
- data/lib/red_quilt/inline/url_sanitizer.rb +64 -0
- data/lib/red_quilt/line.rb +6 -1
- data/lib/red_quilt/lint_pass.rb +2 -2
- data/lib/red_quilt/node_ref.rb +20 -11
- data/lib/red_quilt/renderer/html.rb +32 -20
- data/lib/red_quilt/renderer/mdast.rb +11 -11
- data/lib/red_quilt/table.rb +97 -0
- data/lib/red_quilt/version.rb +1 -1
- data/lib/red_quilt.rb +19 -4
- data/sig/red_quilt.rbs +18 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56e09a19cfb78fad2a7e9a377f3ec6b9033d2160d4c126719e8220245b340709
|
|
4
|
+
data.tar.gz: 35bcb8de686345a72b9d2edf0261c7b201fb138047b571e3cc14b4f62be2671a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5ad64e49f61ddcca8c3453b28ab06e7c04d76da574d82e6185b9a175189f313e043dcb586ff4293ad801b6bbb521ecca902f4dd9cac630186db97a2d8de5b91
|
|
7
|
+
data.tar.gz: fc3764a74f06f1d902afd5eeba1e572ddb97f3fa1c9306f4de937e1929937d22830f856c77b03d2e1fc658be3310d7f7535cb994a7945cf7f5f18ea52e0416c4
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- `NodeRef#info`: returns the fence info string of a code block (e.g. `ruby`
|
|
13
|
+
in ` ```ruby `, or `vtt audio="x.mp3"`); `""` for code blocks without one
|
|
14
|
+
and for every other node type. The raw code content remains available via
|
|
15
|
+
`NodeRef#text`.
|
|
16
|
+
- `Renderer::HTML#render_fragment(nodes)`: renders an Array of `NodeRef` in
|
|
17
|
+
order and returns the HTML fragment without affecting the main render
|
|
18
|
+
output. Renderer state shared across nodes (e.g. the heading-id slugger) is
|
|
19
|
+
preserved between calls. Lets callers that partition a document render the
|
|
20
|
+
pieces separately without reaching into renderer internals.
|
|
21
|
+
- `Arena` semantic payload accessors (`heading_level`, `list_ordered?`,
|
|
22
|
+
`code_block_info`, `link_destination`, `footnote_number`, …) for callers
|
|
23
|
+
that walk `Document#arena`, replacing direct use of the raw `int1`/`str2`
|
|
24
|
+
columns.
|
|
25
|
+
|
|
26
|
+
## [0.7.2] - 2026-06-23
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Opt-in YAML frontmatter support via the `frontmatter:` option on `parse` /
|
|
31
|
+
`render_html` and the `--frontmatter` CLI flag (off by default). A leading
|
|
32
|
+
`---` … `---` block is removed from the rendered body and exposed as
|
|
33
|
+
`Document#frontmatter`; in standalone output its `title` / `lang` keys fill
|
|
34
|
+
in `<title>` / `<html lang>`.
|
|
12
35
|
- Opt-in Mermaid diagram support via the `mermaid:` option on `render_html` /
|
|
13
36
|
`Document#to_html` and the `--mermaid` CLI flag (off by default). Fenced
|
|
14
37
|
` ```mermaid ` code blocks render as `<pre class="mermaid">` containers; in
|
data/README.md
CHANGED
|
@@ -48,6 +48,7 @@ RedQuilt.render_html("Hi <em>tag</em>", allow_html: true)
|
|
|
48
48
|
| `extended_autolinks:` | `false` | GFM: linkify bare `http(s)://` / `www.` / email addresses |
|
|
49
49
|
| `footnotes:` | `false` | GFM footnotes (see below) |
|
|
50
50
|
| `lint:` | `false` | Collect lint diagnostics (empty links, missing image alt, heading-level skips) |
|
|
51
|
+
| `frontmatter:` | `false` | Parse leading YAML frontmatter (`---`) as metadata (see below) |
|
|
51
52
|
|
|
52
53
|
### Footnotes (opt-in)
|
|
53
54
|
|
|
@@ -100,6 +101,41 @@ RedQuilt.parse("```mermaid\ngraph LR\n A --> B\n```")
|
|
|
100
101
|
In standalone output each diagram is made interactive with
|
|
101
102
|
[svg-pan-zoom](https://github.com/bumbu/svg-pan-zoom) (loaded from a CDN).
|
|
102
103
|
|
|
104
|
+
### YAML frontmatter (opt-in)
|
|
105
|
+
|
|
106
|
+
`parse` / `render_html` accept `frontmatter:` to extract a leading YAML
|
|
107
|
+
frontmatter block (the `---` … `---` fences used by Jekyll, Hugo).
|
|
108
|
+
The block is parsed with `Psych.safe_load` and removed from the rendered body;
|
|
109
|
+
the parsed Hash is exposed as `Document#frontmatter`.
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
doc = RedQuilt.parse(<<~MD, frontmatter: true)
|
|
113
|
+
---
|
|
114
|
+
title: My Page
|
|
115
|
+
lang: ja
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
# Body
|
|
119
|
+
MD
|
|
120
|
+
doc.frontmatter # => {"title" => "My Page", "lang" => "ja"}
|
|
121
|
+
doc.to_html # => "<h1>Body</h1>\n" (frontmatter stripped)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
In standalone output the frontmatter's `title` / `lang` fill in `<title>` /
|
|
125
|
+
`<html lang>` when no explicit argument is given (explicit argument >
|
|
126
|
+
frontmatter > default):
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
doc.to_html(standalone: true)
|
|
130
|
+
# <html lang="ja"> … <title>My Page</title> …
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The feature is opt-in, so a bare `---` is never mistaken for frontmatter
|
|
134
|
+
unless `frontmatter: true` is passed. Frontmatter lines are blanked rather
|
|
135
|
+
than deleted, so body source spans and diagnostic line numbers stay relative
|
|
136
|
+
to the start of the file. Invalid YAML records a `:frontmatter` warning
|
|
137
|
+
diagnostic and leaves `Document#frontmatter` as `nil` without raising.
|
|
138
|
+
|
|
103
139
|
### Tilt integration
|
|
104
140
|
|
|
105
141
|
RedQuilt ships a [Tilt](https://github.com/jeremyevans/tilt) adapter.
|
|
@@ -186,6 +222,8 @@ redquilt --mermaid --open input.md
|
|
|
186
222
|
Dir.tmpdir when -o is not given)
|
|
187
223
|
--mermaid Render `mermaid` code blocks as diagrams (loads
|
|
188
224
|
mermaid.js from a CDN in standalone output)
|
|
225
|
+
--frontmatter Parse leading YAML frontmatter (---) as metadata;
|
|
226
|
+
fills <title>/lang in standalone output
|
|
189
227
|
--diagnostics Print diagnostics to stderr
|
|
190
228
|
--diagnostics-only Print diagnostics only (suppress output)
|
|
191
229
|
-h, --help Show help
|
data/docs/api.md
CHANGED
|
@@ -18,6 +18,7 @@ doc.source_map # Line/column lookup (lazy memoized)
|
|
|
18
18
|
doc.diagnostics # Array of RedQuilt::Diagnostic collected while parsing
|
|
19
19
|
doc.allow_html? # Check HTML pass-through setting
|
|
20
20
|
doc.disallow_raw_html? # Check GFM disallowed-raw-HTML filtering setting
|
|
21
|
+
doc.frontmatter # Parsed YAML frontmatter Hash, or nil (see below)
|
|
21
22
|
|
|
22
23
|
# Standalone document with an embedded theme:
|
|
23
24
|
doc.to_html(standalone: true, theme: :default, title: "My Doc", lang: "en")
|
|
@@ -27,6 +28,15 @@ doc.to_html(standalone: true, theme: :default, title: "My Doc", lang: "en")
|
|
|
27
28
|
# Render `mermaid` code blocks as <pre class="mermaid"> diagrams; in
|
|
28
29
|
# standalone mode the mermaid.js runtime is loaded from a CDN too.
|
|
29
30
|
doc.to_html(standalone: true, mermaid: true)
|
|
31
|
+
|
|
32
|
+
# Parse a leading YAML frontmatter block (--- ... ---). Off by default; when
|
|
33
|
+
# enabled the block is removed from the rendered body and exposed as a Hash.
|
|
34
|
+
doc = RedQuilt.parse("---\ntitle: Hi\nlang: ja\n---\n\n# Body", frontmatter: true)
|
|
35
|
+
doc.frontmatter # => {"title" => "Hi", "lang" => "ja"} (nil when absent/disabled)
|
|
36
|
+
# In standalone output frontmatter title/lang fill <title>/<html lang> unless
|
|
37
|
+
# an explicit argument overrides them. Invalid YAML adds a :frontmatter
|
|
38
|
+
# warning diagnostic and leaves doc.frontmatter as nil.
|
|
39
|
+
doc.to_html(standalone: true)
|
|
30
40
|
```
|
|
31
41
|
|
|
32
42
|
## NodeRef (AST node wrapper)
|
|
@@ -40,6 +50,7 @@ node.children # Array[NodeRef]
|
|
|
40
50
|
node.walk # Enumerator[NodeRef] or { |node| ... } block
|
|
41
51
|
node.find_all(:link) # Array[NodeRef] with matching type
|
|
42
52
|
node.text # String (concatenated child text)
|
|
53
|
+
node.info # String fence info of a code block (e.g. "ruby")
|
|
43
54
|
|
|
44
55
|
# Position information (byte offset)
|
|
45
56
|
node.source_span # SourceSpan with start_byte, end_byte
|
data/lib/red_quilt/arena.rb
CHANGED
|
@@ -231,6 +231,102 @@ module RedQuilt
|
|
|
231
231
|
@str2[id]
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
+
# --- Semantic payload accessors -------------------------------------
|
|
235
|
+
#
|
|
236
|
+
# int1..int3 / str1 / str2 are anonymous columns; their meaning is
|
|
237
|
+
# per-NodeType (see the class comment). These readers are the single
|
|
238
|
+
# source of truth for those conventions, so callers (renderers, the
|
|
239
|
+
# NodeRef wrapper, AST/MDAST export) never need to know which raw
|
|
240
|
+
# column a field lives in. Writers use add_node's keyword args,
|
|
241
|
+
# update_*, or a small set of typed writers (e.g.
|
|
242
|
+
# #resolve_footnote_definition) when the intent is worth naming.
|
|
243
|
+
#
|
|
244
|
+
# The reader is responsible for calling these only on the matching
|
|
245
|
+
# NodeType; on a mismatching node they return whatever the raw column
|
|
246
|
+
# holds (typically the 0 / nil default).
|
|
247
|
+
|
|
248
|
+
# HEADING: nesting level (1..6).
|
|
249
|
+
def heading_level(id)
|
|
250
|
+
@int1[id]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# LIST: ordered (`1.`) vs bullet (`-`).
|
|
254
|
+
def list_ordered?(id)
|
|
255
|
+
@int1[id] == 1
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# LIST: the start number of an ordered list (1 unless overridden).
|
|
259
|
+
def list_start(id)
|
|
260
|
+
@int2[id]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# LIST: tight (no blank lines between items) vs loose.
|
|
264
|
+
def list_tight?(id)
|
|
265
|
+
@int3[id] == 1
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# LIST: the item delimiter as authored (e.g. "-", "1.").
|
|
269
|
+
def list_delimiter(id)
|
|
270
|
+
@str1[id]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# TABLE_ROW: header row (rendered in <thead>) vs body row.
|
|
274
|
+
def table_row_header?(id)
|
|
275
|
+
@int1[id] == 1
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# TABLE_CELL: header cell (<th>) vs data cell (<td>).
|
|
279
|
+
def table_cell_header?(id)
|
|
280
|
+
@int1[id] == 1
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# CODE_BLOCK: the fence info string (e.g. 'ruby', 'vtt audio="x"').
|
|
284
|
+
def code_block_info(id)
|
|
285
|
+
@str2[id]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# LINK / IMAGE: destination URL.
|
|
289
|
+
def link_destination(id)
|
|
290
|
+
@str1[id]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# LINK / IMAGE: optional title attribute (nil/empty when absent).
|
|
294
|
+
def link_title(id)
|
|
295
|
+
@str2[id]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# FOOTNOTE_REFERENCE: the assigned footnote number.
|
|
299
|
+
def footnote_number(id)
|
|
300
|
+
@int1[id]
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# FOOTNOTE_REFERENCE: which occurrence of a repeated reference this is
|
|
304
|
+
# (1-based), used to give each backref a unique anchor.
|
|
305
|
+
def footnote_occurrence(id)
|
|
306
|
+
@int2[id]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# FOOTNOTE_REFERENCE / FOOTNOTE_DEFINITION: the author-written label.
|
|
310
|
+
def footnote_label(id)
|
|
311
|
+
@str1[id]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# FOOTNOTE_DEFINITION: total number of references to this footnote,
|
|
315
|
+
# materialized by FootnotePass so renderers can read it off the node
|
|
316
|
+
# instead of consulting the registry. (FOOTNOTE_REFERENCE reuses int2
|
|
317
|
+
# for its own occurrence index; see #footnote_occurrence.)
|
|
318
|
+
def footnote_total_references(id)
|
|
319
|
+
@int2[id]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# FOOTNOTE_DEFINITION: records the resolved number and total reference
|
|
323
|
+
# count onto the node (read back via #footnote_number /
|
|
324
|
+
# #footnote_total_references).
|
|
325
|
+
def resolve_footnote_definition(id, number, total_references)
|
|
326
|
+
@int1[id] = number
|
|
327
|
+
@int2[id] = total_references
|
|
328
|
+
end
|
|
329
|
+
|
|
234
330
|
# Returns a SourceSpan for the node, or nil when the node has no
|
|
235
331
|
# span (source_start < 0, meaning the content is held in str1).
|
|
236
332
|
def source_span(id)
|