metanorma-plugin-lutaml 0.7.10 → 0.7.12

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: '0689b02dd3b4eb11afb1e06d187a5426ef392742efe7b01240d031a3178c136c'
4
+ data.tar.gz: 61c1b2d7010f98036cb33bd7b6935a1ff0368ff2a04a0ecc9849a1a853e60c96
5
5
  SHA512:
6
- metadata.gz: ab318c1c9a6bf060093600533ff59c6b293c70a90c3096ec5ab88100546e2ae2a18d0e52f8571bdf72d1da14df0fd6fdbfbc7a8cf3c9716ba34a2f154a4e5f64
7
- data.tar.gz: 0e3acb93421b591442d0293c08c5fb9b2e8e8c1f4925eb5f9f0fbbb77997b5cbc80e8e30cce5dd4accc4b1fe40d91c2ebce09e7f17db901ad8afbb82b8d0cb37
6
+ metadata.gz: 2a5852495aac28cfa9c034949fee1a4a5b28eefb1ebe10fdd576cfd4e1fa8a4e1a324b9b4cedc85c6db9a1103cedb2f98c3e649156a1e400a6ce0a441bbefe66
7
+ data.tar.gz: ca4ec34bebfcc583c22fdeb95504a73dd81fd426768e6b0a0ed22c7f9072344de39e520a94d2c7938be02623cb8ca108b2eafcf1ad77cfdf7195e50a7a4691cd
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,24 +1,77 @@
1
+ {% assign root = klass.generalization %}
2
+
3
+ {%- capture upper_klass_name -%}
4
+ {{ root.general.upper_klass }}:{{ root.general.name }}
5
+ {%- endcapture -%}
6
+ {%- if upper_klass_name == ":" -%}
7
+ {%- assign upper_klass_name = "-" -%}
8
+ {%- endif -%}
9
+ {%- capture stereotype -%}<< {{ root.stereotype }} >>{%- endcapture -%}
10
+ {%- if stereotype == "<< >>" -%}
11
+ {%- assign stereotype = " " -%}
12
+ {%- endif -%}
13
+
1
14
  [cols="1a,1a,2a"]
2
15
  |===
3
16
  | Definition of Type
4
17
  2+| {{ root.definition }}
5
18
 
6
- h| Upper Type 2+| {{ root.general.upper_klass }}:{{ root.general.name }}
7
- h| Stereotype 2+| << {{ root.stereotype }} >>
19
+ h| Upper Type 2+| {{ upper_klass_name }}
20
+ h| Stereotype 2+| {{ stereotype }}
8
21
 
9
22
  3+h| Inherited Properties
10
23
  h| Property Name h| Property Type and Multiplicity h| Definition
11
- {{ content.inherited_props }}
24
+ {% for attr in root.inherited_props %}
25
+ {%- if attr.has_association? == false -%}
26
+ {%- capture name_col -%}
27
+ {{ attr.name_ns }}:{{ attr.name }}
28
+ ({{ attr.gen_name }})
29
+ {%- endcapture -%}
30
+ | {{ name_col | newline_to_br }}
31
+ | {{ attr.type }} [{{ attr.cardinality.min }}..{{ attr.cardinality.max }}]
32
+ | {{ attr.definition }}
33
+ {%- endif -%}
34
+ {% endfor %}
12
35
 
13
36
  3+h| Self-defined Properties
14
37
  h| Property Name h| Property Type and Multiplicity h| Definition
15
- {{ content.owned_props }}
38
+ {% for attr in root.owned_props %}
39
+ {%- if attr.has_association? == false -%}
40
+ {%- capture name_col -%}
41
+ {{ attr.name_ns }}:{{ attr.name }}
42
+ ({{ attr.gen_name }})
43
+ {%- endcapture -%}
44
+ | {{ name_col | newline_to_br }}
45
+ | {{ attr.type }} [{{ attr.cardinality.min }}..{{ attr.cardinality.max }}]
46
+ | {{ attr.definition }}
47
+ {%- endif -%}
48
+ {% endfor %}
16
49
 
17
50
  3+h| Properties Inherited from Association
18
51
  h| Property Name h| Property Type and Multiplicity h| Definition
