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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +165 -1
- data/README.md +50 -12
- data/USAGE.md +89 -14
- data/lib/mdom/block_detector.rb +293 -60
- data/lib/mdom/builder.rb +151 -27
- data/lib/mdom/error.rb +0 -4
- data/lib/mdom/html_serializer.rb +19 -1
- data/lib/mdom/inline_parser.rb +402 -169
- data/lib/mdom/markdown_serializer.rb +69 -11
- data/lib/mdom/node.rb +9 -1
- data/lib/mdom/parser.rb +30 -2
- data/lib/mdom/tokenizer.rb +64 -21
- data/lib/mdom/translator.rb +148 -148
- data/lib/mdom/version.rb +1 -1
- metadata +5 -5
data/lib/mdom/translator.rb
CHANGED
|
@@ -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
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# 调用,嵌入节点(块级嵌入 / 行内插值)按 +unsafe+ 决定是否原样注入。产出的
|
|
12
|
+
# 只是源码字符串,不绑定任何执行方式(docs/IMPLEMENTATION.md §6.2)——`eval`、
|
|
13
|
+
# 落盘后 `require`、交给 mruby 都可以。
|
|
14
14
|
#
|
|
15
|
-
# MDOM::Translator.to_ruby(MDOM::Parser.parse("# 标题\n"))
|
|
16
|
-
# #
|
|
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
|
-
#
|
|
23
|
+
# 接收者,形式可辨识;嵌入段保持原样,边界明确(Ruby 块原文 / `interpolation`
|
|
24
|
+
# 的块)。这样宿主才有可能判断一段产物是否能在 mruby 上跑。
|
|
21
25
|
#
|
|
22
|
-
# ==
|
|
26
|
+
# == 行内内容结构化,不回吐标记
|
|
23
27
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# 字面 `#{expr}`。+unsafe: true+ 才把嵌入原样注入。
|
|
28
|
+
# 行内容器(段落、标题、单元格……)在产物里是**块形式**的 builder 调用,每个
|
|
29
|
+
# 行内子节点翻译成对应的 builder 行内方法:
|
|
27
30
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
31
|
+
# builder.paragraph do
|
|
32
|
+
# builder.text("正文 ")
|
|
33
|
+
# builder.strong do
|
|
34
|
+
# builder.text("粗")
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
30
37
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
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
|
-
#
|
|
37
|
-
# 会用 InlineParser 重新解析这些字符串。因此本类要把 AST1 的行内子树重新序列化
|
|
38
|
-
# 成 CSM 标记文本(`**粗**`、`[标签](uri)`……)。
|
|
50
|
+
# == 安全闸门(CSM §3.14)
|
|
39
51
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
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
|
-
#
|
|
50
|
-
# `\*星\*`,否则重新解析会得到紧强调);
|
|
51
|
-
# 2. #escape_ruby:把该文本编码进 Ruby 双引号字面量(`\` → `\\`、`"` → `\"`、
|
|
52
|
-
# `#` → `\#` 以免与插值混淆)。
|
|
62
|
+
# == 转义
|
|
53
63
|
#
|
|
54
|
-
#
|
|
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
|
|
123
|
-
when :paragraph then
|
|
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
|
|
130
|
-
when :table_cell then
|
|
131
|
-
when :term then
|
|
132
|
-
when :cite then
|
|
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
|
|
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
|
-
|
|
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
|
|
212
|
-
def
|
|
213
|
-
|
|
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
|
-
#
|
|
236
|
+
# 行内容器(段落、标题、单元格……)与带行内子节点的行内节点:有行内子节点时
|
|
237
|
+
# 用 `do ... end` 逐条构造,没有时退化为单行调用。
|
|
249
238
|
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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
|
|
301
|
-
|
|
273
|
+
def inline_statements(nodes, depth)
|
|
274
|
+
nodes.flat_map { |node| inline_statement(node, depth) }
|
|
302
275
|
end
|
|
303
276
|
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
# `
|
|
313
|
-
def
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
"
|
|
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
|
-
# `[
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
318
|
+
# ``:+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
|
-
|
|
334
|
-
|
|
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
|
-
#
|
|
338
|
-
def
|
|
339
|
-
|
|
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
|
-
#
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
352
|
-
|
|
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
|
-
#
|
|
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
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.
|
|
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:
|
|
33
|
-
|
|
34
|
-
|
|
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: []
|