forked from rurema/bitclust
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmd-roundtrip-check.rb
More file actions
executable file
·145 lines (137 loc) · 5.27 KB
/
Copy pathmd-roundtrip-check.rb
File metadata and controls
executable file
·145 lines (137 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Markdown 変換のラウンドトリップ検証。
# refm/api/src の全ファイルについて rd → md → rd がバイト一致するかを確認する。
#
# usage: ruby tools/md-roundtrip-check.rb [options] <doctree-root>
# --with-doc refm/doc/**/*.rd も検証する(DocConverter の reduce 基準)
# --with-capi refm/capi/src/*.rd も検証する(CapiConverter)
# --inject MarkdownOrchestrator の変換(prune・全体ゲート解除・front matter 注入)で
# 検証する: md → rd が reduce 後の rd を復元すること、および
# md に grouping include が残っていないことを確認する
# --diff 差分のあったファイルの先頭差分行を表示する
# -v 全ファイルの結果を表示する
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'bitclust/rrd_to_markdown'
require 'bitclust/markdown_to_rrd'
require 'bitclust/markdown_orchestrator'
require 'bitclust/doc_converter'
require 'bitclust/capi_converter'
with_doc = ARGV.delete('--with-doc')
with_capi = ARGV.delete('--with-capi')
inject = ARGV.delete('--inject')
show_diff = ARGV.delete('--diff')
verbose = ARGV.delete('-v')
scope_arg = nil
if (i = ARGV.index('--scope'))
ARGV.delete_at(i)
scope_arg = ARGV.delete_at(i) or abort '--scope requires LO,HI'
end
doctree = ARGV.shift or abort "usage: #{$0} [--with-doc] [--inject] [--scope LO,HI] [--diff] [-v] <doctree-root>"
src_root = File.join(doctree, 'refm/api/src')
files = Dir.glob('**/*', base: src_root).select { |f| File.file?(File.join(src_root, f)) }
files -= ['LIBRARIES']
orchestrator = nil
prune_sites = {}
if inject
opts = {}
if scope_arg
opts[:scope] = BitClust::IncludeGraph::Scope.new(*scope_arg.split(','))
end
orchestrator = BitClust::MarkdownOrchestrator.new(src_root, **opts)
orchestrator.warnings.each { |w| warn "W: #{w}" }
prune_sites = orchestrator.graph.grouping_include_sites
puts "prune: #{prune_sites.size} files"
end
targets = files.map { |f| [File.join(src_root, f), f] }
if with_doc
doc_root = File.join(doctree, 'refm/doc')
# *.rd と拡張子なし(spec/regexp18 等)。news/1.8.0.rd-2 系の
# 旧分割ファイルは未参照のため対象外
doc_files = Dir.glob('**/*', base: doc_root).select { |f|
File.file?(File.join(doc_root, f)) &&
(f.end_with?('.rd') || !File.basename(f).include?('.'))
}
targets += doc_files.map { |f| [File.join(doc_root, f), "doc:#{f}"] }
end
if with_capi
capi_root = File.join(doctree, 'refm/capi/src')
targets += Dir.glob('*.rd', base: capi_root).map { |f| [File.join(capi_root, f), "capi:#{f}"] }
end
# md 内のエンティティ H1 直後のヘッダ領域に関係行が残っていないか
# (O3 の不変条件: 関係は front matter のみ)
def body_relations?(md)
in_header = false
md.each_line do |line|
if line =~ /\A#(?!#)\s*(?:class|module|object|reopen|redefine)\b/
in_header = true
elsif in_header
case line
when /\A(?:include|extend|alias)\s+\S/ then return true
when /\A\#@/, /\A\s*\z/ then nil
else in_header = false
end
end
end
false
end
ok = 0
units = 0
failed = []
leftover = []
body_rels = []
targets.sort_by(&:last).each do |full, label|
rrd = File.read(full)
outs =
if orchestrator
orchestrator.units(label, rrd)
.map { |u| [u.path, u.rrd, orchestrator.convert_unit(u), u.front_matter] }
elsif label.start_with?('doc:')
reduced = BitClust::DocConverter.reduce(rrd)
[[label, reduced, BitClust::DocConverter.convert(rrd), nil]]
elsif label.start_with?('capi:')
[[label, rrd, BitClust::CapiConverter.convert(rrd), nil]]
else
[[label, rrd, BitClust::RRDToMarkdown.convert(rrd), nil]]
end
outs.each do |path, reduced, md, front_matter|
units += 1
ulabel = outs.size > 1 ? "#{label} -> #{path}" : label
if (sites = prune_sites[label])
remaining = md.lines.filter_map { |l| $1 if l =~ /\A\#@include\s*\((.*?)\)/ }
leftover << ulabel unless (remaining & sites).empty?
end
# body 関係の不変条件は in-scope(front matter 注入あり)のみ。
# スコープ外ファイルは凍結形のままなので対象外(サルベージで扱う)
body_rels << ulabel if front_matter && !front_matter.empty? && body_relations?(md)
back = BitClust::MarkdownToRRD.convert(md, capi: label.start_with?('capi:'))
if back == reduced
ok += 1
puts "OK #{ulabel}" if verbose
else
failed << ulabel
puts "DIFF #{ulabel}" if verbose || show_diff
if show_diff
reduced.lines.zip(back.lines).each_with_index do |(a, b), i|
next if a == b
puts " line #{i + 1}: #{a.inspect} -> #{b.inspect}"
break
end
end
end
end
end
puts "#{ok}/#{units} byte-exact roundtrip#{inject ? " (reduced base, #{targets.size} sources)" : ''}"
unless failed.empty?
puts "failed: #{failed.size}"
failed.each { |f| puts " #{f}" } unless verbose || show_diff
end
unless leftover.empty?
puts "grouping includes left unpruned: #{leftover.size}"
leftover.each { |f| puts " #{f}" }
end
unless body_rels.empty?
puts "header relations left in body: #{body_rels.size}"
body_rels.each { |f| puts " #{f}" }
end