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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Value object mapping an ExpressSchema::Type to its register/export category.
|
|
5
|
+
#
|
|
6
|
+
# Single source of truth for category identity across the codebase:
|
|
7
|
+
# directory layout (SchemaExporter), display prefix (SchemaNaming), and
|
|
8
|
+
# register.yaml sections (RegisterManifestGenerator) all derive from this.
|
|
9
|
+
#
|
|
10
|
+
# Each category carries: +id+ (slug used in register.yaml section id and
|
|
11
|
+
# export subdirectory), +label+ (human-readable section heading),
|
|
12
|
+
# +prefix+ (prefix used when forming display names, e.g. "Resource: ..."),
|
|
13
|
+
# +types+ (Array of ExpressSchema::Type symbols that belong to it),
|
|
14
|
+
# and +directory+ (subdirectory name; "." for the root output path).
|
|
15
|
+
class SchemaCategory
|
|
16
|
+
attr_reader :id, :label, :prefix, :types, :directory
|
|
17
|
+
|
|
18
|
+
def initialize(id:, label:, prefix:, types:, directory:)
|
|
19
|
+
@id = id
|
|
20
|
+
@label = label
|
|
21
|
+
@prefix = prefix
|
|
22
|
+
@types = types
|
|
23
|
+
@directory = directory
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def member?(type)
|
|
27
|
+
types.include?(type)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
RESOURCES = new(
|
|
31
|
+
id: "resources",
|
|
32
|
+
label: "Resources",
|
|
33
|
+
prefix: "Resource",
|
|
34
|
+
types: [ExpressSchema::Type::RESOURCE].freeze,
|
|
35
|
+
directory: "resources",
|
|
36
|
+
)
|
|
37
|
+
MODULES = new(
|
|
38
|
+
id: "modules",
|
|
39
|
+
label: "Application Modules",
|
|
40
|
+
prefix: "Module",
|
|
41
|
+
types: [ExpressSchema::Type::MODULE_ARM, ExpressSchema::Type::MODULE_MIM].freeze,
|
|
42
|
+
directory: "modules",
|
|
43
|
+
)
|
|
44
|
+
BUSINESS_OBJECT_MODELS = new(
|
|
45
|
+
id: "business_object_models",
|
|
46
|
+
label: "Business Object Models",
|
|
47
|
+
prefix: "Business Object Model",
|
|
48
|
+
types: [ExpressSchema::Type::BUSINESS_OBJECT_MODEL].freeze,
|
|
49
|
+
directory: "business_object_models",
|
|
50
|
+
)
|
|
51
|
+
CORE_MODEL = new(
|
|
52
|
+
id: "core_model",
|
|
53
|
+
label: "Core Model",
|
|
54
|
+
prefix: "Core Model",
|
|
55
|
+
types: [ExpressSchema::Type::CORE_MODEL].freeze,
|
|
56
|
+
directory: "core_model",
|
|
57
|
+
)
|
|
58
|
+
OTHER = new(
|
|
59
|
+
id: "other",
|
|
60
|
+
label: "Other Schemas",
|
|
61
|
+
prefix: "Schema",
|
|
62
|
+
types: [ExpressSchema::Type::STANDALONE].freeze,
|
|
63
|
+
directory: ".",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
ALL = [
|
|
67
|
+
RESOURCES,
|
|
68
|
+
MODULES,
|
|
69
|
+
BUSINESS_OBJECT_MODELS,
|
|
70
|
+
CORE_MODEL,
|
|
71
|
+
OTHER,
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
def self.for_type(type)
|
|
75
|
+
ALL.find { |category| category.member?(type) } || OTHER
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.for_schema(id:, path:)
|
|
79
|
+
type = ExpressSchema::Type.classify(id: id, path: path)
|
|
80
|
+
for_type(type)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "express_schema"
|
|
4
|
-
require_relative "schema_attachment"
|
|
5
|
-
require_relative "schema_document"
|
|
6
|
-
require_relative "schema_exporter"
|
|
7
3
|
require "expressir"
|
|
8
|
-
require_relative "utils"
|
|
9
4
|
|
|
10
5
|
module Suma
|
|
11
6
|
class SchemaCollection
|
|
@@ -30,36 +25,14 @@ module Suma
|
|
|
30
25
|
@schema_name_to_docs[schema_name]
|
|
31
26
|
end
|
|
32
27
|
|
|
33
|
-
def process_schemas(schemas, klass)
|
|
34
|
-
schemas.each do |config_schema|
|
|
35
|
-
process_schema(config_schema, klass)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def process_schema(config_schema, klass)
|
|
40
|
-
s = ExpressSchema.new(
|
|
41
|
-
id: config_schema.id, path: config_schema.path.to_s,
|
|
42
|
-
output_path: @output_path_schemas.to_s
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
doc = klass.new(
|
|
46
|
-
schema: s, output_path: @output_path_docs.join(s.id),
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
@docs[s.id] = doc
|
|
50
|
-
@schemas[s.id] = s
|
|
51
|
-
@schema_name_to_docs[s.id] = doc
|
|
52
|
-
end
|
|
53
|
-
|
|
54
28
|
def finalize
|
|
55
|
-
process_schemas(@config.schemas,
|
|
56
|
-
|
|
57
|
-
manifest_entry = @manifest.lookup_schemas_only
|
|
29
|
+
process_schemas(@config.schemas, SchemaTemplate::Plain)
|
|
58
30
|
|
|
59
|
-
|
|
31
|
+
schemas_only_entries = ManifestTraverser.new(@manifest).find_schemas_only
|
|
32
|
+
schemas_only_entries.each do |entry|
|
|
60
33
|
next unless entry.schema_config
|
|
61
34
|
|
|
62
|
-
process_schemas(entry.schema_config.schemas,
|
|
35
|
+
process_schemas(entry.schema_config.schemas, SchemaTemplate::Document)
|
|
63
36
|
end
|
|
64
37
|
end
|
|
65
38
|
|
|
@@ -67,15 +40,38 @@ module Suma
|
|
|
67
40
|
finalize
|
|
68
41
|
|
|
69
42
|
exporter = SchemaExporter.new(
|
|
70
|
-
schemas:
|
|
43
|
+
schemas: schemas.values,
|
|
71
44
|
output_path: @output_path_schemas,
|
|
72
45
|
options: { annotations: false },
|
|
73
46
|
)
|
|
74
47
|
exporter.export
|
|
75
48
|
|
|
76
|
-
docs.each_pair do |_schema_id,
|
|
77
|
-
|
|
49
|
+
docs.each_pair do |_schema_id, compiler|
|
|
50
|
+
compiler.compile
|
|
78
51
|
end
|
|
79
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def process_schemas(schemas, template_class)
|
|
57
|
+
schemas.each { |s| process_schema(s, template_class) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def process_schema(config_schema, template_class)
|
|
61
|
+
express = ExpressSchema.new(
|
|
62
|
+
id: config_schema.id, path: config_schema.path.to_s,
|
|
63
|
+
output_path: @output_path_schemas.to_s
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
compiler = SchemaCompiler.new(
|
|
67
|
+
schema: express,
|
|
68
|
+
output_path: @output_path_docs.join(express.id),
|
|
69
|
+
template: template_class.new(express.id),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@docs[express.id] = compiler
|
|
73
|
+
@schemas[express.id] = express
|
|
74
|
+
@schema_name_to_docs[express.id] = compiler
|
|
75
|
+
end
|
|
80
76
|
end
|
|
81
77
|
end
|
data/lib/suma/schema_comparer.rb
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "tmpdir"
|
|
5
|
-
require_relative "eengine/wrapper"
|
|
6
|
-
require_relative "eengine_converter"
|
|
7
5
|
|
|
8
6
|
module Suma
|
|
9
7
|
class SchemaComparer
|
|
@@ -37,7 +35,10 @@ module Suma
|
|
|
37
35
|
return nil
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
unless result[:xml_path]
|
|
39
|
+
raise Suma::CompilationError,
|
|
40
|
+
"XML output not found"
|
|
41
|
+
end
|
|
41
42
|
|
|
42
43
|
convert_to_change_yaml(result[:xml_path], out_dir)
|
|
43
44
|
ensure
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Suma
|
|
7
|
+
# Orchestrates the compilation of a single EXPRESS schema into HTML/XML
|
|
8
|
+
# via Metanorma.
|
|
9
|
+
#
|
|
10
|
+
# SchemaCompiler owns all file I/O and the Metanorma::Compile invocation
|
|
11
|
+
# for one schema; the rendered AsciiDoc body is supplied by a SchemaTemplate
|
|
12
|
+
# that the caller injects. This split means templates can be tested as pure
|
|
13
|
+
# functions and the compiler can be tested with a real adoc fixture, without
|
|
14
|
+
# either depending on the other.
|
|
15
|
+
class SchemaCompiler
|
|
16
|
+
attr_reader :schema, :id, :output_path, :template
|
|
17
|
+
|
|
18
|
+
def initialize(schema:, output_path:, template:)
|
|
19
|
+
@schema = schema
|
|
20
|
+
@id = schema.id
|
|
21
|
+
@output_path = output_path
|
|
22
|
+
@template = template
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def compile
|
|
26
|
+
save_config
|
|
27
|
+
save_adoc
|
|
28
|
+
invoke_metanorma
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def output_xml_path
|
|
33
|
+
filename_adoc("xml")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def extensions
|
|
37
|
+
template.extensions
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def filename_adoc(ext = "adoc")
|
|
43
|
+
File.join(output_path, "doc_#{id}.#{ext}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def filename_config
|
|
47
|
+
File.join(output_path, "schema_#{id}.yaml")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def save_adoc
|
|
51
|
+
log_relative "Save EXPRESS adoc", filename_adoc
|
|
52
|
+
|
|
53
|
+
FileUtils.mkdir_p(File.dirname(filename_adoc))
|
|
54
|
+
|
|
55
|
+
config_relative = Pathname.new(filename_config)
|
|
56
|
+
.relative_path_from(Pathname.new(File.dirname(filename_adoc)))
|
|
57
|
+
|
|
58
|
+
File.write(filename_adoc, template.render(config_relative))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def save_config
|
|
62
|
+
log_relative "Save schema config", filename_config
|
|
63
|
+
|
|
64
|
+
FileUtils.mkdir_p(File.dirname(filename_config))
|
|
65
|
+
config = Expressir::SchemaManifest.new
|
|
66
|
+
config.schemas << Expressir::SchemaManifestEntry.new(id: id,
|
|
67
|
+
path: schema.path)
|
|
68
|
+
config.save_to_path(filename_config)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def invoke_metanorma
|
|
72
|
+
log_relative "Compiling schema (id: #{id})", filename_adoc
|
|
73
|
+
Metanorma::Compile.new.compile(
|
|
74
|
+
filename_adoc,
|
|
75
|
+
agree_to_terms: true,
|
|
76
|
+
install_fonts: false,
|
|
77
|
+
)
|
|
78
|
+
log_relative "Compiling schema (id: #{id}) ... done!", filename_adoc
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def log_relative(prefix, path)
|
|
82
|
+
relative = Pathname.new(path).relative_path_from(Dir.pwd)
|
|
83
|
+
Utils.log "#{prefix}: #{relative}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Service for schema-config I/O on a single CollectionManifest node.
|
|
5
|
+
#
|
|
6
|
+
# SchemaDiscovery owns two concerns that previously lived on
|
|
7
|
+
# CollectionManifest:
|
|
8
|
+
#
|
|
9
|
+
# 1. Loading the `schemas.yaml` that sits next to a `collection.yml`
|
|
10
|
+
# into the manifest's `schema_config` slot.
|
|
11
|
+
# 2. Building the doc CollectionManifest sub-tree that hosts the
|
|
12
|
+
# compiled XML output for each schema in `schema_config`.
|
|
13
|
+
#
|
|
14
|
+
# It does not walk the manifest tree — that is ManifestTraverser's job.
|
|
15
|
+
# The split keeps schema I/O in one place and tree traversal in another,
|
|
16
|
+
# so each is independently testable.
|
|
17
|
+
class SchemaDiscovery
|
|
18
|
+
attr_reader :manifest
|
|
19
|
+
|
|
20
|
+
def initialize(manifest)
|
|
21
|
+
@manifest = manifest
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If the manifest's file is a `collection.yml` and a `schemas.yaml`
|
|
25
|
+
# sits alongside it, parse it into an Expressir::SchemaManifest and
|
|
26
|
+
# store it on the manifest. Otherwise leave `schema_config` untouched.
|
|
27
|
+
def load_config
|
|
28
|
+
return unless manifest.file
|
|
29
|
+
return unless File.basename(manifest.file) == "collection.yml"
|
|
30
|
+
|
|
31
|
+
schemas_yaml_path = File.join(File.dirname(manifest.file), "schemas.yaml")
|
|
32
|
+
return unless File.exist?(schemas_yaml_path)
|
|
33
|
+
|
|
34
|
+
manifest.schema_config = Expressir::SchemaManifest.from_file(schemas_yaml_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Build a CollectionManifest sub-tree that hosts the compiled XML for
|
|
38
|
+
# every schema in `manifest.schema_config`. The wrapper is named with
|
|
39
|
+
# a trailing underscore so it does not collide with the parent id.
|
|
40
|
+
def build_added_manifest(schema_output_path)
|
|
41
|
+
doc = CollectionConfig.from_file(manifest.file)
|
|
42
|
+
doc_id = doc.bibdata.id
|
|
43
|
+
|
|
44
|
+
added = CollectionManifest.new(
|
|
45
|
+
title: "Collection",
|
|
46
|
+
type: "collection",
|
|
47
|
+
identifier: "#{manifest.identifier}_",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
added.entry = [
|
|
51
|
+
CollectionManifest.new(
|
|
52
|
+
title: doc_id,
|
|
53
|
+
type: "document",
|
|
54
|
+
entry: build_doc_entries(schema_output_path),
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
added
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Build one CollectionManifest per schema in schema_config, naming
|
|
62
|
+
# each output file as `doc_<basename>.xml` under
|
|
63
|
+
# `<schema_output_path>/<schema_id>/`.
|
|
64
|
+
def build_doc_entries(schema_output_path)
|
|
65
|
+
manifest.schema_config.schemas.map do |schema|
|
|
66
|
+
xml_basename = "#{File.basename(schema.path, '.exp')}.xml"
|
|
67
|
+
CollectionManifest.new(
|
|
68
|
+
identifier: schema.id,
|
|
69
|
+
title: schema.id,
|
|
70
|
+
file: File.join(schema_output_path, schema.id, "doc_#{xml_basename}"),
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/suma/schema_exporter.rb
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "express_schema"
|
|
4
|
-
require_relative "utils"
|
|
5
3
|
require "fileutils"
|
|
6
4
|
|
|
7
5
|
module Suma
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Exports EXPRESS schemas to a directory, with optional ZIP packaging.
|
|
7
|
+
#
|
|
8
|
+
# Pure sink: the exporter accepts already-loaded +Suma::ExpressSchema+
|
|
9
|
+
# instances and writes their content to disk. Construction of those
|
|
10
|
+
# instances (with the right +output_path+ and +is_standalone_file+
|
|
11
|
+
# flags) is the caller's responsibility — the exporter does not
|
|
12
|
+
# reach across the seam to inspect manifest entries or classify
|
|
13
|
+
# schema types itself.
|
|
14
|
+
#
|
|
15
|
+
# This is a deep module: a small interface (one +export+ method, one
|
|
16
|
+
# option hash) backed by save_exp + zip packaging. The CLI and
|
|
17
|
+
# SchemaCollection adapters construct ExpressSchema instances; the
|
|
18
|
+
# exporter never inspects their shape.
|
|
10
19
|
class SchemaExporter
|
|
11
|
-
CATEGORY_MAP = {
|
|
12
|
-
ExpressSchema::Type::RESOURCE => "resources",
|
|
13
|
-
ExpressSchema::Type::MODULE_ARM => "modules",
|
|
14
|
-
ExpressSchema::Type::MODULE_MIM => "modules",
|
|
15
|
-
ExpressSchema::Type::BUSINESS_OBJECT_MODEL => "business_object_models",
|
|
16
|
-
ExpressSchema::Type::CORE_MODEL => "core_model",
|
|
17
|
-
ExpressSchema::Type::STANDALONE => ".",
|
|
18
|
-
}.freeze
|
|
19
|
-
|
|
20
20
|
attr_reader :schemas, :output_path, :options
|
|
21
21
|
|
|
22
22
|
def initialize(schemas:, output_path:, options: {})
|
|
@@ -46,38 +46,10 @@ module Suma
|
|
|
46
46
|
|
|
47
47
|
def export_to_directory(schemas)
|
|
48
48
|
schemas.each do |schema|
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def export_single_schema(schema)
|
|
54
|
-
is_standalone = !schema.is_a?(Expressir::SchemaManifestEntry)
|
|
55
|
-
schema_output_path = determine_output_path(schema, is_standalone)
|
|
56
|
-
|
|
57
|
-
express_schema = ExpressSchema.new(
|
|
58
|
-
id: schema.id,
|
|
59
|
-
path: schema.path.to_s,
|
|
60
|
-
output_path: schema_output_path,
|
|
61
|
-
is_standalone_file: is_standalone,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
express_schema.save_exp(with_annotations: options[:annotations])
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def determine_output_path(schema, is_standalone)
|
|
68
|
-
if is_standalone
|
|
69
|
-
output_path.to_s
|
|
70
|
-
else
|
|
71
|
-
category = categorize_schema(schema)
|
|
72
|
-
output_path.join(category).to_s
|
|
49
|
+
schema.save_exp(with_annotations: options[:annotations])
|
|
73
50
|
end
|
|
74
51
|
end
|
|
75
52
|
|
|
76
|
-
def categorize_schema(schema)
|
|
77
|
-
type = ExpressSchema::Type.classify(id: schema.id, path: schema.path)
|
|
78
|
-
CATEGORY_MAP.fetch(type, "standalone")
|
|
79
|
-
end
|
|
80
|
-
|
|
81
53
|
# rubocop:disable Metrics/MethodLength
|
|
82
54
|
def create_zip_archive
|
|
83
55
|
require "zip"
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "pathname"
|
|
5
|
-
require_relative "utils"
|
|
6
5
|
|
|
7
6
|
module Suma
|
|
8
7
|
class SchemaManifestGenerator
|
|
9
8
|
YAML_FILE_EXTENSIONS = [".yaml", ".yml"].freeze
|
|
10
9
|
|
|
11
|
-
def initialize(metanorma_manifest_file, schema_manifest_file,
|
|
10
|
+
def initialize(metanorma_manifest_file, schema_manifest_file,
|
|
11
|
+
exclude_paths: nil)
|
|
12
12
|
@metanorma_manifest_file = File.expand_path(metanorma_manifest_file)
|
|
13
13
|
@schema_manifest_file = schema_manifest_file
|
|
14
14
|
@exclude_paths = exclude_paths
|
|
@@ -27,9 +27,15 @@ module Suma
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def validate_inputs
|
|
30
|
-
|
|
30
|
+
unless File.exist?(@metanorma_manifest_file)
|
|
31
|
+
raise Errno::ENOENT,
|
|
32
|
+
"Specified file `#{@metanorma_manifest_file}` not found."
|
|
33
|
+
end
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
unless File.file?(@metanorma_manifest_file)
|
|
36
|
+
raise ArgumentError,
|
|
37
|
+
"Specified path `#{@metanorma_manifest_file}` is not a file."
|
|
38
|
+
end
|
|
33
39
|
|
|
34
40
|
[@metanorma_manifest_file, @schema_manifest_file].each do |file|
|
|
35
41
|
unless YAML_FILE_EXTENSIONS.include?(File.extname(file))
|
|
@@ -53,7 +59,8 @@ module Suma
|
|
|
53
59
|
all_schemas = { "schemas" => {} }
|
|
54
60
|
|
|
55
61
|
manifest_files.each do |file|
|
|
56
|
-
schemas_file_path = File.expand_path(file.gsub("collection.yml",
|
|
62
|
+
schemas_file_path = File.expand_path(file.gsub("collection.yml",
|
|
63
|
+
"schemas.yaml"))
|
|
57
64
|
|
|
58
65
|
unless File.exist?(schemas_file_path)
|
|
59
66
|
Utils.log "Schemas file not found: #{schemas_file_path}"
|
|
@@ -83,7 +90,8 @@ module Suma
|
|
|
83
90
|
schema_manifest_path = File.expand_path(@schema_manifest_file, Dir.pwd)
|
|
84
91
|
|
|
85
92
|
schemas_data["schemas"].each do |key, value|
|
|
86
|
-
path_in_schema = File.expand_path(value["path"],
|
|
93
|
+
path_in_schema = File.expand_path(value["path"],
|
|
94
|
+
File.dirname(schemas_file_path))
|
|
87
95
|
|
|
88
96
|
fixed_path = Pathname.new(path_in_schema).relative_path_from(
|
|
89
97
|
Pathname.new(File.dirname(schema_manifest_path)),
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Suma
|
|
4
|
+
# Converts EXPRESS schema identifiers into human-readable display names.
|
|
5
|
+
#
|
|
6
|
+
# Naming is model-driven: the schema type (resource/module) and suffix
|
|
7
|
+
# determine how the identifier is formatted. Acronyms and numeric
|
|
8
|
+
# prefixes are preserved to match ISO 10303 conventions.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# SchemaNaming.display_name("topology_schema")
|
|
12
|
+
# # => "Topology"
|
|
13
|
+
# SchemaNaming.display_name("Activity_method_assignment_mim")
|
|
14
|
+
# # => "Activity Method Assignment (MIM)"
|
|
15
|
+
# SchemaNaming.display_name("aic_advanced_brep")
|
|
16
|
+
# # => "AIC Advanced Brep"
|
|
17
|
+
module SchemaNaming
|
|
18
|
+
# Acronyms preserved as uppercase during title-casing.
|
|
19
|
+
# Source: ISO 10303 naming conventions.
|
|
20
|
+
ACRONYMS = %w[
|
|
21
|
+
aic aec apu bom csg edraw id ifc pdf pld
|
|
22
|
+
xml xpdl 2d 3d
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
# Lowercase function words (ISO title-case convention).
|
|
26
|
+
LOWERCASE_WORDS = %w[a an and as for in of on or the to].freeze
|
|
27
|
+
|
|
28
|
+
# Suffixes stripped from the schema name before title-casing,
|
|
29
|
+
# mapped to the parenthesised label appended to the display name.
|
|
30
|
+
# A +nil+ label means the suffix is stripped silently.
|
|
31
|
+
SUFFIXES = {
|
|
32
|
+
"_arm" => "ARM",
|
|
33
|
+
"_mim" => "MIM",
|
|
34
|
+
"_bom" => "BOM",
|
|
35
|
+
"_schema" => nil,
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Produce a human-readable display name from a schema identifier.
|
|
40
|
+
#
|
|
41
|
+
# @param schema_id [String] the EXPRESS schema identifier
|
|
42
|
+
# @return [String] human-readable name
|
|
43
|
+
def display_name(schema_id)
|
|
44
|
+
base, label = decompose(schema_id)
|
|
45
|
+
title_cased = title_case(base)
|
|
46
|
+
label ? "#{title_cased} (#{label})" : title_cased
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Produce a prefixed display name with the schema category.
|
|
50
|
+
#
|
|
51
|
+
# @param schema_id [String] the EXPRESS schema identifier
|
|
52
|
+
# @param path [String, Pathname] the schema file path (for classification)
|
|
53
|
+
# @return [String] e.g. "Resource: Topology" or "Module: Activity (ARM)"
|
|
54
|
+
def prefixed_name(schema_id, path: nil)
|
|
55
|
+
type = ExpressSchema::Type.classify(id: schema_id, path: path)
|
|
56
|
+
prefix = category_prefix(type)
|
|
57
|
+
"#{prefix}: #{display_name(schema_id)}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Determine the category prefix for a schema type.
|
|
61
|
+
#
|
|
62
|
+
# @param type [Symbol] one of ExpressSchema::Type constants
|
|
63
|
+
# @return [String]
|
|
64
|
+
def category_prefix(type)
|
|
65
|
+
SchemaCategory.for_type(type).prefix
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Split a schema ID into (base_without_suffix, suffix_label_or_nil).
|
|
71
|
+
#
|
|
72
|
+
# @return [(String, String, nil)]
|
|
73
|
+
def decompose(schema_id)
|
|
74
|
+
SUFFIXES.each do |suffix, label|
|
|
75
|
+
if schema_id.end_with?(suffix)
|
|
76
|
+
return [schema_id.delete_suffix(suffix),
|
|
77
|
+
label]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
[schema_id, nil]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Title-case a snake_case identifier, preserving acronyms.
|
|
84
|
+
# The +tr+ collapses runs of underscores (leading, trailing, and
|
|
85
|
+
# consecutive) into spaces, which +split+ then treats as a single
|
|
86
|
+
# separator.
|
|
87
|
+
#
|
|
88
|
+
# @param name [String] snake_case identifier
|
|
89
|
+
# @return [String] title-cased name
|
|
90
|
+
def title_case(name)
|
|
91
|
+
words = name.tr("_", " ").split
|
|
92
|
+
words.each_with_index.map do |word, i|
|
|
93
|
+
capitalize_word(word, first: i.zero?)
|
|
94
|
+
end.join(" ")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Capitalise a single word, preserving acronyms and
|
|
98
|
+
# lowercasing function words (except when first in the name).
|
|
99
|
+
#
|
|
100
|
+
# @param word [String]
|
|
101
|
+
# @param first [Boolean] whether this is the first word
|
|
102
|
+
# @return [String]
|
|
103
|
+
def capitalize_word(word, first: false)
|
|
104
|
+
return word.upcase if ACRONYMS.include?(word.downcase)
|
|
105
|
+
return word.downcase if LOWERCASE_WORDS.include?(word.downcase) && !first
|
|
106
|
+
|
|
107
|
+
word.capitalize
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|