bitclust-core 1.3.0 → 1.6.1

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 +128 -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 +573 -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,328 @@
1
+ require 'test/unit'
2
+ require 'bitclust'
3
+ require 'bitclust/methoddatabase'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+
7
+ class TestMethodEntryTitleLabels < Test::Unit::TestCase
8
+ def test_title_labels_matches_label_when_no_alias
9
+ lib, db = BitClust::RRDParser.parse(<<HERE, 'dummy')
10
+ = class Enumerable2
11
+ == Instance Methods
12
+ --- select -> Array
13
+
14
+ 説明のためのエントリ
15
+ HERE
16
+ _ = lib
17
+ m = db.get_method(BitClust::MethodSpec.parse('Enumerable2#select'))
18
+ assert_equal(['Enumerable2#select'], m.title_labels)
19
+ assert_equal(m.label, m.title_labels.join(', '))
20
+ end
21
+
22
+ def test_title_labels_lists_every_alias
23
+ lib, db = BitClust::RRDParser.parse(<<HERE, 'dummy')
24
+ = class Enumerable2
25
+ == Instance Methods
26
+ --- collect -> Array
27
+ --- map -> Array
28
+
29
+ 説明のためのエントリ
30
+ HERE
31
+ _ = lib
32
+ m = db.get_method(BitClust::MethodSpec.parse('Enumerable2#collect'))
33
+ assert_equal(['Enumerable2#collect', 'Enumerable2#map'], m.title_labels)
34
+ end
35
+
36
+ # #label omits the class prefix for special variables (e.g. "$;", not
37
+ # "Kernel$;"); #title_labels must keep that convention for every alias,
38
+ # unlike #labels which always includes the prefix.
39
+ def test_title_labels_omits_class_prefix_for_special_variables
40
+ lib, db = BitClust::RRDParser.parse(<<HERE, 'dummy')
41
+ = module Kernel
42
+ description
43
+ == Special Variables
44
+ --- $;
45
+ --- $FIELD_SEPARATOR
46
+
47
+ 区切り文字。
48
+ HERE
49
+ _ = lib
50
+ m = db.get_method(BitClust::MethodSpec.parse('Kernel$;'))
51
+ assert_equal('$;', m.label)
52
+ assert_equal(['$;', '$FIELD_SEPARATOR'], m.title_labels)
53
+ assert_equal(['Kernel$;', 'Kernel$FIELD_SEPARATOR'], m.labels)
54
+ end
55
+
56
+ def test_title_labels_matches_label_for_single_special_variable
57
+ lib, db = BitClust::RRDParser.parse(<<HERE, 'dummy')
58
+ = module Kernel
59
+ description
60
+ == Special Variables
61
+ --- $stdout -> IO
62
+
63
+ 標準出力。
64
+ HERE
65
+ _ = lib
66
+ m = db.get_method(BitClust::MethodSpec.parse('Kernel$stdout'))
67
+ assert_equal(['$stdout'], m.title_labels)
68
+ assert_equal(m.label, m.title_labels.join(', '))
69
+ end
70
+ end
71
+
72
+ # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を
73
+ # 独自の「.#」から「?.」に変える(表示のみ。識別子は変えない)。
74
+ #
75
+ # label/short_label/labels/title_labels 自身は refsdatabase.rb の
76
+ # [[a:...]] アンカー解決キー(labels)や `bitclust methods --diff` の
77
+ # expand_mf マッチング(labels)が literal ".#" を前提にしているため、
78
+ # 一切変更しない。表示用には display_label/display_short_label/
79
+ # display_title_labels/display_typemark を新設し、テンプレート側の
80
+ # 実際の表示箇所だけがそちらを呼ぶようにする。
81
+ class TestMethodEntryDisplayTypemark < Test::Unit::TestCase
82
+ SRC = <<HERE
83
+ = module Kernel
84
+ description
85
+ == Module Functions
86
+ --- mf
87
+ --- mf2
88
+
89
+ 説明
90
+
91
+ == Instance Methods
92
+ --- im
93
+
94
+ 説明
95
+
96
+ == Special Variables
97
+ --- $stdout -> IO
98
+
99
+ 標準出力。
100
+ HERE
101
+
102
+ def build(version)
103
+ _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version})
104
+ db
105
+ end
106
+
107
+ def test_module_function_display_stays_dot_hash_before_4_0
108
+ db = build('3.4')
109
+ m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf'))
110
+ assert_equal('.#', m.display_typemark)
111
+ assert_equal('Kernel.#mf', m.display_label)
112
+ assert_equal('.#mf', m.display_short_label)
113
+ assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.display_title_labels)
114
+ end
115
+
116
+ def test_module_function_display_switches_to_question_dot_at_4_0
117
+ db = build('4.0')
118
+ m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf'))
119
+ assert_equal('?.', m.display_typemark)
120
+ assert_equal('Kernel?.mf', m.display_label)
121
+ assert_equal('?.mf', m.display_short_label)
122
+ assert_equal(['Kernel?.mf', 'Kernel?.mf2'], m.display_title_labels)
123
+ end
124
+
125
+ # 識別子として使われる label/labels/short_label/title_labels は、
126
+ # 表示版とは無関係に version が 4.0 以降でも従来どおり ".#" のまま
127
+ def test_identity_methods_are_unaffected_by_version
128
+ db = build('4.0')
129
+ m = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf'))
130
+ assert_equal('.#', m.typemark)
131
+ assert_equal('Kernel.#mf', m.label)
132
+ assert_equal('.#mf', m.short_label)
133
+ assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.title_labels)
134
+ assert_equal(['Kernel.#mf', 'Kernel.#mf2'], m.labels)
135
+ end
136
+
137
+ def test_instance_method_and_special_variable_display_are_unaffected_by_version
138
+ db = build('4.0')
139
+ im = db.get_method(BitClust::MethodSpec.parse('Kernel#im'))
140
+ assert_equal('#', im.display_typemark)
141
+ assert_equal('Kernel#im', im.display_label)
142
+ assert_equal('im', im.display_short_label)
143
+
144
+ sv = db.get_method(BitClust::MethodSpec.parse('Kernel$stdout'))
145
+ assert_equal('$', sv.display_typemark)
146
+ assert_equal('$stdout', sv.display_label)
147
+ assert_equal('$stdout', sv.display_short_label)
148
+ end
149
+ end
150
+
151
+ # meta description(コンパイラを通さない表示テキスト)内の module function
152
+ # 参照 [[m:Kernel.#mf2]] のラベルも、可視ページの bracket_link と同様に
153
+ # DB バージョン 4.0 以降では "?." で表示する(bitclust#282/#283 の続き。
154
+ # 従来この経路だけ ".#" のまま残っていた)
155
+ class TestMethodEntryDescriptionQdot < Test::Unit::TestCase
156
+ SRC = <<HERE
157
+ = module Kernel
158
+
159
+ == Module Functions
160
+
161
+ --- mf
162
+
163
+ [[m:Kernel.#mf2]] を参照してください。
164
+
165
+ --- mf2
166
+
167
+ 説明。
168
+ HERE
169
+
170
+ def description(version)
171
+ _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version})
172
+ db.get_method(BitClust::MethodSpec.parse('Kernel.#mf')).description
173
+ end
174
+
175
+ def test_meta_description_keeps_dot_hash_before_4_0
176
+ assert_equal('Kernel.#mf2 を参照してください。', description('3.4'))
177
+ end
178
+
179
+ def test_meta_description_uses_question_dot_at_4_0
180
+ assert_equal('Kernel?.mf2 を参照してください。', description('4.0'))
181
+ end
182
+ end
183
+
184
+ # メソッド名別の since/until (bitclust#132 P1)。
185
+ #
186
+ # テストリスト:
187
+ # [x] 新規エントリの since_map/until_map は空、since_of/until_of は nil
188
+ # [x] fill_since は初回のみ追加して true を返す。既に値があれば false で上書きしない
189
+ # [x] fill_until も同様
190
+ # [x] fill_since/fill_until は version に '=' や ',' を含むと ArgumentError
191
+ # [x] 実ディスク DB への保存→別インスタンスで再読込しても since_of/until_of が一致
192
+ # [x] 名前に '=' を含む("==", "foo=")・',' を含む("," )・"-@" が
193
+ # エンコード/デコードを経て正しく往復する
194
+ # [x] since_by_name/until_by_name のキー自体がプロパティファイルに無い
195
+ # (旧DB)場合も落ちずに空配列として読める(Entry の '[String]' デシリアライザの
196
+ # nil 安全化)
197
+ class TestMethodEntrySinceUntil < Test::Unit::TestCase
198
+ def setup
199
+ @dir = Dir.mktmpdir
200
+ @db = BitClust::MethodDatabase.new(@dir)
201
+ @db.init
202
+ @db.transaction do
203
+ @db.propset('version', '2.0')
204
+ @db.propset('encoding', 'utf-8')
205
+ end
206
+ end
207
+
208
+ def teardown
209
+ FileUtils.rm_rf(@dir)
210
+ end
211
+
212
+ def test_defaults_are_empty
213
+ m = build_entry('marker')
214
+ assert_equal({}, m.since_map)
215
+ assert_equal({}, m.until_map)
216
+ assert_nil m.since_of('marker')
217
+ assert_nil m.until_of('marker')
218
+ end
219
+
220
+ def test_fill_since_adds_once
221
+ m = build_entry('marker')
222
+ assert_equal(true, m.fill_since('marker', '2.0'))
223
+ assert_equal('2.0', m.since_of('marker'))
224
+ # 既に値があるので上書きされず false
225
+ assert_equal(false, m.fill_since('marker', '3.0'))
226
+ assert_equal('2.0', m.since_of('marker'))
227
+ end
228
+
229
+ def test_fill_until_adds_once
230
+ m = build_entry('marker')
231
+ assert_equal(true, m.fill_until('marker', '3.0'))
232
+ assert_equal('3.0', m.until_of('marker'))
233
+ assert_equal(false, m.fill_until('marker', '4.0'))
234
+ assert_equal('3.0', m.until_of('marker'))
235
+ end
236
+
237
+ def test_fill_since_rejects_version_with_equal_or_comma
238
+ m = build_entry('marker')
239
+ assert_raise(ArgumentError) { m.fill_since('marker', '2.0=1') }
240
+ assert_raise(ArgumentError) { m.fill_since('marker', '2.0,1') }
241
+ end
242
+
243
+ def test_fill_until_rejects_version_with_equal_or_comma
244
+ m = build_entry('marker')
245
+ assert_raise(ArgumentError) { m.fill_until('marker', '2.0=1') }
246
+ assert_raise(ArgumentError) { m.fill_until('marker', '2.0,1') }
247
+ end
248
+
249
+ def test_roundtrip_through_disk
250
+ m = build_entry('marker')
251
+ m.fill_since('marker', '2.0')
252
+ m.fill_until('marker', '3.0')
253
+ m.save
254
+
255
+ reloaded = reload_entry(m.id)
256
+ assert_equal('2.0', reloaded.since_of('marker'))
257
+ assert_equal('3.0', reloaded.until_of('marker'))
258
+ assert_equal({'marker' => '2.0'}, reloaded.since_map)
259
+ assert_equal({'marker' => '3.0'}, reloaded.until_map)
260
+ end
261
+
262
+ def test_roundtrip_names_with_special_characters
263
+ [ '==', 'foo=', '-@', ',' ].each do |name|
264
+ m = build_entry(name)
265
+ m.fill_since(name, '2.0')
266
+ m.save
267
+
268
+ reloaded = reload_entry(m.id)
269
+ assert_equal('2.0', reloaded.since_of(name),
270
+ "since_of(#{name.inspect}) should round-trip")
271
+ end
272
+ end
273
+
274
+ def test_multiple_names_share_one_entry
275
+ m = build_entry_with_names(['-@', 'dedup'])
276
+ m.fill_since('-@', '2.0')
277
+ m.fill_since('dedup', '3.0')
278
+ m.save
279
+
280
+ reloaded = reload_entry(m.id)
281
+ assert_equal('2.0', reloaded.since_of('-@'))
282
+ assert_equal('3.0', reloaded.since_of('dedup'))
283
+ assert_equal({'-@' => '2.0', 'dedup' => '3.0'}, reloaded.since_map)
284
+ end
285
+
286
+ def test_missing_since_by_name_key_is_backward_compatible
287
+ m = build_entry('old_method')
288
+ m.save
289
+ strip_property_line(m.id, 'since_by_name')
290
+ strip_property_line(m.id, 'until_by_name')
291
+
292
+ reloaded = reload_entry(m.id)
293
+ assert_equal([], reloaded.since_by_name)
294
+ assert_equal([], reloaded.until_by_name)
295
+ assert_nil reloaded.since_of('old_method')
296
+ end
297
+
298
+ private
299
+
300
+ def build_entry(name)
301
+ build_entry_with_names([name])
302
+ end
303
+
304
+ def build_entry_with_names(names)
305
+ id = BitClust::NameUtils.build_method_id('_builtin', 'Foo', :instance_method, names.first)
306
+ m = BitClust::MethodEntry.new(@db, id)
307
+ m.names = names
308
+ m.visibility = :public
309
+ m.kind = :defined
310
+ m.source = "説明\n"
311
+ m
312
+ end
313
+
314
+ def reload_entry(id)
315
+ fresh_db = BitClust::MethodDatabase.new(@dir)
316
+ BitClust::MethodEntry.new(fresh_db, id)
317
+ end
318
+
319
+ def property_file_path(id)
320
+ File.join(@dir, BitClust::NameUtils.encodeid("method/#{id}"))
321
+ end
322
+
323
+ def strip_property_line(id, key)
324
+ path = property_file_path(id)
325
+ lines = File.readlines(path).reject {|l| l.start_with?("#{key}=") }
326
+ File.write(path, lines.join)
327
+ end
328
+ end
@@ -4,7 +4,9 @@ require 'bitclust/methodsignature'
4
4
  class TestMethodSignature < Test::Unit::TestCase
