mdom 0.3.0 → 0.4.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.
@@ -8,65 +8,68 @@ module MDOM
8
8
  # (2) AST1 → Ruby 源码(CSM §3.1;docs/IMPLEMENTATION.md §3)。
9
9
  #
10
10
  # 把一棵 AST1 翻译成**一段顶层顺序执行的 Ruby 程序**:排版节点变成 builder
11
- # 调用,嵌入节点(RubyEmbed / 行内插值)原样注入。产出的只是源码字符串,不
12
- # 绑定任何执行方式(docs/IMPLEMENTATION.md §6.2)——`eval`、落盘后 `require`、
13
- # 交给 mruby 都可以。
11
+ # 调用,嵌入节点(块级嵌入 / 行内插值)按 +unsafe+ 决定是否原样注入。产出的
12
+ # 只是源码字符串,不绑定任何执行方式(docs/IMPLEMENTATION.md §6.2)——`eval`、
13
+ # 落盘后 `require`、交给 mruby 都可以。
14
14
  #
15
- # MDOM::Translator.to_ruby(MDOM::Parser.parse("# 标题\n"))
16
- # # => "builder = MDOM::Builder.new\nbuilder.heading(1, \"标题\")\nbuilder.to_document"
15
+ # puts MDOM::Translator.to_ruby(MDOM::Parser.parse("# 标题\n"))
16
+ # # builder = MDOM::Builder.new
17
+ # # builder.heading(1) do
18
+ # # builder.text("标题")
19
+ # # end
20
+ # # builder.to_document
17
21
  #
18
22
  # 两侧的结构约束(docs/IMPLEMENTATION.md §6.3):排版段一律经由 +builder.*+
19
- # 接收者,形式可辨识;嵌入段保持原样,边界明确(Ruby 块原文 / 字符串字面量里的
20
- # `#{}`)。这样宿主才有可能判断一段产物是否能在 mruby 上跑。
23
+ # 接收者,形式可辨识;嵌入段保持原样,边界明确(Ruby 块原文 / `interpolation`
24
+ # 的块)。这样宿主才有可能判断一段产物是否能在 mruby 上跑。
21
25
  #
22
- # == 安全闸门(CSM §3.14)
26
+ # == 行内内容结构化,不回吐标记
23
27
  #
24
- # +unsafe+ 是**翻译期**参数,默认 +false+(安全):嵌入节点不写进产物,所以
25
- # 产物静态上不含用户代码——块级 `RubyEmbed` 只留一行注释占位,行内插值退化为
26
- # 字面 `#{expr}`。+unsafe: true+ 才把嵌入原样注入。
28
+ # 行内容器(段落、标题、单元格……)在产物里是**块形式**的 builder 调用,每个
29
+ # 行内子节点翻译成对应的 builder 行内方法:
27
30
  #
28
- # 安全模式下宿主不需要(也不应该)拿产物去 `eval`:未求值的嵌入由 (6) 直接吃
29
- # AST1 就能按独立样式输出。求值本身只是宿主的一行 `eval`,mdom 不含求值器。
31
+ # builder.paragraph do
32
+ # builder.text("正文 ")
33
+ # builder.strong do
34
+ # builder.text("粗")
35
+ # end
36
+ # end
30
37
  #
31
- # 本类**不依赖 prism**:行内边界已由 InlineParser 判好,这里只做序列化。它只
32
- # +require+ 节点定义(builder 侧)。
38
+ # 产物因此**不经过 InlineParser**:翻译器不输出 CSM 标记文本,也就没有「产物
39
+ # 执行时再解析一次」这一步,排版段真正落在 mruby 子集里(CSM §3.1)。唯一仍
40
+ # 需要行内解析器的形态是 +inline: false+ 解析出的 AST——那时树里只有原文、没有
41
+ # 行内节点(见 #inline_block_statement)。
42
+ #
43
+ # == 插值
33
44
  #
34
- # == 行内内容为什么重新生成标记
45
+ # +unsafe: true+ 时插值是 `builder.interpolation { expr }`:表达式由 Ruby 求值,
46
+ # 值按 #render 的分派表插入当前位置(CSM §3.3)。+unsafe: false+(默认)时是
47
+ # `builder.interpolation("expr")`,构造**未求值**的插值节点(CSM §3.14),交
48
+ # (6) 按独立样式输出。
35
49
  #
