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,290 @@
1
+ # frozen_string_literal: true
2
+ require 'test/unit'
3
+ require 'bitclust'
4
+ require 'bitclust/methoddatabase'
5
+ require 'bitclust/method_since_calculator'
6
+ require 'tmpdir'
7
+ require 'fileutils'
8
+
9
+ # メソッド名別 since/until の算出(bitclust#132 P2)。
10
+ #
11
+ # テストリスト:
12
+ # [x] フロア(ラダー最古版から存在)のメソッドは since も until も付かない
13
+ # [x] #@since ゲート付きメソッドは、追加された版以降の対象すべてで
14
+ # since が付く
15
+ # [x] 別名グループ: シグネチャごとに異なるゲートを持てば名前ごとに
16
+ # 別の since になる(-@ は 2.0.0、dedup は 3.0)。ゲートが効いて
17
+ # いない版の対象ではその別名自体が存在しない
18
+ # [x] #@until ゲート付きメソッドは、消える1つ前の版の対象で until が付き、
19
+ # 消えた後の対象にはエントリ自体が存在しない
20
+ # [x] インスタンスメソッドとシンゲルトンメソッドは同名でも別キー
21
+ # [x] kind=:undefined のエントリは走査で無視される: {: undef} → 実メソッド化
22
+ # したものは実メソッド化した版が since になり、逆に undef 化は削除扱いで
23
+ # 前の版の対象に until が付く
24
+ # [x] kind=:undefined のエントリには適用でも書き込まない
25
+ # [x] ラダーに同じバージョンが重複していれば .new で UserError
26
+ # [x] apply は冪等: 2回目は floor_skipped 以外すべて0
27
+ # [x] 既に値がある名前は上書きしない(著者値が算出値より優先)
28
+ # [x] 対象DBのバージョンがラダーに無ければ apply は UserError
29
+ # [x] 統合: `{: since="X"}` 属性行(#132 P4)でパース時に記録された値は
30
+ # 算出結果で上書きされない(RD ソース上の明示注記が算出値より優先)
31
+ class TestMethodSinceCalculator < Test::Unit::TestCase
32
+ RD = <<~'RD'
33
+ description
34
+
35
+ = class MSCTest
36
+ == Instance Methods
37
+ --- old_timer
38
+
39
+ 説明
40
+
41
+ #@since 2.0.0
42
+ --- newmeth
43
+
44
+ 説明
45
+ #@end
46
+
47
+ #@since 3.0
48
+ --- overridden
49
+ {: since="2.5"}
50
+
51
+ 説明
52
+ #@end
53
+
54
+ #@since 2.0.0
55
+ --- suppressed
56
+ {: since=""}
57
+
58
+ 説明
59
+ #@end
60
+
61
+ #@since 2.0.0
62
+ --- -@
63
+ #@since 3.0
64
+ --- dedup
65
+ #@end
66
+
67
+ 説明
68
+ #@end
69
+
70
+ #@until 3.0
71
+ --- legacy
72
+
73
+ 説明
74
+ #@end
75
+
76
+ --- undefmeth
77
+ {: undef}
78
+
79
+ 説明
80
+
81
+ --- undef_then_real
82
+ #@until 2.0.0
83
+ {: undef}
84
+ #@end
85
+
86
+ 説明
87
+
88
+ #@since 2.0.0
89
+ --- real_then_undef
90
+ #@since 3.0
91
+ {: undef}
92
+ #@end
93
+
94
+ 説明
95
+ #@end
96
+
97
+ --- marker
98
+
99
+ 説明
100
+
101
+ == Singleton Methods
102
+ #@since 2.0.0
103
+ --- marker
104
+
105
+ 説明
106
+ #@end
107
+ RD
108
+
109
+ LADDER_VERSIONS = %w[1.8.7 2.0.0 3.0]
110
+
111
+ def setup
112
+ @tmpdir = Dir.mktmpdir
113
+ @paths = {}
114
+ LADDER_VERSIONS.each {|v| build_db(v) }
115
+ end
116
+
117
+ def teardown
118
+ FileUtils.rm_rf(@tmpdir)
119
+ end
120
+
121
+ def test_floor_method_has_no_since_or_until
122
+ run_calculator('2.0.0')
123
+ run_calculator('3.0')
124
+ assert_nil find_entry('2.0.0', 'i', 'old_timer').since_of('old_timer')
125
+ assert_nil find_entry('2.0.0', 'i', 'old_timer').until_of('old_timer')
126
+ assert_nil find_entry('3.0', 'i', 'old_timer').since_of('old_timer')
127
+ assert_nil find_entry('3.0', 'i', 'old_timer').until_of('old_timer')
128
+ end
129
+
130
+ def test_since_gated_method_gets_since_in_every_later_target
131
+ run_calculator('2.0.0')
132
+ run_calculator('3.0')
133
+ assert_equal '2.0.0', find_entry('2.0.0', 'i', 'newmeth').since_of('newmeth')
134
+ assert_equal '2.0.0', find_entry('3.0', 'i', 'newmeth').since_of('newmeth')
135
+ end
136
+
137
+ def test_alias_group_since_is_split_by_name
138
+ run_calculator('2.0.0')
139
+ run_calculator('3.0')
140
+
141
+ at_2_0_0 = find_entry('2.0.0', 'i', '-@')
142
+ assert_equal ['-@'], at_2_0_0.names
143
+ assert_equal '2.0.0', at_2_0_0.since_of('-@')
144
+
145
+ at_3_0 = find_entry('3.0', 'i', '-@')
146
+ assert_equal ['-@', 'dedup'], at_3_0.names.sort
147
+ assert_equal '2.0.0', at_3_0.since_of('-@')
148
+ assert_equal '3.0', at_3_0.since_of('dedup')
149
+ end
150
+
151
+ def test_until_gated_method
152
+ run_calculator('2.0.0')
153
+ assert_equal '3.0', find_entry('2.0.0', 'i', 'legacy').until_of('legacy')
154
+ assert_nil find_entry('3.0', 'i', 'legacy')
155
+ end
156
+
157
+ def test_singleton_and_instance_method_are_distinct_keys
158
+ run_calculator('2.0.0')
159
+ assert_nil find_entry('2.0.0', 'i', 'marker').since_of('marker')
160
+ assert_equal '2.0.0', find_entry('2.0.0', 's', 'marker').since_of('marker')
161
+ end
162
+
163
+ def test_undefined_entries_are_ignored_in_scan
164
+ run_calculator('3.0')
165
+ # 全版 {: undef} の undefmeth は presence ゼロ → 何も書かれない
166
+ e = find_entry('3.0', 'i', 'undefmeth')
167
+ assert_equal :undefined, e.kind
168
+ assert_nil e.since_of('undefmeth')
169
+ assert_nil e.until_of('undefmeth')
170
+ # 1.8.7 では {: undef}(存在しない印)なので presence に数えず、実メソッド化
171
+ # した 2.0.0 が since になる(scan がフィルタしなければ floor 扱いで nil)
172
+ assert_equal '2.0.0',
173
+ find_entry('3.0', 'i', 'undef_then_real').since_of('undef_then_real')
174
+ end
175
+
176
+ def test_undefined_entries_are_ignored_in_apply
177
+ run_calculator('2.0.0')
178
+ run_calculator('3.0')
179
+ # 3.0 で undef 化 → presence は 2.0.0 のみ。2.0.0 の対象では削除扱いで
180
+ # until が付く
181
+ at_2_0_0 = find_entry('2.0.0', 'i', 'real_then_undef')
182
+ assert_equal '2.0.0', at_2_0_0.since_of('real_then_undef')
183
+ assert_equal '3.0', at_2_0_0.until_of('real_then_undef')
184
+ # 3.0 の :undefined エントリ自体には(算出値があっても)書き込まない
185
+ at_3_0 = find_entry('3.0', 'i', 'real_then_undef')
186
+ assert_equal :undefined, at_3_0.kind
187
+ assert_nil at_3_0.since_of('real_then_undef')
188
+ assert_nil at_3_0.until_of('real_then_undef')
189
+ end
190
+
191
+ def test_duplicate_version_in_ladder_raises
192
+ dbs = ladder_dbs + [fresh_db('2.0.0')]
193
+ assert_raise(BitClust::UserError) { BitClust::MethodSinceCalculator.new(dbs) }
194
+ end
195
+
196
+ def test_apply_is_idempotent
197
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
198
+ calc.scan
199
+ first_stats = calc.apply(fresh_db('2.0.0'))
200
+ second_stats = calc.apply(fresh_db('2.0.0'))
201
+
202
+ assert_operator first_stats[:since_filled], :>, 0
203
+ assert_equal 0, second_stats[:since_filled]
204
+ assert_equal 0, second_stats[:until_filled]
205
+ assert_equal 0, second_stats[:entries_updated]
206
+ assert_equal first_stats[:floor_skipped], second_stats[:floor_skipped]
207
+ end
208
+
209
+ def test_author_value_takes_precedence_over_computed_value
210
+ pre = fresh_db('3.0')
211
+ entry = pre.get_class('MSCTest').entries.find {|m| m.typechar == 'i' && m.name?('newmeth') } or raise
212
+ entry.fill_since('newmeth', '1.9.9')
213
+ entry.save
214
+
215
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
216
+ calc.scan
217
+ calc.apply(fresh_db('3.0'))
218
+
219
+ assert_equal '1.9.9', find_entry('3.0', 'i', 'newmeth').since_of('newmeth')
220
+ end
221
+
222
+ def test_author_since_attribute_parsed_from_source_survives_apply
223
+ # overridden は #@since 3.0 でゲートされているのでラダー上は 3.0 と算出
224
+ # されるが、RD ソースの {: since="2.5"} がパース時点(update_by_stdlibtree)
225
+ # で既に記録されているため、apply(fill_since は未設定時のみ書く)では
226
+ # 上書きされずそのまま残る
227
+ run_calculator('2.0.0')
228
+ run_calculator('3.0')
229
+
230
+ assert_nil find_entry('2.0.0', 'i', 'overridden')
231
+ assert_equal '2.5', find_entry('3.0', 'i', 'overridden').since_of('overridden')
232
+ end
233
+
234
+ def test_explicit_empty_since_suppresses_computed_value
235
+ # suppressed は #@since 2.0.0 ゲートによりラダー上は 2.0.0 と算出されるが、
236
+ # {: since=""}(明示的に不明=バッジ非表示)がパース時に記録されているため
237
+ # apply は上書きしない(bitclust#132: ドキュメント追加がメソッド追加より
238
+ # 遅れて算出が誤る場合の抑止)
239
+ run_calculator('3.0')
240
+ assert_equal '', find_entry('3.0', 'i', 'suppressed').since_of('suppressed')
241
+ end
242
+
243
+ def test_apply_rejects_target_whose_version_is_not_in_ladder
244
+ build_db('4.0')
245
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
246
+ calc.scan
247
+ assert_raise(BitClust::UserError) { calc.apply(fresh_db('4.0')) }
248
+ end
249
+
250
+ private
251
+
252
+ def build_db(version)
253
+ root = "#{@tmpdir}/tree-#{version}/refm/api/src"
254
+ FileUtils.mkdir_p("#{root}/_builtin")
255
+ File.write("#{root}/LIBRARIES", "_builtin\n")
256
+ File.write("#{root}/_builtin.rd", RD)
257
+ prefix = "#{@tmpdir}/db-#{version}"
258
+ db = BitClust::MethodDatabase.new(prefix)
259
+ db.init
260
+ db.transaction do
261
+ db.propset('version', version)
262
+ db.propset('encoding', 'utf-8')
263
+ end
264
+ db.transaction do
265
+ db.update_by_stdlibtree(root)
266
+ end
267
+ @paths[version] = prefix
268
+ prefix
269
+ end
270
+
271
+ def fresh_db(version)
272
+ BitClust::MethodDatabase.new(@paths[version] || raise("no such db: #{version}"))
273
+ end
274
+
275
+ def ladder_dbs
276
+ LADDER_VERSIONS.map {|v| fresh_db(v) }
277
+ end
278
+
279
+ def run_calculator(target_version)
280
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
281
+ calc.scan
282
+ calc.apply(fresh_db(target_version))
283
+ end
284
+
285
+ def find_entry(version, typechar, name)
286
+ fresh_db(version).get_class('MSCTest').entries.find {|m|
287
+ m.typechar == typechar && m.name?(name)
288
+ }
289
+ end
290
+ end
@@ -57,6 +57,22 @@ class TestMethodDatabase < Test::Unit::TestCase
57
57
  @db.get_class("B").dynamically_included.map{|m| m.name})
58
58
  end
59
59
 
60
+ # source_location must be stored without a line number so that regenerating
61
+ # the database does not churn the persisted entries (and the generated HTML).
62
+ def test_source_location_serialized_without_line_number
63
+ entry = @db.search_methods(BitClust::MethodNamePattern.new(nil, nil, 'at_exit')).records.first.entry
64
+ entry.source_location = BitClust::Location.new('refm/api/src/_builtin/Kernel', 42)
65
+ assert_equal('refm/api/src/_builtin/Kernel', entry._get_properties['source_location'])
66
+ end
67
+
68
+ def test_source_location_roundtrip_drops_line_number
69
+ entry = @db.search_methods(BitClust::MethodNamePattern.new(nil, nil, 'at_exit')).records.first.entry
70
+ entry.source_location = BitClust::Location.new('refm/api/src/_builtin/Kernel', 42)
71
+ entry._set_properties(entry._get_properties)
72
+ assert_equal('refm/api/src/_builtin/Kernel', entry.source_location.file)
73
+ assert_nil(entry.source_location.line)
74
+ end
75
+
60
76
  private
61
77
  def setup_files
62
78
  FileUtils.mkdir_p("#{@root}/_builtin")
@@ -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)