bitclust-dev 1.3.0 → 1.6.1
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/tools/md-bridge-check.rb +94 -0
- data/tools/md-compile-check.rb +217 -0
- data/tools/md-db-check.rb +112 -0
- data/tools/md-parse-check.rb +185 -0
- data/tools/md-roundtrip-check.rb +145 -0
- data/tools/md-tree-check.rb +107 -0
- data/tools/stattodo.rb +1 -1
- metadata +21 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6f8d4dcefaa581b519600e255c2e9fba43bcdc3610f808de7fc101126fe1961
|
|
4
|
+
data.tar.gz: 3a86cd213fb1cb1be92d1d1f71c8cf3da91ece0cbd9e24ac05f11639f7a47aee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d28e9f59a0dda51014c904e290b636cf3e6eef1516b04c6a22d3280cced4cf598dba82475e7aec4a3ab2694f7811b9a4bec29197ab7fd2f2027f23c0a45ab2ba
|
|
7
|
+
data.tar.gz: fe4fa1117faa16fd0f4b6cf7f0aa4c09e8cbb8f244b1ba7eb3e20be9be62f4c5e91a79f6adaddc783f5f0c227a34a006c9b6cdc1c691044185f0331db7658504
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# md ブリッジのパーサレベル等価検証。
|
|
5
|
+
# 各ライブラリを「旧ソース(doctree の .rd)」と「md ツリー → MarkdownBridge で
|
|
6
|
+
# 生成した rd ツリー」の両方から RRDParser でパースし、クラス・エントリ構造が
|
|
7
|
+
# 一致することを確認する。
|
|
8
|
+
#
|
|
9
|
+
# usage: ruby tools/md-bridge-check.rb <doctree-src-root> <md-tree-root> [--version V] [-v]
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
12
|
+
require 'tmpdir'
|
|
13
|
+
require 'bitclust'
|
|
14
|
+
require 'bitclust/markdown_bridge'
|
|
15
|
+
|
|
16
|
+
version = '3.4'
|
|
17
|
+
verbose = false
|
|
18
|
+
paths = []
|
|
19
|
+
args = ARGV.dup
|
|
20
|
+
until args.empty?
|
|
21
|
+
case (a = args.shift)
|
|
22
|
+
when '--version' then version = args.shift
|
|
23
|
+
when '-v' then verbose = true
|
|
24
|
+
else paths << a
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
src_root, md_root = paths
|
|
28
|
+
abort "usage: #{$0} <doctree-src-root> <md-tree-root> [--version V]" unless src_root && md_root
|
|
29
|
+
|
|
30
|
+
params = { 'version' => version }
|
|
31
|
+
|
|
32
|
+
def library_names(root, params)
|
|
33
|
+
# 旧 LIBRARIES には重複エントリがある(webrick/httputils)ため uniq
|
|
34
|
+
BitClust::Preprocessor.read(File.join(root, 'LIBRARIES'), params)
|
|
35
|
+
.lines.map(&:strip).reject(&:empty?).uniq
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ライブラリを単体パースし、{[type, class名] => 整列済みエントリ名} を返す
|
|
39
|
+
def structure(root, lib, params)
|
|
40
|
+
db = BitClust::MethodDatabase.dummy(params)
|
|
41
|
+
library = BitClust::RRDParser.new(db).parse_file(File.join(root, "#{lib}.rd"), lib, params)
|
|
42
|
+
library.classes.to_h do |c|
|
|
43
|
+
[[c.type.to_s, c.name], c.entries.flat_map(&:names).sort]
|
|
44
|
+
end
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
{ error: "#{e.class}: #{e.message}" }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Dir.mktmpdir do |bridge|
|
|
50
|
+
t0 = Time.now
|
|
51
|
+
BitClust::MarkdownBridge.build(md_root, bridge)
|
|
52
|
+
puts "bridge built in #{(Time.now - t0).round(1)}s"
|
|
53
|
+
|
|
54
|
+
old_libs = library_names(src_root, params)
|
|
55
|
+
new_libs = library_names(bridge, params)
|
|
56
|
+
puts "library set @#{params['version']}: " \
|
|
57
|
+
"#{old_libs.sort == new_libs.sort ? 'OK' : 'MISMATCH'} " \
|
|
58
|
+
"(old #{old_libs.size}, new #{new_libs.size})"
|
|
59
|
+
(old_libs - new_libs).each { |l| puts " missing in bridge: #{l}" }
|
|
60
|
+
(new_libs - old_libs).each { |l| puts " extra in bridge: #{l}" }
|
|
61
|
+
|
|
62
|
+
ok = 0
|
|
63
|
+
diffs = []
|
|
64
|
+
errors = []
|
|
65
|
+
(old_libs & new_libs).sort.each do |lib|
|
|
66
|
+
old_s = structure(src_root, lib, params)
|
|
67
|
+
new_s = structure(bridge, lib, params)
|
|
68
|
+
if old_s[:error] || new_s[:error]
|
|
69
|
+
if old_s[:error].to_s == new_s[:error].to_s
|
|
70
|
+
ok += 1 # 両側同一条件で失敗(dummy DB 都合等)は等価とみなす
|
|
71
|
+
else
|
|
72
|
+
errors << [lib, old_s[:error], new_s[:error]]
|
|
73
|
+
end
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
if old_s == new_s
|
|
77
|
+
ok += 1
|
|
78
|
+
puts "OK #{lib}" if verbose
|
|
79
|
+
else
|
|
80
|
+
diffs << lib
|
|
81
|
+
puts "DIFF #{lib}"
|
|
82
|
+
(old_s.keys - new_s.keys).each { |k| puts " only in old: #{k.inspect}" }
|
|
83
|
+
(new_s.keys - old_s.keys).each { |k| puts " only in new: #{k.inspect}" }
|
|
84
|
+
(old_s.keys & new_s.keys).each do |k|
|
|
85
|
+
next if old_s[k] == new_s[k]
|
|
86
|
+
puts " #{k.inspect}: entries differ"
|
|
87
|
+
puts " only old: #{(old_s[k] - new_s[k]).first(5).inspect}"
|
|
88
|
+
puts " only new: #{(new_s[k] - old_s[k]).first(5).inspect}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
puts "#{ok}/#{(old_libs & new_libs).size} libraries structurally identical"
|
|
93
|
+
errors.each { |lib, o, n| puts " ERROR #{lib}: old=#{o.inspect} new=#{n.inspect}" }
|
|
94
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# MDCompiler の HTML 等価検証(ネイティブ MD 描画 M1)。
|
|
5
|
+
# データベースの全エントリについて、
|
|
6
|
+
# rd source → RDCompiler → HTML(リファレンス)
|
|
7
|
+
# rd source → RRDToMarkdown → md source → MDCompiler → HTML
|
|
8
|
+
# が一致することを確認する。fragment の md→rd ラウンドトリップも同時に検証。
|
|
9
|
+
#
|
|
10
|
+
# usage: ruby tools/md-compile-check.rb <db-path> [--limit N] [-v]
|
|
11
|
+
# [--only methods|docs|libs|functions] [--shard K/N] [--gfm]
|
|
12
|
+
#
|
|
13
|
+
# メモリの少ないマシンでは 1 プロセスで全件を回さず、--only/--shard で
|
|
14
|
+
# 分割して直列に実行する(例: --only methods --shard 0/4 ... 3/4)。
|
|
15
|
+
#
|
|
16
|
+
# --gfm: M2 GFM モードの整合も検証する。GFM 出力と M1 出力の差が
|
|
17
|
+
# <code>/<strong>/GNU 引用(`x')の正規化で消えることを確認する
|
|
18
|
+
# (= GFM モードが追加するのは表現マークアップだけで内容は同一)。
|
|
19
|
+
|
|
20
|
+
$stdout.sync = true
|
|
21
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
22
|
+
require 'bitclust'
|
|
23
|
+
require 'bitclust/rdcompiler'
|
|
24
|
+
require 'bitclust/mdcompiler'
|
|
25
|
+
require 'bitclust/rrd_to_markdown'
|
|
26
|
+
require 'bitclust/markdown_to_rrd'
|
|
27
|
+
require 'bitclust/capi_converter'
|
|
28
|
+
|
|
29
|
+
limit = nil
|
|
30
|
+
verbose = false
|
|
31
|
+
only = nil
|
|
32
|
+
shard_k = nil
|
|
33
|
+
shard_n = nil
|
|
34
|
+
gfm_mode = false
|
|
35
|
+
paths = []
|
|
36
|
+
args = ARGV.dup
|
|
37
|
+
until args.empty?
|
|
38
|
+
case (a = args.shift)
|
|
39
|
+
when '--limit' then limit = args.shift.to_i
|
|
40
|
+
when '--only' then only = args.shift
|
|
41
|
+
when '--shard'
|
|
42
|
+
shard_k, shard_n = (args.shift || '').split('/').map(&:to_i)
|
|
43
|
+
when '--gfm' then gfm_mode = true
|
|
44
|
+
when '-v' then verbose = true
|
|
45
|
+
else paths << a
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
db_path = paths.shift or abort "usage: #{$0} <db-path> [--limit N]"
|
|
49
|
+
|
|
50
|
+
run = ->(kind) { only.nil? || only == kind }
|
|
51
|
+
in_shard = ->(i) { shard_n.nil? || i % shard_n == shard_k }
|
|
52
|
+
|
|
53
|
+
db = BitClust::MethodDatabase.new(db_path)
|
|
54
|
+
urlmapper = BitClust::URLMapper.new(Hash.new { 'dummy' })
|
|
55
|
+
rd = BitClust::RDCompiler.new(urlmapper, 1, { database: db })
|
|
56
|
+
md = BitClust::MDCompiler.new(urlmapper, 1, { database: db })
|
|
57
|
+
mdg = BitClust::MDCompiler.new(urlmapper, 1, { database: db, gfm: true })
|
|
58
|
+
|
|
59
|
+
stats = Hash.new(0)
|
|
60
|
+
diffs = []
|
|
61
|
+
|
|
62
|
+
# GFM が追加する表現マークアップを落として M1 と比較するための正規化
|
|
63
|
+
normalize_gfm = lambda do |html|
|
|
64
|
+
html.gsub(%r{</?code>|</?strong>|<(th|td) align="[a-z]+">}, '')
|
|
65
|
+
.gsub(%r{</?(?:table|thead|tbody|tr|th|td)>}, '')
|
|
66
|
+
.gsub(/`([^`'\s]+)'/) { $1 }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
check = lambda do |label, kind, rd_src, ref_html|
|
|
70
|
+
md_src = kind == :function ? BitClust::CapiConverter.convert(rd_src)
|
|
71
|
+
: BitClust::RRDToMarkdown.convert(rd_src)
|
|
72
|
+
back = BitClust::MarkdownToRRD.convert(md_src, capi: kind == :function)
|
|
73
|
+
stats[:rt_diff] += 1 if back != rd_src
|
|
74
|
+
|
|
75
|
+
html =
|
|
76
|
+
case kind
|
|
77
|
+
when :method
|
|
78
|
+
# 実エントリの source を一時差し替え(save しないので永続化されない)
|
|
79
|
+
entry = stats[:current_entry]
|
|
80
|
+
original = entry.source
|
|
81
|
+
begin
|
|
82
|
+
entry.source = md_src
|
|
83
|
+
md.compile_method(entry, nil)
|
|
84
|
+
ensure
|
|
85
|
+
entry.source = original
|
|
86
|
+
end
|
|
87
|
+
when :function
|
|
88
|
+
entry = stats[:current_entry]
|
|
89
|
+
original = entry.source
|
|
90
|
+
begin
|
|
91
|
+
entry.source = md_src
|
|
92
|
+
md.compile_function(entry, nil)
|
|
93
|
+
ensure
|
|
94
|
+
entry.source = original
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
md.compile(md_src)
|
|
98
|
+
end
|
|
99
|
+
if gfm_mode
|
|
100
|
+
gfm_html =
|
|
101
|
+
case kind
|
|
102
|
+
when :method, :function
|
|
103
|
+
entry = stats[:current_entry]
|
|
104
|
+
original = entry.source
|
|
105
|
+
begin
|
|
106
|
+
entry.source = md_src
|
|
107
|
+
kind == :method ? mdg.compile_method(entry, nil) : mdg.compile_function(entry, nil)
|
|
108
|
+
ensure
|
|
109
|
+
entry.source = original
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
mdg.compile(md_src)
|
|
113
|
+
end
|
|
114
|
+
if normalize_gfm.call(gfm_html) == normalize_gfm.call(html)
|
|
115
|
+
stats[:gfm_ok] += 1
|
|
116
|
+
else
|
|
117
|
+
stats[:gfm_diff] += 1
|
|
118
|
+
if stats[:gfm_diff] <= 10
|
|
119
|
+
puts "GFM-DIFF #{label}"
|
|
120
|
+
normalize_gfm.call(html).lines.zip(normalize_gfm.call(gfm_html).lines).each_with_index do |(a, b), i|
|
|
121
|
+
next if a == b
|
|
122
|
+
puts " line #{i + 1}: M1 =#{a.inspect}"
|
|
123
|
+
puts " GFM=#{b.inspect}"
|
|
124
|
+
break
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if html == ref_html
|
|
131
|
+
stats[:ok] += 1
|
|
132
|
+
puts "OK #{label}" if verbose
|
|
133
|
+
else
|
|
134
|
+
stats[:diff] += 1
|
|
135
|
+
diffs << label
|
|
136
|
+
if diffs.size <= 10
|
|
137
|
+
puts "DIFF #{label}"
|
|
138
|
+
ref_html.lines.zip(html.lines).each_with_index do |(a, b), i|
|
|
139
|
+
next if a == b
|
|
140
|
+
puts " line #{i + 1}: RD=#{a.inspect}"
|
|
141
|
+
puts " MD=#{b.inspect}"
|
|
142
|
+
break
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
stats[:error] += 1
|
|
148
|
+
diffs << label
|
|
149
|
+
puts "ERROR #{label}: #{e.class}: #{e.message}" if stats[:error] <= 10
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
count = 0
|
|
153
|
+
db.classes.sort_by(&:name).each_with_index do |c, ci|
|
|
154
|
+
break unless run.call('methods')
|
|
155
|
+
next unless in_shard.call(ci)
|
|
156
|
+
c.entries.sort_by { |e| e.names.join }.each do |m|
|
|
157
|
+
break if limit && count >= limit
|
|
158
|
+
count += 1
|
|
159
|
+
ref = begin
|
|
160
|
+
rd.compile_method(m, nil)
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
stats[:ref_error] += 1
|
|
163
|
+
puts "REF-ERROR #{c.name}##{m.names.first}: #{e.class}: #{e.message}" if stats[:ref_error] <= 5
|
|
164
|
+
next
|
|
165
|
+
end
|
|
166
|
+
stats[:current_entry] = m
|
|
167
|
+
check.call("#{c.name}##{m.names.first}", :method, m.source, ref)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
db.docs.sort_by(&:name).each do |d|
|
|
172
|
+
break unless run.call('docs')
|
|
173
|
+
ref = begin
|
|
174
|
+
rd.compile(d.source)
|
|
175
|
+
rescue StandardError
|
|
176
|
+
stats[:ref_error] += 1
|
|
177
|
+
next
|
|
178
|
+
end
|
|
179
|
+
check.call("doc:#{d.name}", :doc, d.source, ref)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
db.libraries.sort_by(&:name).each do |l|
|
|
183
|
+
break unless run.call('libs')
|
|
184
|
+
next if l.source.strip.empty?
|
|
185
|
+
ref = begin
|
|
186
|
+
rd.compile(l.source)
|
|
187
|
+
rescue StandardError
|
|
188
|
+
stats[:ref_error] += 1
|
|
189
|
+
next
|
|
190
|
+
end
|
|
191
|
+
check.call("lib:#{l.name}", :doc, l.source, ref)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
begin
|
|
195
|
+
fdb = BitClust::FunctionDatabase.new(db_path) if run.call('functions')
|
|
196
|
+
fdb ||= nil
|
|
197
|
+
(fdb ? fdb.functions.sort_by(&:name) : []).each do |f|
|
|
198
|
+
ref = begin
|
|
199
|
+
rd.compile_function(f, nil)
|
|
200
|
+
rescue StandardError
|
|
201
|
+
stats[:ref_error] += 1
|
|
202
|
+
next
|
|
203
|
+
end
|
|
204
|
+
stats[:current_entry] = f
|
|
205
|
+
check.call("capi:#{f.name}", :function, f.source, ref)
|
|
206
|
+
end
|
|
207
|
+
rescue StandardError
|
|
208
|
+
puts '(no function database)'
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
puts "compared: #{stats[:ok] + stats[:diff]}, identical: #{stats[:ok]}, " \
|
|
212
|
+
"diffs: #{stats[:diff]}, errors: #{stats[:error]}, " \
|
|
213
|
+
"ref-errors(skipped): #{stats[:ref_error]}, fragment-roundtrip diffs: #{stats[:rt_diff]}"
|
|
214
|
+
puts "gfm: consistent: #{stats[:gfm_ok]}, diffs: #{stats[:gfm_diff]}" if gfm_mode
|
|
215
|
+
diffs.first(30).each { |d| puts " DIFF #{d}" } if stats[:diff] > 10
|
|
216
|
+
ok = stats[:diff].zero? && stats[:error].zero? && stats[:gfm_diff].zero?
|
|
217
|
+
puts ok ? 'HTML EQUIVALENT' : 'NOT EQUIVALENT'
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# 2つの BitClust データベースを比較する(md 移行の DB レベル等価検証)。
|
|
5
|
+
# ライブラリ・クラス・メソッドの集合と、全エントリの本文(source)まで突き合わせる。
|
|
6
|
+
#
|
|
7
|
+
# usage: ruby tools/md-db-check.rb <db-old> <db-new>
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
10
|
+
require 'bitclust'
|
|
11
|
+
|
|
12
|
+
old_path, new_path = ARGV
|
|
13
|
+
abort "usage: #{$0} <db-old> <db-new>" unless old_path && new_path
|
|
14
|
+
|
|
15
|
+
old_db = BitClust::MethodDatabase.new(old_path)
|
|
16
|
+
new_db = BitClust::MethodDatabase.new(new_path)
|
|
17
|
+
|
|
18
|
+
diffs = 0
|
|
19
|
+
report = lambda do |msg|
|
|
20
|
+
diffs += 1
|
|
21
|
+
puts " DIFF #{msg}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
compare_sets = lambda do |label, old_set, new_set|
|
|
25
|
+
only_old = old_set - new_set
|
|
26
|
+
only_new = new_set - old_set
|
|
27
|
+
only_old.each { |x| report.call("#{label} only in old: #{x}") }
|
|
28
|
+
only_new.each { |x| report.call("#{label} only in new: #{x}") }
|
|
29
|
+
old_set & new_set
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# ライブラリ
|
|
33
|
+
old_libs = old_db.libraries.to_h { |l| [l.name, l] }
|
|
34
|
+
new_libs = new_db.libraries.to_h { |l| [l.name, l] }
|
|
35
|
+
common_libs = compare_sets.call('library', old_libs.keys.sort, new_libs.keys.sort)
|
|
36
|
+
puts "libraries: #{common_libs.size} common"
|
|
37
|
+
|
|
38
|
+
lib_field_diffs = 0
|
|
39
|
+
common_libs.each do |name|
|
|
40
|
+
o, n = old_libs[name], new_libs[name]
|
|
41
|
+
lib_field_diffs += 1 if o.requires.map(&:name).sort != n.requires.map(&:name).sort
|
|
42
|
+
lib_field_diffs += 1 if o.sublibraries.map(&:name).sort != n.sublibraries.map(&:name).sort
|
|
43
|
+
report.call("library #{name}: source differs") if o.source.strip != n.source.strip
|
|
44
|
+
end
|
|
45
|
+
puts "library require/sublibrary diffs: #{lib_field_diffs}"
|
|
46
|
+
|
|
47
|
+
# クラスとエントリ
|
|
48
|
+
old_classes = old_db.classes.to_h { |c| [c.name, c] }
|
|
49
|
+
new_classes = new_db.classes.to_h { |c| [c.name, c] }
|
|
50
|
+
common = compare_sets.call('class', old_classes.keys.sort, new_classes.keys.sort)
|
|
51
|
+
puts "classes: #{common.size} common"
|
|
52
|
+
|
|
53
|
+
entry_count = 0
|
|
54
|
+
ws_only = 0
|
|
55
|
+
sig_spacing_only = 0
|
|
56
|
+
source_diffs = []
|
|
57
|
+
# 「---name」(スペース無しシグネチャ)は正規形「--- name」へ寄せる
|
|
58
|
+
# (RRDParser/RDCompiler はどちらも受理。openssl の8箇所)
|
|
59
|
+
normalize_sig = ->(src) { src.gsub(/^---(?=[^\s-])/, '--- ') }
|
|
60
|
+
common.each do |name|
|
|
61
|
+
o, n = old_classes[name], new_classes[name]
|
|
62
|
+
report.call("class #{name}: type #{o.type} vs #{n.type}") if o.type != n.type
|
|
63
|
+
report.call("class #{name}: superclass differs") if o.superclass&.name != n.superclass&.name
|
|
64
|
+
report.call("class #{name}: source differs") if o.source.strip != n.source.strip
|
|
65
|
+
|
|
66
|
+
o_entries = o.entries.to_h { |e| [e.names.sort.join(','), e] }
|
|
67
|
+
n_entries = n.entries.to_h { |e| [e.names.sort.join(','), e] }
|
|
68
|
+
common_entries = compare_sets.call("#{name} entry", o_entries.keys.sort, n_entries.keys.sort)
|
|
69
|
+
common_entries.each do |key|
|
|
70
|
+
entry_count += 1
|
|
71
|
+
next if o_entries[key].source == n_entries[key].source
|
|
72
|
+
if o_entries[key].source.rstrip == n_entries[key].source.rstrip
|
|
73
|
+
ws_only += 1 # 末尾空白のみ(描画に影響しない)
|
|
74
|
+
elsif normalize_sig.call(o_entries[key].source) == n_entries[key].source
|
|
75
|
+
sig_spacing_only += 1
|
|
76
|
+
else
|
|
77
|
+
source_diffs << "#{name}##{key}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
puts "entries compared: #{entry_count}, real source diffs: #{source_diffs.size}, " \
|
|
82
|
+
"trailing-whitespace-only: #{ws_only}, signature-spacing-only: #{sig_spacing_only}"
|
|
83
|
+
source_diffs.first(10).each { |s| puts " SOURCE DIFF #{s}" }
|
|
84
|
+
|
|
85
|
+
# doc(散文ページ)。md 経路は DocConverter.reduce の正規化(末尾スペース・
|
|
86
|
+
# タブ・定義リスト空白等、意味を変えない表記ゆれ)を通るため、
|
|
87
|
+
# 旧側も reduce に通した上での一致を「正規化後一致」として別集計する
|
|
88
|
+
require 'bitclust/doc_converter'
|
|
89
|
+
old_docs = old_db.docs.to_h { |d| [d.name, d] }
|
|
90
|
+
new_docs = new_db.docs.to_h { |d| [d.name, d] }
|
|
91
|
+
common_docs = compare_sets.call('doc', old_docs.keys.sort, new_docs.keys.sort)
|
|
92
|
+
doc_normalized = 0
|
|
93
|
+
doc_diffs = common_docs.count do |k|
|
|
94
|
+
next false if old_docs[k].source == new_docs[k].source
|
|
95
|
+
if BitClust::DocConverter.reduce(old_docs[k].source) == new_docs[k].source
|
|
96
|
+
doc_normalized += 1
|
|
97
|
+
false
|
|
98
|
+
else
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
puts "docs: #{common_docs.size} common, real source diffs: #{doc_diffs}, " \
|
|
103
|
+
"normalized-only: #{doc_normalized}"
|
|
104
|
+
|
|
105
|
+
if diffs.zero? && source_diffs.empty? && doc_diffs.zero?
|
|
106
|
+
notes = []
|
|
107
|
+
notes << "#{ws_only} trailing-whitespace-only" if ws_only.positive?
|
|
108
|
+
notes << "#{sig_spacing_only} signature-spacing-only" if sig_spacing_only.positive?
|
|
109
|
+
puts "DATABASES EQUIVALENT#{notes.empty? ? '' : " (#{notes.join(', ')} diffs)"}"
|
|
110
|
+
else
|
|
111
|
+
puts "TOTAL STRUCTURAL DIFFS: #{diffs + source_diffs.size + doc_diffs}"
|
|
112
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# MDParser のネイティブ md パース検証(M3)。
|
|
5
|
+
# manual/api をネイティブパースした DB と、ブリッジ経由で構築した DB を比較する:
|
|
6
|
+
# (1) library/class/entry の集合と属性が一致
|
|
7
|
+
# (2) native の entry.source(md)を md→rd 変換するとブリッジの source(rd)に一致
|
|
8
|
+
#
|
|
9
|
+
# usage: ruby tools/md-parse-check.rb <manual/api> <bridge-db> [--keep DIR]
|
|
10
|
+
# ruby tools/md-parse-check.rb --capi <manual/capi> <bridge-db>
|
|
11
|
+
|
|
12
|
+
$stdout.sync = true
|
|
13
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
14
|
+
require 'bitclust'
|
|
15
|
+
require 'bitclust/markdown_to_rrd'
|
|
16
|
+
require 'tmpdir'
|
|
17
|
+
|
|
18
|
+
capi = ARGV.delete('--capi')
|
|
19
|
+
md_root = ARGV[0] or abort "usage: #{$0} <manual/api> <bridge-db>"
|
|
20
|
+
bridge_path = ARGV[1] or abort "usage: #{$0} <manual/api> <bridge-db>"
|
|
21
|
+
keep_dir = (i = ARGV.index('--keep')) ? ARGV[i + 1] : nil
|
|
22
|
+
|
|
23
|
+
if capi
|
|
24
|
+
require 'bitclust/capi_converter'
|
|
25
|
+
bridge = BitClust::FunctionDatabase.new(bridge_path)
|
|
26
|
+
version = bridge.properties['version']
|
|
27
|
+
native_dir = Dir.mktmpdir('md-parse-check-capi')
|
|
28
|
+
# init は MethodDatabase 側にのみある(properties 作成のため一時利用)
|
|
29
|
+
seed = BitClust::MethodDatabase.new(native_dir)
|
|
30
|
+
seed.init
|
|
31
|
+
seed.transaction do
|
|
32
|
+
seed.propset 'version', version
|
|
33
|
+
seed.propset 'encoding', bridge.properties['encoding']
|
|
34
|
+
end
|
|
35
|
+
native = BitClust::FunctionDatabase.new(native_dir)
|
|
36
|
+
puts "native capi parse (version #{version})..."
|
|
37
|
+
native.transaction { native.update_by_markdowntree(md_root) }
|
|
38
|
+
native = BitClust::FunctionDatabase.new(native_dir)
|
|
39
|
+
|
|
40
|
+
bf = bridge.functions.to_h { |f| [f.name, f] }
|
|
41
|
+
nf = native.functions.to_h { |f| [f.name, f] }
|
|
42
|
+
diffs = 0
|
|
43
|
+
puts "DIFF function set: missing=#{(bf.keys - nf.keys).first(5)} extra=#{(nf.keys - bf.keys).first(5)}" or diffs += 1 if bf.keys.sort != nf.keys.sort
|
|
44
|
+
src_diffs = 0
|
|
45
|
+
(bf.keys & nf.keys).each do |name|
|
|
46
|
+
b, n = bf[name], nf[name]
|
|
47
|
+
%i[filename macro private type params].each do |attr|
|
|
48
|
+
if b.send(attr) != n.send(attr)
|
|
49
|
+
diffs += 1
|
|
50
|
+
puts "DIFF #{name}: #{attr} #{b.send(attr).inspect} vs #{n.send(attr).inspect}" if diffs <= 10
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
converted = BitClust::MarkdownToRRD.convert(n.source.to_s, capi: true)
|
|
54
|
+
.gsub(/^\#[@%]samplecode(?: (.*))?$/) { "//emlist[#{$1&.strip}][ruby]{" }
|
|
55
|
+
.gsub(/^\#[@%]end[ \t]*$/, '//}')
|
|
56
|
+
if converted.rstrip != b.source.to_s.rstrip
|
|
57
|
+
src_diffs += 1
|
|
58
|
+
puts "SRC-DIFF #{name}" if src_diffs <= 5
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
puts "functions: #{(bf.keys & nf.keys).size}, attribute diffs: #{diffs}, source diffs: #{src_diffs}"
|
|
62
|
+
puts diffs.zero? && src_diffs.zero? ? 'NATIVE CAPI PARSE EQUIVALENT' : 'NOT EQUIVALENT'
|
|
63
|
+
FileUtils.remove_entry(native_dir)
|
|
64
|
+
exit(diffs.zero? && src_diffs.zero? ? 0 : 1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
bridge = BitClust::MethodDatabase.new(bridge_path)
|
|
68
|
+
version = bridge.properties['version']
|
|
69
|
+
|
|
70
|
+
native_dir = keep_dir || Dir.mktmpdir('md-parse-check')
|
|
71
|
+
native = BitClust::MethodDatabase.new(native_dir)
|
|
72
|
+
unless File.exist?(File.join(native_dir, 'properties'))
|
|
73
|
+
native.init
|
|
74
|
+
native.transaction do
|
|
75
|
+
native.propset 'version', version
|
|
76
|
+
native.propset 'encoding', bridge.properties['encoding']
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
puts "native parse (version #{version})..."
|
|
80
|
+
native.transaction do
|
|
81
|
+
native.update_by_markdowntree(md_root)
|
|
82
|
+
end
|
|
83
|
+
# ブリッジ側(ディスクから読む)と条件を揃える。require が作る未保存の
|
|
84
|
+
# スタブライブラリ等、メモリ上にしか無いものを比較に混ぜない
|
|
85
|
+
native = BitClust::MethodDatabase.new(native_dir)
|
|
86
|
+
|
|
87
|
+
diffs = 0
|
|
88
|
+
report = ->(msg) { diffs += 1; puts "DIFF #{msg}" if diffs <= 30 }
|
|
89
|
+
|
|
90
|
+
# ブリッジ DB の source は Preprocessor 通過後(#@samplecode → //emlist 済み)。
|
|
91
|
+
# native md → rd 変換の生形式を同じ形へ正規化して比較する
|
|
92
|
+
to_rd = lambda do |md_src|
|
|
93
|
+
BitClust::MarkdownToRRD.convert(md_src)
|
|
94
|
+
.gsub(/^\#[@%]samplecode(?: (.*))?$/) { "//emlist[#{$1&.strip}][ruby]{" }
|
|
95
|
+
.gsub(/^\#[@%]end[ \t]*$/, '//}')
|
|
96
|
+
.rstrip
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# (1) ライブラリ
|
|
100
|
+
bl = bridge.libraries.to_h { |l| [l.name, l] }
|
|
101
|
+
nl = native.libraries.to_h { |l| [l.name, l] }
|
|
102
|
+
report.call("library set: missing=#{(bl.keys - nl.keys).size} extra=#{(nl.keys - bl.keys).size}: #{((bl.keys - nl.keys) + (nl.keys - bl.keys)).first(5)}") if bl.keys.sort != nl.keys.sort
|
|
103
|
+
common_libs = bl.keys & nl.keys
|
|
104
|
+
common_libs.each do |name|
|
|
105
|
+
b, n = bl[name], nl[name]
|
|
106
|
+
report.call("lib #{name}: category #{b.category.inspect} vs #{n.category.inspect}") if b.category != n.category
|
|
107
|
+
report.call("lib #{name}: requires #{b.requires.map(&:name)} vs #{n.requires.map(&:name)}") if b.requires.map(&:name).sort != n.requires.map(&:name).sort
|
|
108
|
+
report.call("lib #{name}: sublibraries") if b.sublibraries.map(&:name).sort != n.sublibraries.map(&:name).sort
|
|
109
|
+
report.call("lib #{name}: classes #{(b.classes.map(&:name) - n.classes.map(&:name)).first(3)} / #{(n.classes.map(&:name) - b.classes.map(&:name)).first(3)}") if b.classes.map(&:name).sort != n.classes.map(&:name).sort
|
|
110
|
+
if to_rd.call(n.source.to_s + "\n") != b.source.to_s.rstrip
|
|
111
|
+
report.call("lib #{name}: source differs")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# (2) クラスとエントリ
|
|
116
|
+
bc = bridge.classes.to_h { |c| [c.name, c] }
|
|
117
|
+
nc = native.classes.to_h { |c| [c.name, c] }
|
|
118
|
+
report.call("class set: missing=#{(bc.keys - nc.keys).first(5)} extra=#{(nc.keys - bc.keys).first(5)}") if bc.keys.sort != nc.keys.sort
|
|
119
|
+
entry_count = 0
|
|
120
|
+
src_diffs = 0
|
|
121
|
+
(bc.keys & nc.keys).each do |name|
|
|
122
|
+
b, n = bc[name], nc[name]
|
|
123
|
+
report.call("class #{name}: type #{b.type} vs #{n.type}") if b.type != n.type
|
|
124
|
+
report.call("class #{name}: superclass") if b.superclass&.name != n.superclass&.name
|
|
125
|
+
report.call("class #{name}: included #{b.included.map(&:name)} vs #{n.included.map(&:name)}") if b.included.map(&:name) != n.included.map(&:name)
|
|
126
|
+
report.call("class #{name}: extended") if b.extended.map(&:name) != n.extended.map(&:name)
|
|
127
|
+
report.call("class #{name}: dyn-included") if b.dynamically_included.map(&:name).sort != n.dynamically_included.map(&:name).sort
|
|
128
|
+
if to_rd.call(n.source.to_s + "\n") != b.source.to_s.rstrip
|
|
129
|
+
report.call("class #{name}: source differs")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
be = b.entries.to_h { |e| [e.names.sort.join(','), e] }
|
|
133
|
+
ne = n.entries.to_h { |e| [e.names.sort.join(','), e] }
|
|
134
|
+
report.call("#{name} entries: missing=#{(be.keys - ne.keys).first(3)} extra=#{(ne.keys - be.keys).first(3)}") if be.keys.sort != ne.keys.sort
|
|
135
|
+
(be.keys & ne.keys).each do |key|
|
|
136
|
+
entry_count += 1
|
|
137
|
+
x, y = be[key], ne[key]
|
|
138
|
+
report.call("#{name}##{key}: type #{x.type} vs #{y.type}") if x.type != y.type
|
|
139
|
+
report.call("#{name}##{key}: visibility") if x.visibility != y.visibility
|
|
140
|
+
if to_rd.call(y.source.to_s) != x.source.to_s.rstrip
|
|
141
|
+
src_diffs += 1
|
|
142
|
+
if src_diffs <= 5
|
|
143
|
+
puts "SRC-DIFF #{name}##{key}:"
|
|
144
|
+
a = x.source.to_s.rstrip.lines
|
|
145
|
+
c = to_rd.call(y.source.to_s).lines
|
|
146
|
+
a.zip(c).each_with_index do |(p, q), i|
|
|
147
|
+
next if p == q
|
|
148
|
+
puts " line #{i + 1}: BRIDGE #{p.inspect}"
|
|
149
|
+
puts " NATIVE #{q.inspect}"
|
|
150
|
+
break
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# (3) doc ページ
|
|
158
|
+
bd = bridge.docs.to_h { |d| [d.name, d] }
|
|
159
|
+
nd = native.docs.to_h { |d| [d.name, d] }
|
|
160
|
+
report.call("doc set: missing=#{(bd.keys - nd.keys).first(5)} extra=#{(nd.keys - bd.keys).first(5)}") if bd.keys.sort != nd.keys.sort
|
|
161
|
+
doc_src_diffs = 0
|
|
162
|
+
(bd.keys & nd.keys).each do |name|
|
|
163
|
+
b, n = bd[name], nd[name]
|
|
164
|
+
report.call("doc #{name}: title #{b.title.inspect} vs #{n.title.inspect}") if b.title != n.title
|
|
165
|
+
if to_rd.call(n.source.to_s) != b.source.to_s.rstrip
|
|
166
|
+
doc_src_diffs += 1
|
|
167
|
+
if doc_src_diffs <= 3
|
|
168
|
+
puts "DOC-SRC-DIFF #{name}:"
|
|
169
|
+
a = b.source.to_s.rstrip.lines
|
|
170
|
+
c = to_rd.call(n.source.to_s).lines
|
|
171
|
+
a.zip(c).each_with_index do |(p, q), i|
|
|
172
|
+
next if p == q
|
|
173
|
+
puts " line #{i + 1}: BRIDGE #{p.inspect}"
|
|
174
|
+
puts " NATIVE #{q.inspect}"
|
|
175
|
+
break
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
puts "libraries: #{common_libs.size}, classes: #{(bc.keys & nc.keys).size}, " \
|
|
182
|
+
"entries: #{entry_count}, docs: #{(bd.keys & nd.keys).size}"
|
|
183
|
+
puts "attribute diffs: #{diffs}, source diffs (md→rd 変換後): #{src_diffs}, doc source diffs: #{doc_src_diffs}"
|
|
184
|
+
puts diffs.zero? && src_diffs.zero? && doc_src_diffs.zero? ? 'NATIVE PARSE EQUIVALENT' : 'NOT EQUIVALENT'
|
|
185
|
+
FileUtils.remove_entry(native_dir) unless keep_dir
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# Markdown 変換のラウンドトリップ検証。
|
|
5
|
+
# refm/api/src の全ファイルについて rd → md → rd がバイト一致するかを確認する。
|
|
6
|
+
#
|
|
7
|
+
# usage: ruby tools/md-roundtrip-check.rb [options] <doctree-root>
|
|
8
|
+
# --with-doc refm/doc/**/*.rd も検証する(DocConverter の reduce 基準)
|
|
9
|
+
# --with-capi refm/capi/src/*.rd も検証する(CapiConverter)
|
|
10
|
+
# --inject MarkdownOrchestrator の変換(prune・全体ゲート解除・front matter 注入)で
|
|
11
|
+
# 検証する: md → rd が reduce 後の rd を復元すること、および
|
|
12
|
+
# md に grouping include が残っていないことを確認する
|
|
13
|
+
# --diff 差分のあったファイルの先頭差分行を表示する
|
|
14
|
+
# -v 全ファイルの結果を表示する
|
|
15
|
+
|
|
16
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
17
|
+
require 'bitclust/rrd_to_markdown'
|
|
18
|
+
require 'bitclust/markdown_to_rrd'
|
|
19
|
+
require 'bitclust/markdown_orchestrator'
|
|
20
|
+
require 'bitclust/doc_converter'
|
|
21
|
+
require 'bitclust/capi_converter'
|
|
22
|
+
|
|
23
|
+
with_doc = ARGV.delete('--with-doc')
|
|
24
|
+
with_capi = ARGV.delete('--with-capi')
|
|
25
|
+
inject = ARGV.delete('--inject')
|
|
26
|
+
show_diff = ARGV.delete('--diff')
|
|
27
|
+
verbose = ARGV.delete('-v')
|
|
28
|
+
scope_arg = nil
|
|
29
|
+
if (i = ARGV.index('--scope'))
|
|
30
|
+
ARGV.delete_at(i)
|
|
31
|
+
scope_arg = ARGV.delete_at(i) or abort '--scope requires LO,HI'
|
|
32
|
+
end
|
|
33
|
+
doctree = ARGV.shift or abort "usage: #{$0} [--with-doc] [--inject] [--scope LO,HI] [--diff] [-v] <doctree-root>"
|
|
34
|
+
|
|
35
|
+
src_root = File.join(doctree, 'refm/api/src')
|
|
36
|
+
files = Dir.glob('**/*', base: src_root).select { |f| File.file?(File.join(src_root, f)) }
|
|
37
|
+
files -= ['LIBRARIES']
|
|
38
|
+
|
|
39
|
+
orchestrator = nil
|
|
40
|
+
prune_sites = {}
|
|
41
|
+
if inject
|
|
42
|
+
opts = {}
|
|
43
|
+
if scope_arg
|
|
44
|
+
opts[:scope] = BitClust::IncludeGraph::Scope.new(*scope_arg.split(','))
|
|
45
|
+
end
|
|
46
|
+
orchestrator = BitClust::MarkdownOrchestrator.new(src_root, **opts)
|
|
47
|
+
orchestrator.warnings.each { |w| warn "W: #{w}" }
|
|
48
|
+
prune_sites = orchestrator.graph.grouping_include_sites
|
|
49
|
+
puts "prune: #{prune_sites.size} files"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
targets = files.map { |f| [File.join(src_root, f), f] }
|
|
53
|
+
if with_doc
|
|
54
|
+
doc_root = File.join(doctree, 'refm/doc')
|
|
55
|
+
# *.rd と拡張子なし(spec/regexp18 等)。news/1.8.0.rd-2 系の
|
|
56
|
+
# 旧分割ファイルは未参照のため対象外
|
|
57
|
+
doc_files = Dir.glob('**/*', base: doc_root).select { |f|
|
|
58
|
+
File.file?(File.join(doc_root, f)) &&
|
|
59
|
+
(f.end_with?('.rd') || !File.basename(f).include?('.'))
|
|
60
|
+
}
|
|
61
|
+
targets += doc_files.map { |f| [File.join(doc_root, f), "doc:#{f}"] }
|
|
62
|
+
end
|
|
63
|
+
if with_capi
|
|
64
|
+
capi_root = File.join(doctree, 'refm/capi/src')
|
|
65
|
+
targets += Dir.glob('*.rd', base: capi_root).map { |f| [File.join(capi_root, f), "capi:#{f}"] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# md 内のエンティティ H1 直後のヘッダ領域に関係行が残っていないか
|
|
69
|
+
# (O3 の不変条件: 関係は front matter のみ)
|
|
70
|
+
def body_relations?(md)
|
|
71
|
+
in_header = false
|
|
72
|
+
md.each_line do |line|
|
|
73
|
+
if line =~ /\A#(?!#)\s*(?:class|module|object|reopen|redefine)\b/
|
|
74
|
+
in_header = true
|
|
75
|
+
elsif in_header
|
|
76
|
+
case line
|
|
77
|
+
when /\A(?:include|extend|alias)\s+\S/ then return true
|
|
78
|
+
when /\A\#[@%]/, /\A\s*\z/ then nil
|
|
79
|
+
else in_header = false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
ok = 0
|
|
87
|
+
units = 0
|
|
88
|
+
failed = []
|
|
89
|
+
leftover = []
|
|
90
|
+
body_rels = []
|
|
91
|
+
targets.sort_by(&:last).each do |full, label|
|
|
92
|
+
rrd = File.read(full)
|
|
93
|
+
outs =
|
|
94
|
+
if orchestrator
|
|
95
|
+
orchestrator.units(label, rrd)
|
|
96
|
+
.map { |u| [u.path, u.rrd, orchestrator.convert_unit(u), u.front_matter] }
|
|
97
|
+
elsif label.start_with?('doc:')
|
|
98
|
+
reduced = BitClust::DocConverter.reduce(rrd)
|
|
99
|
+
[[label, reduced, BitClust::DocConverter.convert(rrd), nil]]
|
|
100
|
+
elsif label.start_with?('capi:')
|
|
101
|
+
[[label, rrd, BitClust::CapiConverter.convert(rrd), nil]]
|
|
102
|
+
else
|
|
103
|
+
[[label, rrd, BitClust::RRDToMarkdown.convert(rrd), nil]]
|
|
104
|
+
end
|
|
105
|
+
outs.each do |path, reduced, md, front_matter|
|
|
106
|
+
units += 1
|
|
107
|
+
ulabel = outs.size > 1 ? "#{label} -> #{path}" : label
|
|
108
|
+
if (sites = prune_sites[label])
|
|
109
|
+
remaining = md.lines.filter_map { |l| $1 if l =~ /\A\#[@%]include\s*\((.*?)\)/ }
|
|
110
|
+
leftover << ulabel unless (remaining & sites).empty?
|
|
111
|
+
end
|
|
112
|
+
# body 関係の不変条件は in-scope(front matter 注入あり)のみ。
|
|
113
|
+
# スコープ外ファイルは凍結形のままなので対象外(サルベージで扱う)
|
|
114
|
+
body_rels << ulabel if front_matter && !front_matter.empty? && body_relations?(md)
|
|
115
|
+
back = BitClust::MarkdownToRRD.convert(md, capi: label.start_with?('capi:'))
|
|
116
|
+
if back == reduced
|
|
117
|
+
ok += 1
|
|
118
|
+
puts "OK #{ulabel}" if verbose
|
|
119
|
+
else
|
|
120
|
+
failed << ulabel
|
|
121
|
+
puts "DIFF #{ulabel}" if verbose || show_diff
|
|
122
|
+
if show_diff
|
|
123
|
+
reduced.lines.zip(back.lines).each_with_index do |(a, b), i|
|
|
124
|
+
next if a == b
|
|
125
|
+
puts " line #{i + 1}: #{a.inspect} -> #{b.inspect}"
|
|
126
|
+
break
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
puts "#{ok}/#{units} byte-exact roundtrip#{inject ? " (reduced base, #{targets.size} sources)" : ''}"
|
|
134
|
+
unless failed.empty?
|
|
135
|
+
puts "failed: #{failed.size}"
|
|
136
|
+
failed.each { |f| puts " #{f}" } unless verbose || show_diff
|
|
137
|
+
end
|
|
138
|
+
unless leftover.empty?
|
|
139
|
+
puts "grouping includes left unpruned: #{leftover.size}"
|
|
140
|
+
leftover.each { |f| puts " #{f}" }
|
|
141
|
+
end
|
|
142
|
+
unless body_rels.empty?
|
|
143
|
+
puts "header relations left in body: #{body_rels.size}"
|
|
144
|
+
body_rels.each { |f| puts " #{f}" }
|
|
145
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
#
|
|
4
|
+
# 変換後の Markdown ツリーの発見・検証(MARKUP_SPEC §1.1)。
|
|
5
|
+
# LIBRARIES を使わず glob + front matter + H1 だけで構成を組み立て、
|
|
6
|
+
# 孤児・関係リント等の警告を報告する。
|
|
7
|
+
#
|
|
8
|
+
# usage: ruby tools/md-tree-check.rb <md-tree-root> [--src <doctree-src-root>]
|
|
9
|
+
# --src を与えると、ソース側 include グラフと突き合わせて
|
|
10
|
+
# - 発見したライブラリ集合が in-scope ライブラリと一致するか
|
|
11
|
+
# - 「library の無いエンティティ」がスコープ外(サルベージ待ち)由来だけか
|
|
12
|
+
# - in-scope エンティティ名の重複が無いか
|
|
13
|
+
# を確認する
|
|
14
|
+
|
|
15
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
16
|
+
require 'bitclust/markdown_tree'
|
|
17
|
+
require 'bitclust/include_graph'
|
|
18
|
+
|
|
19
|
+
md_root = nil
|
|
20
|
+
src_root = nil
|
|
21
|
+
scope_arg = nil
|
|
22
|
+
args = ARGV.dup
|
|
23
|
+
until args.empty?
|
|
24
|
+
a = args.shift
|
|
25
|
+
if a == '--src'
|
|
26
|
+
src_root = args.shift
|
|
27
|
+
elsif a == '--scope'
|
|
28
|
+
scope_arg = args.shift
|
|
29
|
+
else
|
|
30
|
+
md_root = a
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
abort "usage: #{$0} <md-tree-root> [--src <doctree-src-root>] [--scope LO,HI]" unless md_root
|
|
34
|
+
|
|
35
|
+
tree = BitClust::MarkdownTree.scan(md_root)
|
|
36
|
+
entity_count = tree.entities.values.sum { |e| e[:names].size }
|
|
37
|
+
puts "libraries: #{tree.libraries.size}, entity files: #{tree.entities.size} " \
|
|
38
|
+
"(#{entity_count} entities), fragments: #{tree.fragments.size}"
|
|
39
|
+
|
|
40
|
+
no_lib = tree.entities.select { |path, e| e[:library].nil? && !tree.libraries.key?(path.sub(/\.md\z/, '')) }
|
|
41
|
+
puts "entities without library: #{no_lib.size}"
|
|
42
|
+
|
|
43
|
+
if src_root
|
|
44
|
+
graph = BitClust::IncludeGraph.analyze(src_root)
|
|
45
|
+
scope = BitClust::IncludeGraph::Scope.new(*(scope_arg || '3.0,4.2').split(','))
|
|
46
|
+
fm = graph.front_matter_map(scope)
|
|
47
|
+
lib_fm = graph.library_front_matter_map(scope)
|
|
48
|
+
|
|
49
|
+
md_path = ->(rel) { rel.end_with?('.rd') ? rel.sub(/\.rd\z/, '.md') : "#{rel}.md" }
|
|
50
|
+
|
|
51
|
+
# ライブラリ集合のパリティ
|
|
52
|
+
expected_libs = lib_fm.keys.map { |r| r.sub(/\.rd\z/, '') }.sort
|
|
53
|
+
actual_libs = tree.libraries.keys.sort
|
|
54
|
+
puts "library set parity: #{actual_libs == expected_libs ? 'OK' : 'MISMATCH'} " \
|
|
55
|
+
"(expected #{expected_libs.size}, found #{actual_libs.size})"
|
|
56
|
+
(expected_libs - actual_libs).each { |l| puts " missing library: #{l}" }
|
|
57
|
+
(actual_libs - expected_libs).each { |l| puts " unexpected library: #{l}" }
|
|
58
|
+
|
|
59
|
+
# library なしエンティティの期待バケット:
|
|
60
|
+
# (1) スコープ外メンバー(グラフ到達だがスコープ外。サルベージ待ち)
|
|
61
|
+
# (2) スコープ外ライブラリのルート(LIBRARIES ゲートで除外、インライン・エンティティ持ち)
|
|
62
|
+
# (3) ソース孤児(グラフ到達不能の旧世代ファイル)
|
|
63
|
+
out_members = (graph.groupings.keys - fm.keys).map { |r| md_path.call(r) }
|
|
64
|
+
all_lib_roots = File.foreach(File.join(src_root, 'LIBRARIES'))
|
|
65
|
+
.map(&:chomp).reject { |l| l.empty? || l.start_with?('#@', '#%') }
|
|
66
|
+
.map { |n| "#{n}.rd" }.uniq
|
|
67
|
+
out_lib_mds = (all_lib_roots - lib_fm.keys).map { |r| md_path.call(r) }
|
|
68
|
+
reachable = graph.groupings.keys + graph.fragments + all_lib_roots
|
|
69
|
+
src_files = Dir.glob('**/*', base: src_root)
|
|
70
|
+
.select { |f| File.file?(File.join(src_root, f)) } - ['LIBRARIES']
|
|
71
|
+
orphan_mds = (src_files - reachable).map { |r| md_path.call(r) }
|
|
72
|
+
|
|
73
|
+
buckets = { 'out-of-scope member' => out_members,
|
|
74
|
+
'out-of-scope library' => out_lib_mds,
|
|
75
|
+
'source orphan' => orphan_mds }
|
|
76
|
+
rest = no_lib.keys
|
|
77
|
+
buckets.each do |label, set|
|
|
78
|
+
hit = rest & set
|
|
79
|
+
rest -= hit
|
|
80
|
+
puts " #{label}: #{hit.size}"
|
|
81
|
+
end
|
|
82
|
+
puts " UNEXPECTED: #{rest.size}"
|
|
83
|
+
rest.each { |p| puts " #{p}" }
|
|
84
|
+
|
|
85
|
+
# in-scope エンティティ名の重複(reopen/redefine は各 lib からの寄与なので除外)
|
|
86
|
+
names = Hash.new { |h, k| h[k] = [] }
|
|
87
|
+
tree.entities.each do |path, e|
|
|
88
|
+
next if e[:library].nil? && !tree.libraries.key?(path.sub(/\.md\z/, ''))
|
|
89
|
+
e[:kinds].each { |kind, n| names[n] << path if %w[class module object].include?(kind) }
|
|
90
|
+
end
|
|
91
|
+
dups = names.select { |_, paths| paths.uniq.size > 1 }
|
|
92
|
+
puts "in-scope entity definition duplicates (reopen/redefine 除外): #{dups.size}"
|
|
93
|
+
dups.each { |n, paths| puts " #{n}: #{paths.uniq.join(', ')}" }
|
|
94
|
+
|
|
95
|
+
# 警告をスコープ外・ソース孤児(期待=サルベージ待ち)とそれ以外に分類
|
|
96
|
+
expected_paths = (out_members + out_lib_mds + orphan_mds).to_h { |p| [p, true] }
|
|
97
|
+
expected, real = tree.warnings.partition do |w|
|
|
98
|
+
path = w[/\S+\.md\z/]
|
|
99
|
+
path && expected_paths[path]
|
|
100
|
+
end
|
|
101
|
+
puts "warnings: #{tree.warnings.size} " \
|
|
102
|
+
"(expected out-of-scope/salvage: #{expected.size}, needs attention: #{real.size})"
|
|
103
|
+
real.each { |w| puts " W: #{w}" }
|
|
104
|
+
else
|
|
105
|
+
puts "warnings: #{tree.warnings.size}"
|
|
106
|
+
tree.warnings.each { |w| puts " W: #{w}" }
|
|
107
|
+
end
|
data/tools/stattodo.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bitclust-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- https://github.com/rurema
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: tools
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bitclust-core
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - '='
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.
|
|
18
|
+
version: 1.6.1
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - '='
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 1.
|
|
25
|
+
version: 1.6.1
|
|
27
26
|
description: |
|
|
28
27
|
Rurema is a Japanese ruby documentation project, and
|
|
29
28
|
bitclust is a rurema document processor.
|
|
@@ -31,15 +30,21 @@ description: |
|
|
|
31
30
|
email:
|
|
32
31
|
- ''
|
|
33
32
|
executables:
|
|
34
|
-
-
|
|
35
|
-
- insert-canonical.rb
|
|
33
|
+
- bc-checkparams.rb
|
|
36
34
|
- bc-convert.rb
|
|
35
|
+
- bc-rdoc.rb
|
|
37
36
|
- forall-ruby.rb
|
|
38
|
-
-
|
|
37
|
+
- gencatalog.rb
|
|
38
|
+
- insert-canonical.rb
|
|
39
|
+
- md-bridge-check.rb
|
|
40
|
+
- md-compile-check.rb
|
|
41
|
+
- md-db-check.rb
|
|
42
|
+
- md-parse-check.rb
|
|
43
|
+
- md-roundtrip-check.rb
|
|
44
|
+
- md-tree-check.rb
|
|
39
45
|
- statrefm.rb
|
|
40
46
|
- stattodo.rb
|
|
41
47
|
- update-database.rb
|
|
42
|
-
- bc-rdoc.rb
|
|
43
48
|
extensions: []
|
|
44
49
|
extra_rdoc_files: []
|
|
45
50
|
files:
|
|
@@ -50,6 +55,12 @@ files:
|
|
|
50
55
|
- tools/forall-ruby.rb
|
|
51
56
|
- tools/gencatalog.rb
|
|
52
57
|
- tools/insert-canonical.rb
|
|
58
|
+
- tools/md-bridge-check.rb
|
|
59
|
+
- tools/md-compile-check.rb
|
|
60
|
+
- tools/md-db-check.rb
|
|
61
|
+
- tools/md-parse-check.rb
|
|
62
|
+
- tools/md-roundtrip-check.rb
|
|
63
|
+
- tools/md-tree-check.rb
|
|
53
64
|
- tools/statrefm.rb
|
|
54
65
|
- tools/stattodo.rb
|
|
55
66
|
- tools/update-database.rb
|
|
@@ -62,7 +73,6 @@ metadata:
|
|
|
62
73
|
source_code_uri: https://github.com/rurema/bitclust
|
|
63
74
|
github_repo: https://github.com/rurema/bitclust
|
|
64
75
|
wiki_uri: https://github.com/rurema/doctree/wiki
|
|
65
|
-
post_install_message:
|
|
66
76
|
rdoc_options: []
|
|
67
77
|
require_paths:
|
|
68
78
|
- lib
|
|
@@ -77,8 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
87
|
- !ruby/object:Gem::Version
|
|
78
88
|
version: '0'
|
|
79
89
|
requirements: []
|
|
80
|
-
rubygems_version: 3.
|
|
81
|
-
signing_key:
|
|
90
|
+
rubygems_version: 3.6.9
|
|
82
91
|
specification_version: 4
|
|
83
92
|
summary: BitClust is a rurema document processor.
|
|
84
93
|
test_files: []
|