36
- # 行内容器(段落、标题……)在产物里以**字符串参数**交给 builder,而 builder
37
- # 会用 InlineParser 重新解析这些字符串。因此本类要把 AST1 的行内子树重新序列化
38
- # 成 CSM 标记文本(`**粗**`、`[标签](uri)`……)。
50
+ # == 安全闸门(CSM §3.14)
39
51
  #
40
- # 插值单独处理:+unsafe: true+ 时 `#{}` 在产物里是**真正的 Ruby 插值**(CSM §3.13),
41
- # 由 Ruby 求值;安全模式与字面 `#{` 都写成字面。字面 `#{` 会被转义成 `\#{`。两者在 AST1 里已经分开——真插值是
42
- # +Node::Interpolation+,字面的 `#{` 落在 +Node::Text+ 里——所以边界不必再扫描
43
- # (CSM §3.12:配对与屏蔽交给 prism)。
52
+ # +unsafe+ 是**翻译期**参数,默认 +false+(安全):块级嵌入不写进产物(只留一
53
+ # 行注释占位),插值构造未求值节点,所以产物静态上不含用户代码。+unsafe: true+
54
+ # 才把嵌入原样注入。
44
55
  #
45
- # == 两个转义层
56
+ # 安全模式下宿主不需要(也不应该)拿产物去 `eval`:未求值的嵌入由 (6) 直接吃
57
+ # AST1 就能按独立样式输出。求值本身只是宿主的一行 `eval`,mdom 不含求值器。
46
58
  #
47
- # 产物里的纯文本要过两层转义,顺序不能颠倒:
59
+ # 本类**不依赖 prism**:行内边界已由 InlineParser 判好,这里只做序列化。它只
60
+ # +require+ 节点定义(builder 侧)。
48
61
  #
49
- # 1. #escape_markup:让 InlineParser 把纯文本当**字面**(`*星*` 必须写成
50
- # `\*星\*`,否则重新解析会得到紧强调);
51
- # 2. #escape_ruby:把该文本编码进 Ruby 双引号字面量(`\` → `\\`、`"` → `\"`、
52
- # `#` → `\#` 以免与插值混淆)。
62
+ # == 转义
53
63
  #
54
- # 已由节点结构提供的标记(`**`、`[`……)只过第 2 层。
64
+ # 只有一层转义(#escape_ruby):把值编码进 Ruby 双引号字面量(`\` → `\\`、
65
+ # `"` → `\"`、`#` → `\#`)。旧版为「让 InlineParser 把纯文本当字面」而有的
66
+ # 第二层 CSM 转义(CSM §3.2 的 `\*`、`` \` ``)随行内结构化一起消失——产物里
67
+ # 不再有任何等待重新解析的 CSM 文本。
55
68
  class Translator
56
- # 会被 InlineParser 当作语法开启的字符(CSM §1.5–§1.11、§2、§3.13)。纯文本
57
- # 里出现它们时必须转义;`/`、`:`、`-` 等只在特定上下文里才有意义,不在表内。
58
- MARKUP_CHARS = ["\\", "`", "*", "~", "[", "]", "!", "<", "$", "#", "@"].freeze
59
-
60
69
  # Ruby 双引号字面量的转义表。`#` 也转义,避免 `\#{` 里的 `#` 与随后的 `{`
61
70
  # 被 Ruby 重新拼成插值。
62
71
  RUBY_ESCAPES = { "\\" => "\\\\", "\"" => "\\\"", "#" => "\\#", "\n" => "\\n", "\t" => "\\t" }.freeze
63
72
 
64
- # 行内代码的定界符上限(CSM §1.5:最多 2 个反引号)。
65
- MAX_CODE_TICKS = 2
66
-
67
- # 没有行内内容时传给 builder 的空字符串参数。
68
- EMPTY_ARGUMENT = '""'
69
-
70
73
  # 安全模式下块级嵌入的占位注释(CSM §3.14)。
71
74
  EMBED_PLACEHOLDER = "# [csm] ruby 嵌入已省略(unsafe: false)"
72
75
 
@@ -119,17 +122,17 @@ module MDOM
119
122
  # 逐节点分派。返回语句行数组(已含缩进)。
120
123
  def statement(node, depth)
121
124
  case node.type
