metanorma-plugin-lutaml 0.7.10 → 0.7.11

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: 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