lutaml-lml 0.1.2 → 0.1.4

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lutaml/lml/cli.rb +1 -1
  3. data/lib/lutaml/lml/data_processor/attribute_processing.rb +1 -0
  4. data/lib/lutaml/lml/data_processor/instance_processing.rb +11 -2
  5. data/lib/lutaml/lml/data_processor/view_processing.rb +7 -6
  6. data/lib/lutaml/lml/document_builder.rb +1 -8
  7. data/lib/lutaml/lml/entity_types.rb +30 -0
  8. data/lib/lutaml/lml/executor/condition_evaluator.rb +1 -1
  9. data/lib/lutaml/lml/executor.rb +4 -5
  10. data/lib/lutaml/lml/format/adapter/standard_adapter.rb +1 -1
  11. data/lib/lutaml/lml/formatter/base.rb +7 -4
  12. data/lib/lutaml/lml/grammar/concerns/definitions.rb +29 -21
  13. data/lib/lutaml/lml/grammar/concerns/primitives.rb +11 -2
  14. data/lib/lutaml/lml/grammar/concerns/view_rules.rb +26 -8
  15. data/lib/lutaml/lml/grammar/instances.rb +5 -2
  16. data/lib/lutaml/lml/import_resolver.rb +45 -47
  17. data/lib/lutaml/lml/literal_value.rb +32 -0
  18. data/lib/lutaml/lml/model_compiler.rb +3 -4
  19. data/lib/lutaml/lml/models/association.rb +1 -1
  20. data/lib/lutaml/lml/models/constraint.rb +1 -1
  21. data/lib/lutaml/lml/models/data_type.rb +5 -1
  22. data/lib/lutaml/lml/models/diagram.rb +1 -1
  23. data/lib/lutaml/lml/models/document.rb +8 -7
  24. data/lib/lutaml/lml/models/enum.rb +5 -1
  25. data/lib/lutaml/lml/models/instance_collection.rb +1 -1
  26. data/lib/lutaml/lml/models/operation.rb +1 -1
  27. data/lib/lutaml/lml/models/package.rb +1 -1
  28. data/lib/lutaml/lml/models/primitive_type.rb +5 -1
  29. data/lib/lutaml/lml/models/top_element_attribute.rb +68 -5
  30. data/lib/lutaml/lml/models/uml_class.rb +11 -18
  31. data/lib/lutaml/lml/models/value.rb +1 -1
  32. data/lib/lutaml/lml/parser.rb +3 -8
  33. data/lib/lutaml/lml/pipeline.rb +9 -30
  34. data/lib/lutaml/lml/preprocessor.rb +26 -21
  35. data/lib/lutaml/lml/source.rb +49 -0
  36. data/lib/lutaml/lml/transform.rb +5 -1
  37. data/lib/lutaml/lml/types/text_type.rb +28 -0
  38. data/lib/lutaml/lml/types.rb +10 -0
  39. data/lib/lutaml/lml/version.rb +1 -1
  40. data/lib/lutaml/lml/view_resolution.rb +32 -0
  41. data/lib/lutaml/lml/view_resolver.rb +5 -5
  42. data/lib/lutaml/lml.rb +21 -0
  43. metadata +8 -2
@@ -7,10 +7,10 @@ module Lutaml
7
7
  attribute :name, :string
8
8
  attribute :title, :string
9
9
  attribute :caption, :string
10
- attribute :groups, "Lutaml::Lml::Group", collection: true
10
+ attribute :groups, "Lutaml::Lml::Group", collection: true, default: -> { [] }
11
11
  attribute :fidelity, "Lutaml::Lml::Fidelity"
12
12
  attribute :fontname, :string
13
- attribute :comments, :string, collection: true
13
+ attribute :comments, :string, collection: true, default: -> { [] }
14
14
 
15
15
  attribute :classes, "Lutaml::Lml::UmlClass", collection: true, default: -> { [] }
16
16
  attribute :data_types, "Lutaml::Lml::DataType", collection: true, default: -> { [] }
@@ -20,11 +20,12 @@ module Lutaml
20
20
  attribute :associations, "Lutaml::Lml::Association", collection: true, default: -> { [] }