19
- {{ content.inherited_assoc_props }}
52
+ {% for attr in root.inherited_assoc_props %}
53
+ {%- if attr.has_association? == true -%}
54
+ {%- capture name_col -%}
55
+ {{ attr.name_ns }}:{{ attr.name }}
56
+ ({{ attr.gen_name }})
57
+ {%- endcapture -%}
58
+ | {{ name_col | newline_to_br }}
59
+ | {{ attr.type_ns }}:{{ attr.type }} [{{ attr.cardinality.min }}..{{ attr.cardinality.max }}]
60
+ | {{ attr.definition }}
61
+ {%- endif -%}
62
+ {% endfor %}
20
63
 
21
64
  3+h| Properties Defined in Association
22
65
  h| Property Name h| Property Type and Multiplicity h| Definition
23
- {{ content.assoc_props }}
66
+ {% for attr in root.assoc_props %}
67
+ {%- if attr.has_association? == true -%}
68
+ {%- capture name_col -%}
69
+ {{ attr.name_ns }}:{{ attr.name }}
70
+ ({{ attr.gen_name }})
71
+ {%- endcapture -%}
72
+ | {{ name_col | newline_to_br }}
73
+ | {{ attr.type_ns }}:{{ attr.type }} [{{ attr.cardinality.min }}..{{ attr.cardinality.max }}]
74
+ | {{ attr.definition }}
75
+ {%- endif -%}
76
+ {% endfor %}
24
77
  |===
