suma 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +7 -1
- data/.rubocop_todo.yml +170 -13
- data/CLAUDE.md +37 -11
- data/Gemfile +3 -3
- data/README.adoc +127 -13
- data/exe/suma +1 -1
- data/lib/suma/cli/build.rb +0 -5
- data/lib/suma/cli/check_svg_quality.rb +82 -97
- data/lib/suma/cli/compare.rb +0 -1
- data/lib/suma/cli/convert_jsdai.rb +0 -2
- data/lib/suma/cli/core.rb +119 -0
- data/lib/suma/cli/export.rb +37 -22
- data/lib/suma/cli/extract_terms.rb +5 -8
- data/lib/suma/cli/generate_register.rb +34 -0
- data/lib/suma/cli/generate_schemas.rb +0 -2
- data/lib/suma/cli/reformat.rb +39 -64
- data/lib/suma/cli/validate.rb +14 -7
- data/lib/suma/cli/validate_links.rb +11 -157
- data/lib/suma/cli.rb +12 -141
- data/lib/suma/collection_config.rb +0 -2
- data/lib/suma/collection_manifest.rb +7 -110
- data/lib/suma/eengine/wrapper.rb +0 -1
- data/lib/suma/eengine.rb +8 -0
- data/lib/suma/express_reformatter.rb +94 -0
- data/lib/suma/express_schema.rb +0 -1
- data/lib/suma/jsdai/figure.rb +0 -3
- data/lib/suma/jsdai.rb +5 -2
- data/lib/suma/link_validation.rb +144 -0
- data/lib/suma/link_validator.rb +17 -8
- data/lib/suma/manifest_traverser.rb +92 -0
- data/lib/suma/processor.rb +5 -8
- data/lib/suma/register_manifest_generator.rb +163 -0
- data/lib/suma/schema_category.rb +83 -0
- data/lib/suma/schema_collection.rb +30 -34
- data/lib/suma/schema_comparer.rb +4 -3
- data/lib/suma/schema_compiler.rb +86 -0
- data/lib/suma/schema_discovery.rb +75 -0
- data/lib/suma/schema_exporter.rb +14 -42
- data/lib/suma/schema_manifest_generator.rb +14 -6
- data/lib/suma/schema_naming.rb +111 -0
- data/lib/suma/schema_template/document.rb +141 -0
- data/lib/suma/schema_template/plain.rb +46 -0
- data/lib/suma/schema_template.rb +19 -0
- data/lib/suma/svg_quality/batch_report.rb +0 -2
- data/lib/suma/svg_quality/formatters.rb +12 -0
- data/lib/suma/svg_quality/scanner.rb +61 -0
- data/lib/suma/svg_quality.rb +4 -1
- data/lib/suma/term_classification.rb +78 -0
- data/lib/suma/term_extractor.rb +125 -81
- data/lib/suma/urn.rb +61 -0
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +35 -3
- data/suma.gemspec +1 -1
- metadata +28 -6
- data/lib/suma/schema_attachment.rb +0 -103
- data/lib/suma/schema_document.rb +0 -118
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
module SchemaTemplate
|
|
5
|
+
# Emits an AsciiDoc body with cross-reference anchors for every
|
|
6
|
+
# schema element so other documents can deep-link into the compiled
|
|
7
|
+
# HTML. Only XML is produced — the anchors only resolve against the
|
|
8
|
+
# XML output, not the HTML rendering.
|
|
9
|
+
class Document
|
|
10
|
+
EXTENSIONS = "xml"
|
|
11
|
+
|
|
12
|
+
attr_reader :schema_id
|
|
13
|
+
|
|
14
|
+
def initialize(schema_id)
|
|
15
|
+
@schema_id = schema_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extensions
|
|
19
|
+
EXTENSIONS
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render(path_to_schema_yaml)
|
|
23
|
+
<<~ADOC
|
|
24
|
+
= #{schema_id}
|
|
25
|
+
:lutaml-express-index: schemas; #{path_to_schema_yaml};
|
|
26
|
+
:bare: true
|
|
27
|
+
:mn-document-class: iso
|
|
28
|
+
:mn-output-extensions: #{extensions}
|
|
29
|
+
|
|
30
|
+
[lutaml_express_liquid,schemas,context]
|
|
31
|
+
----
|
|
32
|
+
{% for schema in context.schemas %}
|
|
33
|
+
|
|
34
|
+
[[#{schema_id}]]
|
|
35
|
+
[%unnumbered,type=express]
|
|
36
|
+
== #{schema_id} #{rendered_anchors}
|
|
37
|
+
|
|
38
|
+
[source%unnumbered]
|
|
39
|
+
--
|
|
40
|
+
{{ schema.formatted }}
|
|
41
|
+
--
|
|
42
|
+
{% endfor %}
|
|
43
|
+
----
|
|
44
|
+
|
|
45
|
+
ADOC
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# The raw anchor block contains Liquid comments and newlines that
|
|
51
|
+
# are illegal on the section header line; strip them before
|
|
52
|
+
# interpolating.
|
|
53
|
+
def rendered_anchors
|
|
54
|
+
schema_anchors.gsub(%r{//[^\r\n]+}, "").gsub(/[\n\r]+/, "").gsub(
|
|
55
|
+
/^[\n\r]/, ""
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def schema_anchors
|
|
60
|
+
<<~HEREDOC
|
|
61
|
+
// _fund_cons.liquid
|
|
62
|
+
[[#{schema_id}_funds]]
|
|
63
|
+
|
|
64
|
+
// _constants.liquid
|
|
65
|
+
{% if schema.constants.size > 0 %}
|
|
66
|
+
#{bookmark('constants')}
|
|
67
|
+
{% for thing in schema.constants %}
|
|
68
|
+
#{bookmark('{{thing.id}}')}
|
|
69
|
+
{% endfor %}
|
|
70
|
+
{% endif %}
|
|
71
|
+
|
|
72
|
+
// _types.liquid
|
|
73
|
+
{% if schema.types.size > 0 %}
|
|
74
|
+
#{bookmark('types')}
|
|
75
|
+
// _type.liquid
|
|
76
|
+
{% for thing in schema.types %}
|
|
77
|
+
#{bookmark('{{thing.id}}')}
|
|
78
|
+
{% if thing.items.size > 0 %}
|
|
79
|
+
// _type_items.liquid
|
|
80
|
+
#{bookmark('{{thing.id}}.items')}
|
|
81
|
+
{% for item in thing.items %}
|
|
82
|
+
#{bookmark('{{thing.id}}.items.{{item.id}}')}
|
|
83
|
+
{% endfor %}
|
|
84
|
+
{% endif %}
|
|
85
|
+
{% endfor %}
|
|
86
|
+
{% endif %}
|
|
87
|
+
|
|
88
|
+
// _entities.liquid
|
|
89
|
+
{% if schema.entities.size > 0 %}
|
|
90
|
+
#{bookmark('entities')}
|
|
91
|
+
{% for thing in schema.entities %}
|
|
92
|
+
// _entity.liquid
|
|
93
|
+
#{bookmark('{{thing.id}}')}
|
|
94
|
+
{% endfor %}
|
|
95
|
+
{% endif %}
|
|
96
|
+
|
|
97
|
+
// _subtype_constraints.liquid
|
|
98
|
+
{% if schema.subtype_constraints.size > 0 %}
|
|
99
|
+
#{bookmark('subtype_constraints')}
|
|
100
|
+
// _subtype_constraint.liquid
|
|
101
|
+
{% for thing in schema.subtype_constraints %}
|
|
102
|
+
#{bookmark('{{thing.id}}')}
|
|
103
|
+
{% endfor %}
|
|
104
|
+
{% endif %}
|
|
105
|
+
|
|
106
|
+
// _functions.liquid
|
|
107
|
+
{% if schema.functions.size > 0 %}
|
|
108
|
+
#{bookmark('functions')}
|
|
109
|
+
// _function.liquid
|
|
110
|
+
{% for thing in schema.functions %}
|
|
111
|
+
#{bookmark('{{thing.id}}')}
|
|
112
|
+
{% endfor %}
|
|
113
|
+
{% endif %}
|
|
114
|
+
|
|
115
|
+
// _procedures.liquid
|
|
116
|
+
{% if schema.procedures.size > 0 %}
|
|
117
|
+
#{bookmark('procedures')}
|
|
118
|
+
// _procedure.liquid
|
|
119
|
+
{% for thing in schema.procedures %}
|
|
120
|
+
#{bookmark('{{thing.id}}')}
|
|
121
|
+
{% endfor %}
|
|
122
|
+
{% endif %}
|
|
123
|
+
|
|
124
|
+
// _rules.liquid
|
|
125
|
+
{% if schema.rules.size > 0 %}
|
|
126
|
+
#{bookmark('rules')}
|
|
127
|
+
// _rule.liquid
|
|
128
|
+
{% for thing in schema.rules %}
|
|
129
|
+
#{bookmark('{{thing.id}}')}
|
|
130
|
+
{% endfor %}
|
|
131
|
+
{% endif %}
|
|
132
|
+
HEREDOC
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def bookmark(anchor)
|
|
136
|
+
mangled = anchor.gsub("}}", ' | replace: "\", "-", "-}}')
|
|
137
|
+
"[[#{schema_id}.#{mangled}]]"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
module SchemaTemplate
|
|
5
|
+
# Emits a plain AsciiDoc body for a single EXPRESS schema, producing
|
|
6
|
+
# both HTML and XML outputs via Metanorma.
|
|
7
|
+
class Plain
|
|
8
|
+
EXTENSIONS = "xml,html"
|
|
9
|
+
|
|
10
|
+
attr_reader :schema_id
|
|
11
|
+
|
|
12
|
+
def initialize(schema_id)
|
|
13
|
+
@schema_id = schema_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def extensions
|
|
17
|
+
EXTENSIONS
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render(path_to_schema_yaml)
|
|
21
|
+
<<~ADOC
|
|
22
|
+
= #{schema_id}
|
|
23
|
+
:lutaml-express-index: schemas; #{path_to_schema_yaml};
|
|
24
|
+
:bare: true
|
|
25
|
+
:mn-document-class: iso
|
|
26
|
+
:mn-output-extensions: #{extensions}
|
|
27
|
+
|
|
28
|
+
[lutaml_express_liquid,schemas,context]
|
|
29
|
+
----
|
|
30
|
+
{% for schema in context.schemas %}
|
|
31
|
+
|
|
32
|
+
[%unnumbered]
|
|
33
|
+
== #{schema_id}
|
|
34
|
+
|
|
35
|
+
[source%unnumbered]
|
|
36
|
+
--
|
|
37
|
+
{{ schema.formatted }}
|
|
38
|
+
--
|
|
39
|
+
{% endfor %}
|
|
40
|
+
----
|
|
41
|
+
|
|
42
|
+
ADOC
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Pure renderers for the AsciiDoc source fed to Metanorma when compiling
|
|
5
|
+
# an EXPRESS schema to HTML/XML.
|
|
6
|
+
#
|
|
7
|
+
# Each template knows how to produce the adoc body for one compilation
|
|
8
|
+
# flavour (plain HTML, or HTML with cross-reference anchors). Templates
|
|
9
|
+
# have no I/O and no knowledge of the underlying ExpressSchema — they
|
|
10
|
+
# only need the schema id, because the rendered adoc is consumed by
|
|
11
|
+
# Liquid inside Metanorma, which fetches the schema by id from the
|
|
12
|
+
# surrounding lutaml-express-index.
|
|
13
|
+
#
|
|
14
|
+
# Composition with the compiler lives in SchemaCompiler.
|
|
15
|
+
module SchemaTemplate
|
|
16
|
+
autoload :Plain, "suma/schema_template/plain"
|
|
17
|
+
autoload :Document, "suma/schema_template/document"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
module SvgQuality
|
|
5
|
+
module Formatters
|
|
6
|
+
autoload :TerminalFormatter,
|
|
7
|
+
"suma/svg_quality/formatters/terminal_formatter"
|
|
8
|
+
autoload :JsonFormatter, "suma/svg_quality/formatters/json_formatter"
|
|
9
|
+
autoload :YamlFormatter, "suma/svg_quality/formatters/yaml_formatter"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "svg_conform"
|
|
4
|
+
|
|
5
|
+
module Suma
|
|
6
|
+
module SvgQuality
|
|
7
|
+
# Deep module behind the SVG-quality seam: takes paths in, returns
|
|
8
|
+
# +Report+ / +BatchReport+ out. Owns validator construction and
|
|
9
|
+
# per-file result capture. Does not own file discovery, sorting,
|
|
10
|
+
# filtering, or presentation — those stay in the CLI adapter.
|
|
11
|
+
#
|
|
12
|
+
# The progress adapter is injected: pass any object responding to
|
|
13
|
+
# +#call(index, total, report)+. +NullProgress+ is the default
|
|
14
|
+
# no-op; the CLI passes a lambda that writes to +$stderr+.
|
|
15
|
+
class Scanner
|
|
16
|
+
DEFAULT_PROFILE = :metanorma
|
|
17
|
+
|
|
18
|
+
attr_reader :profile, :progress
|
|
19
|
+
|
|
20
|
+
def initialize(profile: DEFAULT_PROFILE,
|
|
21
|
+
progress: NullProgress.new)
|
|
22
|
+
@profile = profile
|
|
23
|
+
@progress = progress
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def scan(paths)
|
|
27
|
+
validator = build_validator
|
|
28
|
+
reports = paths.each_with_index.map do |path, index|
|
|
29
|
+
scan_one(validator, path, index, paths.size)
|
|
30
|
+
end
|
|
31
|
+
BatchReport.new(reports)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def scan_file(path)
|
|
35
|
+
Report.new(path.to_s, build_validator.validate_file(path.to_s,
|
|
36
|
+
profile: profile))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Default no-op progress adapter. Real progress reporters are
|
|
40
|
+
# passed by the caller; this satisfies the same interface so the
|
|
41
|
+
# scanner can be invoked from specs without forcing the
|
|
42
|
+
# dependency.
|
|
43
|
+
class NullProgress
|
|
44
|
+
def call(_index, _total, _report); end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def build_validator
|
|
50
|
+
SvgConform::Validator.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def scan_one(validator, path, index, total)
|
|
54
|
+
result = validator.validate_file(path.to_s, profile: profile)
|
|
55
|
+
report = Report.new(path.to_s, result)
|
|
56
|
+
progress.call(index, total, report)
|
|
57
|
+
report
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/suma/svg_quality.rb
CHANGED
|
@@ -4,7 +4,10 @@ require "svg_conform"
|
|
|
4
4
|
|
|
5
5
|
module Suma
|
|
6
6
|
module SvgQuality
|
|
7
|
-
|
|
7
|
+
autoload :Report, "suma/svg_quality/report"
|
|
8
|
+
autoload :BatchReport, "suma/svg_quality/batch_report"
|
|
9
|
+
autoload :Scanner, "suma/svg_quality/scanner"
|
|
10
|
+
|
|
8
11
|
module QualityTiers
|
|
9
12
|
CRITICAL = { name: :critical, min_errors: 200, emoji: "💥" }.freeze
|
|
10
13
|
HIGH = { name: :high, min_errors: 100, emoji: "🔴" }.freeze
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Term-extractor-specific classification of an EXPRESS schema.
|
|
5
|
+
#
|
|
6
|
+
# Bridges ExpressSchema::Type (the canonical classification shared
|
|
7
|
+
# across the codebase) and the Glossarist-specific labels
|
|
8
|
+
# TermExtractor emits: the domain string ("application module" /
|
|
9
|
+
# "resource") that goes into a concept's +domain+ field, and the
|
|
10
|
+
# entity-type URN term used in generated concept definitions.
|
|
11
|
+
#
|
|
12
|
+
# The mapping is data — a frozen Hash keyed by ExpressSchema::Type
|
|
13
|
+
# symbol — so adding a new schema type is a one-line addition to
|
|
14
|
+
# BY_TYPE (open/closed principle). The previous implementation
|
|
15
|
+
# switched on string keys in three separate places; this consolidates
|
|
16
|
+
# them into one source of truth.
|
|
17
|
+
class TermClassification
|
|
18
|
+
attr_reader :type, :domain_label, :entity_term, :entity_display
|
|
19
|
+
|
|
20
|
+
def initialize(type:, domain_label:, entity_term:, entity_display:)
|
|
21
|
+
@type = type
|
|
22
|
+
@domain_label = domain_label
|
|
23
|
+
@entity_term = entity_term
|
|
24
|
+
@entity_display = entity_display
|
|
25
|
+
freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def domain_for(schema_id)
|
|
29
|
+
"#{domain_label}: #{schema_id}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
BY_TYPE = {
|
|
33
|
+
ExpressSchema::Type::RESOURCE => new(
|
|
34
|
+
type: ExpressSchema::Type::RESOURCE,
|
|
35
|
+
domain_label: "resource",
|
|
36
|
+
entity_term: "express-language.entity_data_type",
|
|
37
|
+
entity_display: "entity data type",
|
|
38
|
+
),
|
|
39
|
+
ExpressSchema::Type::MODULE_ARM => new(
|
|
40
|
+
type: ExpressSchema::Type::MODULE_ARM,
|
|
41
|
+
domain_label: "application module",
|
|
42
|
+
entity_term: "general.application_object",
|
|
43
|
+
entity_display: "application object",
|
|
44
|
+
),
|
|
45
|
+
ExpressSchema::Type::MODULE_MIM => new(
|
|
46
|
+
type: ExpressSchema::Type::MODULE_MIM,
|
|
47
|
+
domain_label: "application module",
|
|
48
|
+
entity_term: "express-language.entity_data_type",
|
|
49
|
+
entity_display: "entity data type",
|
|
50
|
+
),
|
|
51
|
+
ExpressSchema::Type::BUSINESS_OBJECT_MODEL => new(
|
|
52
|
+
type: ExpressSchema::Type::BUSINESS_OBJECT_MODEL,
|
|
53
|
+
domain_label: "resource",
|
|
54
|
+
entity_term: "express-language.entity_data_type",
|
|
55
|
+
entity_display: "entity data type",
|
|
56
|
+
),
|
|
57
|
+
ExpressSchema::Type::CORE_MODEL => new(
|
|
58
|
+
type: ExpressSchema::Type::CORE_MODEL,
|
|
59
|
+
domain_label: "resource",
|
|
60
|
+
entity_term: "express-language.entity_data_type",
|
|
61
|
+
entity_display: "entity data type",
|
|
62
|
+
),
|
|
63
|
+
ExpressSchema::Type::STANDALONE => new(
|
|
64
|
+
type: ExpressSchema::Type::STANDALONE,
|
|
65
|
+
domain_label: "resource",
|
|
66
|
+
entity_term: "express-language.entity_data_type",
|
|
67
|
+
entity_display: "entity data type",
|
|
68
|
+
),
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
def self.for_schema(id:, path:)
|
|
72
|
+
type = ExpressSchema::Type.classify(id: id, path: path)
|
|
73
|
+
BY_TYPE.fetch(type) do |t|
|
|
74
|
+
raise Error, "[suma] no term classification for type #{t.inspect}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|