122
- when :heading then [line(depth, heading_call(node))]
123
- when :paragraph then [line(depth, paragraph_call(node))]
125
+ when :heading then inline_block_statement(node, depth, "heading", heading_arguments(node))
126
+ when :paragraph then inline_block_statement(node, depth, "paragraph")
124
127
  when :hrule then [line(depth, "#{@builder}.hrule")]
125
128
  when :code_block then [line(depth, code_block_call(node))]
126
129
  when :math_block then [line(depth, math_block_call(node))]
127
130
  when :table then block_statement(node, depth, "#{@builder}.table")
128
131
  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))]
132
+ when :table_header_cell then inline_block_statement(node, depth, "header_cell", cell_arguments(node))
133
+ when :table_cell then inline_block_statement(node, depth, "cell", cell_arguments(node))
134
+ when :term then inline_block_statement(node, depth, "term")
135
+ when :cite then inline_block_statement(node, depth, "cite")
133
136
  when :definition then block_statement(node, depth, "#{@builder}.definition")
134
137
  when :list_item then block_statement(node, depth, item_head(node))
135
138
  when :blockquote then block_statement(node, depth, "#{@builder}.blockquote")
@@ -153,25 +156,10 @@ module MDOM
153
156
 
154
157
  # -- 块级节点 -------------------------------------------------------------
155
158
 
156
- def heading_call(node)
159
+ def heading_arguments(node)
157
160
  args = [node.level.to_s]
158
- argument = inline_argument(node)
159
- args << argument if argument
160
161
  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})"
162
+ args
175
163
  end
176
164
 
177
165
  def item_head(node)
@@ -181,6 +169,8 @@ module MDOM
181
169
  def item_arguments(node)
182
170
  args = ["marker: #{ruby_literal(node.marker)}"]
183
171
  args << "task: true" if node.task?
172
+ # 任务列表里的普通项:必须显式写 false,否则 builder 会从列表继承 task。
173
+ args << "task: false" if !node.task? && node.parent.is_a?(Node::List) && node.parent.task?
184
174
  args << "checked: #{node.checked.inspect}" unless node.checked.nil?
185
175
  args
186
176
  end
@@ -208,11 +198,9 @@ module MDOM
208
198
  "#{@builder}.math_block(#{ruby_literal(node.tex)})"
209
199
  end
210
200
 
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)
201
+ # 表格单元格:正文(PHRASING)由 #inline_block_statement 填充,+align+ 按需带上。
202
+ def cell_arguments(node)
203
+ node.align ? ["align: #{node.align.inspect}"] : []
216
204
  end
217
205
 
218
206
  def call(receiver, name, args)
@@ -245,118 +233,125 @@ module MDOM
245
233
  node.children.select { |child| inline?(child) }
246
234
  end
247
235
 
248
- # 行内内容的 Ruby 字符串字面量;没有行内内容时返回 +nil+。
236
+ # 行内容器(段落、标题、单元格……)与带行内子节点的行内节点:有行内子节点时
237
+ # 用 `do ... end` 逐条构造,没有时退化为单行调用。
249
238
  #
250
- # 兜底:以 +inline: false+ 解析出的 AST 没有行内节点,此时原文本身就是 CSM
251
- # 源码,直接交给 builder 重新解析。
252
- def inline_argument(node)
239
+ # 唯一的例外是 +inline: false+ 解析出的 AST:树里只有 +attributes[:text]+ 原文,
240
+ # 没有行内节点。此时把原文作为**字符串参数**追加到调用上交给 builder 重新解析
241
+ # ——这是产物里唯一仍需要行内解析器的形态。
242
+ def inline_block_statement(node, depth, name, args = [])
253
243
  nodes = inline_nodes(node)
254
- return "\"#{inline_fragments(nodes)}\"" unless nodes.empty?
244
+ return inline_block_lines(nodes, depth, call(@builder, name, args)) unless nodes.empty?
255
245
 
256
246
  text = node.attributes[:text].to_s
257
- return nil if text.empty?
258
-
259
- "\"#{escape_ruby(text)}\""
247
+ args = text.empty? ? args : insert_text_argument(args, ruby_literal(text))
248
+ [line(depth, call(@builder, name, args))]
260
249
  end
261
250
 
