expressir 2.3.1 → 2.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c855bf4d946e55fcd6e67cbe9e7116c4bae1e5fb3b1078b71661f45e2fa595d3
4
- data.tar.gz: 6973d40c8ec148024102cbffcadfd5719dbf7d0ceba002caf33e55c0ad02acda
3
+ metadata.gz: bd7415865cfb8be37ccbd1dbeef665f6878230e396060cc6771c8c7df7d8fea3
4
+ data.tar.gz: b1fccc63fc2bfe3c6ab2a7772a6c634a85a45125917fd3b9fb15f366537a8bc0
5
5
  SHA512:
6
- metadata.gz: 543ea137c1a7705e9aa899db477d7bf947ebe678d105197d1545355156a07f2d5dd66f9a9a7e812c64c9b4a446f05adede91ebbe51632c827607c429fd1d148d
7
- data.tar.gz: 3f44737379fbf50a0990e42bd711ddfb23765f7648c12340617f38d7cab2a57a69647ef7763c8f9e1607861e778e64a25db29f0d257901d58df59bd9bbe1718f
6
+ metadata.gz: 5c9c971cdc4f3eabdcd05736f29da963c1eb947e190210b18ffb3712ea7359a93a803b3d28039fc1ec29ee0c53dd7420aba2f3be53b171829e1dcd5a91e420cb
7
+ data.tar.gz: dac877b21617de02ae79f622b5e58590e286d94f4c9d32702bbcbe157152249616598b6553128fcb9a9dc2bd80ae07d0d1abdd71f0ab3c6fdc08ce8172bd5211
@@ -115,26 +115,6 @@ module Expressir
115
115
  end
116
116
  end
117
117
 
118
- def extract_description(compare_report)
119
- parts = []
120
-
121
- [compare_report.modifications, compare_report.additions,
122
- compare_report.deletions].each do |section|
123
- next unless section&.modified_objects
124
-
125
- section.modified_objects.each do |obj|
126
- next unless obj.description
127
-
128
- description_text = normalize_description(obj.description)
129
- next if description_text.strip.empty?
130
-
131
- parts << convert_html_to_asciidoc(description_text.strip)
132
- end
133
- end
134
-
135
- parts.empty? ? nil : parts.join("\n\n")
136
- end
137
-
138
118
  def normalize_description(description)
139
119
  # Handle both String and Array (when XML has nested elements)
140
120
  case description
@@ -340,14 +340,6 @@ module Expressir
340
340
  end
341
341
  end
342
342
 
343
- def to_snake_case(name)
344
- cached_snake_case(name)
345
- end
346
-
347
- def convert_keys_to_snake_case(obj)
348
- fast_convert_keys(obj)
349
- end
350
-
351
343
  def extract_source_info(data)
352
344
  return nil unless data
353
345
  return nil unless @source
@@ -728,7 +728,6 @@ module Expressir
728
728
  @exp_file.schemas.each do |schema|
729
729
  schema.file = schema_file
730
730
  schema.file_basename = File.basename(schema_file, ".exp")
731
- schema.formatted = schema.to_s(no_remarks: true)
732
731
  end
733
732
 
734
733
  unless skip_references
@@ -820,7 +819,6 @@ root_path: nil, use_native: nil)
820
819
  exp_file.schemas.each do |schema|
821
820
  schema.file = nil
822
821
  schema.file_basename = nil
823
- schema.formatted = schema.to_s(no_remarks: true)
824
822
  end
825
823
 
826
824
  unless skip_references
@@ -855,7 +853,6 @@ include_source: nil)
855
853
  exp_file.schemas.each do |schema|
856
854
  schema.file = nil
857
855
  schema.file_basename = nil
858
- schema.formatted = schema.to_s(no_remarks: true)
859
856
  end
860
857
 
861
858
  unless skip_references
@@ -903,7 +900,6 @@ include_source: nil)
903
900
  exp_file.schemas.each do |schema|
904
901
  schema.file = nil
905
902
  schema.file_basename = nil
906
- schema.formatted = schema.to_s(no_remarks: true)
907
903
  end
908
904
 
909
905
  unless skip_references
@@ -11,6 +11,17 @@ module Expressir
11
11
  # 2. Proximity-based matching for simple tags
12
12
  # 3. NOT creating spurious schema-level items for ambiguous tags
