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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +65 -0
  4. data/Rakefile +32 -0
  5. data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
  6. data/data/bitclust/searchpage/index.html +90 -0
  7. data/data/bitclust/template/class +69 -8
  8. data/data/bitclust/template/function +2 -2
  9. data/data/bitclust/template/layout +11 -3
  10. data/data/bitclust/template/method +2 -2
  11. data/data/bitclust/template/search +1 -1
  12. data/data/bitclust/template.epub/class +22 -1
  13. data/data/bitclust/template.epub/function +2 -2
  14. data/data/bitclust/template.epub/layout +1 -3
  15. data/data/bitclust/template.epub/method +2 -2
  16. data/data/bitclust/template.lillia/class +26 -5
  17. data/data/bitclust/template.lillia/layout +1 -3
  18. data/data/bitclust/template.lillia/method +2 -2
  19. data/data/bitclust/template.offline/class +48 -4
  20. data/data/bitclust/template.offline/function +2 -2
  21. data/data/bitclust/template.offline/layout +27 -3
  22. data/data/bitclust/template.offline/method +2 -2
  23. data/lib/bitclust/app.rb +16 -17
  24. data/lib/bitclust/capi_converter.rb +20 -0
  25. data/lib/bitclust/classentry.rb +67 -17
  26. data/lib/bitclust/compat.rb +2 -2
  27. data/lib/bitclust/completion.rb +58 -27
  28. data/lib/bitclust/crossrubyutils.rb +8 -5
  29. data/lib/bitclust/database.rb +28 -8
  30. data/lib/bitclust/doc_converter.rb +63 -0
  31. data/lib/bitclust/docentry.rb +1 -1
  32. data/lib/bitclust/entity_splitter.rb +221 -0
  33. data/lib/bitclust/entry.rb +47 -7
  34. data/lib/bitclust/functiondatabase.rb +13 -1
  35. data/lib/bitclust/functionentry.rb +5 -1
  36. data/lib/bitclust/functionreferenceparser.rb +4 -4
  37. data/lib/bitclust/generators/epub.rb +10 -3
  38. data/lib/bitclust/htmlutils.rb +1 -1
  39. data/lib/bitclust/include_graph.rb +375 -0
  40. data/lib/bitclust/include_pruner.rb +144 -0
  41. data/lib/bitclust/interface.rb +1 -1
  42. data/lib/bitclust/libraryentry.rb +8 -7
  43. data/lib/bitclust/lineinput.rb +7 -5
  44. data/lib/bitclust/link_checker.rb +179 -0
  45. data/lib/bitclust/markdown_bridge.rb +186 -0
  46. data/lib/bitclust/markdown_orchestrator.rb +267 -0
  47. data/lib/bitclust/markdown_to_rrd.rb +824 -0
  48. data/lib/bitclust/markdown_tree.rb +217 -0
  49. data/lib/bitclust/mdcompiler.rb +883 -0
  50. data/lib/bitclust/mdparser.rb +347 -0
  51. data/lib/bitclust/messagecatalog.rb +3 -3
  52. data/lib/bitclust/method_since_calculator.rb +117 -0
  53. data/lib/bitclust/methoddatabase.rb +124 -14
  54. data/lib/bitclust/methodentry.rb +125 -7
  55. data/lib/bitclust/methodid.rb +12 -11
  56. data/lib/bitclust/methodsignature.rb +10 -4
  57. data/lib/bitclust/nameutils.rb +41 -22
  58. data/lib/bitclust/preprocessor.rb +66 -29
  59. data/lib/bitclust/rdcompiler.rb +195 -95
  60. data/lib/bitclust/refsdatabase.rb +23 -8
  61. data/lib/bitclust/reloadable_request_handler.rb +72 -0
  62. data/lib/bitclust/requesthandler.rb +21 -16
  63. data/lib/bitclust/ridatabase.rb +10 -4
  64. data/lib/bitclust/rrd_to_markdown.rb +925 -0
  65. data/lib/bitclust/rrdparser.rb +196 -30
  66. data/lib/bitclust/runner.rb +17 -2
  67. data/lib/bitclust/screen.rb +80 -11
  68. data/lib/bitclust/search_index_generator.rb +288 -0
  69. data/lib/bitclust/searcher.rb +69 -20
  70. data/lib/bitclust/simplesearcher.rb +24 -12
  71. data/lib/bitclust/subcommand.rb +7 -1
  72. data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
  73. data/lib/bitclust/subcommands/checklink_command.rb +54 -0
  74. data/lib/bitclust/subcommands/chm_command.rb +15 -7
  75. data/lib/bitclust/subcommands/classes_command.rb +5 -0
  76. data/lib/bitclust/subcommands/extract_command.rb +5 -0
  77. data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
  78. data/lib/bitclust/subcommands/init_command.rb +4 -1
  79. data/lib/bitclust/subcommands/list_command.rb +12 -4
  80. data/lib/bitclust/subcommands/lookup_command.rb +28 -9
  81. data/lib/bitclust/subcommands/methods_command.rb +10 -2
  82. data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
  83. data/lib/bitclust/subcommands/preproc_command.rb +5 -0
  84. data/lib/bitclust/subcommands/property_command.rb +2 -1
  85. data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
  86. data/lib/bitclust/subcommands/server_command.rb +10 -8
  87. data/lib/bitclust/subcommands/setup_command.rb +24 -10
  88. data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
  89. data/lib/bitclust/subcommands/update_command.rb +110 -2
  90. data/lib/bitclust/syntax_highlighter.rb +48 -4
  91. data/lib/bitclust/textutils.rb +3 -3
  92. data/lib/bitclust/version.rb +1 -1
  93. data/lib/bitclust/version_badges.rb +90 -0
  94. data/lib/bitclust/whole_file_gate.rb +137 -0
  95. data/test/test_app.rb +269 -0
  96. data/test/test_bitclust.rb +2 -0
  97. data/test/test_capi_converter.rb +91 -0
  98. data/test/test_class_screen.rb +413 -0
  99. data/test/test_copy_doc_md.rb +53 -0
  100. data/test/test_doc_converter.rb +78 -0
  101. data/test/test_entity_splitter.rb +231 -0
  102. data/test/test_entry.rb +207 -2
  103. data/test/test_eol_warning.rb +43 -0
  104. data/test/test_functionreferenceparser.rb +11 -0
  105. data/test/test_include_graph.rb +640 -0
  106. data/test/test_include_pruner.rb +133 -0
  107. data/test/test_link_checker.rb +114 -0
  108. data/test/test_lookup_command.rb +135 -0
  109. data/test/test_markdown_bridge.rb +217 -0
  110. data/test/test_markdown_orchestrator.rb +478 -0
  111. data/test/test_markdown_to_rrd.rb +757 -0
  112. data/test/test_markdown_tree.rb +369 -0
  113. data/test/test_mdcompiler.rb +1276 -0
  114. data/test/test_mdparser.rb +538 -0
  115. data/test/test_method_screen.rb +166 -0
  116. data/test/test_method_since_calculator.rb +290 -0
  117. data/test/test_methoddatabase.rb +16 -0
  118. data/test/test_methodentry.rb +328 -0
  119. data/test/test_methodsignature.rb +3 -1
  120. data/test/test_methodsince_command.rb +168 -0
  121. data/test/test_nameutils.rb +22 -0
  122. data/test/test_preprocessor.rb +215 -0
  123. data/test/test_rdcompiler.rb +401 -30
  124. data/test/test_refsdatabase.rb +32 -0
  125. data/test/test_rrd_to_markdown.rb +968 -0
  126. data/test/test_rrdparser.rb +295 -2
  127. data/test/test_run_ruby_wasm.rb +54 -0
  128. data/test/test_run_worker_prelude.rb +83 -0
  129. data/test/test_runner.rb +51 -0
  130. data/test/test_search_index_generator.rb +513 -0
  131. data/test/test_search_screen.rb +48 -0
  132. data/test/test_searcher.rb +132 -0
  133. data/test/test_searchpage_command.rb +167 -0
  134. data/test/test_simplesearcher.rb +28 -0
  135. data/test/test_statichtml_command.rb +204 -0
  136. data/test/test_syntax_highlighter.rb +60 -0
  137. data/test/test_whole_file_gate.rb +175 -0
  138. data/theme/default/js/NOTICE +42 -0
  139. data/theme/default/js/run-worker.js +111 -0
  140. data/theme/default/js/run.js +350 -0
  141. data/theme/default/js/search_controller.js +134 -0
  142. data/theme/default/js/search_init.js +176 -0
  143. data/theme/default/js/search_navigation.js +110 -0
  144. data/theme/default/js/search_page.js +202 -0
  145. data/theme/default/js/search_ranker.js +244 -0
  146. data/theme/default/script.js +91 -18
  147. data/theme/default/search.css +149 -0
  148. data/theme/default/style.css +193 -21
  149. data/theme/default/syntax-highlight.css +8 -0
  150. data/theme/lillia/style.css +45 -1
  151. metadata +161 -20
  152. data/README +0 -32
