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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a17aae0f1153193b6c5f295680331f6661bb8ac0fd3916f46a9d143820af535c
4
- data.tar.gz: 5a9bce05ca0b5b2053a6c62f6c047d258d01743b88deb6034ae3c6386ac38583
3
+ metadata.gz: b6f8d4dcefaa581b519600e255c2e9fba43bcdc3610f808de7fc101126fe1961
4
+ data.tar.gz: 3a86cd213fb1cb1be92d1d1f71c8cf3da91ece0cbd9e24ac05f11639f7a47aee
5
5
  SHA512:
6
- metadata.gz: f879ecf2eab0add265f0e8807999cb57f366e755f6d378de6da3db6803c793f1231e88d6c24e03ca565fe101c4b5aaf08a42eca33b0c9a92ce74f5c11f1fb72f
7
- data.tar.gz: 036f33247f1b23c469861caae060d47f1a8921e9f271e85c826e1b4e626c17d6fe8243c9f561990986bd9916209d39950546af3c6588fafc56029638d9ec5f10
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
@@ -85,7 +85,7 @@ def count_todo_in_file(path)
85
85
  n = 0
86
86
  File.open(path) {|f|
87
87
  pp = BitClust::LineCollector.wrap(f)
88
- pp.grep(/\A\#@todo/) { n += 1 }
88
+ pp.grep(/\A\#[@%]todo/) { n += 1 }
89
89
  }
90
90
  n
91
91
  end
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.3.0
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: 2020-12-29 00:00:00.000000000 Z
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.3.0
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.3.0
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
- - gencatalog.rb
35
- - insert-canonical.rb
33
+ - bc-checkparams.rb
36
34
  - bc-convert.rb
35
+ - bc-rdoc.rb
37
36
  - forall-ruby.rb
38
- - bc-checkparams.rb
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.1.4
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: []