13
13
  class RemarkAttacher
14
+ # Child collection attributes to walk when traversing model elements.
15
+ # These are the named collections on model elements (e.g., entity.attributes,
16
+ # schema.entities, function.parameters). Extracted to a constant to avoid
17
+ # duplication between calculate_children_end_line and collect_children.
18
+ CHILD_COLLECTION_ATTRIBUTES = %i[
19
+ schemas types entities functions procedures rules constants
20
+ attributes derived_attributes inverse_attributes
21
+ where_rules unique_rules informal_propositions
22
+ parameters variables statements items remark_items
23
+ ].freeze
24
+
14
25
  def initialize(source)
15
26
  @source = source
16
27
  @attached_spans = Set.new
@@ -23,8 +34,14 @@ module Expressir
23
34
  def attach(model)
24
35
  @model = model
25
36
  remarks = extract_all_remarks
26
- attach_tagged_remarks(model, remarks)
27
- attach_untagged_remarks(model, remarks)
37
+
38
+ # Build nodes_with_positions ONCE for both tagged and untagged remark passes.
39
+ # This avoids double tree walk (381K nodes × 2 = 762K visits) which was
40
+ # the largest memory overhead in remark attachment (~430MB for large files).
41
+ nodes_with_positions = build_sorted_nodes_with_positions(model)
42
+
43
+ attach_tagged_remarks(model, remarks, nodes_with_positions)
44
+ attach_untagged_remarks(remarks, nodes_with_positions)
28
45
 
29
46
  # Free expensive data structures after attachment is complete.
30
47
  # These are only needed during the attach process.
@@ -32,7 +49,6 @@ module Expressir
32
49
  @source_lines = nil
33
50
  @scope_map = nil
34
51
  @line_cache = nil
35
- @remarks_cache = nil
36
52
 
37
53
  model
38
54
  end
@@ -131,9 +147,7 @@ module Expressir
131
147
  @line_cache[position] ||= @source.byteslice(0...position).count("\n") + 1
132
148
  end
133
149
 
134
- alias get_line_number_from_offset get_line_number
135
-
136
- def attach_tagged_remarks(model, remarks)
150
+ def attach_tagged_remarks(model, remarks, nodes_with_positions)
137
151
  tagged = remarks.select { |r| r[:tag] }
138
152
  return if tagged.empty?
139
153
 
@@ -143,12 +157,6 @@ module Expressir
143
157
  # This is the key optimization that makes scope lookup O(1) per remark
144
158
  @scope_map ||= build_scope_map
145
159
 
146
- # Collect nodes with positions for position-based fallback
147
- nodes_with_positions = []
148
- collect_nodes_with_positions(model, nodes_with_positions)
149
- # Use stable sort to ensure deterministic ordering across Ruby versions
150
- nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
151
-
152
160
  tagged.sort_by { |r| r[:position] }.each do |remark|
153
161
  next if @attached_spans.include?(remark[:position])
154
162
 
@@ -566,102 +574,6 @@ module Expressir
566
574
  find_containing_scope_position(remark_line, nodes_with_positions)
567
575
  end
568
576
 