21
21
  attribute :diagrams, "Lutaml::Lml::Diagram", collection: true, default: -> { [] }
22
22
 
23
- # LML-specific
23
+ # LML-specific. instance/instances/fidelity/show_filter/hide_filter
24
+ # are deliberately default-less: nil means "absent".
24
25
  attribute :instance, "Lutaml::Lml::Instance"
25
- attribute :requires, :string, collection: true
26
+ attribute :requires, :string, collection: true, default: -> { [] }
26
27
  attribute :instances, "Lutaml::Lml::InstanceCollection"
27
- attribute :view_imports, "Lutaml::Lml::ViewImport", collection: true
28
+ attribute :view_imports, "Lutaml::Lml::ViewImport", collection: true, default: -> { [] }
28
29
  attribute :show_filter, "Lutaml::Lml::ViewFilter"
29
30
  attribute :hide_filter, "Lutaml::Lml::ViewFilter"
30
31
 
@@ -32,13 +33,13 @@ module Lutaml
32
33
  # formatters that need to walk every classifiable type without
33
34
  # caring which collection holds it.
34
35
  def all_classes
35
- classes + enums + data_types + primitives
36
+ EntityTypes.all.flat_map { |type| public_send(type.entity_type) }
36
37
  end
37
38
 
38
39
  # Class-like entities that can own associations. Excludes enums
39
40
  # (which have no attributes/associations of their own).
40
41
  def classifiable_classes
41
- classes + data_types + primitives
42
+ EntityTypes.classifiable.flat_map { |type| public_send(type.entity_type) }
42
43
  end
43
44
  end
44
45
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  class Enum < Lutaml::Model::Serializable
6
6
  # From TopElement
7
7
  attribute :name, :string
8
- attribute :definition, :string
8
+ attribute :definition, Lutaml::Lml::Types::TextType
9
9
  attribute :keyword, :string, default: "enumeration"
10
10
  attribute :stereotype, :string, collection: true, default: -> { [] }
11
11
  attribute :visibility, :string, default: "public"
@@ -23,6 +23,10 @@ module Lutaml
23
23
  def self.entity_type
24
24
  :enums
25
25
  end
26
+
27
+ def self.classifiable?
28
+ false
29
+ end
26
30
  end
27
31
  end
28
32
  end
@@ -6,7 +6,7 @@ module Lutaml
6
6
  attribute :instances, "Lutaml::Lml::Instance", collection: true, default: []
7
7
  attribute :imports, "Lutaml::Lml::InstancesImport", collection: true, default: []
8
8
  attribute :exports, "Lutaml::Lml::InstancesExport", collection: true, default: []
9
- attribute :collections, "Lutaml::Lml::Collection", default: []
9
+ attribute :collections, "Lutaml::Lml::Collection", collection: true, default: []
10
10
  end
11
11
  end
12
12
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  class Operation < Lutaml::Model::Serializable
6
6
  # From TopElement
7
7
  attribute :name, :string
8
- attribute :definition, :string
8
+ attribute :definition, Lutaml::Lml::Types::TextType
9
9
  attribute :keyword, :string
10
10
  attribute :stereotype, :string, collection: true, default: -> { [] }
11
11
  attribute :visibility, :string, default: "public"
@@ -5,7 +5,7 @@ module Lutaml
5
5
  class Package < Lutaml::Model::Serializable
6
6
  # From TopElement
7
7
  attribute :name, :string
8
- attribute :definition, :string
8
+ attribute :definition, Lutaml::Lml::Types::TextType
9
9
  attribute :keyword, :string
10
10
  attribute :stereotype, :string, collection: true, default: -> { [] }
11
11
  attribute :visibility, :string, default: "public"
@@ -7,7 +7,7 @@ module Lutaml
7
7
  # accesses the same attributes as DataType via format_class.
8
8
  attribute :name, :string
9
9
  attribute :keyword, :string, default: "primitive"
10
- attribute :definition, :string
10
+ attribute :definition, Lutaml::Lml::Types::TextType
11
11
  attribute :stereotype, :string, collection: true, default: -> { [] }