262
- # 行内节点序列化:返回**Ruby 字面量体**(已转义,插值保持为裸 `#{}`)。
263
- #
264
- # 标记(`**`、`[`……)由节点结构给出,只过 Ruby 转义;纯文本额外过一层 CSM
265
- # 转义,让 InlineParser 重新解析时把它当字面。
266
- def inline_fragments(nodes)
267
- nodes.map { |node| inline_fragment(node) }.join
251
+ # 文本是**位置参数**,必须排在关键字参数之前(`heading(1, "x", closed: true)`)。
252
+ def insert_text_argument(args, literal)
253
+ keyword = args.index { |arg| keyword_argument?(arg) }
254
+ return args + [literal] if keyword.nil?
255
+
256
+ args.dup.insert(keyword, literal)
268
257
  end
269
258
 
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"
259
+ # 形如 `name: value` 的产物片段(`closed: true` / `align: :left`)。
260
+ def keyword_argument?(argument)
261
+ colon = argument.index(": ")
262
+ return false if colon.nil? || colon.zero?
263
+
264
+ argument[0...colon].each_char.all? do |char|
265
+ char == "_" || (char >= "a" && char <= "z") || (char >= "0" && char <= "9")
289
266
  end
290
267
  end
291
268
 
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)
269
+ def inline_block_lines(nodes, depth, head)
270
+ [line(depth, "#{head} do"), *inline_statements(nodes, depth + 1), line(depth, "end")]
298
271
  end
299
272
 
300
- def wrap_markup(node, opener, closer = opener)
301
- "#{escape_ruby(opener)}#{inline_fragments(node.children)}#{escape_ruby(closer)}"
273
+ def inline_statements(nodes, depth)
274
+ nodes.flat_map { |node| inline_statement(node, depth) }
302
275
  end
303
276
 
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}"
277
+ # 行内节点 → builder 行内方法(CSM 元素表里的每一种行内构造)。
278
+ #
279
+ # 顺序与 +MDOM::InlineParser+ 的分派一致:叶子单行,带内容的走块;`interpolation`
280
+ # 是嵌入通道,单独处理(见 #interpolation_statement)。
281
+ def inline_statement(node, depth)
282
+ case node.type
283
+ when :text then [line(depth, "#{@builder}.text(#{ruby_literal(node.text)})")]
284
+ when :code_span then [line(depth, "#{@builder}.code_span(#{ruby_literal(node.code)})")]
285
+ when :hard_break then [line(depth, "#{@builder}.hardbreak")]
286
+ when :mention then mention_statement(node, depth)
287
+ when :emphasis then inline_block_statement(node, depth, "em")
288
+ when :strong then inline_block_statement(node, depth, "strong")
289
+ when :del then inline_block_statement(node, depth, "del")
290
+ when :ins then inline_block_statement(node, depth, "ins")
291
+ when :link then inline_block_statement(node, depth, "link", link_arguments(node))
292
+ when :image then inline_block_statement(node, depth, "image", image_arguments(node))
293
+ when :inline_quote then inline_block_statement(node, depth, "inline_quote", [ruby_literal(node.uri)])
294
+ when :wiki_link then inline_block_statement(node, depth, "wiki_link", wiki_link_arguments(node))
295
+ when :meter then inline_block_statement(node, depth, "meter", bar_arguments(node))
296
+ when :progress then inline_block_statement(node, depth, "progress", bar_arguments(node))
297
+ when :math then inline_block_statement(node, depth, "math", [ruby_literal(node.tex)])
298
+ when :interpolation then interpolation_statement(node, depth)
299
+ else raise Error, "cannot translate inline #{node.type.inspect} to Ruby"
300
+ end
310
301
  end
311
302
 
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}"
303
+ # `@user` / `@project:x` → `builder.mention("user", kind: "project")`。
304
+ def mention_statement(node, depth)
305
+ args = [ruby_literal(node.name)]
306
+ kind = node.attributes[:kind]
307
+ args << "kind: #{ruby_literal(kind)}" unless kind.nil?
308
+ [line(depth, call(@builder, "mention", args))]
318
309
  end
319
310
 
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("]]")}"
311
+ # `[标签](uri "title")` / `<uri>`。
312
+ def link_arguments(node)
313
+ args = [ruby_literal(node.uri)]
314
+ args << "title: #{ruby_literal(node.title)}" unless node.title.nil?
315
+ args
326
316
  end
327
317
 