569
- def find_scope_by_text_search(remark_line)
570
- lines = source_lines
571
- return nil if remark_line < 1 || remark_line > lines.length
572
-
573
- # Track nested scopes by searching backwards from remark_line
574
- scope_stack = []
575
-
576
- lines.each_with_index do |line, idx|
577
- line_num = idx + 1
578
- break if line_num > remark_line
579
-
580
- # Check for START keywords first
581
- if line =~ /^\s*SCHEMA\s+(\w+)/i
582
- scope_stack << { type: :schema, name: $1, line: line_num }
583
- end
584
-
585
- if line =~ /^\s*FUNCTION\s+(\w+)/i
586
- scope_stack << { type: :function, name: $1, line: line_num }
587
- end
588
-
589
- if line =~ /^\s*PROCEDURE\s+(\w+)/i
590
- scope_stack << { type: :procedure, name: $1, line: line_num }
591
- end
592
-
593
- if line =~ /^\s*RULE\s+(\w+)/i
594
- scope_stack << { type: :rule, name: $1, line: line_num }
595
- end
596
-
597
- if line =~ /^\s*ENTITY\s+(\w+)/i
598
- scope_stack << { type: :entity, name: $1, line: line_num }
599
- end
600
-
601
- if line =~ /^\s*TYPE\s+(\w+)/i
602
- scope_stack << { type: :type, name: $1, line: line_num }
603
- end
604
-
605
- # Then check for END keywords (to handle inline closures on same line)
606
- if (line =~ /END_TYPE/i) && (scope_stack.last&.dig(:type) == :type)
607
- scope_stack.pop
608
- end
609
- if (line =~ /END_FUNCTION/i) && (scope_stack.last&.dig(:type) == :function)
610
- scope_stack.pop
611
- end
612
- if (line =~ /END_PROCEDURE/i) && (scope_stack.last&.dig(:type) == :procedure)
613
- scope_stack.pop
614
- end
615
- if (line =~ /END_RULE/i) && (scope_stack.last&.dig(:type) == :rule)
616
- scope_stack.pop
617
- end
618
- if (line =~ /END_ENTITY/i) && (scope_stack.last&.dig(:type) == :entity)
619
- scope_stack.pop
620
- end
621
- if (line =~ /END_SCHEMA/i) && (scope_stack.last&.dig(:type) == :schema)
622
- scope_stack.pop
623
- end
624
- end
625
-
626
- # Find the innermost scope and get the corresponding model node
627
- return nil if scope_stack.empty?
628
-
629
- innermost = scope_stack.last
630
- find_scope_node(innermost[:type], innermost[:name])
631
- end
632
-
633
- def find_scope_node(type, name)
634
- return nil unless @model && name
635
-
636
- @model.schemas.each do |schema|
637
- # Check schema itself
638
- if type == :schema && schema.id == name
639
- return schema
640
- end
641
-
642
- # Check schema-level declarations
643
- case type
644
- when :function
645
- found = schema.functions&.find { |f| f.id == name }
646
- return found if found
647
- when :procedure
648
- found = schema.procedures&.find { |p| p.id == name }
649
- return found if found
650
- when :rule
651
- found = schema.rules&.find { |r| r.id == name }
652
- return found if found
653
- when :entity
654
- found = schema.entities&.find { |e| e.id == name }
655
- return found if found
656
- when :type
657
- found = schema.types&.find { |t| t.id == name }
658
- return found if found
659
- end
660
- end
661
-
662
- nil
663
- end
664
-
665
577
  def build_scope_path(node)
666
578
  return nil unless node
667
579
 
@@ -682,36 +594,6 @@ module Expressir
682
594
  parts.empty? ? nil : parts.join(".")
683
595
  end
684
596
 
685
- def find_containing_scope_for_ip(remark_line, nodes_with_positions)
686
- # First try scope map (O(1))
687
- scope = find_containing_scope_by_name(remark_line)
688
- if scope && supports_informal_propositions?(scope)
689
- return scope
690
- end
691
-
692
- # Fallback to position-based detection
693
- containing_nodes = nodes_with_positions.select do |n|
694
- n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
695
- !repository?(n[:node]) && !cache?(n[:node])
696
- end
697
-
698
- # Find the innermost node that supports informal propositions
699
- if containing_nodes.any?
700
- containing_nodes.reverse_each do |n|
701
- node = n[:node]
702
- if supports_informal_propositions?(node)
703
- return node
704
- end
705
-
706
- # Fallback to schema
707
- return node if node.is_a?(Model::Declarations::Schema)
708
- end
709
- end
710
-
711
- # Fallback: search for containing entity/type/rule by source text
712
- find_scope_by_source_text(remark_line)
713
- end
714
-
715
597
  def find_scope_by_source_text(remark_line)
716
598
  # Search backwards from remark_line for containing scope
717
599
  lines = source_lines
@@ -917,15 +799,10 @@ module Expressir
917
799
  item
918
800
  end
919
801
 
920
- def attach_untagged_remarks(model, remarks)
802
+ def attach_untagged_remarks(remarks, nodes_with_positions)
921
803
  untagged = remarks.reject { |r| r[:tag] }
922
804
  return unless untagged.any?
923
805
 
924
- nodes_with_positions = []
925
- collect_nodes_with_positions(model, nodes_with_positions)
926
- # Use stable sort to preserve original order for equal keys
927
- nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
928
-
929
806
  untagged.each do |remark|