12
12
  attribute :visibility, :string, default: "public"
13
13
  attribute :is_abstract, :boolean, default: false
@@ -21,6 +21,10 @@ module Lutaml
21
21
  def self.entity_type
22
22
  :primitives
23
23
  end
24
+
25
+ def self.classifiable?
26
+ true
27
+ end
24
28
  end
25
29
  end
26
30
  end
@@ -16,16 +16,79 @@ module Lutaml
16
16
  attribute :is_static, :boolean, default: false
17
17
  attribute :is_read_only, :boolean, default: false
18
18
  attribute :stereotype, :string, collection: true, default: -> { [] }
19
- attribute :definition, :string
19
+ attribute :definition, Lutaml::Lml::Types::TextType
20
20
  attribute :association, :string
21
21
  attribute :default, :string
22
22
 
23
23
  # LML-specific attributes
24
- attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: []
25
- attribute :value, "Lutaml::Lml::TopElementAttribute", collection: true
26
- attribute :attributes, "Lutaml::Lml::TopElementAttribute", collection: true, default: []
24
+ attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
25
+ attribute :value, LiteralValue
26
+ attribute :attributes, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
27
27
  attribute :extended, :boolean
28
- attribute :instances, "Lutaml::Lml::Instance", collection: true, default: []
28
+ attribute :instances, "Lutaml::Lml::Instance", collection: true, default: -> { [] }
29
+
30
+ # Declared in full because a mapping block replaces the defaults. The
31
+ # rules below are the defaults, 1:1, except for `value`.
32
+ #
33
+ # `value` needs custom methods because a list literal reaches the model
34
+ # as a bare Array, and lutaml-model refuses one on a non-collection
35
+ # attribute: Attribute#cast splits it, then valid_collection! raises
36
+ # CollectionTrueMissingError. A custom `from` returns before that check,
37
+ # so a non-empty list survives a round trip without changing the
38
+ # serialized form. An empty list still does not: the transform skips a
39
+ # blank value before reaching the custom method, so `[]` reloads as nil.
40
+ key_value do
41
+ map "name", to: :name
42
+ map "visibility", to: :visibility
43
+ map "type", to: :type
44
+ map "id", to: :id
45
+ map "contain", to: :contain
46
+ map "static", to: :static
47
+ map "cardinality", to: :cardinality
48
+ map "keyword", to: :keyword
49
+ map "is_derived", to: :is_derived
50
+ map "is_static", to: :is_static
51
+ map "is_read_only", to: :is_read_only
52
+ map "stereotype", to: :stereotype
53
+ map "definition", to: :definition
54
+ map "association", to: :association
55
+ map "default", to: :default
56
+ map "properties", to: :properties
57
+ map "value", to: :value, with: { to: :value_to, from: :value_from }
58
+ map "attributes", to: :attributes
59
+ map "extended", to: :extended
60
+ map "instances", to: :instances
61
+ end
62
+
63
+ # Public because lutaml-model invokes mapping methods via public_send.
64
+ #
65
+ # Skipping nil keeps the key out of the output, which is what the default
66
+ # mapping does. `false` is a real literal, so the guard tests for nil
67
+ # rather than truthiness.
68
+ def value_to(model, doc)
69
+ literal = model.value
70
+ return if literal.nil?
71
+
72
+ doc["value"] = unwrap_literal(literal)
73
+ end
74
+
75
+ # lutaml-model runs this on a throwaway mapper instance, so `self` is not
76
+ # the object being deserialized - `model` is. Writing to `self.value`
77
+ # here would update an object that is discarded a moment later.
78
+ def value_from(model, value)
79
+ model.value = value
80
+ end
81
+
82
+ private
83
+
84
+ # A LiteralValue sits at the top level for a map literal, and inside the
85
+ # array for a list of references. Writing one through unwrapped raises
86
+ # ArgumentError in the adapter.
87
+ def unwrap_literal(value)
88
+ return value.map { |item| unwrap_literal(item) } if value.is_a?(::Array)
89
+
90
+ value.is_a?(LiteralValue) ? value.value : value
91
+ end
29
92
  end
30
93
  end
