red_quilt 0.7.0 → 0.7.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45de1415fe8cc64323b390d8cedc4d6691fa6c0ab17337fac8aa956cef8bd107
4
- data.tar.gz: c3392372dd65fe74149678dbb9b5ebc8cc389b6f44ca4b0013e6eadfd565568d
3
+ metadata.gz: 689739c1f6cf971cbeaacdbb65b50fcf7600cecef38bc25aa607ab5e87a559e5
4
+ data.tar.gz: 2d57a5a5993bfb8352c18ec8c6da5dc06169d59198e72870399c85d4cc1e1769
5
5
  SHA512:
6
- metadata.gz: c7f3c0226e9a2e2cac06273147dfd497f45566ba99d766cee30ccd6fbff728669ab1d9ab112c93de33cce405e2cea9c8789e4f431a70c10d546e2bc2309c8c5b
7
- data.tar.gz: 69cae77089c64fbf8821cc6340e0c0ad8749004fbf03ec5acf1b55daa388404db5a64a276f48267ab9e58a66a21eb2892b9f120b942e105930862c94e29de7fe
6
+ metadata.gz: b3f3b90749d7307db9121d9f1af968a72e94cad5aa06a3f9cf54505ff047e482246f5aa2c0fa14f1b74d9b3d03b516629b3f1c4e57e5ecb6cf5d171d4bc2b6f6
7
+ data.tar.gz: f8a8929d8075e0c9e3ec32145daac57ca4565ff299bed3f119faee01f5253727d44acea4dacac579ee676715138e4f03f16e0555544b4b25ee1e30c779db1de5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Opt-in Mermaid diagram support via the `mermaid:` option on `render_html` /
13
+ `Document#to_html` and the `--mermaid` CLI flag (off by default). Fenced
14
+ ` ```mermaid ` code blocks render as `<pre class="mermaid">` containers; in
15
+ standalone output the mermaid.js runtime is loaded from a CDN and each
16
+ diagram is made interactive (wheel zoom, drag pan, +/-/reset controls) with
17
+ svg-pan-zoom.
18
+
19
+ ## [0.7.1] - 2026-06-06
20
+
21
+ ### Added
22
+
23
+ - `--open` CLI flag: render the Markdown to a standalone HTML file and open it
24
+ in the default browser (forces `--standalone`; writes under `Dir.tmpdir`
25
+ when `-o` is not given).
26
+
8
27
  ## [0.7.0] - 2026-05-29
9
28
 
10
29
  ### Added
data/README.md CHANGED
@@ -75,14 +75,31 @@ doc.diagnostics.first.severity # => :warning
75
75
  ### Heading anchors (opt-in)
76
76
 
77
77
  `render_html` / `to_html` accept `heading_ids:` to give every heading a
78
- slugified `id` for anchor links. Slugs follow GitHub's scheme but keep Unicode
79
- intact, so Japanese headings stay readable; duplicates get `-1`, `-2` suffixes.
78
+ slugified `id` for anchor links.
80
79
 
81
80
  ```ruby
82
81
  RedQuilt.render_html("# Hello World\n\n## はじめに", heading_ids: true)
83
82
  # => "<h1 id=\"hello-world\">Hello World</h1>\n<h2 id=\"はじめに\">はじめに</h2>\n"