930
807
  next if @attached_spans.include?(remark[:position])
931
808
 
@@ -1092,10 +969,7 @@ module Expressir
1092
969
  end
1093
970
 
1094
971
  # Check specific child collections
1095
- %i[schemas types entities functions procedures rules constants
1096
- attributes derived_attributes inverse_attributes
1097
- where_rules unique_rules informal_propositions
1098
- parameters variables statements items remark_items].each do |attr|
972
+ CHILD_COLLECTION_ATTRIBUTES.each do |attr|
1099
973
  collection = safe_get_collection(node, attr)
1100
974
  collection&.each do |child|
1101
975
  if child.is_a?(Model::ModelElement) && child.source_offset && child.source
@@ -1114,10 +988,7 @@ module Expressir
1114
988
  collect_nodes_with_positions(child, result, visited)
1115
989
  end
1116
990
 
1117
- %i[schemas types entities functions procedures rules constants
1118
- attributes derived_attributes inverse_attributes
1119
- where_rules unique_rules informal_propositions
1120
- parameters variables statements items remark_items].each do |attr|
991
+ CHILD_COLLECTION_ATTRIBUTES.each do |attr|
1121
992
  collection = safe_get_collection(node, attr)
1122
993
  collection&.each do |item|
1123
994
  collect_nodes_with_positions(item, result, visited)
@@ -1125,6 +996,16 @@ module Expressir
1125
996
  end
1126
997
  end
1127
998
 
999
+ # Build sorted nodes_with_positions ONCE for both tagged and untagged remark passes.
1000
+ # This merges the two separate tree walks into one, cutting node visits in half.
1001
+ def build_sorted_nodes_with_positions(model)
1002
+ nodes_with_positions = []
1003
+ collect_nodes_with_positions(model, nodes_with_positions)
1004
+ # Stable sort: nil positions last, ties broken by insertion order
1005
+ nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
1006
+ nodes_with_positions
1007
+ end
1008
+
1128
1009
  def find_nearest_node(remark, nodes)
1129
1010
  remark_line = remark[:line]
1130
1011
 
@@ -19,9 +19,7 @@ module Expressir
19
19
  attribute :procedures, Procedure, collection: true
20
20
  attribute :_class, :string, default: -> { self.class.name }
21
21
  attribute :selected, :boolean, default: false
22
- attribute :formatted, :string
23
22
  attribute :file_basename, :string
24
- attribute :full_source, :string
25
23
 
26
24
  key_value do
27
25
  map "_class", to: :_class, render_default: true
@@ -67,6 +65,10 @@ module Expressir
67
65
  Expressir::Express::Formatter.format(self)
68
66
  end
69
67
 
68
+ def formatted
69
+ @formatted ||= to_s(no_remarks: true)
70
+ end
71
+
70
72
  def source
71
73
  formatter = Class.new(Expressir::Express::Formatter) do
72
74
  include Expressir::Express::SchemaHeadFormatter
@@ -203,14 +203,6 @@ module Expressir
203
203
  self.untagged_remarks << remark_info
204
204
  end
205
205
 
206
- # Get all remarks as RemarkInfo objects
207
- # @return [Array<RemarkInfo>] Array of RemarkInfo objects
208
- def remark_infos
209
- return [] if untagged_remarks.nil?
210
-
211
- untagged_remarks.grep(RemarkInfo)
212
- end
213
-
214
206
  private
215
207
 
216
208
  # @return [nil]
@@ -1,3 +1,3 @@
1
1
  module Expressir
2
- VERSION = "2.3.1".freeze
2
+ VERSION = "2.3.3".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expressir
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-01 00:00:00.000000000 Z
11
+ date: 2026-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -507,7 +507,6 @@ files:
507
507
  - lib/expressir/package/reader.rb
508
508
  - lib/expressir/schema_manifest.rb
509
509
  - lib/expressir/schema_manifest_entry.rb
510
- - lib/expressir/transformer.rb
511
510
  - lib/expressir/version.rb
512
511
  homepage: https://github.com/lutaml/expressir
513
512
  licenses:
@@ -1,7 +0,0 @@
1
- module Expressir
2
- module Express
3
- module Transformer
4
- autoload :RemarkHandling, "#{__dir__}/express/transformer/remark_handling"
5
- end
6
- end
7
- end