bitclust-core 1.2.6 → 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 (160) 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 +49 -5
  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 +17 -17
  24. data/lib/bitclust/capi_converter.rb +20 -0
  25. data/lib/bitclust/classentry.rb +68 -17
  26. data/lib/bitclust/compat.rb +3 -2
  27. data/lib/bitclust/completion.rb +59 -27
  28. data/lib/bitclust/crossrubyutils.rb +9 -5
  29. data/lib/bitclust/database.rb +29 -8
  30. data/lib/bitclust/doc_converter.rb +63 -0
  31. data/lib/bitclust/docentry.rb +2 -1
  32. data/lib/bitclust/entity_splitter.rb +221 -0
  33. data/lib/bitclust/entry.rb +48 -7
  34. data/lib/bitclust/exception.rb +1 -0
  35. data/lib/bitclust/functiondatabase.rb +14 -1
  36. data/lib/bitclust/functionentry.rb +6 -1
  37. data/lib/bitclust/functionreferenceparser.rb +5 -4
  38. data/lib/bitclust/generators/epub.rb +11 -3
  39. data/lib/bitclust/htmlutils.rb +2 -1
  40. data/lib/bitclust/include_graph.rb +375 -0
  41. data/lib/bitclust/include_pruner.rb +144 -0
  42. data/lib/bitclust/interface.rb +2 -1
  43. data/lib/bitclust/libraryentry.rb +9 -7
  44. data/lib/bitclust/lineinput.rb +8 -5
  45. data/lib/bitclust/link_checker.rb +179 -0
  46. data/lib/bitclust/markdown_bridge.rb +186 -0
  47. data/lib/bitclust/markdown_orchestrator.rb +267 -0
  48. data/lib/bitclust/markdown_to_rrd.rb +824 -0
  49. data/lib/bitclust/markdown_tree.rb +217 -0
  50. data/lib/bitclust/mdcompiler.rb +883 -0
  51. data/lib/bitclust/mdparser.rb +347 -0
  52. data/lib/bitclust/messagecatalog.rb +4 -3
  53. data/lib/bitclust/method_since_calculator.rb +117 -0
  54. data/lib/bitclust/methoddatabase.rb +125 -14
  55. data/lib/bitclust/methodentry.rb +130 -7
  56. data/lib/bitclust/methodid.rb +13 -11
  57. data/lib/bitclust/methodsignature.rb +11 -4
  58. data/lib/bitclust/nameutils.rb +42 -22
  59. data/lib/bitclust/parseutils.rb +1 -0
  60. data/lib/bitclust/preprocessor.rb +70 -32
  61. data/lib/bitclust/progress_bar.rb +1 -0
  62. data/lib/bitclust/rdcompiler.rb +197 -96
  63. data/lib/bitclust/refsdatabase.rb +24 -8
  64. data/lib/bitclust/reloadable_request_handler.rb +72 -0
  65. data/lib/bitclust/requesthandler.rb +22 -16
  66. data/lib/bitclust/ridatabase.rb +11 -4
  67. data/lib/bitclust/rrd_to_markdown.rb +925 -0
  68. data/lib/bitclust/rrdparser.rb +197 -30
  69. data/lib/bitclust/runner.rb +18 -2
  70. data/lib/bitclust/screen.rb +81 -11
  71. data/lib/bitclust/search_index_generator.rb +288 -0
  72. data/lib/bitclust/searcher.rb +72 -22
  73. data/lib/bitclust/server.rb +6 -1
  74. data/lib/bitclust/silent_progress_bar.rb +1 -0
  75. data/lib/bitclust/simplesearcher.rb +25 -12
  76. data/lib/bitclust/subcommand.rb +8 -1
  77. data/lib/bitclust/subcommands/ancestors_command.rb +10 -2
  78. data/lib/bitclust/subcommands/checklink_command.rb +54 -0
  79. data/lib/bitclust/subcommands/chm_command.rb +16 -7
  80. data/lib/bitclust/subcommands/classes_command.rb +6 -0
  81. data/lib/bitclust/subcommands/epub_command.rb +1 -0
  82. data/lib/bitclust/subcommands/extract_command.rb +6 -0
  83. data/lib/bitclust/subcommands/htmlfile_command.rb +22 -7
  84. data/lib/bitclust/subcommands/init_command.rb +5 -1
  85. data/lib/bitclust/subcommands/list_command.rb +13 -4
  86. data/lib/bitclust/subcommands/lookup_command.rb +29 -9
  87. data/lib/bitclust/subcommands/methods_command.rb +11 -2
  88. data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
  89. data/lib/bitclust/subcommands/preproc_command.rb +6 -0
  90. data/lib/bitclust/subcommands/property_command.rb +3 -1
  91. data/lib/bitclust/subcommands/query_command.rb +1 -0
  92. data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
  93. data/lib/bitclust/subcommands/server_command.rb +16 -10
  94. data/lib/bitclust/subcommands/setup_command.rb +25 -10
  95. data/lib/bitclust/subcommands/statichtml_command.rb +132 -6
  96. data/lib/bitclust/subcommands/update_command.rb +111 -2
  97. data/lib/bitclust/syntax_highlighter.rb +52 -5
  98. data/lib/bitclust/textutils.rb +4 -3
  99. data/lib/bitclust/version.rb +2 -1
  100. data/lib/bitclust/version_badges.rb +90 -0
  101. data/lib/bitclust/whole_file_gate.rb +137 -0
  102. data/lib/bitclust.rb +1 -0
  103. data/test/test_app.rb +269 -0
  104. data/test/test_bitclust.rb +2 -0
  105. data/test/test_capi_converter.rb +91 -0
  106. data/test/test_class_screen.rb +413 -0
  107. data/test/test_copy_doc_md.rb +53 -0
  108. data/test/test_doc_converter.rb +78 -0
  109. data/test/test_entity_splitter.rb +231 -0
  110. data/test/test_entry.rb +207 -2
  111. data/test/test_eol_warning.rb +43 -0
  112. data/test/test_functionreferenceparser.rb +11 -0
  113. data/test/test_include_graph.rb +640 -0
  114. data/test/test_include_pruner.rb +133 -0
  115. data/test/test_link_checker.rb +114 -0
  116. data/test/test_lookup_command.rb +135 -0
  117. data/test/test_markdown_bridge.rb +217 -0
  118. data/test/test_markdown_orchestrator.rb +478 -0
  119. data/test/test_markdown_to_rrd.rb +757 -0
  120. data/test/test_markdown_tree.rb +369 -0
  121. data/test/test_mdcompiler.rb +1276 -0
  122. data/test/test_mdparser.rb +538 -0
  123. data/test/test_method_screen.rb +166 -0
  124. data/test/test_method_since_calculator.rb +290 -0
  125. data/test/test_methoddatabase.rb +16 -0
  126. data/test/test_methodentry.rb +328 -0
  127. data/test/test_methodsignature.rb +3 -1
  128. data/test/test_methodsince_command.rb +168 -0
  129. data/test/test_nameutils.rb +22 -0
  130. data/test/test_preprocessor.rb +215 -0
  131. data/test/test_rdcompiler.rb +429 -27
  132. data/test/test_refsdatabase.rb +32 -0
  133. data/test/test_rrd_to_markdown.rb +968 -0
  134. data/test/test_rrdparser.rb +295 -2
  135. data/test/test_run_ruby_wasm.rb +54 -0
  136. data/test/test_run_worker_prelude.rb +83 -0
  137. data/test/test_runner.rb +51 -0
  138. data/test/test_search_index_generator.rb +513 -0
  139. data/test/test_search_screen.rb +48 -0
  140. data/test/test_searcher.rb +153 -0
  141. data/test/test_searchpage_command.rb +167 -0
  142. data/test/test_simplesearcher.rb +28 -0
  143. data/test/test_statichtml_command.rb +204 -0
  144. data/test/test_syntax_highlighter.rb +60 -0
  145. data/test/test_whole_file_gate.rb +175 -0
  146. data/theme/default/js/NOTICE +42 -0
  147. data/theme/default/js/run-worker.js +111 -0
  148. data/theme/default/js/run.js +350 -0
  149. data/theme/default/js/search_controller.js +134 -0
  150. data/theme/default/js/search_init.js +176 -0
  151. data/theme/default/js/search_navigation.js +110 -0
  152. data/theme/default/js/search_page.js +202 -0
  153. data/theme/default/js/search_ranker.js +244 -0
  154. data/theme/default/script.js +91 -18
  155. data/theme/default/search.css +149 -0
  156. data/theme/default/style.css +193 -21
  157. data/theme/default/syntax-highlight.css +8 -0
  158. data/theme/lillia/style.css +45 -1
  159. metadata +178 -21
  160. data/README +0 -32