84
83
  ```
85
84
 
85
+ ### Mermaid diagrams (opt-in)
86
+
87
+ `render_html` / `to_html` accept `mermaid:` to render ` ```mermaid ` fenced
88
+ code blocks for [mermaid.js](https://mermaid.js.org/).
89
+ In standalone output the mermaid.js runtime is also loaded from a CDN.
90
+
91
+ ```ruby
92
+ RedQuilt.render_html("```mermaid\ngraph LR\n A --> B\n```", mermaid: true)
93
+ # => "<pre class=\"mermaid\">graph LR\n A --&gt; B\n</pre>\n"
94
+
95
+ # Full page that renders the diagram in a browser (CDN script included):
96
+ RedQuilt.parse("```mermaid\ngraph LR\n A --> B\n```")
97
+ .to_html(standalone: true, mermaid: true)
98
+ ```
99
+
100
+ In standalone output each diagram is made interactive with
101
+ [svg-pan-zoom](https://github.com/bumbu/svg-pan-zoom) (loaded from a CDN).
102
+
86
103
  ### Tilt integration
87
104
 
88
105
  RedQuilt ships a [Tilt](https://github.com/jeremyevans/tilt) adapter.
@@ -100,13 +117,13 @@ Native options (`allow_html:`, `footnotes:`, …) pass straight through; Tilt's
100
117
  ## Documentation
101
118
 
102
119
  - [API reference](docs/api.md) — `Document` / `NodeRef` / `SourceSpan`, supported syntax, and usage examples
103
- - [Architecture overview](docs/architecture.ja.md) (日本語)
104
- - [Arena usage guide](docs/arena-usage.ja.md) (日本語)
105
- - [CommonMark conformance notes](docs/commonmark-conformance.ja.md) (日本語)
120
+ - [Architecture overview](docs/architecture.md) ([日本語](docs/architecture.ja.md))
121
+ - [Arena usage guide](docs/arena-usage.md) ([日本語](docs/arena-usage.ja.md))
122
+ - [CommonMark conformance notes](docs/commonmark-conformance.md) ([日本語](docs/commonmark-conformance.ja.md))
106
123
 
107
124
  ## CommonMark Compatibility
108
125
 
109
- RedQuilt achieves 100% compliance with the CommonMark v0.31.2 specification.
126
+ RedQuilt achieves 100% compliance with the CommonMark v0.31.2 test cases.
110
127
  See the [conformance notes](docs/commonmark-conformance.ja.md) for GFM
111
128
  extensions and intentional deviations.
112
129
 
@@ -137,6 +154,15 @@ redquilt --extended-autolinks --footnotes input.md
137
154
 
138
155
  # Standalone page with the bare template (no embedded CSS)
139
156
  redquilt --theme none input.md
157
+
158
+ # Write HTML to a file instead of stdout
159
+ redquilt -o output.html input.md
160
+
161
+ # Render and open the result in the default browser
162
+ redquilt --open input.md
163
+
164
+ # Render mermaid code blocks as diagrams (loads mermaid.js from a CDN)
165
+ redquilt --mermaid --open input.md
140
166
  ```
141
167
 
142
168
  ### Options
@@ -154,6 +180,12 @@ redquilt --theme none input.md
154
180
  --lang LANG html lang attribute (default: "en")
155
181
  --css URL Add a stylesheet link
156
182
  --theme THEME Embedded stylesheet: default (default) or none
183
+ -o, --output FILE Write HTML to FILE instead of stdout
184
+ --open Write HTML to a file and open it in the default
185
+ browser (forces --standalone; uses a file under
186
+ Dir.tmpdir when -o is not given)
187
+ --mermaid Render `mermaid` code blocks as diagrams (loads
188
+ mermaid.js from a CDN in standalone output)
157
189
  --diagnostics Print diagnostics to stderr
158
190
  --diagnostics-only Print diagnostics only (suppress output)
159
191
  -h, --help Show help
@@ -206,7 +238,9 @@ RedQuilt.render_html(user_markdown, allow_html: true)
206
238
  bundle exec rake spec
207
239
  ```
208
240
 
209
- Runs 70+ CommonMark compatibility and feature tests.
241
+ Runs the full CommonMark 0.31.2 conformance suite (all 652 official examples,
242
+ parsed directly from `spec/fixtures/cmark_spec-0.31.2.md`) plus RedQuilt's own
243
+ feature tests — 1000 examples in total.
210
244
 
211
245
  ### Benchmark
212
246
 
data/Rakefile CHANGED
@@ -6,3 +6,34 @@ require "rspec/core/rake_task"
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
+
10
+ desc "Report CommonMark spec conformance, broken down by section"
11
+ task :conformance do
12
+ require_relative "lib/red_quilt"
13
+ require_relative "spec/support/commonmark_spec_loader"
14
+
15
+ examples = CommonMarkSpecLoader.examples
16
+ sections = examples.each_with_object({}) do |example, acc|
17
+ stats = acc[example[:section]] ||= { pass: 0, total: 0 }
18
+ stats[:total] += 1
19
+ actual = RedQuilt.parse(example[:markdown], allow_html: true).to_html
20
+ stats[:pass] += 1 if actual == example[:html]
21
+ end
22
+
23
+ total = { pass: sections.values.sum { |s| s[:pass] }, total: examples.size }
24
+ width = sections.keys.map(&:length).max
25
+ divider = " #{'-' * width} ---------- -------"
26
+
27
+ puts "CommonMark #{CommonMarkSpecLoader::VERSION} conformance", ""
28
+ puts format(" %-#{width}s %-10s %s", "Section", "Pass/Total", "Rate")
29
+ puts divider
30
+ sections.each do |name, stats|
31
+ rate = stats[:pass].fdiv(stats[:total]) * 100
32
+ puts format(" %-#{width}s %4d / %4d %5.1f%%", name, stats[:pass], stats[:total], rate)
33
+ end
34
+ puts divider
35
+ rate = total[:pass].fdiv(total[:total]) * 100
36
+ puts format(" %-#{width}s %4d / %4d %5.1f%%", "TOTAL", total[:pass], total[:total], rate)
37
+
38
+ abort("\nConformance is below 100%.") unless total[:pass] == total[:total]
39
+ end
data/docs/api.md CHANGED
@@ -23,6 +23,10 @@ doc.disallow_raw_html? # Check GFM disallowed-raw-HTML filtering setting
23
23
  doc.to_html(standalone: true, theme: :default, title: "My Doc", lang: "en")
24
24
  # theme: :default (compact, dark-mode-aware stylesheet) or :none (bare).
25
25
  # css: "style.css" links an external stylesheet instead.
26
+
27
+ # Render `mermaid` code blocks as <pre class="mermaid"> diagrams; in
28
+ # standalone mode the mermaid.js runtime is loaded from a CDN too.
29
+ doc.to_html(standalone: true, mermaid: true)
26
30
  ```
27
31
 
28
32
  ## NodeRef (AST node wrapper)
@@ -35,7 +35,7 @@ Source (Markdown String)
35
35
  ## 各ステージの責務
36
36
 
37
37
  ### `RedQuilt.normalize_input`
38
- CommonMark§2.3/2.4の最小前処理。`\r\n`/`\r`→`\n`の行末正規化と、NUL→U+FFFDの置換だけを行う。
38
+ - CommonMark§2.3/2.4の最小前処理。`\r\n`/`\r`→`\n`の行末正規化と、NUL→U+FFFDの置換だけを行う。
39
39
 
40
40
  ### BlockParser
41
41
  - 行分割: sourceを`Line` Struct配列へ。各行はbyte spanで保持する。
@@ -45,11 +45,11 @@ CommonMark§2.3/2.4の最小前処理。`\r\n`/`\r`→`\n`の行末正規化と
45
45
  - 桁計算: タブ展開を含むインデント計算は`Indentation`に委譲。
46
46
  - 出力: inline未解決のblockノードをArenaに構築する。
47
47
 
48
- ### InlinePass / Lexer / Builder
48
+ ### InlinePass / Inline::Lexer / Inline::Builder
49
49
  - 対象選定: paragraph / heading / table cellの各inline targetを走査して処理。
50
50
  - Lexer: targetのbyte span、またはstr1 literalの範囲をスキャンしTokens(parallel array)へ。
51
- - Builder①linear pass: code span / link / image / autolink / 簡易inlineを解決。
52
- - Builder②process_emphasis: delimiter stackを畳んでemphasis / strong / strikethroughを確定(CommonMark§6.2、strikethroughはGFM拡張)。
51
+ - Builder① linear_pass: code span / link / image / autolink / 簡易inlineを解決。
52
+ - Builder② process_emphasis: delimiter stackを畳んでemphasis / strong / strikethroughを確定(CommonMark§6.2、strikethroughはGFM拡張)。
53
53
  - footnote参照: `[^label]`を`FootnoteRegistry`で解決し、初回参照順に採番して`FOOTNOTE_REFERENCE`を生成。
54
54
 
55
55
  ### FootnotePass (`footnotes: true`)
@@ -0,0 +1,99 @@
1
+ # RedQuilt Architecture Overview
2
+
3
+ This document gives a high-level view of how RedQuilt is structured.
4
+
5
+ ## Pipeline
6
+
7
+ ```
8
+ Source (Markdown String)
9
+
10
+ ▼ RedQuilt.normalize_input (lib/red_quilt.rb)
11
+
12
+ ▼ BlockParser (lib/red_quilt/block_parser.rb)
13
+ │ dispatch / container parsers / build_lines
14
+ │ (list.rb, blockquote.rb, reference_definition.rb)
15
+
16
+ ▼ Arena (raw inline spans)
17
+ │ The body of each paragraph / heading / table cell is kept
18
+ │ as a byte span or a str1 literal.
19
+
20
+ ▼ InlinePass (lib/red_quilt/inline_pass.rb)
21
+ │ ├─ Inline::Lexer (lib/red_quilt/inline/lexer.rb)
22
+ │ │ byte scan -> Tokens (parallel array)
23
+ │ └─ Inline::Builder (lib/red_quilt/inline/builder.rb)
24
+ │ linear pass -> process_emphasis (CommonMark §6.2)
25
+
26
+ ▼ Arena (inline resolved)
27
+
28
+ ▼ (option) FootnotePass (footnotes: true)
29
+ ▼ (option) ExtendedAutolinkPass (extended_autolinks: true)
30
+ ▼ (option) LintPass (lint: true)
31
+
32
+ ▼ Renderer::HTML (lib/red_quilt/renderer/html.rb)
33
+ walk the arena and append to a mutable String
34
+ ```
35
+
36
+ ## Responsibility of each stage
37
+
38
+ ### `RedQuilt.normalize_input`
39
+ - Minimal preprocessing required by CommonMark §2.3 / §2.4. It only normalizes
40
+ line endings (`\r\n` / `\r` -> `\n`) and replaces NUL with U+FFFD.
41
+
42
+ ### BlockParser
43
+ - Line splitting: turn the source into an array of `Line` structs. Each line is
44
+ kept as a byte span.
45
+ - Dispatch: decide the block kind from the first byte of the line
46
+ (`paragraph_only_line?` quickly routes non-block lines).
47
+ - Container delegation: lists and blockquotes are delegated to `List::Parser`
48
+ and `Blockquote::Parser`, which call `parse_lines` recursively.
49
+ - Collecting and excluding definitions: link reference definitions (the
50
+ reference table) and opt-in footnote definitions (`FootnoteRegistry`) are
51
+ pulled out of the body flow and gathered in dedicated collectors.
52
+ - Column calculation: indentation that includes tab expansion is delegated to
53
+ `Indentation`.
54
+ - Output: build block nodes in the Arena, with inline content still unresolved.
55
+
56
+ ### InlinePass / Inline::Lexer / Inline::Builder
57
+ - Target selection: scan and process each inline target (paragraph / heading /
58
+ table cell).
59
+ - Lexer: scan the target's byte span, or the range of a str1 literal, into
60
+ Tokens (a parallel array).
61
+ - Builder, step 1 (linear_pass): resolve code spans, links, images, autolinks,
62
+ and simple inlines.
63
+ - Builder, step 2 (process_emphasis): collapse the delimiter stack to finalize
64
+ emphasis / strong / strikethrough (CommonMark §6.2; strikethrough is a GFM
65
+ extension).
66
+ - Footnote references: resolve `[^label]` through `FootnoteRegistry`, number
67
+ them in first-reference order, and create a `FOOTNOTE_REFERENCE`.
68
+
69
+ ### FootnotePass (`footnotes: true`)
70
+ - Reordering: sort the definitions under `FOOTNOTES_SECTION` (at the end of the
71
+ root) into first-reference order.
72
+ - Pruning: detach unreferenced definitions.
73
+ - Section removal: if there are no references at all, remove the section itself.
74
+
75
+ ### Renderer::HTML
76
+ - Walk: walk the arena recursively and append directly with `<<` to a mutable
77
+ String opened with `+""`.
78
+ - Raw HTML: `allow_html` switches between passing HTML through and escaping it;
79
+ `disallow_raw_html` filters HTML using GFM "Disallowed Raw HTML".
80
+ - Footnotes: render `FOOTNOTE_REFERENCE` as a sup link, and the trailing
81
+ `FOOTNOTES_SECTION` as `<section class="footnotes">` with backrefs.
82
+
83
+ ## Where the main subsystems live
84
+
85
+ | Area | Files |
86
+ |---|---|
87
+ | Entry point / input normalization | `lib/red_quilt.rb` |
88
+ | Public API | `lib/red_quilt/document.rb`, `node_ref.rb` |
89
+ | Arena | `lib/red_quilt/arena.rb` |
90
+ | Block parsing | `block_parser.rb`, `list.rb`, `blockquote.rb`, `indentation.rb` |
91
+ | Reference definitions | `reference_definition.rb` |
92
+ | Footnotes (opt-in) | `footnote_definition.rb`, `footnote_registry.rb`, `footnote_pass.rb` |
93
+ | Inline parsing | `inline.rb`, `inline/lexer.rb`, `inline/tokens.rb`, `inline/flanking.rb`, `inline/builder.rb`, `inline/link_scanner.rb` |
94
+ | Inline entities | `inline/html_entities.rb` |
95
+ | HTML / MDAST output | `renderer/html.rb`, `renderer/mdast.rb` |
96
+ | Extension passes | `inline_pass.rb`, `footnote_pass.rb`, `extended_autolink_pass.rb`, `lint_pass.rb` |
97
+ | Source positions | `source_span.rb`, `source_map.rb` |
98
+ | Diagnostics | `diagnostic.rb` |
99
+ | CLI | `cli.rb`, `exe/redquilt` |
@@ -92,7 +92,7 @@ ArenaはASTを「オブジェクトのツリー」ではなく[parallel array](h
92
92
  - メモリ局所性が良く、GC圧が小さい
93
93
  - ノードを「軽い」値として扱えるのでRenderer / Builderをinline化しやすい
94
94
 
95
- ###列(column)一覧
95
+ #### 列(column)一覧
96
96
 
97
97
  |列名|用途|
98
98
  |------|------|
@@ -183,7 +183,7 @@ Arenaの公開メソッドは以下の3レイヤーに分けて読むと意図
183
183
 
184
184
  各NodeTypeがどのint / strスロットを使うかは規約で決まっています。以下が現在の規約です。
185
185
 
186
- ### Blockノード
186
+ #### Blockノード
187
187
 
188
188
  | NodeType | int1 | int2 | int3 | str1 | str2 |
189
189
  |----------|------|------|------|------|------|
@@ -202,7 +202,7 @@ Arenaの公開メソッドは以下の3レイヤーに分けて読むと意図
202
202
  | `FOOTNOTE_DEFINITION` | - | - | - | 正規化済みlabel | - |
203
203
  | `FOOTNOTES_SECTION` | - | - | - | - | - |
204
204
 
205
- ### Inlineノード
205
+ #### Inlineノード
206
206
 
207
207
  | NodeType | int1 | int2 | int3 | str1 | str2 |
208
208
  |----------|------|------|------|------|------|
@@ -219,7 +219,7 @@ Arenaの公開メソッドは以下の3レイヤーに分けて読むと意図
219
219
 
220
220
  > footnoteは`footnotes: true`時のみ生成されます。`FOOTNOTES_SECTION`はroot直下の最後の子として置かれ(span-less、`source_start: -1`)、参照された`FOOTNOTE_DEFINITION`を初回参照順に保持します。backrefの個数はfootnote番号とlabelからrender時に算出します。
221
221
 
222
- ### Source spanの慣習
222
+ #### Source spanの慣習
223
223
 
224
224
  - `source_start` / `source_len`: 元documentのbytes (絶対byte offset)
225
225
  - `source_start < 0`: spanなし。leafノードでは内容を`str1`にliteralとして持つことが多いが、container inlineは子ノードだけを持つ場合がある。
@@ -334,7 +334,7 @@ arena.update_span(text_id, 0, 12)
334
334
 
335
335
  ## 6. パフォーマンス上の注意
336
336
 
337
- ####ホットパスでは`each_child`を使う
337
+ #### ホットパスでは`each_child`を使う
338
338
 
339
339
  ブロック直yieldでEnumerator allocationを避ける。`child_ids`は外部API用
340
340