@@ -0,0 +1,757 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ require 'bitclust/markdown_to_rrd'
6
+
7
+ class TestMarkdownToRRD < Test::Unit::TestCase
8
+ def convert(md)
9
+ BitClust::MarkdownToRRD.convert(md)
10
+ end
11
+
12
+ # Step 1: H1 ヘッダ変換とパススルー
13
+
14
+ def test_module_header
15
+ assert_equal "= module Comparable\n",
16
+ convert("# module Comparable\n")
17
+ end
18
+
19
+ def test_class_header_with_superclass
20
+ assert_equal "= class Array < Object\n",
21
+ convert("# class Array < Object\n")
22
+ end
23
+
24
+ def test_reopen_header
25
+ assert_equal "= reopen Kernel\n",
26
+ convert("# reopen Kernel\n")
27
+ end
28
+
29
+ def test_text_passthrough
30
+ md = "# module Comparable\n\n説明文。\n"
31
+ expected = "= module Comparable\n\n説明文。\n"
32
+ assert_equal expected, convert(md)
33
+ end
34
+
35
+ def test_directive_passthrough
36
+ md = "# module Comparable\n\n\#@since 2.4.0\ntext\n\#@end\n"
37
+ expected = "= module Comparable\n\n\#@since 2.4.0\ntext\n\#@end\n"
38
+ assert_equal expected, convert(md)
39
+ end
40
+
41
+ def test_comment_passthrough
42
+ md = "\#@# これはコメント\n"
43
+ expected = "\#@# これはコメント\n"
44
+ assert_equal expected, convert(md)
45
+ end
46
+
47
+ def test_include_passthrough
48
+ md = "\#@include(io/buffer.md)\n"
49
+ expected = "\#@include(io/buffer.md)\n"
50
+ assert_equal expected, convert(md)
51
+ end
52
+
53
+ # Step 2: H2 セクションヘッダ
54
+
55
+ def test_h2_instance_methods
56
+ assert_equal "== Instance Methods\n",
57
+ convert("## Instance Methods\n")
58
+ end
59
+
60
+ def test_h2_class_methods
61
+ assert_equal "== Class Methods\n",
62
+ convert("## Class Methods\n")
63
+ end
64
+
65
+ def test_h2_constants
66
+ assert_equal "== Constants\n",
67
+ convert("## Constants\n")
68
+ end
69
+
70
+ def test_h2_module_functions
71
+ assert_equal "== Module Functions\n",
72
+ convert("## Module Functions\n")
73
+ end
74
+
75
+ # Step 3: メソッドシグネチャ + コードブロック
76
+
77
+ def test_instance_method_signature
78
+ assert_equal "--- ==(other) -> bool\n",
79
+ convert("### def ==(other) -> bool\n")
80
+ end
81
+
82
+ def test_method_signature_with_block
83
+ assert_equal "--- each {|item| ... } -> self\n",
84
+ convert("### def each {|item| ... } -> self\n")
85
+ end
86
+
87
+ def test_multiple_signatures
88
+ md = "### def [](nth) -> object | nil\n### def at(nth) -> object | nil\n"
89
+ expected = "--- [](nth) -> object | nil\n--- at(nth) -> object | nil\n"
90
+ assert_equal expected, convert(md)
91
+ end
92
+
93
+ def test_code_block_without_title
94
+ md = "```ruby\n1 == 1 # => true\n```\n"
95
+ expected = "\#@samplecode\n1 == 1 # => true\n\#@end\n"
96
+ assert_equal expected, convert(md)
97
+ end
98
+
99
+ def test_code_block_with_title
100
+ md = "```ruby title=\"例\"\n1 == 1\n```\n"
101
+ expected = "\#@samplecode 例\n1 == 1\n\#@end\n"
102
+ assert_equal expected, convert(md)
103
+ end
104
+
105
+ def test_code_block_without_language
106
+ md = "```\nplain text\n```\n"
107
+ expected = "//emlist{\nplain text\n//}\n"
108
+ assert_equal expected, convert(md)
109
+ end
110
+
111
+ def test_code_block_title_with_backslash
112
+ md = "```ruby title=\"C:\\\\path\"\ncode\n```\n"
113
+ expected = "\#@samplecode C:\\path\ncode\n\#@end\n"
114
+ assert_equal expected, convert(md)
115
+ end
116
+
117
+ def test_code_block_lang_without_title
118
+ md = "```sh\necho hello\n```\n"
119
+ expected = "//emlist[][sh]{\necho hello\n//}\n"
120
+ assert_equal expected, convert(md)
121
+ end
122
+
123
+ def test_code_block_longer_fence
124
+ md = "````ruby\ncode\n````\n"
125
+ expected = "\#@samplecode\ncode\n\#@end\n"
126
+ assert_equal expected, convert(md)
127
+ end
128
+
129
+ def test_code_block_longer_fence_not_closed_by_shorter
130
+ md = "````ruby\ncode\n```\nmore\n````\n"
131
+ expected = "\#@samplecode\ncode\n```\nmore\n\#@end\n"
132
+ assert_equal expected, convert(md)
133
+ end
134
+
135
+ # Step 4: self. → ClassName. 変換
136
+
137
+ def test_classname_method_preserved
138
+ md = <<~MD
139
+ # class Array < Object
140
+
141
+ ## Class Methods
142
+
143
+ ### def Array.try_convert(obj) -> Array | nil
144
+ MD
145
+ result = convert(md)
146
+ assert_match(/\A--- Array\.try_convert/, result.lines[4])
147
+ end
148
+
149
+ def test_module_function_to_rrd
150
+ md = <<~MD
151
+ # module Benchmark
152
+
153
+ ## Module Functions
154
+
155
+ ### module_function def measure(label) -> Benchmark::Tms
156
+ MD
157
+ result = convert(md)
158
+ assert_match(/--- measure\(label\)/, result)
159
+ end
160
+
161
+ # Step 5: 定数・グローバル変数
162
+
163
+ def test_constant_signature
164
+ assert_equal "--- CR -> String\n",
165
+ convert("### const CR -> String\n")
166
+ end
167
+
168
+ def test_constant_without_arrow
169
+ assert_equal "--- START\n",
170
+ convert("### const START\n")
171
+ end
172
+
173
+ def test_global_variable_signature
174
+ assert_equal "--- $DEBUG -> bool\n",
175
+ convert("### gvar $DEBUG -> bool\n")
176
+ end
177
+
178
+ # module_function
179
+
180
+ def test_module_function_signature
181
+ assert_equal "--- measure(label) -> Benchmark::Tms\n",
182
+ convert("### module_function def measure(label) -> Benchmark::Tms\n")
183
+ end
184
+
185
+ # ClassName.method はそのまま保持
186
+
187
+ def test_class_method_keeps_classname
188
+ assert_equal "--- Array.try_convert(obj) -> Array\n",
189
+ convert("### def Array.try_convert(obj) -> Array\n")
190
+ end
191
+
192
+ # .? → .# 参照変換
193
+
194
+ def test_module_function_ref_roundtrip
195
+ assert_equal "[[m:Kernel.#open]]\n",
196
+ convert("[m:Kernel?.open]\n")
197
+ end
198
+
199
+ # Step 6: param/return/raise メタデータ
200
+
201
+ def test_param
202
+ assert_equal "@param other 自身と比較したいオブジェクトを指定します。\n",
203
+ convert("- **param** `other` -- 自身と比較したいオブジェクトを指定します。\n")
204
+ end
205
+
206
+ def test_raise
207
+ assert_equal "@raise ArgumentError <=> が nil を返したときに発生します。\n",
208
+ convert("- **raise** `ArgumentError` -- <=> が nil を返したときに発生します。\n")
209
+ end
210
+
211
+ def test_return_without_type
212
+ assert_equal "@return 説明\n",
213
+ convert("- **return** -- 説明\n")
214
+ end
215
+
216
+ def test_return_with_type
217
+ assert_equal "@return 説明\n",
218
+ convert("- **return** `String` -- 説明\n")
219
+ end
220
+
221
+ def test_metadata_continuation_line
222
+ md = "- **raise** `ArgumentError` -- self <=> min か、self <=> max が nil を返\n したときに発生します。\n"
223
+ expected = "@raise ArgumentError self <=> min か、self <=> max が nil を返\n したときに発生します。\n"
224
+ assert_equal expected, convert(md)
225
+ end
226
+
227
+ # Step 7: SEE 参照
228
+
229
+ def test_see_standalone
230
+ assert_equal "@see [[m:String#-@]]\n",
231
+ convert("**SEE** [m:String#-@]\n")
232
+ end
233
+
234
+ def test_see_multiple
235
+ assert_equal "@see [[m:Array#-]], [[m:Array#union]]\n",
236
+ convert("**SEE** [m:Array#-], [m:Array#union]\n")
237
+ end
238
+
239
+ def test_see_with_different_types
240
+ assert_equal "@see [[d:spec/m17n]]\n",
241
+ convert("**SEE** [d:spec/m17n]\n")
242
+ end
243
+
244
+ # Step 8: クロスリファレンス(インライン)
245
+
246
+ def test_inline_class_ref
247
+ assert_equal "[[c:String]] を参照。\n",
248
+ convert("[c:String] を参照。\n")
249
+ end
250
+
251
+ def test_inline_method_ref
252
+ assert_equal "[[m:Array#each]] と同じ。\n",
253
+ convert("[m:Array#each] と同じ。\n")
254
+ end
255
+
256
+ def test_inline_ref_with_surrounding_text
257
+ assert_equal "[[m:Array#dup]] 同様\n",
258
+ convert("[m:Array#dup] 同様\n")
259
+ end
260
+
261
+ def test_inline_lib_ref
262
+ assert_equal "[[lib:json]] を使う。\n",
263
+ convert("[lib:json] を使う。\n")
264
+ end
265
+
266
+ def test_inline_ref_not_in_code_block
267
+ md = "```ruby\n[c:String]\n```\n"
268
+ expected = "\#@samplecode\n[c:String]\n\#@end\n"
269
+ assert_equal expected, convert(md)
270
+ end
271
+
272
+ def test_inline_multiple_refs_in_line
273
+ assert_equal "[[c:Array]] と [[c:Hash]] を使う。\n",
274
+ convert("[c:Array] と [c:Hash] を使う。\n")
275
+ end
276
+
277
+ # Step 9: アンカー付き見出し・H4
278
+
279
+ def test_h3_with_anchor
280
+ assert_equal "===[a:mutable] 破壊的な変更\n",
281
+ convert("### 破壊的な変更 {#mutable}\n")
282
+ end
283
+
284
+ def test_h3_with_hyphenated_anchor
285
+ # doctree/manual の glossary.md 等、用語アンカーはハイフン区切り
286
+ assert_equal "===[a:thread-safe] スレッドセーフ\n",
287
+ convert("### スレッドセーフ {#thread-safe}\n")
288
+ end
289
+
290
+ def test_h4_heading
291
+ assert_equal "==== 小見出し\n",
292
+ convert("#### 小見出し\n")
293
+ end
294
+
295
+ def test_h4_with_anchor
296
+ assert_equal "====[a:detail] 詳細\n",
297
+ convert("#### 詳細 {#detail}\n")
298
+ end
299
+
300
+ # Step 10: YAML front matter
301
+
302
+ def test_include_metadata_passthrough
303
+ md = "# class Array < Object\ninclude Enumerable\n\n説明\n"
304
+ expected = "= class Array < Object\ninclude Enumerable\n\n説明\n"
305
+ assert_equal expected, convert(md)
306
+ end
307
+
308
+ def test_multiple_includes_passthrough
309
+ md = "# class Array < Object\ninclude Enumerable\ninclude Comparable\n"
310
+ expected = "= class Array < Object\ninclude Enumerable\ninclude Comparable\n"
311
+ assert_equal expected, convert(md)
312
+ end
313
+
314
+ def test_extend_passthrough
315
+ md = "# class Foo < Object\nextend Forwardable\n"
316
+ expected = "= class Foo < Object\nextend Forwardable\n"
317
+ assert_equal expected, convert(md)
318
+ end
319
+
320
+ def test_alias_passthrough
321
+ md = "# class Integer < Numeric\nalias Fixnum\n"
322
+ expected = "= class Integer < Numeric\nalias Fixnum\n"
323
+ assert_equal expected, convert(md)
324
+ end
325
+
326
+ def test_front_matter_include_to_body
327
+ md = "---\ninclude:\n - Enumerable\n---\n# class Array < Object\n\n説明\n"
328
+ expected = "= class Array < Object\ninclude Enumerable\n\n説明\n"
329
+ assert_equal expected, convert(md)
330
+ end
331
+
332
+ def test_front_matter_multiple_includes_to_body
333
+ md = "---\ninclude:\n - Enumerable\n - Comparable\n---\n# class Array < Object\n"
334
+ expected = "= class Array < Object\ninclude Enumerable\ninclude Comparable\n"
335
+ assert_equal expected, convert(md)
336
+ end
337
+
338
+ def test_front_matter_extend_to_body
339
+ md = "---\nextend:\n - Forwardable\n---\n# class Foo < Object\n"
340
+ expected = "= class Foo < Object\nextend Forwardable\n"
341
+ assert_equal expected, convert(md)
342
+ end
343
+
344
+ def test_front_matter_alias_to_body
345
+ md = "---\nalias:\n - Fixnum\n---\n# class Integer < Numeric\n"
346
+ expected = "= class Integer < Numeric\nalias Fixnum\n"
347
+ assert_equal expected, convert(md)
348
+ end
349
+
350
+ def test_front_matter_relations_grammar_order
351
+ # RD 文法順(alias → extend → include)で body に復元する
352
+ md = "---\ninclude:\n - I\nextend:\n - E\nalias:\n - A\n---\n# class Foo < Object\n"
353
+ expected = "= class Foo < Object\nalias A\nextend E\ninclude I\n"
354
+ assert_equal expected, convert(md)
355
+ end
356
+
357
+ def test_front_matter_versioned_alias_to_body
358
+ # front matter 内の #@ を保持して body に復元する(YAML コメントで失わない)
359
+ md = "---\nalias:\n\#@until 3.2\n - Fixnum\n - Bignum\n\#@end\n---\n# class Integer < Numeric\n\n説明\n"
360
+ expected = "= class Integer < Numeric\n\#@until 3.2\nalias Fixnum\nalias Bignum\n\#@end\n\n説明\n"
361
+ assert_equal expected, convert(md)
362
+ end
363
+
364
+ def test_front_matter_versioned_require_to_body
365
+ md = "---\ncategory: Network\nrequire:\n\#@since 1.9.1\n - cgi/core\n - cgi/cookie\n\#@end\n---\n説明\n"
366
+ expected = "category Network\n\n\#@since 1.9.1\nrequire cgi/core\nrequire cgi/cookie\n\#@end\n\n説明\n"
367
+ assert_equal expected, convert(md)
368
+ end
369
+
370
+ def test_front_matter_category
371
+ md = "---\ncategory: Network\n---\nライブラリの説明\n"
372
+ expected = "category Network\n\nライブラリの説明\n"
373
+ assert_equal expected, convert(md)
374
+ end
375
+
376
+ def test_front_matter_require
377
+ md = "---\nrequire:\n - cgi/core\n - cgi/cookie\n---\nライブラリの説明\n"
378
+ expected = "require cgi/core\nrequire cgi/cookie\n\nライブラリの説明\n"
379
+ assert_equal expected, convert(md)
380
+ end
381
+
382
+ def test_front_matter_sublibrary
383
+ md = "---\nsublibrary:\n - json/ext\n---\nライブラリの説明\n"
384
+ expected = "sublibrary json/ext\n\nライブラリの説明\n"
385
+ assert_equal expected, convert(md)
386
+ end
387
+
388
+ def test_front_matter_name_is_dropped
389
+ # name: は md 側のファイル名衝突回避(rdoc/rdoc.lib.md)でライブラリ名を
390
+ # 保持するための md 専用キー。rd には現れない
391
+ md = "---\ntype: library\nname: rdoc/rdoc\n---\nライブラリの説明\n"
392
+ assert_equal "ライブラリの説明\n", convert(md)
393
+ end
394
+
395
+ def test_front_matter_category_and_require
396
+ md = "---\ncategory: Network\nrequire:\n - socket\n---\nライブラリの説明\n"
397
+ expected = "category Network\n\nrequire socket\n\nライブラリの説明\n"
398
+ assert_equal expected, convert(md)
399
+ end
400
+
401
+ def test_front_matter_gated_category_is_restored
402
+ # cmath 型の据え置きゲート: ゲート付き category は #@ 行ごと復元する
403
+ md = "---\n\#@since 1.9.1\ncategory: Math\n\#@end\n---\n" \
404
+ "\#@since 1.9.1\n本文。\n\#@end\n"
405
+ expected = "\#@since 1.9.1\ncategory Math\n\#@end\n\n" \
406
+ "\#@since 1.9.1\n本文。\n\#@end\n"
407
+ assert_equal expected, convert(md)
408
+ end
409
+
410
+ def test_front_matter_gated_metadata_blocks_are_restored
411
+ # regate_metadata 形: require と sublibrary が別々のゲートブロック
412
+ md = "---\n" \
413
+ "require:\n\#@since 1.9.1\n - a\n - b\n\#@end\n" \
414
+ "sublibrary:\n\#@since 1.9.1\n - s\n\#@end\n" \
415
+ "---\n" \
416
+ "\#@since 1.9.1\n本文。\n\#@end\n"
417
+ expected = "\#@since 1.9.1\nrequire a\nrequire b\n\#@end\n\n" \
418
+ "\#@since 1.9.1\nsublibrary s\n\#@end\n\n" \
419
+ "\#@since 1.9.1\n本文。\n\#@end\n"
420
+ assert_equal expected, convert(md)
421
+ end
422
+
423
+ def test_front_matter_gated_library_list_is_dropped
424
+ # 多重所属のゲート付き library リスト(注入キー)は md→rd で完全に捨てる。
425
+ # ブロック内の #@ 行が leading コメント扱いで本文へ漏れないこと
426
+ md = "---\n" \
427
+ "library:\n" \
428
+ " - _builtin\n" \
429
+ "\#@until 1.9.1\n" \
430
+ " - thread\n" \
431
+ "\#@end\n" \
432
+ "---\n" \
433
+ "# class Mutex < Object\n\n説明\n"
434
+ assert_equal "= class Mutex < Object\n\n説明\n", convert(md)
435
+ end
436
+
437
+ # Step 11: リスト変換
438
+
439
+ def test_list_items
440
+ md = "- self が other より大きいなら正の整数\n- self と other が等しいなら 0\n"
441
+ expected = " * self が other より大きいなら正の整数\n * self と other が等しいなら 0\n"
442
+ assert_equal expected, convert(md)
443
+ end
444
+
445
+ def test_list_items_with_indent_preserved
446
+ # B4: 元のインデント幅を保持
447
+ md = " - item1\n - item2\n"
448
+ expected = " * item1\n * item2\n"
449
+ assert_equal expected, convert(md)
450
+ end
451
+
452
+ def test_list_items_with_2space_indent
453
+ md = " - item1\n - item2\n"
454
+ expected = " * item1\n * item2\n"
455
+ assert_equal expected, convert(md)
456
+ end
457
+
458
+ def test_list_not_confused_with_metadata
459
+ md = "- **param** `x` -- 説明\n- 普通のリスト\n"
460
+ expected = "@param x 説明\n * 普通のリスト\n"
461
+ assert_equal expected, convert(md)
462
+ end
463
+
464
+ # Step 12: 定義リスト
465
+
466
+ def test_definition_list_dash_format
467
+ md = "- **type** -- Content-Type header\n"
468
+ expected = ": type\n Content-Type header\n"
469
+ assert_equal expected, convert(md)
470
+ end
471
+
472
+ def test_definition_list_colon_format
473
+ md = "- **type**: Content-Type header\n"
474
+ expected = ": type\n Content-Type header\n"
475
+ assert_equal expected, convert(md)
476
+ end
477
+
478
+ def test_definition_list_colon_multiple
479
+ md = "- **type**: Content-Type header\n- **charset**: Character set\n"
480
+ expected = ": type\n Content-Type header\n: charset\n Character set\n"
481
+ assert_equal expected, convert(md)
482
+ end
483
+
484
+ # Step 13: 統合テスト — Comparable
485
+
486
+ def test_comparable_integration
487
+ md_path = File.expand_path('../../samples/Comparable.v2.md', __dir__)
488
+ omit 'samples/Comparable.v2.md not found' unless File.exist?(md_path)
489
+
490
+ md = File.read(md_path)
491
+ result = convert(md)
492
+
493
+ # 構造的に正しい RD が生成されていることを検証
494
+ assert_match(/\A= module Comparable\n/, result)
495
+ assert_match(/^== Instance Methods\n/, result)
496
+ assert_match(/^--- ==\(other\) -> bool$/, result)
497
+ assert_match(/^--- >\(other\) -> bool$/, result)
498
+ assert_match(/^--- >=\(other\) -> bool$/, result)
499
+ assert_match(/^--- <\(other\) -> bool$/, result)
500
+ assert_match(/^--- <=\(other\) -> bool$/, result)
501
+ assert_match(/^--- between\?\(min, max\) -> bool$/, result)
502
+ assert_match(/^--- clamp\(min, max\) -> object$/, result)
503
+ assert_match(/^@param other/, result)
504
+ assert_match(/^@param min/, result)
505
+ assert_match(/^@raise ArgumentError/, result)
506
+ assert_match(/^\#@samplecode/, result)
507
+ assert_match(/^\#@end/, result)
508
+ assert_match(/^\#@since 2\.4\.0/, result)
509
+ assert_match(/^\#@since 2\.7\.0/, result)
510
+ assert_match(/^\#@since 3\.0/, result)
511
+ end
512
+
513
+ # Bug fixes: サンプルファイル検証で発見
514
+
515
+ def test_see_as_list_item
516
+ # - **see** は @see に変換すべき
517
+ assert_equal "@see [[d:spec/m17n]]\n",
518
+ convert("- **see** [d:spec/m17n]\n")
519
+ end
520
+
521
+ def test_see_as_list_item_with_multiple_refs
522
+ md = "- **see** [m:CGI.accept_charset], [m:CGI.accept_charset=]\n"
523
+ expected = "@see [[m:CGI.accept_charset]], [[m:CGI.accept_charset=]]\n"
524
+ assert_equal expected, convert(md)
525
+ end
526
+
527
+ def test_front_matter_no_leading_blank_line
528
+ # front matter 後に余分な空行が出力されない
529
+ md = "---\ninclude:\n - Comparable\n---\n\n# class String < Object\n"
530
+ result = convert(md)
531
+ assert_match(/\A= class String < Object\n/, result)
532
+ end
533
+
534
+ def test_dlist_item_inline_refs
535
+ # 定義リスト内のインライン参照が変換される
536
+ md = "- **expires** -- 有効期限を [c:Time] で指定します。\n"
537
+ expected = ": expires\n 有効期限を [[c:Time]] で指定します。\n"
538
+ assert_equal expected, convert(md)
539
+ end
540
+
541
+ def test_metadata_inline_refs
542
+ # @param 内のインライン参照が変換される
543
+ md = "- **param** `options` -- [c:Hash] か文字列で指定します。\n"
544
+ expected = "@param options [[c:Hash]] か文字列で指定します。\n"
545
+ assert_equal expected, convert(md)
546
+ end
547
+
548
+ def test_see_with_hyphenated_ref_type
549
+ # ruby-list: のようなハイフン付き参照型
550
+ assert_equal "@see [[ruby-list:35911]]\n",
551
+ convert("- **see** [ruby-list:35911]\n")
552
+ end
553
+
554
+ def test_inline_hyphenated_ref_type
555
+ assert_equal "[[ruby-dev:12345]] を参照。\n",
556
+ convert("[ruby-dev:12345] を参照。\n")
557
+ end
558
+
559
+ # A2: ] エスケープのラウンドトリップ
560
+
561
+ def test_bracket_method_ref_roundtrip
562
+ # [m:Hash#\[\]] → [[m:Hash#[] ]]
563
+ assert_equal "[[m:Hash#[] ]]\n",
564
+ convert("[m:Hash#\\[\\]]\n")
565
+ end
566
+
567
+ def test_bracket_method_in_text_roundtrip
568
+ assert_equal "@see [[m:Hash#[] ]]\n",
569
+ convert("@see [m:Hash#\\[\\]]\n")
570
+ end
571
+
572
+ # B2/B5: メタデータスペース保持
573
+
574
+ def test_return_alignment_spaces_roundtrip
575
+ # - **return** -- 説明 → @return 説明
576
+ assert_equal "@return 説明\n",
577
+ convert("- **return** -- 説明\n")
578
+ end
579
+
580
+ def test_param_spaces_roundtrip
581
+ assert_equal "@param obj 説明\n",
582
+ convert("- **param** `obj` -- 説明\n")
583
+ end
584
+
585
+ # B3: @see 区切り保持
586
+
587
+ def test_see_comma_no_space_roundtrip
588
+ assert_equal "@see [[m:A]],[[m:B]]\n",
589
+ convert("@see [m:A],[m:B]\n")
590
+ end
591
+
592
+ def test_see_comma_with_space_roundtrip
593
+ assert_equal "@see [[m:A]], [[m:B]]\n",
594
+ convert("@see [m:A], [m:B]\n")
595
+ end
596
+
597
+ # C1: インデントコード -3
598
+
599
+ def test_indented_code_fenced
600
+ md = "説明\n`````\ncode line 1\ncode line 2\n`````\n\n"
601
+ expected = "説明\n code line 1\n code line 2\n\n"
602
+ assert_equal expected, convert(md)
603
+ end
604
+
605
+ def test_indented_code_fenced_8spaces
606
+ md = "説明\n```````````\ncode\n```````````\n\n"
607
+ expected = "説明\n code\n\n"
608
+ assert_equal expected, convert(md)
609
+ end
610
+
611
+ def test_indented_code_preserves_inner_indent
612
+ md = "説明\n````````\nif true\n puts 1\nend\n````````\n\n"
613
+ expected = "説明\n if true\n puts 1\n end\n\n"
614
+ assert_equal expected, convert(md)
615
+ end
616
+
617
+ def test_empty_title_treated_as_no_title
618
+ md = "```ruby title=\"\"\ncode\n```\n"
619
+ expected = "\#@samplecode\ncode\n\#@end\n"
620
+ assert_equal expected, convert(md)
621
+ end
622
+
623
+ # 番号付きリスト
624
+
625
+ def test_ordered_list
626
+ md = "1. 最初の項目\n2. 次の項目\n3. 最後の項目\n"
627
+ expected = " (1) 最初の項目\n (2) 次の項目\n (3) 最後の項目\n"
628
+ assert_equal expected, convert(md)
629
+ end
630
+
631
+ def test_bold_number_to_text
632
+ md = "**1.** テキスト\n"
633
+ expected = "1. テキスト\n"
634
+ assert_equal expected, convert(md)
635
+ end
636
+
637
+ # リスト項目のスペース保持
638
+
639
+ def test_list_item_preserves_multiple_spaces
640
+ md = "- item\n"
641
+ expected = " * item\n"
642
+ assert_equal expected, convert(md)
643
+ end
644
+
645
+ # ---- doc ツリー対応: リテラルエスケープの復元 ----
646
+
647
+ def test_escaped_bare_ref_is_unescaped_literally
648
+ # rd→md が「参照に見えるリテラル」を \[ でエスケープしたものを復元する
649
+ assert_equal " [ruby-talk:198440] を参照。\n",
650
+ convert(" \\[ruby-talk:198440] を参照。\n")
651
+ end
652
+
653
+ def test_unescaped_bare_ref_is_still_restored
654
+ assert_equal "[[m:Array#each]] を参照。\n", convert("[m:Array#each] を参照。\n")
655
+ end
656
+
657
+ def test_escaped_backtick_bare_ref_resolves_same_as_unescaped
658
+ # znz さん提示の書き方(doctree#3232): [m:$\`](バッククォートを
659
+ # エスケープした参照)は [m:$`](素のバッククォート、既存の後方互換)
660
+ # と同じ [[m:$`]] へ復元される
661
+ assert_equal "[[m:$`]] を参照。\n", convert("[m:$\\`] を参照。\n")
662
+ assert_equal "[[m:$`]] を参照。\n", convert("[m:$`] を参照。\n")
663
+ end
664
+
665
+ def test_escaped_leading_hash_is_unescaped
666
+ assert_equal "# : 2002-08-01 IO#read\n# 本文。\n",
667
+ convert("\\# : 2002-08-01 IO#read\n\\# 本文。\n")
668
+ end
669
+
670
+ def test_anchored_h1_restores
671
+ assert_equal "=[a:ruby] Rubyの起動\n", convert("# Rubyの起動 {#ruby}\n")
672
+ end
673
+
674
+ def test_heading_trailing_space_restores
675
+ assert_equal "=== 2004-12-06 \n", convert("### 2004-12-06 \n")
676
+ end
677
+
678
+ # ---- restore_description: entry description 用の rd 表示テキスト復元 ----
679
+
680
+ def test_restore_description_list_markers
681
+ # DublinCoreModel 型: description(先頭段落)がリストのとき、
682
+ # md の行頭マーカーを rd 表示(* / (N))へ戻す
683
+ assert_equal "* [[url:http://example.org/]]",
684
+ BitClust::MarkdownToRRD.restore_description("- [url:http://example.org/]")
685
+ assert_equal "(1) 一つ目\n(2) 二つ目",
686
+ BitClust::MarkdownToRRD.restore_description("1. 一つ目\n2. 二つ目")
687
+ # **N.** の太字番号(rd では離散番号テキスト)は N. のまま戻し、
688
+ # olist 復元で (N) 化しない
689
+ assert_equal "1. 番号テキスト",
690
+ BitClust::MarkdownToRRD.restore_description("**1.** 番号テキスト")
691
+ end
692
+
693
+ def test_restore_description_heading
694
+ # doc/help 型: description(先頭段落)が見出しのとき md 記法を rd 表示へ
695
+ assert_equal "=== 記号の説明",
696
+ BitClust::MarkdownToRRD.restore_description("### 記号の説明")
697
+ assert_equal "===[a:str] 特別な文字列に対するマッチ",
698
+ BitClust::MarkdownToRRD.restore_description("### 特別な文字列に対するマッチ {#str}")
699
+ # ハイフン入りアンカー(doctree/manual の glossary.md 用語アンカー等)も
700
+ # プレースホルダ往復(\x00...\x00 → [a:...])を経て正しく復元される
701
+ assert_equal "===[a:thread-safe] スレッドセーフ",
702
+ BitClust::MarkdownToRRD.restore_description("### スレッドセーフ {#thread-safe}")
703
+ end
704
+
705
+ def test_restore_description_metadata
706
+ # CGI.escapeElement / ACL 型: @param・@see が先頭段落に来る
707
+ assert_equal "@param string 文字列を指定します。",
708
+ BitClust::MarkdownToRRD.restore_description("- **param** `string` -- 文字列を指定します。")
709
+ assert_equal "@see [[m:ACL.new]]",
710
+ BitClust::MarkdownToRRD.restore_description("- **SEE** [m:ACL.new]")
711
+ assert_equal "@param bool 真偽値。\n@see [[m:BasicSocket#do_not_reverse_lookup]]",
712
+ BitClust::MarkdownToRRD.restore_description(
713
+ "- **param** `bool` -- 真偽値。\n- **SEE** [m:BasicSocket#do_not_reverse_lookup]")
714
+ assert_equal "@raise TypeError 型が合わないとき。",
715
+ BitClust::MarkdownToRRD.restore_description("- **raise** `TypeError` -- 型が合わないとき。")
716
+ assert_equal "@return 結果。",
717
+ BitClust::MarkdownToRRD.restore_description("- **return** -- 結果。")
718
+ end
719
+
720
+ def test_restore_description_fences
721
+ # Addrinfo 型: 段落先頭がフェンス(閉じは次の段落へ切れている)
722
+ assert_equal " require 'socket'",
723
+ BitClust::MarkdownToRRD.restore_description("`````\nrequire 'socket'")
724
+ # LL2NUM 型: 説明行にフェンスブロックが直結(閉じフェンスあり)
725
+ assert_equal "説明。\n long long n = 42;\n VALUE num = LL2NUM(n);",
726
+ BitClust::MarkdownToRRD.restore_description("説明。\n``````\nlong long n = 42;\nVALUE num = LL2NUM(n);\n``````")
727
+ # ENV.each 型: ```ruby は旧経路(前処理後)の //emlist 形へ。
728
+ # フェンス内容行は復元を受けない(# => が見出し復元で壊れない)
729
+ assert_equal "//emlist[][ruby]{\nENV['FOO'] = 'bar'\n# => ENV\n//}",
730
+ BitClust::MarkdownToRRD.restore_description("```ruby\nENV['FOO'] = 'bar'\n# => ENV\n```")
731
+ assert_equal "//emlist[例][ruby]{\np [c:String]\n//}",
732
+ BitClust::MarkdownToRRD.restore_description("```ruby title=\"例\"\np [c:String]\n```")
733
+ assert_equal "//emlist{\nplain - text\n//}",
734
+ BitClust::MarkdownToRRD.restore_description("```\nplain - text\n```")
735
+ end
736
+
737
+ def test_restore_description_escapes
738
+ # stat_col 型: 行頭 # のエスケープ、symref 型: \` のエスケープ
739
+ assert_equal "#ifdef HASH_LOG のときだけ定義される、開発者用関数。",
740
+ BitClust::MarkdownToRRD.restore_description("\\#ifdef HASH_LOG のときだけ定義される、開発者用関数。")
741
+ assert_equal "付記B: `未定義` の振る舞いの例",
742
+ BitClust::MarkdownToRRD.restore_description("付記B: \\`未定義\\` の振る舞いの例")
743
+ end
744
+
745
+ # ---- メタデータ領域の #@# コメントの復元(irb.rd 対応)----
746
+
747
+ def test_leading_comment_restores_before_category
748
+ md = "---\n\#@# Author: Keiju\ncategory: Development\n---\n本文。\n"
749
+ assert_equal "\#@# Author: Keiju\n\ncategory Development\n\n本文。\n", convert(md)
750
+ end
751
+
752
+ def test_comment_inside_require_block_restores
753
+ md = "---\ncategory: Development\nrequire:\n - b\n\#@# note\n - c\n---\n本文。\n"
754
+ assert_equal "category Development\n\nrequire b\n\#@# note\nrequire c\n\n本文。\n", convert(md)
755
+ end
756
+
757
+ end