bitclust-core 1.3.0 → 1.6.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/Gemfile +3 -0
- data/README.md +65 -0
- data/Rakefile +32 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
- data/data/bitclust/searchpage/index.html +90 -0
- data/data/bitclust/template/class +69 -8
- data/data/bitclust/template/function +2 -2
- data/data/bitclust/template/layout +11 -3
- data/data/bitclust/template/method +2 -2
- data/data/bitclust/template/search +1 -1
- data/data/bitclust/template.epub/class +22 -1
- data/data/bitclust/template.epub/function +2 -2
- data/data/bitclust/template.epub/layout +1 -3
- data/data/bitclust/template.epub/method +2 -2
- data/data/bitclust/template.lillia/class +26 -5
- data/data/bitclust/template.lillia/layout +1 -3
- data/data/bitclust/template.lillia/method +2 -2
- data/data/bitclust/template.offline/class +48 -4
- data/data/bitclust/template.offline/function +2 -2
- data/data/bitclust/template.offline/layout +27 -3
- data/data/bitclust/template.offline/method +2 -2
- data/lib/bitclust/app.rb +16 -17
- data/lib/bitclust/capi_converter.rb +20 -0
- data/lib/bitclust/classentry.rb +67 -17
- data/lib/bitclust/compat.rb +2 -2
- data/lib/bitclust/completion.rb +58 -27
- data/lib/bitclust/crossrubyutils.rb +8 -5
- data/lib/bitclust/database.rb +28 -8
- data/lib/bitclust/doc_converter.rb +63 -0
- data/lib/bitclust/docentry.rb +1 -1
- data/lib/bitclust/entity_splitter.rb +221 -0
- data/lib/bitclust/entry.rb +47 -7
- data/lib/bitclust/functiondatabase.rb +13 -1
- data/lib/bitclust/functionentry.rb +5 -1
- data/lib/bitclust/functionreferenceparser.rb +4 -4
- data/lib/bitclust/generators/epub.rb +10 -3
- data/lib/bitclust/htmlutils.rb +1 -1
- data/lib/bitclust/include_graph.rb +375 -0
- data/lib/bitclust/include_pruner.rb +144 -0
- data/lib/bitclust/interface.rb +1 -1
- data/lib/bitclust/libraryentry.rb +8 -7
- data/lib/bitclust/lineinput.rb +7 -5
- data/lib/bitclust/link_checker.rb +179 -0
- data/lib/bitclust/markdown_bridge.rb +186 -0
- data/lib/bitclust/markdown_orchestrator.rb +267 -0
- data/lib/bitclust/markdown_to_rrd.rb +824 -0
- data/lib/bitclust/markdown_tree.rb +217 -0
- data/lib/bitclust/mdcompiler.rb +883 -0
- data/lib/bitclust/mdparser.rb +347 -0
- data/lib/bitclust/messagecatalog.rb +3 -3
- data/lib/bitclust/method_since_calculator.rb +117 -0
- data/lib/bitclust/methoddatabase.rb +124 -14
- data/lib/bitclust/methodentry.rb +125 -7
- data/lib/bitclust/methodid.rb +12 -11
- data/lib/bitclust/methodsignature.rb +10 -4
- data/lib/bitclust/nameutils.rb +41 -22
- data/lib/bitclust/preprocessor.rb +66 -29
- data/lib/bitclust/rdcompiler.rb +195 -95
- data/lib/bitclust/refsdatabase.rb +23 -8
- data/lib/bitclust/reloadable_request_handler.rb +72 -0
- data/lib/bitclust/requesthandler.rb +21 -16
- data/lib/bitclust/ridatabase.rb +10 -4
- data/lib/bitclust/rrd_to_markdown.rb +925 -0
- data/lib/bitclust/rrdparser.rb +196 -30
- data/lib/bitclust/runner.rb +17 -2
- data/lib/bitclust/screen.rb +80 -11
- data/lib/bitclust/search_index_generator.rb +288 -0
- data/lib/bitclust/searcher.rb +69 -20
- data/lib/bitclust/simplesearcher.rb +24 -12
- data/lib/bitclust/subcommand.rb +7 -1
- data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
- data/lib/bitclust/subcommands/checklink_command.rb +54 -0
- data/lib/bitclust/subcommands/chm_command.rb +15 -7
- data/lib/bitclust/subcommands/classes_command.rb +5 -0
- data/lib/bitclust/subcommands/extract_command.rb +5 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
- data/lib/bitclust/subcommands/init_command.rb +4 -1
- data/lib/bitclust/subcommands/list_command.rb +12 -4
- data/lib/bitclust/subcommands/lookup_command.rb +28 -9
- data/lib/bitclust/subcommands/methods_command.rb +10 -2
- data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
- data/lib/bitclust/subcommands/preproc_command.rb +5 -0
- data/lib/bitclust/subcommands/property_command.rb +2 -1
- data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
- data/lib/bitclust/subcommands/server_command.rb +10 -8
- data/lib/bitclust/subcommands/setup_command.rb +24 -10
- data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
- data/lib/bitclust/subcommands/update_command.rb +110 -2
- data/lib/bitclust/syntax_highlighter.rb +48 -4
- data/lib/bitclust/textutils.rb +3 -3
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust/version_badges.rb +90 -0
- data/lib/bitclust/whole_file_gate.rb +137 -0
- data/test/test_app.rb +269 -0
- data/test/test_bitclust.rb +2 -0
- data/test/test_capi_converter.rb +91 -0
- data/test/test_class_screen.rb +413 -0
- data/test/test_copy_doc_md.rb +53 -0
- data/test/test_doc_converter.rb +78 -0
- data/test/test_entity_splitter.rb +231 -0
- data/test/test_entry.rb +207 -2
- data/test/test_eol_warning.rb +43 -0
- data/test/test_functionreferenceparser.rb +11 -0
- data/test/test_include_graph.rb +640 -0
- data/test/test_include_pruner.rb +133 -0
- data/test/test_link_checker.rb +114 -0
- data/test/test_lookup_command.rb +135 -0
- data/test/test_markdown_bridge.rb +217 -0
- data/test/test_markdown_orchestrator.rb +478 -0
- data/test/test_markdown_to_rrd.rb +757 -0
- data/test/test_markdown_tree.rb +369 -0
- data/test/test_mdcompiler.rb +1276 -0
- data/test/test_mdparser.rb +538 -0
- data/test/test_method_screen.rb +166 -0
- data/test/test_method_since_calculator.rb +290 -0
- data/test/test_methoddatabase.rb +16 -0
- data/test/test_methodentry.rb +328 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_methodsince_command.rb +168 -0
- data/test/test_nameutils.rb +22 -0
- data/test/test_preprocessor.rb +215 -0
- data/test/test_rdcompiler.rb +401 -30
- data/test/test_refsdatabase.rb +32 -0
- data/test/test_rrd_to_markdown.rb +968 -0
- data/test/test_rrdparser.rb +295 -2
- data/test/test_run_ruby_wasm.rb +54 -0
- data/test/test_run_worker_prelude.rb +83 -0
- data/test/test_runner.rb +51 -0
- data/test/test_search_index_generator.rb +513 -0
- data/test/test_search_screen.rb +48 -0
- data/test/test_searcher.rb +132 -0
- data/test/test_searchpage_command.rb +167 -0
- data/test/test_simplesearcher.rb +28 -0
- data/test/test_statichtml_command.rb +204 -0
- data/test/test_syntax_highlighter.rb +60 -0
- data/test/test_whole_file_gate.rb +175 -0
- data/theme/default/js/NOTICE +42 -0
- data/theme/default/js/run-worker.js +111 -0
- data/theme/default/js/run.js +350 -0
- data/theme/default/js/search_controller.js +134 -0
- data/theme/default/js/search_init.js +176 -0
- data/theme/default/js/search_navigation.js +110 -0
- data/theme/default/js/search_page.js +202 -0
- data/theme/default/js/search_ranker.js +244 -0
- data/theme/default/script.js +91 -18
- data/theme/default/search.css +149 -0
- data/theme/default/style.css +193 -21
- data/theme/default/syntax-highlight.css +8 -0
- data/theme/lillia/style.css +45 -1
- metadata +161 -20
- data/README +0 -32
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module BitClust
|
|
6
|
+
class MarkdownToRRD
|
|
7
|
+
def self.convert(markdown, capi: false)
|
|
8
|
+
new(markdown, capi: capi).convert
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# md テキスト断片のインライン記法を rd 形式へ復元する
|
|
12
|
+
# ([type:target] → [[type:target]]、\[ エスケープ解除)。
|
|
13
|
+
# MDCompiler がテキストノードの処理を共有するための公開 API
|
|
14
|
+
def self.restore_inline(text)
|
|
15
|
+
new('').send(:convert_bare_refs, text)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# md テキストノードを旧経路と同じ表示形(rd のインライン形式)へ戻す:
|
|
19
|
+
# `__X__` 自動スパン解除、`token` → `token'(GNU 風引用)、
|
|
20
|
+
# 行頭 **N.** → N.、[x:y] → [[x:y]]。
|
|
21
|
+
# MDCompiler の M1 等価描画(compile_text のテキストノード)用。
|
|
22
|
+
# 行頭構造は触らない(テキストノードの行頭 # や - は本文の一部)
|
|
23
|
+
def self.restore_text(text)
|
|
24
|
+
restore_inline(
|
|
25
|
+
text.gsub(/`(__\w+__)`/, '\1')
|
|
26
|
+
.gsub(/(?<!\\)`([^`'\s]+)`/) { "`#{$1}'" }
|
|
27
|
+
.gsub(/^\*\*(\d+\.)\*\* /, '\1 ')
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# entry#description(meta description 等のコンパイラ非経由テキスト)と
|
|
32
|
+
# RefsDatabase のラベル用: インラインに加えて行頭構造
|
|
33
|
+
# (見出し・@param 系・リスト記号・フェンス・エスケープ)も旧表示形へ戻す
|
|
34
|
+
def self.restore_description(text)
|
|
35
|
+
trailing_newline = text.end_with?("\n")
|
|
36
|
+
saved = [] #: Array[String]
|
|
37
|
+
text = restore_display_fences(text, saved) if text =~ /^`{3,}/
|
|
38
|
+
text = text.lines.map { |l| restore_display_line(l) }.join
|
|
39
|
+
text = text.chomp unless trailing_newline
|
|
40
|
+
# 行頭リスト記号の復元は **N.** 復元より先に行う(復元後の
|
|
41
|
+
# 「N. 」を olist と誤認して (N) 化しないため。DublinCoreModel)
|
|
42
|
+
restore_inline(
|
|
43
|
+
text.gsub(/`(__\w+__)`/, '\1')
|
|
44
|
+
.gsub(/(?<!\\)`([^`'\s]+)`/) { "`#{$1}'" }
|
|
45
|
+
.gsub(/^(\s*)- /, '\1* ')
|
|
46
|
+
.gsub(/^(\s*)(\d+)\. /, '\1(\2) ')
|
|
47
|
+
.gsub(/^\*\*(\d+\.)\*\* /, '\1 ')
|
|
48
|
+
.gsub(/^\\#/, '#')
|
|
49
|
+
.gsub(/\\`/, '`')
|
|
50
|
+
).gsub(/\x00([-\w]+)\x00/) { "[a:#{$1}]" }
|
|
51
|
+
.gsub(/\x01(\d+)\x01/) { saved[$1.to_i] }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# description(段落単位の切り出し)内のフェンスを旧経路の表示形へ戻す:
|
|
55
|
+
# 4+ フェンス(インデントコード由来)→ インデントブロック、
|
|
56
|
+
# ```lang → //emlist[caption][lang]{(前処理後の #@samplecode/emlist 形)。
|
|
57
|
+
# 段落分割で閉じフェンスが切れている形も受ける。
|
|
58
|
+
# フェンス内容はコードなので、後段の行復元・インライン復元から
|
|
59
|
+
# \x01<idx>\x01 プレースホルダで保護し最後に戻す
|
|
60
|
+
def self.restore_display_fences(text, saved)
|
|
61
|
+
protect = lambda do |line, ends_nl|
|
|
62
|
+
saved << line
|
|
63
|
+
"\x01#{saved.size - 1}\x01#{ends_nl ? "\n" : ''}"
|
|
64
|
+
end
|
|
65
|
+
out = +''
|
|
66
|
+
# len と indent(emlist 形は nil)
|
|
67
|
+
fence = nil #: [Integer, Integer | nil]?
|
|
68
|
+
text.each_line do |l|
|
|
69
|
+
nl = l.end_with?("\n")
|
|
70
|
+
if fence
|
|
71
|
+
if l =~ /\A`{#{fence[0]}}\s*$/
|
|
72
|
+
out << protect.call('//}', nl) unless fence[1]
|
|
73
|
+
fence = nil
|
|
74
|
+
elsif l =~ /\A\s*$/
|
|
75
|
+
out << l
|
|
76
|
+
elsif fence[1]
|
|
77
|
+
out << protect.call((' ' * fence[1]) + l.chomp, nl)
|
|
78
|
+
else
|
|
79
|
+
out << protect.call(l.chomp, nl)
|
|
80
|
+
end
|
|
81
|
+
elsif l =~ /\A(`{4,})\s*$/
|
|
82
|
+
len = ($1 || raise).length
|
|
83
|
+
fence = [len, len - 3]
|
|
84
|
+
elsif l =~ /\A(`{3,})(\w+)?(?:\s+title="((?:[^"\\]|\\.)*)")?\s*$/
|
|
85
|
+
len = ($1 || raise).length
|
|
86
|
+
lang = $2
|
|
87
|
+
caption = $3&.gsub(/\\(["\\])/, '\1')
|
|
88
|
+
open = lang ? "//emlist[#{caption}][#{lang}]{" : '//emlist{'
|
|
89
|
+
out << protect.call(open, nl)
|
|
90
|
+
fence = [len, nil]
|
|
91
|
+
else
|
|
92
|
+
out << l
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
out
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 行頭構造(見出し・メタデータ行)の rd 表示形への復元。
|
|
99
|
+
# 全文変換(convert)の対応箇所と同じ規則の表示専用ミラー
|
|
100
|
+
def self.restore_display_line(l)
|
|
101
|
+
case l
|
|
102
|
+
when /\A(\#{1,6}) (.*?) \{#([-\w]+)\}([ \t]*\n?)\z/m
|
|
103
|
+
# アンカーは restore_inline の裸参照復元([a:x]→[[a:x]])を
|
|
104
|
+
# 避けるため \x00 で包み、最後に [a:x] へ戻す
|
|
105
|
+
"#{'=' * ($1 || raise).length}\x00#{$3}\x00 #{$2}#{$4}"
|
|
106
|
+
when /\A(\#{1,6}) (.*)\z/m
|
|
107
|
+
"#{'=' * ($1 || raise).length} #{$2}"
|
|
108
|
+
when /\A- \*\*(param|arg|raise)\*\*(\s+)`([^`]+)` --(.*)\z/m
|
|
109
|
+
"@#{$1}#{$2}#{$3}#{$4}"
|
|
110
|
+
when /\A- \*\*return\*\* --(.*)\z/m
|
|
111
|
+
"@return#{$1}"
|
|
112
|
+
when /\A- \*\*SEE\*\*(\s*)(.*)\z/m
|
|
113
|
+
"@see#{$1}#{$2}"
|
|
114
|
+
else
|
|
115
|
+
l
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# capi: C API リファレンスモード。### は見出しではなくシグネチャ
|
|
120
|
+
# 「--- <C sig>」へ復元する(capi に本文見出しは無い)
|
|
121
|
+
def initialize(markdown, capi: false)
|
|
122
|
+
@src = markdown
|
|
123
|
+
@capi = capi
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def convert
|
|
127
|
+
@lines = @src.lines
|
|
128
|
+
@out = []
|
|
129
|
+
@index = 0
|
|
130
|
+
@front_matter = {}
|
|
131
|
+
|
|
132
|
+
parse_front_matter
|
|
133
|
+
process_body
|
|
134
|
+
@out.join
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def parse_front_matter
|
|
140
|
+
return unless @index < @lines.length && @lines[@index] =~ /\A---\s*$/
|
|
141
|
+
advance # skip opening ---
|
|
142
|
+
yaml_lines = [] #: Array[String]
|
|
143
|
+
while @index < @lines.length
|
|
144
|
+
line = current_line
|
|
145
|
+
if line =~ /\A---\s*$/
|
|
146
|
+
advance # skip closing ---
|
|
147
|
+
break
|
|
148
|
+
end
|
|
149
|
+
yaml_lines << line
|
|
150
|
+
advance
|
|
151
|
+
end
|
|
152
|
+
parse_front_matter_raw(yaml_lines)
|
|
153
|
+
emit_library_metadata
|
|
154
|
+
# Skip blank lines after front matter
|
|
155
|
+
advance while @index < @lines.length && current_line =~ /\A\s*\n?\z/
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def emit_library_metadata
|
|
159
|
+
@out.concat(@library_metadata_body) if @library_metadata_body
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# front matter を生行で走査し、#@ ディレクティブを保持したまま
|
|
163
|
+
# ライブラリメタ(category/require/sublibrary; H1 前)と
|
|
164
|
+
# クラス関係(include/extend/alias; H1 直後)の body 行を組み立てる。
|
|
165
|
+
# YAML.safe_load は #@ をコメントとして落とすため生行を使う。
|
|
166
|
+
def parse_front_matter_raw(yaml_lines)
|
|
167
|
+
# list key => [[:item, val] | [:dir, line]]
|
|
168
|
+
blocks = {} #: Hash[String?, Array[[Symbol, String]]]
|
|
169
|
+
category = nil
|
|
170
|
+
# ゲート付き category(rd 行の組み立て済み列)
|
|
171
|
+
category_block = nil #: Array[String]?
|
|
172
|
+
cat_depth = 0
|
|
173
|
+
# category ゲート候補のトップレベル #@ 行
|
|
174
|
+
pending = [] #: Array[String]
|
|
175
|
+
# メタ領域先頭の #@# コメント行(irb.rd の Author 行)
|
|
176
|
+
leading = [] #: Array[String]
|
|
177
|
+
key = nil
|
|
178
|
+
yaml_lines.each do |l|
|
|
179
|
+
case l
|
|
180
|
+
when /\A(include|extend|alias|require|sublibrary|library):\s*$/
|
|
181
|
+
# library のゲート付きリスト(多重所属・注入キー)は収集だけして
|
|
182
|
+
# 破棄する(ブロック内の #@ 行が leading に漏れないように)
|
|
183
|
+
key = $1; blocks[key] = []
|
|
184
|
+
when /\Acategory:\s*(.*)$/
|
|
185
|
+
if pending.any?
|
|
186
|
+
# ゲート付き category(cmath 型): #@ 行ごと復元する
|
|
187
|
+
category_block = pending.dup << "category #{($1 || raise).strip}\n"
|
|
188
|
+
cat_depth = pending.count { |p| p =~ /\A\#@(?:since|until|if)\b/ } -
|
|
189
|
+
pending.count { |p| p =~ /\A\#@end\b/ }
|
|
190
|
+
pending = []
|
|
191
|
+
else
|
|
192
|
+
category = ($1 || raise).strip
|
|
193
|
+
end
|
|
194
|
+
key = nil
|
|
195
|
+
when /\A\s+- (.+?)\s*$/
|
|
196
|
+
blocks[key] << [:item, $1 || raise] if key
|
|
197
|
+
when /\A\#@\#/
|
|
198
|
+
key ? blocks[key] << [:dir, l] : leading << l
|
|
199
|
+
when /\A\#@/
|
|
200
|
+
if key
|
|
201
|
+
blocks[key] << [:dir, l]
|
|
202
|
+
elsif category_block && cat_depth > 0
|
|
203
|
+
category_block << l
|
|
204
|
+
cat_depth += 1 if l =~ /\A\#@(?:since|until|if)\b/
|
|
205
|
+
cat_depth -= 1 if l =~ /\A\#@end\b/
|
|
206
|
+
else
|
|
207
|
+
pending << l
|
|
208
|
+
end
|
|
209
|
+
when /\A\S/
|
|
210
|
+
key = nil # その他のトップレベルキー(type/since/until 等)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
leading.concat(pending) # category に消費されなかった #@ 行(通常は無い)
|
|
214
|
+
# クラス関係(H1 直後、RD 文法順 alias → extend → include)
|
|
215
|
+
@class_relations = []
|
|
216
|
+
%w[alias extend include].each do |k|
|
|
217
|
+
next unless blocks[k]
|
|
218
|
+
blocks[k].each { |type, v| @class_relations << (type == :dir ? v : "#{k} #{v}\n") }
|
|
219
|
+
end
|
|
220
|
+
# ライブラリメタ(H1 前)
|
|
221
|
+
@library_metadata_body = []
|
|
222
|
+
unless leading.empty?
|
|
223
|
+
@library_metadata_body.concat(leading) << "\n"
|
|
224
|
+
end
|
|
225
|
+
if category_block
|
|
226
|
+
@library_metadata_body.concat(category_block) << "\n"
|
|
227
|
+
elsif category
|
|
228
|
+
@library_metadata_body << "category #{category}\n" << "\n"
|
|
229
|
+
end
|
|
230
|
+
%w[require sublibrary].each do |k|
|
|
231
|
+
next unless blocks[k]
|
|
232
|
+
blocks[k].each { |type, v| @library_metadata_body << (type == :dir ? v : "#{k} #{v}\n") }
|
|
233
|
+
@library_metadata_body << "\n"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def process_body
|
|
238
|
+
while @index < @lines.length
|
|
239
|
+
line = current_line
|
|
240
|
+
case line
|
|
241
|
+
when /\A```/
|
|
242
|
+
convert_code_block(line)
|
|
243
|
+
when /\A### module_function def /
|
|
244
|
+
convert_module_function_signature(line)
|
|
245
|
+
when /\A### def /
|
|
246
|
+
convert_method_signature(line)
|
|
247
|
+
when /\A### const /
|
|
248
|
+
convert_const_signature(line)
|
|
249
|
+
when /\A### gvar /
|
|
250
|
+
convert_gvar_signature(line)
|
|
251
|
+
when /\A### /
|
|
252
|
+
@capi ? convert_capi_signature(line) : convert_h3_heading(line)
|
|
253
|
+
when /\A##### /
|
|
254
|
+
convert_h5_heading(line)
|
|
255
|
+
when /\A#### /
|
|
256
|
+
convert_h4_heading(line)
|
|
257
|
+
when /\A- \*\*(param|return|raise)\*\*/
|
|
258
|
+
convert_metadata(line, $1)
|
|
259
|
+
when /\A- \*\*SEE\*\*/
|
|
260
|
+
convert_see_list_upper(line)
|
|
261
|
+
when /\A- \*\*see\*\*/
|
|
262
|
+
convert_see_list(line)
|
|
263
|
+
when /\A\*\*SEE\*\*/
|
|
264
|
+
convert_see(line)
|
|
265
|
+
when /\A@see /
|
|
266
|
+
convert_see_passthrough(line)
|
|
267
|
+
when /\A## /
|
|
268
|
+
convert_h2(line)
|
|
269
|
+
when /\A- \*\*(?!param\b|return\b|raise\b|see\b)(.+?)\*\*:/
|
|
270
|
+
convert_dlist_colon_item(line)
|
|
271
|
+
when /\A- \*\*(?!param\b|return\b|raise\b|see\b)(\w+)\*\* -- /
|
|
272
|
+
convert_dlist_item(line)
|
|
273
|
+
when /\A\*\*(\d+)\.\*\*\s/
|
|
274
|
+
convert_bold_number(line)
|
|
275
|
+
when /\A(\s*)- /
|
|
276
|
+
convert_list_item(line, $1 || raise)
|
|
277
|
+
when /\A(\s{0,3})(\d+)\.\s/
|
|
278
|
+
convert_ordered_list_item(line, $2.to_i, $1 || raise)
|
|
279
|
+
when /\A:\s/
|
|
280
|
+
convert_dlist_passthrough(line)
|
|
281
|
+
when /\A {4,}/
|
|
282
|
+
convert_indented_code(line)
|
|
283
|
+
when /\A\#@/
|
|
284
|
+
raw_passthrough(line)
|
|
285
|
+
when /\A\\#/
|
|
286
|
+
convert_escaped_hash_line(line)
|
|
287
|
+
when /\A# /
|
|
288
|
+
convert_h1(line)
|
|
289
|
+
else
|
|
290
|
+
passthrough(line)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def convert_code_block(line)
|
|
296
|
+
# バッククォートの個数を取得
|
|
297
|
+
line =~ /\A(`{3,})/
|
|
298
|
+
fence = $1 || raise
|
|
299
|
+
fence_len = fence.length
|
|
300
|
+
|
|
301
|
+
# 4個以上のバッククォート(言語指定なし)→ インデントコードに復元
|
|
302
|
+
if fence_len > 3 && line =~ /\A`{4,}\s*$/
|
|
303
|
+
indent = ' ' * (fence_len - 3)
|
|
304
|
+
advance
|
|
305
|
+
while @index < @lines.length
|
|
306
|
+
l = current_line
|
|
307
|
+
if l =~ /\A`{#{fence_len}}\s*$/
|
|
308
|
+
advance
|
|
309
|
+
return
|
|
310
|
+
end
|
|
311
|
+
if l =~ /\A\s*$/
|
|
312
|
+
@out << l
|
|
313
|
+
else
|
|
314
|
+
@out << indent + l
|
|
315
|
+
end
|
|
316
|
+
advance
|
|
317
|
+
end
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
title = nil
|
|
322
|
+
lang = nil
|
|
323
|
+
fence_len = 3
|
|
324
|
+
if line =~ /\A(`{3,})(\w*)/
|
|
325
|
+
fence_len = ($1 || raise).length
|
|
326
|
+
lang = ($2 || raise).empty? ? nil : $2
|
|
327
|
+
end
|
|
328
|
+
if line =~ /title="((?:[^"\\]|\\.)*)"/
|
|
329
|
+
title = ($1 || raise).gsub(/\\(["\\])/, '\1')
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
if lang == 'ruby'
|
|
333
|
+
# Ruby → #@samplecode
|
|
334
|
+
samplecode = '#@samplecode'
|
|
335
|
+
samplecode_end = '#@end'
|
|
336
|
+
@out << (title && !title.empty? ? "#{samplecode} #{title}\n" : "#{samplecode}\n")
|
|
337
|
+
else
|
|
338
|
+
# Other language or unspecified → //emlist
|
|
339
|
+
parts = ['//emlist']
|
|
340
|
+
if title && !title.empty?
|
|
341
|
+
parts << "[#{title}]"
|
|
342
|
+
parts << "[#{lang}]" if lang
|
|
343
|
+
elsif lang
|
|
344
|
+
parts << '[]'
|
|
345
|
+
parts << "[#{lang}]"
|
|
346
|
+
end
|
|
347
|
+
parts << "{"
|
|
348
|
+
@out << parts.join + "\n"
|
|
349
|
+
end
|
|
350
|
+
advance
|
|
351
|
+
while @index < @lines.length
|
|
352
|
+
line = current_line
|
|
353
|
+
if line =~ /\A`{#{fence_len}}\s*\n?\z/
|
|
354
|
+
if lang == 'ruby'
|
|
355
|
+
@out << "#{samplecode_end}\n"
|
|
356
|
+
else
|
|
357
|
+
@out << "//}\n"
|
|
358
|
+
end
|
|
359
|
+
advance
|
|
360
|
+
return
|
|
361
|
+
end
|
|
362
|
+
# No cross-reference conversion inside code blocks
|
|
363
|
+
@out << line
|
|
364
|
+
advance
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# dd/@param 系の説明内にある、インデントされたフェンス(rd では
|
|
369
|
+
# //emlist・#@samplecode の桁0出力に対応。CommonMark 準拠のため
|
|
370
|
+
# rrd_to_markdown.rb が項目の内容カラムへインデントして出すように
|
|
371
|
+
# なった分の桁0版)を、桁0の //emlist{ / #@samplecode に戻す。
|
|
372
|
+
# convert_code_block の 3 バッククォート(lang 付き)分岐と対称。
|
|
373
|
+
# 4+ バッククォート(インデントコード由来)のインデント版は
|
|
374
|
+
# rrd_to_markdown.rb が dd/@param からその形式を出さないため未対応
|
|
375
|
+
def convert_indented_code_block(line, indent)
|
|
376
|
+
line =~ /\A[ \t]+(`{3,})(\w*)(?:\s+title="((?:[^"\\]|\\.)*)")?\s*$/ or raise
|
|
377
|
+
fence_len = ($1 || raise).length
|
|
378
|
+
lang = ($2 || raise).empty? ? nil : $2
|
|
379
|
+
title = $3&.gsub(/\\(["\\])/, '\1')
|
|
380
|
+
strip_re = /\A[ \t]{1,#{indent.length}}/
|
|
381
|
+
|
|
382
|
+
if lang == 'ruby'
|
|
383
|
+
samplecode = '#@samplecode'
|
|
384
|
+
samplecode_end = '#@end'
|
|
385
|
+
@out << (title && !title.empty? ? "#{samplecode} #{title}\n" : "#{samplecode}\n")
|
|
386
|
+
else
|
|
387
|
+
parts = ['//emlist']
|
|
388
|
+
if title && !title.empty?
|
|
389
|
+
parts << "[#{title}]"
|
|
390
|
+
parts << "[#{lang}]" if lang
|
|
391
|
+
elsif lang
|
|
392
|
+
parts << '[]'
|
|
393
|
+
parts << "[#{lang}]"
|
|
394
|
+
end
|
|
395
|
+
parts << "{"
|
|
396
|
+
@out << parts.join + "\n"
|
|
397
|
+
end
|
|
398
|
+
advance
|
|
399
|
+
terminator = /\A[ \t]{0,#{indent.length}}`{#{fence_len}}\s*\n?\z/
|
|
400
|
+
while @index < @lines.length
|
|
401
|
+
l = current_line
|
|
402
|
+
if l =~ terminator
|
|
403
|
+
@out << (lang == 'ruby' ? "#{samplecode_end}\n" : "//}\n")
|
|
404
|
+
advance
|
|
405
|
+
return
|
|
406
|
+
elsif l =~ /\A\#@/
|
|
407
|
+
@out << l # ディレクティブはカラム0のまま(rrd_to_markdown.rb 側と対称)
|
|
408
|
+
else
|
|
409
|
+
@out << l.sub(strip_re, '')
|
|
410
|
+
end
|
|
411
|
+
advance
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def convert_method_signature(line)
|
|
416
|
+
@out << line.sub(/\A### def /, '--- ')
|
|
417
|
+
advance
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def convert_module_function_signature(line)
|
|
421
|
+
@out << line.sub(/\A### module_function def /, '--- ')
|
|
422
|
+
advance
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def convert_const_signature(line)
|
|
426
|
+
@out << line.sub(/\A### const /, '--- ')
|
|
427
|
+
advance
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def convert_gvar_signature(line)
|
|
431
|
+
@out << line.sub(/\A### gvar /, '--- ')
|
|
432
|
+
advance
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def convert_metadata(line, kind)
|
|
436
|
+
case kind
|
|
437
|
+
when 'param'
|
|
438
|
+
if (m = /\A- \*\*param\*\*(\s+)`([^`]+)` --(.*)$/.match(line))
|
|
439
|
+
@out << convert_inline_refs("@param#{m[1]}#{m[2]}#{m[3]}\n")
|
|
440
|
+
end
|
|
441
|
+
when 'raise'
|
|
442
|
+
if (m = /\A- \*\*raise\*\*(\s+)`([^`]+)` --(.*)$/.match(line))
|
|
443
|
+
@out << convert_inline_refs("@raise#{m[1]}#{m[2]}#{m[3]}\n")
|
|
444
|
+
end
|
|
445
|
+
when 'return'
|
|
446
|
+
if (m = /\A- \*\*return\*\*(?: `[^`]+`)? --(.*)$/.match(line))
|
|
447
|
+
@out << convert_inline_refs("@return#{m[1]}\n")
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
advance
|
|
451
|
+
collect_md_continuation_lines(greedy: true)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# greedy: rd 側の @param 系継続(RDCompiler の dd_without_p 相当)と対称。
|
|
455
|
+
# 空白のみの行も継続として保持し、完全な空行でのみ停止する
|
|
456
|
+
def collect_md_continuation_lines(greedy: false)
|
|
457
|
+
nest = 0
|
|
458
|
+
while @index < @lines.length
|
|
459
|
+
line = current_line
|
|
460
|
+
if line =~ /\A\#@(?:since|until|if)\b/
|
|
461
|
+
nest += 1
|
|
462
|
+
raw_passthrough(line)
|
|
463
|
+
elsif line =~ /\A\#@else/
|
|
464
|
+
raw_passthrough(line)
|
|
465
|
+
elsif line =~ /\A\#@end/
|
|
466
|
+
# rd 側と対称: nest 0 の #@end も透過(fileutils の @param 継続)
|
|
467
|
+
nest -= 1 if nest > 0
|
|
468
|
+
raw_passthrough(line)
|
|
469
|
+
elsif line =~ /\A\s+\S/ && line !~ /\A- / && line !~ /\A\*\*/ && line !~ /\A[ \t]*`{3,}/
|
|
470
|
+
@out << convert_inline_refs(line)
|
|
471
|
+
advance
|
|
472
|
+
elsif greedy && line =~ /\A[ \t]+$/
|
|
473
|
+
@out << line
|
|
474
|
+
advance
|
|
475
|
+
elsif greedy && line =~ /\A`{3,}/
|
|
476
|
+
convert_code_block(line) # rd 側と対称: 直結するコードブロックは説明の一部
|
|
477
|
+
elsif greedy && line =~ /\A([ \t]+)`{3,}/
|
|
478
|
+
convert_indented_code_block(line, $1 || raise) # 上と同じくインデント版
|
|
479
|
+
else
|
|
480
|
+
break
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def convert_see(line)
|
|
486
|
+
# **SEE** → @see、[→[[ 置換、区切りは保持
|
|
487
|
+
rest = line.sub(/\A\*\*SEE\*\*(\s*)/, '').chomp
|
|
488
|
+
space = $1
|
|
489
|
+
@out << "@see#{space}#{convert_inline_refs(rest)}\n"
|
|
490
|
+
advance
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def convert_see_list_upper(line)
|
|
494
|
+
# - **SEE** [ref] → @see [[ref]]、区切り保持
|
|
495
|
+
rest = line.sub(/\A- \*\*SEE\*\*(\s*)/, '').chomp
|
|
496
|
+
space = $1
|
|
497
|
+
@out << "@see#{space}#{convert_inline_refs(rest)}\n"
|
|
498
|
+
advance
|
|
499
|
+
collect_md_continuation_lines
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def convert_see_list(line)
|
|
503
|
+
# - **see** [display][type:ref] → @see [[type:ref]]
|
|
504
|
+
rest = line.sub(/\A- \*\*see\*\*\s*/, '').chomp
|
|
505
|
+
@out << "@see #{convert_inline_refs(rest)}\n"
|
|
506
|
+
advance
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def convert_see_passthrough(line)
|
|
510
|
+
# @see [m:A], [m:B] → @see [[m:A]], [[m:B]] (区切り保持)
|
|
511
|
+
rest = line.sub(/\A@see\s+/, '').chomp
|
|
512
|
+
@out << "@see #{convert_inline_refs(rest)}\n"
|
|
513
|
+
advance
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def convert_heading_with_anchor(line, prefix)
|
|
517
|
+
if line =~ /\{#([^}]+)\}\s*$/
|
|
518
|
+
anchor = $1
|
|
519
|
+
text = line.sub(/\A#+\s+/, '').sub(/\s*\{#[^}]+\}\s*$/, '')
|
|
520
|
+
@out << "#{prefix}[a:#{anchor}] #{restore_heading_backticks(text)}\n"
|
|
521
|
+
else
|
|
522
|
+
text = line.sub(/\A#+\s+/, '').chomp
|
|
523
|
+
@out << "#{prefix} #{restore_heading_backticks(text)}\n"
|
|
524
|
+
end
|
|
525
|
+
advance
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# 見出しテキストのバッククォート復元。参照変換(convert_bare_refs)は
|
|
529
|
+
# 通さない(rd→md も見出しでは参照エスケープをしないため対称に)
|
|
530
|
+
def restore_heading_backticks(text)
|
|
531
|
+
strip_code_spans(text).gsub(/\\`/, '`')
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def convert_h3_heading(line)
|
|
535
|
+
convert_heading_with_anchor(line, '===')
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def convert_h4_heading(line)
|
|
539
|
+
convert_heading_with_anchor(line, '====')
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def convert_h5_heading(line)
|
|
543
|
+
# プレフィックス置換のみ(rd 側と対称、空白を保持)
|
|
544
|
+
@out << line.sub(/\A##### /, '===== ')
|
|
545
|
+
advance
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def strip_code_span(text)
|
|
549
|
+
text.sub(/\A`(.+)`\z/, '\\1')
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def convert_dlist_colon_item(line)
|
|
553
|
+
# - **`term`**: description → : term\n description
|
|
554
|
+
# - **term**:\n line1\n line2 → : term\n line1\n line2
|
|
555
|
+
if line =~ /\A- \*\*(.+?)\*\*: (.+)$/
|
|
556
|
+
# 構造の `term` は GNU 引用復元より先に unwrap する
|
|
557
|
+
@out << ": #{convert_inline_refs(strip_code_span($1 || raise))}\n #{convert_inline_refs($2 || raise)}\n"
|
|
558
|
+
advance
|
|
559
|
+
elsif line =~ /\A- \*\*(.+?)\*\*:\s*$/
|
|
560
|
+
@out << ": #{convert_inline_refs(strip_code_span($1 || raise))}\n"
|
|
561
|
+
advance
|
|
562
|
+
# 継続行を収集(空行を挟む場合も含む)。rd 側と対称:
|
|
563
|
+
# RDCompiler の dd_with_p はインデント段落とコードブロックを
|
|
564
|
+
# 交互に何個でも説明として受ける(String#% 型)
|
|
565
|
+
while @index < @lines.length
|
|
566
|
+
l = current_line
|
|
567
|
+
if l =~ /\A\s+\S/ && l !~ /\A[ \t]+`{3,}/
|
|
568
|
+
@out << convert_inline_refs(l)
|
|
569
|
+
advance
|
|
570
|
+
elsif l =~ /\A`{3,}/
|
|
571
|
+
convert_code_block(l)
|
|
572
|
+
elsif l =~ /\A([ \t]+)`{3,}/
|
|
573
|
+
convert_indented_code_block(l, $1 || raise) # 上と同じくインデント版
|
|
574
|
+
elsif l =~ /\A\#@/
|
|
575
|
+
# rd 側と対称: #@ 指令行は dd の文脈に透明
|
|
576
|
+
raw_passthrough(l)
|
|
577
|
+
elsif l =~ /\A\s*$/
|
|
578
|
+
# rd 側と対称: 透明なのは版解決で消える指令のみ(#@include は停止要因)
|
|
579
|
+
scan = @index + 1
|
|
580
|
+
scan += 1 while scan < @lines.length &&
|
|
581
|
+
(@lines[scan] =~ /\A\s*$/ ||
|
|
582
|
+
(@lines[scan] =~ /\A\#@/ && @lines[scan] !~ /\A\#@include/))
|
|
583
|
+
nxt = scan < @lines.length ? @lines[scan] : nil
|
|
584
|
+
if nxt && nxt !~ /\A- \*\*/ && (nxt =~ /\A\s+\S/ || nxt =~ /\A`{3,}/)
|
|
585
|
+
@out << l
|
|
586
|
+
advance
|
|
587
|
+
else
|
|
588
|
+
break
|
|
589
|
+
end
|
|
590
|
+
else
|
|
591
|
+
break
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
else
|
|
595
|
+
advance
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def convert_dlist_item(line)
|
|
600
|
+
if line =~ /\A- \*\*(.+?)\*\* -- (.*)$/
|
|
601
|
+
@out << ": #{$1}\n #{convert_inline_refs($2 || raise)}\n"
|
|
602
|
+
end
|
|
603
|
+
advance
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def convert_bold_number(line)
|
|
607
|
+
# **N.** text → N. text (太字番号テキスト → RD のテキスト)
|
|
608
|
+
@out << convert_inline_refs(line.sub(/\A\*\*(\d+\.)\*\*\s/, '\\1 '))
|
|
609
|
+
advance
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def convert_ordered_list_item(line, num, indent)
|
|
613
|
+
rrd_indent = indent.empty? ? ' ' : indent
|
|
614
|
+
@out << convert_inline_refs(line.sub(/\A\s*\d+\. /, "#{rrd_indent}(#{num}) "))
|
|
615
|
+
advance
|
|
616
|
+
# 継続行を収集(convert_list_item と同じ、rd 側と対称)
|
|
617
|
+
while @index < @lines.length
|
|
618
|
+
l = current_line
|
|
619
|
+
if l =~ /\A\#@/
|
|
620
|
+
raw_passthrough(l)
|
|
621
|
+
elsif l =~ /\A\s*$/
|
|
622
|
+
break
|
|
623
|
+
elsif l =~ /\A(\s+)\S/ && l !~ /\A\s*-\s/ && l !~ /\A\s*\d+\.\s/
|
|
624
|
+
@out << convert_inline_refs(l)
|
|
625
|
+
advance
|
|
626
|
+
else
|
|
627
|
+
break
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def convert_list_item(line, indent)
|
|
633
|
+
rrd_indent = indent.empty? ? ' ' : indent
|
|
634
|
+
line =~ /\A\s*-(\s+)/
|
|
635
|
+
content_indent = (indent.empty? ? 1 : indent.length) + 1 + ($1 || raise).length
|
|
636
|
+
@out << convert_inline_refs(line.sub(/\A\s*-(\s+)/, "#{rrd_indent}*\\1"))
|
|
637
|
+
advance
|
|
638
|
+
# 継続行を収集(空行・リスト項目・#@ で停止)。
|
|
639
|
+
# rd 側と対称: 継続はインデント深さ不問(RDCompiler の項目継続規則)
|
|
640
|
+
while @index < @lines.length
|
|
641
|
+
l = current_line
|
|
642
|
+
if l =~ /\A\#@/
|
|
643
|
+
raw_passthrough(l)
|
|
644
|
+
elsif l =~ /\A\s*$/
|
|
645
|
+
break
|
|
646
|
+
elsif l =~ /\A(\s+)\S/ && l !~ /\A\s*-\s/ && l !~ /\A\s*\d+\.\s/
|
|
647
|
+
@out << convert_inline_refs(l)
|
|
648
|
+
advance
|
|
649
|
+
else
|
|
650
|
+
break
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def convert_dlist_passthrough(line)
|
|
656
|
+
# `: term` + 説明行をそのままパススルー
|
|
657
|
+
passthrough(line)
|
|
658
|
+
while @index < @lines.length
|
|
659
|
+
l = current_line
|
|
660
|
+
if l =~ /\A\s+\S/
|
|
661
|
+
passthrough(l)
|
|
662
|
+
else
|
|
663
|
+
break
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def convert_indented_code(line)
|
|
669
|
+
# C1: インデント -3 して出力
|
|
670
|
+
# 最初の行が実際にコードでなければパススルー
|
|
671
|
+
unless current_line =~ /\A( {4,})\S/
|
|
672
|
+
passthrough(current_line)
|
|
673
|
+
return
|
|
674
|
+
end
|
|
675
|
+
while @index < @lines.length
|
|
676
|
+
line = current_line
|
|
677
|
+
if line =~ /\A( {4,})\S/
|
|
678
|
+
n = ($1 || raise).length
|
|
679
|
+
deindent = [n - 3, 1].max
|
|
680
|
+
@out << (' ' * deindent) + (line[n..] || raise)
|
|
681
|
+
advance
|
|
682
|
+
elsif line =~ /\A\s*$/
|
|
683
|
+
if @index + 1 < @lines.length && @lines[@index + 1] =~ /\A {4,}\S/
|
|
684
|
+
@out << line
|
|
685
|
+
advance
|
|
686
|
+
else
|
|
687
|
+
break
|
|
688
|
+
end
|
|
689
|
+
else
|
|
690
|
+
break
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def convert_h2(line)
|
|
696
|
+
@out << line.sub(/\A## /, '== ')
|
|
697
|
+
advance
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# capi の C シグネチャ(キーワード無し ###)を復元する
|
|
701
|
+
def convert_capi_signature(line)
|
|
702
|
+
@out << line.sub(/\A### /, '--- ')
|
|
703
|
+
advance
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
# rd→md がエスケープした行頭 # のリテラル本文を復元する
|
|
707
|
+
def convert_escaped_hash_line(line)
|
|
708
|
+
@out << convert_inline_refs(line.sub(/\A\\/, ''))
|
|
709
|
+
advance
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def convert_h1(line)
|
|
713
|
+
return convert_heading_with_anchor(line, '=') if line =~ /\{#[^}]+\}\s*$/
|
|
714
|
+
@out << line.sub(/\A# /, '= ')
|
|
715
|
+
advance
|
|
716
|
+
if @class_relations && !@class_relations.empty?
|
|
717
|
+
@out.concat(@class_relations)
|
|
718
|
+
@class_relations = []
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def raw_passthrough(line)
|
|
723
|
+
@out << line
|
|
724
|
+
advance
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def passthrough(line)
|
|
728
|
+
@out << convert_inline_refs(line)
|
|
729
|
+
advance
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# テキスト行のコードスパンを rd の元表記へ復元する:
|
|
733
|
+
# - `__WORD__`(自動スパン)→ __WORD__
|
|
734
|
+
# - `token`(GNU 風引用由来)→ `token'
|
|
735
|
+
# エスケープ済み \` はスパン開始とみなさない(convert_bare_refs が後で復元)
|
|
736
|
+
def strip_code_spans(text)
|
|
737
|
+
text.gsub(/`(__\w+__)`/, '\\1')
|
|
738
|
+
.gsub(/(?<!\\)`([^`'\s]+)`/) { "`#{$1}'" }
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Markdown のブラケットリンク: エスケープされた \[ \] を含むパターン
|
|
742
|
+
def convert_inline_refs(line)
|
|
743
|
+
# コードスパンの復元(`__X__`・GNU 風引用)は \` の解除より先。
|
|
744
|
+
# 続けて bare [type:target] → [[type:target]] (手動パースで \[\] 対応)
|
|
745
|
+
convert_bare_refs(strip_code_spans(line))
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
def convert_bare_refs(line)
|
|
749
|
+
result = +""
|
|
750
|
+
i = 0
|
|
751
|
+
while i < line.length
|
|
752
|
+
if line[i] == '\\' && line[i + 1] == '['
|
|
753
|
+
# rd→md がエスケープしたリテラル [x:y](参照ではない)→ そのまま復元
|
|
754
|
+
result << '['
|
|
755
|
+
i += 2
|
|
756
|
+
next
|
|
757
|
+
end
|
|
758
|
+
if line[i] == '\\' && line[i + 1] == '`'
|
|
759
|
+
# rd→md がエスケープした生バッククォート → そのまま復元
|
|
760
|
+
result << '`'
|
|
761
|
+
i += 2
|
|
762
|
+
next
|
|
763
|
+
end
|
|
764
|
+
if line[i] == '[' && (i == 0 || line[i-1] != '[') && (i + 1 >= line.length || line[i+1] != '[')
|
|
765
|
+
# [ の開始を検出([[ は除外)— エスケープを考慮して ] を探す
|
|
766
|
+
j = find_closing_bracket(line, i + 1)
|
|
767
|
+
if j
|
|
768
|
+
inner = line[i+1...j] || raise
|
|
769
|
+
if inner =~ /\A[a-zA-Z][a-zA-Z-]*:/
|
|
770
|
+
result << convert_md_ref_to_rrd(inner)
|
|
771
|
+
i = j + 1
|
|
772
|
+
next
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
end
|
|
776
|
+
result << (line[i] || raise)
|
|
777
|
+
i += 1
|
|
778
|
+
end
|
|
779
|
+
result
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def find_closing_bracket(line, start)
|
|
783
|
+
j = start
|
|
784
|
+
while j < line.length
|
|
785
|
+
if line[j] == '\\' && j + 1 < line.length && (line[j+1] == '[' || line[j+1] == ']' || line[j+1] == '\\')
|
|
786
|
+
j += 2
|
|
787
|
+
elsif line[j] == ']'
|
|
788
|
+
return j
|
|
789
|
+
else
|
|
790
|
+
j += 1
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
nil
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
def convert_md_ref_to_rrd(ref)
|
|
797
|
+
# Markdown の \[ \] \` \\ → RD の [ ] ` \ 。
|
|
798
|
+
# \` の復元は convert_bare_refs のトップレベル分岐([...] の外)と
|
|
799
|
+
# 対称: [m:$\`](バッククォートをエスケープした参照)も
|
|
800
|
+
# [m:$`](素のバッククォート)と同じ [[m:$`]] へ復元する
|
|
801
|
+
# (rurema/doctree#3232 レビュー、MDCompiler#extract_code_spans が
|
|
802
|
+
# コードスパンの開始候補から \` を除外するのと同じ理由で、
|
|
803
|
+
# ブラケット内の \` もコードスパンの巻き添えにならない)
|
|
804
|
+
unescaped = ref.gsub('\\[', '[').gsub('\\]', ']').gsub('\\`', '`').gsub('\\\\', '\\')
|
|
805
|
+
# ? → .# (モジュール関数参照)
|
|
806
|
+
unescaped = unescaped.sub(/\?\./, '.#')
|
|
807
|
+
# RD で [] で終わるメソッド名のみ末尾スペースが必要 ([[m:Hash#[] ]])
|
|
808
|
+
# []= のように [] の後に文字がある場合はスペース不要 ([[m:String#[]=]])
|
|
809
|
+
if unescaped.end_with?('[]')
|
|
810
|
+
"[[#{unescaped} ]]"
|
|
811
|
+
else
|
|
812
|
+
"[[#{unescaped}]]"
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def current_line
|
|
817
|
+
@lines[@index]
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
def advance
|
|
821
|
+
@index += 1
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|