5
5
 
6
6
  data("special var" => ["$_ -> String | nil", "--- $_ -> String | nil"],
7
- "backquote" => ["`command` -> String", "--- `(command) -> String"])
7
+ "backquote" => ["`command` -> String", "--- `(command) -> String"],
8
+ "aset" => ["self[key] = value", "--- []=(key, value)"],
9
+ "aset splat" => ["[]=(*idxary)", "--- []=(*idxary)"])
8
10
  def test_friendlyname(data)
9
11
  friendly_string, method_signature = data
10
12
  assert_equal(friendly_string, BitClust::MethodSignature.parse(method_signature).friendly_string)
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+ require 'test/unit'
3
+ require 'bitclust'
4
+ require 'bitclust/methoddatabase'
5
+ require 'bitclust/subcommands/methodsince_command'
6
+ require 'stringio'
7
+ require 'tmpdir'
8
+ require 'fileutils'
9
+
10
+ # methodsince サブコマンド(bitclust#132 P2)。
11
+ #
12
+ # テストリスト:
13
+ # [x] グローバル --database 不要(needs_database? が false)
14
+ # [x] --update が無いとエラー
15
+ # [x] ラダー DB を走査して --update 対象に since/until が書き込まれる
16
+ # (フロアのメソッドには付かず、ゲート付きメソッドには付く)
17
+ # [x] 統計行が "basename (version V): entries_updated=.. since_filled=..
18
+ # until_filled=.. floor_skipped=.." の形式で出力される
19
+ # [x] --update は複数指定でき、対象ごとに統計行が出力される
20
+ # [x] --update に無い位置引数の DB はラダーには参加するが書き込まれない
21
+ # [x] --dry-run は統計を表示するだけで実DBファイルには一切書き込まない
22
+ # [x] Runner に登録されている(bitclust methodsince として呼べる)
23
+ class TestMethodsinceCommand < Test::Unit::TestCase
24
+ def setup
25
+ @tmpdir = "methodsince_test_tmp"
26
+ FileUtils.rm_rf(@tmpdir)
27
+ @db10 = build_db('1.0', <<~'RD')
28
+ description
29
+
30
+ = class Foo < Object
31
+ == Instance Methods
32
+ --- old
33
+
34
+ 説明
35
+ RD
36
+ @db20 = build_db('2.0', <<~'RD')
37
+ description
38
+
39
+ = class Foo < Object
40
+ == Instance Methods
41
+ --- old
42
+
43
+ 説明
44
+
45
+ #@since 2.0
46
+ --- newone
47
+
48
+ 説明
49
+ #@end
50
+ RD
51
+ end
52
+
53
+ def teardown
54
+ FileUtils.rm_rf(@tmpdir)
55
+ end
56
+
57
+ def build_db(version, builtin_rd)
58
+ root = "#{@tmpdir}/tree-#{version}/refm/api/src"
59
+ FileUtils.mkdir_p("#{root}/_builtin")
60
+ File.write("#{root}/LIBRARIES", "_builtin\n")
61
+ File.write("#{root}/_builtin.rd", builtin_rd)
62
+ prefix = "#{@tmpdir}/db-#{version}"
63
+ db = BitClust::MethodDatabase.new(prefix)
64
+ db.init
65
+ db.transaction do
66
+ db.propset('version', version)
67
+ db.propset('encoding', 'utf-8')
68
+ end
69
+ db.transaction do
70
+ db.update_by_stdlibtree(root)
71
+ end
72
+ prefix
73
+ end
74
+
75
+ def run_command(argv)
76
+ cmd = BitClust::Subcommands::MethodsinceCommand.new
77
+ cmd.parse(argv)
78
+ out = StringIO.new
79
+ orig_stdout = $stdout
80
+ $stdout = out
81
+ begin
82
+ cmd.exec(argv, { prefix: nil, capi: false })
83
+ ensure
84
+ $stdout = orig_stdout
85
+ end
86
+ out.string
87
+ end
88
+
89
+ def find_entry(prefix, name)
90
+ db = BitClust::MethodDatabase.new(prefix)
91
+ db.get_class('Foo').entries.find {|m| m.name?(name) }
92
+ end
93
+
94
+ def test_needs_no_global_database_option
95
+ cmd = BitClust::Subcommands::MethodsinceCommand.new
96
+ assert_false cmd.needs_database?
97
+ end
98
+
99
+ def test_no_update_is_an_error
100
+ cmd = BitClust::Subcommands::MethodsinceCommand.new
101
+ argv = [@db10, @db20]
102
+ cmd.parse(argv)
103
+ assert_raise(SystemExit) do
104
+ capture_stderr { cmd.exec(argv, { prefix: nil, capi: false }) }
105
+ end
106
+ end
107
+
108
+ def test_fills_since_into_update_target
109
+ run_command([@db10, "--update=#{@db20}"])
110
+ assert_nil find_entry(@db20, 'old').since_of('old')
111
+ assert_equal '2.0', find_entry(@db20, 'newone').since_of('newone')
112
+ end
113
+
114
+ def test_prints_one_stats_line_per_target
115
+ out = run_command([@db10, "--update=#{@db20}"])
116
+ assert_match(
117
+ /\Adb-2\.0 \(version 2\.0\): entries_updated=\d+ since_filled=\d+ until_filled=\d+ floor_skipped=\d+\n\z/,
118
+ out)
119
+ end
120
+
121
+ def test_multiple_update_targets_each_get_a_stats_line
122
+ out = run_command(["--update=#{@db10}", "--update=#{@db20}"])
123
+ lines = out.lines
124
+ assert_equal 2, lines.size
125
+ assert_match(/\Adb-1\.0 /, lines[0])
126
+ assert_match(/\Adb-2\.0 /, lines[1])
127
+ end
128
+
129
+ def test_positional_ladder_db_is_not_written_to
130
+ before = File.read(property_file_path(@db10, 'old'))
131
+ run_command([@db10, "--update=#{@db20}"])
132
+ after = File.read(property_file_path(@db10, 'old'))
133
+ assert_equal before, after
134
+ end
135
+
136
+ def test_dry_run_leaves_real_database_untouched
137
+ run_command([@db10, "--update=#{@db20}", '--dry-run'])
138
+ assert_nil find_entry(@db20, 'newone').since_of('newone')
139
+ end
140
+
141
+ def test_dry_run_still_prints_computed_stats
142
+ out = run_command([@db10, "--update=#{@db20}", '--dry-run'])
143
+ assert_match(/since_filled=1\b/, out)
144
+ end
145
+
146
+ def test_registered_in_runner
147
+ require 'bitclust/runner'
148
+ runner = BitClust::Runner.new
149
+ runner.prepare
150
+ subcommands = runner.instance_variable_get(:@subcommands)
151
+ assert_kind_of(BitClust::Subcommands::MethodsinceCommand, subcommands['methodsince'])
152
+ end
153
+
154
+ private
155
+
156
+ def property_file_path(prefix, name)
157
+ id = BitClust::NameUtils.build_method_id('_builtin', 'Foo', :instance_method, name)
158
+ File.join(prefix, BitClust::NameUtils.encodeid("method/#{id}"))
159
+ end
160
+
161
+ def capture_stderr
162
+ orig = $stderr
163
+ $stderr = StringIO.new
164
+ yield
165
+ ensure
166
+ $stderr = orig
167
+ end
168
+ end
@@ -306,6 +306,28 @@ class TestNameUtils < Test::Unit::TestCase
306
306
  assert_equal(expected, typemark2char(target))
