suma 0.5.3 → 0.5.5
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 +24 -1
- data/lib/suma/cli/validate.rb +49 -0
- data/lib/suma/link_validation.rb +18 -3
- data/lib/suma/mapping_drift.rb +167 -0
- data/lib/suma/schema_template/document.rb +2 -2
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +1 -0
- data/suma.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3924d8ea8d4f6e7dd9d94226840b4d6135606edfeffca7198d71d26b59f53208
|
|
4
|
+
data.tar.gz: adce8f15063a18626c2b7828e746078f2e1e588594e393aaa122e81144fd3ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f602953242571ffe779c059b4f5e16120fe08e6fa196dad10e7f095369ebe642afd7bc91d5f404ebadb4b8e48378bfca366c3f62697c001c2e2b0823ba81f1b8
|
|
7
|
+
data.tar.gz: 16b6596fe1658ccbb7ffa6b4d08969bf1ae9442835a498f99bb2bf756f713576b547dfb422741306753facd7a01fa3d790ff137f6b03f8a79b3a761427eddb09
|
data/Gemfile
CHANGED
|
@@ -13,10 +13,25 @@ gem "canon"
|
|
|
13
13
|
# dropped: CI builds against a stable released metanorma, not a floating branch.
|
|
14
14
|
# Flavor gem needed to compile the dummy-collection integration fixture
|
|
15
15
|
# (spec/fixtures/dummy_collection, suma#107).
|
|
16
|
-
|
|
16
|
+
# metanorma-iso releases (<= 3.5.0) require "pubid-iso"/"pubid-cen"/
|
|
17
|
+
# "pubid-iec" (pubid 1.15 split gems) that do not exist on the relaton-3
|
|
18
|
+
# chain, and whose lib/pubid/*.rb collide with the pubid 2 monolith's.
|
|
19
|
+
# The pubid-2 migration is on metanorma-iso main; it needs the unreleased
|
|
20
|
+
# metanorma-standoc "Standoc::Document" split, so pin that from main too.
|
|
21
|
+
# Flip both to released gems once metanorma-iso releases the migration.
|
|
22
|
+
gem "metanorma-iso", github: "metanorma/metanorma-iso", branch: "main"
|
|
23
|
+
gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
|
|
24
|
+
# metanorma#607: Collection#bibitem must drop localized <edition> variants
|
|
25
|
+
# (metanorma-iso main emits them) before the relaton parse.
|
|
26
|
+
gem "metanorma", github: "metanorma/metanorma",
|
|
27
|
+
branch: "fix/collection-bibdata-localized-edition"
|
|
28
|
+
# iso/standoc main also use unreleased metanorma-document 0.5.x model
|
|
29
|
+
# classes (e.g. Components::Inline::TermrefElement).
|
|
30
|
+
gem "metanorma-document", github: "metanorma/metanorma-document", branch: "main"
|
|
17
31
|
# gem "metanorma-plugin-lutaml", github: "metanorma/metanorma-plugin-lutaml", branch: "main"
|
|
18
32
|
# gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "main"
|
|
19
33
|
# gem "expressir", github: "lutaml/expressir", branch: "main"
|
|
34
|
+
gem "expressir", "2.4.27"
|
|
20
35
|
gem "nokogiri"
|
|
21
36
|
gem "openssl", "~> 3.0"
|
|
22
37
|
gem "rake"
|
|
@@ -25,3 +40,11 @@ gem "rubocop"
|
|
|
25
40
|
gem "rubocop-performance"
|
|
26
41
|
gem "rubocop-rake"
|
|
27
42
|
gem "rubocop-rspec"
|
|
43
|
+
# isodoc 3.7.3 `require`s "sassc-embedded" while rendering HTML but does
|
|
44
|
+
# not declare it.
|
|
45
|
+
gem "sassc-embedded", "~> 1.0"
|
|
46
|
+
# relaton-render's floor (>= 2.0.0.pre.alpha.6) admits the stale June
|
|
47
|
+
# 2.2.0.pre prerelease, which predates lutaml-model 0.8.78's strict
|
|
48
|
+
# collection checks (duplicate <edition> raise). 2.1.9 is the current
|
|
49
|
+
# lutaml-model-0.8 line.
|
|
50
|
+
gem "relaton-bib", "2.1.9"
|
data/lib/suma/cli/validate.rb
CHANGED
|
@@ -10,6 +10,9 @@ module Suma
|
|
|
10
10
|
# indexing, validation) lives in the deep module and is reachable
|
|
11
11
|
# from specs without invoking Thor.
|
|
12
12
|
class Validate < Thor
|
|
13
|
+
def self.exit_on_failure?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
13
16
|
desc "links SCHEMAS_FILE DOCUMENTS_PATH [OUTPUT_FILE]",
|
|
14
17
|
"Extract and validate express links without creating intermediate file"
|
|
15
18
|
def links(schemas_file = "schemas-srl.yml",
|
|
@@ -22,6 +25,52 @@ module Suma
|
|
|
22
25
|
).call
|
|
23
26
|
puts LinkValidation.generate_summary(result)
|
|
24
27
|
end
|
|
28
|
+
|
|
29
|
+
desc "mappings DOCUMENTS_PATH",
|
|
30
|
+
"Validate module mapping.yaml links and reference paths " \
|
|
31
|
+
"against the parsed module corpus; only NEW drift " \
|
|
32
|
+
"(not in the baseline) fails"
|
|
33
|
+
option :baseline, type: :string, default: nil,
|
|
34
|
+
desc: "Baseline YAML path (default: " \
|
|
35
|
+
"mapping-drift-baseline.yml under DOCUMENTS_PATH)"
|
|
36
|
+
option :write_baseline, type: :boolean, default: false,
|
|
37
|
+
desc: "Write the scanned drift as the new " \
|
|
38
|
+
"baseline instead of checking"
|
|
39
|
+
def mappings(documents_path = "documents")
|
|
40
|
+
drift = MappingDrift.new(documents_path)
|
|
41
|
+
path = options[:baseline] || default_baseline_path(documents_path)
|
|
42
|
+
|
|
43
|
+
return write_baseline(drift, path) if options[:write_baseline]
|
|
44
|
+
|
|
45
|
+
assert_baseline!(path)
|
|
46
|
+
fail_on_new_drift(*drift.check(path), path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def default_baseline_path(documents_path)
|
|
52
|
+
File.join(documents_path, "mapping-drift-baseline.yml")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def assert_baseline!(path)
|
|
56
|
+
return if File.file?(path)
|
|
57
|
+
|
|
58
|
+
raise Thor::Error, "no baseline at #{path} — generate one with " \
|
|
59
|
+
"--write-baseline and commit it"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fail_on_new_drift(report, fresh, path)
|
|
63
|
+
say "#{report.entries.size} drift entr(ies), #{fresh.size} new vs #{path}"
|
|
64
|
+
return if fresh.empty?
|
|
65
|
+
|
|
66
|
+
raise Thor::Error, "#{fresh.size} new mapping drift:\n#{fresh.join("\n")}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def write_baseline(drift, path)
|
|
70
|
+
report = drift.scan
|
|
71
|
+
File.write(path, report.to_yaml)
|
|
72
|
+
say "baseline written: #{path} (#{report.entries.size} entries)"
|
|
73
|
+
end
|
|
25
74
|
end
|
|
26
75
|
end
|
|
27
76
|
end
|
data/lib/suma/link_validation.rb
CHANGED
|
@@ -127,13 +127,28 @@ module Suma
|
|
|
127
127
|
def validate_links(schemas_config, links_by_file)
|
|
128
128
|
paths_by_id = schemas_config.schemas.to_h { |s| [s.id, s.path] }
|
|
129
129
|
progress.start("Loading schemas", paths_by_id.size)
|
|
130
|
-
repo =
|
|
131
|
-
progress.increment
|
|
132
|
-
end
|
|
130
|
+
repo = load_repository(paths_by_id.values)
|
|
133
131
|
index = SchemaIndex.new(repo)
|
|
134
132
|
LinkValidator.new(index).validate(links_by_file)
|
|
135
133
|
end
|
|
136
134
|
|
|
135
|
+
# Compiled-set warm start (expressir TODO.suma-improvements/04),
|
|
136
|
+
# opt-in via SUMA_COMPILED_SET=1: parse the closure once into an
|
|
137
|
+
# EXSCS1 artifact beside the output and hydrate from it on
|
|
138
|
+
# subsequent runs.
|
|
139
|
+
def load_repository(paths)
|
|
140
|
+
return Expressir::Express::Parser.from_files(paths) unless ENV["SUMA_COMPILED_SET"] == "1"
|
|
141
|
+
|
|
142
|
+
dir = output_file ? output_file.dirname : Pathname.new(Dir.pwd)
|
|
143
|
+
set_path = File.join(dir, "schema-closure.exscs")
|
|
144
|
+
if File.file?(set_path)
|
|
145
|
+
return Expressir::Express::LazyRepository.new(set_path).to_eager
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
FileUtils.mkdir_p(dir)
|
|
149
|
+
Expressir::Express::Parser.from_files(paths, compiled_set: set_path)
|
|
150
|
+
end
|
|
151
|
+
|
|
137
152
|
def write_summary(result)
|
|
138
153
|
FileUtils.mkdir_p(output_file.dirname)
|
|
139
154
|
File.write(output_file, self.class.generate_summary(result))
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "expressir"
|
|
5
|
+
|
|
6
|
+
module Suma
|
|
7
|
+
# Mapping validation surfaced in builds (expressir
|
|
8
|
+
# TODO.suma-improvements/05): validate every module mapping.yaml —
|
|
9
|
+
# its <<express:...>> links and reference paths — against the parsed
|
|
10
|
+
# module corpus, and compare the drift against a committed baseline
|
|
11
|
+
# so only NEW drift fails a build.
|
|
12
|
+
#
|
|
13
|
+
# Owns: module discovery, the shared repository parse, per-module
|
|
14
|
+
# validation, and the baseline comparison. Does not own: presentation
|
|
15
|
+
# and exit codes (the CLI adapter), or the validation rules
|
|
16
|
+
# (Expressir::Mapping does).
|
|
17
|
+
class MappingDrift
|
|
18
|
+
# One drift finding; +key+ is the stable identity compared across
|
|
19
|
+
# runs: "<module path>|<kind>|<detail>".
|
|
20
|
+
class Entry < Lutaml::Model::Serializable
|
|
21
|
+
attribute :key, :string
|
|
22
|
+
|
|
23
|
+
key_value do
|
|
24
|
+
map "key", to: :key
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Serializable drift report / baseline (same wire shape).
|
|
29
|
+
class Report < Lutaml::Model::Serializable
|
|
30
|
+
attribute :entries, Entry, collection: true
|
|
31
|
+
|
|
32
|
+
key_value do
|
|
33
|
+
map "entries", to: :entries
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def keys
|
|
37
|
+
entries.map(&:key)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
MAPPING_GLOB = "**/mapping.yaml"
|
|
42
|
+
MODULE_SCHEMAS = %w[arm.exp mim.exp].freeze
|
|
43
|
+
private_constant :MAPPING_GLOB, :MODULE_SCHEMAS
|
|
44
|
+
|
|
45
|
+
attr_reader :documents_path, :stepmod
|
|
46
|
+
|
|
47
|
+
# +stepmod+ overrides where closure schema resolution looks up
|
|
48
|
+
# USE/REFERENCE dependencies (STEPmod `schemas/**/<name>.exp`
|
|
49
|
+
# layout); it defaults to +documents_path+ itself.
|
|
50
|
+
def initialize(documents_path, stepmod: nil)
|
|
51
|
+
@documents_path = Pathname.new(documents_path).expand_path
|
|
52
|
+
@stepmod = stepmod
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Every module mapping under documents_path that has arm/mim
|
|
56
|
+
# schemas next to it.
|
|
57
|
+
def discover
|
|
58
|
+
Dir[documents_path.join(MAPPING_GLOB).to_s].filter_map do |mapping|
|
|
59
|
+
schemas = module_schemas(File.dirname(mapping))
|
|
60
|
+
{ mapping: mapping, schemas: schemas } if schemas.any?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Validate every discovered module against one shared repository.
|
|
65
|
+
# @return [Report]
|
|
66
|
+
def scan
|
|
67
|
+
modules = discover
|
|
68
|
+
return Report.new(entries: []) if modules.empty?
|
|
69
|
+
|
|
70
|
+
paths, gaps = closure(modules)
|
|
71
|
+
repository = Expressir::Express::Parser.from_files(paths)
|
|
72
|
+
Report.new(entries: gaps + modules.flat_map do |mod|
|
|
73
|
+
drift_for(mod, repository)
|
|
74
|
+
end)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Scan and compare against the baseline file.
|
|
78
|
+
# @return [[Report, Array<String>]] the full report and the keys
|
|
79
|
+
# not present in the baseline (the NEW drift).
|
|
80
|
+
def check(baseline_path)
|
|
81
|
+
known = Report.from_yaml(File.read(baseline_path)).keys
|
|
82
|
+
report = scan
|
|
83
|
+
[report, report.keys - known]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def module_schemas(dir)
|
|
89
|
+
MODULE_SCHEMAS
|
|
90
|
+
.map { |name| File.join(dir, name) }
|
|
91
|
+
.select { |path| File.file?(path) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The repository covers every module schema plus its transitive
|
|
95
|
+
# USE/REFERENCE closure — reference paths name resource-schema
|
|
96
|
+
# types the modules themselves never declare. A schema the corpus
|
|
97
|
+
# does not carry (e.g. external PLIB templates) becomes a closure
|
|
98
|
+
# gap entry instead of aborting the whole scan; the affected
|
|
99
|
+
# module still validates against its own schemas.
|
|
100
|
+
def closure(modules)
|
|
101
|
+
paths = []
|
|
102
|
+
gaps = []
|
|
103
|
+
modules.each do |mod|
|
|
104
|
+
mod[:schemas].each do |root|
|
|
105
|
+
paths.concat(closure_of(root, mod[:mapping], gaps))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
[paths.compact.uniq, gaps]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def closure_of(root, mapping_path, gaps)
|
|
112
|
+
Expressir::Commands::ParityInputs.closure_paths(root,
|
|
113
|
+
stepmod: stepmod_root)
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
gaps << Entry.new(key: entry_key(mapping_path, "closure", e.message))
|
|
116
|
+
[root]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def stepmod_root
|
|
120
|
+
stepmod || documents_path.to_s
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def drift_for(mod, repository)
|
|
124
|
+
mapping_path = mod[:mapping]
|
|
125
|
+
begin
|
|
126
|
+
document = Expressir::Mapping.load_file(mapping_path)
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
return [Entry.new(key: entry_key(mapping_path, "load", e.message))]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
unknown = Expressir::Mapping.unknown_links(document, repository)
|
|
132
|
+
unknown.map do |link|
|
|
133
|
+
Entry.new(key: entry_key(mapping_path, "unknown_link", link.text))
|
|
134
|
+
end + refpath_issues(mapping_path, document, repository)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def refpath_issues(mapping_path, document, repository)
|
|
138
|
+
Expressir::Mapping.refpaths(document).flat_map do |location, content|
|
|
139
|
+
parsed = Expressir::Mapping::RefPath.parse(content)
|
|
140
|
+
parse_error_entries(mapping_path, location, parsed) +
|
|
141
|
+
validate_entries(mapping_path, location, parsed, repository)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def parse_error_entries(mapping_path, location, parsed)
|
|
146
|
+
parsed.parse_errors.map do |error|
|
|
147
|
+
Entry.new(key: entry_key(mapping_path, "refpath",
|
|
148
|
+
"#{location}: #{error}"))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def validate_entries(mapping_path, location, parsed, repository)
|
|
153
|
+
Expressir::Mapping::RefPath.validate(parsed, repository).map do |issue|
|
|
154
|
+
detail = "#{location} [step #{issue.step}]: #{issue.message}"
|
|
155
|
+
Entry.new(key: entry_key(mapping_path, "refpath", detail))
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def entry_key(mapping_path, kind, detail)
|
|
160
|
+
"#{relative(mapping_path)}|#{kind}|#{detail}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def relative(path)
|
|
164
|
+
Pathname.new(path).expand_path.relative_path_from(documents_path).to_s
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/suma/version.rb
CHANGED
data/lib/suma.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Suma
|
|
|
16
16
|
autoload :LinkValidation, "suma/link_validation"
|
|
17
17
|
autoload :LinkValidator, "suma/link_validator"
|
|
18
18
|
autoload :ManifestTraverser, "suma/manifest_traverser"
|
|
19
|
+
autoload :MappingDrift, "suma/mapping_drift"
|
|
19
20
|
autoload :NoteProcessor, "suma/note_processor"
|
|
20
21
|
autoload :NullCache, "suma/null_cache"
|
|
21
22
|
autoload :RegisterManifestGenerator, "suma/register_manifest_generator"
|
data/suma.gemspec
CHANGED
|
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
|
|
40
40
|
spec.add_dependency "plurimath"
|
|
41
41
|
spec.add_dependency "ruby-progressbar"
|
|
42
42
|
spec.add_dependency "rubyzip"
|
|
43
|
-
spec.add_dependency "svg_conform", "
|
|
43
|
+
spec.add_dependency "svg_conform", ">= 0.1.0"
|
|
44
44
|
spec.add_dependency "table_tennis"
|
|
45
45
|
spec.add_dependency "thor", ">= 0.20"
|
|
46
46
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: suma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: expressir
|
|
@@ -118,14 +118,14 @@ dependencies:
|
|
|
118
118
|
name: svg_conform
|
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
|
-
- - "
|
|
121
|
+
- - ">="
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: 0.1.0
|
|
124
124
|
type: :runtime
|
|
125
125
|
prerelease: false
|
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
127
127
|
requirements:
|
|
128
|
-
- - "
|
|
128
|
+
- - ">="
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
130
|
version: 0.1.0
|
|
131
131
|
- !ruby/object:Gem::Dependency
|
|
@@ -210,6 +210,7 @@ files:
|
|
|
210
210
|
- lib/suma/link_validator.rb
|
|
211
211
|
- lib/suma/link_validator/step.rb
|
|
212
212
|
- lib/suma/manifest_traverser.rb
|
|
213
|
+
- lib/suma/mapping_drift.rb
|
|
213
214
|
- lib/suma/note_processor.rb
|
|
214
215
|
- lib/suma/null_cache.rb
|
|
215
216
|
- lib/suma/processor.rb
|