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,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ require 'bitclust/include_pruner'
6
+
7
+ # IncludePruner: RD テキストから指定 target の #@include 行を除去する純変換(rd→rd)。
8
+ # 新パイプラインでは grouping include(エンティティの取り込み)は glob + front matter の
9
+ # 発見に置き換わるため、ライブラリ概要ファイルから除去する。fragment include は温存。
10
+ #
11
+ # テストリスト:
12
+ # [x] target が無ければ入力を変更しない(バイト一致)
13
+ # [x] 指定 target の #@include 行を除去する
14
+ # [x] 指定外の target(fragment include)は残す
15
+ # [x] 連続する include 行の除去
16
+ # [x] 空行で区切られた include 群 → 空行が1つに畳まれる(net/http.rd パターン)
17
+ # [x] 除去で空になった版ゲートブロックはブロックごと除去(_builtin.rd の Bignum パターン)
18
+ # [x] ネストしたブロックが連鎖的に空になる場合も除去
19
+ # [x] #@else 枝の中の include 除去、枝に他の内容があればブロックは残る(thread.rd パターン)
20
+ # [x] 片枝だけ空になった #@else ブロックは構造を保持
21
+ # [x] 両枝とも空になったブロックは全体を除去
22
+ # [x] もともと空のブロックは(除去が起きなければ)そのまま
23
+ # [x] 末尾 include 除去後、直前の空行は保持する(continuation.rd/webrick/server.rd パターン。
24
+ # メタデータ領域再生成の空行仕様と整合し md→rd がバイト一致する。空行終端は既存149ファイルと同スタイル)
25
+ # [x] ファイル先頭の include 除去で残る先頭空行は削る(doctree 正規化と整合)
26
+ # [x] #@samplecode ブロックは版ゲートとして扱わない(空でも除去しない)
27
+ class TestIncludePruner < Test::Unit::TestCase
28
+ def prune(src, targets)
29
+ BitClust::IncludePruner.prune(src, targets)
30
+ end
31
+
32
+ def test_no_targets_returns_input_unchanged
33
+ src = "text\n\n\#@include(foo/Bar)\n"
34
+ assert_equal src, prune(src, [])
35
+ assert_equal src, prune(src, ["other/Target"])
36
+ end
37
+
38
+ def test_removes_matching_include_line
39
+ src = "text\n\#@include(foo/Bar)\ntext2\n"
40
+ assert_equal "text\ntext2\n", prune(src, ["foo/Bar"])
41
+ end
42
+
43
+ def test_keeps_non_matching_includes
44
+ src = "\#@include(frag)\n\#@include(foo/Bar)\n"
45
+ assert_equal "\#@include(frag)\n", prune(src, ["foo/Bar"])
46
+ end
47
+
48
+ def test_unbalanced_gates_leave_input_unchanged
49
+ # 対応の取れない #@end を含むファイルは構造が判定できないので手を付けない
50
+ src = "\#@end\n\#@include(a/A)\n"
51
+ assert_equal src, prune(src, ["a/A"])
52
+ end
53
+
54
+ def test_removes_consecutive_includes
55
+ src = "text\n\n\#@include(a/A)\n\#@include(a/B)\n\#@include(a/C)\n"
56
+ assert_equal "text\n\n", prune(src, ["a/A", "a/B", "a/C"])
57
+ end
58
+
59
+ def test_collapses_blank_lines_between_removed_includes
60
+ # net/http.rd: include が空行区切りで並ぶ
61
+ src = "text\n\n\#@include(a/A)\n\n\#@include(a/B)\n\n\#@include(a/C)\n\ntext2\n"
62
+ assert_equal "text\n\ntext2\n", prune(src, ["a/A", "a/B", "a/C"])
63
+ end
64
+
65
+ def test_removes_emptied_gate_block
66
+ # _builtin.rd: #@until 2.4.0 / #@include(_builtin/Bignum) / #@end
67
+ src = "\#@include(a/A)\n\#@until 2.4.0\n\#@include(a/B)\n\#@end\n\#@include(a/C)\n"
68
+ assert_equal "", prune(src, ["a/A", "a/B", "a/C"])
69
+ end
70
+
71
+ def test_removes_nested_emptied_blocks
72
+ src = "text\n\#@since 2.0.0\n\#@until 3.0\n\#@include(a/A)\n\#@end\n\#@end\n"
73
+ assert_equal "text\n", prune(src, ["a/A"])
74
+ end
75
+
76
+ def test_keeps_block_when_branch_retains_content
77
+ # thread.rd: #@else 枝に散文とエンティティ定義が残る
78
+ src = <<~RRD
79
+ \#@since 2.3.0
80
+ 新しい説明。
81
+ \#@else
82
+ 古い説明。
83
+ \#@until 1.9.1
84
+ \#@include(thread/Mutex)
85
+ \#@end
86
+ \#@include(thread/Queue)
87
+ \#@end
88
+ RRD
89
+ expected = <<~RRD
90
+ \#@since 2.3.0
91
+ 新しい説明。
92
+ \#@else
93
+ 古い説明。
94
+ \#@end
95
+ RRD
96
+ assert_equal expected, prune(src, ["thread/Mutex", "thread/Queue"])
97
+ end
98
+
99
+ def test_keeps_block_structure_when_only_one_branch_empties
100
+ src = "\#@until 2.0.0\n\#@include(a/Old)\n\#@else\n説明。\n\#@end\n"
101
+ expected = "\#@until 2.0.0\n\#@else\n説明。\n\#@end\n"
102
+ assert_equal expected, prune(src, ["a/Old"])
103
+ end
104
+
105
+ def test_removes_block_when_both_branches_empty
106
+ src = "text\n\#@until 2.0.0\n\#@include(a/Old)\n\#@else\n\#@include(a/New)\n\#@end\n"
107
+ assert_equal "text\n", prune(src, ["a/Old", "a/New"])
108
+ end
109
+
110
+ def test_keeps_preexisting_empty_block_without_removal
111
+ src = "\#@since 2.0.0\n\#@end\ntext\n\#@include(a/A)\n"
112
+ assert_equal "\#@since 2.0.0\n\#@end\ntext\n", prune(src, ["a/A"])
113
+ end
114
+
115
+ def test_keeps_preceding_blank_when_trailing_include_removed
116
+ # continuation.rd: 本文 + 空行 + include で終わる。空行は保持する
117
+ # (webrick/server.rd の require 群 + 空行 + include 群でも、md→rd の
118
+ # メタデータ再生成が「require 群 + 空行」を出すため、保持しないと一致しない)
119
+ src = "本文。\n\n\#@include(_builtin/Continuation)\n"
120
+ assert_equal "本文。\n\n", prune(src, ["_builtin/Continuation"])
121
+ end
122
+
123
+ def test_strips_leading_blank_after_removal_at_bof
124
+ src = "\#@include(a/A)\n\ntext\n"
125
+ assert_equal "text\n", prune(src, ["a/A"])
126
+ end
127
+
128
+ def test_samplecode_block_is_not_a_gate_block
129
+ # samplecode の #@end を版ゲートと混同すると対応が壊れる
130
+ src = "\#@samplecode 例\ncode\n\#@end\n\#@since 3.0\n\#@include(a/A)\n\#@end\n"
131
+ assert_equal "\#@samplecode 例\ncode\n\#@end\n", prune(src, ["a/A"])
132
+ end
133
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'bitclust'
7
+ require 'bitclust/methoddatabase'
8
+ require 'bitclust/link_checker'
9
+
10
+ # bitclust#286 派生の要望: [[c:]]/[[m:]] 等の参照は描画時に存在検証されず
11
+ # リンク切れが静かに発生するため、DB 全体を走査して壊れた参照を報告する
12
+ # LinkChecker を提供する。抽出は実際のコンパイル経路(bracket_link)を
13
+ # 使うので、コードスパン・フェンス内の参照風テキストは対象外になる。
14
+ class TestLinkChecker < Test::Unit::TestCase
15
+ FOO_MD = <<~'MD'
16
+ ---
17
+ library: foo
18
+ ---
19
+ # class Foo < Object
20
+
21
+ 正しい参照: [c:Foo] と [m:Foo#bar] と [m:Foo.baz] と [m:Foo::CONST]
22
+ と [lib:foo] と [d:spec/page]。
23
+
24
+ 壊れた参照: [c:Nope] と [m:Foo#nope] と [m:Nope#x] と [lib:nolib]
25
+ と [d:spec/nope]。
26
+
27
+ コードスパン内は対象外: `[c:CodeSpan]`。
28
+
29
+ ```ruby
30
+ # フェンス内も対象外: [m:Fence#no]
31
+ ```
32
+
33
+ ## Instance Methods
34
+
35
+ ### def bar -> nil
36
+
37
+ メソッド本文からの壊れた参照: [m:Foo#missing_from_method]
38
+
39
+ ## Class Methods
40
+
41
+ ### def baz -> nil
42
+
43
+ baz です。
44
+
45
+ ## Constants
46
+
47
+ ### const CONST -> Integer
48
+
49
+ 定数です。
50
+ MD
51
+
52
+ def build_db(dir)
53
+ api = File.join(dir, 'manual', 'api')
54
+ FileUtils.mkdir_p(File.join(api, 'foo'))
55
+ FileUtils.mkdir_p(File.join(dir, 'manual', 'doc', 'spec'))
56
+ File.write(File.join(api, 'foo.md'),
57
+ "---\ntype: library\n---\nライブラリ概要。壊れた参照: [c:AlsoNope]\n")
58
+ File.write(File.join(api, 'foo', 'Foo.md'), FOO_MD)
59
+ File.write(File.join(dir, 'manual', 'doc', 'spec', 'page.md'),
60
+ "# ページ\n\ndoc からの壊れた参照: [m:Foo#doc_missing]。正しい参照: [c:Foo]。\n")
61
+
62
+ prefix = File.join(dir, 'db')
63
+ db = BitClust::MethodDatabase.new(prefix)
64
+ db.init
65
+ db.transaction do
66
+ db.propset('version', '3.4')
67
+ db.propset('encoding', 'utf-8')
68
+ end
69
+ db.transaction do
70
+ db.update_by_markdowntree(api)
71
+ end
72
+ BitClust::MethodDatabase.new(prefix)
73
+ end
74
+
75
+ def test_check_all_reports_only_broken_refs
76
+ Dir.mktmpdir do |dir|
77
+ db = build_db(dir)
78
+ findings = BitClust::LinkChecker.new(db).check_all
79
+ refs = findings.map(&:ref).sort
80
+ assert_equal(
81
+ [
82
+ 'c:AlsoNope',
83
+ 'c:Nope',
84
+ 'd:spec/nope',
85
+ 'lib:nolib',
86
+ 'm:Foo#doc_missing',
87
+ 'm:Foo#missing_from_method',
88
+ 'm:Foo#nope',
89
+ 'm:Nope#x',
90
+ ],
91
+ refs
92
+ )
93
+ end
94
+ end
95
+
96
+ def test_findings_carry_location
97
+ Dir.mktmpdir do |dir|
98
+ db = build_db(dir)
99
+ findings = BitClust::LinkChecker.new(db).check_all
100
+ f = findings.find { |x| x.ref == 'm:Foo#nope' }
101
+ assert_not_nil f
102
+ assert_match(/Foo\.md/, f.location.to_s)
103
+ end
104
+ end
105
+
106
+ def test_exit_style_summary
107
+ Dir.mktmpdir do |dir|
108
+ db = build_db(dir)
109
+ checker = BitClust::LinkChecker.new(db)
110
+ checker.check_all
111
+ assert_equal 8, checker.broken_count
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+ require 'test/unit'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+ require 'stringio'
6
+ require 'bitclust'
7
+ require 'bitclust/methoddatabase'
8
+ require 'bitclust/subcommands/lookup_command'
9
+
10
+ # lookup サブコマンド(bitclust#294)。
11
+ #
12
+ # テストリスト:
13
+ # [x] --method --html が Markdown ソースの DB で HTML を返す
14
+ # (シグネチャ dt に index_id の id 属性と permalink が付く)
15
+ # [x] --method --html が RD ソースの DB でも HTML を返す
16
+ # [x] --method(text)は従来どおりソースをそのまま出力する
17
+ # [x] --class --html は従来どおり動く
18
+ class TestLookupCommand < Test::Unit::TestCase
19
+ FOO_MD = <<~'MD'
20
+ ---
21
+ library: foo
22
+ ---
23
+ # class Foo < Object
24
+
25
+ クラスの説明。
26
+
27
+ ## Instance Methods
28
+
29
+ ### def bar -> nil
30
+
31
+ bar の説明。
32
+ MD
33
+
34
+ FOO_RD = <<~'RD'
35
+ description
36
+
37
+ = class Foo < Object
38
+
39
+ クラスの説明。
40
+
41
+ == Instance Methods
42
+
43
+ --- bar -> nil
44
+
45
+ bar の説明。
46
+ RD
47
+
48
+ def build_markdown_db(dir)
49
+ api = File.join(dir, 'manual', 'api')
50
+ FileUtils.mkdir_p(File.join(api, 'foo'))
51
+ File.write(File.join(api, 'foo.md'), "---\ntype: library\n---\nfoo ライブラリ。\n")
52
+ File.write(File.join(api, 'foo', 'Foo.md'), FOO_MD)
53
+ prefix = File.join(dir, 'db')
54
+ db = BitClust::MethodDatabase.new(prefix)
55
+ db.init
56
+ db.transaction do
57
+ db.propset('version', '3.4')
58
+ db.propset('encoding', 'utf-8')
59
+ end
60
+ db.transaction do
61
+ db.update_by_markdowntree(api)
62
+ end
63
+ prefix
64
+ end
65
+
66
+ def build_rd_db(dir)
67
+ root = File.join(dir, 'refm', 'api', 'src')
68
+ FileUtils.mkdir_p(root)
69
+ File.write(File.join(root, 'LIBRARIES'), "foo\n")
70
+ File.write(File.join(root, 'foo.rd'), FOO_RD)
71
+ prefix = File.join(dir, 'db')
72
+ db = BitClust::MethodDatabase.new(prefix)
73
+ db.init
74
+ db.transaction do
75
+ db.propset('version', '3.4')
76
+ db.propset('encoding', 'utf-8')
77
+ end
78
+ db.transaction do
79
+ db.update_by_stdlibtree(root)
80
+ end
81
+ prefix
82
+ end
83
+
84
+ def run_lookup(prefix, argv)
85
+ cmd = BitClust::Subcommands::LookupCommand.new
86
+ cmd.parse(argv)
87
+ out = StringIO.new
88
+ orig_stdout = $stdout
89
+ $stdout = out
90
+ begin
91
+ cmd.exec([], { prefix: prefix, capi: false })
92
+ ensure
93
+ $stdout = orig_stdout
94
+ end
95
+ out.string
96
+ end
97
+
98
+ def test_method_html_on_markdown_db
99
+ Dir.mktmpdir do |dir|
100
+ prefix = build_markdown_db(dir)
101
+ html = run_lookup(prefix, ['--method', 'Foo#bar', '--html'])
102
+ assert_match(/<dt class="method-heading" id="[^"]+"><code>bar/, html)
103
+ assert_match(/permalink/, html)
104
+ assert_match(/bar の説明。/, html)
105
+ end
106
+ end
107
+
108
+ def test_method_html_on_rd_db
109
+ Dir.mktmpdir do |dir|
110
+ prefix = build_rd_db(dir)
111
+ html = run_lookup(prefix, ['--method', 'Foo#bar', '--html'])
112
+ assert_match(/<dt class="method-heading" id="[^"]+"><code>bar/, html)
113
+ assert_match(/permalink/, html)
114
+ assert_match(/bar の説明。/, html)
115
+ end
116
+ end
117
+
118
+ def test_method_text
119
+ Dir.mktmpdir do |dir|
120
+ prefix = build_markdown_db(dir)
121
+ text = run_lookup(prefix, ['--method', 'Foo#bar'])
122
+ assert_match(/\Atype: instance_method$/, text)
123
+ assert_match(/^### def bar -> nil$/, text)
124
+ end
125
+ end
126
+
127
+ def test_class_html
128
+ Dir.mktmpdir do |dir|
129
+ prefix = build_markdown_db(dir)
130
+ html = run_lookup(prefix, ['--class', 'Foo', '--html'])
131
+ assert_match(%r{<dt>name</dt><dd>Foo</dd>}, html)
132
+ assert_match(/クラスの説明。/, html)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+
7
+ require 'bitclust/markdown_bridge'
8
+
9
+ # MarkdownBridge: md ツリー → 旧形式の rd ツリー(LIBRARIES + .rd)を生成する。
10
+ # 既存の update 機構(LIBRARIES → RRDParser/Preprocessor)をそのまま使って
11
+ # md ツリーから DB を組み立てるためのブリッジ。
12
+ #
13
+ # テストリスト:
14
+ # [x] LIBRARIES を発見結果から再生成(名前順、版ゲート付きライブラリは #@ で包む)
15
+ # [x] ライブラリ .rd = 変換済み本文 + メンバーへの #@include(lib のディレクトリ相対)
16
+ # [x] メンバー rd = 変換済み本文を front matter の since/until で #@ ラップ
17
+ # [x] 断片は拡張子なしで emit、include ターゲットは emit 名へ書き換え
18
+ # [x] dual ファイル(lib + インライン・エンティティ)はゲートラップしない(LIBRARIES 側で処理)
19
+ class TestMarkdownBridge < Test::Unit::TestCase
20
+ FILES = {
21
+ "foo.md" => "---\ntype: library\ncategory: Cat\n---\n概要。\n\n\#@include(frag)\n",
22
+ "foo/A.md" => "---\nlibrary: foo\nsince: \"3.2\"\n---\n# class A < Object\n\nA。\n",
23
+ "foo/B.md" => "---\nlibrary: foo\n---\n# class B < Object\nB。\n",
24
+ "frag.md" => "断片。\n",
25
+ "gated.md" => "---\ntype: library\nuntil: \"3.1\"\n---\ngated 概要。\n",
26
+ "bar/baz.md" => "---\ntype: library\n---\nbaz 概要。\n",
27
+ "bar/C.md" => "---\nlibrary: bar/baz\n---\n# class C < Object\nC。\n",
28
+ "dual.md" => "---\ntype: library\n---\n# class Dual < Object\nDual。\n",
29
+ # 大文字小文字衝突回避で改名されたサブライブラリ(rdoc/rdoc.lib.md 型)。
30
+ # 名前は front matter の name: が正
31
+ "sub/Sub.md" => "---\nlibrary: sub/sub\n---\n# class Sub < Object\nSub。\n",
32
+ "sub/sub.lib.md" => "---\ntype: library\nname: sub/sub\n---\nsub 概要。\n",
33
+ }.freeze
34
+
35
+ def build
36
+ Dir.mktmpdir do |md|
37
+ FILES.each do |path, content|
38
+ full = File.join(md, path)
39
+ FileUtils.mkdir_p(File.dirname(full))
40
+ File.write(full, content)
41
+ end
42
+ Dir.mktmpdir do |out|
43
+ BitClust::MarkdownBridge.build(md, out)
44
+ result = {}
45
+ Dir.glob('**/*', base: out).each do |f|
46
+ full = File.join(out, f)
47
+ result[f] = File.read(full) if File.file?(full)
48
+ end
49
+ return result
50
+ end
51
+ end
52
+ end
53
+
54
+ def build_from(files)
55
+ Dir.mktmpdir do |md|
56
+ files.each do |path, content|
57
+ full = File.join(md, path)
58
+ FileUtils.mkdir_p(File.dirname(full))
59
+ File.write(full, content)
60
+ end
61
+ Dir.mktmpdir do |out|
62
+ BitClust::MarkdownBridge.build(md, out)
63
+ result = {}
64
+ Dir.glob('**/*', base: out).each do |f|
65
+ full = File.join(out, f)
66
+ result[f] = File.read(full) if File.file?(full)
67
+ end
68
+ return result
69
+ end
70
+ end
71
+ end
72
+
73
+ def test_gated_membership_wraps_include_sites
74
+ # 多重所属(ゲート付き library リスト): 各ライブラリの .rd に、その
75
+ # membership のゲートで #@include サイトをラップして再具現化する
76
+ out = build_from(
77
+ "builtin.md" => "---\ntype: library\n---\nbuiltin 概要。\n",
78
+ "thread.md" => "---\ntype: library\n---\nthread 概要。\n",
79
+ "thread/Mutex.md" =>
80
+ "---\n" \
81
+ "library:\n" \
82
+ " - builtin\n" \
83
+ "\#@until 1.9.1\n" \
84
+ " - thread\n" \
85
+ "\#@end\n" \
86
+ "---\n" \
87
+ "# class Mutex < Object\nM。\n"
88
+ )
89
+ assert_equal "builtin 概要。\n\n\#@include(thread/Mutex)\n", out["builtin.rd"]
90
+ assert_equal "thread 概要。\n\n\#@until 1.9.1\n\#@include(thread/Mutex)\n\#@end\n",
91
+ out["thread.rd"]
92
+ end
93
+
94
+ def test_libraries_manifest
95
+ out = build
96
+ assert_equal "bar/baz\ndual\nfoo\n\#@until 3.1\ngated\n\#@end\nsub/sub\n", out["LIBRARIES"]
97
+ end
98
+
99
+ def test_renamed_library_file_emits_by_front_matter_name
100
+ # sub/sub.lib.md(大文字小文字衝突回避の改名)は name: のライブラリ名で
101
+ # <name>.rd に emit され、メンバー include も付く
102
+ out = build
103
+ assert_equal "sub 概要。\n\n\#@include(Sub)\n", out["sub/sub.rd"]
104
+ assert_nil out["sub/sub.lib"]
105
+ end
106
+
107
+ def test_source_map_maps_emitted_to_md
108
+ # source_location の md パスへの再マップ用(emit 相対 → md 相対)
109
+ Dir.mktmpdir do |md|
110
+ FILES.each do |path, content|
111
+ full = File.join(md, path)
112
+ FileUtils.mkdir_p(File.dirname(full))
113
+ File.write(full, content)
114
+ end
115
+ Dir.mktmpdir do |out|
116
+ bridge = BitClust::MarkdownBridge.build(md, out)
117
+ assert_equal "foo.md", bridge.source_map["foo.rd"]
118
+ assert_equal "foo/A.md", bridge.source_map["foo/A"]
119
+ assert_equal "frag.md", bridge.source_map["frag"]
120
+ return
121
+ end
122
+ end
123
+ end
124
+
125
+ def test_build_doc_returns_source_map
126
+ Dir.mktmpdir do |md|
127
+ File.write(File.join(md, "index.md"), "# はじめに\n\n本文。\n")
128
+ Dir.mktmpdir do |out|
129
+ map = BitClust::MarkdownBridge.build_doc(md, File.join(out, "doc"))
130
+ assert_equal({ "index.rd" => "index.md" }, map)
131
+ return
132
+ end
133
+ end
134
+ end
135
+
136
+ def test_library_rd_with_member_includes
137
+ out = build
138
+ assert_equal "category Cat\n\n概要。\n\n\#@include(frag)\n\n" \
139
+ "\#@include(foo/A)\n\#@include(foo/B)\n", out["foo.rd"]
140
+ assert_equal "baz 概要。\n\n\#@include(C)\n", out["bar/baz.rd"]
141
+ end
142
+
143
+ def test_member_rd_with_gate_wrapper
144
+ out = build
145
+ assert_equal "\#@since 3.2\n= class A < Object\n\nA。\n\#@end\n", out["foo/A"]
146
+ assert_equal "= class B < Object\nB。\n", out["foo/B"]
147
+ end
148
+
149
+ def test_fragment_is_emitted_without_extension
150
+ out = build
151
+ assert_equal "断片。\n", out["frag"]
152
+ end
153
+
154
+ def test_dual_file_keeps_inline_entities_without_wrapper
155
+ out = build
156
+ assert_equal "= class Dual < Object\nDual。\n", out["dual.rd"]
157
+ end
158
+
159
+ def test_build_doc_emits_local_fragments_without_extension
160
+ # spec/regexp.rd → #@include(regexp19): doc 内ローカル断片は拡張子なしで emit
161
+ # (copy_doc は **/*.rd だけをページとして読むため、断片に .rd を付けると
162
+ # ページとして二重取り込みされる上、include の解決も切れる)
163
+ Dir.mktmpdir do |md|
164
+ FileUtils.mkdir_p(File.join(md, "spec"))
165
+ File.write(File.join(md, "spec/regexp.md"), "# 正規表現\n\n\#@include(regexp19)\n")
166
+ File.write(File.join(md, "spec/regexp19.md"), "断片。\n")
167
+ Dir.mktmpdir do |out|
168
+ BitClust::MarkdownBridge.build_doc(md, File.join(out, "doc"))
169
+ assert_equal "= 正規表現\n\n\#@include(regexp19)\n",
170
+ File.read(File.join(out, "doc/spec/regexp.rd"))
171
+ assert_equal "断片。\n", File.read(File.join(out, "doc/spec/regexp19"))
172
+ return
173
+ end
174
+ end
175
+ end
176
+
177
+ def test_build_doc_converts_and_rewrites_cross_tree_includes
178
+ # manual/doc の md → ブリッジの doc/*.rd。クロスツリー include は
179
+ # 旧レイアウト(../api/src/)へ戻す
180
+ Dir.mktmpdir do |md|
181
+ FileUtils.mkdir_p(File.join(md, "spec"))
182
+ File.write(File.join(md, "pack_template.md"),
183
+ "# pack テンプレート\n\n\#@include(../api/_builtin/pack-template)\n")
184
+ File.write(File.join(md, "spec/intro.md"), "# はじめに\n\n本文。\n")
185
+ Dir.mktmpdir do |out|
186
+ BitClust::MarkdownBridge.build_doc(md, File.join(out, "doc"))
187
+ assert_equal "= pack テンプレート\n\n\#@include(../api/src/_builtin/pack-template)\n",
188
+ File.read(File.join(out, "doc/pack_template.rd"))
189
+ assert_equal "= はじめに\n\n本文。\n", File.read(File.join(out, "doc/spec/intro.rd"))
190
+ return
191
+ end
192
+ end
193
+ end
194
+
195
+ def test_reopen_members_are_included_last
196
+ # json.rd/rake.rd: reopen が dynamic include する module は先に定義されている
197
+ # 必要がある(RRDParser の検証)。reopen/redefine だけのファイルは後ろに並べる
198
+ files = {
199
+ "j.md" => "---\ntype: library\n---\nj 概要。\n",
200
+ "j/Array.md" => "---\nlibrary: j\n---\n# reopen Array\n",
201
+ "j/Gen.md" => "---\nlibrary: j\n---\n# module Gen\n",
202
+ }
203
+ Dir.mktmpdir do |md|
204
+ files.each do |path, content|
205
+ full = File.join(md, path)
206
+ FileUtils.mkdir_p(File.dirname(full))
207
+ File.write(full, content)
208
+ end
209
+ Dir.mktmpdir do |out|
210
+ BitClust::MarkdownBridge.build(md, out)
211
+ assert_equal "j 概要。\n\n\#@include(j/Gen)\n\#@include(j/Array)\n",
212
+ File.read(File.join(out, "j.rd"))
213
+ return
214
+ end
215
+ end
216
+ end
217
+ end