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.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +65 -0
- data/Rakefile +32 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
- data/data/bitclust/searchpage/index.html +90 -0
- data/data/bitclust/template/class +69 -8
- data/data/bitclust/template/function +2 -2
- data/data/bitclust/template/layout +11 -3
- data/data/bitclust/template/method +2 -2
- data/data/bitclust/template/search +1 -1
- data/data/bitclust/template.epub/class +22 -1
- data/data/bitclust/template.epub/function +2 -2
- data/data/bitclust/template.epub/layout +1 -3
- data/data/bitclust/template.epub/method +2 -2
- data/data/bitclust/template.lillia/class +26 -5
- data/data/bitclust/template.lillia/layout +1 -3
- data/data/bitclust/template.lillia/method +2 -2
- data/data/bitclust/template.offline/class +48 -4
- data/data/bitclust/template.offline/function +2 -2
- data/data/bitclust/template.offline/layout +27 -3
- data/data/bitclust/template.offline/method +2 -2
- data/lib/bitclust/app.rb +16 -17
- data/lib/bitclust/capi_converter.rb +20 -0
- data/lib/bitclust/classentry.rb +67 -17
- data/lib/bitclust/compat.rb +2 -2
- data/lib/bitclust/completion.rb +58 -27
- data/lib/bitclust/crossrubyutils.rb +8 -5
- data/lib/bitclust/database.rb +28 -8
- data/lib/bitclust/doc_converter.rb +63 -0
- data/lib/bitclust/docentry.rb +1 -1
- data/lib/bitclust/entity_splitter.rb +221 -0
- data/lib/bitclust/entry.rb +47 -7
- data/lib/bitclust/functiondatabase.rb +13 -1
- data/lib/bitclust/functionentry.rb +5 -1
- data/lib/bitclust/functionreferenceparser.rb +4 -4
- data/lib/bitclust/generators/epub.rb +10 -3
- data/lib/bitclust/htmlutils.rb +1 -1
- data/lib/bitclust/include_graph.rb +375 -0
- data/lib/bitclust/include_pruner.rb +144 -0
- data/lib/bitclust/interface.rb +1 -1
- data/lib/bitclust/libraryentry.rb +8 -7
- data/lib/bitclust/lineinput.rb +7 -5
- data/lib/bitclust/link_checker.rb +179 -0
- data/lib/bitclust/markdown_bridge.rb +186 -0
- data/lib/bitclust/markdown_orchestrator.rb +267 -0
- data/lib/bitclust/markdown_to_rrd.rb +824 -0
- data/lib/bitclust/markdown_tree.rb +217 -0
- data/lib/bitclust/mdcompiler.rb +883 -0
- data/lib/bitclust/mdparser.rb +347 -0
- data/lib/bitclust/messagecatalog.rb +3 -3
- data/lib/bitclust/method_since_calculator.rb +117 -0
- data/lib/bitclust/methoddatabase.rb +124 -14
- data/lib/bitclust/methodentry.rb +125 -7
- data/lib/bitclust/methodid.rb +12 -11
- data/lib/bitclust/methodsignature.rb +10 -4
- data/lib/bitclust/nameutils.rb +41 -22
- data/lib/bitclust/preprocessor.rb +66 -29
- data/lib/bitclust/rdcompiler.rb +195 -95
- data/lib/bitclust/refsdatabase.rb +23 -8
- data/lib/bitclust/reloadable_request_handler.rb +72 -0
- data/lib/bitclust/requesthandler.rb +21 -16
- data/lib/bitclust/ridatabase.rb +10 -4
- data/lib/bitclust/rrd_to_markdown.rb +925 -0
- data/lib/bitclust/rrdparser.rb +196 -30
- data/lib/bitclust/runner.rb +17 -2
- data/lib/bitclust/screen.rb +80 -11
- data/lib/bitclust/search_index_generator.rb +288 -0
- data/lib/bitclust/searcher.rb +69 -20
- data/lib/bitclust/simplesearcher.rb +24 -12
- data/lib/bitclust/subcommand.rb +7 -1
- data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
- data/lib/bitclust/subcommands/checklink_command.rb +54 -0
- data/lib/bitclust/subcommands/chm_command.rb +15 -7
- data/lib/bitclust/subcommands/classes_command.rb +5 -0
- data/lib/bitclust/subcommands/extract_command.rb +5 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
- data/lib/bitclust/subcommands/init_command.rb +4 -1
- data/lib/bitclust/subcommands/list_command.rb +12 -4
- data/lib/bitclust/subcommands/lookup_command.rb +28 -9
- data/lib/bitclust/subcommands/methods_command.rb +10 -2
- data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
- data/lib/bitclust/subcommands/preproc_command.rb +5 -0
- data/lib/bitclust/subcommands/property_command.rb +2 -1
- data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
- data/lib/bitclust/subcommands/server_command.rb +10 -8
- data/lib/bitclust/subcommands/setup_command.rb +24 -10
- data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
- data/lib/bitclust/subcommands/update_command.rb +110 -2
- data/lib/bitclust/syntax_highlighter.rb +48 -4
- data/lib/bitclust/textutils.rb +3 -3
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust/version_badges.rb +90 -0
- data/lib/bitclust/whole_file_gate.rb +137 -0
- data/test/test_app.rb +269 -0
- data/test/test_bitclust.rb +2 -0
- data/test/test_capi_converter.rb +91 -0
- data/test/test_class_screen.rb +413 -0
- data/test/test_copy_doc_md.rb +53 -0
- data/test/test_doc_converter.rb +78 -0
- data/test/test_entity_splitter.rb +231 -0
- data/test/test_entry.rb +207 -2
- data/test/test_eol_warning.rb +43 -0
- data/test/test_functionreferenceparser.rb +11 -0
- data/test/test_include_graph.rb +640 -0
- data/test/test_include_pruner.rb +133 -0
- data/test/test_link_checker.rb +114 -0
- data/test/test_lookup_command.rb +135 -0
- data/test/test_markdown_bridge.rb +217 -0
- data/test/test_markdown_orchestrator.rb +478 -0
- data/test/test_markdown_to_rrd.rb +757 -0
- data/test/test_markdown_tree.rb +369 -0
- data/test/test_mdcompiler.rb +1276 -0
- data/test/test_mdparser.rb +538 -0
- data/test/test_method_screen.rb +166 -0
- data/test/test_method_since_calculator.rb +290 -0
- data/test/test_methoddatabase.rb +16 -0
- data/test/test_methodentry.rb +328 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_methodsince_command.rb +168 -0
- data/test/test_nameutils.rb +22 -0
- data/test/test_preprocessor.rb +215 -0
- data/test/test_rdcompiler.rb +401 -30
- data/test/test_refsdatabase.rb +32 -0
- data/test/test_rrd_to_markdown.rb +968 -0
- data/test/test_rrdparser.rb +295 -2
- data/test/test_run_ruby_wasm.rb +54 -0
- data/test/test_run_worker_prelude.rb +83 -0
- data/test/test_runner.rb +51 -0
- data/test/test_search_index_generator.rb +513 -0
- data/test/test_search_screen.rb +48 -0
- data/test/test_searcher.rb +132 -0
- data/test/test_searchpage_command.rb +167 -0
- data/test/test_simplesearcher.rb +28 -0
- data/test/test_statichtml_command.rb +204 -0
- data/test/test_syntax_highlighter.rb +60 -0
- data/test/test_whole_file_gate.rb +175 -0
- data/theme/default/js/NOTICE +42 -0
- data/theme/default/js/run-worker.js +111 -0
- data/theme/default/js/run.js +350 -0
- data/theme/default/js/search_controller.js +134 -0
- data/theme/default/js/search_init.js +176 -0
- data/theme/default/js/search_navigation.js +110 -0
- data/theme/default/js/search_page.js +202 -0
- data/theme/default/js/search_ranker.js +244 -0
- data/theme/default/script.js +91 -18
- data/theme/default/search.css +149 -0
- data/theme/default/style.css +193 -21
- data/theme/default/syntax-highlight.css +8 -0
- data/theme/lillia/style.css +45 -1
- metadata +161 -20
- data/README +0 -32
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# bitclust/version_badges.rb
|
|
4
|
+
#
|
|
5
|
+
# This program is free software.
|
|
6
|
+
# You can distribute/modify this program under the Ruby License.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
module BitClust
|
|
10
|
+
|
|
11
|
+
# Renders the small "since"/"until" version badges shown next to a
|
|
12
|
+
# method's signature (bitclust#132 phase P3). The version data comes from
|
|
13
|
+
# MethodEntry's per-name since_map/until_map (bitclust#132 phase P2); an
|
|
14
|
+
# empty map means "no badge", which keeps the output byte-identical to
|
|
15
|
+
# what it was before this feature existed.
|
|
16
|
+
#
|
|
17
|
+
# Included by both RDCompiler (the <dt class="method-heading"> emitted
|
|
18
|
+
# for RD/Markdown method pages) and TemplateScreen (the default server
|
|
19
|
+
# template's method table rows), so it only relies on the `_`
|
|
20
|
+
# (Translatable) and escape_html (HTMLUtils) methods that both hosts
|
|
21
|
+
# already provide.
|
|
22
|
+
module VersionBadges
|
|
23
|
+
SINCE_CSS_CLASS = 'method-since-badge'
|
|
24
|
+
UNTIL_CSS_CLASS = 'method-until-badge'
|
|
25
|
+
SINCE_CATALOG_KEY = 'since Ruby %s'
|
|
26
|
+
UNTIL_CATALOG_KEY = 'removed in Ruby %s'
|
|
27
|
+
|
|
28
|
+
# HTML for the badges attached to the <dt> heading of one of +entry+'s
|
|
29
|
+
# signature lines (RDCompiler#method_signature). since/until are
|
|
30
|
+
# decided independently: when every one of entry's names maps to the
|
|
31
|
+
# very same version, the badge is rendered once, on the first
|
|
32
|
+
# signature's <dt> (+first+ true), instead of being repeated on every
|
|
33
|
+
# alias's <dt>; otherwise (mixed/partial) it is looked up per +name+.
|
|
34
|
+
def heading_version_badges(entry, name, first)
|
|
35
|
+
name = badge_lookup_name(name)
|
|
36
|
+
join_badge_spans(
|
|
37
|
+
heading_badge_span(entry, name, first, entry.since_map,
|
|
38
|
+
SINCE_CSS_CLASS, SINCE_CATALOG_KEY),
|
|
39
|
+
heading_badge_span(entry, name, first, entry.until_map,
|
|
40
|
+
UNTIL_CSS_CLASS, UNTIL_CATALOG_KEY)
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# HTML for the badges attached to a single signature line in the
|
|
45
|
+
# default (server) template's method table
|
|
46
|
+
# (data/bitclust/template/class). Each row already lists every alias on
|
|
47
|
+
# its own line, so this always looks the version up by +name+ directly
|
|
48
|
+
# -- no uniform-across-all-names aggregation, unlike heading_version_badges.
|
|
49
|
+
def row_version_badges(entry, name)
|
|
50
|
+
name = badge_lookup_name(name)
|
|
51
|
+
join_badge_spans(
|
|
52
|
+
badge_span(entry.since_map[name], SINCE_CSS_CLASS, SINCE_CATALOG_KEY),
|
|
53
|
+
badge_span(entry.until_map[name], UNTIL_CSS_CLASS, UNTIL_CATALOG_KEY)
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# シグネチャ行から取り出した名前を since_map/until_map のキーに合わせる。
|
|
60
|
+
# 特殊変数のシグネチャ名は "$SAFE" のように $ 付きだが、エントリの names
|
|
61
|
+
# (= マップのキー)は先頭の $ を除いた形("SAFE"。"$$" なら "$")で
|
|
62
|
+
# 格納されている(rrdparser の method_signature と同じ規約)
|
|
63
|
+
def badge_lookup_name(name)
|
|
64
|
+
name.sub(/\A\$/, '')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def heading_badge_span(entry, name, first, map, css_class, catalog_key)
|
|
68
|
+
return nil if map.empty?
|
|
69
|
+
version = uniform_map?(entry, map) ? (first ? map.values.first : nil) : map[name]
|
|
70
|
+
badge_span(version, css_class, catalog_key)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Whether +map+ assigns the very same version to every one of entry's
|
|
74
|
+
# names, i.e. the badge applies uniformly and doesn't need to be
|
|
75
|
+
# repeated per alias.
|
|
76
|
+
def uniform_map?(entry, map)
|
|
77
|
+
map.size == entry.names.size && map.values.uniq.size == 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def badge_span(version, css_class, catalog_key)
|
|
81
|
+
# 空文字は「明示的に不明」({: since=""})= バッジ非表示(bitclust#132)
|
|
82
|
+
return nil if version.nil? || version.empty?
|
|
83
|
+
%Q(<span class="#{css_class}">#{escape_html(_(catalog_key, version))}</span>)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def join_badge_spans(*spans)
|
|
87
|
+
spans.compact.join(' ')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bitclust/include_graph'
|
|
4
|
+
|
|
5
|
+
module BitClust
|
|
6
|
+
# ファイル全体を包む単一の版ゲート(#@else 無し)の検出と解除(O4)。
|
|
7
|
+
#
|
|
8
|
+
# 旧 RD ではライブラリ/エンティティ自体の版ゲートをファイル全体の
|
|
9
|
+
# #@since/#@until/#@if ラップで表現していた。新パイプラインでは
|
|
10
|
+
# front matter の since/until(構造ゲート)で表現するため、
|
|
11
|
+
# スコープの下で「常に真」または「since/until で表現できる」ラップは外す。
|
|
12
|
+
#
|
|
13
|
+
# 据え置き(nil を返す):
|
|
14
|
+
# - #@else 付き(fiddle.rd。枝の選択は本文の版分岐として温存)
|
|
15
|
+
# - スコープ外のゲート(profile.rd 等。旧版サルベージは別スコープで再実行)
|
|
16
|
+
# - 常に真と証明できない #@if 条件
|
|
17
|
+
module WholeFileGate
|
|
18
|
+
GATE_OPEN_RE = /\A\#@(since|until|if)\s*(.*)$/
|
|
19
|
+
BLOCK_OPEN_RE = /\A\#@(?:since|until|if|samplecode)\b/
|
|
20
|
+
BLANK_RE = /\A\s*\z/
|
|
21
|
+
|
|
22
|
+
# 常に真と証明できる #@if 条件の形(rss.rd の (version >= "1.8.2"))
|
|
23
|
+
VERSION_GE_RE = /\A\(?\s*version\s*>=\s*"([\d.]+)"\s*\)?\z/
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# ファイル全体を包むゲートの Condition を返す。該当しなければ nil
|
|
28
|
+
def detect(src)
|
|
29
|
+
open_idx, close_idx, cond, has_else = parse(src.lines)
|
|
30
|
+
return nil if open_idx.nil? || has_else
|
|
31
|
+
cond if close_idx
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 据え置きゲート(unwrap できない library ルート等)のメタデータ領域
|
|
35
|
+
# (require/sublibrary/category)を、本文と分けてそれぞれ同じゲートで包んだ
|
|
36
|
+
# 意味等価な正規形に書き換える。native パースはメタデータを front matter から
|
|
37
|
+
# しか読まないため、ゲート内に埋もれた rd 形式のメタ行を、既存のゲート対応
|
|
38
|
+
# メタデータ収集器が front matter 化できる位置(ファイル先頭のブロック)へ出す。
|
|
39
|
+
# 全体ゲートでない・メタデータが無い・本文が無い場合は nil
|
|
40
|
+
def regate_metadata(src)
|
|
41
|
+
lines = src.lines
|
|
42
|
+
open_idx, close_idx, cond, has_else = parse(lines)
|
|
43
|
+
return nil if open_idx.nil? || close_idx.nil? || has_else || cond.nil?
|
|
44
|
+
content = lines[(open_idx + 1)...close_idx] || raise
|
|
45
|
+
i = 0
|
|
46
|
+
i += 1 while content[i] && content[i] =~ BLANK_RE
|
|
47
|
+
meta_start = i
|
|
48
|
+
meta_end = nil
|
|
49
|
+
j = meta_start
|
|
50
|
+
while content[j]
|
|
51
|
+
case content[j]
|
|
52
|
+
when /\A(?:require|sublibrary|category)\s+\S/ then meta_end = j + 1
|
|
53
|
+
when BLANK_RE then nil
|
|
54
|
+
else break
|
|
55
|
+
end
|
|
56
|
+
j += 1
|
|
57
|
+
end
|
|
58
|
+
return nil unless meta_end
|
|
59
|
+
rest = content[meta_end..] || raise
|
|
60
|
+
rest.shift while rest.first && rest.first =~ BLANK_RE
|
|
61
|
+
return nil if rest.empty?
|
|
62
|
+
gate_line = lines[open_idx]
|
|
63
|
+
# 種別(category/require/sublibrary)ごとに独立ブロックへ分ける
|
|
64
|
+
# (メタデータ収集器の「#@ ブロックは単一種のみ」の制約に合わせる)
|
|
65
|
+
runs = [] #: Array[[String?, Array[String]]]
|
|
66
|
+
(content[meta_start...meta_end] || raise).each do |l|
|
|
67
|
+
next if l =~ BLANK_RE
|
|
68
|
+
kind = l[/\A(category|require|sublibrary)/, 1]
|
|
69
|
+
if runs.last && runs.last[0] == kind
|
|
70
|
+
runs.last[1] << l
|
|
71
|
+
else
|
|
72
|
+
runs << [kind, [l]]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
meta_blocks = runs.flat_map { |_, ls| [gate_line] + ls + ["\#@end\n", "\n"] }
|
|
76
|
+
((lines[0...open_idx] || raise) +
|
|
77
|
+
meta_blocks +
|
|
78
|
+
[gate_line] + rest + ["\#@end\n"] +
|
|
79
|
+
(lines[(close_idx + 1)..] || raise)).join
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# スコープの下で解除できる全体ゲートなら [解除後の src, front matter に
|
|
83
|
+
# 書く gate({} は不要の意)] を返す。据え置きなら nil
|
|
84
|
+
def unwrap_for_scope(src, scope)
|
|
85
|
+
lines = src.lines
|
|
86
|
+
open_idx, close_idx, cond, has_else = parse(lines)
|
|
87
|
+
return nil if open_idx.nil? || close_idx.nil? || has_else || cond.nil?
|
|
88
|
+
|
|
89
|
+
gate =
|
|
90
|
+
case cond.kind
|
|
91
|
+
when :if
|
|
92
|
+
return nil unless provably_true_if?(cond.version, scope)
|
|
93
|
+
{} #: IncludeGraph::gate
|
|
94
|
+
else
|
|
95
|
+
scoped = scope.gate([cond])
|
|
96
|
+
return nil unless scoped # スコープ外 → 据え置き
|
|
97
|
+
scoped
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
body = (lines[0...open_idx] || raise) + (lines[(open_idx + 1)...close_idx] || raise) + (lines[(close_idx + 1)..] || raise)
|
|
101
|
+
body.shift while body.first && body.first =~ BLANK_RE
|
|
102
|
+
[body.join, gate]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# [開き行 idx, 閉じ行 idx, Condition, トップレベル #@else の有無] を返す。
|
|
106
|
+
# 全体ゲートでなければ open_idx が nil
|
|
107
|
+
def parse(lines)
|
|
108
|
+
open_idx = lines.index { |l| l !~ BLANK_RE }
|
|
109
|
+
return [nil] unless open_idx && lines[open_idx] =~ GATE_OPEN_RE
|
|
110
|
+
cond = IncludeGraph::Condition.new(($1 || raise).to_sym, ($2 || raise).strip)
|
|
111
|
+
|
|
112
|
+
nest = 0
|
|
113
|
+
has_else = false
|
|
114
|
+
close_idx = nil #: Integer?
|
|
115
|
+
(open_idx...lines.length).each do |i|
|
|
116
|
+
case lines[i]
|
|
117
|
+
when BLOCK_OPEN_RE then nest += 1
|
|
118
|
+
when /\A\#@else\b/ then has_else = true if nest == 1
|
|
119
|
+
when /\A\#@end\b/
|
|
120
|
+
nest -= 1
|
|
121
|
+
if nest.zero?
|
|
122
|
+
close_idx = i
|
|
123
|
+
break
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
return [nil] if close_idx.nil?
|
|
128
|
+
# 閉じ行の後に非空行があれば全体ゲートではない
|
|
129
|
+
return [nil] if (lines[(close_idx + 1)..] || raise).any? { |l| l !~ BLANK_RE }
|
|
130
|
+
[open_idx, close_idx, cond, has_else]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def provably_true_if?(condition, scope)
|
|
134
|
+
condition =~ VERSION_GE_RE && Gem::Version.new($1) <= scope.lo
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/test/test_app.rb
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'bitclust'
|
|
4
|
+
require 'bitclust/app'
|
|
5
|
+
require 'rack'
|
|
6
|
+
require 'rack/mock'
|
|
7
|
+
require 'webrick'
|
|
8
|
+
require 'tmpdir'
|
|
9
|
+
require 'fileutils'
|
|
10
|
+
require 'stringio'
|
|
11
|
+
|
|
12
|
+
# bitclust#275: `rake generate` で DB を再生成しても `bitclust server` を
|
|
13
|
+
# 再起動しなくてよいようにする。
|
|
14
|
+
#
|
|
15
|
+
# テストリスト:
|
|
16
|
+
# [x] DB を再生成(rm_rf+init+update)した後、同じ App/Interface への同じ
|
|
17
|
+
# リクエストで新しい内容が返り、古い内容(メモ化されていたはず)が消えて
|
|
18
|
+
# いる(Rack ハンドラ、dbpath が String の単一 DB)
|
|
19
|
+
# [x] 同じことが WEBrick ハンドラ(options[:rack] を渡さない既定経路)でも
|
|
20
|
+
# 成り立つ
|
|
21
|
+
# [x] dbpath が Array(複数バージョン)のときも、各バージョンの Interface が
|
|
22
|
+
# 独立に再生成を検知する。片方だけ再生成しても、もう片方の内容は無関係
|
|
23
|
+
# のまま
|
|
24
|
+
# [x] DB 再生成の途中(rm_rf 直後、properties が一時的に存在しない)に
|
|
25
|
+
# リクエストが来ても例外にならず、直前の内容を返し続ける(次のリクエスト
|
|
26
|
+
# で改めて判定する)
|
|
27
|
+
# [x] --capi 有効時は FunctionDatabase 側の再生成も反映される
|
|
28
|
+
class TestApp < Test::Unit::TestCase
|
|
29
|
+
def setup
|
|
30
|
+
$bitclust_context_cache = nil
|
|
31
|
+
@tmpdir = Dir.mktmpdir('bitclust-app-reload')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def teardown
|
|
35
|
+
FileUtils.rm_rf(@tmpdir)
|
|
36
|
+
$bitclust_context_cache = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_rack_handler_reflects_regenerated_db_without_restart
|
|
40
|
+
dbdir = "#{@tmpdir}/db"
|
|
41
|
+
srcroot = "#{@tmpdir}/src"
|
|
42
|
+
build_method_db(dbdir, srcroot, 'ORIGINAL_MARKER')
|
|
43
|
+
|
|
44
|
+
interface = build_app(dbdir, :rack => true).interfaces['/view/']
|
|
45
|
+
|
|
46
|
+
assert_include(rack_body(interface, '/class/Target'), 'ORIGINAL_MARKER')
|
|
47
|
+
|
|
48
|
+
regenerate(dbdir) { build_method_db(dbdir, srcroot, 'UPDATED_MARKER') }
|
|
49
|
+
|
|
50
|
+
after = rack_body(interface, '/class/Target')
|
|
51
|
+
assert_include(after, 'UPDATED_MARKER')
|
|
52
|
+
assert_not_include(after, 'ORIGINAL_MARKER')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_webrick_handler_reflects_regenerated_db_without_restart
|
|
56
|
+
dbdir = "#{@tmpdir}/db"
|
|
57
|
+
srcroot = "#{@tmpdir}/src"
|
|
58
|
+
build_method_db(dbdir, srcroot, 'ORIGINAL_MARKER')
|
|
59
|
+
|
|
60
|
+
servlet = build_app(dbdir).interfaces['/view/'].get_instance(WEBrick::Config::HTTP)
|
|
61
|
+
|
|
62
|
+
assert_include(webrick_body(servlet, '/class/Target'), 'ORIGINAL_MARKER')
|
|
63
|
+
|
|
64
|
+
regenerate(dbdir) { build_method_db(dbdir, srcroot, 'UPDATED_MARKER') }
|
|
65
|
+
|
|
66
|
+
after = webrick_body(servlet, '/class/Target')
|
|
67
|
+
assert_include(after, 'UPDATED_MARKER')
|
|
68
|
+
assert_not_include(after, 'ORIGINAL_MARKER')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_each_version_reloads_independently_for_array_dbpath
|
|
72
|
+
dbdir_30 = "#{@tmpdir}/db-3.0"
|
|
73
|
+
dbdir_40 = "#{@tmpdir}/db-4.0"
|
|
74
|
+
src_30 = "#{@tmpdir}/src-3.0"
|
|
75
|
+
src_40 = "#{@tmpdir}/src-4.0"
|
|
76
|
+
build_method_db(dbdir_30, src_30, 'V30_ORIGINAL')
|
|
77
|
+
build_method_db(dbdir_40, src_40, 'V40_ORIGINAL')
|
|
78
|
+
|
|
79
|
+
datadir = File.expand_path('../data/bitclust', __dir__)
|
|
80
|
+
app = BitClust::App.new(
|
|
81
|
+
:dbpath => [dbdir_30, dbdir_40],
|
|
82
|
+
:rack => true,
|
|
83
|
+
:baseurl => '',
|
|
84
|
+
:capi => false,
|
|
85
|
+
:encoding => 'utf-8',
|
|
86
|
+
:datadir => datadir,
|
|
87
|
+
:templatedir => "#{datadir}/template.offline"
|
|
88
|
+
)
|
|
89
|
+
interface_30 = app.interfaces['3.0']
|
|
90
|
+
interface_40 = app.interfaces['4.0']
|
|
91
|
+
|
|
92
|
+
assert_include(rack_body(interface_30, '/class/Target'), 'V30_ORIGINAL')
|
|
93
|
+
assert_include(rack_body(interface_40, '/class/Target'), 'V40_ORIGINAL')
|
|
94
|
+
|
|
95
|
+
regenerate(dbdir_30) { build_method_db(dbdir_30, src_30, 'V30_UPDATED') }
|
|
96
|
+
|
|
97
|
+
v30_after = rack_body(interface_30, '/class/Target')
|
|
98
|
+
assert_include(v30_after, 'V30_UPDATED')
|
|
99
|
+
assert_not_include(v30_after, 'V30_ORIGINAL')
|
|
100
|
+
# 触っていない 4.0 側は無関係のまま
|
|
101
|
+
assert_include(rack_body(interface_40, '/class/Target'), 'V40_ORIGINAL')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_stale_content_served_without_raising_while_properties_missing
|
|
105
|
+
dbdir = "#{@tmpdir}/db"
|
|
106
|
+
srcroot = "#{@tmpdir}/src"
|
|
107
|
+
build_method_db(dbdir, srcroot, 'ORIGINAL_MARKER')
|
|
108
|
+
|
|
109
|
+
interface = build_app(dbdir, :rack => true).interfaces['/view/']
|
|
110
|
+
assert_include(rack_body(interface, '/class/Target'), 'ORIGINAL_MARKER')
|
|
111
|
+
|
|
112
|
+
# rake generate の rm_rf 直後(init/update が終わる前)を再現する: properties
|
|
113
|
+
# が一時的に存在しない
|
|
114
|
+
FileUtils.rm_rf(dbdir)
|
|
115
|
+
|
|
116
|
+
assert_nothing_raised do
|
|
117
|
+
during = rack_body(interface, '/class/Target')
|
|
118
|
+
assert_include(during, 'ORIGINAL_MARKER')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
regenerate(dbdir) { build_method_db(dbdir, srcroot, 'UPDATED_MARKER') }
|
|
122
|
+
|
|
123
|
+
assert_include(rack_body(interface, '/class/Target'), 'UPDATED_MARKER')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_capi_function_database_also_reloads
|
|
127
|
+
dbdir = "#{@tmpdir}/db"
|
|
128
|
+
srcroot = "#{@tmpdir}/src"
|
|
129
|
+
build_method_db(dbdir, srcroot, 'ORIGINAL_MARKER')
|
|
130
|
+
build_function(dbdir, 'This is the original function doc.')
|
|
131
|
+
|
|
132
|
+
interface = build_app(dbdir, :rack => true, :capi => true).interfaces['/view/']
|
|
133
|
+
|
|
134
|
+
assert_include(rack_body(interface, '/function/public_func'), 'the original function doc')
|
|
135
|
+
|
|
136
|
+
regenerate(dbdir) do
|
|
137
|
+
build_method_db(dbdir, srcroot, 'UPDATED_MARKER')
|
|
138
|
+
build_function(dbdir, 'This is the updated function doc.')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
after = rack_body(interface, '/function/public_func')
|
|
142
|
+
assert_include(after, 'the updated function doc')
|
|
143
|
+
assert_not_include(after, 'the original function doc')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# rake generate の途中(init 後〜update 完了前)にリクエストが来ると、
|
|
147
|
+
# init 時点の properties mtime で途中状態の DB を掴んで再構築してしまう。
|
|
148
|
+
# update 完了(transaction の commit)時に properties の mtime が進むこと
|
|
149
|
+
# により、次のリクエストで完成した DB を読み直して収束すること。
|
|
150
|
+
# (utime による人工的な mtime 操作をせず、commit 側の touch だけに頼る)
|
|
151
|
+
def test_request_during_regeneration_converges_after_update_completes
|
|
152
|
+
dbdir = "#{@tmpdir}/db"
|
|
153
|
+
srcroot = "#{@tmpdir}/src"
|
|
154
|
+
build_method_db(dbdir, srcroot, 'ORIGINAL_MARKER')
|
|
155
|
+
interface = build_app(dbdir, :rack => true).interfaces['/view/']
|
|
156
|
+
assert_include(rack_body(interface, '/class/Target'), 'ORIGINAL_MARKER')
|
|
157
|
+
|
|
158
|
+
# 再生成の途中状態を再現: rm_rf → init+propset まで(update はまだ)
|
|
159
|
+
FileUtils.rm_rf(dbdir)
|
|
160
|
+
db = BitClust::MethodDatabase.new(dbdir)
|
|
161
|
+
db.init
|
|
162
|
+
db.transaction do
|
|
163
|
+
db.propset('version', '3.4')
|
|
164
|
+
db.propset('encoding', 'utf-8')
|
|
165
|
+
end
|
|
166
|
+
# 途中状態のリクエストは(内容はともかく)例外にならないこと
|
|
167
|
+
assert_nothing_raised { rack_body(interface, '/class/Target') }
|
|
168
|
+
|
|
169
|
+
# update 完了 → 次のリクエストで新しい内容に収束すること
|
|
170
|
+
File.write("#{srcroot}/testlib.rd", <<~RD)
|
|
171
|
+
= class Target < Object
|
|
172
|
+
UPDATED_MARKER
|
|
173
|
+
|
|
174
|
+
== Instance Methods
|
|
175
|
+
--- foo
|
|
176
|
+
foo body
|
|
177
|
+
RD
|
|
178
|
+
db.transaction do
|
|
179
|
+
db.update_by_stdlibtree(srcroot)
|
|
180
|
+
end
|
|
181
|
+
after = rack_body(interface, '/class/Target')
|
|
182
|
+
assert_include(after, 'UPDATED_MARKER')
|
|
183
|
+
assert_not_include(after, 'ORIGINAL_MARKER')
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# Screen#run_template は互換 ERB を self.class(ClassScreen 等)にキャッシュ
|
|
189
|
+
# するため、同一プロセス内で異なる templatedir を混ぜて使うと「最初に
|
|
190
|
+
# コンパイルされた方が以後ずっと勝つ」(test_method_screen.rb 冒頭のコメント
|
|
191
|
+
# 参照)。このテストファイルはスイート内の他の Screen 系テストと同じ
|
|
192
|
+
# template.offline に統一し、新たな衝突を持ち込まない
|
|
193
|
+
def build_app(dbdir, extra = {})
|
|
194
|
+
datadir = File.expand_path('../data/bitclust', __dir__)
|
|
195
|
+
BitClust::App.new({
|
|
196
|
+
:dbpath => dbdir,
|
|
197
|
+
:viewpath => '/view/',
|
|
198
|
+
:baseurl => '',
|
|
199
|
+
:capi => false,
|
|
200
|
+
:encoding => 'utf-8',
|
|
201
|
+
:datadir => datadir,
|
|
202
|
+
:templatedir => "#{datadir}/template.offline"
|
|
203
|
+
}.merge(extra))
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def rack_body(interface, path)
|
|
207
|
+
env = Rack::MockRequest.env_for(path)
|
|
208
|
+
_status, _headers, body = interface.call(env)
|
|
209
|
+
body.join
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def webrick_body(servlet, path)
|
|
213
|
+
config = WEBrick::Config::HTTP
|
|
214
|
+
wreq = WEBrick::HTTPRequest.new(config)
|
|
215
|
+
wreq.parse(StringIO.new("GET #{path} HTTP/1.1\r\nHost: example.com\r\n\r\n"))
|
|
216
|
+
wres = WEBrick::HTTPResponse.new(config)
|
|
217
|
+
servlet.do_GET(wreq, wres)
|
|
218
|
+
wres.body
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# DB 再生成の前後で properties の mtime が必ず変わるようにする。
|
|
222
|
+
# ファイルシステムの mtime 解像度(秒単位のものもある)に依存しないよう、
|
|
223
|
+
# 再生成後に明示的に前より先の時刻へ File.utime で進める
|
|
224
|
+
def regenerate(dbdir)
|
|
225
|
+
properties_path = "#{dbdir}/properties"
|
|
226
|
+
before = File.exist?(properties_path) ? File.mtime(properties_path) : nil
|
|
227
|
+
yield
|
|
228
|
+
forced = (before || Time.now) + 5
|
|
229
|
+
File.utime(forced, forced, properties_path)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def build_method_db(dbdir, srcroot, marker)
|
|
233
|
+
FileUtils.mkdir_p(srcroot)
|
|
234
|
+
File.write("#{srcroot}/LIBRARIES", "testlib\n")
|
|
235
|
+
File.write("#{srcroot}/testlib.rd", <<~RD)
|
|
236
|
+
= class Target < Object
|
|
237
|
+
#{marker}
|
|
238
|
+
|
|
239
|
+
== Instance Methods
|
|
240
|
+
--- foo
|
|
241
|
+
foo body
|
|
242
|
+
RD
|
|
243
|
+
db = BitClust::MethodDatabase.new(dbdir)
|
|
244
|
+
db.init
|
|
245
|
+
db.transaction do
|
|
246
|
+
db.propset('version', '3.4')
|
|
247
|
+
db.propset('encoding', 'utf-8')
|
|
248
|
+
end
|
|
249
|
+
db.transaction do
|
|
250
|
+
db.update_by_stdlibtree(srcroot)
|
|
251
|
+
end
|
|
252
|
+
db
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def build_function(dbdir, doc)
|
|
256
|
+
FileUtils.mkdir_p("#{dbdir}/function")
|
|
257
|
+
File.write("#{dbdir}/function/public_func", <<~HERE)
|
|
258
|
+
filename=test.c
|
|
259
|
+
macro=false
|
|
260
|
+
private=false
|
|
261
|
+
type=VALUE
|
|
262
|
+
name=public_func
|
|
263
|
+
params=()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
#{doc}
|
|
267
|
+
HERE
|
|
268
|
+
end
|
|
269
|
+
end
|
data/test/test_bitclust.rb
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test/unit'
|
|
4
|
+
|
|
5
|
+
require 'bitclust/capi_converter'
|
|
6
|
+
require 'bitclust/markdown_to_rrd'
|
|
7
|
+
|
|
8
|
+
# CapiConverter: refm/capi(C API リファレンス)の md 変換。
|
|
9
|
+
# 構造は「--- シグネチャ + 本文」の列のみ(見出し・include・emlist なし、
|
|
10
|
+
# library 等のクロスファイル情報も無い)。front matter 不使用。
|
|
11
|
+
#
|
|
12
|
+
# テストリスト:
|
|
13
|
+
# [x] シグネチャ: --- <C sig> ↔ ### <C sig>(def 等のキーワードは付けない)
|
|
14
|
+
# [x] MACRO / static プレフィクスも素通し
|
|
15
|
+
# [x] 本文の [[f:...]] 参照 ↔ [f:...]
|
|
16
|
+
# [x] インデントコード例(api と同じフェンス化)
|
|
17
|
+
# [x] #@since/#@until ディレクティブのパススルー
|
|
18
|
+
# [x] 定義リスト・ネストリスト(api と同じ変換)
|
|
19
|
+
# [x] convert(rrd) を md→rd すると元に戻る(roundtrip)
|
|
20
|
+
class TestCapiConverter < Test::Unit::TestCase
|
|
21
|
+
def convert(rrd)
|
|
22
|
+
BitClust::CapiConverter.convert(rrd)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def roundtrip(rrd)
|
|
26
|
+
BitClust::MarkdownToRRD.convert(convert(rrd), capi: true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_signature
|
|
30
|
+
assert_equal "### VALUE rb_ary_new()\n\n空の Ruby の配列を作成し返します。\n",
|
|
31
|
+
convert("--- VALUE rb_ary_new()\n\n空の Ruby の配列を作成し返します。\n")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_macro_and_static_signatures
|
|
35
|
+
assert_equal "### MACRO type* ALLOC(type)\n", convert("--- MACRO type* ALLOC(type)\n")
|
|
36
|
+
assert_equal "### static VALUE assign(VALUE self, NODE *lhs)\n",
|
|
37
|
+
convert("--- static VALUE assign(VALUE self, NODE *lhs)\n")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_function_reference
|
|
41
|
+
assert_equal "[f:SPECIAL_CONST_P](obj) が真のとき落ちます。\n",
|
|
42
|
+
convert("[[f:SPECIAL_CONST_P]](obj) が真のとき落ちます。\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_indented_code_block
|
|
46
|
+
# フェンス個数はベースインデント幅をエンコードする(3+4=7個。api と同仕様)
|
|
47
|
+
rrd = "使用例\n\n VALUE ary;\n ary = rb_ary_new();\n"
|
|
48
|
+
md = convert(rrd)
|
|
49
|
+
assert_match(/^`{7}\nVALUE ary;\nary = rb_ary_new\(\);\n`{7}$/, md)
|
|
50
|
+
assert_equal rrd, roundtrip(rrd)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_directives_pass_through
|
|
54
|
+
rrd = "\#@until 2.2.0\n--- void Check_SafeStr(VALUE v)\n\n古い API です。\n\#@end\n"
|
|
55
|
+
assert_equal "\#@until 2.2.0\n### void Check_SafeStr(VALUE v)\n\n古い API です。\n\#@end\n",
|
|
56
|
+
convert(rrd)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_roundtrip_composite
|
|
60
|
+
rrd = <<~RRD
|
|
61
|
+
--- MACRO int BUILTIN_TYPE(VALUE obj)
|
|
62
|
+
|
|
63
|
+
obj の構造体型 ID を返します。
|
|
64
|
+
[[f:SPECIAL_CONST_P]](obj) が真のオブジェクトに対して使うと落ちます。
|
|
65
|
+
|
|
66
|
+
: argcが0以上の時
|
|
67
|
+
その関数がとる引数の数を意味します。
|
|
68
|
+
|
|
69
|
+
* 必須引数の数 (省略可能な引数があるなら省略不可)
|
|
70
|
+
* 省略可能な引数の数 (ゼロ個ならば省略可)
|
|
71
|
+
|
|
72
|
+
--- VALUE rb_ary_new2(long len)
|
|
73
|
+
|
|
74
|
+
使用例
|
|
75
|
+
|
|
76
|
+
VALUE ary;
|
|
77
|
+
ary = rb_ary_new2(len);
|
|
78
|
+
RRD
|
|
79
|
+
assert_equal rrd, roundtrip(rrd)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_body_text_starting_with_require_is_not_metadata
|
|
83
|
+
# eval.c.rd の rb_require 型: 「require の C 版です。」のような本文が
|
|
84
|
+
# フラグメント先頭に来ても library メタデータと誤認しない
|
|
85
|
+
# (capi には require/category ヘッダは存在しない)
|
|
86
|
+
rrd = "\nrequire の C 版です。feature「fname」をロードします。\n"
|
|
87
|
+
md = BitClust::CapiConverter.convert(rrd)
|
|
88
|
+
assert_equal rrd, md
|
|
89
|
+
assert_equal rrd, roundtrip(rrd)
|
|
90
|
+
end
|
|
91
|
+
end
|