suma 0.2.6 → 0.3.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -1
  3. data/.rubocop_todo.yml +170 -13
  4. data/CLAUDE.md +37 -11
  5. data/Gemfile +3 -3
  6. data/README.adoc +57 -1
  7. data/exe/suma +1 -1
  8. data/lib/suma/cli/build.rb +0 -5
  9. data/lib/suma/cli/check_svg_quality.rb +0 -6
  10. data/lib/suma/cli/compare.rb +0 -1
  11. data/lib/suma/cli/convert_jsdai.rb +0 -2
  12. data/lib/suma/cli/core.rb +119 -0
  13. data/lib/suma/cli/export.rb +0 -3
  14. data/lib/suma/cli/extract_terms.rb +5 -8
  15. data/lib/suma/cli/generate_register.rb +34 -0
  16. data/lib/suma/cli/generate_schemas.rb +0 -2
  17. data/lib/suma/cli/reformat.rb +0 -1
  18. data/lib/suma/cli/validate.rb +0 -2
  19. data/lib/suma/cli/validate_links.rb +0 -2
  20. data/lib/suma/cli.rb +12 -141
  21. data/lib/suma/collection_config.rb +0 -2
  22. data/lib/suma/collection_manifest.rb +7 -110
  23. data/lib/suma/eengine/wrapper.rb +0 -1
  24. data/lib/suma/eengine.rb +8 -0
  25. data/lib/suma/express_schema.rb +0 -1
  26. data/lib/suma/jsdai/figure.rb +0 -3
  27. data/lib/suma/jsdai.rb +5 -2
  28. data/lib/suma/link_validator.rb +15 -7
  29. data/lib/suma/manifest_traverser.rb +92 -0
  30. data/lib/suma/processor.rb +5 -8
  31. data/lib/suma/register_manifest_generator.rb +163 -0
  32. data/lib/suma/schema_category.rb +83 -0
  33. data/lib/suma/schema_collection.rb +29 -33
  34. data/lib/suma/schema_comparer.rb +4 -3
  35. data/lib/suma/schema_compiler.rb +86 -0
  36. data/lib/suma/schema_discovery.rb +75 -0
  37. data/lib/suma/schema_exporter.rb +2 -18
  38. data/lib/suma/schema_manifest_generator.rb +14 -6
  39. data/lib/suma/schema_naming.rb +111 -0
  40. data/lib/suma/schema_template/document.rb +141 -0
  41. data/lib/suma/schema_template/plain.rb +46 -0
  42. data/lib/suma/schema_template.rb +19 -0
  43. data/lib/suma/svg_quality/batch_report.rb +0 -2
  44. data/lib/suma/svg_quality/formatters.rb +12 -0
  45. data/lib/suma/svg_quality.rb +3 -1
  46. data/lib/suma/term_extractor.rb +119 -46
  47. data/lib/suma/urn.rb +61 -0
  48. data/lib/suma/version.rb +1 -1
  49. data/lib/suma.rb +31 -3
  50. data/suma.gemspec +1 -1
  51. metadata +24 -6
  52. data/lib/suma/schema_attachment.rb +0 -103
  53. data/lib/suma/schema_document.rb +0 -118
@@ -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
@@ -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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "report"
4
-
5
3
  module Suma
6
4
  module SvgQuality
7
5
  # Batch report wrapping multiple SVG quality reports
@@ -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
@@ -4,7 +4,9 @@ require "svg_conform"
4
4
 
5
5
  module Suma
6
6
  module SvgQuality
7
- # Quality levels computed from error count
7
+ autoload :Report, "suma/svg_quality/report"
8
+ autoload :BatchReport, "suma/svg_quality/batch_report"
9
+
8
10
  module QualityTiers
9
11
  CRITICAL = { name: :critical, min_errors: 200, emoji: "💥" }.freeze
10
12
  HIGH = { name: :high, min_errors: 100, emoji: "🔴" }.freeze
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require_relative "utils"
5
4
  require "expressir"
6
5
  require "glossarist"
7
6
 
@@ -16,13 +15,16 @@ module Suma
16
15
  \s*?\.?$ # Optional whitespace and period at the end
17
16
  }x
18
17
 
19
- def initialize(schema_manifest_file, output_path, language_code: "eng")
18
+ def initialize(schema_manifest_file, output_path, urn:,
19
+ language_code: "eng")
20
20
  @schema_manifest_file = File.expand_path(schema_manifest_file)
21
21
  @output_path = output_path
22
22
  @language_code = language_code
23
+ @urn = Suma::Urn.new(urn)
23
24
  end
24
25
 
25
26
  def call
27
+ validate_inputs
26
28
  get_exp_files.map do |exp_file|
27
29
  extract(exp_file)
28
30
  end
@@ -30,12 +32,24 @@ module Suma
30
32
 
31
33
  private
32
34
 
35
+ def validate_inputs
36
+ unless File.exist?(@schema_manifest_file)
37
+ raise Errno::ENOENT, "Specified SCHEMA_MANIFEST_FILE " \
38
+ "`#{@schema_manifest_file}` not found."
39
+ end
40
+ unless File.file?(@schema_manifest_file)
41
+ raise Errno::ENOENT, "Specified SCHEMA_MANIFEST_FILE " \
42
+ "`#{@schema_manifest_file}` is not a file."
43
+ end
44
+ end
45
+
33
46
  def get_exp_files
34
47
  config = Expressir::SchemaManifest.from_file(@schema_manifest_file)
35
48
  paths = config.schemas.map(&:path)
36
49
 
37
50
  if paths.empty?
38
- raise Errno::ENOENT, "No EXPRESS files found in `#{@schema_manifest_file}`."
51
+ raise Errno::ENOENT,
52
+ "No EXPRESS files found in `#{@schema_manifest_file}`."
39
53
  end
40
54
 
41
55
  paths
@@ -56,33 +70,55 @@ module Suma
56
70
  end
57
71
 
58
72
  def output_data(collection)
59
- FileUtils.mkdir_p(File.expand_path(@output_path)) unless File.exist?(@output_path)
73
+ output_dir = File.expand_path(@output_path)
74
+ FileUtils.mkdir_p(output_dir)
60
75
  Utils.log "Saving collection to files in: #{@output_path}"
61
- collection.save_to_files(File.expand_path(@output_path))
76
+
77
+ collection.each do |concept|
78
+ doc = Glossarist::V3::ConceptDocument.from_managed_concept(concept)
79
+ doc.localizations = concept.data.localizations.keys.map do |lang|
80
+ concept.localization(lang)
81
+ end
82
+
83
+ filename = "#{concept.uuid.gsub(/[^\w.-]/, '_')}.yaml"
84
+ File.write(File.join(output_dir, filename), doc.to_yamls,
85
+ encoding: "utf-8")
86
+ end
62
87
  end
63
88
 
64
89
  def build_managed_concept_collection(schema)
65
90
  source_ref = get_source_ref(schema)
91
+ section_ref = get_section_ref(schema)
66
92
 
67
93
  Glossarist::ManagedConceptCollection.new.tap do |collection|
68
94
  schema.entities.each do |entity|
95
+ localized_concept_id = Glossarist::Utilities::UUID.uuid_v5(
96
+ Glossarist::Utilities::UUID::OID_NAMESPACE,
97
+ "#{schema.id}.#{entity.id}-#{@language_code}",
98
+ )
99
+
69
100
  localized_concept = build_localized_concept(
70
101
  schema: schema,
71
102
  entity: entity,
72
103
  source_ref: source_ref,
104
+ uuid: localized_concept_id,
73
105
  )
74
- localized_concept_id = get_localized_concept_identifier(schema, entity)
75
106
 
76
- managed_data = Glossarist::ManagedConceptData.new.tap do |data|
77
- data.id = get_entity_identifier(schema, entity)
78
- data.localizations[@language_code] = localized_concept
107
+ managed_data = Glossarist::V3::ManagedConceptData.new.tap do |data|
108
+ data.id = "#{schema.id}.#{entity.id}"
109
+ data.localizations.store(@language_code, localized_concept)
79
110
  data.localized_concepts = { @language_code => localized_concept_id }
111
+ data.domains = [section_ref] if section_ref
80
112
  end
81
113
 
82
- managed_concept = Glossarist::ManagedConcept.new.tap do |concept|
83
- concept.id = get_entity_identifier(schema, entity)
84
- concept.uuid = concept.id
114
+ managed_concept = Glossarist::V3::ManagedConcept.new.tap do |concept|
115
+ concept.id = managed_data.id
116
+ concept.uuid = Glossarist::Utilities::UUID.uuid_v5(
117
+ Glossarist::Utilities::UUID::OID_NAMESPACE,
118
+ managed_data.id,
119
+ )
85
120
  concept.data = managed_data
121
+ concept.schema_version = "v3"
86
122
  end
87
123
 
88
124
  collection.store(managed_concept)
@@ -90,10 +126,10 @@ module Suma
90
126
  end
91
127
  end
92
128
 
93
- def build_localized_concept(schema:, entity:, source_ref:)
129
+ def build_localized_concept(schema:, entity:, source_ref:, uuid:)
94
130
  schema_domain = get_domain(schema)
95
131
 
96
- localized_concept_data = Glossarist::ConceptData.new.tap do |data|
132
+ localized_concept_data = Glossarist::V3::ConceptData.new.tap do |data|
97
133
  data.terms = get_entity_terms(entity)
98
134
  data.definition = get_entity_definitions(entity, schema)
99
135
  data.language_code = @language_code
@@ -105,34 +141,60 @@ module Suma
105
141
  data.examples = []
106
142
  end
107
143
 
108
- Glossarist::LocalizedConcept.new.tap { |c| c.data = localized_concept_data }
144
+ Glossarist::V3::LocalizedConcept.new(
145
+ data: localized_concept_data, uuid: uuid,
146
+ )
147
+ end
148
+
149
+ def schema_urn(schema)
150
+ @urn.for_schema(schema.id)
151
+ end
152
+
153
+ def term_urn(concept_identifier)
154
+ @urn.for_term(concept_identifier)
109
155
  end
110
156
 
111
- def get_entity_identifier(schema, entity)
112
- "#{schema.id}.#{entity.id}"
157
+ def express_entity_urn(full_ref)
158
+ @urn.for_entity(full_ref)
113
159
  end
114
160
 
115
- def get_localized_concept_identifier(schema, entity)
116
- "#{schema.id}.#{entity.id}-#{@language_code}"
161
+ def urn_mention(urn, display)
162
+ "{{#{urn},#{display}}}"
163
+ end
164
+
165
+ def get_section_ref(schema)
166
+ return nil unless @urn
167
+
168
+ Glossarist::ConceptReference.new(
169
+ concept_id: "section-#{schema.id}",
170
+ source: schema_urn(schema),
171
+ ref_type: "section",
172
+ )
117
173
  end
118
174
 
119
175
  def get_source_ref(schema)
120
- origin = Glossarist::Citation.new.tap do |citation|
121
- citation.ref = "ISO 10303"
122
- custom_locality = build_custom_locality(schema)
123
- citation.custom_locality = custom_locality unless custom_locality.empty?
176
+ ref = Glossarist::Citation::Ref.new
177
+ ref.source = schema_urn(schema)
178
+
179
+ build_custom_locality(schema).each do |cl|
180
+ case cl.name
181
+ when "version" then ref.version = cl.value
182
+ end
124
183
  end
125
184
 
126
- Glossarist::ConceptSource.new(type: "authoritative", origin: origin)
185
+ origin = Glossarist::V3::Citation.new
186
+ origin.ref = ref
187
+ Glossarist::V3::ConceptSource.new(id: schema.id, type: "authoritative",
188
+ origin: origin)
127
189
  end
128
190
 
129
191
  def build_custom_locality(schema)
130
192
  localities = []
131
- localities << Glossarist::CustomLocality.new(name: "schema", value: schema.id)
132
193
 
133
194
  version_item = schema.version.items.detect { |i| i.name == "version" }
134
195
  if version_item
135
- localities << Glossarist::CustomLocality.new(name: "version", value: version_item.value)
196
+ localities << Glossarist::CustomLocality.new(name: "version",
197
+ value: version_item.value)
136
198
  end
137
199
 
138
200
  localities
@@ -160,10 +222,10 @@ module Suma
160
222
 
161
223
  def get_entity_definitions(entity, schema)
162
224
  schema_type = extract_file_type(schema.file)
163
- schema_domain = get_domain(schema)
225
+ get_domain(schema)
164
226
 
165
- definition = generate_entity_definition(entity, schema_domain, schema_type)
166
- [Glossarist::DetailedDefinition.new(content: definition)]
227
+ definition = generate_entity_definition(entity, schema, schema_type)
228
+ [Glossarist::V3::DetailedDefinition.new(content: definition)]
167
229
  end
168
230
 
169
231
  def get_entity_notes(entity, schema_domain, definitions)
@@ -172,7 +234,7 @@ module Suma
172
234
  if entity.remarks && !entity.remarks.empty?
173
235
  trimmed_def = trim_definition(entity.remarks)
174
236
  if trimmed_def && !trimmed_def.empty?
175
- notes << Glossarist::DetailedDefinition.new(
237
+ notes << Glossarist::V3::DetailedDefinition.new(
176
238
  content: convert_express_xref(trimmed_def, schema_domain),
177
239
  )
178
240
  end
@@ -343,11 +405,14 @@ module Suma
343
405
 
344
406
  def express_reference_to_mention(description)
345
407
  description
346
- .gsub(/<<express:([^,]+)>>/) do |_match|
347
- "{{#{Regexp.last_match[1].split('.').last}}}"
348
- end.gsub(/<<express:([^,]+),([^>]+)>>/) do |_match|
349
- "{{#{Regexp.last_match[1].split('.').last}," \
350
- "#{Regexp.last_match[2]}}}"
408
+ .gsub(/<<express:([\w.]+)>>/) do |_match|
409
+ full_ref = Regexp.last_match[1]
410
+ entity_id = full_ref.split(".").last
411
+ urn_mention(express_entity_urn(full_ref), entity_id)
412
+ end.gsub(/<<express:([\w.]+),([\w. ][\w. ]*)>>/) do |_match|
413
+ full_ref = Regexp.last_match[1]
414
+ display = Regexp.last_match(2)
415
+ urn_mention(express_entity_urn(full_ref), display)
351
416
  end
352
417
  end
353
418
 
@@ -355,38 +420,46 @@ module Suma
355
420
  entity_id.downcase.gsub("_", " ")
356
421
  end
357
422
 
358
- def generate_entity_definition(entity, _domain, schema_type)
423
+ def generate_entity_definition(entity, schema, schema_type)
359
424
  return "" if entity.nil?
360
425
 
361
426
  entity_type = case schema_type
362
427
  when "module_arm"
363
- "{{application object}}"
428
+ urn_mention(term_urn("general.application_object"),
429
+ "application object")
364
430
  when "module_mim"
365
- "{{entity data type}}"
431
+ urn_mention(term_urn("express-language.entity_data_type"),
432
+ "entity data type")
366
433
  when "resource", "business_object_model"
367
- "{{entity data type}}"
434
+ urn_mention(term_urn("express-language.entity_data_type"),
435
+ "entity data type")
368
436
  else
369
437
  raise Error, "[suma] encountered unsupported schema_type"
370
438
  end
371
439
 
440
+ entity_ref = urn_mention(term_urn("express-language.entity"), "entity")
441
+
372
442
  if entity.subtype_of.empty?
373
443
  "#{entity_type} " \
374
444
  "that represents the " \
375
- "#{entity_name_to_text(entity.id)} {{entity}}"
445
+ "#{entity_name_to_text(entity.id)} #{entity_ref}"
376
446
  else
377
- entity_subtypes = entity.subtype_of.map { |e| "{{#{e.id}}}" }
447
+ entity_subtypes = entity.subtype_of.map do |e|
448
+ urn_mention(express_entity_urn("#{schema.id}.#{e.id}"), e.id)
449
+ end
378
450
 
379
451
  "#{entity_type} that is a type of " \
380
452
  "#{entity_subtypes.join(' and ')} " \
381
453
  "that represents the " \
382
- "#{entity_name_to_text(entity.id)} {{entity}}"
454
+ "#{entity_name_to_text(entity.id)} #{entity_ref}"
383
455
  end
384
456
  end
385
457
 
386
- def convert_express_xref(content, schema_domain)
387
- content.gsub(/<<express:(.*),(.*)>>/) do
388
- "{{<#{schema_domain}>" \
389
- "#{Regexp.last_match(1).split('.').last},#{Regexp.last_match(2)}}}"
458
+ def convert_express_xref(content, _schema_domain)
459
+ content.gsub(/<<express:([\w.]+),([\w. ][\w. ]*)>>/) do
460
+ full_ref = Regexp.last_match(1)
461
+ display = Regexp.last_match(2)
462
+ urn_mention(express_entity_urn(full_ref), display)
390
463
  end
391
464
  end
392
465
  end