lutaml-lml 0.1.0

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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/lib/lutaml/lml/association_label_resolver.rb +52 -0
  3. data/lib/lutaml/lml/cli.rb +262 -0
  4. data/lib/lutaml/lml/data_processor/attribute_processing.rb +81 -0
  5. data/lib/lutaml/lml/data_processor/collection_processing.rb +37 -0
  6. data/lib/lutaml/lml/data_processor/instance_processing.rb +63 -0
  7. data/lib/lutaml/lml/data_processor/value_processing.rb +98 -0
  8. data/lib/lutaml/lml/data_processor/view_processing.rb +25 -0
  9. data/lib/lutaml/lml/data_processor.rb +49 -0
  10. data/lib/lutaml/lml/document_builder.rb +139 -0
  11. data/lib/lutaml/lml/executor/adapter_helpers.rb +45 -0
  12. data/lib/lutaml/lml/executor/condition_evaluator.rb +169 -0
  13. data/lib/lutaml/lml/executor/csv_adapter.rb +88 -0
  14. data/lib/lutaml/lml/executor/format_adapter.rb +54 -0
  15. data/lib/lutaml/lml/executor/xml_adapter.rb +102 -0
  16. data/lib/lutaml/lml/executor.rb +89 -0
  17. data/lib/lutaml/lml/format/adapter/document.rb +11 -0
  18. data/lib/lutaml/lml/format/adapter/mapping.rb +19 -0
  19. data/lib/lutaml/lml/format/adapter/standard_adapter.rb +127 -0
  20. data/lib/lutaml/lml/format/adapter/transform.rb +11 -0
  21. data/lib/lutaml/lml/format.rb +29 -0
  22. data/lib/lutaml/lml/formatter/base.rb +79 -0
  23. data/lib/lutaml/lml/formatter/graphviz/document_formatter.rb +89 -0
  24. data/lib/lutaml/lml/formatter/graphviz/html_builder.rb +72 -0
  25. data/lib/lutaml/lml/formatter/graphviz/node_formatter.rb +74 -0
  26. data/lib/lutaml/lml/formatter/graphviz/relationship_formatter.rb +130 -0
  27. data/lib/lutaml/lml/formatter/graphviz.rb +90 -0
  28. data/lib/lutaml/lml/formatter.rb +8 -0
  29. data/lib/lutaml/lml/grammar/concerns/associations.rb +76 -0
  30. data/lib/lutaml/lml/grammar/concerns/attributes.rb +126 -0
  31. data/lib/lutaml/lml/grammar/concerns/data_structures.rb +84 -0
  32. data/lib/lutaml/lml/grammar/concerns/definitions.rb +222 -0
  33. data/lib/lutaml/lml/grammar/concerns/instance_rules.rb +59 -0
  34. data/lib/lutaml/lml/grammar/concerns/primitives.rb +89 -0
  35. data/lib/lutaml/lml/grammar/concerns/view_rules.rb +34 -0
  36. data/lib/lutaml/lml/grammar/concerns.rb +17 -0
  37. data/lib/lutaml/lml/grammar/core.rb +71 -0
  38. data/lib/lutaml/lml/grammar/full.rb +12 -0
  39. data/lib/lutaml/lml/grammar/instances.rb +38 -0
  40. data/lib/lutaml/lml/grammar.rb +12 -0
  41. data/lib/lutaml/lml/has_attributes.rb +14 -0
  42. data/lib/lutaml/lml/import_resolver.rb +89 -0
  43. data/lib/lutaml/lml/layout/engine.rb +17 -0
  44. data/lib/lutaml/lml/layout/graph_viz_engine.rb +19 -0
  45. data/lib/lutaml/lml/layout.rb +8 -0
  46. data/lib/lutaml/lml/model_compiler.rb +325 -0
  47. data/lib/lutaml/lml/models/action.rb +10 -0
  48. data/lib/lutaml/lml/models/association.rb +26 -0
  49. data/lib/lutaml/lml/models/cardinality.rb +10 -0
  50. data/lib/lutaml/lml/models/collection.rb +11 -0
  51. data/lib/lutaml/lml/models/constraint.rb +20 -0
  52. data/lib/lutaml/lml/models/data_type.rb +31 -0
  53. data/lib/lutaml/lml/models/diagram.rb +17 -0
  54. data/lib/lutaml/lml/models/document.rb +45 -0
  55. data/lib/lutaml/lml/models/enum.rb +28 -0
  56. data/lib/lutaml/lml/models/fidelity.rb +10 -0
  57. data/lib/lutaml/lml/models/group.rb +11 -0
  58. data/lib/lutaml/lml/models/instance.rb +13 -0
  59. data/lib/lutaml/lml/models/instance_collection.rb +12 -0
  60. data/lib/lutaml/lml/models/instances_export.rb +10 -0
  61. data/lib/lutaml/lml/models/instances_import.rb +11 -0
  62. data/lib/lutaml/lml/models/operation.rb +23 -0
  63. data/lib/lutaml/lml/models/operation_parameter.rb +11 -0
  64. data/lib/lutaml/lml/models/package.rb +22 -0
  65. data/lib/lutaml/lml/models/primitive_type.rb +26 -0
  66. data/lib/lutaml/lml/models/top_element_attribute.rb +31 -0
  67. data/lib/lutaml/lml/models/uml_class.rb +84 -0
  68. data/lib/lutaml/lml/models/value.rb +12 -0
  69. data/lib/lutaml/lml/models/view_filter.rb +11 -0
  70. data/lib/lutaml/lml/models/view_import.rb +11 -0
  71. data/lib/lutaml/lml/parser.rb +22 -0
  72. data/lib/lutaml/lml/pipeline.rb +64 -0
  73. data/lib/lutaml/lml/preprocessor.rb +56 -0
  74. data/lib/lutaml/lml/transform.rb +20 -0
  75. data/lib/lutaml/lml/version.rb +7 -0
  76. data/lib/lutaml/lml/view_resolver.rb +48 -0
  77. data/lib/lutaml/lml/yaml_parser.rb +17 -0
  78. data/lib/lutaml/lml.rb +67 -0
  79. metadata +178 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3953cc3c0f81db402cab373b0da574ebd8832e2aa42c98de6e88e3fc157e4503
