bitclust-core 1.6.0 → 1.7.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.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +4 -2
- data/data/bitclust/template/function-index +1 -0
- data/data/bitclust/template/layout +1 -0
- data/data/bitclust/template.offline/function-index +1 -0
- data/data/bitclust/template.offline/layout +3 -0
- data/lib/bitclust/classentry.rb +1 -1
- data/lib/bitclust/docentry.rb +1 -1
- data/lib/bitclust/functionentry.rb +1 -1
- data/lib/bitclust/irb.rb +68 -0
- data/lib/bitclust/markdown_exporter.rb +311 -0
- data/lib/bitclust/mdcompiler.rb +15 -1
- data/lib/bitclust/mdparser.rb +42 -1
- data/lib/bitclust/methoddatabase.rb +4 -0
- data/lib/bitclust/methodentry.rb +24 -0
- data/lib/bitclust/methodsignature.rb +1 -1
- data/lib/bitclust/preprocessor.rb +4 -1
- data/lib/bitclust/rbs_overload_matcher.rb +179 -0
- data/lib/bitclust/rbs_sig_importer.rb +276 -0
- data/lib/bitclust/rbs_signatures.rb +125 -0
- data/lib/bitclust/rdcompiler.rb +12 -1
- data/lib/bitclust/runner.rb +2 -0
- data/lib/bitclust/screen.rb +4 -0
- data/lib/bitclust/search_index_generator.rb +28 -10
- data/lib/bitclust/searcher.rb +12 -2
- data/lib/bitclust/subcommands/rbssig_command.rb +99 -0
- data/lib/bitclust/subcommands/setup_command.rb +4 -1
- data/lib/bitclust/subcommands/statichtml_command.rb +59 -0
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust-irb.rb +2 -0
- data/test/test_irb_plugin.rb +118 -0
- data/test/test_link_checker.rb +1 -1
- data/test/test_markdown_exporter.rb +260 -0
- data/test/test_mdcompiler.rb +47 -5
- data/test/test_mdparser.rb +219 -4
- data/test/test_methodentry.rb +111 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_preprocessor.rb +26 -0
- data/test/test_rbs_overload_matcher.rb +170 -0
- data/test/test_rbs_sig_importer.rb +237 -0
- data/test/test_rbssig_command.rb +186 -0
- data/test/test_rdcompiler.rb +99 -1
- data/test/test_search_index_generator.rb +81 -0
- data/test/test_setup_command.rb +21 -0
- data/test/test_statichtml_command.rb +137 -0
- data/theme/default/js/run.js +14 -0
- data/theme/default/js/search_init.js +3 -1
- data/theme/default/js/search_page.js +7 -1
- data/theme/default/js/version_switcher.js +147 -0
- data/theme/default/search.css +21 -0
- data/theme/default/style.css +83 -7
- data/theme/lillia/style.css +26 -0
- metadata +35 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'bitclust'
|
|
4
|
+
require 'bitclust/methoddatabase'
|
|
5
|
+
require 'bitclust/subcommands/rbssig_command'
|
|
6
|
+
require 'stringio'
|
|
7
|
+
require 'tmpdir'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
require 'json'
|
|
10
|
+
|
|
11
|
+
# rbssig サブコマンド(RBS シグネチャ表示)。
|
|
12
|
+
#
|
|
13
|
+
# bitclust rbssig --sig-dir=DIR <dbpath>
|
|
14
|
+
# bitclust rbssig --sig-root=RBS_CHECKOUT <dbpath>
|
|
15
|
+
#
|
|
16
|
+
# RBS シグネチャは 4.0 以降のドキュメント専用なので、DB の version が
|
|
17
|
+
# 4.0 未満ならエラーにする(比較は display_typemark と同じく Gem::Version。
|
|
18
|
+
# 文字列比較だと "10.0" が "4.0" より小さく見える)。
|
|
19
|
+
#
|
|
20
|
+
# テストリスト:
|
|
21
|
+
# [x] グローバル --database 不要(needs_database? が false)
|
|
22
|
+
# [x] --sig-root/--sig-dir がどちらも無いとエラー
|
|
23
|
+
# [x] dbpath がちょうど 1 個でないとエラー
|
|
24
|
+
# [x] DB の version が 4.0 未満だとエラー
|
|
25
|
+
# [x] version 10.0 は 4.0 以降として通る(Gem::Version 比較)
|
|
26
|
+
# [x] 4.0 の DB に rbs_sig が書き込まれ、統計行が出力される
|
|
27
|
+
# [x] --dry-run は統計だけ表示して実 DB には書き込まない
|
|
28
|
+
# [x] --sig-root は core/ と stdlib/ を持つチェックアウトを読める
|
|
29
|
+
# [x] Runner に登録されている(bitclust rbssig として呼べる)
|
|
30
|
+
class TestRbssigCommand < Test::Unit::TestCase
|
|
31
|
+
def setup
|
|
32
|
+
@tmpdir = Dir.mktmpdir
|
|
33
|
+
@sig_dir = File.join(@tmpdir, 'sig')
|
|
34
|
+
FileUtils.mkdir_p(@sig_dir)
|
|
35
|
+
File.write(File.join(@sig_dir, 'foo.rbs'), <<~RBS)
|
|
36
|
+
class Foo
|
|
37
|
+
def bar: (Integer) -> String
|
|
38
|
+
end
|
|
39
|
+
RBS
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def teardown
|
|
43
|
+
FileUtils.rm_rf(@tmpdir)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_needs_no_global_database_option
|
|
47
|
+
cmd = BitClust::Subcommands::RbssigCommand.new
|
|
48
|
+
assert_false cmd.needs_database?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_no_sig_source_is_an_error
|
|
52
|
+
db = build_db('4.0')
|
|
53
|
+
assert_command_error([db])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_requires_exactly_one_dbpath
|
|
57
|
+
db = build_db('4.0')
|
|
58
|
+
assert_command_error(["--sig-dir=#{@sig_dir}"])
|
|
59
|
+
assert_command_error(["--sig-dir=#{@sig_dir}", db, db])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_old_database_version_is_an_error
|
|
63
|
+
db = build_db('3.4')
|
|
64
|
+
assert_command_error(["--sig-dir=#{@sig_dir}", db])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_version_10_passes_the_gate
|
|
68
|
+
db = build_db('10.0')
|
|
69
|
+
out = run_command(["--sig-dir=#{@sig_dir}", db])
|
|
70
|
+
assert_match(/version 10\.0/, out)
|
|
71
|
+
assert_equal [['t', '('], ['c', 'Integer'], ['t', ') -> '], ['c', 'String']],
|
|
72
|
+
find_entry(db, 'bar').rbs_signature_overloads[0]['segments']
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_fills_rbs_sig_and_prints_stats
|
|
76
|
+
db = build_db('4.0')
|
|
77
|
+
out = run_command(["--sig-dir=#{@sig_dir}", db])
|
|
78
|
+
assert_equal '(Integer) -> String',
|
|
79
|
+
find_entry(db, 'bar').rbs_signature_overloads[0]['segments'].map {|_k, t| t }.join
|
|
80
|
+
assert_nil find_entry(db, 'nope').rbs_signature_overloads
|
|
81
|
+
assert_match(
|
|
82
|
+
/\Adb-4\.0 \(version 4\.0\): entries_updated=1 sigs_matched=1 methods_missed=1\n\z/,
|
|
83
|
+
out)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_dry_run_leaves_database_untouched
|
|
87
|
+
db = build_db('4.0')
|
|
88
|
+
out = run_command(["--sig-dir=#{@sig_dir}", db, '--dry-run'])
|
|
89
|
+
assert_nil find_entry(db, 'bar').rbs_signature_overloads
|
|
90
|
+
assert_match(/entries_updated=1/, out)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_sig_root_reads_core_and_stdlib_layout
|
|
94
|
+
root = File.join(@tmpdir, 'rbs-checkout')
|
|
95
|
+
FileUtils.mkdir_p("#{root}/core")
|
|
96
|
+
File.write("#{root}/core/foo.rbs", <<~RBS)
|
|
97
|
+
class Foo
|
|
98
|
+
def bar: (String) -> Integer
|
|
99
|
+
end
|
|
100
|
+
RBS
|
|
101
|
+
# core_root を与えると rbs は stringio を暗黙に要求するので、
|
|
102
|
+
# 本物のチェックアウトと同じく stdlib/ も置く
|
|
103
|
+
FileUtils.mkdir_p("#{root}/stdlib/stringio/0")
|
|
104
|
+
File.write("#{root}/stdlib/stringio/0/stringio.rbs", "class StringIO\nend\n")
|
|
105
|
+
|
|
106
|
+
db = build_db('4.0')
|
|
107
|
+
run_command(["--sig-root=#{root}", db])
|
|
108
|
+
assert_equal '(String) -> Integer',
|
|
109
|
+
find_entry(db, 'bar').rbs_signature_overloads[0]['segments'].map {|_k, t| t }.join
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_registered_in_runner
|
|
113
|
+
require 'bitclust/runner'
|
|
114
|
+
runner = BitClust::Runner.new
|
|
115
|
+
runner.prepare
|
|
116
|
+
subcommands = runner.instance_variable_get(:@subcommands)
|
|
117
|
+
assert_kind_of BitClust::Subcommands::RbssigCommand, subcommands['rbssig']
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def build_db(version)
|
|
123
|
+
root = "#{@tmpdir}/tree-#{version}/refm/api/src"
|
|
124
|
+
FileUtils.mkdir_p("#{root}/_builtin")
|
|
125
|
+
File.write("#{root}/LIBRARIES", "_builtin\n")
|
|
126
|
+
File.write("#{root}/_builtin.rd", <<~RD)
|
|
127
|
+
description
|
|
128
|
+
|
|
129
|
+
= class Foo < Object
|
|
130
|
+
== Instance Methods
|
|
131
|
+
--- bar(val) -> String
|
|
132
|
+
|
|
133
|
+
説明
|
|
134
|
+
|
|
135
|
+
--- nope -> nil
|
|
136
|
+
|
|
137
|
+
説明
|
|
138
|
+
RD
|
|
139
|
+
prefix = "#{@tmpdir}/db-#{version}"
|
|
140
|
+
db = BitClust::MethodDatabase.new(prefix)
|
|
141
|
+
db.init
|
|
142
|
+
db.transaction do
|
|
143
|
+
db.propset('version', version)
|
|
144
|
+
db.propset('encoding', 'utf-8')
|
|
145
|
+
end
|
|
146
|
+
db.transaction do
|
|
147
|
+
db.update_by_stdlibtree(root)
|
|
148
|
+
end
|
|
149
|
+
prefix
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def find_entry(prefix, name)
|
|
153
|
+
db = BitClust::MethodDatabase.new(prefix)
|
|
154
|
+
db.get_class('Foo').entries.find {|m| m.name?(name) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def run_command(argv)
|
|
158
|
+
cmd = BitClust::Subcommands::RbssigCommand.new
|
|
159
|
+
cmd.parse(argv)
|
|
160
|
+
out = StringIO.new
|
|
161
|
+
orig_stdout = $stdout
|
|
162
|
+
$stdout = out
|
|
163
|
+
begin
|
|
164
|
+
cmd.exec(argv, { prefix: nil, capi: false })
|
|
165
|
+
ensure
|
|
166
|
+
$stdout = orig_stdout
|
|
167
|
+
end
|
|
168
|
+
out.string
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def assert_command_error(argv)
|
|
172
|
+
cmd = BitClust::Subcommands::RbssigCommand.new
|
|
173
|
+
cmd.parse(argv)
|
|
174
|
+
assert_raise(SystemExit) do
|
|
175
|
+
capture_stderr { cmd.exec(argv, { prefix: nil, capi: false }) }
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def capture_stderr
|
|
180
|
+
orig = $stderr
|
|
181
|
+
$stderr = StringIO.new
|
|
182
|
+
yield
|
|
183
|
+
ensure
|
|
184
|
+
$stderr = orig
|
|
185
|
+
end
|
|
186
|
+
end
|
data/test/test_rdcompiler.rb
CHANGED
|
@@ -28,9 +28,102 @@ class TestRDCompiler < Test::Unit::TestCase
|
|
|
28
28
|
mock(method_entry).names.any_times{ names }
|
|
29
29
|
mock(method_entry).since_map.any_times{ since_map }
|
|
30
30
|
mock(method_entry).until_map.any_times{ until_map }
|
|
31
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
31
32
|
assert_equal(expected, @c.compile_method(method_entry))
|
|
32
33
|
end
|
|
33
34
|
|
|
35
|
+
# RBS シグネチャ表示: rbs_sig property を持つエントリだけが、メソッド
|
|
36
|
+
# 見出しの <dt> 群の直後・説明 <dd> の直前に 1 オーバーロード 1 行の
|
|
37
|
+
# <dt class="rbs-signature"> を出す(dd だと説明の一部に見える。pre を
|
|
38
|
+
# 使うと script.js が COPY ボタンを付けてしまうので code のみ)。
|
|
39
|
+
# 型名セグメントは DB に存在するクラスだけリンク化し、テキストは
|
|
40
|
+
# エスケープした上で -> を → にする。
|
|
41
|
+
def test_method_with_rbs_signatures_renders_dt_lines
|
|
42
|
+
stub(@db).fetch_class('String') { :exists }
|
|
43
|
+
stub(@db).fetch_class('Integer') { raise BitClust::ClassNotFound, 'Integer' }
|
|
44
|
+
overloads = [
|
|
45
|
+
{'segments' => [['t', '('], ['c', 'Integer'], ['t', ') -> '], ['c', 'String']]},
|
|
46
|
+
{'segments' => [['t', '(String & Integer) -> void']]},
|
|
47
|
+
]
|
|
48
|
+
method_entry = rbs_method_entry("--- index(val) -> Integer\n\n説明\n", overloads)
|
|
49
|
+
|
|
50
|
+
html = @c.compile_method(method_entry)
|
|
51
|
+
assert_include html,
|
|
52
|
+
%Q[(Integer) → <a href="dummy/class/String">String</a>]
|
|
53
|
+
assert_include html, '(String & Integer) → void'
|
|
54
|
+
assert_match(
|
|
55
|
+
%r!</dt>\n(<dt class="rbs-signature"><code>.+</code></dt>\n){2}<dd class="method-description">!,
|
|
56
|
+
html)
|
|
57
|
+
assert_not_match(/<pre|<dd class="rbs-/, html)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 引数パターンでチャンクが分かれるメソッド(Array.new 型)では、各
|
|
61
|
+
# オーバーロードが引数名・アリティ・ブロック有無で最も近いチャンクの
|
|
62
|
+
# 見出し直下にだけ出る(全チャンクへの重複表示をしない)
|
|
63
|
+
def test_method_with_rbs_signatures_assigns_overloads_to_chunks
|
|
64
|
+
src = <<~RD
|
|
65
|
+
--- index(sub) -> Integer | nil
|
|
66
|
+
|
|
67
|
+
文字列で探す。
|
|
68
|
+
|
|
69
|
+
--- index(pattern) {|s| ... } -> Integer | nil
|
|
70
|
+
|
|
71
|
+
ブロックで探す。
|
|
72
|
+
RD
|
|
73
|
+
overloads = [
|
|
74
|
+
{'segments' => [['t', '(String sub) -> Integer?']],
|
|
75
|
+
'params' => ['sub'], 'arity' => [1, 1]},
|
|
76
|
+
{'segments' => [['t', '(Regexp pattern) { (String) -> bool } -> Integer?']],
|
|
77
|
+
'params' => ['pattern'], 'arity' => [1, 1], 'block' => 'req'},
|
|
78
|
+
]
|
|
79
|
+
method_entry = rbs_method_entry(src, overloads)
|
|
80
|
+
|
|
81
|
+
html = @c.compile_method(method_entry)
|
|
82
|
+
assert_match(
|
|
83
|
+
%r!<code>index\(sub\).*?</dt>\n<dt class="rbs-signature"><code>\(String sub\) → Integer\?</code></dt>\n<dd!m,
|
|
84
|
+
html)
|
|
85
|
+
assert_match(
|
|
86
|
+
%r!<code>index\(pattern\).*?</dt>\n<dt class="rbs-signature"><code>\(Regexp pattern\) \{ \(String\) → bool \} → Integer\?</code></dt>\n<dd!m,
|
|
87
|
+
html)
|
|
88
|
+
assert_equal 2, html.scan('rbs-signature').size
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# 旧形式の DB(メタ情報なし)ではチャンク振り分けができないので、
|
|
92
|
+
# 先頭チャンクにまとめて出す(重複はしない)
|
|
93
|
+
def test_method_with_rbs_signatures_without_meta_falls_back_to_first_chunk
|
|
94
|
+
src = <<~RD
|
|
95
|
+
--- index(sub) -> Integer | nil
|
|
96
|
+
|
|
97
|
+
文字列で探す。
|
|
98
|
+
|
|
99
|
+
--- index(pattern) {|s| ... } -> Integer | nil
|
|
100
|
+
|
|
101
|
+
ブロックで探す。
|
|
102
|
+
RD
|
|
103
|
+
overloads = [
|
|
104
|
+
{'segments' => [['t', '(String sub) -> Integer?']]},
|
|
105
|
+
{'segments' => [['t', '(Regexp pattern) { (String) -> bool } -> Integer?']]},
|
|
106
|
+
]
|
|
107
|
+
html = @c.compile_method(rbs_method_entry(src, overloads))
|
|
108
|
+
assert_equal 2, html.scan('rbs-signature').size
|
|
109
|
+
assert_match(
|
|
110
|
+
%r!<code>index\(sub\).*?</dt>\n(<dt class="rbs-signature">.*\n){2}<dd!,
|
|
111
|
+
html)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def rbs_method_entry(src, overloads)
|
|
115
|
+
method_entry = Object.new
|
|
116
|
+
mock(method_entry).source { src }
|
|
117
|
+
mock(method_entry).index_id.any_times { 'dummy' }
|
|
118
|
+
mock(method_entry).defined?.any_times { true }
|
|
119
|
+
mock(method_entry).id.any_times { 'String/i.index._builtin' }
|
|
120
|
+
mock(method_entry).names.any_times { [] }
|
|
121
|
+
mock(method_entry).since_map.any_times { {} }
|
|
122
|
+
mock(method_entry).until_map.any_times { {} }
|
|
123
|
+
mock(method_entry).rbs_signature_overloads.any_times { overloads }
|
|
124
|
+
method_entry
|
|
125
|
+
end
|
|
126
|
+
|
|
34
127
|
# badge のカタログ訳(「Ruby %s から」等)を検証するテストで使う、
|
|
35
128
|
# 日本語カタログを読み込んだ compiler を作る(setup の @c はカタログ無しの
|
|
36
129
|
# C ロケールなので、_() はキーそのまま(英語)を返してしまう)
|
|
@@ -230,6 +323,7 @@ HERE
|
|
|
230
323
|
mock(method_entry).names.any_times { ['mf'] }
|
|
231
324
|
mock(method_entry).since_map.any_times { {} }
|
|
232
325
|
mock(method_entry).until_map.any_times { {} }
|
|
326
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
233
327
|
mock(method_entry).klass.any_times { klass }
|
|
234
328
|
mock(method_entry).display_typemark.any_times { '?.' }
|
|
235
329
|
|
|
@@ -250,6 +344,7 @@ HERE
|
|
|
250
344
|
mock(method_entry).names.any_times { ['mf'] }
|
|
251
345
|
mock(method_entry).since_map.any_times { {} }
|
|
252
346
|
mock(method_entry).until_map.any_times { {} }
|
|
347
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
253
348
|
|
|
254
349
|
html = @c.compile_method(method_entry)
|
|
255
350
|
assert_include(html, '<code>mf</code>')
|
|
@@ -979,7 +1074,7 @@ HERE
|
|
|
979
1074
|
|
|
980
1075
|
data("library root" => ['[[lib:/]]', '<a href="dummy/library/">ライブラリ一覧</a>'],
|
|
981
1076
|
"builtin library" => ['[[lib:_builtin]]', '<a href="dummy/library/_builtin">組み込みライブラリ</a>'],
|
|
982
|
-
"C API root" => ['[[f:/]]', '<a href="dummy/function/"
|
|
1077
|
+
"C API root" => ['[[f:/]]', '<a href="dummy/function/">C API 関数一覧</a>'])
|
|
983
1078
|
def test_bracket_link_with_catalog(data)
|
|
984
1079
|
target, expected = data
|
|
985
1080
|
prefix = File.expand_path('../data/bitclust/catalog', __dir__)
|
|
@@ -1029,6 +1124,7 @@ HERE
|
|
|
1029
1124
|
mock(method_entry).names.any_times { [] }
|
|
1030
1125
|
mock(method_entry).since_map.any_times { {} }
|
|
1031
1126
|
mock(method_entry).until_map.any_times { {} }
|
|
1127
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
1032
1128
|
html = @c.compile_method(method_entry)
|
|
1033
1129
|
assert_not_include(html, 'UNKNOWN_META_INFO')
|
|
1034
1130
|
assert_not_include(html, '{:')
|
|
@@ -1052,6 +1148,7 @@ HERE
|
|
|
1052
1148
|
mock(method_entry).names.any_times { [] }
|
|
1053
1149
|
mock(method_entry).since_map.any_times { {} }
|
|
1054
1150
|
mock(method_entry).until_map.any_times { {} }
|
|
1151
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
1055
1152
|
html = @c.compile_method(method_entry)
|
|
1056
1153
|
assert_not_include(html, '{:')
|
|
1057
1154
|
assert_include(html, 'to_a2')
|
|
@@ -1074,6 +1171,7 @@ HERE
|
|
|
1074
1171
|
mock(method_entry).names.any_times { [] }
|
|
1075
1172
|
mock(method_entry).since_map.any_times { {} }
|
|
1076
1173
|
mock(method_entry).until_map.any_times { {} }
|
|
1174
|
+
mock(method_entry).rbs_signature_overloads.any_times { nil }
|
|
1077
1175
|
html = @c.compile_method(method_entry)
|
|
1078
1176
|
assert_not_include(html, '{:')
|
|
1079
1177
|
assert_include(html, 'このメソッドは定義されていません。')
|
|
@@ -511,3 +511,84 @@ HERE
|
|
|
511
511
|
end
|
|
512
512
|
end
|
|
513
513
|
end
|
|
514
|
+
|
|
515
|
+
class TestSearchIndexSnippet < Test::Unit::TestCase
|
|
516
|
+
# 検索結果の説明文(snippet)。meta description と同じ entry.description
|
|
517
|
+
# (最初の説明段落のプレーンテキスト)を、空でないときだけ持たせる。
|
|
518
|
+
#
|
|
519
|
+
# テストリスト:
|
|
520
|
+
# [x] class/method/special variable の各エントリが snippet を持つ
|
|
521
|
+
# [x] 説明が空のエントリは snippet キー自体を持たない
|
|
522
|
+
# [x] merge は複数版で同一エントリのとき最新版の snippet を採用する
|
|
523
|
+
# [x] 最新版に無いページは収録されている最終版の snippet を保つ
|
|
524
|
+
def setup
|
|
525
|
+
@prefix = 'db_snip'
|
|
526
|
+
@base = 'tree_snip'
|
|
527
|
+
@root = "#{@base}/refm/api/src"
|
|
528
|
+
FileUtils.mkdir_p("#{@root}")
|
|
529
|
+
File.open("#{@root}/LIBRARIES", 'w+') { |f| f.puts '_builtin' }
|
|
530
|
+
File.open("#{@root}/_builtin.rd", 'w+') do |f|
|
|
531
|
+
f.puts <<'HERE'
|
|
532
|
+
description
|
|
533
|
+
|
|
534
|
+
= class Foo < Object
|
|
535
|
+
クラスの説明。
|
|
536
|
+
== Instance Methods
|
|
537
|
+
--- foo
|
|
538
|
+
|
|
539
|
+
メソッドの説明。
|
|
540
|
+
--- undocumented
|
|
541
|
+
= module Kernel
|
|
542
|
+
== Special Variables
|
|
543
|
+
--- $; -> String | nil
|
|
544
|
+
|
|
545
|
+
区切り文字。
|
|
546
|
+
HERE
|
|
547
|
+
end
|
|
548
|
+
@db = BitClust::MethodDatabase.new(@prefix)
|
|
549
|
+
@db.init
|
|
550
|
+
@db.transaction do
|
|
551
|
+
[%w[version 3.4], %w[encoding utf-8]].each { |k, v| @db.propset(k, v) }
|
|
552
|
+
end
|
|
553
|
+
@db.transaction { @db.update_by_stdlibtree(@root) }
|
|
554
|
+
@gen = BitClust::SearchIndexGenerator.new
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def teardown
|
|
558
|
+
FileUtils.rm_r([@prefix, @base], :force => true)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def find_entry(index, full_name)
|
|
562
|
+
index.find { |e| e[:full_name] == full_name }
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def test_snippet_carries_entry_description
|
|
566
|
+
index = @gen.build_index(@db)
|
|
567
|
+
assert_equal 'クラスの説明。', find_entry(index, 'Foo')[:snippet]
|
|
568
|
+
assert_equal 'メソッドの説明。', find_entry(index, 'Foo#foo')[:snippet]
|
|
569
|
+
assert_equal '区切り文字。', find_entry(index, '$;')[:snippet]
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def test_snippet_is_omitted_when_description_is_empty
|
|
573
|
+
index = @gen.build_index(@db)
|
|
574
|
+
e = find_entry(index, 'Foo#undocumented')
|
|
575
|
+
assert_not_nil e
|
|
576
|
+
assert_false e.key?(:snippet)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def test_merge_takes_snippet_from_newest_version
|
|
580
|
+
old_e = { name: 'X', full_name: 'X', type: 'class', path: 'class/-x.html', snippet: '古い説明' }
|
|
581
|
+
new_e = { name: 'X', full_name: 'X', type: 'class', path: 'class/-x.html', snippet: '新しい説明' }
|
|
582
|
+
merged = BitClust::SearchIndexGenerator.merge([['3.4', [new_e]], ['3.3', [old_e]]])
|
|
583
|
+
assert_equal 1, merged.size
|
|
584
|
+
assert_equal '新しい説明', merged.first[:snippet]
|
|
585
|
+
assert_equal %w[3.3 3.4], merged.first[:versions]
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def test_merge_keeps_snippet_of_last_version_having_the_page
|
|
589
|
+
only_old = { name: 'Y', full_name: 'Y', type: 'class', path: 'class/-y.html', snippet: '最終収録版の説明' }
|
|
590
|
+
merged = BitClust::SearchIndexGenerator.merge([['3.4', []], ['3.3', [only_old]]])
|
|
591
|
+
assert_equal '最終収録版の説明', merged.first[:snippet]
|
|
592
|
+
assert_equal %w[3.3], merged.first[:versions]
|
|
593
|
+
end
|
|
594
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'bitclust'
|
|
4
|
+
require 'bitclust/subcommands/setup_command'
|
|
5
|
+
|
|
6
|
+
# setup サブコマンドの既定バージョン。
|
|
7
|
+
#
|
|
8
|
+
# テストリスト:
|
|
9
|
+
# [x] 既定の対象バージョンに teeny が付いていない
|
|
10
|
+
# (#%if (version == "V") や #%version V の等値ゲートは文字列一致なので、
|
|
11
|
+
# teeny 付きの版名で DB を作ると doctree CI・生成済みドキュメントと
|
|
12
|
+
# ゲートの判定がずれる)
|
|
13
|
+
class TestSetupCommand < Test::Unit::TestCase
|
|
14
|
+
def test_default_versions_have_no_teeny
|
|
15
|
+
versions = BitClust::Subcommands::SetupCommand.new.instance_variable_get(:@versions)
|
|
16
|
+
assert_false(versions.empty?)
|
|
17
|
+
versions.each do |version|
|
|
18
|
+
assert_match(/\A\d+\.\d+\z/, version)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -202,3 +202,140 @@ class TestStatichtmlSitemap < Test::Unit::TestCase
|
|
|
202
202
|
end
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
|
+
|
|
206
|
+
class TestStatichtmlMarkdownOutput < Test::Unit::TestCase
|
|
207
|
+
def build_command(markdown_output: true)
|
|
208
|
+
cmd = BitClust::Subcommands::StatichtmlCommand.new
|
|
209
|
+
argv = ["--fs-casesensitive"]
|
|
210
|
+
argv.unshift("--markdown-output") if markdown_output
|
|
211
|
+
cmd.parse(argv)
|
|
212
|
+
cmd.instance_variable_set(:@verbose, false)
|
|
213
|
+
cmd.send(:create_manager_config)
|
|
214
|
+
urlmapper = cmd.instance_variable_get(:@urlmapper)
|
|
215
|
+
urlmapper.bitclust_html_base = '..'
|
|
216
|
+
cmd.instance_variable_set(:@markdown_exporter,
|
|
217
|
+
BitClust::MarkdownExporter.new(urlmapper, '.html'))
|
|
218
|
+
cmd
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def markdown_db
|
|
222
|
+
db = BitClust::MethodDatabase.dummy({ "version" => "3.4" })
|
|
223
|
+
def db.properties = { 'source_format' => 'markdown' }
|
|
224
|
+
db
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_option_defaults_to_false
|
|
228
|
+
cmd = BitClust::Subcommands::StatichtmlCommand.new
|
|
229
|
+
assert_false(cmd.instance_variable_get(:@markdown_output))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_option_is_parsed
|
|
233
|
+
cmd = BitClust::Subcommands::StatichtmlCommand.new
|
|
234
|
+
cmd.parse(["--markdown-output"])
|
|
235
|
+
assert_true(cmd.instance_variable_get(:@markdown_output))
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def test_markdown_path_replaces_html_suffix
|
|
239
|
+
cmd = BitClust::Subcommands::StatichtmlCommand.new
|
|
240
|
+
assert_equal("out/class/ARGF.class.md",
|
|
241
|
+
cmd.send(:markdown_path, Pathname.new("out/class/ARGF.class.html")).to_s)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def test_writes_md_next_to_html_for_markdown_db
|
|
245
|
+
Dir.mktmpdir do |dir|
|
|
246
|
+
cmd = build_command
|
|
247
|
+
db = markdown_db
|
|
248
|
+
entry = BitClust::DocEntry.new(db, "help")
|
|
249
|
+
entry.title = "ヘルプ"
|
|
250
|
+
entry.source = "\n[c:String] を参照。\n"
|
|
251
|
+
path = Pathname.new(dir) + "doc/help.html"
|
|
252
|
+
FileUtils.mkdir_p(path.dirname)
|
|
253
|
+
cmd.send(:create_markdown_file_p, entry, path, db)
|
|
254
|
+
md = File.read(File.join(dir, "doc", "help.md"))
|
|
255
|
+
assert_equal("# ヘルプ\n\n[String](../class/String.md) を参照。\n", md)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def test_skips_md_for_non_markdown_db
|
|
260
|
+
Dir.mktmpdir do |dir|
|
|
261
|
+
cmd = build_command
|
|
262
|
+
db = BitClust::MethodDatabase.dummy({ "version" => "3.4" })
|
|
263
|
+
def db.properties = {}
|
|
264
|
+
entry = BitClust::DocEntry.new(db, "help")
|
|
265
|
+
entry.title = "ヘルプ"
|
|
266
|
+
entry.source = "\n本文\n"
|
|
267
|
+
path = Pathname.new(dir) + "doc/help.html"
|
|
268
|
+
FileUtils.mkdir_p(path.dirname)
|
|
269
|
+
cmd.send(:create_markdown_file_p, entry, path, db)
|
|
270
|
+
assert_false(File.exist?(File.join(dir, "doc", "help.md")))
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def test_warns_when_db_is_not_markdown
|
|
275
|
+
cmd = build_command
|
|
276
|
+
db = BitClust::MethodDatabase.dummy({ "version" => "3.4" })
|
|
277
|
+
def db.properties = {}
|
|
278
|
+
orig_stderr, $stderr = $stderr, StringIO.new
|
|
279
|
+
begin
|
|
280
|
+
cmd.send(:warn_markdown_output_skipped, db, "the method database")
|
|
281
|
+
assert_match(/--markdown-output is ignored/, $stderr.string)
|
|
282
|
+
ensure
|
|
283
|
+
$stderr = orig_stderr
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_no_warning_without_option
|
|
288
|
+
cmd = build_command(markdown_output: false)
|
|
289
|
+
db = BitClust::MethodDatabase.dummy({ "version" => "3.4" })
|
|
290
|
+
def db.properties = {}
|
|
291
|
+
orig_stderr, $stderr = $stderr, StringIO.new
|
|
292
|
+
begin
|
|
293
|
+
cmd.send(:warn_markdown_output_skipped, db, "the method database")
|
|
294
|
+
assert_equal("", $stderr.string)
|
|
295
|
+
ensure
|
|
296
|
+
$stderr = orig_stderr
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
class TestStatichtmlVersionSwitcher < Test::Unit::TestCase
|
|
302
|
+
def build_command(themedir, outputdir)
|
|
303
|
+
cmd = BitClust::Subcommands::StatichtmlCommand.new
|
|
304
|
+
cmd.instance_variable_set(:@manager_config, { :themedir => Pathname.new(themedir) })
|
|
305
|
+
cmd.instance_variable_set(:@outputdir, Pathname.new(outputdir))
|
|
306
|
+
cmd.instance_variable_set(:@verbose, false)
|
|
307
|
+
cmd
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def test_version_switcher_js_is_copied
|
|
311
|
+
Dir.mktmpdir do |dir|
|
|
312
|
+
themedir = File.join(dir, 'theme')
|
|
313
|
+
outputdir = File.join(dir, 'out')
|
|
314
|
+
FileUtils.mkdir_p(File.join(themedir, 'js'))
|
|
315
|
+
FileUtils.mkdir_p(outputdir)
|
|
316
|
+
File.write(File.join(themedir, 'js', 'version_switcher.js'), "// vs\n")
|
|
317
|
+
cmd = build_command(themedir, outputdir)
|
|
318
|
+
cmd.send(:copy_version_switcher_script)
|
|
319
|
+
assert_true(File.file?(File.join(outputdir, 'js', 'version_switcher.js')))
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# An older themedir without the file must not abort the whole build.
|
|
324
|
+
def test_theme_without_version_switcher_js_is_tolerated
|
|
325
|
+
Dir.mktmpdir do |dir|
|
|
326
|
+
themedir = File.join(dir, 'theme')
|
|
327
|
+
outputdir = File.join(dir, 'out')
|
|
328
|
+
FileUtils.mkdir_p(themedir)
|
|
329
|
+
FileUtils.mkdir_p(outputdir)
|
|
330
|
+
cmd = build_command(themedir, outputdir)
|
|
331
|
+
orig_stderr, $stderr = $stderr, StringIO.new
|
|
332
|
+
begin
|
|
333
|
+
assert_nothing_raised { cmd.send(:copy_version_switcher_script) }
|
|
334
|
+
assert_match(/version_switcher\.js not found/, $stderr.string)
|
|
335
|
+
ensure
|
|
336
|
+
$stderr = orig_stderr
|
|
337
|
+
end
|
|
338
|
+
assert_false(File.exist?(File.join(outputdir, 'js', 'version_switcher.js')))
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
data/theme/default/js/run.js
CHANGED
|
@@ -134,6 +134,20 @@ function setupBlock(pre, runner) {
|
|
|
134
134
|
}
|
|
135
135
|
group.prepend(button)
|
|
136
136
|
|
|
137
|
+
// RUN の使い方と制限(ruby.wasm で動かないサンプルがあること等)を
|
|
138
|
+
// 説明するヘルプページの RUN 節への動線。リンク先はページごとに相対
|
|
139
|
+
// URL が変わるため、サーバー側が layout の meta で埋め込んだ値を使う
|
|
140
|
+
const helpUrl = document.querySelector('meta[name="rurema-run-help"]')?.content
|
|
141
|
+
if (helpUrl) {
|
|
142
|
+
const help = document.createElement('a')
|
|
143
|
+
help.className = 'highlight__help-link'
|
|
144
|
+
help.href = helpUrl
|
|
145
|
+
help.textContent = '?'
|
|
146
|
+
help.title = 'RUN ボタンのヘルプ'
|
|
147
|
+
help.setAttribute('aria-label', 'RUN ボタンのヘルプ')
|
|
148
|
+
group.append(help)
|
|
149
|
+
}
|
|
150
|
+
|
|
137
151
|
let output
|
|
138
152
|
let outputTextNode
|
|
139
153
|
const ensureOutput = () => {
|
|
@@ -112,7 +112,9 @@
|
|
|
112
112
|
this.escapeHTML(typeClass) + '">' + this.formatType(r.type) + '</span>';
|
|
113
113
|
}
|
|
114
114
|
html += '</p>';
|
|
115
|
-
|
|
115
|
+
// snippet はインデックス生成側が入れたプレーンテキスト
|
|
116
|
+
// (エントリの説明の最初の段落)。HTML として解釈させない
|
|
117
|
+
if (r.snippet) html += '<div class="search-snippet">' + this.escapeHTML(r.snippet) + '</div>';
|
|
116
118
|
li.innerHTML = html;
|
|
117
119
|
return li;
|
|
118
120
|
};
|
|
@@ -158,7 +158,13 @@
|
|
|
158
158
|
html += '<span class="search-type search-type-' +
|
|
159
159
|
this.escapeHTML(typeClass) + '">' + this.formatType(r.type) + '</span>';
|
|
160
160
|
}
|
|
161
|
-
html += '</p
|
|
161
|
+
html += '</p>';
|
|
162
|
+
// snippet はインデックス生成側が入れたプレーンテキスト
|
|
163
|
+
// (エントリの説明の最初の段落)。HTML として解釈させない
|
|
164
|
+
if (r.snippet) {
|
|
165
|
+
html += '<div class="search-snippet">' + this.escapeHTML(r.snippet) + '</div>';
|
|
166
|
+
}
|
|
167
|
+
html += '<p class="search-versions">';
|
|
162
168
|
for (var i = 0; i < versions.length; i++) {
|
|
163
169
|
html += '<a href="' + this.escapeHTML(versionHref(versions[i], r.path)) +
|
|
164
170
|
'">' + this.escapeHTML(versions[i]) + '</a>';
|