@@ -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,357 @@
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
+ guidance = get_guidance_file(document, yaml_config["guidance"])
59
+ result_document = parse_result_document(full_path, guidance)
60
+ document.attributes["lutaml_xmi_cache"] ||= {}
61
+ document.attributes["lutaml_xmi_cache"][full_path] = result_document
62
+ result_document
63
+ end
64
+
65
+ def get_guidance_file(document, guidance_config)
66
+ guidance = nil
67
+
68
+ if guidance_config
69
+ guidance = Utils.relative_file_path(document,
70
+ guidance_config)
71
+ end
72
+
73
+ guidance
74
+ end
75
+
76
+ def parse_yaml_config_file(document, file_path)
77
+ return {} if file_path.nil?
78
+
79
+ relative_file_path = Utils.relative_file_path(document, file_path)
80
+ YAML.safe_load(File.read(relative_file_path, encoding: "UTF-8"))
81
+ end
82
+
83
+ def processed_lines(document, input_lines)
84
+ result = []
85
+ loop do
86
+ result.push(*process_text_blocks(document, input_lines))
87
+ end
88
+ result
89
+ end
90
+
91
+ def get_macro_regexp
92
+ self.class.const_get(:MACRO_REGEXP)
93
+ end
94
+
95
+ def process_text_blocks(document, input_lines) # rubocop:disable Metrics/MethodLength
96
+ line = input_lines.next
97
+ block_match = line.match(get_macro_regexp)
98
+
99
+ return [line] if block_match.nil?
100
+
101
+ yaml_config = parse_yaml_config_file(document, block_match[2])
102
+ lutaml_document = lutaml_document_from_file_or_cache(document,
103
+ block_match[1],
104
+ yaml_config)
105
+ fill_in_diagrams_attributes(document, lutaml_document)
106
+ model_representation(
107
+ lutaml_document, document,
108
+ collect_additional_context(document, input_lines, input_lines.next),
109
+ yaml_config
110
+ )
111
+ end
112
+
113
+ def get_original_document(wrapper)
114
+ doc = wrapper
115
+ return doc if doc.instance_of?(::Lutaml::XMI::RootDrop)
116
+
117
+ doc.original_document
118
+ end
119
+
120
+ def fill_in_entities_refs_attributes(document, # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
121
+ lutaml_document, _options)
122
+ lutaml_document = get_original_document(lutaml_document)
123
+ # render_style = options.fetch(RENDER_STYLE_ATTRIBUTE, "default")
124
+ all_children_packages = lutaml_document.packages
125
+ .map(&:children_packages).flatten
126
+ package_flat_packages = lambda do |pks|
127
+ pks.each_with_object({}) do |package, res|
128
+ res[package.name] = package.xmi_id
129
+ end
130
+ end
131
+ children_pks = package_flat_packages.call(all_children_packages)
132
+ ref_dictionary = package_flat_packages.call(lutaml_document.packages)
133
+ .merge(children_pks)
134
+ %w[class enum data_type].each do |type|
135
+ package_flat = lambda do |pks|
136
+ pks.each_with_object({}) do |package, res|
137
+ plural = type == "class" ? "classes" : "#{type}s"
138
+ package.send(plural).map do |entity|
139
+ res["#{type}:#{package.name}:#{entity.name}"] = entity.xmi_id
140
+ end
141
+ end
142
+ end
143
+ children_pks_diags = package_flat.call(all_children_packages)
144
+ ref_dictionary = ref_dictionary
145
+ .merge(package_flat.call(lutaml_document.packages)
146
+ .merge(children_pks_diags))
147
+ end
148
+ document.attributes["lutaml_entity_id"] = ref_dictionary
149
+ end
150
+
151
+ def fill_in_diagrams_attributes(document, lutaml_document) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
152
+ lutaml_document = get_original_document(lutaml_document)
153
+ package_flat_diagrams = lambda do |pks|
154
+ pks.each_with_object({}) do |package, res|
155
+ package.diagrams.map do |diag|
156
+ res["#{package.name}:#{diag.name}"] = diag.xmi_id
157
+ end
158
+ end
159
+ end
160
+ children_pks_diags = package_flat_diagrams.call(
161
+ lutaml_document.packages.map(&:children_packages).flatten,
162
+ )
163
+
164
+ document.attributes["lutaml_figure_id"] = package_flat_diagrams
165
+ .call(lutaml_document.packages)
166
+ .merge(children_pks_diags)
167
+ end
168
+
169
+ def collect_additional_context(document, input_lines, end_mark) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
170
+ additional_context = Hash.new { |hash, key| hash[key] = [] }
171
+ additional_context["all_macros"] = []
172
+ block_lines = []
173
+
174
+ while (block_line = input_lines.next) != end_mark
175
+ block_lines.push(block_line)
176
+ end
177
+
178
+ processed_lines = process(
179
+ document,
180
+ ::Asciidoctor::PreprocessorReader.new(document, block_lines),
181
+ ).read_lines
182
+
183
+ block_document = ::Asciidoctor::Document
184
+ .new(processed_lines, {}).parse
185
+ block_document.blocks.each do |block|
186
+ next unless SUPPORTED_NESTED_MACRO.include?(
187
+ block.attributes["role"],
188
+ )
189
+
190
+ attrs = block.attributes
191
+ name = attrs.delete("role")
192
+ package = attrs.delete("package")
193
+ macro_keyword = [name, package].compact.join(";")
194
+ block_text = if block.lines.length.positive?
195
+ block.lines.join("\n")
196
+ else
197
+ ""
198
+ end
199
+ additional_context[macro_keyword]
200
+ .push({ "text" => block_text }.merge(attrs))
201
+ additional_context["all_macros"]
202
+ .push({ "text" => block_text,
203
+ "type" => name, "package" => package }.merge(attrs))
204
+ end
205
+ additional_context
206
+ end
207
+
208
+ def package_level(lutaml_document, level)
209
+ return lutaml_document if level <= 0
210
+
211
+ package_level(lutaml_document["packages"].first, level - 1)
212
+ end
213
+
214
+ def create_context_object(lutaml_document, additional_context, options) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
215
+ root_package = package_level(lutaml_document.to_liquid,
216
+ options["package_root_level"] || 1)
217
+ if options.empty? || options["packages"].nil?
218
+ return {
219
+ "render_nested_packages" => true,
220
+ "packages" => root_package["packages"],
221
+ "root_packages" => [root_package],
222
+ "additional_context" => additional_context
223
+ .merge("external_classes" => options["external_classes"]),
224
+ "name" => root_package["name"],
225
+ }
226
+ end
227
+
228
+ all_packages = [root_package, *root_package["children_packages"]]
229
+ {
230
+ "packages" => sort_and_filter_out_packages(all_packages, options),
231
+ "package_entities" => package_entities(options),
232
+ "package_skip_sections" => package_skip_sections(options),
233
+ "additional_context" => additional_context
234
+ .merge("external_classes" => options["external_classes"]),
235
+ "root_packages" => [root_package],
236
+ "render_nested_packages" => options["render_nested_packages"] ||
237
+ false,
238
+ "name" => root_package["name"],
239
+ }
240
+ end
241
+
242
+ def package_entities(options) # rubocop:disable Metrics/AbcSize
243
+ return {} unless options["packages"]
244
+
245
+ options["packages"].find_all do |entity|
246
+ entity.is_a?(Hash) && entity.values.first["render_entities"]
247
+ end.map do |entity|
248
+ [entity.keys.first,
249
+ entity.values.first["render_entities"].map { |n| [n, true] }.to_h]
250
+ end.to_h
251
+ end
252
+
253
+ def package_skip_sections(options) # rubocop:disable Metrics/AbcSize
254
+ return {} unless options["packages"]
255
+
256
+ options["packages"].find_all do |entity|
257
+ entity.is_a?(Hash) && entity.values.first["skip_tables"]
258
+ end.map do |entity|
259
+ [entity.keys.first,
260
+ entity.values.first["skip_tables"].map { |n| [n, true] }.to_h]
261
+ end.to_h
262
+ end
263
+
264
+ def sort_and_filter_out_packages(all_packages, options) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
265
+ return all_packages if options["packages"].nil?
266
+
267
+ result = []
268
+ # Step one - filter out all skipped packages
269
+ all_packages =
270
+ filter_out_all_skipped_packages(options, all_packages)
271
+
272
+ # Step two - select supplied packages by pattern
273
+ select_supplied_packages_by_pattern(options, all_packages,
274
+ result)
275
+ end
276
+
277
+ def filter_out_all_skipped_packages(options, all_packages)
278
+ options["packages"].find_all do |entity|
279
+ entity.is_a?(Hash) && entity["skip"]
280
+ end.each do |entity|
281
+ entity_regexp = config_entity_regexp(entity["skip"])
282
+ all_packages.delete_if do |package|
283
+ package["name"] =~ entity_regexp
284
+ end
285
+ end
286
+
287
+ all_packages
288
+ end
289
+
290
+ def select_supplied_packages_by_pattern(options, all_packages, result) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
291
+ options["packages"].find_all do |entity|
292
+ entity.is_a?(String) || (entity.is_a?(Hash) && !entity["skip"])
293
+ end.each do |entity_obj|
294
+ entity = if entity_obj.is_a?(String)
295
+ entity_obj
296
+ else
297
+ entity_obj.keys.first
298
+ end
299
+ entity_regexp = config_entity_regexp(entity)
300
+ all_packages.each do |package|
301
+ if package["name"]&.match?(entity_regexp)
302
+ result.push(package)
303
+ all_packages.delete_if do |nest_package|
304
+ nest_package["name"] == package["name"]
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ result
311
+ end
312
+
313
+ def config_entity_regexp(entity)
314
+ additional_sym = ".*" if /\*$/.match?(entity)
315
+ %r{^#{Regexp.escape(entity.delete('*'))}#{additional_sym}$}
316
+ end
317
+
318
+ def model_representation(lutaml_doc, document, add_context, options) # rubocop:disable Metrics/MethodLength
319
+ fill_in_entities_refs_attributes(document, lutaml_doc, options)
320
+ render_result, errors = Utils.render_liquid_string(
321
+ template_string: template(options["section_depth"] || 2,
322
+ options["render_style"],
323
+ options["include_root"]),
324
+ context_items: create_context_object(lutaml_doc,
325
+ add_context,
326
+ options),
327
+ context_name: "context",
328
+ document: document,
329
+ include_path: template_path(document, options["template_path"]),
330
+ )
331
+ Utils.notify_render_errors(document, errors)
332
+ render_result.split("\n")
333
+ end
334
+
335
+ def template_path(document, template_path)
336
+ return LIQUID_INCLUDE_PATH if template_path.nil?
337
+
338
+ Utils.relative_file_path(document, template_path)
339
+ end
340
+
341
+ def template(section_depth, render_style, include_root)
342
+ include_name = RENDER_STYLES_INCLUDES.fetch(render_style,
343
+ DEFAULT_RENDER_INCLUDE)
344
+ result = ""
345
+ if include_root
346
+ result += <<~LIQUID
347
+ {% 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 %}
348
+ LIQUID
349
+ end
350
+ result + <<~LIQUID
351
+ {% 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 %}
352
+ LIQUID
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end