328
- def mention_fragment(node)
329
- scope = node.attributes[:kind]
330
- escape_ruby("@#{scope ? "#{scope}:" : ""}#{node.attributes[:name]}")
318
+ # `![alt](uri "title")`:+alt+ 是替代文本原文,显示内容由子节点给出。
319
+ def image_arguments(node)
320
+ args = [ruby_literal(node.uri), "alt: #{ruby_literal(node.alt)}"]
321
+ args << "title: #{ruby_literal(node.title)}" unless node.title.nil?
322
+ args
331
323
  end
332
324
 
333
- def bar_fragment(node, opener)
334
- "#{escape_ruby(opener)}#{inline_fragments(node.children)}#{escape_ruby("]")}"
325
+ # `[[target|label]]`:+label+ 省略时不传。
326
+ def wiki_link_arguments(node)
327
+ args = [ruby_literal(node.attributes[:target])]
328
+ label = node.attributes[:label]
329
+ args << ruby_literal(label) unless label.nil?
330
+ args
335
331
  end
336
332
 
337
- # 链接/图片的目的地(`uri "title"`)。uri 与 title 是字面,不做 CSM 转义。
338
- def destination(node)
339
- title = node.attributes[:title]
340
- title ? "#{node.uri} \"#{title}\"" : node.uri.to_s
333
+ # `[=m/n]` / `[->m/n]`:+value+ / +max+ 按字面。
334
+ def bar_arguments(node)
335
+ [ruby_literal(node.value), ruby_literal(node.max)]
341
336
  end
342
337
 
343
- # 行内代码:反引号数量要与内容里的反引号错开(CSM §1.5),内容按字面。
344
- def code_span_fragment(code)
345
- ticks = "`" * (code.include?("`") ? MAX_CODE_TICKS : 1)
346
- escape_ruby("#{ticks}#{code}#{ticks}")
338
+ # 插值(CSM §3.13):不安全时把表达式交给 Ruby 求值(结果按 #render 的分派表
339
+ # 插入当前位置),安全时构造**未求值**的插值节点(CSM §3.14)。
340
+ def interpolation_statement(node, depth)
341
+ return [line(depth, "#{@builder}.interpolation(#{ruby_literal(node.expr)})")] unless @unsafe
342
+
343
+ expression_statements(node.expr, depth)
347
344
  end
348
345
 
349
- # -- 转义 -----------------------------------------------------------------
346
+ # `builder.interpolation { 表达式 }`;表达式含换行时改用 `do ... end`。
347
+ def expression_statements(expr, depth)
348
+ return [line(depth, "#{@builder}.interpolation { #{expr} }")] unless expr.include?("\n")
350
349
 
351
- # 第 1 层:纯文本 → 其 CSM 源码形式(给每个标记字符加 `\`)。
352
- def escape_markup(text)
353
- text.each_char.map { |char| MARKUP_CHARS.include?(char) ? "\\#{char}" : char }.join
350
+ body = expr.split("\n", -1).map { |text| line(depth + 1, text) }
351
+ [line(depth, "#{@builder}.interpolation do"), *body, line(depth, "end")]
354
352
  end
355
353
 
356
- # 第 2 层:CSM 源码 → Ruby 双引号字面量的体。
357
- def escape_ruby(text)
358
- text.each_char.map { |char| RUBY_ESCAPES.fetch(char, char) }.join
359
- end
354
+ # -- 转义 -----------------------------------------------------------------
360
355
 
361
356
  # 值 → Ruby 字面量(含引号)。
362
357
  def ruby_literal(value)
@@ -364,6 +359,11 @@ module MDOM
364
359
 
365
360
  "\"#{escape_ruby(value.to_s)}\""
366
361
  end
362
+
363
+ # CSM 源码文本(或任意值)→ Ruby 双引号字面量的体。
364
+ def escape_ruby(text)
365
+ text.each_char.map { |char| RUBY_ESCAPES.fetch(char, char) }.join
366
+ end
367
367
  end
368
368
  end
369
369
 
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.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mdom
@@ -29,10 +29,10 @@ dependencies:
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
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.'
32
+ description: MDOM is the Ruby toolchain for CSM (Complex Simple Markup). It parses
33
+ CSM documents into an AST, translates them to safe-by-default Ruby source, re-serializes
34
+ them to CSM, and renders HTML. CSM treats Markdown as syntactic sugar for Ruby,
35
+ so a `.rb.md` document is both readable Markdown and an executable Ruby program.
36
36
  executables: []
37
37
  extensions: []
38
38
  extra_rdoc_files: []