bitclust-core 1.6.1 → 1.8.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/class-index +0 -1
- data/data/bitclust/template/function-index +1 -0
- data/data/bitclust/template/layout +1 -0
- data/data/bitclust/template.epub/class +5 -5
- data/data/bitclust/template.epub/class-index +0 -1
- data/data/bitclust/template.epub/contents +7 -4
- data/data/bitclust/template.epub/layout +4 -4
- data/data/bitclust/template.offline/class-index +0 -1
- 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/generators/epub.rb +69 -5
- data/lib/bitclust/irb.rb +74 -0
- data/lib/bitclust/markdown_bridge.rb +11 -6
- data/lib/bitclust/markdown_exporter.rb +311 -0
- data/lib/bitclust/markdown_tree.rb +118 -11
- data/lib/bitclust/mdcompiler.rb +15 -1
- data/lib/bitclust/mdparser.rb +42 -1
- data/lib/bitclust/methoddatabase.rb +17 -4
- data/lib/bitclust/methodentry.rb +24 -0
- data/lib/bitclust/methodsignature.rb +1 -1
- data/lib/bitclust/nameutils.rb +13 -0
- data/lib/bitclust/preprocessor.rb +4 -37
- 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/remote.rb +405 -0
- data/lib/bitclust/runner.rb +7 -10
- data/lib/bitclust/screen.rb +6 -1
- data/lib/bitclust/search_index_generator.rb +28 -10
- data/lib/bitclust/searcher.rb +54 -25
- data/lib/bitclust/subcommands/chm_command.rb +1 -1
- data/lib/bitclust/subcommands/epub_command.rb +2 -2
- data/lib/bitclust/subcommands/extract_command.rb +7 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +7 -0
- data/lib/bitclust/subcommands/methods_command.rb +5 -28
- data/lib/bitclust/subcommands/query_command.rb +1 -0
- data/lib/bitclust/subcommands/rbssig_command.rb +99 -0
- data/lib/bitclust/subcommands/server_command.rb +21 -10
- data/lib/bitclust/subcommands/setup_command.rb +32 -11
- data/lib/bitclust/subcommands/statichtml_command.rb +59 -0
- data/lib/bitclust/syntax_highlighter.rb +65 -4
- data/lib/bitclust/user_dirs.rb +62 -0
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust-irb.rb +2 -0
- data/test/test_chm_command.rb +22 -0
- data/test/test_epub_command.rb +60 -0
- data/test/test_epub_generator.rb +330 -0
- data/test/test_extract_command.rb +45 -0
- data/test/test_irb_plugin.rb +212 -0
- data/test/test_link_checker.rb +1 -1
- data/test/test_markdown_bridge.rb +35 -0
- data/test/test_markdown_exporter.rb +260 -0
- data/test/test_markdown_tree.rb +144 -0
- data/test/test_mdcompiler.rb +47 -5
- data/test/test_mdparser.rb +223 -4
- data/test/test_methodentry.rb +111 -0
- data/test/test_methods_command.rb +32 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_opensearchdescription_screen.rb +39 -0
- data/test/test_preprocessor.rb +26 -0
- data/test/test_query_command.rb +30 -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_remote.rb +374 -0
- data/test/test_runner.rb +61 -1
- data/test/test_search_index_generator.rb +81 -0
- data/test/test_searcher.rb +63 -0
- data/test/test_server_command.rb +41 -0
- data/test/test_setup_command.rb +164 -0
- data/test/test_statichtml_command.rb +137 -0
- data/test/test_syntax_highlighter.rb +124 -0
- data/test/test_user_dirs.rb +62 -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 +71 -2
- data/lib/bitclust/ridatabase.rb +0 -359
|
@@ -161,9 +161,6 @@ module BitClust
|
|
|
161
161
|
data << "<span class=\"nf\">#{token}</span>"
|
|
162
162
|
when @stack.last == :embexpr
|
|
163
163
|
data << "<span class=\"n\">#{token}</span>"
|
|
164
|
-
when @stack.last == :heredoc
|
|
165
|
-
style = COLORS[:heredoc_beg]
|
|
166
|
-
data << "<span class=\"#{style}\">#{token}"
|
|
167
164
|
when @stack.last == :method_call
|
|
168
165
|
data << "<span class=\"nf\">#{token}</span>"
|
|
169
166
|
@stack.pop
|
|
@@ -192,6 +189,48 @@ module BitClust
|
|
|
192
189
|
data
|
|
193
190
|
end
|
|
194
191
|
|
|
192
|
+
# on_symbeg が開いた <span class="ss"> を、シンボルの末尾トークン
|
|
193
|
+
# (HTML エスケープ済み)を添えて閉じ、@stack の :symbol を外す。
|
|
194
|
+
# on_ident/on_const/on_kw は自前で閉じるので、それ以外の
|
|
195
|
+
# 末尾になりうるトークン(ivar/cvar/gvar/backtick/op/文字列終端)から呼ぶ
|
|
196
|
+
def close_symbol_span(token, data)
|
|
197
|
+
data << "#{escape_html(token)}</span>"
|
|
198
|
+
@stack.pop
|
|
199
|
+
data
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def on_ivar(token, data)
|
|
203
|
+
if @stack.last == :symbol
|
|
204
|
+
close_symbol_span(token, data)
|
|
205
|
+
else
|
|
206
|
+
on_default(:on_ivar, token, data)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def on_cvar(token, data)
|
|
211
|
+
if @stack.last == :symbol
|
|
212
|
+
close_symbol_span(token, data)
|
|
213
|
+
else
|
|
214
|
+
on_default(:on_cvar, token, data)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def on_gvar(token, data)
|
|
219
|
+
if @stack.last == :symbol
|
|
220
|
+
close_symbol_span(token, data)
|
|
221
|
+
else
|
|
222
|
+
on_default(:on_gvar, token, data)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def on_backtick(token, data)
|
|
227
|
+
if @stack.last == :symbol
|
|
228
|
+
close_symbol_span(token, data)
|
|
229
|
+
else
|
|
230
|
+
on_default(:on_backtick, token, data)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
195
234
|
def on_kw(token, data)
|
|
196
235
|
case
|
|
197
236
|
when @stack.last == :symbol
|
|
@@ -221,6 +260,8 @@ module BitClust
|
|
|
221
260
|
|
|
222
261
|
def on_op(token, data)
|
|
223
262
|
case
|
|
263
|
+
when @stack.last == :symbol
|
|
264
|
+
close_symbol_span(token, data)
|
|
224
265
|
when token == "::" && [:class, :module].include?(_ = @stack.last)
|
|
225
266
|
@name_buffer << token
|
|
226
267
|
when token == "<<" && @stack.last == :class
|
|
@@ -312,7 +353,7 @@ module BitClust
|
|
|
312
353
|
|
|
313
354
|
def on_symbeg(token, data)
|
|
314
355
|
style = COLORS[:symbeg]
|
|
315
|
-
data << "<span class=\"#{style}\">#{token}"
|
|
356
|
+
data << "<span class=\"#{style}\">#{escape_html(token)}"
|
|
316
357
|
@stack << :symbol
|
|
317
358
|
data
|
|
318
359
|
end
|
|
@@ -336,14 +377,34 @@ module BitClust
|
|
|
336
377
|
data << escape_html(token)
|
|
337
378
|
when @stack.last == :string2
|
|
338
379
|
data << "<span class=\"s2\">#{escape_html(token)}</span>"
|
|
380
|
+
when @stack.last == :symbol
|
|
381
|
+
data << escape_html(token)
|
|
339
382
|
else
|
|
340
383
|
on_default(:on_tstring_content, token, data)
|
|
341
384
|
end
|
|
342
385
|
data
|
|
343
386
|
end
|
|
344
387
|
|
|
388
|
+
# "a": / 'b': 形式のシンボルキー。開いている文字列の span を
|
|
389
|
+
# 文字列終端と同じ流儀で閉じる
|
|
390
|
+
def on_label_end(token, data)
|
|
391
|
+
case @stack.last
|
|
392
|
+
when :string1
|
|
393
|
+
data << "#{escape_html(token)}</span>"
|
|
394
|
+
@stack.pop
|
|
395
|
+
when :string2
|
|
396
|
+
data << "<span class=\"s2\">#{escape_html(token)}</span>"
|
|
397
|
+
@stack.pop
|
|
398
|
+
else
|
|
399
|
+
on_default(:on_label_end, token, data)
|
|
400
|
+
end
|
|
401
|
+
data
|
|
402
|
+
end
|
|
403
|
+
|
|
345
404
|
def on_tstring_end(token, data)
|
|
346
405
|
case
|
|
406
|
+
when @stack.last == :symbol
|
|
407
|
+
data << "#{escape_html(token)}</span>"
|
|
347
408
|
when token == "'"
|
|
348
409
|
data << "#{token}</span>"
|
|
349
410
|
when %i[qwords words qsymbols symbols].include?(_ = @stack.last)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# ユーザーごとの設定ファイル・DB・キャッシュの置き場所。
|
|
4
|
+
# XDG Base Directory(XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_CACHE_HOME、
|
|
5
|
+
# 未設定なら ~/.config ~/.local/share ~/.cache)配下の bitclust/ を使い、
|
|
6
|
+
# 従来の ~/.bitclust は互換のため読み取りの fallback として残す。
|
|
7
|
+
|
|
8
|
+
require 'pathname'
|
|
9
|
+
require 'yaml'
|
|
10
|
+
|
|
11
|
+
module BitClust
|
|
12
|
+
module UserDirs
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def home
|
|
16
|
+
Pathname(ENV.fetch('HOME')).expand_path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# 従来の置き場所(bitclust setup が 1.7 まで作っていた場所)
|
|
20
|
+
def legacy_dir
|
|
21
|
+
home + '.bitclust'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def config_home
|
|
25
|
+
xdg_dir('XDG_CONFIG_HOME', '.config')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def data_home
|
|
29
|
+
xdg_dir('XDG_DATA_HOME', File.join('.local', 'share'))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def cache_home
|
|
33
|
+
xdg_dir('XDG_CACHE_HOME', '.cache')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 設定ファイルの候補(優先順)
|
|
37
|
+
def config_candidates
|
|
38
|
+
[config_home + 'config', legacy_dir + 'config']
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 存在する設定ファイル。XDG の場所を先に見て、無ければ従来の
|
|
42
|
+
# ~/.bitclust/config。どちらも無ければ nil
|
|
43
|
+
def config_file
|
|
44
|
+
config_candidates.find(&:exist?)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 設定ファイルを読む。無ければ nil
|
|
48
|
+
def load_config
|
|
49
|
+
path = config_file
|
|
50
|
+
path ? YAML.load_file(path) : nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 環境変数が絶対パスならそれを、無ければ HOME 直下の既定を使う
|
|
54
|
+
# (XDG の仕様では相対パスの指定は無視する)
|
|
55
|
+
def xdg_dir(env_key, default_subdir)
|
|
56
|
+
value = ENV[env_key]
|
|
57
|
+
base = (value && Pathname(value).absolute?) ? Pathname(value) : home + default_subdir
|
|
58
|
+
base + 'bitclust'
|
|
59
|
+
end
|
|
60
|
+
private_class_method :xdg_dir
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/bitclust/version.rb
CHANGED
data/lib/bitclust-irb.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'bitclust'
|
|
3
|
+
require 'bitclust/subcommands/chm_command'
|
|
4
|
+
|
|
5
|
+
class TestChmSitemap < Test::Unit::TestCase
|
|
6
|
+
def test_to_html_builds_nested_sitemap
|
|
7
|
+
root = BitClust::Subcommands::ChmCommand::Sitemap.new('Ruby', 'doc/index.html')
|
|
8
|
+
child = BitClust::Subcommands::ChmCommand::Sitemap::Content.new('Array <=> & "q"', 'class/-array.html')
|
|
9
|
+
root << child
|
|
10
|
+
html = root.to_html
|
|
11
|
+
assert_match(%r{<param name="Name" value="Ruby">}, html)
|
|
12
|
+
assert_match(%r{<param name="Local" value="doc/index.html">}, html)
|
|
13
|
+
assert_match(%r{<UL>.*Array <=> & "q".*</UL>}m, html)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_to_html_without_children_has_no_ul
|
|
17
|
+
leaf = BitClust::Subcommands::ChmCommand::Sitemap::Content.new('Leaf')
|
|
18
|
+
html = leaf.to_html
|
|
19
|
+
assert_not_match(/<UL>/, html)
|
|
20
|
+
assert_not_match(/Local/, html)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'test/unit/rr'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'bitclust'
|
|
6
|
+
require 'bitclust/subcommands/epub_command'
|
|
7
|
+
require 'bitclust/generators/epub'
|
|
8
|
+
|
|
9
|
+
# `bitclust epub` は常に失敗していた:
|
|
10
|
+
#
|
|
11
|
+
# 1) --catalog=PATH は @catalogdir に保存されるが、exec は(定義されていない
|
|
12
|
+
# ため常に nil の)@catalog をジェネレータへ渡していた。ジェネレータは
|
|
13
|
+
# それを statichtml へ --catalog=(空文字列)として渡し、
|
|
14
|
+
# Pathname.new("").realpath が Errno::ENOENT で失敗する。
|
|
15
|
+
# 2) @catalogdir の既定値が nil だったので、--catalog を指定しない限り
|
|
16
|
+
# `bitclust epub -o DIR` は必ず失敗していた
|
|
17
|
+
# (chm_command / statichtml_command と同じく、既定は同梱の
|
|
18
|
+
# data/bitclust/catalog にすべき)。
|
|
19
|
+
class TestEPUBCommandCatalog < Test::Unit::TestCase
|
|
20
|
+
def test_default_catalogdir_is_the_bundled_catalog
|
|
21
|
+
cmd = BitClust::Subcommands::EPUBCommand.new
|
|
22
|
+
cmd.parse([])
|
|
23
|
+
expected = cmd.srcdir_root + "data/bitclust/catalog"
|
|
24
|
+
assert_equal(expected, cmd.instance_variable_get(:@catalogdir))
|
|
25
|
+
assert_true(expected.directory?, "#{expected} must exist so --catalog can be omitted")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_catalog_option_overrides_the_default
|
|
29
|
+
Dir.mktmpdir do |dir|
|
|
30
|
+
cmd = BitClust::Subcommands::EPUBCommand.new
|
|
31
|
+
cmd.parse(["--catalog=#{dir}"])
|
|
32
|
+
assert_equal(Pathname.new(dir).realpath, cmd.instance_variable_get(:@catalogdir))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def capture_generator_options(cmd)
|
|
37
|
+
captured = nil
|
|
38
|
+
fake_generator = Object.new
|
|
39
|
+
stub(fake_generator).generate { nil }
|
|
40
|
+
stub(BitClust::Generators::EPUB).new {|opts| captured = opts; fake_generator }
|
|
41
|
+
cmd.exec([], { :prefix => nil, :capi => false })
|
|
42
|
+
captured
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_exec_passes_the_default_catalogdir_to_the_generator
|
|
46
|
+
cmd = BitClust::Subcommands::EPUBCommand.new
|
|
47
|
+
cmd.parse([])
|
|
48
|
+
opts = capture_generator_options(cmd)
|
|
49
|
+
assert_equal(cmd.instance_variable_get(:@catalogdir), opts[:catalog])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_exec_passes_the_overridden_catalogdir_to_the_generator
|
|
53
|
+
Dir.mktmpdir do |dir|
|
|
54
|
+
cmd = BitClust::Subcommands::EPUBCommand.new
|
|
55
|
+
cmd.parse(["--catalog=#{dir}"])
|
|
56
|
+
opts = capture_generator_options(cmd)
|
|
57
|
+
assert_equal(Pathname.new(dir).realpath, opts[:catalog])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'rexml/document'
|
|
6
|
+
require 'bitclust'
|
|
7
|
+
require 'bitclust/generators/epub'
|
|
8
|
+
require 'bitclust/screen'
|
|
9
|
+
require 'bitclust/subcommands/statichtml_command'
|
|
10
|
+
|
|
11
|
+
# contents.opf のマニフェストは、statichtml が OEBPS 以下に生成する
|
|
12
|
+
# class/method/library/doc/function ページと css/image リソースを
|
|
13
|
+
# すべて(ユニークな id で)列挙しなければならない(EPUB の仕様上必須)。
|
|
14
|
+
class TestEpubGeneratorContentsOpf < Test::Unit::TestCase
|
|
15
|
+
def build_epub_generator
|
|
16
|
+
datadir = File.expand_path('../data/bitclust', __dir__)
|
|
17
|
+
BitClust::Generators::EPUB.new(:templatedir => Pathname.new("#{datadir}/template.epub"))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def setup
|
|
21
|
+
@dir = Dir.mktmpdir
|
|
22
|
+
@epub_directory = Pathname.new(@dir)
|
|
23
|
+
@oebps = @epub_directory + "OEBPS"
|
|
24
|
+
FileUtils.mkdir_p(@oebps + "class")
|
|
25
|
+
FileUtils.mkdir_p(@oebps + "method/-array/i")
|
|
26
|
+
FileUtils.mkdir_p(@oebps + "doc")
|
|
27
|
+
FileUtils.mkdir_p(@oebps + "library")
|
|
28
|
+
FileUtils.mkdir_p(@oebps + "function")
|
|
29
|
+
FileUtils.mkdir_p(@oebps + "images")
|
|
30
|
+
|
|
31
|
+
File.write(@oebps + "class/-array.xhtml", "<html/>")
|
|
32
|
+
File.write(@oebps + "class/index.xhtml", "<html/>") # クラス一覧ページ
|
|
33
|
+
File.write(@oebps + "method/-array/i/each.xhtml", "<html/>")
|
|
34
|
+
File.write(@oebps + "doc/index.xhtml", "<html/>") # マニュアルのトップページ
|
|
35
|
+
File.write(@oebps + "doc/help.xhtml", "<html/>")
|
|
36
|
+
File.write(@oebps + "library/index.xhtml", "<html/>")
|
|
37
|
+
File.write(@oebps + "library/json.xhtml", "<html/>")
|
|
38
|
+
File.write(@oebps + "function/index.xhtml", "<html/>")
|
|
39
|
+
File.write(@oebps + "function/rb_ary_new.xhtml", "<html/>")
|
|
40
|
+
File.write(@oebps + "style.css", "body{}")
|
|
41
|
+
File.write(@oebps + "images/rurema.png", "\x89PNG\r\n")
|
|
42
|
+
File.write(@epub_directory + "nav.xhtml", "<html/>")
|
|
43
|
+
|
|
44
|
+
@generator = build_epub_generator
|
|
45
|
+
@generator.send(:generate_contents_opf, @epub_directory)
|
|
46
|
+
@opf_path = @epub_directory + "contents.opf"
|
|
47
|
+
@xml = File.read(@opf_path)
|
|
48
|
+
@doc = REXML::Document.new(@xml)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def teardown
|
|
52
|
+
FileUtils.rm_rf(@dir)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def items
|
|
56
|
+
REXML::XPath.match(@doc, "//manifest/item")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_contents_opf_is_well_formed_xml
|
|
60
|
+
assert_nothing_raised { REXML::Document.new(@xml) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_manifest_lists_every_xhtml_page_under_oebps
|
|
64
|
+
hrefs = items.map {|item| item.attributes['href'] }
|
|
65
|
+
%w[
|
|
66
|
+
OEBPS/class/-array.xhtml
|
|
67
|
+
OEBPS/class/index.xhtml
|
|
68
|
+
OEBPS/method/-array/i/each.xhtml
|
|
69
|
+
OEBPS/doc/index.xhtml
|
|
70
|
+
OEBPS/doc/help.xhtml
|
|
71
|
+
OEBPS/library/index.xhtml
|
|
72
|
+
OEBPS/library/json.xhtml
|
|
73
|
+
OEBPS/function/index.xhtml
|
|
74
|
+
OEBPS/function/rb_ary_new.xhtml
|
|
75
|
+
nav.xhtml
|
|
76
|
+
].each do |href|
|
|
77
|
+
assert_include(hrefs, href)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_manifest_lists_non_xhtml_resources_that_exist
|
|
82
|
+
hrefs = items.map {|item| item.attributes['href'] }
|
|
83
|
+
assert_include(hrefs, "OEBPS/style.css")
|
|
84
|
+
assert_include(hrefs, "OEBPS/images/rurema.png")
|
|
85
|
+
style_item = items.find {|item| item.attributes['href'] == "OEBPS/style.css" }
|
|
86
|
+
assert_equal("text/css", style_item.attributes['media-type'])
|
|
87
|
+
image_item = items.find {|item| item.attributes['href'] == "OEBPS/images/rurema.png" }
|
|
88
|
+
assert_equal("image/png", image_item.attributes['media-type'])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_manifest_item_ids_are_unique
|
|
92
|
+
ids = items.map {|item| item.attributes['id'] }
|
|
93
|
+
assert_equal(ids.size, ids.uniq.size, "duplicate manifest item ids: #{ids}")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_manifest_item_ids_are_valid_xml_ncnames
|
|
97
|
+
ids = items.map {|item| item.attributes['id'] }
|
|
98
|
+
assert_false(ids.empty?)
|
|
99
|
+
ids.each do |id|
|
|
100
|
+
assert_match(/\A[A-Za-z_][A-Za-z0-9_.-]*\z/, id, "not a valid NCName: #{id.inspect}")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# doc/index.xhtml はマニフェスト中に決め打ちの id="index" として
|
|
105
|
+
# 一度だけ登場し、doc 以下を走査する側から重複登録されない。
|
|
106
|
+
def test_doc_index_is_listed_exactly_once_with_the_index_id
|
|
107
|
+
index_items = items.select {|item| item.attributes['href'] == "OEBPS/doc/index.xhtml" }
|
|
108
|
+
assert_equal(1, index_items.size)
|
|
109
|
+
assert_equal("index", index_items.first.attributes['id'])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_manifest_references_xhtml_not_html_for_the_index
|
|
113
|
+
hrefs = items.map {|item| item.attributes['href'] }
|
|
114
|
+
assert_not_include(hrefs, "OEBPS/doc/index.html")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 目次ページ (nav) ・マニュアルのトップページ・クラスページだけが
|
|
118
|
+
# spine(読み進める順序)に載る。約 11,700 あるメソッドページなどを
|
|
119
|
+
# spine に加えると読書順序が壊れるので、マニフェストには載るが spine
|
|
120
|
+
# には載らない。
|
|
121
|
+
def test_spine_still_excludes_method_library_doc_and_function_pages
|
|
122
|
+
spine_idrefs = REXML::XPath.match(@doc, "//spine/itemref").map {|el| el.attributes['idref'] }
|
|
123
|
+
assert_equal(%w[toc index], spine_idrefs.first(2))
|
|
124
|
+
|
|
125
|
+
class_item_ids = items.select {|item| item.attributes['href'].to_s.start_with?('OEBPS/class/') }
|
|
126
|
+
.map {|item| item.attributes['id'] }
|
|
127
|
+
assert_equal(%w[toc index] + class_item_ids, spine_idrefs)
|
|
128
|
+
|
|
129
|
+
other_ids = items.map {|item| item.attributes['id'] } - %w[toc index] - class_item_ids
|
|
130
|
+
assert_false(other_ids.empty?)
|
|
131
|
+
other_ids.each do |id|
|
|
132
|
+
assert_not_include(spine_idrefs, id)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# EPUB のパッケージ(contents.opf)に載る前に、statichtml が出力ルートへ
|
|
138
|
+
# 書く 2 行のリダイレクトスタブ(<meta http-equiv=refresh> + <a>、
|
|
139
|
+
# ルート要素を持たず XML として整形式でない)を消す。
|
|
140
|
+
class TestEpubGeneratorIndexStub < Test::Unit::TestCase
|
|
141
|
+
def build_epub_generator
|
|
142
|
+
datadir = File.expand_path('../data/bitclust', __dir__)
|
|
143
|
+
BitClust::Generators::EPUB.new(:templatedir => Pathname.new("#{datadir}/template.epub"))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def setup
|
|
147
|
+
@dir = Dir.mktmpdir
|
|
148
|
+
@contents_directory = Pathname.new(@dir) + "OEBPS"
|
|
149
|
+
FileUtils.mkdir_p(@contents_directory)
|
|
150
|
+
@generator = build_epub_generator
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def teardown
|
|
154
|
+
FileUtils.rm_rf(@dir)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_removes_the_redirect_stub_statichtml_writes_at_the_root
|
|
158
|
+
stub_path = @contents_directory + "index.xhtml"
|
|
159
|
+
File.write(stub_path, "<meta http-equiv=\"refresh\" content=\"0; URL=doc/index.xhtml\">\n<a href=\"doc/index.xhtml\">Go</a>\n")
|
|
160
|
+
assert_raise(REXML::ParseException) { REXML::Document.new(File.read(stub_path)) }
|
|
161
|
+
|
|
162
|
+
@generator.send(:remove_index_stub, @contents_directory)
|
|
163
|
+
|
|
164
|
+
assert_false(stub_path.exist?)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_is_a_noop_when_the_stub_is_absent
|
|
168
|
+
stub_path = @contents_directory + "index.xhtml"
|
|
169
|
+
assert_nothing_raised { @generator.send(:remove_index_stub, @contents_directory) }
|
|
170
|
+
assert_false(stub_path.exist?)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# doc/index.xhtml (マニュアルのトップページ) と紛れないことを確認する
|
|
174
|
+
def test_does_not_touch_the_doc_index_page
|
|
175
|
+
doc_dir = @contents_directory + "doc"
|
|
176
|
+
FileUtils.mkdir_p(doc_dir)
|
|
177
|
+
doc_index = doc_dir + "index.xhtml"
|
|
178
|
+
File.write(doc_index, "<html/>")
|
|
179
|
+
@generator.send(:remove_index_stub, @contents_directory)
|
|
180
|
+
assert_true(doc_index.exist?)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# data/bitclust/template.epub が整形式 XHTML を出力すること(layout の
|
|
185
|
+
# <meta>/<link> の自己終了、class のインデックス一覧での "<=>" や "&" を
|
|
186
|
+
# 含むメソッド名のエスケープ)を検証する。
|
|
187
|
+
#
|
|
188
|
+
# Screen#run_template は ERB 化したテンプレート本体をレシーバの
|
|
189
|
+
# self.class(ClassScreen / MethodScreen そのもの)に「respond_to? なら
|
|
190
|
+
# コンパイルしない」というガード付きでキャッシュする。同一プロセス内で
|
|
191
|
+
# template.offline 用の別テスト(例: test_class_screen.rb)が先に走ると、
|
|
192
|
+
# そちらのコンパイル結果を再利用してしまい、このテストが実際には
|
|
193
|
+
# template.epub を検証できなくなる(test_method_screen.rb に既知の注記
|
|
194
|
+
# あり)。
|
|
195
|
+
#
|
|
196
|
+
# 専用のサブクラスに分けるだけでは防げない: respond_to? は継承した
|
|
197
|
+
# メソッドにも true を返すので、スーパークラス(本物の ClassScreen /
|
|
198
|
+
# MethodScreen)側で他のテストが先に class_template 等をコンパイル済みだと、
|
|
199
|
+
# このサブクラスの run_template はガードに阻まれて自分の
|
|
200
|
+
# @template_repository(= template.epub)で再コンパイルしない。
|
|
201
|
+
# そこで、最初の render より前に明示的に def_method して
|
|
202
|
+
# class_template / method_template / layout をこのサブクラス自身に
|
|
203
|
+
# 直接定義しておく。直接定義されたメソッドは継承したものより優先されるので、
|
|
204
|
+
# 他のテストの実行順序に関係なく template.epub の内容を使わせられる。
|
|
205
|
+
class EpubTestClassScreen < BitClust::ClassScreen; end
|
|
206
|
+
class EpubTestMethodScreen < BitClust::MethodScreen; end
|
|
207
|
+
class EpubTestClassIndexScreen < BitClust::ClassIndexScreen; end
|
|
208
|
+
|
|
209
|
+
epub_template_repository = BitClust::TemplateRepository.new(
|
|
210
|
+
"#{File.expand_path('../data/bitclust', __dir__)}/template.epub"
|
|
211
|
+
)
|
|
212
|
+
[
|
|
213
|
+
[EpubTestClassScreen, 'class', 'class_template'],
|
|
214
|
+
[EpubTestMethodScreen, 'method', 'method_template'],
|
|
215
|
+
[EpubTestClassIndexScreen, 'class-index', 'class_index_template'],
|
|
216
|
+
].each do |klass, id, method_name|
|
|
217
|
+
ERB.new(epub_template_repository.load(id)).def_method(klass, method_name, "#{id}.erb")
|
|
218
|
+
end
|
|
219
|
+
[EpubTestClassScreen, EpubTestMethodScreen, EpubTestClassIndexScreen].each do |klass|
|
|
220
|
+
ERB.new(epub_template_repository.load('layout')).def_method(klass, 'layout', 'layout.erb')
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class TestEpubTemplateWellFormedness < Test::Unit::TestCase
|
|
224
|
+
SRC = <<'HERE'
|
|
225
|
+
= module Helper
|
|
226
|
+
比較演算子を提供するヘルパーモジュール
|
|
227
|
+
= class Spaceship < Object
|
|
228
|
+
extend Helper
|
|
229
|
+
比較演算子とビット演算子を持つサンプルクラス
|
|
230
|
+
== Instance Methods
|
|
231
|
+
--- <=>(other) -> Integer
|
|
232
|
+
|
|
233
|
+
比較する。
|
|
234
|
+
|
|
235
|
+
--- &(other) -> Spaceship
|
|
236
|
+
|
|
237
|
+
論理積を返す。
|
|
238
|
+
HERE
|
|
239
|
+
|
|
240
|
+
def setup
|
|
241
|
+
datadir = File.expand_path('../data/bitclust', __dir__)
|
|
242
|
+
@lib, @db = BitClust::RRDParser.parse(SRC, 'testlib', { 'version' => '3.4' })
|
|
243
|
+
urlmapper = BitClust::Subcommands::StatichtmlCommand::URLMapperEx.new(
|
|
244
|
+
:suffix => '.xhtml',
|
|
245
|
+
:css_url => 'style.css',
|
|
246
|
+
:favicon_url => 'rurema.png'
|
|
247
|
+
)
|
|
248
|
+
urlmapper.bitclust_html_base = '..'
|
|
249
|
+
@manager = BitClust::ScreenManager.new(
|
|
250
|
+
:templatedir => "#{datadir}/template.epub",
|
|
251
|
+
:catalogdir => "#{datadir}/catalog",
|
|
252
|
+
:encoding => 'utf-8',
|
|
253
|
+
:default_encoding => 'utf-8',
|
|
254
|
+
:urlmapper => urlmapper,
|
|
255
|
+
:target_version => '3.4'
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def class_entry
|
|
260
|
+
@lib.fetch_class('Spaceship')
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def method_entry(name)
|
|
264
|
+
class_entry.fetch_method(BitClust::MethodSpec.parse("Spaceship##{name}"))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def render_class_page
|
|
268
|
+
@manager.send(:new_screen, EpubTestClassScreen, class_entry, :database => @db).body
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def render_method_page(name)
|
|
272
|
+
@manager.send(:new_screen, EpubTestMethodScreen, [method_entry(name)], :database => @db).body
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def render_class_index_page
|
|
276
|
+
@manager.send(:new_screen, EpubTestClassIndexScreen, [class_entry], :database => @db).body
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def test_class_page_escapes_spaceship_operator_in_the_index_list
|
|
280
|
+
html = render_class_page
|
|
281
|
+
assert_include(html, '<=>')
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def test_class_page_escapes_ampersand_operator_in_the_index_list
|
|
285
|
+
html = render_class_page
|
|
286
|
+
# "&" 単体がエスケープされて "&" として出る(リンクテキストの終端)
|
|
287
|
+
assert_match(/&<\/a>/, html)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def test_class_page_is_well_formed_xml
|
|
291
|
+
html = render_class_page
|
|
292
|
+
assert_nothing_raised { REXML::Document.new(html) }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def test_method_page_for_spaceship_operator_is_well_formed_xml
|
|
296
|
+
html = render_method_page('<=>')
|
|
297
|
+
assert_nothing_raised { REXML::Document.new(html) }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def test_method_page_for_ampersand_operator_is_well_formed_xml
|
|
301
|
+
html = render_method_page('&')
|
|
302
|
+
assert_nothing_raised { REXML::Document.new(html) }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def test_layout_void_elements_are_self_closing
|
|
306
|
+
html = render_class_page
|
|
307
|
+
assert_not_match(%r{<meta[^>]*[^/]>}, html)
|
|
308
|
+
assert_not_match(%r{<link[^>]*[^/]>}, html)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# template.epub/class の "extend:" 行も <br>(非自己終了)を出しており、
|
|
312
|
+
# そのクラスページ全体が整形式 XML にならなかった(実データベースの
|
|
313
|
+
# Array・CSV クラスで再現した。どちらも extend/aliases 行を持つ)。
|
|
314
|
+
def test_class_page_extend_line_uses_a_self_closing_br
|
|
315
|
+
html = render_class_page
|
|
316
|
+
assert_include(html, 'extend: ')
|
|
317
|
+
assert_not_match(%r{<br[^>]*[^/]>}, html)
|
|
318
|
+
assert_nothing_raised { REXML::Document.new(html) }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# template.epub/class-index には対応する <ul> の無い孤立した </ul> が
|
|
322
|
+
# あり、実データベースで OEBPS/class/index.xhtml が整形式にならなかった
|
|
323
|
+
# (template.offline/class-index にも同じ孤立タグがあるが、HTML5 として
|
|
324
|
+
# 読まれる分には実害がないため未修正のまま。EPUB の XHTML では致命的)。
|
|
325
|
+
def test_class_index_page_has_no_orphaned_closing_tag
|
|
326
|
+
html = render_class_index_page
|
|
327
|
+
assert_not_include(html, '</ul>')
|
|
328
|
+
assert_nothing_raised { REXML::Document.new(html) }
|
|
329
|
+
end
|
|
330
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'bitclust'
|
|
5
|
+
require 'bitclust/subcommands/extract_command'
|
|
6
|
+
require 'bitclust/subcommands/htmlfile_command'
|
|
7
|
+
|
|
8
|
+
class TestRdOnlyCommandsRejectMarkdown < Test::Unit::TestCase
|
|
9
|
+
def with_md_file
|
|
10
|
+
Dir.mktmpdir do |dir|
|
|
11
|
+
path = File.join(dir, 'Array.md')
|
|
12
|
+
File.write(path, "---\nlibrary: _builtin\n---\n# class Array < Object\n")
|
|
13
|
+
yield path
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def capture_stderr
|
|
18
|
+
err = StringIO.new
|
|
19
|
+
orig, $stderr = $stderr, err
|
|
20
|
+
yield
|
|
21
|
+
err.string
|
|
22
|
+
ensure
|
|
23
|
+
$stderr = orig
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_extract_explains_markdown_is_unsupported
|
|
27
|
+
with_md_file do |path|
|
|
28
|
+
cmd = BitClust::Subcommands::ExtractCommand.new
|
|
29
|
+
cmd.parse([path])
|
|
30
|
+
msg = capture_stderr { assert_raise(SystemExit) { cmd.exec([path], {:prefix => nil, :capi => false}) } }
|
|
31
|
+
assert_match(/Markdown/, msg)
|
|
32
|
+
assert_match(/refm/, msg)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_htmlfile_explains_markdown_is_unsupported
|
|
37
|
+
with_md_file do |path|
|
|
38
|
+
cmd = BitClust::Subcommands::HtmlfileCommand.new
|
|
39
|
+
cmd.parse([path])
|
|
40
|
+
msg = capture_stderr { assert_raise(SystemExit) { cmd.exec([path], {:prefix => nil, :capi => false}) } }
|
|
41
|
+
assert_match(/Markdown/, msg)
|
|
42
|
+
assert_match(/rake generate|bitclust server/, msg)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|