4
+ data.tar.gz: d3816ea57a18d40ff1577757367f41fc604e54ad945545223820787dff81a86b
5
+ SHA512:
6
+ metadata.gz: cd7dc48c1d8a1d89718e755e93ffc7a0ac0d577aab1d6c0986ddd7d45d6df3f88b1716f1514e445df0e3ab6c10b306904b3219247126bf5e60b8568acb6aafd3
7
+ data.tar.gz: 9c79221cf405d9512c094da610abdb7ab4f8675ce64ca7180c0039e337a281872483ef31db2f9344a0e7c853d1d51085e3108b08b1e6fba83bd200134da5aa2d
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class AssociationLabelResolver
6
+ def enrich(document)
7
+ class_index = build_class_index(document)
8
+ document.associations.each do |assoc|
9
+ enrich_owner_end(assoc, class_index)
10
+ enrich_member_end(assoc, class_index)
11
+ end
12
+ document
13
+ end
14
+
15
+ private
16
+
17
+ def build_class_index(document)
18
+ document.all_classes.each_with_object({}) { |klass, index| index[klass.name] = klass }
19
+ end
20
+
21
+ def enrich_owner_end(assoc, class_index)
22
+ return if assoc.owner_end_attribute_name
23
+
24
+ owner_class = class_index[assoc.owner_end.to_s]
25
+ return unless owner_class
26
+
27
+ attr = find_attribute_by_type(owner_class, assoc.member_end.to_s)
28
+ return unless attr
29
+
30
+ assoc.owner_end_attribute_name = attr.name
31
+ assoc.owner_end_cardinality = attr.cardinality if attr.cardinality
32
+ end
33
+
34
+ def enrich_member_end(assoc, class_index)
35
+ return if assoc.member_end_attribute_name
36
+
37
+ member_class = class_index[assoc.member_end.to_s]
38
+ return unless member_class
39
+
40
+ attr = find_attribute_by_type(member_class, assoc.owner_end.to_s)
41
+ return unless attr
42
+
43
+ assoc.member_end_attribute_name = attr.name
44
+ assoc.member_end_cardinality = attr.cardinality if attr.cardinality
45
+ end
46
+
47
+ def find_attribute_by_type(klass, target_type_name)
48
+ klass.attributes&.find { |a| a.type == target_type_name }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'pathname'
5
+ require 'lutaml/lml'
6
+
7
+ module Lutaml
8
+ module Cli
9
+ # Thor CLI commands for LutaML DSL generation and validation.
10
+ class LmlCommands < Thor
11
+ include ::Lutaml::Lml::HasAttributes
12
+
13
+ SUPPORTED_FORMATS = %w[yaml lutaml exp].freeze
14
+ DEFAULT_INPUT_FORMAT = 'lutaml'
15
+ DEFAULT_OUTPUT_FORMAT = 'svg'
16
+
17
+ EXTENSION_FORMAT_MAP = {
18
+ '.svg' => 'svg', '.svgz' => 'svgz',
19
+ '.png' => 'png', '.gif' => 'gif', '.jpg' => 'jpg', '.jpeg' => 'jpeg',
20
+ '.pdf' => 'pdf', '.ps' => 'ps', '.eps' => 'eps',
21
+ '.dot' => 'dot', '.xdot' => 'xdot',
22
+ '.fig' => 'fig', '.json' => 'json',
23
+ '.imap' => 'imap', '.cmapx' => 'cmapx'
24
+ }.freeze
25
+
26
+ PARSE_HANDLERS = {
27
+ 'lutaml' => ->(path) { Lutaml::Lml::Parser.parse(File.new(path)) },
28
+ 'yaml' => ->(path) { Lutaml::Lml::YamlParser.parse(path.to_s) },
29
+ 'yml' => ->(path) { Lutaml::Lml::YamlParser.parse(path.to_s) },
30
+ 'exp' => lambda { |path|
31
+ require 'lutaml/express'
32
+ Lutaml::Express::Parsers::Exp.parse(File.new(path))
33
+ }
34
+ }.freeze
35
+
36
+ FORMATTER_ATTR_TARGETS = %i[graph edge node].freeze
37
+
38
+ def initialize(*args)
39
+ super
40
+ @formatter = ::Lutaml::Formatter::Graphviz.new if defined?(::Lutaml::Formatter::Graphviz)
41
+ @out_object = $stdout
42
+ end
43
+
44
+ desc 'generate [PATHS]', 'Generate diagram output from LutaML DSL files'
45
+ long_desc <<-DESC
46
+ Generate diagram output from one or more LutaML DSL files.
47
+
48
+ Supports multiple input formats (lutaml, yaml, exp) and can output
49
+ to various formats (svg, png, dot, pdf, etc.).
50
+
51
+ Examples:
52
+ lutaml lml generate model.lutaml -o diagram.svg
53
+
54
+ lutaml lml generate model.lutaml -o diagram.dot -t dot
55
+
56
+ lutaml lml generate model.lutaml project.lutaml -o output/
57
+ DESC
58
+ method_option :output, type: :string, aliases: '-o',
59
+ desc: 'Output path (file or directory)'
60
+ method_option :formatter, type: :string, aliases: '-f',
61
+ desc: 'Output formatter (default: graphviz)'
62
+ method_option :type, type: :string, aliases: '-t',
63
+ desc: 'Output format type (svg, png, dot, etc.)'
64
+ method_option :input_format, type: :string, aliases: '-i',
65
+ desc: 'Input format (lutaml, yaml, exp)'
66
+ method_option :graph, type: :string, aliases: '-g',
67
+ desc: 'Graph attributes (key=value,key2=value2)'
68
+ method_option :edge, type: :string, aliases: '-e',
69
+ desc: 'Edge attributes (key=value,key2=value2)'
70
+ method_option :node, type: :string, aliases: '-n',
71
+ desc: 'Node attributes (key=value,key2=value2)'
72
+ method_option :all, type: :string, aliases: '-a',
73
+ desc: 'Set attributes for graph, edge, and node'
74
+ def generate(*paths)
75
+ assert_input_paths(paths)
76
+
77
+ setup_options
78
+ @paths = paths.map { |path| Pathname.new(path) }
79
+
80
+ assert_output_path
81
+
82
+ @paths.each { |input_path| process_single_file(input_path) }
83
+ end
84
+
85
+ desc 'validate [PATHS]', 'Validate LutaML DSL syntax'
86
+ long_desc <<-DESC
87
+ Validate the syntax of one or more LutaML DSL files.
88
+
89
+ Checks for syntax errors and structural issues in the DSL files
90
+ without generating output.
91
+
92
+ Examples:
93
+ lutaml lml validate model.lutaml
94
+
95
+ lutaml lml validate model.lutaml project.lutaml
96
+ DESC
97
+ def validate(*paths)
98
+ assert_input_paths(paths)
99
+ errors = paths.map { |p| validate_single_file(p) }.compact
100
+ report_validation(errors)
101
+ end
102
+
103
+ desc 'compile PATH', 'Compile LML model definitions into Ruby classes'
104
+ long_desc <<-DESC
105
+ Parse an LML file containing model definitions and compile them
106
+ into Ruby classes that can be used to instantiate and validate data.
107
+
108
+ The compiled classes are anonymous Serializable subclasses registered
109
+ in the ModelCompiler. With --namespace, classes are also registered
110
+ as constants in the given module.
111
+
112
+ Examples:
113
+ lutaml lml compile models.lml
114
+
115
+ lutaml lml compile models.lml --namespace MyModels
116
+ DESC
117
+ method_option :namespace, type: :string, aliases: '-n',
118
+ desc: 'Register compiled classes in a module'
119
+ def compile(path)
120
+ raise Thor::Error, "File does not exist: #{path}" unless File.exist?(path)
121
+
122
+ ns = options[:namespace] ? resolve_namespace(options[:namespace]) : nil
123
+ result = Lutaml::Lml::ModelCompiler.new(namespace: ns).compile(File.new(path))
124
+
125
+ result.each_key do |name|
126
+ say " compiled: #{name}", :green
127
+ end
128
+ say "\n#{result.size} class(es) compiled", :green
129
+ end
130
+
131
+ no_commands do
132
+ def resolve_namespace(name)
133
+ name.split('::').reduce(Object) do |mod, const_name|
134
+ mod.const_get(const_name)
135
+ end
136
+ rescue NameError
137
+ raise Thor::Error, "Namespace '#{name}' not found"
138
+ end
139
+
140
+ def assert_input_paths(paths)
141
+ return unless paths.empty?
142
+
143
+ raise Thor::Error,
144
+ 'No input files provided. Please specify at least ' \
145
+ 'one .lutaml file.'
146
+ end
147
+
148
+ def assert_output_path
149
+ return unless @output_path&.file? && @paths.length > 1
150
+
151
+ raise Thor::Error,
152
+ 'Output path must be a directory if multiple input files ' \
153
+ 'are given'
154
+ end
155
+
156
+ def process_single_file(input_path)
157
+ raise Thor::Error, "File does not exist: #{input_path}" unless input_path.exist?
158
+
159
+ document = parse_document(input_path)
160
+ result = @formatter.format(document)
161
+ write_output(input_path, result)
162
+ end
163
+
164
+ def resolve_output_path(input_path)
165
+ return @out_object unless @output_path
166
+
167
+ path = @output_path
168
+ path = path.join("#{input_path.basename('.*')}.#{@formatter.type}") if path.directory?
169
+ path
170
+ end
171
+
172
+ def write_output(input_path, result)
173
+ target = resolve_output_path(input_path)
174
+ if target.is_a?(Pathname)
175
+ target.open('w+') { |f| f.write(result) }
176
+ say "Generated: #{target}", :green
177
+ else
178
+ target.puts(result)
179
+ end
180
+ end
181
+
182
+ def validate_single_file(path_string)
183
+ input_path = Pathname.new(path_string)
184
+ return report_file_error(input_path, 'File does not exist') unless input_path.exist?
185
+
186
+ parse_document(input_path)
187
+ say "✓ #{input_path}", :green
188
+ nil
189
+ rescue StandardError => e
190
+ report_file_error(input_path, e.message)
191
+ end
192
+
193
+ def report_file_error(input_path, message)
194
+ say "✗ #{input_path}: #{message}", :red
195
+ "#{input_path}: #{message}"
196
+ end
197
+
198
+ def report_validation(errors)
199
+ if errors.any?
200
+ say "\nValidation failed with #{errors.size} error(s)", :red
201
+ exit 1
202
+ else
203
+ say "\nAll files valid!", :green
204
+ end
205
+ end
206
+
207
+ def parse_document(input_path)
208
+ handler = PARSE_HANDLERS[@input_format]
209
+ return handler.call(input_path) if handler
210
+
211
+ raise Thor::Error, "Unsupported input format: #{@input_format}"
212
+ end
213
+
214
+ def setup_options
215
+ @formatter = options[:formatter] if options[:formatter]
216
+ @output_path = Pathname.new(options[:output]) if options[:output]
217
+ @input_format = options[:input_format] || DEFAULT_INPUT_FORMAT
218
+ @type = resolve_output_type
219
+
220
+ setup_formatter_options
221
+ end
222
+
223
+ def resolve_output_type
224
+ return options[:type] if options[:type]
225
+ return DEFAULT_OUTPUT_FORMAT unless @output_path
226
+
227
+ ext = @output_path.extname.downcase
228
+ EXTENSION_FORMAT_MAP.fetch(ext, DEFAULT_OUTPUT_FORMAT)
229
+ end
230
+
231
+ def parse_kv_string(str)
232
+ str.split(',').to_h do |pair|
233
+ key, value = pair.split('=', 2)
234
+ [key.strip, value&.strip]
235
+ end
236
+ end
237
+
238
+ def apply_kv_to_formatter(option_key, str)
239
+ targets = option_key == :all ? FORMATTER_ATTR_TARGETS : [option_key]
240
+ parse_kv_string(str).each do |key, value|
241
+ targets.each { |t| @formatter.public_send(t)[key] = value }
242
+ end
243
+ end
244
+
245
+ def setup_formatter_options
246
+ return unless @formatter
247
+
248
+ @formatter.type = @type if @type
249
+
250
+ FORMATTER_ATTR_TARGETS.each do |target|
251
+ apply_kv_to_formatter(target, options[target]) if options[target]
252
+ end
253
+ apply_kv_to_formatter(:all, options[:all]) if options[:all]
254
+ end
255
+ end
256
+
257
+ def self.exit_on_failure?
258
+ true
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ module AttributeProcessing
7
+ EXCLUDED_PASS_THROUGH_KEYS = %i[key value comments add attributes properties].freeze
8
+
9
+ def process_attributes(obj)
10
+ case obj
11
+ when Array then process_attributes_array(obj)
12
+ when Hash then process_attributes_hash(obj)
13
+ end
14
+ end
15
+
16
+ def process_attributes_array(obj)
17
+ return obj.map { |item| process_attributes(item) } unless single_key_hashes?(obj)
18
+
19
+ obj.each_with_object({}) do |item, hash|
20
+ hash[:properties] ||= []
21
+ if item.key?(:properties)
22
+ hash[:properties] << process_attributes(item[:properties])
23
+ else
24
+ hash.merge!(process_attributes(item))
25
+ end
26
+ end
27
+ end
28
+
29
+ def process_attributes_hash(obj)
30
+ result = extract_name_and_value(obj)
31
+ convert_instance_type(result)
32
+ result[:extended] = !obj[:add].nil? if obj.key?(:add)
33
+ apply_nested_attributes(result, obj)
34
+ apply_remaining_keys(result, obj)
35
+ result
36
+ end
37
+
38
+ private
39
+
40
+ def single_key_hashes?(arr)
41
+ arr.all? { |e| e.is_a?(Hash) && e.keys.size == 1 }
42
+ end
43
+
44
+ def extract_name_and_value(obj)
45
+ result = {}
46
+ result[:name] = obj[:key] if obj.key?(:key)
47
+
48
+ if obj.key?(:comments)
49
+ result[:name] = 'Comment'
50
+ result[:value] = process_value(obj[:comments]).last
51
+ elsif obj.key?(:value)
52
+ result[:type], result[:value] = process_value(obj[:value])
53
+ end
54
+
55
+ result
56
+ end
57
+
58
+ def convert_instance_type(result)
59
+ return unless result[:type]&.start_with?('Instance')
60
+
61
+ value = result.delete(:value)
62
+ result[:instances] = value.is_a?(Array) ? value : [value]
63
+ result.delete(:type)
64
+ end
65
+
66
+ def apply_nested_attributes(result, obj)
67
+ result[:attributes] = process_attributes(obj[:attributes]) if obj.key?(:attributes)
68
+ result[:properties] = process_attributes(obj[:properties]) if obj.key?(:properties)
69
+ end
70
+
71
+ def apply_remaining_keys(result, obj)
72
+ obj.each do |key, value|
73
+ next if EXCLUDED_PASS_THROUGH_KEYS.include?(key)
74
+
75
+ result[key] = value unless result.key?(key)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ module CollectionProcessing
7
+ def process_collections(obj)
8
+ obj.each_with_object({}) do |(key, value), result|
9
+ result[key] = process_value(value).last
10
+ end
11
+ end
12
+
13
+ def process_imports(obj)
14
+ process_io_definitions(obj)
15
+ end
16
+
17
+ def process_exports(obj)
18
+ process_io_definitions(obj)
19
+ end
20
+
21
+ private
22
+
23
+ def process_io_definitions(obj)
24
+ obj.map do |item|
25
+ item.each_with_object({}) do |(key, value), result|
26
+ result[key] = if key == :attributes
27
+ process_attributes(value)
28
+ else
29
+ process_value(value).last
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ module InstanceProcessing
7
+ INSTANCE_KEY_HANDLERS = {
8
+ instance: :process_instance,
9
+ collections: :process_collections,
10
+ imports: :process_imports,
11
+ exports: :process_exports
12
+ }.freeze
13
+
14
+ INSTANCE_FIELD_HANDLERS = {
15
+ instance_type: :handle_instance_type,
16
+ instance: :handle_instance_nested,
17
+ attributes: :handle_instance_attributes,
18
+ template: :handle_instance_template
19
+ }.freeze
20
+
21
+ def process_instances(obj)
22
+ return [] unless obj.is_a?(Array)
23
+
24
+ obj.each_with_object({}) do |instance, acc|
25
+ acc[:instances] ||= []
26
+ key = INSTANCE_KEY_HANDLERS.keys.find { |k| instance.key?(k) }
27
+ next unless key
28
+
29
+ result = public_send(INSTANCE_KEY_HANDLERS[key], instance[key])
30
+ key == :instance ? (acc[:instances] << result) : (acc[key] = result)
31
+ end
32
+ end
33
+
34
+ def process_instance(hash)
35
+ hash.each_with_object({}) do |(key, value), result|
36
+ handler = INSTANCE_FIELD_HANDLERS[key]
37
+ if handler
38
+ public_send(handler, value, result)
39
+ else
40
+ result[key] = process_value(value).last
41
+ end
42
+ end
43
+ end
44
+
45
+ def handle_instance_type(value, result)
46
+ result[:type] = process_value(value).last
47
+ end
48
+
49
+ def handle_instance_nested(value, result)
50
+ result[:instance] = process_instance(value)
51
+ end
52
+
53
+ def handle_instance_attributes(value, result)
54
+ result[:attributes] = process_attributes(value)
55
+ end
56
+
57
+ def handle_instance_template(value, result)
58
+ result[:template] = process_attributes(value[:attributes])
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ module ValueProcessing
7
+ VALUE_TYPE_KEYS = %i[instance list string boolean key_value_map number float condition require].freeze
8
+
9
+ VALUE_TYPE_HANDLERS = {
10
+ instance: :handle_instance_value,
11
+ list: :handle_list_value,
12
+ string: :handle_string_value,
13
+ boolean: :handle_boolean_value,
14
+ key_value_map: :handle_key_value_map,
15
+ number: :handle_number_value,
16
+ float: :handle_float_value,
17
+ condition: :handle_delegate_value,
18
+ require: :handle_delegate_value
19
+ }.freeze
20
+
21
+ def process_value(value)
22
+ return [] if value.nil?
23
+ return process_hash_value(value) if value.is_a?(Hash)
24
+ return process_array_value(value) if value.is_a?(Array)
25
+
26
+ [value.class.to_s, value]
27
+ end
28
+
29
+ def process_list_value(list)
30
+ process_typed_sequence(list)
31
+ end
32
+
33
+ def process_array_value(value)
34
+ process_typed_sequence(value)
35
+ end
36
+
37
+ def handle_instance_value(_key, value)
38
+ ['Instance', process_instance(value[:instance])]
39
+ end
40
+
41
+ def handle_list_value(_key, value)
42
+ process_typed_sequence(value[:list])
43
+ end
44
+
45
+ def handle_string_value(_key, value)
46
+ ['String', value[:string]]
47
+ end
48
+
49
+ def handle_boolean_value(_key, value)
50
+ ['Boolean', value[:boolean] == 'true']
51
+ end
52
+
53
+ def handle_key_value_map(_key, value)
54
+ process_key_value_map(value[:key_value_map])
55
+ end
56
+
57
+ def handle_number_value(_key, value)
58
+ ['Number', value[:number].to_i]
59
+ end
60
+
61
+ def handle_float_value(_key, value)
62
+ ['Float', value[:float].to_f]
63
+ end
64
+
65
+ def handle_delegate_value(key, value)
66
+ process_value(value[key])
67
+ end
68
+
69
+ private
70
+
71
+ def process_hash_value(value)
72
+ type_key = VALUE_TYPE_KEYS.find { |k| value.key?(k) }
73
+ return ['Hash', value] unless type_key
74
+
75
+ public_send(VALUE_TYPE_HANDLERS[type_key], type_key, value)
76
+ end
77
+
78
+ def process_typed_sequence(items)
79
+ type = 'String'
80
+ values = items.map do |item|
81
+ item_type, item_value = process_value(item)
82
+ type = item_type
83
+ item_value
84
+ end
85
+ ["#{type}[]", values]
86
+ end
87
+
88
+ def process_key_value_map(kv_map)
89
+ result = kv_map.each_with_object({}) do |kv, h|
90
+ key, value = kv.values_at(:key, :value)
91
+ h[key.to_sym] = process_value(value).last
92
+ end
93
+ ['Hash', result]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ module ViewProcessing
7
+ def process_show_list(data)
8
+ extract_entity_names(data)
9
+ end
10
+
11
+ def process_hide_list(data)
12
+ extract_entity_names(data)
13
+ end
14
+
15
+ private
16
+
17
+ def extract_entity_names(data)
18
+ return data.map { |d| extract_entity_names(d) } if data.is_a?(Array)
19
+ return data[:entity_name].to_s if data.is_a?(Hash) && data.key?(:entity_name)
20
+ data.to_s
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ class DataProcessor
6
+ autoload :ValueProcessing, "lutaml/lml/data_processor/value_processing"
7
+ autoload :AttributeProcessing, "lutaml/lml/data_processor/attribute_processing"
8
+ autoload :InstanceProcessing, "lutaml/lml/data_processor/instance_processing"
9
+ autoload :CollectionProcessing, "lutaml/lml/data_processor/collection_processing"
10
+ autoload :ViewProcessing, "lutaml/lml/data_processor/view_processing"
11
+
12
+ include ValueProcessing
13
+ include AttributeProcessing
14
+ include InstanceProcessing
15
+ include CollectionProcessing
16
+ include ViewProcessing
17
+
18
+ KEY_HANDLERS = {
19
+ requires: :process_requires,
20
+ instances: :process_instances,
21
+ instance: :process_instance,
22
+ attributes: :process_attributes,
23
+ show_list: :process_show_list,
24
+ hide_list: :process_hide_list
25
+ }.freeze
26
+
27
+ def self.process(obj)
28
+ new.process_data(obj)
29
+ end
30
+
31
+ def process_data(obj)
32
+ return obj unless obj.is_a?(Hash) || obj.is_a?(Array)
33
+
34
+ obj.is_a?(Array) ? obj.map { |item| process_data(item) } : process_hash(obj)
35
+ end
36
+
37
+ def process_hash(obj)
38
+ obj.each_with_object({}) do |(key, value), result|
39
+ handler = KEY_HANDLERS[key]
40
+ result[key] = handler ? public_send(handler, value) : process_data(value)
41
+ end
42
+ end
43
+
44
+ def process_requires(obj)
45
+ obj.map { |req| process_value(req).last }
46
+ end
47
+ end
48
+ end
49
+ end