mdom 0.2.0 → 0.3.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.
@@ -0,0 +1,379 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+ require_relative "node/inline_nodes"
5
+ require_relative "node/nodes"
6
+
7
+ module MDOM
8
+ # (2) AST1 → Ruby 源码(CSM §3.1;docs/IMPLEMENTATION.md §3)。
9
+ #
10
+ # 把一棵 AST1 翻译成**一段顶层顺序执行的 Ruby 程序**:排版节点变成 builder
11
+ # 调用,嵌入节点(RubyEmbed / 行内插值)原样注入。产出的只是源码字符串,不
12
+ # 绑定任何执行方式(docs/IMPLEMENTATION.md §6.2)——`eval`、落盘后 `require`、
13
+ # 交给 mruby 都可以。
14
+ #
15
+ # MDOM::Translator.to_ruby(MDOM::Parser.parse("# 标题\n"))
16
+ # # => "builder = MDOM::Builder.new\nbuilder.heading(1, \"标题\")\nbuilder.to_document"
17
+ #
18
+ # 两侧的结构约束(docs/IMPLEMENTATION.md §6.3):排版段一律经由 +builder.*+
19
+ # 接收者,形式可辨识;嵌入段保持原样,边界明确(Ruby 块原文 / 字符串字面量里的
20
+ # `#{}`)。这样宿主才有可能判断一段产物是否能在 mruby 上跑。
21
+ #
22
+ # == 安全闸门(CSM §3.14)
23
+ #
24
+ # +unsafe+ 是**翻译期**参数,默认 +false+(安全):嵌入节点不写进产物,所以
25
+ # 产物静态上不含用户代码——块级 `RubyEmbed` 只留一行注释占位,行内插值退化为
26
+ # 字面 `#{expr}`。+unsafe: true+ 才把嵌入原样注入。
27
+ #
28
+ # 安全模式下宿主不需要(也不应该)拿产物去 `eval`:未求值的嵌入由 (6) 直接吃
29
+ # AST1 就能按独立样式输出。求值本身只是宿主的一行 `eval`,mdom 不含求值器。
30
+ #
31
+ # 本类**不依赖 prism**:行内边界已由 InlineParser 判好,这里只做序列化。它只
32
+ # +require+ 节点定义(builder 侧)。
33
+ #
34
+ # == 行内内容为什么重新生成标记
35
+ #
36
+ # 行内容器(段落、标题……)在产物里以**字符串参数**交给 builder,而 builder
37
+ # 会用 InlineParser 重新解析这些字符串。因此本类要把 AST1 的行内子树重新序列化
38
+ # 成 CSM 标记文本(`**粗**`、`[标签](uri)`……)。
39
+ #
40
+ # 插值单独处理:+unsafe: true+ 时 `#{}` 在产物里是**真正的 Ruby 插值**(CSM §3.13),
41
+ # 由 Ruby 求值;安全模式与字面 `#{` 都写成字面。字面 `#{` 会被转义成 `\#{`。两者在 AST1 里已经分开——真插值是
42
+ # +Node::Interpolation+,字面的 `#{` 落在 +Node::Text+ 里——所以边界不必再扫描
43
+ # (CSM §3.12:配对与屏蔽交给 prism)。
44
+ #
45
+ # == 两个转义层
46
+ #
47
+ # 产物里的纯文本要过两层转义,顺序不能颠倒:
48
+ #
49
+ # 1. #escape_markup:让 InlineParser 把纯文本当**字面**(`*星*` 必须写成
50
+ # `\*星\*`,否则重新解析会得到紧强调);
51
+ # 2. #escape_ruby:把该文本编码进 Ruby 双引号字面量(`\` → `\\`、`"` → `\"`、
52
+ # `#` → `\#` 以免与插值混淆)。
53
+ #
54
+ # 已由节点结构提供的标记(`**`、`[`……)只过第 2 层。
55
+ class Translator
56
+ # 会被 InlineParser 当作语法开启的字符(CSM §1.5–§1.11、§2、§3.13)。纯文本
57
+ # 里出现它们时必须转义;`/`、`:`、`-` 等只在特定上下文里才有意义,不在表内。
58
+ MARKUP_CHARS = ["\\", "`", "*", "~", "[", "]", "!", "<", "$", "#", "@"].freeze
59
+
60
+ # Ruby 双引号字面量的转义表。`#` 也转义,避免 `\#{` 里的 `#` 与随后的 `{`
61
+ # 被 Ruby 重新拼成插值。
62
+ RUBY_ESCAPES = { "\\" => "\\\\", "\"" => "\\\"", "#" => "\\#", "\n" => "\\n", "\t" => "\\t" }.freeze
63
+
64
+ # 行内代码的定界符上限(CSM §1.5:最多 2 个反引号)。
65
+ MAX_CODE_TICKS = 2
66
+
67
+ # 没有行内内容时传给 builder 的空字符串参数。
68
+ EMPTY_ARGUMENT = '""'
69
+
70
+ # 安全模式下块级嵌入的占位注释(CSM §3.14)。
71
+ EMBED_PLACEHOLDER = "# [csm] ruby 嵌入已省略(unsafe: false)"
72
+
73
+ def self.to_ruby(node, **options)
74
+ new(**options).to_ruby(node)
75
+ end
76
+
77
+ # +indent+ 是产物每层嵌套的缩进宽度;+builder+ 是产物里 builder 变量的名字。
78
+ # +unsafe+ 为 +false+(默认)时嵌入节点不写进产物(CSM §3.14)。
79
+ def initialize(indent: 2, builder: "builder", unsafe: false)
80
+ @indent = " " * indent
81
+ @builder = builder
82
+ @unsafe = unsafe
83
+ end
84
+
85
+ # 翻译一棵树(+Node::Document+ 或任意子节点),返回 Ruby 源码字符串。
86
+ #
87
+ # 第一个 `__END__` 必须排在 `to_document` **之后**:Ruby 的 `__END__` 会截断
88
+ # 其后的程序文本,排在它后面的语句不会执行(CSM §3.1、§4)。
89
+ def to_ruby(node)
90
+ parts = [assignment] + statements(node, 0) + [finish]
91
+ marker = end_marker_of(node)
92
+ parts.concat(end_marker_statement(marker)) if marker
93
+ parts.join("\n")
94
+ end
95
+
96
+ private
97
+
98
+ def assignment = "#{@builder} = MDOM::Builder.new"
99
+
100
+ def finish = "#{@builder}.to_document"
101
+
102
+ def children_of(node)
103
+ node.type == :document ? node.children : [node]
104
+ end
105
+
106
+ def end_marker_of(node)
107
+ children_of(node).find { |child| child.type == :end_marker }
108
+ end
109
+
110
+ # -- 骨架 -----------------------------------------------------------------
111
+
112
+ def statements(node, depth)
113
+ children_of(node).reject { |child| child.type == :end_marker }
114
+ .flat_map { |child| statement(child, depth) }
115
+ end
116
+
117
+ def line(depth, text) = (@indent * depth) + text
118
+
119
+ # 逐节点分派。返回语句行数组(已含缩进)。
120
+ def statement(node, depth)
121
+ case node.type
122
+ when :heading then [line(depth, heading_call(node))]
123
+ when :paragraph then [line(depth, paragraph_call(node))]
124
+ when :hrule then [line(depth, "#{@builder}.hrule")]
125
+ when :code_block then [line(depth, code_block_call(node))]
126
+ when :math_block then [line(depth, math_block_call(node))]
127
+ when :table then block_statement(node, depth, "#{@builder}.table")
128
+ when :table_row then block_statement(node, depth, "#{@builder}.row")
129
+ when :table_header_cell then [line(depth, cell_call(node, "header_cell"))]
130
+ when :table_cell then [line(depth, cell_call(node, "cell"))]
131
+ when :term then [line(depth, term_call(node))]
132
+ when :cite then [line(depth, cite_call(node))]
133
+ when :definition then block_statement(node, depth, "#{@builder}.definition")
134
+ when :list_item then block_statement(node, depth, item_head(node))
135
+ when :blockquote then block_statement(node, depth, "#{@builder}.blockquote")
136
+ when :list then block_statement(node, depth, list_head(node))
137
+ when :definition_list then block_statement(node, depth, "#{@builder}.definition_list")
138
+ when :div then block_statement(node, depth, "#{@builder}.division")
139
+ when :ruby_embed then ruby_embed_statement(node, depth)
140
+ when :end_marker then end_marker_statement(node)
141
+ else raise Error, "cannot translate #{node.type.inspect} to Ruby"
142
+ end
143
+ end
144
+
145
+ # 块级调用:无子节点时退化为单行,有子节点时用 `do ... end` 包住。
146
+ # 内容策略为 FLOW 的容器只收块级子节点(CSM 元素表),这里原样递归它们。
147
+ def block_statement(node, depth, head)
148
+ body = node.children.flat_map { |child| statement(child, depth + 1) }
149
+ return [line(depth, head)] if body.empty?
150
+
151
+ [line(depth, "#{head} do"), *body, line(depth, "end")]
152
+ end
153
+
154
+ # -- 块级节点 -------------------------------------------------------------
155
+
156
+ def heading_call(node)
157
+ args = [node.level.to_s]
158
+ argument = inline_argument(node)
159
+ args << argument if argument
160
+ args << "closed: true" if node.closed?
161
+ call(@builder, "heading", args)
162
+ end
163
+
164
+ # 引用来源(`>-`,PHRASING)。现在它是 Blockquote 的普通子节点。
165
+ def cite_call(node)
166
+ "#{@builder}.cite(#{inline_argument(node) || EMPTY_ARGUMENT})"
167
+ end
168
+
169
+ def paragraph_call(node)
170
+ "#{@builder}.paragraph(#{inline_argument(node) || EMPTY_ARGUMENT})"
171
+ end
172
+
173
+ def term_call(node)
174
+ "#{@builder}.term(#{inline_argument(node) || EMPTY_ARGUMENT})"
175
+ end
176
+
177
+ def item_head(node)
178
+ call(@builder, "item", item_arguments(node))
179
+ end
180
+
181
+ def item_arguments(node)
182
+ args = ["marker: #{ruby_literal(node.marker)}"]
183
+ args << "task: true" if node.task?
184
+ args << "checked: #{node.checked.inspect}" unless node.checked.nil?
185
+ args
186
+ end
187
+
188
+ def list_head(node)
189
+ args = [
190
+ "ordered: #{node.ordered?}",
191
+ "task: #{node.task?}",
192
+ "marker: #{ruby_literal(node.attributes[:marker])}"
193
+ ]
194
+ call(@builder, "list", args)
195
+ end
196
+
197
+ def code_block_call(node)
198
+ args = [ruby_literal(node.code)]
199
+ args << "lang: #{ruby_literal(node.lang)}" if node.lang
200
+ args << "params: #{ruby_literal(node.params)}" if node.params
201
+ fence = node.attributes[:fence]
202
+ args << "fence: #{fence}" unless fence.nil? || fence == 3
203
+ call(@builder, "code_block", args)
204
+ end
205
+
206
+ # 块级公式:+tex+ 按字面(CSM §1.11)。
207
+ def math_block_call(node)
208
+ "#{@builder}.math_block(#{ruby_literal(node.tex)})"
209
+ end
210
+
211
+ # 表格单元格:正文(PHRASING)作字符串参数,+align+ 按需带上。
212
+ def cell_call(node, name)
213
+ args = [inline_argument(node) || EMPTY_ARGUMENT]
214
+ args << "align: #{node.align.inspect}" if node.align
215
+ call(@builder, name, args)
216
+ end
217
+
218
+ def call(receiver, name, args)
219
+ args.empty? ? "#{receiver}.#{name}" : "#{receiver}.#{name}(#{args.join(", ")})"
220
+ end
221
+
222
+ # Ruby 嵌入块:源码原文(已剥离 N 层 CSM 缩进)按所在层级重新缩进后原样注入
223
+ # (CSM §3.6)。`begin`/`end` 标志与 `.render` 收尾都在原文里,不再改动。
224
+ # 安全模式下不写源码,只留一行注释占位(CSM §3.14)。
225
+ def ruby_embed_statement(node, depth)
226
+ return [line(depth, EMBED_PLACEHOLDER)] unless @unsafe
227
+
228
+ prefix = @indent * depth
229
+ node.source.split("\n", -1).map { |text| text.empty? ? text : prefix + text }
230
+ end
231
+
232
+ # 第一个 `__END__` 原样写入,其后内容交给 Ruby 的 `DATA`(CSM §4)。
233
+ def end_marker_statement(node)
234
+ data = node.attributes[:data]
235
+ return ["__END__"] if data.nil? || data.empty?
236
+
237
+ ["__END__", *data.split("\n", -1)]
238
+ end
239
+
240
+ # -- 行内内容 -------------------------------------------------------------
241
+
242
+ def inline?(node) = node.is_a?(Node::Inline)
243
+
244
+ def inline_nodes(node)
245
+ node.children.select { |child| inline?(child) }
246
+ end
247
+
248
+ # 行内内容的 Ruby 字符串字面量;没有行内内容时返回 +nil+。
249
+ #
250
+ # 兜底:以 +inline: false+ 解析出的 AST 没有行内节点,此时原文本身就是 CSM
251
+ # 源码,直接交给 builder 重新解析。
252
+ def inline_argument(node)
253
+ nodes = inline_nodes(node)
254
+ return "\"#{inline_fragments(nodes)}\"" unless nodes.empty?
255
+
256
+ text = node.attributes[:text].to_s
257
+ return nil if text.empty?
258
+
259
+ "\"#{escape_ruby(text)}\""
260
+ end
261
+
262
+ # 行内节点序列化:返回**Ruby 字面量体**(已转义,插值保持为裸 `#{}`)。
263
+ #
264
+ # 标记(`**`、`[`……)由节点结构给出,只过 Ruby 转义;纯文本额外过一层 CSM
265
+ # 转义,让 InlineParser 重新解析时把它当字面。
266
+ def inline_fragments(nodes)
267
+ nodes.map { |node| inline_fragment(node) }.join
268
+ end
269
+
270
+ def inline_fragment(node)
271
+ case node.type
272
+ when :text then escape_ruby(escape_markup(node.text))
273
+ when :interpolation then interpolation_fragment(node)
274
+ when :emphasis then wrap_markup(node, "*")
275
+ when :strong then wrap_markup(node, "**")
276
+ when :del then wrap_markup(node, "~~")
277
+ when :ins then wrap_markup(node, "~+", "+~")
278
+ when :code_span then code_span_fragment(node.code)
279
+ when :link then link_fragment(node, "")
280
+ when :image then link_fragment(node, "!")
281
+ when :inline_quote then inline_quote_fragment(node)
282
+ when :wiki_link then wiki_link_fragment(node)
283
+ when :mention then mention_fragment(node)
284
+ when :meter then bar_fragment(node, "[=")
285
+ when :progress then bar_fragment(node, "[->")
286
+ when :math then wrap_markup(node, "$")
287
+ when :hard_break then escape_ruby("\\\n")
288
+ else raise Error, "cannot translate inline #{node.type.inspect} to Ruby"
289
+ end
290
+ end
291
+
292
+ # 插值:不安全时是真正的 Ruby 插值(交 Ruby 求值);安全时退化为**字面**
293
+ # `#{expr}`——求值后它落回 CSM 源码,由 builder 重新解析成未求值的
294
+ # Interpolation 节点,供 (6) 按独立样式输出(CSM §3.14)。
295
+ def interpolation_fragment(node)
296
+ literal = "\#{#{node.expr}}"
297
+ @unsafe ? literal : escape_ruby(literal)
298
+ end
299
+
300
+ def wrap_markup(node, opener, closer = opener)
301
+ "#{escape_ruby(opener)}#{inline_fragments(node.children)}#{escape_ruby(closer)}"
302
+ end
303
+
304
+ # `[标签](uri "title")` / `![alt](uri "title")`。
305
+ def link_fragment(node, sigil)
306
+ label = escape_ruby("#{sigil}[")
307
+ body = inline_fragments(node.children)
308
+ target = escape_ruby("](#{destination(node)})")
309
+ "#{label}#{body}#{target}"
310
+ end
311
+
312
+ # `[文本](@uri)` → `<q cite="uri">`。
313
+ def inline_quote_fragment(node)
314
+ label = escape_ruby("[")
315
+ body = inline_fragments(node.children)
316
+ target = escape_ruby("](@#{node.uri})")
317
+ "#{label}#{body}#{target}"
318
+ end
319
+
320
+ # `[[target|label]]`;省略 label 时用 +[[target]]+。
321
+ def wiki_link_fragment(node)
322
+ target = node.attributes[:target]
323
+ return escape_ruby("[[#{target}]]") if node.attributes[:label].nil?
324
+
325
+ "#{escape_ruby("[[#{target}|")}#{inline_fragments(node.children)}#{escape_ruby("]]")}"
326
+ end
327
+
328
+ def mention_fragment(node)
329
+ scope = node.attributes[:kind]
330
+ escape_ruby("@#{scope ? "#{scope}:" : ""}#{node.attributes[:name]}")
331
+ end
332
+
333
+ def bar_fragment(node, opener)
334
+ "#{escape_ruby(opener)}#{inline_fragments(node.children)}#{escape_ruby("]")}"
335
+ end
336
+
337
+ # 链接/图片的目的地(`uri "title"`)。uri 与 title 是字面,不做 CSM 转义。
338
+ def destination(node)
339
+ title = node.attributes[:title]
340
+ title ? "#{node.uri} \"#{title}\"" : node.uri.to_s
341
+ end
342
+
343
+ # 行内代码:反引号数量要与内容里的反引号错开(CSM §1.5),内容按字面。
344
+ def code_span_fragment(code)
345
+ ticks = "`" * (code.include?("`") ? MAX_CODE_TICKS : 1)
346
+ escape_ruby("#{ticks}#{code}#{ticks}")
347
+ end
348
+
349
+ # -- 转义 -----------------------------------------------------------------
350
+
351
+ # 第 1 层:纯文本 → 其 CSM 源码形式(给每个标记字符加 `\`)。
352
+ def escape_markup(text)
353
+ text.each_char.map { |char| MARKUP_CHARS.include?(char) ? "\\#{char}" : char }.join
354
+ end
355
+
356
+ # 第 2 层:CSM 源码 → Ruby 双引号字面量的体。
357
+ def escape_ruby(text)
358
+ text.each_char.map { |char| RUBY_ESCAPES.fetch(char, char) }.join
359
+ end
360
+
361
+ # 值 → Ruby 字面量(含引号)。
362
+ def ruby_literal(value)
363
+ return "nil" if value.nil?
364
+
365
+ "\"#{escape_ruby(value.to_s)}\""
366
+ end
367
+ end
368
+ end
369
+
370
+ module MDOM
371
+ module Node
372
+ # 便捷入口:+node.to_ruby(**options)+ ≡ +MDOM::Translator.to_ruby(node, **options)+。
373
+ class BaseNode
374
+ def to_ruby(**options)
375
+ Translator.to_ruby(self, **options)
376
+ end
377
+ end
378
+ end
379
+ end
data/lib/mdom/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MDOM
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/mdom.rb CHANGED
@@ -1,16 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "mdom/error"
3
4
  require_relative "mdom/version"
4
- require_relative "mdom/node"
5
- require_relative "mdom/nodes"
5
+ require_relative "mdom/hooks"
6
6
  require_relative "mdom/parser"
7
- require_relative "mdom/inline_parser"
8
- require_relative "mdom/serializer"
7
+ require_relative "mdom/translator"
9
8
  require_relative "mdom/builder"
9
+ require_relative "mdom/html_serializer"
10
+ require_relative "mdom/markdown_serializer"
10
11
 
12
+ # CSM(Complex Simple Markup)的 Ruby 工具链。
13
+ #
14
+ # `require "mdom"` 是**总入口**,会把两侧都加载进来(解析侧需要 `prism`)。只想在
15
+ # mruby/WASM 上构造文档时,直接 `require "mdom/builder"` 即可——那一侧零 gem 依赖。
16
+ #
17
+ # 六个转换关系见 `docs/CSM.md` §6;这里接的是其中已经实现的四段:
18
+ # (1) 解析、(2) 翻译、(3)(5) `to_markdown`、(6) HTML 序列化。(4) 执行不在 mdom
19
+ # 范围内——它就是宿主的一行 `eval`。
11
20
  module MDOM
12
- # Serialize a node (or subtree) back to a Markdown string.
13
- def self.serialize(node)
14
- Serializer.serialize(node)
21
+ # 一次 #compile 的结果。
22
+ #
23
+ # 三个阶段的产物都在这里;钩子改写过的版本就是这里看到的版本。
24
+ Compiled = Struct.new(:document, :ruby, :html, :meta, keyword_init: true)
25
+
26
+ # (1):CSM 源码 → AST1。等价于 +MDOM::Parser.parse+。
27
+ def self.parse(source, **options)
28
+ Parser.parse(source, **options)
29
+ end
30
+
31
+ # (1)(2)(6) 全链路,并在五个点上触发钩子(CSM §5.3)。
32
+ #
33
+ # 顺序:
34
+ #
35
+ # 1. `Parser.parse` → 触发 `:after_parse(doc)`
36
+ # 2. 触发 **`:before_compile(doc)`** —— 此时树仍可改写,程序化生成目录就放这里
37
+ # 3. `Translator.to_ruby` → 触发 `:after_compile(doc, ruby)`
38
+ # 4. 触发 `:before_serialize(doc)`
39
+ # 5. `HtmlSerializer.serialize` → 触发 `:after_serialize(html, meta)`
40
+ #
41
+ # 参数:
42
+ # * +hooks:+ —— 本次调用专用的临时钩子,形如 +{ before_compile: [proc] }+;
43
+ # 与类级注册表里的钩子一起执行,顺序是「全局 → 临时」;
44
+ # * +parser:+ / +translator:+ / +serializer:+ —— 分别透传给三个阶段。
45
+ #
46
+ # 返回值:+MDOM::Compiled+(含 +document+ / +ruby+ / +html+ / +meta+)。
47
+ def self.compile(source, hooks: {}, parser: {}, translator: {}, serializer: {})
48
+ document = Parser.parse(source, **parser)
49
+ run_hooks(:after_parse, hooks, document)
50
+
51
+ run_hooks(:before_compile, hooks, document)
52
+ ruby = Translator.to_ruby(document, **translator)
53
+ run_hooks(:after_compile, hooks, document, ruby)
54
+
55
+ run_hooks(:before_serialize, hooks, document)
56
+ html = HtmlSerializer.serialize(document, **serializer)
57
+ run_hooks(:after_serialize, hooks, html, document.meta)
58
+
59
+ Compiled.new(document: document, ruby: ruby, html: html, meta: document.meta)
60
+ end
61
+
62
+ # 触发一个钩子点:先全局注册表,再本次调用传入的临时钩子。
63
+ def self.run_hooks(point, hooks, *args)
64
+ Hooks.run(point, *args, extra: hooks.fetch(point, []))
15
65
  end
66
+ private_class_method :run_hooks
16
67
  end
metadata CHANGED
@@ -1,27 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mdom
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: MDOM parses Markdown into a node tree (block + inline), supports traversal,
13
- querying and mutation, and serializes back to a Markdown string.
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: prism
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0.19'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0.19'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '2.0'
32
+ description: 'MDOM parses CSM documents into a document tree (AST) and translates
33
+ it to Ruby source (safe by default: no embedded code is emitted) or to HTML. Markdown
34
+ is treated as syntactic sugar for Ruby, so a `.rb.md` document is both readable
35
+ Markdown and an executable Ruby program.'
14
36
  executables: []
15
37
  extensions: []
16
38
  extra_rdoc_files: []
17
39
  files:
40
+ - CHANGELOG.md
41
+ - LICENSE.txt
42
+ - README.md
43
+ - USAGE.md
18
44
  - lib/mdom.rb
45
+ - lib/mdom/block_detector.rb
19
46
  - lib/mdom/builder.rb
47
+ - lib/mdom/error.rb
48
+ - lib/mdom/hooks.rb
49
+ - lib/mdom/html_serializer.rb
20
50
  - lib/mdom/inline_parser.rb
51
+ - lib/mdom/markdown_serializer.rb
21
52
  - lib/mdom/node.rb
22
- - lib/mdom/nodes.rb
53
+ - lib/mdom/node/inline_nodes.rb
54
+ - lib/mdom/node/nodes.rb
23
55
  - lib/mdom/parser.rb
24
- - lib/mdom/serializer.rb
56
+ - lib/mdom/tokenizer.rb
57
+ - lib/mdom/translator.rb
25
58
  - lib/mdom/version.rb
26
59
  licenses:
27
60
  - MIT
@@ -43,5 +76,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
43
76
  requirements: []
44
77
  rubygems_version: 3.6.7
45
78
  specification_version: 4
46
- summary: 'A Markdown DOM: a self-contained AST for Markdown.'
79
+ summary: CSM (Complex Simple Markup) toolchain for Ruby.
47
80
  test_files: []