31
94
  end
@@ -3,9 +3,10 @@
3
3
  module Lutaml
4
4
  module Lml
5
5
  class UmlClass < Lutaml::Model::Serializable
6
- # From TopElement
6
+ # From TopElement. TextType unescapes \{ \} and normalizes lines
7
+ # on every assignment (builder, yaml, direct).
7
8
  attribute :name, :string
8
- attribute :definition, :string
9
+ attribute :definition, Lutaml::Lml::Types::TextType
9
10
  attribute :keyword, :string
10
11
  attribute :stereotype, :string, collection: true, default: -> { [] }
11
12
  attribute :visibility, :string, default: "public"
@@ -31,9 +32,7 @@ module Lutaml
31
32
  map "name", to: :name
32
33
  map "keyword", to: :keyword
33
34
  map "is_abstract", to: :is_abstract
34
- map "definition", to: :definition, with: {
35
- to: :definition_to_yaml, from: :definition_from_yaml
36
- }
35
+ map "definition", to: :definition
37
36
  map "modifier", to: :modifier
38
37
  map "stereotype", to: :stereotype
39
38
  map "visibility", to: :visibility
@@ -42,6 +41,9 @@ module Lutaml
42
41
  map "operations", to: :operations
43
42
  map "constraints", to: :constraints
44
43
  map "data_types", to: :data_types
44
+ # Owner-side defaulting is a parent-context rule ("an
45
+ # association nested under a class defaults its owner to that
46
+ # class"); only a mapping proc can see the containing model.
45
47
  map "associations", to: :associations, with: {
46
48
  to: :associations_to_yaml, from: :associations_from_yaml
47
49
  }
@@ -63,22 +65,13 @@ module Lutaml
63
65
  model.associations = associations
64
66
  end
65
67
 
