lutaml 0.10.13 → 0.10.15

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +29 -33
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
  4. data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
  5. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
  6. data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
  7. data/lib/lutaml/cli/interactive_shell.rb +53 -27
  8. data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
  9. data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
  10. data/lib/lutaml/formatter/graphviz.rb +65 -35
  11. data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
  12. data/lib/lutaml/qea/factory/association_builder.rb +144 -127
  13. data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
  14. data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
  15. data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
  16. data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
  17. data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
  18. data/lib/lutaml/qea/lookup_indexes.rb +31 -13
  19. data/lib/lutaml/uml/inheritance_walker.rb +11 -7
  20. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
  21. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
  22. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
  23. data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
  24. data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
  25. data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
  26. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
  27. data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
  28. data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
  29. data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
  30. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
  31. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
  32. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
  33. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
  34. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
  35. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
  36. data/lib/lutaml/version.rb +1 -1
  37. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
  38. data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
  39. metadata +2 -9
  40. data/TODO.cleanups/01-resolve-production-todos.md +0 -65
  41. data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
  42. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
  43. data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
  44. data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
  45. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
  46. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66f287c7903f45ebce9fc0abdc3561ad6ef3df65fdb4b437a126685610756df7
4
- data.tar.gz: 8459a2ad63cf3e4071c7d56ec4ae105410d3044aad2f05810419d4dd90adf861
3
+ metadata.gz: cb62e96e62545c49b005e89cc972bc7dfb9510dbd9867831db9b6f4ab0bbcc40
4
+ data.tar.gz: 3975abff3b9a8e54714a76209d5917ed1bcf291baf9dcdf405c658d211934714
5
5
  SHA512:
6
- metadata.gz: d395f7c78511a601edef5eab6e96f5f896e46a135675767dfd031df36caaa6cfb34a3e5c834d96feed728760a59bd1fe2321ff2ce25490274d9d2693345eb2aa
7
- data.tar.gz: e0ab7f240736d995f948283a9668f578133a39443d95d1889178c7e8c97afb7872d581bc0d8b2d0b79db9c3587087448c82048bb2bce00ee28179b011c422597
6
+ metadata.gz: a09b2b6074e7fb3fa04d2084b16c35ac5f9c115708ac8f50efaac08b5999cd21613053df9eebe487a714c361317e4411de2ecc8d2286b8100e3fe23102854265
7
+ data.tar.gz: 6d6c1dd06afac86c94ba1047087914b59b95d09e11308482f90bb3b99b4b65633eb443a565200842f07ce51064b14e2441a4406f408697a8fe55ef67ce33c9eb
data/.rubocop_todo.yml CHANGED
@@ -1,33 +1,18 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-12 08:24:03 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-13 02:18:49 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 1
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, IndentationWidth.
12
- # SupportedStyles: with_first_argument, with_fixed_indentation
13
- Layout/ArgumentAlignment:
14
- Exclude:
15
- - 'spec/lutaml/xml/parsers/xsd_spec.rb'
16
-
17
- # Offense count: 156
9
+ # Offense count: 155
18
10
  # This cop supports safe autocorrection (--autocorrect).
19
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
20
12
  # URISchemes: http, https
21
13
  Layout/LineLength:
22
14
  Enabled: false
23
15
 
24
- # Offense count: 1
25
- # This cop supports safe autocorrection (--autocorrect).
26
- # Configuration parameters: AllowInHeredoc.
27
- Layout/TrailingWhitespace:
28
- Exclude:
29
- - 'spec/lutaml/xml/parsers/xsd_spec.rb'
30
-
31
16
  # Offense count: 1
32
17
  Lint/BinaryOperatorWithIdenticalOperands:
33
18
  Exclude:
@@ -87,36 +72,48 @@ Lint/UnusedMethodArgument:
87
72
  Exclude:
88
73
  - 'lib/lutaml/cli/uml/export_command.rb'
89
74
 
90
- # Offense count: 2
75
+ # Offense count: 3
91
76
  Lint/UselessConstantScoping:
92
77
  Exclude:
93
78
  - 'lib/lutaml/cli/interactive_shell.rb'
79
+ - 'lib/lutaml/qea/factory/ea_to_uml_factory.rb'
94
80
 
95
- # Offense count: 51
81
+ # Offense count: 8
96
82
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
97
83
  Metrics/AbcSize:
98
- Enabled: false
84
+ Exclude:
85
+ - 'lib/lutaml/cli/interactive_shell/query_commands.rb'
86
+ - 'lib/lutaml/qea/factory/generalization_builder.rb'
87
+ - 'lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb'
88
+ - 'lib/lutaml/uml_repository/queries/search_query.rb'
89
+ - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
90
+ - 'lib/lutaml/xmi/liquid_drops/klass_drop.rb'
99
91
 
100
92
  # Offense count: 4
101
- # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
102
- # AllowedMethods: refine
103
- Metrics/BlockLength:
104
- Max: 53
105
-
106
- # Offense count: 35
107
93
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
108
94
  Metrics/CyclomaticComplexity:
109
- Enabled: false
95
+ Exclude:
96
+ - 'lib/lutaml/qea/factory/generalization_builder.rb'
97
+ - 'lib/lutaml/uml_repository/index_builders/association_index.rb'
98
+ - 'lib/lutaml/uml_repository/queries/search_query.rb'
99
+ - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
110
100
 
111
- # Offense count: 75
101
+ # Offense count: 48
112
102
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
113
103
  Metrics/MethodLength:
114
- Max: 62
104
+ Max: 18
105
+
106
+ # Offense count: 4
107
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
108
+ Metrics/ParameterLists:
109
+ Max: 7
115
110
 
116
- # Offense count: 26
111
+ # Offense count: 2
117
112
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
118
113
  Metrics/PerceivedComplexity:
119
- Enabled: false
114
+ Exclude:
115
+ - 'lib/lutaml/qea/factory/ea_to_uml_factory.rb'
116
+ - 'lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb'
120
117
 
121
118
  # Offense count: 3
122
119
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
@@ -136,11 +133,10 @@ Naming/PredicateMethod:
136
133
  - 'lib/lutaml/qea/factory/document_builder.rb'
137
134
  - 'spec/lutaml/xmi/liquid_drops/nil_safety_spec.rb'
138
135
 
139
- # Offense count: 3
136
+ # Offense count: 2
140
137
  # Configuration parameters: MinSize.
141
138
  Performance/CollectionLiteralInLoop:
142
139
  Exclude:
143
- - 'lib/lutaml/xmi/parsers/xmi_connector.rb'
144
140
  - 'spec/lutaml/qea/infrastructure/table_reader_spec.rb'
145
141
  - 'spec/lutaml/uml_repository/index_builder_spec.rb'
146
142
 
@@ -6,32 +6,40 @@ module Lutaml
6
6
  module Cli
7
7
  class InteractiveShell
8
8
  class ExportHandler < CommandBase
9
+ EXPORT_FORMATS = {
10
+ "csv" => :export_csv,
11
+ "json" => :export_json,
12
+ "yaml" => :export_yaml,
13
+ }.freeze
14
+
9
15
  def cmd_export(args)
10
- if last_results.nil? || last_results.empty?
11
- puts OutputFormatter.warning("No results to export")
12
- return
13
- end
16
+ return warn_no_results if last_results.nil? || last_results.empty?
17
+ return warn_export_usage unless valid_export_args?(args)
14
18
 
15
- if args.size < 3 || args[0] != "last"
16
- puts OutputFormatter.warning("Usage: export last FORMAT FILE")
17
- return
18
- end
19
+ dispatch_export(args[1].downcase, args[2])
20
+ end
19
21
 
20
- format = args[1].downcase
21
- file_path = args[2]
22
+ def valid_export_args?(args)
23
+ args.size >= 3 && args[0] == "last"
24
+ end
22
25
 
23
- case format
24
- when "csv"
25
- export_csv(file_path)
26
- when "json"
27
- export_json(file_path)
28
- when "yaml"
29
- export_yaml(file_path)
26
+ def dispatch_export(format, file_path)
27
+ exporter = EXPORT_FORMATS[format]
28
+ if exporter
29
+ public_send(exporter, file_path)
30
30
  else
31
31
  puts OutputFormatter.error("Unsupported format: #{format}")
32
32
  end
33
33
  end
34
34
 
35
+ def warn_no_results
36
+ puts OutputFormatter.warning("No results to export")
37
+ end
38
+
39
+ def warn_export_usage
40
+ puts OutputFormatter.warning("Usage: export last FORMAT FILE")
41
+ end
42
+
35
43
  def export_csv(file_path)
36
44
  require "csv"
37
45
 
@@ -8,18 +8,14 @@ module Lutaml
8
8
  class InteractiveShell
9
9
  class HelpDisplay < CommandBase
10
10
  def display_welcome
11
- puts OutputFormatter.colorize(
12
- "╔═══════════════════════════════════════╗", :cyan
13
- )
14
- puts OutputFormatter.colorize(
15
- "║ LutaML Interactive Shell (REPL) ║", :cyan
16
- )
17
- puts OutputFormatter.colorize(
18
- "╚═══════════════════════════════════════╝", :cyan
19
- )
20
- puts ""
21
- puts "Type 'help' for available commands, 'exit' to quit"
22
- puts ""
11
+ puts <<~WELCOME
12
+ #{OutputFormatter.colorize('╔═══════════════════════════════════════╗', :cyan)}
13
+ #{OutputFormatter.colorize('║ LutaML Interactive Shell (REPL) ║', :cyan)}
14
+ #{OutputFormatter.colorize('╚═══════════════════════════════════════╝', :cyan)}
15
+
16
+ Type 'help' for available commands, 'exit' to quit
17
+
18
+ WELCOME
23
19
 
24
20
  stats = repository.statistics
25
21
  puts "Repository loaded:"
@@ -65,43 +61,41 @@ module Lutaml
65
61
  end
66
62
 
67
63
  def display_general_help
68
- puts OutputFormatter.colorize("Available Commands:", :cyan)
69
- puts ""
64
+ puts <<~HELP
65
+ #{OutputFormatter.colorize('Available Commands:', :cyan)}
70
66
 
71
- puts OutputFormatter.colorize("Navigation:", :yellow)
72
- puts " cd PATH Change to package path"
73
- puts " pwd Print current path"
74
- puts " ls [PATH] List packages"
75
- puts " tree [PATH] Show package tree"
76
- puts " up Go to parent package"
77
- puts " root Go to ModelRoot"
78
- puts " back Go to previous location"
79
- puts ""
67
+ #{OutputFormatter.colorize('Navigation:', :yellow)}
68
+ cd PATH Change to package path
69
+ pwd Print current path
70
+ ls [PATH] List packages
71
+ tree [PATH] Show package tree
72
+ up Go to parent package
73
+ root Go to ModelRoot
74
+ back Go to previous location
80
75
 
81
- puts OutputFormatter.colorize("Query:", :yellow)
82
- puts " find CLASS Find class (fuzzy search)"
83
- puts " show class QNAME Show class details"
84
- puts " show package PATH Show package details"
85
- puts " show NUMBER Show numbered result"
86
- puts " search QUERY Full-text search"
87
- puts " ? QUERY Alias for search"
88
- puts ""
76
+ #{OutputFormatter.colorize('Query:', :yellow)}
77
+ find CLASS Find class (fuzzy search)
78
+ show class QNAME Show class details
79
+ show package PATH Show package details
80
+ show NUMBER Show numbered result
81
+ search QUERY Full-text search
82
+ ? QUERY Alias for search
89
83
 
90
- puts OutputFormatter.colorize("Bookmarks:", :yellow)
91
- puts " bookmark add NAME Bookmark current location"
92
- puts " bookmark list List bookmarks"
93
- puts " bookmark go NAME Jump to bookmark"
94
- puts " bookmark rm NAME Remove bookmark"
95
- puts " bm NAME Quick jump"
96
- puts ""
84
+ #{OutputFormatter.colorize('Bookmarks:', :yellow)}
85
+ bookmark add NAME Bookmark current location
86
+ bookmark list List bookmarks
87
+ bookmark go NAME Jump to bookmark
88
+ bookmark rm NAME Remove bookmark
89
+ bm NAME Quick jump
97
90
 
98
- puts OutputFormatter.colorize("Utilities:", :yellow)
99
- puts " help [COMMAND] Show help"
100
- puts " history Show command history"
101
- puts " clear Clear screen"
102
- puts " config Show configuration"
103
- puts " stats Show statistics"
104
- puts " exit, quit, q Exit shell"
91
+ #{OutputFormatter.colorize('Utilities:', :yellow)}
92
+ help [COMMAND] Show help
93
+ history Show command history
94
+ clear Clear screen
95
+ config Show configuration
96
+ stats Show statistics
97
+ exit, quit, q Exit shell
98
+ HELP
105
99
  end
106
100
 
107
101
  def display_command_help(command)
@@ -38,22 +38,13 @@ module Lutaml
38
38
  if packages.empty?
39
39
  puts OutputFormatter.warning("No packages found at #{path}")
40
40
  else
41
- packages.each do |pkg|
42
- icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:package]} " : ""
43
- puts "#{icon}#{pkg.name}"
44
- end
45
- puts ""
46
- puts "Total: #{packages.size} package(s)"
41
+ display_packages(packages)
47
42
  end
48
43
  end
49
44
 
50
45
  def cmd_tree(args)
51
46
  path = args.empty? ? current_path : resolve_path(args[0])
52
-
53
- max_depth = nil
54
- args.each_with_index do |arg, i|
55
- max_depth = args[i + 1].to_i if arg == "-d" && args[i + 1]
56
- end
47
+ max_depth = extract_depth(args)
57
48
 
58
49
  tree_data = repository.package_tree(path, max_depth: max_depth)
59
50
 
@@ -62,11 +53,7 @@ module Lutaml
62
53
  return
63
54
  end
64
55
 
65
- if config[:icons]
66
- puts EnhancedFormatter.format_tree_with_icons(tree_data, config)
67
- else
68
- puts OutputFormatter.format_tree(tree_data)
69
- end
56
+ puts format_tree(tree_data)
70
57
  end
71
58
 
72
59
  def cmd_up(_args)
@@ -108,21 +95,53 @@ module Lutaml
108
95
  return path if path.start_with?("ModelRoot")
109
96
  return current_path if path == "."
110
97
  return "ModelRoot" if path == "/"
98
+ return resolve_parent_path(path) if path.start_with?("../")
99
+ return resolve_child_path(path) if path.start_with?("./")
111
100
 
112
- if path.start_with?("../")
113
- parts = current_path.split("::")
114
- path.scan("../").each { parts.pop }
115
- remaining = path.gsub(/^(\.\.\/)+/, "")
116
- new_path = parts + remaining.split("/")
117
- new_path.join("::")
118
- elsif path.start_with?("./")
119
- "#{current_path}::#{path[2..]}"
101
+ resolve_simple_path(path)
102
+ end
103
+
104
+ def resolve_child_path(path)
105
+ "#{current_path}::#{path[2..]}"
106
+ end
107
+
108
+ def resolve_simple_path(path)
109
+ current_path == "ModelRoot" ? path : "#{current_path}::#{path}"
110
+ end
111
+
112
+ private
113
+
114
+ def display_packages(packages)
115
+ packages.each do |pkg|
116
+ icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:package]} " : ""
117
+ puts "#{icon}#{pkg.name}"
118
+ end
119
+ puts ""
120
+ puts "Total: #{packages.size} package(s)"
121
+ end
122
+
123
+ def extract_depth(args)
124
+ args.each_with_index do |arg, i|
125
+ return args[i + 1].to_i if arg == "-d" && args[i + 1]
126
+ end
127
+ nil
128
+ end
129
+
130
+ def format_tree(tree_data)
131
+ if config[:icons]
132
+ EnhancedFormatter.format_tree_with_icons(tree_data, config)
120
133
  else
121
- current_path == "ModelRoot" ? path : "#{current_path}::#{path}"
134
+ OutputFormatter.format_tree(tree_data)
122
135
  end
123
136
  end
124
137
 
125
- private
138
+ def resolve_parent_path(path)
139
+ parts = current_path.split("::")
140
+ path.scan("../").each { parts.pop }
141
+ remaining = path.gsub(/^(\.\.\/)+/, "")
142
+ new_path = parts + remaining.split("/")
143
+ new_path.join("::")
144
+ end
126
145
 
127
146
  def push_path_history
128
147
  return if path_history.last == current_path
@@ -19,16 +19,10 @@ module Lutaml
19
19
  if results[:class].empty?
20
20
  puts OutputFormatter.warning("No classes found matching '#{query}'")
21
21
  else
22
- self.last_results = results[:class]
23
-
24
22
  puts OutputFormatter.colorize(
25
- "Found #{last_results.size} class(es):", :cyan
23
+ "Found #{results[:class].size} class(es):", :cyan
26
24
  )
27
- last_results.each_with_index do |qname, i|
28
- puts " #{i + 1}. #{qname}"
29
- end
30
- puts ""
31
- puts "Use 'show NUMBER' to view details"
25
+ display_class_results(results[:class])
32
26
  end
33
27
  end
34
28
 
@@ -94,18 +88,7 @@ module Lutaml
94
88
  if config[:icons]
95
89
  puts EnhancedFormatter.format_class_details_enhanced(cls)
96
90
  else
97
- puts OutputFormatter.colorize("Class: #{qname}", :cyan)
98
- puts "=" * 50
99
- puts ""
100
- puts "Name: #{cls.name}"
101
-
102
- if cls.is_a?(Lutaml::Uml::Classifier) && cls.attributes && !cls.attributes.empty?
103
- puts ""
104
- puts OutputFormatter.colorize("Attributes:", :yellow)
105
- cls.attributes.each do |attr|
106
- puts " - #{attr.name}: #{attr.type}"
107
- end
108
- end
91
+ display_class_plain(cls, qname)
109
92
  end
110
93
  end
111
94
 
@@ -119,18 +102,7 @@ module Lutaml
119
102
  return
120
103
  end
121
104
 
122
- puts OutputFormatter.colorize("Package: #{path}", :cyan)
123
- puts "=" * 50
124
- puts ""
125
- puts "Name: #{pkg.name}"
126
- puts ""
127
-
128
- classes = repository.classes_in_package(path)
129
- puts OutputFormatter.colorize("Classes (#{classes.size}):", :yellow)
130
- classes.each do |cls|
131
- icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:class]} " : ""
132
- puts " #{icon}#{cls.name}"
133
- end
105
+ display_package_contents(pkg, path)
134
106
  end
135
107
 
136
108
  def show_numbered_result(number)
@@ -159,26 +131,80 @@ module Lutaml
159
131
 
160
132
  case type
161
133
  when :class
162
- self.last_results = items
163
- items.each_with_index do |qname, i|
164
- icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:class]} " : ""
165
- puts " #{i + 1}. #{icon}#{qname}"
166
- end
167
- puts ""
168
- puts "Use 'show NUMBER' to view details"
134
+ display_class_results(items)
169
135
  when :attribute
170
- items.each do |item|
171
- puts " - #{item[:class_name]}::#{item[:attribute_name]} : " \
172
- "#{item[:type]}"
173
- end
136
+ display_attribute_results(items)
174
137
  when :association
175
- items.each do |item|
176
- icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:association]} " : ""
177
- puts " #{icon}#{item[:source]} → #{item[:target]}"
178
- end
138
+ display_association_results(items)
179
139
  end
180
140
  end
181
141
  end
142
+
143
+ private
144
+
145
+ def display_class_results(items)
146
+ self.last_results = items
147
+ items.each_with_index do |qname, i|
148
+ icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:class]} " : ""
149
+ puts " #{i + 1}. #{icon}#{qname}"
150
+ end
151
+ puts ""
152
+ puts "Use 'show NUMBER' to view details"
153
+ end
154
+
155
+ def display_attribute_results(items)
156
+ items.each do |item|
157
+ puts " - #{item[:class_name]}::#{item[:attribute_name]} : " \
158
+ "#{item[:type]}"
159
+ end
160
+ end
161
+
162
+ def display_association_results(items)
163
+ items.each do |item|
164
+ icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:association]} " : ""
165
+ puts " #{icon}#{item[:source]} → #{item[:target]}"
166
+ end
167
+ end
168
+
169
+ def display_class_plain(cls, qname)
170
+ puts OutputFormatter.colorize("Class: #{qname}", :cyan)
171
+ puts "=" * 50
172
+ puts ""
173
+ puts "Name: #{cls.name}"
174
+
175
+ display_class_attributes(cls) if has_displayable_attributes?(cls)
176
+ end
177
+
178
+ def has_displayable_attributes?(cls)
179
+ cls.is_a?(Lutaml::Uml::Classifier) && cls.attributes && !cls.attributes.empty?
180
+ end
181
+
182
+ def display_class_attributes(cls)
183
+ puts ""
184
+ puts OutputFormatter.colorize("Attributes:", :yellow)
185
+ cls.attributes.each do |attr|
186
+ puts " - #{attr.name}: #{attr.type}"
187
+ end
188
+ end
189
+
190
+ def display_package_contents(pkg, path)
191
+ puts OutputFormatter.colorize("Package: #{path}", :cyan)
192
+ puts "=" * 50
193
+ puts ""
194
+ puts "Name: #{pkg.name}"
195
+ puts ""
196
+
197
+ display_package_classes(path)
198
+ end
199
+
200
+ def display_package_classes(path)
201
+ classes = repository.classes_in_package(path)
202
+ puts OutputFormatter.colorize("Classes (#{classes.size}):", :yellow)
203
+ classes.each do |cls|
204
+ icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:class]} " : ""
205
+ puts " #{icon}#{cls.name}"
206
+ end
207
+ end
182
208
  end
183
209
  end
184
210
  end
@@ -24,65 +24,91 @@ module Lutaml
24
24
  attr_accessor :current_path, :last_results
25
25
 
26
26
  def initialize(lur_path_or_repo, config: nil)
27
- @config = {
27
+ @config = default_config.merge(config || {})
28
+ @repository = load_repository(lur_path_or_repo)
29
+
30
+ @current_path = "ModelRoot"
31
+ @bookmarks = {}
32
+ @last_results = nil
33
+ @path_history = ["ModelRoot"]
34
+ @running = false
35
+
36
+ init_commands
37
+ setup_readline
38
+ load_history
39
+ end
40
+
41
+ def start
42
+ @running = true
43
+ @help.display_welcome
44
+
45
+ run_repl_loop
46
+
47
+ save_history
48
+ puts "\nGoodbye!"
49
+ end
50
+
51
+ private
52
+
53
+ def default_config
54
+ {
28
55
  color: true,
29
56
  icons: true,
30
57
  show_counts: true,
31
58
  page_size: 50,
32
- }.merge(config || {})
59
+ }
60
+ end
33
61
 
62
+ def load_repository(lur_path_or_repo)
34
63
  if lur_path_or_repo.is_a?(String)
35
64
  OutputFormatter.progress("Loading repository")
36
- @repository = Lutaml::UmlRepository::Repository.from_package(lur_path_or_repo)
65
+ repo = Lutaml::UmlRepository::Repository.from_package(lur_path_or_repo)
37
66
  OutputFormatter.progress_done
67
+ repo
38
68
  else
39
- @repository = lur_path_or_repo
69
+ lur_path_or_repo
40
70
  end
71
+ end
41
72
 
42
- @current_path = "ModelRoot"
43
- @bookmarks = {}
44
- @last_results = nil
45
- @path_history = ["ModelRoot"]
46
- @running = false
47
-
73
+ def init_commands
48
74
  @navigation = NavigationCommands.new(self)
49
75
  @query = QueryCommands.new(self)
50
76
  @bookmarks_cmd = BookmarkCommands.new(self)
51
77
  @export = ExportHandler.new(self)
52
78
  @help = HelpDisplay.new(self)
53
-
54
- setup_readline
55
- load_history
56
79
  end
57
80
 
58
- def start
59
- @running = true
60
- @help.display_welcome
61
-
81
+ def run_repl_loop
62
82
  while @running
63
83
  begin
64
84
  input = Readline.readline(prompt, true)
65
85
  break if input.nil?
66
86
  next if input.strip.empty?
67
87
 
68
- if Readline::HISTORY.length > 1 && Readline::HISTORY[-2] == input
69
- Readline::HISTORY.pop
70
- end
71
-
88
+ deduplicate_history(input)
72
89
  execute_command(input.strip)
73
90
  rescue Interrupt
74
- puts "\nUse 'exit' or 'quit' to exit the shell"
91
+ handle_interrupt
75
92
  rescue StandardError => e
76
- puts OutputFormatter.error("Error: #{e.message}")
77
- puts e.backtrace.first(3).join("\n") if ENV["DEBUG"]
93
+ handle_error(e)
78
94
  end
79
95
  end
96
+ end
80
97
 
81
- save_history
82
- puts "\nGoodbye!"
98
+ def handle_interrupt
99
+ puts "\nUse 'exit' or 'quit' to exit the shell"
83
100
  end
84
101
 
85
- private
102
+ def handle_error(error)
103
+ puts OutputFormatter.error("Error: #{error.message}")
104
+ puts error.backtrace.first(3).join("\n") if ENV["DEBUG"]
105
+ end
106
+
107
+ def deduplicate_history(input)
108
+ if Readline::HISTORY.length > 1 && Readline::HISTORY[-2] == input
109
+ Readline::HISTORY.pop
110
+ end
111
+ end
86
112
 
87
113
  COMMAND_DISPATCH = {
88
114
  # Navigation