307
307
  end
308
308
 
309
+ # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を
310
+ # 独自の「.#」から「?.」に変える。表示だけの変換であり、識別子(typemark
311
+ # そのもの・typemark2char 等)は一切変えないので、対象は '.#' のみ。
312
+ # バージョン比較は文字列比較ではなく Gem::Version で行う(例: "10.0" を
313
+ # 文字列比較すると "4.0" より小さく見えてしまう)。
314
+ data("module function before 4.0 stays as-is" => ['.#', '.#', '3.4'],
315
+ "module function at exactly 4.0 switches" => ['?.', '.#', '4.0'],
316
+ "module function after 4.0 switches" => ['?.', '.#', '4.1'],
317
+ "module function 4.0.0 counts as 4.0" => ['?.', '.#', '4.0.0'],
318
+ "module function double-digit major (10.0)" => ['?.', '.#', '10.0'],
319
+ "module function just under 4.0 stays as-is" => ['.#', '.#', '3.99'],
320
+ "module function with nil version stays as-is" => ['.#', '.#', nil],
321
+ "module function with empty version stays" => ['.#', '.#', ''],
322
+ "singleton method mark is never rewritten" => ['.', '.', '4.0'],
323
+ "instance method mark is never rewritten" => ['#', '#', '4.0'],
324
+ "constant mark is never rewritten" => ['::', '::', '4.0'],
325
+ "special variable mark is never rewritten" => ['$', '$', '4.0'])
326
+ def test_display_typemark(data)
327
+ expected, mark, version = data
328
+ assert_equal(expected, display_typemark(mark, version))
329
+ end
330
+
309
331
  data("s" => [".", "s"],
310
332
  "i" => ["#", "i"],
311
333
  "m" => [".#", "m"],