66
- def definition_to_yaml(model, doc)
67
- doc["definition"] = model.definition if model.definition
68
- end
69
-
70
- def definition_from_yaml(model, value)
71
- model.definition = value.to_s
72
- .gsub(/\\}/, "}")
73
- .gsub(/\\{/, "{")
74
- .split("\n")
75
- .map(&:strip)
76
- .join("\n")
77
- end
78
-
79
68
  def self.entity_type
80
69
  :classes
81
70
  end
71
+
72
+ def self.classifiable?
73
+ true
74
+ end
82
75
  end
83
76
  end
84
77
  end
@@ -6,7 +6,7 @@ module Lutaml
6
6
  attribute :name, :string
7
7
  attribute :id, :string
8
8
  attribute :type, :string
9
- attribute :definition, :string
9
+ attribute :definition, Lutaml::Lml::Types::TextType
10
10
  end
11
11
  end
12
12
  end
@@ -5,18 +5,13 @@ require "parslet/convenience"
5
5
 
6
6
  module Lutaml
7
7
  module Lml
8
+ # The Parslet parser: raw LML text → parse tree. Pipeline-level
9
+ # entry points live on the Lutaml::Lml module (parse /
10
+ # parse_document).
8
11
  class Parser < Parslet::Parser
9
12
  include Grammar::Full
10
13
 
11
14
  root(:diagram)
12
-
13
- def self.parse(io)
14
- Pipeline.call(io)
15
- end
16
-
17
- def self.parse_document(io)
18
- Pipeline.call(io, resolve: false)
19
- end
20
15
  end
21
16
  end
22
17
  end
@@ -5,26 +5,24 @@ require "parslet/convenience"
5
5
 
6
6
  module Lutaml
7
7
  module Lml
8
+ # Parse pipeline only: preprocess → parse → transform → process →
9
+ # build. View resolution and label enrichment are a separate,
10
+ # explicit post-parse step (ViewResolution) — file I/O for imports
11
+ # does not belong inside parsing.
8
12
  class Pipeline
9
- def self.call(input, resolve: true)
10
- new(input, resolve: resolve).call
13
+ def self.call(input)
14
+ new(input).call
11
15
  end
12
16
 
13
- def initialize(input, resolve: true)
14
- @input = input
15
- @resolve = resolve
17
+ def initialize(input)
18
+ @input = Source.wrap(input)
16
19
  end
17
20
 
18
21
  def call
19
22
  data = Preprocessor.call(@input)
20
23
  hash = parse_raw(data)
21
24
  hash = DataProcessor.process(hash)
22
- document = build_document(hash)
23
- if @resolve
24
- document = resolve_document(document)
25
- AssociationLabelResolver.new.enrich(document)
26
- end
27
- document
25
+ build_document(hash)
28
26
  end
29
27
 
30
28
  private
@@ -40,25 +38,6 @@ module Lutaml
40
38
  def build_document(hash)
41
39
  DocumentBuilder.new(DocumentBuilder::DEFAULT_REGISTRY).build(:document, hash)
42
40
  end
43
-
44
- def resolve_document(document)
45
- return document unless document.view_imports&.any?
46
-
47
- base_path = @input.is_a?(StringIO) ? nil : @input.path
48
- entities, associations = ImportResolver.new(base_path).resolve(document)
49
- entities, associations = ViewResolver.new.resolve(document, entities, associations)
50
- rebuild_document(document, entities, associations)
51
- end
52
-
53
- def rebuild_document(document, entities, associations)
54
- grouped = entities.group_by { |e| e.class.entity_type }
55
-
56
- document.classes = grouped[:classes] || []
57
- document.enums = grouped[:enums] || []
58
- document.data_types = grouped[:data_types] || []
59
- document.associations = associations
60
- document
61
- end
62
41
  end
63
42
  end
64
43
  end
@@ -6,7 +6,7 @@ module Lutaml
6
6
  attr_reader :input_file
7
7
 
8
8
  def initialize(input_file)
9
- @input_file = input_file
9
+ @input_file = Source.wrap(input_file)
10
10
  end
11
11
 
12
12
  class << self
@@ -16,37 +16,42 @@ module Lutaml
16
16
  end
17
17
 
18
18
  def call
19
- input_file.rewind
20
- include_root = input_file.is_a?(StringIO) ? Dir.pwd : File.dirname(input_file.path)
21
- input_file.read.split("\n").reduce([]) do |res, line|
22
- res.push(*process_dsl_line(include_root, line))
23
- end.join("\n")
19
+ expand_lines(@input_file.read, @input_file.base_dir, [])
24
20
  end
25
21
 
26
22
  private
27
23
 
28
- def process_dsl_line(include_root, line)
29
- process_include_line(include_root, process_comment_line(line))
24
+ def expand_lines(text, base_dir, chain)
25
+ text.split(/\r?\n/)
26
+ .flat_map { |line| expand_line(line, base_dir, chain) }
27
+ .join("\n")
30
28
  end
31
29
 
32
- def process_comment_line(line)
33
- has_comment = line.match(%r{//.*})
34
- return line if has_comment.nil?
30
+ def expand_line(line, base_dir, chain)
31
+ cleaned = strip_comment(line)
32
+ path = include_path(cleaned, base_dir)
33
+ return [cleaned] unless path
35
34
 
36
- line.gsub(%r{//.*}, "")
35
+ raise Error, "circular include detected: #{path}" if chain.include?(path)
36
+
37
+ expand_lines(read_included(path), File.dirname(path), chain + [path])
37
38
  end
38
39
 
39
- def process_include_line(include_root, line)
40
- include_path_match = line.match(/^\s*include\s+(.+)/)
41
- return line if include_path_match.nil?
40
+ def include_path(line, base_dir)
41
+ match = line.match(/^\s*include\s+(.+)/)
42
+ return nil unless match
42
43
 
43
- path_to_file = File.expand_path(include_path_match[1].strip, include_root)
44
- File.read(path_to_file).split("\n").map do |l|
45
- process_comment_line(l)
46
- end
44
+ File.expand_path(match[1].strip, base_dir)
45
+ end
46
+
47
+ def read_included(path)
48
+ File.read(path)
47
49
  rescue Errno::ENOENT, Errno::EACCES => e
48
- warn "Skipping #{path_to_file}: #{e.message}"
49
- []
50
+ raise Error, "cannot read include #{path}: #{e.message}"
51
+ end
52
+
53
+ def strip_comment(line)
54
+ line.sub(%r{//.*}, "")
50
55
  end
51
56
  end
52
57
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Lutaml
6
+ module Lml
7
+ # Normalizes pipeline input (String, StringIO, File, Tempfile) behind
8
+ # one interface: full text, filesystem path (or nil), and the
9
+ # directory imports/includes resolve against.
10
+ class Source
11
+ attr_reader :input
12
+
13
+ def self.wrap(input)
14
+ input.is_a?(Source) ? input : new(input)
15
+ end
16
+
17
+ def initialize(input)
18
+ @input = input
19
+ end
20
+
21
+ def read
22
+ return @input if @input.is_a?(String)
23
+
24
+ @input.rewind
25
+ @input.read
26
+ end
27
+
28
+ # Filesystem path, or nil for anonymous input (String/StringIO).
29
+ def path
30
+ io = unwrapped
31
+ io.is_a?(File) ? io.path : nil
32
+ end
33
+
34
+ def base_dir
35
+ path ? File.dirname(path) : Dir.pwd
36
+ end
37
+
38
+ private
39
+
40
+ # Tempfile delegates to File (it is not a File subclass on
41
+ # Ruby >= 3.4), so unwrap before type checks.
42
+ def unwrapped
43
+ io = @input
44
+ io = io.__getobj__ while io.is_a?(Delegator)
45
+ io
46
+ end
47
+ end
48
+ end
49
+ end
@@ -14,7 +14,11 @@ module Lutaml
14
14
  rule(visibility_modifier: simple(:visibility_value)) do
15
15
  VISIBILITY_MAP.fetch(visibility_value.to_s, 'public')
16
16
  end
17
- rule(simple(:member)) { member.nil? ? member : member.to_s.strip }
17
+ # Global scalar normalizer: Parslet applies this to every leaf in
18
+ # the parse tree (names may legally contain trailing spaces via
19
+ # class_name_chars). Binding is named :value to make the breadth
20
+ # explicit — it is not tied to any one grammar rule.
21
+ rule(simple(:value)) { value.nil? ? value : value.to_s.strip }
18
22
  end
19
23
  end
20
24
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Lutaml
6
+ module Lml
7
+ module Types
8
+ # LML `definition` body text. The grammar allows literal braces to
9
+ # be escaped (\{ \}) so they do not close the definition block;
10
+ # casting unescapes them and normalizes line indentation. One
11
+ # implementation shared by every attribute declared with this
12
+ # type — no per-model (de)serialization procs.
13
+ class TextType < Lutaml::Model::Type::String
14
+ def self.cast(value, options = {})
15
+ text = super
16
+ return nil if text.nil?
17
+ return text if Lutaml::Model::Utils.uninitialized?(text)
18
+
19
+ text.gsub(/\\}/, '}')
20
+ .gsub(/\\{/, '{')
21
+ .split("\n")
22
+ .map(&:strip)
23
+ .join("\n")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Custom lutaml-model types shared by LML models.
4
+ module Lutaml
5
+ module Lml
6
+ module Types
7
+ autoload :TextType, 'lutaml/lml/types/text_type'
8
+ end
9
+ end
10
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Lml
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Lml
5
+ # Post-parse step: expand view imports, apply show/hide filters,
6
+ # rebuild the document's entity collections, and enrich association
7
+ # labels. Import expansion runs only when the document declares
8
+ # view imports; label enrichment always runs.
9
+ class ViewResolution
10
+ def self.call(document, base_dir)
11
+ document = resolve_imports(document, base_dir) if document.view_imports.any?
12
+ AssociationLabelResolver.new.enrich(document)
13
+ end
14
+
15
+ def self.resolve_imports(document, base_dir)
16
+ entity_index, associations = ImportResolver.new(base_dir).resolve(document)
17
+ entities, associations = ViewResolver.new.resolve(document, entity_index, associations)
18
+ rebuild_document(document, entities, associations)
19
+ end
20
+
21
+ def self.rebuild_document(document, entities, associations)
22
+ grouped = entities.group_by { |e| e.class.entity_type }
23
+
24
+ EntityTypes.symbols.each do |symbol|
25
+ document.public_send("#{symbol}=", grouped[symbol] || [])
26
+ end
27
+ document.associations = associations
28
+ document
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,8 +3,9 @@
3
3
  module Lutaml
4
4
  module Lml
5
5
  class ViewResolver
6
- def resolve(document, entities, associations)
7
- visible = apply_filters(document, entities)
6
+ # entity_index is the ImportResolver's name → entity map.
7
+ def resolve(document, entity_index, associations)
8
+ visible = apply_filters(document, entity_index)
8
9
  filtered_associations = filter_associations(associations, visible)
9
10
 
10
11
  [visible, filtered_associations]
@@ -12,9 +13,8 @@ module Lutaml
12
13
 
13
14
  private
14
15
 
15
- def apply_filters(document, entities)
16
- names = entities.each_with_object({}) { |e, h| h[e.name] = e }
17
-
16
+ def apply_filters(document, entity_index)
17
+ names = entity_index.reject { |name, _| name.nil? }
18
18
  names = apply_show_filter(names, document.show_filter)
19
19
  names = apply_hide_filter(names, document.hide_filter)
20
20
 
data/lib/lutaml/lml.rb CHANGED
@@ -14,27 +14,47 @@ module Lutaml
14
14
  module Lml
15
15
  class Error < Lutaml::Error; end
16
16
  class ParsingError < Error; end
17
+ class ImportError < Error; end
17
18
 
18
19
  def self.compile(input, namespace: nil)
19
20
  ModelCompiler.new(namespace: namespace).compile(input)
20
21
  end
21
22
 
23
+ # Full entry point: parse, then resolve view imports and enrich
24
+ # association labels.
25
+ def self.parse(input)
26
+ source = Source.wrap(input)
27
+ document = Pipeline.call(source)
28
+ ViewResolution.call(document, source.base_dir)
29
+ end
30
+
31
+ # Parse only — no import resolution, no label enrichment.
32
+ def self.parse_document(input)
33
+ Pipeline.call(input)
34
+ end
35
+
22
36
  # Top-level autoloads
23
37
  autoload :Parser, "lutaml/lml/parser"
24
38
  autoload :Pipeline, "lutaml/lml/pipeline"
25
39
  autoload :Preprocessor, "lutaml/lml/preprocessor"
40
+ autoload :Source, "lutaml/lml/source"
26
41
  autoload :Transform, "lutaml/lml/transform"
27
42
  autoload :DataProcessor, "lutaml/lml/data_processor"
28
43
  autoload :DocumentBuilder, "lutaml/lml/document_builder"
29
44
  autoload :ModelCompiler, "lutaml/lml/model_compiler"
30
45
  autoload :ImportResolver, "lutaml/lml/import_resolver"
31
46
  autoload :ViewResolver, "lutaml/lml/view_resolver"
47
+ autoload :ViewResolution, "lutaml/lml/view_resolution"
48
+ autoload :EntityTypes, "lutaml/lml/entity_types"
32
49
  autoload :AssociationLabelResolver, "lutaml/lml/association_label_resolver"
33
50
  autoload :Executor, "lutaml/lml/executor"
34
51
  autoload :YamlParser, "lutaml/lml/yaml_parser"
35
52
  autoload :HasAttributes, "lutaml/lml/has_attributes"
36
53
  autoload :VERSION, "lutaml/lml/version"
37
54
 
55
+ # Type classes (Lutaml::Model::Type::Value subclasses, not models)
56
+ autoload :LiteralValue, "lutaml/lml/literal_value"
57
+
38
58
  # Model classes (in Lutaml::Lml namespace, files in models/ directory)
39
59
  autoload :Action, "lutaml/lml/models/action"
40
60
  autoload :Association, "lutaml/lml/models/association"
@@ -64,5 +84,6 @@ module Lutaml
64
84
  # Namespaces with their own autoloads
65
85
  autoload :Grammar, "lutaml/lml/grammar"
66
86
  autoload :Format, "lutaml/lml/format"
87
+ autoload :Types, "lutaml/lml/types"
67
88
  end
68
89
  end