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.
- checksums.yaml +7 -0
- data/lib/lutaml/lml/association_label_resolver.rb +52 -0
- data/lib/lutaml/lml/cli.rb +262 -0
- data/lib/lutaml/lml/data_processor/attribute_processing.rb +81 -0
- data/lib/lutaml/lml/data_processor/collection_processing.rb +37 -0
- data/lib/lutaml/lml/data_processor/instance_processing.rb +63 -0
- data/lib/lutaml/lml/data_processor/value_processing.rb +98 -0
- data/lib/lutaml/lml/data_processor/view_processing.rb +25 -0
- data/lib/lutaml/lml/data_processor.rb +49 -0
- data/lib/lutaml/lml/document_builder.rb +139 -0
- data/lib/lutaml/lml/executor/adapter_helpers.rb +45 -0
- data/lib/lutaml/lml/executor/condition_evaluator.rb +169 -0
- data/lib/lutaml/lml/executor/csv_adapter.rb +88 -0
- data/lib/lutaml/lml/executor/format_adapter.rb +54 -0
- data/lib/lutaml/lml/executor/xml_adapter.rb +102 -0
- data/lib/lutaml/lml/executor.rb +89 -0
- data/lib/lutaml/lml/format/adapter/document.rb +11 -0
- data/lib/lutaml/lml/format/adapter/mapping.rb +19 -0
- data/lib/lutaml/lml/format/adapter/standard_adapter.rb +127 -0
- data/lib/lutaml/lml/format/adapter/transform.rb +11 -0
- data/lib/lutaml/lml/format.rb +29 -0
- data/lib/lutaml/lml/formatter/base.rb +79 -0
- data/lib/lutaml/lml/formatter/graphviz/document_formatter.rb +89 -0
- data/lib/lutaml/lml/formatter/graphviz/html_builder.rb +72 -0
- data/lib/lutaml/lml/formatter/graphviz/node_formatter.rb +74 -0
- data/lib/lutaml/lml/formatter/graphviz/relationship_formatter.rb +130 -0
- data/lib/lutaml/lml/formatter/graphviz.rb +90 -0
- data/lib/lutaml/lml/formatter.rb +8 -0
- data/lib/lutaml/lml/grammar/concerns/associations.rb +76 -0
- data/lib/lutaml/lml/grammar/concerns/attributes.rb +126 -0
- data/lib/lutaml/lml/grammar/concerns/data_structures.rb +84 -0
- data/lib/lutaml/lml/grammar/concerns/definitions.rb +222 -0
- data/lib/lutaml/lml/grammar/concerns/instance_rules.rb +59 -0
- data/lib/lutaml/lml/grammar/concerns/primitives.rb +89 -0
- data/lib/lutaml/lml/grammar/concerns/view_rules.rb +34 -0
- data/lib/lutaml/lml/grammar/concerns.rb +17 -0
- data/lib/lutaml/lml/grammar/core.rb +71 -0
- data/lib/lutaml/lml/grammar/full.rb +12 -0
- data/lib/lutaml/lml/grammar/instances.rb +38 -0
- data/lib/lutaml/lml/grammar.rb +12 -0
- data/lib/lutaml/lml/has_attributes.rb +14 -0
- data/lib/lutaml/lml/import_resolver.rb +89 -0
- data/lib/lutaml/lml/layout/engine.rb +17 -0
- data/lib/lutaml/lml/layout/graph_viz_engine.rb +19 -0
- data/lib/lutaml/lml/layout.rb +8 -0
- data/lib/lutaml/lml/model_compiler.rb +325 -0
- data/lib/lutaml/lml/models/action.rb +10 -0
- data/lib/lutaml/lml/models/association.rb +26 -0
- data/lib/lutaml/lml/models/cardinality.rb +10 -0
- data/lib/lutaml/lml/models/collection.rb +11 -0
- data/lib/lutaml/lml/models/constraint.rb +20 -0
- data/lib/lutaml/lml/models/data_type.rb +31 -0
- data/lib/lutaml/lml/models/diagram.rb +17 -0
- data/lib/lutaml/lml/models/document.rb +45 -0
- data/lib/lutaml/lml/models/enum.rb +28 -0
- data/lib/lutaml/lml/models/fidelity.rb +10 -0
- data/lib/lutaml/lml/models/group.rb +11 -0
- data/lib/lutaml/lml/models/instance.rb +13 -0
- data/lib/lutaml/lml/models/instance_collection.rb +12 -0
- data/lib/lutaml/lml/models/instances_export.rb +10 -0
- data/lib/lutaml/lml/models/instances_import.rb +11 -0
- data/lib/lutaml/lml/models/operation.rb +23 -0
- data/lib/lutaml/lml/models/operation_parameter.rb +11 -0
- data/lib/lutaml/lml/models/package.rb +22 -0
- data/lib/lutaml/lml/models/primitive_type.rb +26 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +31 -0
- data/lib/lutaml/lml/models/uml_class.rb +84 -0
- data/lib/lutaml/lml/models/value.rb +12 -0
- data/lib/lutaml/lml/models/view_filter.rb +11 -0
- data/lib/lutaml/lml/models/view_import.rb +11 -0
- data/lib/lutaml/lml/parser.rb +22 -0
- data/lib/lutaml/lml/pipeline.rb +64 -0
- data/lib/lutaml/lml/preprocessor.rb +56 -0
- data/lib/lutaml/lml/transform.rb +20 -0
- data/lib/lutaml/lml/version.rb +7 -0
- data/lib/lutaml/lml/view_resolver.rb +48 -0
- data/lib/lutaml/lml/yaml_parser.rb +17 -0
- data/lib/lutaml/lml.rb +67 -0
- 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
|