metanorma-plugin-lutaml 0.7.9 → 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: 29510e3dd02119b47d1d179c94d0a954b596dc0255410091d8c0db366e6a4dd3
4
- data.tar.gz: b2502ffd6bea597bc56c8882712c58d13e72799eca5387c76329a9ef813d62eb
3
+ metadata.gz: c0601a4c6b35e5d1fd1c58d24240e3a5dea1b87bae62cbf9ce17ac904c21d5bf
4
+ data.tar.gz: 60d05d99810e378da5df64b8ec603fbf522a3e66c10ef9ef776c0c616dd2a9e0
5
5
  SHA512:
6
- metadata.gz: b06a2f0d8be8e9073c3f631b260d8e88525353afb8e4e6cdf11bd6db9d78994543eb6a9acda9a2602212943ff972a700efa7c7d3dc870951cd3f2c6e5a219ff2
7
- data.tar.gz: c80c2d456bec408dccb3c621397bc9313e2cc6a53cf7c5671d504737087ebb597b446d2fc043e3deaaf809a0941f0f3b732837bf4b5aee404685d70cd6ab0d27
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
@@ -315,7 +315,7 @@ lutaml_gml_dictionary::/path/to/dictionary.xml[template="/path/to/template.liqui
315
315
 
316
316
  The command accepts the options listed below:
317
317
 
318
- * `"/path/to/dictionary.xml` specifies the path of xml file of the
318
+ * `/path/to/dictionary.xml` specifies the path of xml file of the
319
319
  GML Dictionary.
320
320
 
321
321
  * `template="/path/to/template.liquid"` specifies the liquid template.
@@ -364,6 +364,36 @@ h| Help | Description
364
364
  --
365
365
  -----
366
366
 
367
+ === Rendering a LutaML table of a class: `lutaml_klass_table`
368
+
369
+ This command allows to render a LutaML table of a class by using Liquid Drop.
370
+
371
+ The table will show:
372
+
373
+ * Class Name
374
+ * Class Definition
375
+ * Inherited Properties
376
+ * Self-defined Properties
377
+ * Properties Inherited from Association
378
+ * Properties Defined in Association
379
+
380
+ [source,adoc]
381
+ -----
382
+ lutaml_klass_table::/path/to/example.xmi[name="NameOfClass",template="/path/to/templates/_my_klass_table.liquid"]
383
+ -----
384
+
385
+ The command accepts the options listed below:
386
+
387
+ * `/path/to/example.xmi` specifies the path of xmi file.
388
+
389
+ * `name="NameOfClass"` specifies the name of the Class.
390
+
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
+
367
397
  === Generating UML class and attributes: `lutaml_uml_class`
368
398
 
369
399
  This command allows rendering a definition clause for a UML class.
@@ -796,6 +826,33 @@ and iterate through packages according to the order supplied in the file. All
796
826
  packages that matches `skip` in the YAML config file will be skipped during
797
827
  render.
798
828
 
829
+ === Usage of `lutaml_ea_xmi` macro
830
+
831
+ This command is a replacement for `lutaml_uml_datamodel_description` to perform
832
+ the same functionalities of `lutaml_uml_datamodel_description`, which is to
833
+ render data model packages and its dependent objects for supplied XMI file, by
834
+ using Liquid Drop. The performance of `lutaml_ea_xmi` can be improved by
835
+ 10~20 times. (Tested with a 10.6MB XMI file with 120000+ lines)
836
+
837
+ To use this macro, you only need to replace `lutaml_uml_datamodel_description`
838
+ by `lutaml_ea_xmi`.
839
+
840
+ Replace:
841
+
842
+ [source,adoc]
843
+ -----
844
+ [lutaml_uml_datamodel_description, path/to/example.xmi]
845
+ ...
846
+ -----
847
+
848
+ By:
849
+
850
+ [source,adoc]
851
+ -----
852
+ [lutaml_ea_xmi, path/to/example.xmi]
853
+ ...
854
+ -----
855
+
799
856
  == Documentation
800
857
 
801
858
  Please refer to https://www.metanorma.org.
@@ -1,23 +1,65 @@
1
+ {% assign root = klass.generalization %}
1
2
  [cols="1a,1a,2a"]
2
3
  |===
3
- | 型の定義
4
+ | Definition of Type
4
5
  2+| {{ root.definition }}
5
6
 
6
- h| 上位の型 2+| {{ root.general.upper_klass }}:{{ root.general.name }}
7
- h| ステレオタイプ 2+| << {{ root.stereotype }} >>
7
+ h| Upper Type 2+| {{ root.general.upper_klass }}:{{ root.general.name }}
8
+ h| Stereotype 2+| << {{ root.stereotype }} >>
8
9
 
9
- 3+h| 継承する属性
10
- h| 属性名 h| 属性の型及び多重度 h| 定義
11
- {{ content.inherited_props }}
10
+ 3+h| Inherited Properties
11
+ h| Property Name h| Property Type and Multiplicity h| Definition
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
- 3+h| 自身に定義された属性
14
- {{ content.owned_props }}
24
+ 3+h| Self-defined Properties
25
+ h| Property Name h| Property Type and Multiplicity h| Definition
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 %}
15
37
 
16
- 3+h| 継承する関連役割
17
- h| 関連役割名 h| 関連役割の型及び多重度 h| 定義
18
- {{ content.inherited_assoc_props }}
38
+ 3+h| Properties Inherited from Association
39
+ h| Property Name h| Property Type and Multiplicity h| Definition
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 %}
19
51
 
20
- 3+h| 自身に定義された関連役割
21
- h| 関連役割名 h| 関連役割の型及び多重度 h| 定義
22
- {{ content.assoc_props }}
52
+ 3+h| Properties Defined in Association
53
+ h| Property Name h| Property Type and Multiplicity h| Definition
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 %}
23
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