metanorma-plugin-lutaml 0.7.10 → 0.7.12

Sign up to get free protection for your applications and to get access to all the features.
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