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,538 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+ require 'stringio'
5
+ require 'fileutils'
6
+ require 'bitclust/mdparser'
7
+ require 'bitclust/rrdparser'
8
+ require 'bitclust/methoddatabase'
9
+
10
+ # MDParser: Markdown ソースを直接パースして DB エントリを作る(フェーズ3 M3)。
11
+ # 構造(クラス・エントリ・関係)は同等 rd の RRDParser と一致し、
12
+ # source には md 断片がそのまま入る。
13
+ class TestMDParser < Test::Unit::TestCase
14
+ PARAMS = { "version" => "3.4" }
15
+
16
+ def parse_md(md, libname = "_builtin")
17
+ db = BitClust::MethodDatabase.dummy(PARAMS)
18
+ lib = BitClust::MDParser.new(db).parse(StringIO.new(md), libname, PARAMS)
19
+ [db, lib]
20
+ end
21
+
22
+ def parse_rd(rd, libname = "_builtin")
23
+ db = BitClust::MethodDatabase.dummy(PARAMS)
24
+ lib = BitClust::RRDParser.new(db).parse(StringIO.new(rd), libname, PARAMS)
25
+ [db, lib]
26
+ end
27
+
28
+ def test_class_with_include_and_method
29
+ md = <<~MD
30
+ ---
31
+ library: _builtin
32
+ include:
33
+ - Enumerable
34
+ ---
35
+ # class Array < Object
36
+
37
+ 配列クラス。
38
+
39
+ ## Instance Methods
40
+
41
+ ### def index(val) -> Integer
42
+
43
+ 説明。
44
+ MD
45
+ rd = <<~RD
46
+ = class Array < Object
47
+ include Enumerable
48
+
49
+ 配列クラス。
50
+
51
+ == Instance Methods
52
+
53
+ --- index(val) -> Integer
54
+
55
+ 説明。
56
+ RD
57
+ _, mlib = parse_md(md)
58
+ _, rlib = parse_rd(rd)
59
+
60
+ mc = mlib.classes.first
61
+ rc = rlib.classes.first
62
+ assert_equal rc.name, mc.name
63
+ assert_equal rc.type, mc.type
64
+ assert_equal rc.superclass&.name, mc.superclass&.name
65
+ assert_equal rc.included.map(&:name), mc.included.map(&:name)
66
+ assert_equal rc.entries.map(&:names), mc.entries.map(&:names)
67
+ assert_equal rc.entries.first.type, mc.entries.first.type
68
+
69
+ # source は md のまま
70
+ assert_equal "配列クラス。", mc.source
71
+ assert_equal "### def index(val) -> Integer\n\n説明。\n", mc.entries.first.source
72
+ end
73
+
74
+ def test_module_and_class_methods_section
75
+ md = <<~MD
76
+ ---
77
+ library: _builtin
78
+ ---
79
+ # module Comparable
80
+
81
+ 比較演算モジュール。
82
+
83
+ ## Class Methods
84
+
85
+ ### def new -> Comparable
86
+
87
+ 生成。
88
+ MD
89
+ _, lib = parse_md(md)
90
+ c = lib.classes.first
91
+ assert_equal "Comparable", c.name
92
+ assert_equal :module, c.type
93
+ assert_equal :singleton_method, c.entries.first.type
94
+ end
95
+
96
+ def test_constants_section_with_const_keyword
97
+ md = <<~MD
98
+ ---
99
+ library: _builtin
100
+ ---
101
+ # class Float < Numeric
102
+
103
+ 浮動小数点数。
104
+
105
+ ## Constants
106
+
107
+ ### const DIG -> Integer
108
+
109
+ 桁数。
110
+ MD
111
+ _, lib = parse_md(md)
112
+ e = lib.classes.first.entries.first
113
+ assert_equal :constant, e.type
114
+ assert_equal ["DIG"], e.names
115
+ assert_equal "### const DIG -> Integer\n\n桁数。\n", e.source
116
+ end
117
+
118
+ def test_reopen_with_dynamic_include_across_files
119
+ # ライブラリはファイル単位で組み立てる(module 定義とreopen が別ファイル、
120
+ # 同じ libname で順にパースすると同一 Library に合流する)
121
+ module_md = <<~MD
122
+ ---
123
+ library: mylib
124
+ ---
125
+ # module MyModule
126
+
127
+ モジュール。
128
+ MD
129
+ reopen_md = <<~MD
130
+ ---
131
+ library: mylib
132
+ include:
133
+ - MyModule
134
+ ---
135
+ # reopen Object
136
+ MD
137
+ db = BitClust::MethodDatabase.dummy(PARAMS)
138
+ BitClust::MDParser.new(db).parse(StringIO.new(module_md), "mylib", PARAMS)
139
+ BitClust::MDParser.new(db).parse(StringIO.new(reopen_md), "mylib", PARAMS)
140
+ assert_equal ["MyModule"], db.get_class("Object").dynamically_included.map(&:name)
141
+ end
142
+
143
+ def test_gvar_signature
144
+ md = <<~MD
145
+ ---
146
+ library: _builtin
147
+ ---
148
+ # module Kernel
149
+
150
+ カーネル。
151
+
152
+ ## Special Variables
153
+
154
+ ### gvar $stdin -> IO
155
+
156
+ 標準入力。
157
+ MD
158
+ _, lib = parse_md(md)
159
+ e = lib.classes.first.entries.first
160
+ assert_equal :special_variable, e.type
161
+ assert_equal "### gvar $stdin -> IO\n\n標準入力。\n", e.source
162
+ end
163
+
164
+ def test_relations_require_single_entity
165
+ # 案B: 関係(include 等)を持つファイルは単一エンティティでなければならない
166
+ md = <<~MD
167
+ ---
168
+ library: _builtin
169
+ include:
170
+ - Enumerable
171
+ ---
172
+ # class Foo < Object
173
+
174
+ ふー。
175
+
176
+ # class Bar < Object
177
+
178
+ ばー。
179
+ MD
180
+ assert_raise(BitClust::ParseError) { parse_md(md) }
181
+ end
182
+
183
+ def test_unsupported_signature_forms_are_rejected
184
+ # RBS 形式(MARKUP_SPEC §3.2 で構想)と self. プレフィクスは描画側
185
+ # (MethodSignature.parse)が受理できないため、パース時に拒否する
186
+ base = <<~MD
187
+ ---
188
+ library: _builtin
189
+ ---
190
+ # class Foo < Object
191
+
192
+ テスト。
193
+
194
+ ## Instance Methods
195
+
196
+ %s
197
+
198
+ 説明。
199
+ MD
200
+ ['### def each: () { (untyped) -> void } -> self',
201
+ '### def self.new(size = 0) -> Foo'].each do |sig|
202
+ assert_raise(BitClust::ParseError, sig) { parse_md(base % sig) }
203
+ end
204
+ end
205
+
206
+ def test_library_file_with_category
207
+ md = <<~MD
208
+ ---
209
+ type: library
210
+ category: FileFormat
211
+ ---
212
+ CSV を扱うライブラリです。
213
+ MD
214
+ _, lib = parse_md(md, "csv")
215
+ assert_equal "FileFormat", lib.category
216
+ assert_equal "CSV を扱うライブラリです。", lib.source
217
+ end
218
+
219
+ def test_since_directive_in_body
220
+ md = <<~MD
221
+ ---
222
+ library: _builtin
223
+ ---
224
+ # class Array < Object
225
+
226
+ 配列。
227
+
228
+ ## Instance Methods
229
+
230
+ \#@since 3.4
231
+ ### def newmethod -> nil
232
+
233
+ 新しい。
234
+ \#@end
235
+ MD
236
+ _, lib = parse_md(md)
237
+ assert_equal [["newmethod"]], lib.classes.first.entries.map(&:names)
238
+ end
239
+
240
+ def test_update_by_markdowntree
241
+ # MarkdownTree 駆動の組み立て: lib ファイル → メンバー(reopen 後置)、
242
+ # 版ゲート外のライブラリ/メンバーはスキップ
243
+ require 'tmpdir'
244
+ Dir.mktmpdir do |root|
245
+ write = ->(rel, s) {
246
+ path = File.join(root, rel)
247
+ FileUtils.mkdir_p(File.dirname(path))
248
+ File.write(path, s)
249
+ }
250
+ write.call('mylib.md', <<~MD)
251
+ ---
252
+ type: library
253
+ category: Text
254
+ ---
255
+ mylib の説明。
256
+ MD
257
+ write.call('mylib/MyModule.md', <<~MD)
258
+ ---
259
+ library: mylib
260
+ ---
261
+ # module MyModule
262
+
263
+ モジュール。
264
+ MD
265
+ # 辞書順で module より先に来る reopen ファイル(後置されることを確認)
266
+ write.call('mylib/Kernel.md', <<~MD)
267
+ ---
268
+ library: mylib
269
+ include:
270
+ - MyModule
271
+ ---
272
+ # reopen Object
273
+ MD
274
+ write.call('mylib/Gone.md', <<~MD)
275
+ ---
276
+ library: mylib
277
+ until: "3.0"
278
+ ---
279
+ # class Gone < Object
280
+
281
+ 3.0 で消えた。
282
+ MD
283
+ write.call('oldlib.md', <<~MD)
284
+ ---
285
+ type: library
286
+ until: "2.0"
287
+ ---
288
+ 古いライブラリ。
289
+ MD
290
+
291
+ db = BitClust::MethodDatabase.dummy(PARAMS)
292
+ db.update_by_markdowntree(root)
293
+
294
+ assert_equal ["mylib"], db.libraries.map(&:name)
295
+ lib = db.fetch_library("mylib")
296
+ assert_equal "Text", lib.category
297
+ assert_equal "mylib の説明。", lib.source
298
+ # reopen はメソッド定義が無い限り lib.classes に載らない(rd と同じ)
299
+ assert_equal %w[MyModule], lib.classes.map(&:name).sort
300
+ assert_equal ["MyModule"], db.get_class("Object").dynamically_included.map(&:name)
301
+ # source_location は md の実パス
302
+ assert_equal "#{root}/mylib.md", lib.source_location.file
303
+ assert_equal "#{root}/mylib/MyModule.md", db.get_class("MyModule").source_location.file
304
+ end
305
+ end
306
+
307
+ def test_update_by_markdowntree_gated_memberships
308
+ # 多重所属(ゲート付き library リスト): 版によって所属ライブラリが変わる。
309
+ # 3.4 では builtin のみ、3.0 では builtin と oldlib の両方に属する
310
+ require 'tmpdir'
311
+ Dir.mktmpdir do |root|
312
+ write = ->(rel, s) {
313
+ path = File.join(root, rel)
314
+ FileUtils.mkdir_p(File.dirname(path))
315
+ File.write(path, s)
316
+ }
317
+ write.call('builtin.md', "---\ntype: library\n---\nbuiltin。\n")
318
+ write.call('oldlib.md', "---\ntype: library\n---\noldlib。\n")
319
+ write.call('oldlib/Shared.md', <<~'MD')
320
+ ---
321
+ library:
322
+ - builtin
323
+ #@until 3.2
324
+ - oldlib
325
+ #@end
326
+ ---
327
+ # class Shared < Object
328
+
329
+ 共有クラス。
330
+ MD
331
+
332
+ db34 = BitClust::MethodDatabase.dummy("version" => "3.4")
333
+ db34.update_by_markdowntree(root)
334
+ assert_equal %w[Shared], db34.fetch_library("builtin").classes.map(&:name)
335
+ assert_equal [], db34.fetch_library("oldlib").classes.map(&:name)
336
+
337
+ db30 = BitClust::MethodDatabase.dummy("version" => "3.0")
338
+ db30.update_by_markdowntree(root)
339
+ assert_equal %w[Shared], db30.fetch_library("builtin").classes.map(&:name)
340
+ assert_equal %w[Shared], db30.fetch_library("oldlib").classes.map(&:name)
341
+ end
342
+ end
343
+
344
+ def test_copy_doc_md_keeps_relative_location
345
+ # Windows CI 対応: doc エントリの source_location は md_root と同じ形
346
+ # (相対で渡せば相対 manual/doc/...)で格納する。絶対パス化すると
347
+ # Windows のドライブレター(D:)のコロンで Location の
348
+ # デシリアライズ(file:line 分割)が壊れる
349
+ require 'tmpdir'
350
+ Dir.mktmpdir do |root|
351
+ write = ->(rel, s) {
352
+ path = File.join(root, rel)
353
+ FileUtils.mkdir_p(File.dirname(path))
354
+ File.write(path, s)
355
+ }
356
+ write.call('manual/api/mylib.md', <<~MD)
357
+ ---
358
+ type: library
359
+ category: Text
360
+ ---
361
+ mylib の説明。
362
+ MD
363
+ write.call('manual/doc/help.md', "= ヘルプ\n\n本文。\n")
364
+ write.call('manual/doc/parts.md', "\#@include(included)\n")
365
+ write.call('manual/doc/included.md', "= 部品\n\n部品本文。\n")
366
+
367
+ db = BitClust::MethodDatabase.new(File.join(root, 'db'))
368
+ db.init
369
+ db.transaction {
370
+ db.propset 'version', '3.4'
371
+ db.propset 'encoding', 'utf-8'
372
+ }
373
+ Dir.chdir(root) do
374
+ db.transaction {
375
+ db.update_by_markdowntree('manual/api')
376
+ }
377
+ end
378
+ doc_ids = db.docs.map(&:name).sort
379
+ assert_equal %w[help parts], doc_ids # include 参照先はページにしない
380
+ assert_equal 'manual/doc/help.md',
381
+ db.docs.find { |d| d.name == 'help' }.source_location.file.to_s
382
+ end
383
+ end
384
+
385
+ def test_location_deserialize_drops_line
386
+ # source_location の永続化はファイルのみ(行番号は生成物の diff を安定させる
387
+ # ため落とす。master b1d81d7 のポリシー)。旧形式の file:line も
388
+ # 後方互換で file だけを読む。Windows で壊れないよう doc の格納パスは
389
+ # 相対に保つ(copy_doc_md 側の慣例)
390
+ db = BitClust::MethodDatabase.dummy(PARAMS)
391
+ e = BitClust::DocEntry.new(db, 'x')
392
+ e.__send__(:_set_properties,
393
+ { 'source_location' => 'manual/doc/help.md:12' })
394
+ loc = e.instance_variable_get(:@source_location)
395
+ assert_equal 'manual/doc/help.md', loc.file
396
+ assert_nil loc.line
397
+ end
398
+
399
+ def test_function_parser
400
+ # capi: キーワード無しの ### 全行がシグネチャ。source は本文のみ(md のまま)
401
+ require 'bitclust/functiondatabase'
402
+ md = <<~MD
403
+ ### VALUE rb_ary_new()
404
+
405
+ 空の Ruby の配列を作成し返します。
406
+
407
+ ```
408
+ VALUE ary = rb_ary_new();
409
+ ```
410
+
411
+ ### static int rb_ary_size(VALUE ary)
412
+
413
+ 内部関数です。
414
+ MD
415
+ db = BitClust::FunctionDatabase.dummy(PARAMS)
416
+ io = StringIO.new(md)
417
+ def io.path = "array.c.md"
418
+ BitClust::MDFunctionParser.new(db).parse(io, "array.c", PARAMS)
419
+ funcs = db.functions.sort_by(&:name)
420
+ assert_equal %w[rb_ary_new rb_ary_size], funcs.map(&:name)
421
+ f = funcs.first
422
+ assert_equal "array.c", f.filename
423
+ assert_equal "VALUE", f.type
424
+ # body はヘッダ直後の空行込み(レガシー FunctionReferenceParser と同じ)
425
+ assert_equal "\n空の Ruby の配列を作成し返します。\n\n```\nVALUE ary = rb_ary_new();\n```\n\n",
426
+ f.source
427
+ assert funcs.last.private
428
+ end
429
+
430
+ def test_multi_entity_bundle_without_relations
431
+ md = <<~MD
432
+ ---
433
+ library: _builtin
434
+ ---
435
+ # class Errno::EPERM < SystemCallError
436
+
437
+ EPERM。
438
+
439
+ # class Errno::ENOENT < SystemCallError
440
+
441
+ ENOENT。
442
+ MD
443
+ _, lib = parse_md(md)
444
+ assert_equal %w[Errno::EPERM Errno::ENOENT], lib.classes.map(&:name)
445
+ end
446
+ def test_method_attribute_list_sets_kind
447
+ md = <<~MD
448
+ ---
449
+ library: _builtin
450
+ ---
451
+ # class Hoge
452
+
453
+ クラスの説明。
454
+
455
+ ## Instance Methods
456
+
457
+ ### def fuga -> nil
458
+ {: nomethod}
459
+
460
+ 説明のためのエントリです。
461
+
462
+ ### def piyo -> nil
463
+ {: undef}
464
+ MD
465
+ db, = parse_md(md)
466
+ assert_equal(:nomethod, db.get_method(BitClust::MethodSpec.parse('Hoge#fuga')).kind)
467
+ assert_equal(:undefined, db.get_method(BitClust::MethodSpec.parse('Hoge#piyo')).kind)
468
+ end
469
+
470
+ def test_method_attribute_on_every_alias_signature
471
+ md = <<~MD
472
+ ---
473
+ library: _builtin
474
+ ---
475
+ # class Hoge
476
+
477
+ クラスの説明。
478
+
479
+ ## Instance Methods
480
+
481
+ ### def fuga -> nil
482
+ {: nomethod}
483
+ ### def fuga2 -> nil
484
+ {: nomethod}
485
+
486
+ 説明のためのエントリです。
487
+ MD
488
+ db, = parse_md(md)
489
+ fuga = db.get_method(BitClust::MethodSpec.parse('Hoge#fuga'))
490
+ assert_equal(:nomethod, fuga.kind)
491
+ assert_equal(['fuga', 'fuga2'], fuga.names)
492
+ end
493
+
494
+ def test_method_attribute_since_is_recorded
495
+ md = <<~MD
496
+ ---
497
+ library: _builtin
498
+ ---
499
+ # class Hoge
500
+
501
+ クラスの説明。
502
+
503
+ ## Instance Methods
504
+
505
+ ### def fuga -> nil
506
+ {: since="3.1"}
507
+
508
+ 説明のためのエントリです。
509
+ MD
510
+ db, = parse_md(md)
511
+ entry = db.get_method(BitClust::MethodSpec.parse('Hoge#fuga'))
512
+ assert_equal('3.1', entry.since_of('fuga'))
513
+ end
514
+
515
+ def test_method_attribute_since_differs_per_alias_signature
516
+ md = <<~MD
517
+ ---
518
+ library: _builtin
519
+ ---
520
+ # class Hoge
521
+
522
+ クラスの説明。
523
+
524
+ ## Instance Methods
525
+
526
+ ### def fuga -> nil
527
+ {: since="2.0.0"}
528
+ ### def fuga2 -> nil
529
+ {: since="3.0"}
530
+
531
+ 説明のためのエントリです。
532
+ MD
533
+ db, = parse_md(md)
534
+ entry = db.get_method(BitClust::MethodSpec.parse('Hoge#fuga'))
535
+ assert_equal('2.0.0', entry.since_of('fuga'))
536
+ assert_equal('3.0', entry.since_of('fuga2'))
537
+ end
538
+ end
@@ -0,0 +1,166 @@
1
+ require 'test/unit'
2
+ require 'bitclust'
3
+ require 'bitclust/screen'
4
+
5
+ # Covers data/bitclust/template.offline, used by `bitclust statichtml` (the
6
+ # production docs.ruby-lang.org build) and `bitclust chm`.
7
+ #
8
+ # NOTE: only one templatedir is exercised here on purpose. Screen#run_template
9
+ # caches the compiled ERB template methods on the *class* (MethodScreen),
10
+ # keyed only by method name (e.g. "method_template"), regardless of which
11
+ # @template_repository the instance was built with. So constructing
12
+ # MethodScreen from two different templatedirs (e.g. this one and
13
+ # data/bitclust/template) within the same process is unreliable: whichever
14
+ # templatedir's ERB gets compiled first "wins" for every MethodScreen built
15
+ # afterwards, in every test file, for the rest of the run. This is a
16
+ # pre-existing quirk of Screen, not something introduced here.
17
+ #
18
+ # data/bitclust/template, data/bitclust/template.lillia and
19
+ # data/bitclust/template.epub were hand-verified (each in its own process,
20
+ # not committed as an automated test here) to build @title the same way as
21
+ # template.offline below, just keeping their pre-existing
22
+ # "#{entry.type_label} #{entry.label}" prefix -- i.e. the fix is
23
+ # entry.label -> entry.title_labels.join(', ') in all four `method`
24
+ # templates, so the CHM (template) and EPUB (template.epub) screens get the
25
+ # same alias listing.
26
+ class TestMethodScreenTitle < Test::Unit::TestCase
27
+ SRC = <<'HERE'
28
+ = class Enumerable2 < Object
29
+ enumerable class
30
+ == Instance Methods
31
+ --- collect -> Array
32
+ --- map -> Array
33
+
34
+ 説明のためのエントリ
35
+
36
+ --- select -> Array
37
+
38
+ 説明2
39
+ HERE
40
+
41
+ def setup
42
+ @lib, @db = BitClust::RRDParser.parse(SRC, 'testlib')
43
+ datadir = File.expand_path('../data/bitclust', __dir__)
44
+ @manager = BitClust::ScreenManager.new(
45
+ :templatedir => "#{datadir}/template.offline",
46
+ :catalogdir => "#{datadir}/catalog",
47
+ :encoding => 'utf-8',
48
+ :default_encoding => 'utf-8',
49
+ :base_url => '',
50
+ :target_version => '3.4'
51
+ )
52
+ end
53
+
54
+ def method_html(spec)
55
+ entry = @db.get_method(BitClust::MethodSpec.parse(spec))
56
+ @manager.method_screen([entry], :database => @db).body
57
+ end
58
+
59
+ def title_of(html)
60
+ html[/<title>(.*?) \(Ruby/, 1]
61
+ end
62
+
63
+ # template.offline's <title> has never included the "instance method"
64
+ # type_label prefix (only entry.label); the alias fix must keep that
65
+ # pre-existing convention and just expand the single name to every alias.
66
+ def test_title_lists_every_alias
67
+ assert_equal('Enumerable2#collect, Enumerable2#map',
68
+ title_of(method_html('Enumerable2#collect')))
69
+ end
70
+
71
+ # Both aliases are the very same entry, so they share one page/title
72
+ # regardless of which alias URL was requested.
73
+ def test_title_is_the_same_regardless_of_which_alias_was_requested
74
+ assert_equal(method_html('Enumerable2#collect'), method_html('Enumerable2#map'))
75
+ end
76
+
77
+ # A method without any alias renders exactly as before: only its own name.
78
+ def test_title_is_unchanged_for_a_method_without_aliases
79
+ html = method_html('Enumerable2#select')
80
+ assert_equal('Enumerable2#select', title_of(html))
81
+ end
82
+
83
+ # Only the <title> tag changes; the on-page h1 headline keeps showing only
84
+ # the entry's own (alphabetically-first) name, as before.
85
+ def test_h1_headline_is_not_affected
86
+ html = method_html('Enumerable2#collect')
87
+ assert_match(%r{<h1>instance method Enumerable2\#collect</h1>}, html)
88
+ assert_not_match(/<h1>[^<]*Enumerable2#map/, html)
89
+ end
90
+ end
91
+
92
+ # bitclust#132 P3: since/until バージョンバッジが method ページ(template.offline、
93
+ # statichtml/chm が使う経路)の <dt> に実際に描画されることのエンドツーエンド確認
94
+ class TestMethodScreenVersionBadges < Test::Unit::TestCase
95
+ SRC = <<'HERE'
96
+ = class Enumerable2 < Object
97
+ enumerable class
98
+ == Instance Methods
99
+ --- select -> Array
100
+
101
+ 説明
102
+ HERE
103
+
104
+ def setup
105
+ @lib, @db = BitClust::RRDParser.parse(SRC, 'testlib')
106
+ datadir = File.expand_path('../data/bitclust', __dir__)
107
+ @manager = BitClust::ScreenManager.new(
108
+ :templatedir => "#{datadir}/template.offline",
109
+ :catalogdir => "#{datadir}/catalog",
110
+ :encoding => 'utf-8',
111
+ :default_encoding => 'utf-8',
112
+ :base_url => '',
113
+ :target_version => '3.4'
114
+ )
115
+ end
116
+
117
+ def test_since_badge_is_rendered_after_fill_since
118
+ entry = @db.get_method(BitClust::MethodSpec.parse('Enumerable2#select'))
119
+ entry.fill_since('select', '3.2')
120
+ html = @manager.method_screen([entry], :database => @db).body
121
+ assert_include(html, '<span class="method-since-badge">Ruby 3.2 から</span>')
122
+ end
123
+ end
124
+
125
+ # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を
126
+ # 独自の「.#」から「?.」に変える(表示のみ、識別子は変えない)。この
127
+ # メソッドページの見出し・<title> がまさに issue に挙がっている
128
+ # 「module function Kernel.#open」の例そのもの
129
+ class TestMethodScreenModuleFunctionDisplay < Test::Unit::TestCase
130
+ SRC = <<'HERE'
131
+ = module Kernel
132
+ description
133
+ == Module Functions
134
+ --- mf
135
+
136
+ 説明
137
+ HERE
138
+
139
+ def render(version)
140
+ _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version})
141
+ entry = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf'))
142
+ datadir = File.expand_path('../data/bitclust', __dir__)
143
+ manager = BitClust::ScreenManager.new(
144
+ :templatedir => "#{datadir}/template.offline",
145
+ :catalogdir => "#{datadir}/catalog",
146
+ :encoding => 'utf-8',
147
+ :default_encoding => 'utf-8',
148
+ :base_url => '',
149
+ :target_version => version
150
+ )
151
+ manager.method_screen([entry], :database => db).body
152
+ end
153
+
154
+ def test_headline_and_title_show_dot_hash_before_4_0
155
+ html = render('3.4')
156
+ assert_match(%r{<h1>module function Kernel\.#mf</h1>}, html)
157
+ assert_include(html, '<title>Kernel.#mf')
158
+ end
159
+
160
+ def test_headline_and_title_switch_to_question_dot_at_4_0
161
+ html = render('4.0')
162
+ assert_match(%r{<h1>module function Kernel\?\.mf</h1>}, html)
163
+ assert_include(html, '<title>Kernel?.mf')
164
+ assert_not_include(html, 'Kernel.#mf')
165
+ end
166
+ end