red_quilt 0.6.1 → 0.7.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.
@@ -25,7 +25,7 @@ spec通りに動く挙動は仕様書(<https://spec.commonmark.org/0.31.2/>、<h
25
25
  ### 1.2 実装上の前提
26
26
 
27
27
  - 入力はUTF-8文字列。`force_encoding(Encoding::UTF_8)` 等の前処理は呼び出し側の責任。
28
- - spec§2.3 / §2.4が要求する正規化(NUL→U+FFFD、`\r\n` / `\r` → `\n`、blank linespace/tab限定)はいずれも実装している。
28
+ - spec§2.3 / §2.4が要求する正規化(NUL→U+FFFD、`\r\n` / `\r` → `\n`)と、blank line定義のspace/tab限定はいずれも実装している。
29
29
  これはspec通りなので本書では個別項目化しない。
30
30
 
31
31
  ### 1.3 各項目のフォーマット
@@ -51,6 +51,7 @@ specの文言が複数解釈を許す箇所、あるいは "must" が曖昧な
51
51
  **RedQuiltの挙動**: U+007Fを含めて拒否。
52
52
 
53
53
  **実装**: `lib/red_quilt/inline/lexer.rb` — `URI_AUTOLINK_RE`
54
+
54
55
  **テスト**: `spec/whitespace_strictness_spec.rb` — "URI autolink (CommonMark 6.5)"
55
56
 
56
57
  ### 2.2 Raw HTML tagのseparatorをspace/tab/CR/LFに限定
@@ -75,8 +76,8 @@ FF (U+000C) / VT (U+000B) は含めない。インラインのraw HTML、HTML bl
75
76
  **RedQuiltの挙動**: link tail内ではspace / tabのみをseparatorとし、line endingは別途1回までカウント。
76
77
  FF / VTが現れた場合はlinkとしては成立しない(段落の通常テキストとして扱う)。
77
78
 
78
- **実装**: `lib/red_quilt/inline/builder.rb` — `link_tail_whitespace_byte?`、
79
- `skip_link_whitespace`、`parse_link_tail`
79
+ **実装**: `lib/red_quilt/inline/link_scanner.rb` — `link_tail_whitespace_byte?`、
80
+ `skip_link_whitespace`、`inline_link`、`parse_link_title`
80
81
 
81
82
  **テスト**: `spec/whitespace_strictness_spec.rb` — "inline link tail whitespace (CommonMark 6.3)"
82
83
 
@@ -106,7 +107,8 @@ parentheses".
106
107
  **実装**:
107
108
  - 定数: `lib/red_quilt/reference_definition.rb` — `LABEL_MAX_LENGTH = 999`、`label_too_long?` ヘルパ
108
109
  - 定義側: `match_label`(単一行・複数行のどちらでも判定)
109
- - 使用側: `lib/red_quilt/inline/builder.rb` — `try_reference_link` / `read_reference_label`
110
+ - 使用側: `lib/red_quilt/inline/builder.rb` — `try_reference_link`、
111
+ `lib/red_quilt/inline/link_scanner.rb` — `reference_label`
110
112
 
111
113
  **テスト**: `spec/link_validation_spec.rb` — "link label length limit (999 characters)"
112
114
 
@@ -151,14 +153,15 @@ specには規定が無いが、RedQuilt側で安全性・利便性のために
151
153
 
152
154
  ### 3.1 unsafe URL schemeのサニタイズ
153
155
 
154
- **RedQuiltの挙動**: link / imageのdestinationのschemeが以下の安全リストに含まれない場合、`href` を空文字列にして出力する。
156
+ **RedQuiltの挙動**: link / imageのdestinationのschemeが以下の安全リストに含まれない場合、`href` / `src` を空文字列にして出力する。
155
157
  同時に `:unsafe_url` のdiagnosticをwarningとして発行する。
158
+ CommonMark autolink (`<scheme:...>`) はspec適合のため安全リストではなくdenylistで扱い、script実行につながるschemeのみ空hrefにする。
156
159
 
157
160
  **安全スキーム**: `http`、`https`、`mailto`、`ftp`、`tel`、`ssh`
158
161
 
159
- **ブロックされる代表例**: `javascript:`、`vbscript:`、`data:`
162
+ **autolinkでブロックされるscheme**: `javascript`、`vbscript`、`data`
160
163
 
161
- **実装**: `lib/red_quilt/inline/builder.rb` — `SAFE_SCHEMES`、sanitize_destination`
164
+ **実装**: `lib/red_quilt/inline/builder.rb` — `SAFE_SCHEMES`、`UNSAFE_AUTOLINK_SCHEMES`、`sanitize_destination`、`block_unsafe_autolink`
162
165
 
163
166
  **テスト**: `spec/red_quilt_spec.rb` — "sanitizes unsafe URL schemes"
164
167
 
@@ -174,9 +177,13 @@ specには規定が無いが、RedQuilt側で安全性・利便性のために
174
177
  |---|---|---|
175
178
  | `:missing_reference` | warning | full reference link `[text][ref]` の定義が無い |
176
179
  | `:duplicate_reference` | warning | 同じlabelのreference definitionが複数あった(最初の定義を採用) |
177
- | `:unsafe_url` | warning | safe schemeリストに無いURLを空hrefに置換した |
180
+ | `:duplicate_footnote` | warning | 同じlabelのfootnote definitionが複数あった(最初の定義を採用、`footnotes: true`時のみ) |
181
+ | `:unsafe_url` | warning | unsafeなURLを空の`href` / `src`に置換した |
182
+ | `:empty_link` | warning | link destinationが空(`lint: true`時のみ) |
183
+ | `:missing_alt` | info | imageのalt textが空(`lint: true`時のみ) |
184
+ | `:heading_level_skip` | info | 見出しレベルが1段を超えて飛んだ(`lint: true`時のみ) |
178
185
 
179
- **実装**: `lib/red_quilt/diagnostic.rb`(値オブジェクト)、`lib/red_quilt/block_parser.rb`(duplicate)、`lib/red_quilt/inline/builder.rb`(missing / unsafe)
186
+ **実装**: `lib/red_quilt/diagnostic.rb`(値オブジェクト)、`lib/red_quilt/block_parser.rb`(duplicate reference)、`lib/red_quilt/footnote_definition.rb`(duplicate footnote)、`lib/red_quilt/inline/builder.rb`(missing / unsafe)、`lib/red_quilt/lint_pass.rb`(lint rules)
180
187
 
181
188
  ### 3.3 `allow_html` / `disallow_raw_html` フラグ
182
189
 
@@ -205,7 +212,7 @@ GFMが定めるdisallowedタグ群: `title`, `textarea`, `style`, `xmp`, `iframe
205
212
 
206
213
  常時有効。`~~text~~` の2連tildeのみ対応(GFMの挙動と一致)。単一tilde`~text~` は通常テキストとして扱う。
207
214
 
208
- **実装**: `lib/red_quilt/inline/builder.rb`(`scan_delim_run` 内の `~` 取扱)、`lib/red_quilt/inline/lexer.rb`(`SPECIAL_BYTES` `0x7E`)
215
+ **実装**: `lib/red_quilt/inline/lexer.rb`(`SPECIAL_BYTES` と `scan_delim_run` `~` 取扱)、`lib/red_quilt/inline/builder.rb`(`process_emphasis` `STRIKETHROUGH` を生成)
209
216
 
210
217
  ### 4.3 GFM Disallowed Raw HTML
211
218
 
@@ -220,6 +227,13 @@ GFMが定めるdisallowedタグ群: `title`, `textarea`, `style`, `xmp`, `iframe
220
227
 
221
228
  **実装**: `lib/red_quilt/extended_autolink_pass.rb`
222
229
 
230
+ ### 4.5 GFM Footnotes
231
+
232
+ オプトイン。`footnotes: true` を指定すると `[^label]: ...` の定義を本文フローから除外し、`[^label]` 参照をsupリンクに変換する。
233
+ 参照された定義だけを初回参照順に並べ、root末尾の`FOOTNOTES_SECTION`として出力する。未参照の定義は出力しない。
234
+
235
+ **実装**: `lib/red_quilt/footnote_definition.rb`、`lib/red_quilt/footnote_registry.rb`、`lib/red_quilt/footnote_pass.rb`
236
+
223
237
  ## 5. 未対応 / 既知の制限
224
238
 
225
239
  - GFM Task List Items (`- [ ]` / `- [x]`) は未対応。通常のlist itemとしてパースされる。
@@ -237,5 +251,7 @@ GFMが定めるdisallowedタグ群: `title`, `textarea`, `style`, `xmp`, `iframe
237
251
  | NCR桁上限と無効codepoint | `spec/numeric_character_reference_spec.rb` |
238
252
  | GFM tableのcolumn count一致 | `spec/red_quilt_spec.rb` — "table separator validation" |
239
253
  | GFM extended autolinkのdomain検証 | `spec/extended_autolink_spec.rb` |
254
+ | GFM footnotes | `spec/footnotes_spec.rb` |
240
255
  | URL schemeサニタイズ | `spec/red_quilt_spec.rb` — "sanitizes unsafe URL schemes" |
256
+ | Diagnostics / lint diagnostics | `spec/diagnostic_spec.rb` |
241
257
  | Disallowed Raw HTML | `spec/red_quilt_spec.rb` —disallow_raw_html系 |
data/lib/red_quilt/cli.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optparse"
4
+ require "red_quilt"
4
5
 
5
6
  module RedQuilt
6
7
  # Entry point for the `redquilt` executable. Defined as a module-level
@@ -45,8 +45,10 @@ module RedQuilt
45
45
  # theme: a bundled stylesheet to inline (`:none` embeds nothing, keeping
46
46
  # the bare template; `:default` embeds RedQuilt's default theme). `css`
47
47
  # (an external stylesheet link) is independent and may be combined.
48
- def to_html(standalone: false, title: nil, lang: "en", css: nil, theme: :none)
49
- body = Renderer::HTML.new(self).render
48
+ # heading_ids: when true, every heading gets a slugified `id` (Unicode
49
+ # preserving, deduplicated within the document) for anchor links.
50
+ def to_html(standalone: false, title: nil, lang: "en", css: nil, theme: :none, heading_ids: false)
51
+ body = Renderer::HTML.new(self, heading_ids: heading_ids).render
50
52
  return body unless standalone
51
53
 
52
54
  wrap_standalone_html(body, title: title.to_s, lang: lang.to_s, css: css, theme: Theme.css(theme))
@@ -3,10 +3,11 @@
3
3
  module RedQuilt
4
4
  module Renderer
5
5
  class HTML
6
- def initialize(document)
6
+ def initialize(document, heading_ids: false)
7
7
  @document = document
8
8
  @arena = document.arena
9
9
  @out = +""
10
+ @slugger = Slug::Counter.new if heading_ids
10
11
  end
11
12
 
12
13
  def render
@@ -45,7 +46,12 @@ module RedQuilt
45
46
  @out << "</p>\n"
46
47
  when NodeType::HEADING
47
48
  level = @arena.int1(node_id)
48
- @out << "<h#{level}>"
49
+ if @slugger
50
+ id = @slugger.generate(PlainText.from(@arena, node_id))
51
+ @out << %(<h#{level} id="#{escape_html(id)}">)
52
+ else
53
+ @out << "<h#{level}>"
54
+ end
49
55
  render_children(node_id)
50
56
  @out << "</h#{level}>\n"
51
57
  when NodeType::THEMATIC_BREAK
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedQuilt
4
+ # Heading-anchor slugs, following GitHub's github-slugger approach:
5
+ # downcase, strip punctuation, spaces to hyphens -- but keep Unicode
6
+ # letters/marks/numbers verbatim, so Japanese (and other non-ASCII)
7
+ # headings survive instead of collapsing to empty ids.
8
+ module Slug
9
+ # Drop anything that is not a letter, mark, number, underscore, space,
10
+ # or hyphen. Browsers percent-encode non-ASCII fragment ids on the wire
11
+ # but resolve and display them fine, matching GitHub.
12
+ STRIP_RE = /[^\p{L}\p{M}\p{N}_ -]+/u
13
+ SPACE_RE = / +/
14
+
15
+ module_function
16
+
17
+ def slugify(text)
18
+ base = text.downcase.gsub(STRIP_RE, "").strip.gsub(SPACE_RE, "-")
19
+ base.empty? ? "section" : base
20
+ end
21
+
22
+ # Deduplicates slugs within a single document: the first occurrence of a
23
+ # base keeps it, later collisions get `-1`, `-2`, ... suffixes (matching
24
+ # GitHub's anchor numbering).
25
+ class Counter
26
+ def initialize
27
+ @seen = Hash.new(0)
28
+ end
29
+
30
+ def generate(text)
31
+ base = Slug.slugify(text)
32
+ count = @seen[base]
33
+ @seen[base] += 1
34
+ count.zero? ? base : "#{base}-#{count}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tilt"
4
+ require "red_quilt"
5
+
6
+ module RedQuilt
7
+ # Tilt template adapter. Require this file explicitly to register RedQuilt
8
+ # with Tilt -- `tilt` is an optional dependency, so a missing gem surfaces
9
+ # as a LoadError here rather than silently disabling the integration:
10
+ #
11
+ # require "red_quilt/tilt"
12
+ # Tilt.new("page.md").render
13
+ class TiltTemplate < Tilt::Template
14
+ self.default_mime_type = "text/html"
15
+
16
+ NATIVE_OPTIONS = %i[allow_html disallow_raw_html extended_autolinks footnotes lint heading_ids].freeze
17
+
18
+ def prepare; end
19
+
20
+ def evaluate(_scope, _locals)
21
+ @output ||= RedQuilt.render_html(data, **render_options)
22
+ end
23
+
24
+ # RedQuilt escapes raw HTML and never emits embedded scripting, so the
25
+ # output is a finished, script-free fragment.
26
+ def allows_script?
27
+ false
28
+ end
29
+
30
+ private
31
+
32
+ def render_options
33
+ opts = options.slice(*NATIVE_OPTIONS)
34
+ # Tilt's cross-engine markdown convention is :escape_html; RedQuilt's
35
+ # native switch is its inverse (allow_html), so map it when present.
36
+ opts[:allow_html] = !options[:escape_html] if options.key?(:escape_html)
37
+ opts
38
+ end
39
+ end
40
+
41
+ Tilt.register(TiltTemplate, "md", "markdown", "mkd", "mkdn", "mdown")
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RedQuilt
4
- VERSION = "0.6.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/red_quilt.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "red_quilt/arena"
10
10
  require_relative "red_quilt/node_ref"
11
11
  require_relative "red_quilt/diagnostic"
12
12
  require_relative "red_quilt/plain_text"
13
+ require_relative "red_quilt/slug"
13
14
  require_relative "red_quilt/theme"
14
15
  require_relative "red_quilt/document"
15
16
  require_relative "red_quilt/inline"
@@ -56,13 +57,13 @@ module RedQuilt
56
57
  document
57
58
  end
58
59
 
59
- def render_html(source, allow_html: false, disallow_raw_html: false, extended_autolinks: false, footnotes: false, lint: false)
60
+ def render_html(source, allow_html: false, disallow_raw_html: false, extended_autolinks: false, footnotes: false, lint: false, heading_ids: false)
60
61
  parse(source,
61
62
  allow_html: allow_html,
62
63
  disallow_raw_html: disallow_raw_html,
63
64
  extended_autolinks: extended_autolinks,
64
65
  footnotes: footnotes,
65
- lint: lint).to_html
66
+ lint: lint).to_html(heading_ids: heading_ids)
66
67
  end
67
68
 
68
69
  private
data/sig/red_quilt.rbs CHANGED
@@ -1,19 +1,50 @@
1
1
  module RedQuilt
2
2
  VERSION: String
3
3
 
4
- def self.parse: (String source, ?allow_html: bool) -> Document
5
- def self.render_html: (String source, ?allow_html: bool) -> String
4
+ class Error < StandardError
5
+ end
6
+
7
+ def self.parse: (String source,
8
+ ?allow_html: bool,
9
+ ?disallow_raw_html: bool,
10
+ ?extended_autolinks: bool,
11
+ ?footnotes: bool,
12
+ ?lint: bool) -> Document
13
+
14
+ def self.render_html: (String source,
15
+ ?allow_html: bool,
16
+ ?disallow_raw_html: bool,
17
+ ?extended_autolinks: bool,
18
+ ?footnotes: bool,
19
+ ?lint: bool,
20
+ ?heading_ids: bool) -> String
6
21
 
7
22
  class Document
8
23
  attr_reader source: String
9
24
  attr_reader arena: Arena
10
25
  attr_reader root_id: Integer
26
+ attr_reader references: Hash[String, Hash[Symbol, untyped]]
27
+ attr_reader footnotes: FootnoteRegistry?
11
28
 
29
+ def initialize: (String source, Arena arena, Integer root_id,
30
+ ?allow_html: bool,
31
+ ?disallow_raw_html: bool,
32
+ ?references: Hash[String, Hash[Symbol, untyped]],
33
+ ?footnotes: FootnoteRegistry?) -> void
34
+
35
+ def allow_html?: () -> bool
36
+ def disallow_raw_html?: () -> bool
12
37
  def root: () -> NodeRef
13
- def to_html: () -> String
38
+ def walk: () { (NodeRef) -> void } -> void
39
+ | () -> Enumerator[NodeRef, void]
40
+ def to_html: (?standalone: bool, ?title: String?, ?lang: String,
41
+ ?css: String?, ?theme: Symbol?, ?heading_ids: bool) -> String
14
42
  def to_ast: () -> Hash[Symbol, untyped]
15
- def allow_html?: () -> bool
43
+ def to_json: (*untyped) -> String
44
+ def to_mdast: () -> Hash[String, untyped]
45
+ def first_heading_text: () -> String?
16
46
  def source_map: () -> SourceMap
47
+ def diagnostics: () -> Array[Diagnostic]
17
48
  end
18
49
 
19
50
  class NodeRef
@@ -22,18 +53,83 @@ module RedQuilt
22
53
  attr_reader document: Document
23
54
  attr_reader node_id: Integer
24
55
 
56
+ def initialize: (Document document, Integer node_id) -> void
57
+ def each: () { (NodeRef) -> void } -> void
58
+ | () -> Enumerator[NodeRef, void]
25
59
  def type: () -> Symbol
26
60
  def children: () -> Array[NodeRef]
27
61
  def walk: () { (NodeRef) -> void } -> void
28
- def walk: () -> Enumerator[NodeRef, void]
29
- def find_all: (Symbol type) -> Array[NodeRef]
30
- def to_h: () -> Hash[Symbol, untyped]
62
+ | () -> Enumerator[NodeRef, void]
31
63
  def text: () -> String?
32
64
  def source_span: () -> SourceSpan?
65
+ def find_all: (Symbol type) -> Array[NodeRef]
33
66
  def source_location: () -> { start_line: Integer, start_column: Integer, end_line: Integer, end_column: Integer }?
67
+ def to_h: () -> Hash[Symbol, untyped]
34
68
  end
35
69
 
70
+ # Low-level parallel-array storage for the AST. See docs/arena-usage.ja.md.
36
71
  class Arena
72
+ NO_NODE: Integer
73
+
74
+ class IntegrityError < StandardError
75
+ end
76
+
77
+ attr_reader source: String
78
+
79
+ def initialize: (String source) -> void
80
+
81
+ # Structure mutators. The child/insert/detach helpers return the
82
+ # affected node id; update_* / reparent return values are incidental.
83
+ def add_node: (Integer type,
84
+ ?source_start: Integer, ?source_len: Integer,
85
+ ?int1: Integer, ?int2: Integer, ?int3: Integer,
86
+ ?str1: String?, ?str2: String?) -> Integer
87
+ def append_child: (Integer parent_id, Integer child_id) -> Integer
88
+ def insert_before: (Integer parent_id, Integer ref_id, Integer new_id) -> Integer
89
+ def detach: (Integer child_id) -> Integer
90
+ def reparent: (Integer new_parent_id, Integer first_id, Integer last_id) -> void
91
+ def update_span: (Integer id, Integer start_byte, Integer end_byte) -> void
92
+ def update_str1: (Integer id, String? value) -> void
93
+ def update_int3: (Integer id, Integer value) -> void
94
+
95
+ # Raw id accessors (may return NO_NODE)
96
+ def raw_parent_id: (Integer id) -> Integer
97
+ def raw_first_child_id: (Integer id) -> Integer
98
+ def raw_last_child_id: (Integer id) -> Integer
99
+ def raw_next_sibling_id: (Integer id) -> Integer
100
+ def raw_prev_sibling_id: (Integer id) -> Integer
101
+
102
+ # Column accessors
103
+ def type: (Integer id) -> Integer
104
+ def type_name: (Integer id) -> Symbol
105
+ def source_start: (Integer id) -> Integer
106
+ def source_len: (Integer id) -> Integer
107
+ def source_end: (Integer id) -> Integer
108
+ def int1: (Integer id) -> Integer
109
+ def int2: (Integer id) -> Integer
110
+ def int3: (Integer id) -> Integer
111
+ def str1: (Integer id) -> String?
112
+ def str2: (Integer id) -> String?
113
+
114
+ # Semantic accessors
115
+ def source_span: (Integer id) -> SourceSpan?
116
+ def text: (Integer id) -> String?
117
+
118
+ # Traversal
119
+ def each_child: (Integer id) { (Integer child_id) -> void } -> self
120
+ def child_ids: (Integer id) -> Enumerator[Integer, void]
121
+
122
+ def check_integrity!: (Integer root_id) -> self
123
+ end
124
+
125
+ class SourceSpan
126
+ attr_reader start_byte: Integer
127
+ attr_reader end_byte: Integer
128
+
129
+ def initialize: (Integer start_byte, Integer end_byte) -> void
130
+ def length: () -> Integer
131
+ def ==: (untyped other) -> bool
132
+ def to_h: () -> { start_byte: Integer, end_byte: Integer }
37
133
  end
38
134
 
39
135
  class SourceMap
@@ -41,5 +137,100 @@ module RedQuilt
41
137
  def line_column: (Integer byte_offset) -> { line: Integer, column: Integer }
42
138
  end
43
139
 
44
- SourceSpan = Data[Integer, Integer]
140
+ class Diagnostic
141
+ SEVERITIES: Array[Symbol]
142
+
143
+ attr_reader severity: Symbol
144
+ attr_reader rule: Symbol
145
+ attr_reader message: String
146
+ attr_reader source_span: SourceSpan?
147
+
148
+ def initialize: (severity: Symbol, rule: Symbol, message: String,
149
+ ?source_span: SourceSpan?) -> void
150
+ def to_h: () -> Hash[Symbol, untyped]
151
+ def ==: (untyped other) -> bool
152
+ end
153
+
154
+ # Integer NodeType constants (block: 1, 10-22; inline: 100-112).
155
+ module NodeType
156
+ DOCUMENT: Integer
157
+ PARAGRAPH: Integer
158
+ HEADING: Integer
159
+ THEMATIC_BREAK: Integer
160
+ BLOCKQUOTE: Integer
161
+ LIST: Integer
162
+ LIST_ITEM: Integer
163
+ CODE_BLOCK: Integer
164
+ HTML_BLOCK: Integer
165
+ TABLE: Integer
166
+ TABLE_ROW: Integer
167
+ TABLE_CELL: Integer
168
+ FOOTNOTE_DEFINITION: Integer
169
+ FOOTNOTES_SECTION: Integer
170
+ TEXT: Integer
171
+ SOFTBREAK: Integer
172
+ HARDBREAK: Integer
173
+ EMPHASIS: Integer
174
+ STRONG: Integer
175
+ CODE_SPAN: Integer
176
+ LINK: Integer
177
+ IMAGE: Integer
178
+ HTML_INLINE: Integer
179
+ STRIKETHROUGH: Integer
180
+ FOOTNOTE_REFERENCE: Integer
181
+
182
+ TYPE_NAMES: Hash[Integer, Symbol]
183
+
184
+ def self.name_for: (Integer type) -> Symbol
185
+ end
186
+
187
+ # Unicode-preserving heading-anchor slugs (github-slugger style).
188
+ module Slug
189
+ STRIP_RE: Regexp
190
+ SPACE_RE: Regexp
191
+
192
+ def self.slugify: (String text) -> String
193
+
194
+ # Per-document collision counter; later duplicates get `-N` suffixes.
195
+ class Counter
196
+ def initialize: () -> void
197
+ def generate: (String text) -> String
198
+ end
199
+ end
200
+
201
+ # Bundled stylesheets for standalone HTML output (`:none` => no CSS).
202
+ # `name` is coerced with `to_sym`, so String names ("default") work too.
203
+ module Theme
204
+ NAMES: Array[Symbol]
205
+
206
+ def self.css: (Symbol | String | nil name) -> String?
207
+ end
208
+
209
+ # Shared footnote state (created per parse when `footnotes: true`).
210
+ class FootnoteRegistry
211
+ def initialize: () -> void
212
+ def define: (String label, Integer node_id) -> bool
213
+ def defined?: (String label) -> bool
214
+ def definition_node: (String label) -> Integer?
215
+ def reference: (String label) -> [ Integer, Integer ]?
216
+ def number: (String label) -> Integer?
217
+ def occurrences: (String label) -> Integer
218
+ def referenced_labels: () -> Array[String]
219
+ def any_referenced?: () -> bool
220
+ end
221
+
222
+ module CLI
223
+ # Duck types for the injected IO objects (tests pass StringIO, not IO).
224
+ interface _Reader
225
+ def read: () -> String?
226
+ end
227
+
228
+ interface _Writer
229
+ def write: (*untyped) -> Integer
230
+ def puts: (*untyped) -> void
231
+ def <<: (untyped) -> self
232
+ end
233
+
234
+ def self.run: (Array[String] argv, ?stdin: _Reader, ?stdout: _Writer, ?stderr: _Writer) -> Integer
235
+ end
45
236
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red_quilt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-05-29 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: A modern Markdown document processor in pure Ruby, with an arena-style
13
13
  AST and full CommonMark spec test suite compliance.
@@ -18,16 +18,16 @@ executables:
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
- - ".rspec"
22
21
  - ".rubocop.yml"
23
22
  - ".rubocop_todo.yml"
24
23
  - CHANGELOG.md
24
+ - LICENSE.txt
25
25
  - README.md
26
26
  - Rakefile
27
- - ast-spec.md
28
- - docs/architecture.md
29
- - docs/arena-usage.md
30
- - docs/commonmark-conformance.md
27
+ - docs/api.md
28
+ - docs/architecture.ja.md
29
+ - docs/arena-usage.ja.md
30
+ - docs/commonmark-conformance.ja.md
31
31
  - exe/redquilt
32
32
  - lib/red_quilt.rb
33
33
  - lib/red_quilt/arena.rb
@@ -59,18 +59,23 @@ files:
59
59
  - lib/red_quilt/reference_definition.rb
60
60
  - lib/red_quilt/renderer/html.rb
61
61
  - lib/red_quilt/renderer/mdast.rb
62
+ - lib/red_quilt/slug.rb
62
63
  - lib/red_quilt/source_map.rb
63
64
  - lib/red_quilt/source_span.rb
64
65
  - lib/red_quilt/theme.rb
65
66
  - lib/red_quilt/themes/default.css
67
+ - lib/red_quilt/tilt.rb
66
68
  - lib/red_quilt/version.rb
67
- - mise.toml
68
69
  - sig/red_quilt.rbs
69
70
  homepage: https://github.com/takahashim/red_quilt
70
- licenses: []
71
+ licenses:
72
+ - MIT
71
73
  metadata:
72
74
  homepage_uri: https://github.com/takahashim/red_quilt
73
75
  source_code_uri: https://github.com/takahashim/red_quilt
76
+ changelog_uri: https://github.com/takahashim/red_quilt/blob/main/CHANGELOG.md
77
+ documentation_uri: https://github.com/takahashim/red_quilt#readme
78
+ rubygems_mfa_required: 'true'
74
79
  rdoc_options: []
75
80
  require_paths:
76
81
  - lib
@@ -85,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
90
  - !ruby/object:Gem::Version
86
91
  version: '0'
87
92
  requirements: []
88
- rubygems_version: 4.0.10
93
+ rubygems_version: 3.6.2
89
94
  specification_version: 4
90
95
  summary: CommonMark-based Markdown processor written in pure Ruby
91
96
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper