metanorma-plugin-lutaml 0.7.10 → 0.7.11

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: 4a8884a3a005d1987c9403af11f7dddb62657e28178881e7ef848383280edfcb
4
- data.tar.gz: 210ca361b8facc4702196a1f2f734e79abe0b325e30db269c991884c0cbc2714
3
+ metadata.gz: c0601a4c6b35e5d1fd1c58d24240e3a5dea1b87bae62cbf9ce17ac904c21d5bf
4
+ data.tar.gz: 60d05d99810e378da5df64b8ec603fbf522a3e66c10ef9ef776c0c616dd2a9e0
5
5
  SHA512:
6
- metadata.gz: ab318c1c9a6bf060093600533ff59c6b293c70a90c3096ec5ab88100546e2ae2a18d0e52f8571bdf72d1da14df0fd6fdbfbc7a8cf3c9716ba34a2f154a4e5f64
7
- data.tar.gz: 0e3acb93421b591442d0293c08c5fb9b2e8e8c1f4925eb5f9f0fbbb77997b5cbc80e8e30cce5dd4accc4b1fe40d91c2ebce09e7f17db901ad8afbb82b8d0cb37
6
+ metadata.gz: 1a6e74aa665b6b3917ce08bc0a8aa7464dd3acb3fa6e1664f0a4e9951c3a204769299d8fbe3ff3be3f9b700b9d3cf28def826844cb0fa6de94fc6158827eb011
7
+ data.tar.gz: e772698bfc26cbb86bf58e171dcd2e531c91b834f67b7241939ee9db2cf51516d8531c46ae69eae14148c46575c85fe94e02502b73b0d78e2c3ff8bc2546d6fa
data/.gitignore CHANGED
@@ -4,3 +4,5 @@ spec/assets/lutaml
4
4
  test.err
5
5
 
6
6
  .rubocop-https--*
7
+ .DS_Store
8
+ .vscode
data/README.adoc CHANGED
@@ -379,7 +379,7 @@ The table will show:
379
379
 
380
380
  [source,adoc]
381
381
  -----
382
- lutaml_klass_table::/path/to/example.xmi[name="NameOfClass",tmpl_folder="/path/to/templates"]
382
+ lutaml_klass_table::/path/to/example.xmi[name="NameOfClass",template="/path/to/templates/_my_klass_table.liquid"]
383
383
  -----
384
384
 
385
385
  The command accepts the options listed below:
@@ -388,11 +388,11 @@ The command accepts the options listed below:
388
388
 
389
389
  * `name="NameOfClass"` specifies the name of the Class.
390
390
 
391
- * `tmpl_folder="/path/to/templates"` specifies the folder path of the templates.
392
- (Optional) By default, it will look for the templates `_klass_table.liquid`,
393
- `_klass_row.liquid` and `_klass_assoc_row.liquid` defined in
394
- `lib/metanorma/plugin/lutaml/templates`. These templates can be customized by
395
- defining them in `/path/to/templates` and then set the `tmpl_folder` option.
391
+ * `template="/path/to/templates/_my_klass_table.liquid"` specifies the path of
392
+ the liquid template. (Optional)
393
+ By default, it will look for the template `_klass_table.liquid` defined in
394
+ `lib/metanorma/plugin/lutaml/templates`. This template can be customized by
395
+ changing the template path in the `template` option.
396
396
 
397
397
  === Generating UML class and attributes: `lutaml_uml_class`
398
398
 
@@ -1,3 +1,4 @@
1
+ {% assign root = klass.generalization %}
1
2
  [cols="1a,1a,2a"]
2
3
  |===
3
4
  | Definition of Type
@@ -8,17 +9,57 @@ h| Stereotype 2+| << {{ root.stereotype }} >>
8
9
 
9
10
  3+h| Inherited Properties
10
11
  h| Property Name h| Property Type and Multiplicity h| Definition
11
- {{ content.inherited_props }}
12
+ {% for attr in root.inherited_props %}
13
+ {%- if attr.has_association? == false -%}
14
+ {%- capture name_col -%}
15
+ {{ attr.name_ns }}:{{ attr.name }}
16
+ ({{ attr.gen_name }})
17
+ {%- endcapture -%}
18
+ | {{ name_col | newline_to_br }}
19
+ | {{ attr.type }} [{{ attr.cardinality }}]
20
+ | {{ attr.definition }}
21
+ {%- endif -%}
22
+ {% endfor %}
12
23
 
13
24
  3+h| Self-defined Properties
14
25
  h| Property Name h| Property Type and Multiplicity h| Definition
15
- {{ content.owned_props }}
26
+ {% for attr in root.owned_props %}
27
+ {%- if attr.has_association? == false -%}
28
+ {%- capture name_col -%}
29
+ {{ attr.name_ns }}:{{ attr.name }}
30
+ ({{ attr.gen_name }})
31
+ {%- endcapture -%}
32
+ | {{ name_col | newline_to_br }}
33
+ | {{ attr.type }} [{{ attr.cardinality }}]
34
+ | {{ attr.definition }}
35
+ {%- endif -%}
36
+ {% endfor %}
16
37
 
17
38
  3+h| Properties Inherited from Association
18
39
  h| Property Name h| Property Type and Multiplicity h| Definition
19
- {{ content.inherited_assoc_props }}
40
+ {% for attr in root.inherited_assoc_props %}
41
+ {%- if attr.has_association? == true -%}
42
+ {%- capture name_col -%}
43
+ {{ attr.name_ns }}:{{ attr.name }}
44
+ ({{ attr.gen_name }})
45
+ {%- endcapture -%}
46
+ | {{ name_col | newline_to_br }}
47
+ | {{ attr.type_ns }}:{{ attr.type }} [{{ attr.cardinality }}]
48
+ | {{ attr.definition }}
49
+ {%- endif -%}
50
+ {% endfor %}
20
51
 
21
52
  3+h| Properties Defined in Association
22
53
  h| Property Name h| Property Type and Multiplicity h| Definition
23
- {{ content.assoc_props }}
54
+ {% for attr in root.assoc_props %}
55
+ {%- if attr.has_association? == true -%}
56
+ {%- capture name_col -%}
57
+ {{ attr.name_ns }}:{{ attr.name }}
58
+ ({{ attr.gen_name }})
59
+ {%- endcapture -%}
60
+ | {{ name_col | newline_to_br }}
61
+ | {{ attr.type_ns }}:{{ attr.type }} [{{ attr.cardinality }}]
62
+ | {{ attr.definition }}
63
+ {%- endif -%}
64
+ {% endfor %}
24
65
  |===
@@ -12,8 +12,7 @@ module Metanorma
12
12
  named :lutaml_ea_diagram
13
13
 
14
14
  def process(parent, _target, attrs)
15
- orig_doc = parent.document.attributes["lutaml_xmi_cache"]
16
- .values.first.original_document
15
+ orig_doc = get_original_document(parent)
17
16
  diagram = fetch_diagram_by_name(orig_doc, attrs["name"])
18
17
  return if diagram.nil?
19
18
 
@@ -27,6 +26,13 @@ module Metanorma
27
26
 
28
27
  private
29
28
 
29
+ def get_original_document(parent)
30
+ doc = parent.document.attributes["lutaml_xmi_cache"].values.first
31
+ return doc if doc.instance_of?(::Lutaml::XMI::RootDrop)
32
+
33
+ doc.original_document
34
+ end
35
+
30
36
  def img_src_path(document, attrs, diagram)
31
37
  base_path = attrs["base_path"]
32
38
  format = attrs["format"] || "png"
@@ -0,0 +1,345 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+ require "asciidoctor"
5
+ require "asciidoctor/reader"
6
+ require "lutaml"
7
+ require "lutaml/uml"
8
+ require "lutaml/formatter"
9
+ require "metanorma/plugin/lutaml/utils"
10
+
11
+ module Metanorma
12
+ module Plugin
13
+ module Lutaml
14
+ module LutamlEaXmiBase
15
+ LIQUID_INCLUDE_PATH = File.join(
16
+ Gem.loaded_specs["metanorma-plugin-lutaml"].full_gem_path,
17
+ "lib", "metanorma", "plugin", "lutaml", "liquid_templates"
18
+ )
19
+ DEFAULT_RENDER_INCLUDE = "packages"
20
+ RENDER_STYLES_INCLUDES = {
21
+ "default" => "packages",
22
+ "entity_list" => "packages_entity_list",
23
+ "entity_list_class" => "packages_entity_list_class",
24
+ "data_dictionary" => "packages_data_dictionary",
25
+ }.freeze
26
+ RENDER_STYLE_ATTRIBUTE = "render_style"
27
+ SUPPORTED_NESTED_MACRO = %w[
28
+ before diagram_include_block after include_block package_text
29
+ ].freeze
30
+
31
+ # search document for block `lutaml_ea_xmi`
32
+ # or `lutaml_uml_datamodel_description`
33
+ # read include derectives that goes after that in block and transform
34
+ # into yaml2text blocks
35
+ def process(document, reader)
36
+ r = Asciidoctor::PreprocessorNoIfdefsReader.new document, reader.lines
37
+ input_lines = r.readlines.to_enum
38
+ Asciidoctor::PreprocessorNoIfdefsReader
39
+ .new(document, processed_lines(document, input_lines))
40
+ end
41
+
42
+ private
43
+
44
+ def lutaml_document_from_file_or_cache(document, file_path, yaml_config) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
45
+ full_path = Utils.relative_file_path(document, file_path)
46
+ if document.attributes["lutaml_xmi_cache"] &&
47
+ document.attributes["lutaml_xmi_cache"][full_path]
48
+ return document.attributes["lutaml_xmi_cache"][full_path]
49
+ end
50
+
51
+ yaml_config["ea_extension"]&.each do |ea_extension_path|
52
+ ea_extension_full_path = File.expand_path(
53
+ ea_extension_path, File.dirname(file_path)
54
+ )
55
+ Xmi::EaRoot.load_extension(ea_extension_full_path)
56
+ end
57
+
58
+ result_document = parse_result_document(full_path)
59
+ document.attributes["lutaml_xmi_cache"] ||= {}
60
+ document.attributes["lutaml_xmi_cache"][full_path] = result_document
61
+ result_document
62
+ end
63
+
64
+ def parse_yaml_config_file(document, file_path)
65
+ return {} if file_path.nil?
66
+
67
+ relative_file_path = Utils.relative_file_path(document, file_path)
68
+ YAML.safe_load(File.read(relative_file_path, encoding: "UTF-8"))
69
+ end
70
+
71
+ def processed_lines(document, input_lines)
72
+ result = []
73
+ loop do
74
+ result.push(*process_text_blocks(document, input_lines))
75
+ end
76
+ result
77
+ end
78
+
79
+ def get_macro_regexp
80
+ self.class.const_get(:MACRO_REGEXP)
81
+ end
82
+
83
+ def process_text_blocks(document, input_lines) # rubocop:disable Metrics/MethodLength
84
+ line = input_lines.next
85
+ block_match = line.match(get_macro_regexp)
86
+
87
+ return [line] if block_match.nil?
88
+
89
+ yaml_config = parse_yaml_config_file(document, block_match[2])
90
+ lutaml_document = lutaml_document_from_file_or_cache(document,
91
+ block_match[1],
92
+ yaml_config)
93
+ fill_in_diagrams_attributes(document, lutaml_document)
94
+ model_representation(
95
+ lutaml_document, document,
96
+ collect_additional_context(document, input_lines, input_lines.next),
97
+ yaml_config
98
+ )
99
+ end
100
+
101
+ def get_original_document(wrapper)
102
+ doc = wrapper
103
+ return doc if doc.instance_of?(::Lutaml::XMI::RootDrop)
104
+
105
+ doc.original_document
106
+ end
107
+
108
+ def fill_in_entities_refs_attributes(document, # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
109
+ lutaml_document, _options)
110
+ lutaml_document = get_original_document(lutaml_document)
111
+ # render_style = options.fetch(RENDER_STYLE_ATTRIBUTE, "default")
112
+ all_children_packages = lutaml_document.packages
113
+ .map(&:children_packages).flatten
114
+ package_flat_packages = lambda do |pks|
115
+ pks.each_with_object({}) do |package, res|
116
+ res[package.name] = package.xmi_id
117
+ end
118
+ end
119
+ children_pks = package_flat_packages.call(all_children_packages)
120
+ ref_dictionary = package_flat_packages.call(lutaml_document.packages)
121
+ .merge(children_pks)
122
+ %w[class enum data_type].each do |type|
123
+ package_flat = lambda do |pks|
124
+ pks.each_with_object({}) do |package, res|
125
+ plural = type == "class" ? "classes" : "#{type}s"
126
+ package.send(plural).map do |entity|
127
+ res["#{type}:#{package.name}:#{entity.name}"] = entity.xmi_id
128
+ end
129
+ end
130
+ end
131
+ children_pks_diags = package_flat.call(all_children_packages)
132
+ ref_dictionary = ref_dictionary
133
+ .merge(package_flat.call(lutaml_document.packages)
134
+ .merge(children_pks_diags))
135
+ end
136
+ document.attributes["lutaml_entity_id"] = ref_dictionary
137
+ end
138
+
139
+ def fill_in_diagrams_attributes(document, lutaml_document) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
140
+ lutaml_document = get_original_document(lutaml_document)
141
+ package_flat_diagrams = lambda do |pks|
142
+ pks.each_with_object({}) do |package, res|
143
+ package.diagrams.map do |diag|
144
+ res["#{package.name}:#{diag.name}"] = diag.xmi_id
145
+ end
146
+ end
147
+ end
148
+ children_pks_diags = package_flat_diagrams.call(
149
+ lutaml_document.packages.map(&:children_packages).flatten,
150
+ )
151
+
152
+ document.attributes["lutaml_figure_id"] = package_flat_diagrams
153
+ .call(lutaml_document.packages)
154
+ .merge(children_pks_diags)
155
+ end
156
+
157
+ def collect_additional_context(document, input_lines, end_mark) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
158
+ additional_context = Hash.new { |hash, key| hash[key] = [] }
159
+ additional_context["all_macros"] = []
160
+ block_lines = []
161
+
162
+ while (block_line = input_lines.next) != end_mark
163
+ block_lines.push(block_line)
164
+ end
165
+
166
+ processed_lines = process(
167
+ document,
168
+ ::Asciidoctor::PreprocessorReader.new(document, block_lines),
169
+ ).read_lines
170
+
171
+ block_document = ::Asciidoctor::Document
172
+ .new(processed_lines, {}).parse
173
+ block_document.blocks.each do |block|
174
+ next unless SUPPORTED_NESTED_MACRO.include?(
175
+ block.attributes["role"],
176
+ )
177
+
178
+ attrs = block.attributes
179
+ name = attrs.delete("role")
180
+ package = attrs.delete("package")
181
+ macro_keyword = [name, package].compact.join(";")
182
+ block_text = if block.lines.length.positive?
183
+ block.lines.join("\n")
184
+ else
185
+ ""
186
+ end
187
+ additional_context[macro_keyword]
188
+ .push({ "text" => block_text }.merge(attrs))
189
+ additional_context["all_macros"]
190
+ .push({ "text" => block_text,
191
+ "type" => name, "package" => package }.merge(attrs))
192
+ end
193
+ additional_context
194
+ end
195
+
196
+ def package_level(lutaml_document, level)
197
+ return lutaml_document if level <= 0
198
+
199
+ package_level(lutaml_document["packages"].first, level - 1)
200
+ end
201
+
202
+ def create_context_object(lutaml_document, additional_context, options) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
203
+ root_package = package_level(lutaml_document.to_liquid,
204
+ options["package_root_level"] || 1)
205
+ if options.empty? || options["packages"].nil?
206
+ return {
207
+ "render_nested_packages" => true,
208
+ "packages" => root_package["packages"],
209
+ "root_packages" => [root_package],
210
+ "additional_context" => additional_context
211
+ .merge("external_classes" => options["external_classes"]),
212
+ "name" => root_package["name"],
213
+ }
214
+ end
215
+
216
+ all_packages = [root_package, *root_package["children_packages"]]
217
+ {
218
+ "packages" => sort_and_filter_out_packages(all_packages, options),
219
+ "package_entities" => package_entities(options),
220
+ "package_skip_sections" => package_skip_sections(options),
221
+ "additional_context" => additional_context
222
+ .merge("external_classes" => options["external_classes"]),
223
+ "root_packages" => [root_package],
224
+ "render_nested_packages" => options["render_nested_packages"] ||
225
+ false,
226
+ "name" => root_package["name"],
227
+ }
228
+ end
229
+
230
+ def package_entities(options) # rubocop:disable Metrics/AbcSize
231
+ return {} unless options["packages"]
232
+
233
+ options["packages"].find_all do |entity|
234
+ entity.is_a?(Hash) && entity.values.first["render_entities"]
235
+ end.map do |entity|
236
+ [entity.keys.first,
237
+ entity.values.first["render_entities"].map { |n| [n, true] }.to_h]
238
+ end.to_h
239
+ end
240
+
241
+ def package_skip_sections(options) # rubocop:disable Metrics/AbcSize
242
+ return {} unless options["packages"]
243
+
244
+ options["packages"].find_all do |entity|
245
+ entity.is_a?(Hash) && entity.values.first["skip_tables"]
246
+ end.map do |entity|
247
+ [entity.keys.first,
248
+ entity.values.first["skip_tables"].map { |n| [n, true] }.to_h]
249
+ end.to_h
250
+ end
251
+
252
+ def sort_and_filter_out_packages(all_packages, options) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
253
+ return all_packages if options["packages"].nil?
254
+
255
+ result = []
256
+ # Step one - filter out all skipped packages
257
+ all_packages =
258
+ filter_out_all_skipped_packages(options, all_packages)
259
+
260
+ # Step two - select supplied packages by pattern
261
+ select_supplied_packages_by_pattern(options, all_packages,
262
+ result)
263
+ end
264
+
265
+ def filter_out_all_skipped_packages(options, all_packages)
266
+ options["packages"].find_all do |entity|
267
+ entity.is_a?(Hash) && entity["skip"]
268
+ end.each do |entity|
269
+ entity_regexp = config_entity_regexp(entity["skip"])
270
+ all_packages.delete_if do |package|
271
+ package["name"] =~ entity_regexp
272
+ end
273
+ end
274
+
275
+ all_packages
276
+ end
277
+
278
+ def select_supplied_packages_by_pattern(options, all_packages, result) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
279
+ options["packages"].find_all do |entity|
280
+ entity.is_a?(String) || (entity.is_a?(Hash) && !entity["skip"])
281
+ end.each do |entity_obj|
282
+ entity = if entity_obj.is_a?(String)
283
+ entity_obj
284
+ else
285
+ entity_obj.keys.first
286
+ end
287
+ entity_regexp = config_entity_regexp(entity)
288
+ all_packages.each do |package|
289
+ if package["name"]&.match?(entity_regexp)
290
+ result.push(package)
291
+ all_packages.delete_if do |nest_package|
292
+ nest_package["name"] == package["name"]
293
+ end
294
+ end
295
+ end
296
+ end
297
+
298
+ result
299
+ end
300
+
301
+ def config_entity_regexp(entity)
302
+ additional_sym = ".*" if /\*$/.match?(entity)
303
+ %r{^#{Regexp.escape(entity.delete('*'))}#{additional_sym}$}
304
+ end
305
+
306
+ def model_representation(lutaml_doc, document, add_context, options) # rubocop:disable Metrics/MethodLength
307
+ fill_in_entities_refs_attributes(document, lutaml_doc, options)
308
+ render_result, errors = Utils.render_liquid_string(
309
+ template_string: template(options["section_depth"] || 2,
310
+ options["render_style"],
311
+ options["include_root"]),
312
+ context_items: create_context_object(lutaml_doc,
313
+ add_context,
314
+ options),
315
+ context_name: "context",
316
+ document: document,
317
+ include_path: template_path(document, options["template_path"]),
318
+ )
319
+ Utils.notify_render_errors(document, errors)
320
+ render_result.split("\n")
321
+ end
322
+
323
+ def template_path(document, template_path)
324
+ return LIQUID_INCLUDE_PATH if template_path.nil?
325
+
326
+ Utils.relative_file_path(document, template_path)
327
+ end
328
+
329
+ def template(section_depth, render_style, include_root)
330
+ include_name = RENDER_STYLES_INCLUDES.fetch(render_style,
331
+ DEFAULT_RENDER_INCLUDE)
332
+ result = ""
333
+ if include_root
334
+ result += <<~LIQUID
335
+ {% include "#{include_name}", package_skip_sections: context.package_skip_sections, package_entities: context.package_entities, context: context.root_packages, additional_context: context.additional_context, render_nested_packages: false %}
336
+ LIQUID
337
+ end
338
+ result + <<~LIQUID
339
+ {% include "#{include_name}", depth: #{section_depth}, package_skip_sections: context.package_skip_sections, package_entities: context.package_entities, context: context, additional_context: context.additional_context, render_nested_packages: context.render_nested_packages %}
340
+ LIQUID
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end