@@ -0,0 +1,153 @@
1
+ require "test/unit"
2
+ require "bitclust"
3
+ require "bitclust/searcher"
4
+ require "fileutils"
5
+ require "tmpdir"
6
+
7
+ class TestTerminalView < Test::Unit::TestCase
8
+ include BitClust
9
+
10
+ def test_show_class
11
+ view = TerminalView.new(Plain.new, {})
12
+ db = Database.dummy
13
+ foo = ClassEntry.new(db, "Foo")
14
+ bar = ClassEntry.new(db, "Bar")
15
+ out, err = capture_output do
16
+ assert_nothing_raised do
17
+ view.show_class([foo, bar])
18
+ end
19
+ end
20
+ assert_equal %w[Bar Foo], out.split
21
+ assert_empty err
22
+ end
23
+ end
24
+
25
+ class TestSearcherWindowsDrivePath < Test::Unit::TestCase
26
+ include BitClust
27
+
28
+ ENV_KEYS = %w[REFE2_SERVER BITCLUST_SERVER REFE2_DATADIR BITCLUST_DATADIR]
29
+
30
+ def setup
31
+ @saved_env = {}
32
+ ENV_KEYS.each {|key| @saved_env[key] = ENV.delete(key) }
33
+ @saved_dir = Dir.pwd
34
+ @searcher = Searcher.new
35
+ end
36
+
37
+ def teardown
38
+ Dir.chdir(@saved_dir)
39
+ ENV_KEYS.each do |key|
40
+ if @saved_env[key]
41
+ ENV[key] = @saved_env[key]
42
+ else
43
+ ENV.delete(key)
44
+ end
45
+ end
46
+ end
47
+
48
+ def test_windows_drive_path_predicate
49
+ assert_true @searcher.send(:windows_drive_path?, "G:/Users/foo/.bitclust/db-2.2.0")
50
+ assert_true @searcher.send(:windows_drive_path?, 'C:\Users\foo\.bitclust\db-2.2.0')
51
+ assert_false @searcher.send(:windows_drive_path?, "/home/foo/.bitclust/db-2.2.0")
52
+ assert_false @searcher.send(:windows_drive_path?, "druby://localhost:10001")
53
+ end
54
+
55
+ def test_drive_path_uri_keeps_drive_letter
56
+ uri = @searcher.send(:drive_path_uri, "G:/Users/foo/.bitclust/db-2.2.0")
57
+ assert_equal "file", uri.scheme
58
+ assert_equal "G:/Users/foo/.bitclust/db-2.2.0", uri.path
59
+ end
60
+
61
+ def test_find_dblocation_keeps_windows_drive_letter
62
+ with_fake_datadir("G:/Users/foo/.bitclust/db-2.2.0") do |relative_path|
63
+ ENV["BITCLUST_DATADIR"] = relative_path
64
+ location = @searcher.send(:find_dblocation)
65
+ assert_equal "file", location.scheme
66
+ assert_equal relative_path, location.path
67
+ end
68
+ end
69
+
70
+ def test_find_dblocation_keeps_unix_absolute_path
71
+ Dir.mktmpdir do |dir|
72
+ datadir = File.join(dir, ".bitclust", "db-2.2.0")
73
+ FileUtils.mkdir_p(datadir)
74
+ FileUtils.touch(File.join(datadir, "properties"))
75
+ ENV["BITCLUST_DATADIR"] = datadir
76
+ location = @searcher.send(:find_dblocation)
77
+ assert_equal "file", location.scheme
78
+ assert_equal datadir, location.path
79
+ end
80
+ end
81
+
82
+ def test_database_option_keeps_windows_drive_letter
83
+ @searcher.parser.parse!(["--database=G:/Users/foo/.bitclust/db-2.2.0"])
84
+ dblocation = @searcher.instance_variable_get(:@dblocation)
85
+ assert_equal "file", dblocation.scheme
86
+ assert_equal "G:/Users/foo/.bitclust/db-2.2.0", dblocation.path
87
+ end
88
+
89
+ private
90
+
91
+ # datadir/properties を tmpdir 内に作り、tmpdir へ chdir したうえで
92
+ # datadir までの相対パス(ドライブレターを含む)を渡す。":" は Unix の
93
+ # ファイル名として有効な文字なので、Linux 上でも "G:" というディレク
94
+ # トリを作ってドライブレター付きパスの再現ができる
95
+ def with_fake_datadir(relative_path)
96
+ Dir.mktmpdir do |dir|
97
+ datadir = File.join(dir, *relative_path.split("/"))
98
+ FileUtils.mkdir_p(datadir)
99
+ FileUtils.touch(File.join(datadir, "properties"))
100
+ Dir.chdir(dir) do
101
+ yield relative_path
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # refe の module_function 表示(bitclust#297)。
108
+ # HTML 側(#282)と同じく、DB のバージョンが 4.0 以降なら
109
+ # "Foo.#baz" ではなく "Foo?.baz" と表示する。
110
+ class TestSearcherModuleFunctionDisplay < Test::Unit::TestCase
111
+ include BitClust
112
+
113
+ def record_for(version)
114
+ db = MethodDatabase.dummy("version" => version)
115
+ spec = MethodSpec.parse("Foo.#baz")
116
+ SearchResult::Record.new(db, spec, spec)
117
+ end
118
+
119
+ def test_names_folds_module_function_typemark_for_40
120
+ assert_equal ["Foo?.baz"], record_for("4.0").names
121
+ end
122
+
123
+ def test_names_keeps_typemark_before_40
124
+ assert_equal ["Foo.#baz"], record_for("3.4").names
125
+ end
126
+
127
+ def test_names_without_db_version_keeps_typemark
128
+ spec = MethodSpec.parse("Foo.#baz")
129
+ assert_equal ["Foo.#baz"], SearchResult::Record.new(nil, spec, spec).names
130
+ end
131
+ end
132
+
133
+ # refe のクエリ側も 4.0 以降の表示形式 "Foo?.baz" を受け付ける(bitclust#297)
134
+ class TestSearcherModuleFunctionQuery < Test::Unit::TestCase
135
+ include BitClust
136
+
137
+ def parse(pat)
138
+ Searcher.new.send(:parse_method_spec_pattern, pat)
139
+ end
140
+
141
+ def test_question_dot_pattern
142
+ assert_equal ["Foo", ".#", "baz"], parse("Foo?.baz")
143
+ end
144
+
145
+ def test_dot_hash_pattern_still_works
146
+ assert_equal ["Foo", ".#", "baz"], parse("Foo.#baz")
147
+ end
148
+
149
+ def test_predicate_method_names_unaffected
150
+ assert_equal ["Foo", "#", "empty?"], parse("Foo#empty?")
151
+ assert_equal ["Foo", ".", "b?"], parse("Foo.b?")
152
+ end
153
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+ require 'test/unit'
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'bitclust'
6
+ require 'bitclust/methoddatabase'
7
+ require 'bitclust/subcommands/searchpage_command'
8
+
9
+ # searchpage サブコマンド: 複数バージョンの DB から /ja/search/ 置換用の
10
+ # 静的検索ページ一式(index.html + 統合 search_data.js + JS/CSS アセット)を
11
+ # 生成する。rurema-search(サーバ常駐の全文検索)のリタイア先。
12
+ #
13
+ # テストリスト:
14
+ # [x] 統合 index: 全版共通エントリは versions に全版、片方だけのものはその版のみ
15
+ # [x] index.html に search_versions(昇順)と検索 UI の要素が埋まる
16
+ # [x] vendored JS + NOTICE + search_page.js + search.css がコピーされる
17
+ # [x] ページ専用 UI なので search_init.js(ページ内ボックス用)は含めない
18
+ # [x] DB を持たない引数なし呼び出しはエラー
19
+ # [x] グローバル --database 不要(needs_database? が false)
20
+ class TestSearchpageCommand < Test::Unit::TestCase
21
+ def setup
22
+ @tmpdir = "searchpage_test_tmp"
23
+ FileUtils.rm_rf(@tmpdir)
24
+ @db34 = build_db('3.4', <<~'RD')
25
+ description
26
+
27
+ = class Foo < Object
28
+ == Instance Methods
29
+ --- foo
30
+ RD
31
+ @db41 = build_db('4.1', <<~'RD')
32
+ description
33
+
34
+ = class Foo < Object
35
+ == Instance Methods
36
+ --- foo
37
+ --- bar
38
+ RD
39
+ @out = "#{@tmpdir}/out"
40
+ end
41
+
42
+ def teardown
43
+ FileUtils.rm_rf(@tmpdir)
44
+ end
45
+
46
+ def build_db(version, builtin_rd)
47
+ root = "#{@tmpdir}/tree-#{version}/refm/api/src"
48
+ FileUtils.mkdir_p("#{root}/_builtin")
49
+ File.write("#{root}/LIBRARIES", "_builtin\n")
50
+ File.write("#{root}/_builtin.rd", builtin_rd)
51
+ prefix = "#{@tmpdir}/db-#{version}"
52
+ db = BitClust::MethodDatabase.new(prefix)
53
+ db.init
54
+ db.transaction do
55
+ db.propset('version', version)
56
+ db.propset('encoding', 'utf-8')
57
+ end
58
+ db.transaction do
59
+ db.update_by_stdlibtree(root)
60
+ end
61
+ prefix
62
+ end
63
+
64
+ def run_command(argv)
65
+ cmd = BitClust::Subcommands::SearchpageCommand.new
66
+ cmd.parse(argv)
67
+ cmd.exec(argv, { prefix: nil, capi: false })
68
+ cmd
69
+ end
70
+
71
+ def test_needs_no_global_database_option
72
+ cmd = BitClust::Subcommands::SearchpageCommand.new
73
+ assert_false cmd.needs_database?
74
+ end
75
+
76
+ def test_merged_index_versions
77
+ run_command(["--outputdir=#{@out}", @db41, @db34])
78
+ js = File.read("#{@out}/js/search_data.js")
79
+ assert_match(/\Avar search_data = \{/, js)
80
+ data = JSON.parse(js.sub(/\Avar search_data = /, '').sub(/;\z/, ''))
81
+ foo = data['index'].find { |e| e['full_name'] == 'Foo#foo' }
82
+ bar = data['index'].find { |e| e['full_name'] == 'Foo#bar' }
83
+ klass = data['index'].find { |e| e['full_name'] == 'Foo' }
84
+ assert_equal %w[3.4 4.1], foo['versions']
85
+ assert_equal %w[4.1], bar['versions']
86
+ assert_equal %w[3.4 4.1], klass['versions']
87
+ end
88
+
89
+ def test_merged_index_shows_module_functions_as_question_dot_only
90
+ # bitclust#279 コメント対応: 版によって表示が ".#"(4.0 より前)と
91
+ # "?."(4.0 以降)に割れる module function は、統合ページでは "?."
92
+ # 表記の1エントリに合流させる(両表記が併記されるとわかりにくい)
93
+ db33 = build_db('3.3', <<~'RD')
94
+ description
95
+
96
+ = module Kernel
97
+ == Module Functions
98
+ --- at_exit{ ... } -> Proc
99
+ aaa
100
+ RD
101
+ db40 = build_db('4.0', <<~'RD')
102
+ description
103
+
104
+ = module Kernel
105
+ == Module Functions
106
+ --- at_exit{ ... } -> Proc
107
+ aaa
108
+ RD
109
+ run_command(["--outputdir=#{@out}", db40, db33])
110
+ js = File.read("#{@out}/js/search_data.js")
111
+ data = JSON.parse(js.sub(/\Avar search_data = /, '').sub(/;\z/, ''))
112
+ entries = data['index'].select { |e| e['name'] == 'at_exit' }
113
+ assert_equal ['Kernel?.at_exit'], entries.map { |e| e['full_name'] }
114
+ assert_equal %w[3.3 4.0], entries[0]['versions']
115
+ assert_not_match(/Kernel\.#/, js)
116
+ end
117
+
118
+ def test_index_html_embeds_versions_and_ui
119
+ run_command(["--outputdir=#{@out}", @db41, @db34])
120
+ html = File.read("#{@out}/index.html")
121
+ assert_match(/var search_versions = \["3\.4","4\.1"\];/, html)
122
+ assert_match(/var search_version_base = "\.\.\/";/, html)
123
+ assert_match(/id="search-field"/, html)
124
+ assert_match(/id="search-results"/, html)
125
+ assert_match(%r{js/search_data\.js}, html)
126
+ assert_match(%r{js/search_page\.js}, html)
127
+ end
128
+
129
+ def test_assets_are_copied
130
+ run_command(["--outputdir=#{@out}", @db41, @db34])
131
+ %w[search_navigation.js search_ranker.js search_controller.js
132
+ search_page.js NOTICE].each do |f|
133
+ assert File.exist?("#{@out}/js/#{f}"), "js/#{f} not copied"
134
+ end
135
+ assert File.exist?("#{@out}/search.css")
136
+ # ページ内ドロップダウン用の search_init.js はこのページでは使わない
137
+ assert_false File.exist?("#{@out}/js/search_init.js")
138
+ end
139
+
140
+ def test_no_database_arguments_is_an_error
141
+ cmd = BitClust::Subcommands::SearchpageCommand.new
142
+ argv = ["--outputdir=#{@out}"]
143
+ cmd.parse(argv)
144
+ assert_raise(SystemExit) do
145
+ capture_stderr { cmd.exec(argv, { prefix: nil, capi: false }) }
146
+ end
147
+ end
148
+
149
+ def test_missing_outputdir_is_an_error
150
+ cmd = BitClust::Subcommands::SearchpageCommand.new
151
+ argv = [@db34]
152
+ cmd.parse(argv)
153
+ assert_raise(SystemExit) do
154
+ capture_stderr { cmd.exec(argv, { prefix: nil, capi: false }) }
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def capture_stderr
161
+ orig = $stderr
162
+ $stderr = StringIO.new
163
+ yield
164
+ ensure
165
+ $stderr = orig
166
+ end
167
+ end
@@ -15,6 +15,12 @@ class TestSearcher < Test::Unit::TestCase
15
15
  == Class Methods
16
16
  --- bar
17
17
  = reopen Kernel
18
+ == Module Functions
19
+ --- open
20
+
21
+ == Instance Methods
22
+ --- puts
23
+
18
24
  == Special Variables
19
25
  --- $spespe
20
26
 
@@ -45,4 +51,26 @@ HERE
45
51
  assert_equal([], search_pattern(@db, " "), 'space')
46
52
  assert_equal([], search_pattern(@db, ""), 'blank')
47
53
  end
54
+
55
+ # bitclust#250 follow-up: the dynamic server's /search accepts free-text
56
+ # queries, and a module function may be typed in either notation --
57
+ # "Kernel.#open" (bitclust's own internal spec-string form, still what
58
+ # docs for Ruby < 4.0 display) or "Kernel?.open" (what docs for Ruby >=
59
+ # 4.0 display since #277). Both must resolve to the same method,
60
+ # regardless of which notation the *query* uses -- the query parser has
61
+ # no idea (and shouldn't need to know) which doc version the user is
62
+ # thinking of.
63
+ def test_module_function_query_notation
64
+ [['Kernel?.open', 'open'], # new (>=4.0 display) notation
65
+ ['Kernel.#open', 'open'], # existing (bitclust-internal) notation: regression check
66
+ ['?.open', 'open'], # bare module-function marker, no class name
67
+ ['Kernel#puts', 'puts'], # instance method: unaffected by the "?." change
68
+ ].each{|q, expected|
69
+ ret = search_pattern(@db, q)
70
+ assert_not_equal([], ret, q)
71
+ assert_equal(expected, ret[0].name, q)
72
+ }
73
+ # Singleton-method dot notation ('Hoge.h' => 'hoge' in test_simple_search
74
+ # above) is also unaffected; kept there rather than duplicated here.
75
+ end
48
76
  end
@@ -0,0 +1,204 @@
1
+ require 'test/unit'
2
+ require 'tmpdir'
3
+ require 'stringio'
4
+ require 'bitclust'
5
+ require 'bitclust/subcommands/statichtml_command'
6
+
7
+ class TestStatichtmlURLMapperEx < Test::Unit::TestCase
8
+ def setup
9
+ @urlmapper = BitClust::Subcommands::StatichtmlCommand::URLMapperEx.new(
10
+ :suffix => 'html',
11
+ :edit_base_url => 'https://github.com/rurema/doctree/edit/master',
12
+ )
13
+ end
14
+
15
+ # The edit URL must not contain a line number, so that shifting source lines
16
+ # does not churn the generated HTML diff.
17
+ def test_edit_url_has_no_line_number
18
+ location = BitClust::Location.new('refm/api/src/_builtin/Array', 42)
19
+ assert_equal('https://github.com/rurema/doctree/edit/master/refm/api/src/_builtin/Array',
20
+ @urlmapper.edit_url(location))
21
+ end
22
+
23
+ # A location restored from the database has no line number (nil); the edit URL
24
+ # must still be a clean file link without a trailing "#L".
25
+ def test_edit_url_without_line_in_location
26
+ location = BitClust::Location.new('refm/api/src/_builtin/Array', nil)
27
+ assert_equal('https://github.com/rurema/doctree/edit/master/refm/api/src/_builtin/Array',
28
+ @urlmapper.edit_url(location))
29
+ end
30
+ end
31
+
32
+ class TestStatichtmlRunRubyWasm < Test::Unit::TestCase
33
+ def build_command(themedir, outputdir)
34
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
35
+ cmd.instance_variable_set(:@manager_config, { :themedir => Pathname.new(themedir) })
36
+ cmd.instance_variable_set(:@outputdir, Pathname.new(outputdir))
37
+ cmd.instance_variable_set(:@verbose, false)
38
+ cmd
39
+ end
40
+
41
+ def test_option_defaults_to_nil
42
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
43
+ assert_nil(cmd.instance_variable_get(:@run_ruby_wasm))
44
+ end
45
+
46
+ def test_option_is_parsed
47
+ url = 'https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@2.9.3-2.9.4/dist/ruby+stdlib.wasm'
48
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
49
+ cmd.parse(["--run-ruby-wasm=#{url}"])
50
+ assert_equal(url, cmd.instance_variable_get(:@run_ruby_wasm))
51
+ end
52
+
53
+ def test_run_js_is_copied
54
+ Dir.mktmpdir do |dir|
55
+ themedir = File.join(dir, 'theme')
56
+ outputdir = File.join(dir, 'out')
57
+ FileUtils.mkdir_p(File.join(themedir, 'js'))
58
+ FileUtils.mkdir_p(outputdir)
59
+ File.write(File.join(themedir, 'js', 'run.js'), "// run\n")
60
+ File.write(File.join(themedir, 'js', 'run-worker.js'), "// worker\n")
61
+ cmd = build_command(themedir, outputdir)
62
+ cmd.send(:copy_run_ruby_wasm_script)
63
+ assert_true(File.file?(File.join(outputdir, 'js', 'run.js')))
64
+ assert_true(File.file?(File.join(outputdir, 'js', 'run-worker.js')))
65
+ end
66
+ end
67
+
68
+ def test_theme_without_run_js_is_tolerated
69
+ Dir.mktmpdir do |dir|
70
+ themedir = File.join(dir, 'theme')
71
+ outputdir = File.join(dir, 'out')
72
+ FileUtils.mkdir_p(themedir)
73
+ FileUtils.mkdir_p(outputdir)
74
+ cmd = build_command(themedir, outputdir)
75
+ orig_stderr, $stderr = $stderr, StringIO.new
76
+ begin
77
+ assert_nothing_raised { cmd.send(:copy_run_ruby_wasm_script) }
78
+ assert_match(/run\.js not found/, $stderr.string)
79
+ assert_match(/run-worker\.js not found/, $stderr.string)
80
+ ensure
81
+ $stderr = orig_stderr
82
+ end
83
+ assert_false(File.exist?(File.join(outputdir, 'js', 'run.js')))
84
+ assert_false(File.exist?(File.join(outputdir, 'js', 'run-worker.js')))
85
+ end
86
+ end
87
+
88
+ # A themedir with run.js but not yet upgraded with run-worker.js (or vice
89
+ # versa) should still copy whichever file it does have, rather than
90
+ # aborting or silently skipping both.
91
+ def test_partial_theme_copies_what_it_has
92
+ Dir.mktmpdir do |dir|
93
+ themedir = File.join(dir, 'theme')
94
+ outputdir = File.join(dir, 'out')
95
+ FileUtils.mkdir_p(File.join(themedir, 'js'))
96
+ FileUtils.mkdir_p(outputdir)
97
+ File.write(File.join(themedir, 'js', 'run.js'), "// run\n")
98
+ cmd = build_command(themedir, outputdir)
99
+ orig_stderr, $stderr = $stderr, StringIO.new
100
+ begin
101
+ cmd.send(:copy_run_ruby_wasm_script)
102
+ assert_match(/run-worker\.js not found/, $stderr.string)
103
+ ensure
104
+ $stderr = orig_stderr
105
+ end
106
+ assert_true(File.file?(File.join(outputdir, 'js', 'run.js')))
107
+ assert_false(File.exist?(File.join(outputdir, 'js', 'run-worker.js')))
108
+ end
109
+ end
110
+ end
111
+
112
+ class TestStatichtmlSitemap < Test::Unit::TestCase
113
+ def build_command(outputdir)
114
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
115
+ cmd.instance_variable_set(:@outputdir, Pathname.new(outputdir))
116
+ cmd.instance_variable_set(:@verbose, false)
117
+ cmd
118
+ end
119
+
120
+ def test_option_defaults_to_nil
121
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
122
+ assert_nil(cmd.instance_variable_get(:@sitemap_baseurl))
123
+ end
124
+
125
+ def test_option_is_parsed
126
+ cmd = BitClust::Subcommands::StatichtmlCommand.new
127
+ cmd.parse(['--sitemap-baseurl=https://docs.ruby-lang.org/ja/3.4/'])
128
+ assert_equal('https://docs.ruby-lang.org/ja/3.4/',
129
+ cmd.instance_variable_get(:@sitemap_baseurl))
130
+ end
131
+
132
+ def test_record_sitemap_path_is_noop_without_baseurl
133
+ Dir.mktmpdir do |dir|
134
+ cmd = build_command(dir)
135
+ cmd.send(:record_sitemap_path, Pathname.new(dir) + 'class' + 'String.html')
136
+ assert_empty(cmd.instance_variable_get(:@sitemap_paths))
137
+ end
138
+ end
139
+
140
+ def test_record_sitemap_path_keeps_relative_path
141
+ Dir.mktmpdir do |dir|
142
+ cmd = build_command(dir)
143
+ cmd.instance_variable_set(:@sitemap_baseurl, 'https://docs.ruby-lang.org/ja/3.4/')
144
+ cmd.send(:record_sitemap_path, Pathname.new(dir) + 'class' + 'String.html')
145
+ assert_equal(%w[class/String.html], cmd.instance_variable_get(:@sitemap_paths))
146
+ end
147
+ end
148
+
149
+ def test_create_sitemap_writes_loc_urls
150
+ Dir.mktmpdir do |dir|
151
+ cmd = build_command(dir)
152
+ cmd.instance_variable_set(:@sitemap_paths,
153
+ ['class/String.html', 'method/String/i/upcase.html'])
154
+ cmd.send(:create_sitemap, Pathname.new(dir), 'https://docs.ruby-lang.org/ja/3.4/')
155
+ xml = File.read(File.join(dir, 'sitemap.xml'))
156
+ assert_match(%r{\A<\?xml version="1\.0" encoding="UTF-8"\?>\n}, xml)
157
+ assert_match(%r{<urlset xmlns="http://www\.sitemaps\.org/schemas/sitemap/0\.9">}, xml)
158
+ assert_match(%r{<url><loc>https://docs\.ruby-lang\.org/ja/3\.4/class/String\.html</loc></url>}, xml)
159
+ assert_match(%r{<url><loc>https://docs\.ruby-lang\.org/ja/3\.4/method/String/i/upcase\.html</loc></url>}, xml)
160
+ assert_match(%r{</urlset>\n\z}, xml)
161
+ end
162
+ end
163
+
164
+ def test_create_sitemap_adds_trailing_slash_to_baseurl
165
+ Dir.mktmpdir do |dir|
166
+ cmd = build_command(dir)
167
+ cmd.instance_variable_set(:@sitemap_paths, ['class/String.html'])
168
+ cmd.send(:create_sitemap, Pathname.new(dir), 'https://docs.ruby-lang.org/ja/3.4')
169
+ xml = File.read(File.join(dir, 'sitemap.xml'))
170
+ assert_match(%r{<loc>https://docs\.ruby-lang\.org/ja/3\.4/class/String\.html</loc>}, xml)
171
+ end
172
+ end
173
+
174
+ def test_create_sitemap_escapes_special_characters
175
+ Dir.mktmpdir do |dir|
176
+ cmd = build_command(dir)
177
+ cmd.instance_variable_set(:@sitemap_paths, ['method/String/i/=3d=3d.html'])
178
+ cmd.send(:create_sitemap, Pathname.new(dir), 'https://example.com/a&b/')
179
+ xml = File.read(File.join(dir, 'sitemap.xml'))
180
+ assert_match(%r{<loc>https://example\.com/a&amp;b/method/String/i/=3d=3d\.html</loc>}, xml)
181
+ assert_not_match(/a&b/, xml)
182
+ end
183
+ end
184
+
185
+ def test_create_sitemap_warns_and_truncates_beyond_limit
186
+ Dir.mktmpdir do |dir|
187
+ cmd = build_command(dir)
188
+ limit = BitClust::Subcommands::StatichtmlCommand::MAX_SITEMAP_URLS
189
+ paths = (1..(limit + 1)).map {|i| "class/C#{i}.html" }
190
+ cmd.instance_variable_set(:@sitemap_paths, paths)
191
+ orig_stderr, $stderr = $stderr, StringIO.new
192
+ begin
193
+ cmd.send(:create_sitemap, Pathname.new(dir), 'https://example.com/')
194
+ assert_match(/warning:.*#{limit}/, $stderr.string)
195
+ ensure
196
+ $stderr = orig_stderr
197
+ end
198
+ xml = File.read(File.join(dir, 'sitemap.xml'))
199
+ assert_equal(limit, xml.scan('<url>').size)
200
+ assert_match(%r{<loc>https://example\.com/class/C1\.html</loc>}, xml)
201
+ assert_not_match(/C#{limit + 1}\.html/, xml)
202
+ end
203
+ end
204
+ end
@@ -82,4 +82,64 @@ class TestSyntaxHighlighter < Test::Unit::TestCase
82
82
  END
83
83
  assert_equal(expected, highlight(source))
84
84
  end
85
+
86
+ test 'symbol const' do
87
+ source = <<~END
88
+ class Foo
89
+ def to_hash
90
+ {:Bar => 'a',
91
+ :Baz => 'b'
92
+ }
93
+ end
94
+ end
95
+ END
96
+ expected = <<~END
97
+ <span class="k">class</span> <span class="nn"></span><span class="o"></span><span class="nc">Foo</span>
98
+ <span class="k">def</span> <span class="nf">to_hash</span>
99
+ <span class="p">{</span><span class="ss">:Bar</span> <span class="o">=&gt;</span> <span class="s1">'a'</span>,
100
+ <span class="ss">:Baz</span> <span class="o">=&gt;</span> <span class="s1">'b'</span>
101
+ <span class="p">}</span>
102
+ <span class="k">end</span>
103
+ <span class="k">end</span>
104
+ END
105
+ assert_equal(expected, highlight(source))
106
+ end
107
+
108
+ test 'symbol list' do
109
+ source = <<~END
110
+ %i[hellow world].each {|s| p s }
111
+ END
112
+ expected = <<~END
113
+ <span class="ss">%i[hellow world]</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span><span class="o">|</span>s<span class="o">|</span> <span class="nb">p</span> s <span class="p">}</span>
114
+ END
115
+ assert_equal(expected, highlight(source))
116
+
117
+ source = <<~END
118
+ %I[hellow world].each {|s| p s }
119
+ END
120
+ expected = <<~END
121
+ <span class="ss">%I[hellow world]</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span><span class="o">|</span>s<span class="o">|</span> <span class="nb">p</span> s <span class="p">}</span>
122
+ END
123
+ assert_equal(expected, highlight(source))
124
+ end
125
+
126
+ test 'one liner module' do
127
+ source = <<~END
128
+ module Foo; end
129
+ END
130
+ expected = <<~END
131
+ <span class="k">module</span> <span class="nn">Foo</span>; <span class="k">end</span>
132
+ END
133
+ assert_equal(expected, highlight(source))
134
+ end
135
+
136
+ test 'one liner class' do
137
+ source = <<~END
138
+ class Foo; end
139
+ END
140
+ expected = <<~END
141
+ <span class="k">class</span> <span class="nn"></span><span class="o"></span><span class="nc">Foo</span>; <span class="k">end</span>
142
+ END
143
+ assert_equal(expected, highlight(source))
144
+ end
85
145
  end