lutaml 0.10.4 → 0.10.6

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +10 -0
  4. data/.rubocop_todo.yml +218 -94
  5. data/TODO.cleanups/01-resolve-production-todos.md +65 -0
  6. data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
  7. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
  8. data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
  9. data/TODO.cleanups/05-replace-marshal-load.md +37 -0
  10. data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
  11. data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
  12. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
  13. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
  14. data/TODO.cleanups/10-split-large-files.md +47 -0
  15. data/bin/console +0 -1
  16. data/exe/lutaml +1 -0
  17. data/lib/lutaml/cli/element_identifier.rb +3 -6
  18. data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
  19. data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
  20. data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
  21. data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
  22. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
  23. data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
  24. data/lib/lutaml/cli/interactive_shell.rb +116 -802
  25. data/lib/lutaml/cli/uml/build_command.rb +5 -5
  26. data/lib/lutaml/cli/uml/verify_command.rb +0 -1
  27. data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
  28. data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
  29. data/lib/lutaml/formatter/graphviz.rb +1 -2
  30. data/lib/lutaml/qea/database.rb +1 -47
  31. data/lib/lutaml/qea/factory/association_builder.rb +188 -0
  32. data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
  33. data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
  34. data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
  35. data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
  36. data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
  37. data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
  38. data/lib/lutaml/qea/lookup_indexes.rb +54 -0
  39. data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
  40. data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
  41. data/lib/lutaml/uml/has_members.rb +0 -1
  42. data/lib/lutaml/uml/inheritance_walker.rb +92 -0
  43. data/lib/lutaml/uml/model_helpers.rb +129 -0
  44. data/lib/lutaml/uml/node/attribute.rb +3 -1
  45. data/lib/lutaml/uml/node/class_node.rb +3 -3
  46. data/lib/lutaml/uml/operation.rb +2 -0
  47. data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
  48. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
  49. data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
  50. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
  51. data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
  52. data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
  53. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
  54. data/lib/lutaml/uml_repository/index_builder.rb +3 -271
  55. data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
  56. data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
  57. data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
  58. data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
  59. data/lib/lutaml/uml_repository/package_loader.rb +37 -17
  60. data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
  61. data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
  62. data/lib/lutaml/uml_repository/repository.rb +7 -57
  63. data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
  64. data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
  65. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
  66. data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
  67. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
  68. data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
  69. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
  70. data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
  71. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
  72. data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
  73. data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
  74. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
  75. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
  76. data/lib/lutaml/version.rb +1 -1
  77. data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
  78. data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
  79. data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
  80. data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
  81. data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
  82. data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
  83. data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
  84. data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
  85. data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
  86. data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
  87. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
  88. data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
  89. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
  90. data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
  91. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
  92. data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
  93. data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
  94. data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
  95. data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
  96. data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
  97. data/lib/lutaml/xmi/parsers/xml.rb +7 -120
  98. data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
  99. data/lib/lutaml.rb +0 -1
  100. metadata +48 -21
  101. data/lib/lutaml/cli/commands/base_command.rb +0 -118
  102. data/lib/lutaml/command_line.rb +0 -272
  103. data/lib/lutaml/sysml/allocate.rb +0 -9
  104. data/lib/lutaml/sysml/allocated.rb +0 -9
  105. data/lib/lutaml/sysml/binding_connector.rb +0 -9
  106. data/lib/lutaml/sysml/block.rb +0 -32
  107. data/lib/lutaml/sysml/constraint_block.rb +0 -14
  108. data/lib/lutaml/sysml/copy.rb +0 -8
  109. data/lib/lutaml/sysml/derive_requirement.rb +0 -9
  110. data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
  111. data/lib/lutaml/sysml/refine.rb +0 -9
  112. data/lib/lutaml/sysml/requirement.rb +0 -44
  113. data/lib/lutaml/sysml/requirement_related.rb +0 -9
  114. data/lib/lutaml/sysml/satisfy.rb +0 -9
  115. data/lib/lutaml/sysml/test_case.rb +0 -25
  116. data/lib/lutaml/sysml/trace.rb +0 -9
  117. data/lib/lutaml/sysml/verify.rb +0 -8
  118. data/lib/lutaml/sysml/xmi_file.rb +0 -486
  119. data/lib/lutaml/sysml.rb +0 -11
@@ -4,21 +4,15 @@ require "readline"
4
4
  require "pathname"
5
5
  require_relative "enhanced_formatter"
6
6
  require_relative "../uml_repository/repository"
7
+ require_relative "interactive_shell/command_base"
8
+ require_relative "interactive_shell/navigation_commands"
9
+ require_relative "interactive_shell/query_commands"
10
+ require_relative "interactive_shell/bookmark_commands"
11
+ require_relative "interactive_shell/export_handler"
12
+ require_relative "interactive_shell/help_display"
7
13
 
8
14
  module Lutaml
9
15
  module Cli
10
- # InteractiveShell provides a full-featured REPL for
11
- # exploring UML repositories
12
- #
13
- # Features:
14
- # - Readline integration with history
15
- # - Tab completion for commands and paths
16
- # - Colorized prompts and output
17
- # - Navigation commands (cd, pwd, ls, tree, up, root, back)
18
- # - Query commands (find, show, search)
19
- # - Bookmark management
20
- # - Results management
21
- # - Command history persistence
22
16
  class InteractiveShell
23
17
  HISTORY_FILE = File.expand_path("~/.lutaml-xmi-history")
24
18
  MAX_HISTORY = 1000
@@ -26,14 +20,7 @@ module Lutaml
26
20
  attr_reader :repository, :current_path, :config, :bookmarks,
27
21
  :last_results, :path_history
28
22
 
29
- # Initialize the interactive shell
30
- #
31
- # @param lur_path_or_repo [String, UmlRepository] Path to LUR file or
32
- # repository
33
- # @param config [Hash] Configuration options
34
- # @option config [Boolean] :color Enable colored output
35
- # @option config [Boolean] :icons Enable icons in output
36
- def initialize(lur_path_or_repo, config: nil) # rubocop:disable Metrics/MethodLength
23
+ def initialize(lur_path_or_repo, config: nil)
37
24
  @config = {
38
25
  color: true,
39
26
  icons: true,
@@ -41,7 +28,6 @@ module Lutaml
41
28
  page_size: 50,
42
29
  }.merge(config || {})
43
30
 
44
- # Load repository
45
31
  if lur_path_or_repo.is_a?(String)
46
32
  OutputFormatter.progress("Loading repository")
47
33
  @repository = Lutaml::UmlRepository::Repository.from_package(lur_path_or_repo)
@@ -50,37 +36,33 @@ module Lutaml
50
36
  @repository = lur_path_or_repo
51
37
  end
52
38
 
53
- # Initialize state
54
39
  @current_path = "ModelRoot"
55
40
  @bookmarks = {}
56
41
  @last_results = nil
57
42
  @path_history = ["ModelRoot"]
58
43
  @running = false
59
44
 
45
+ @navigation = NavigationCommands.new(self)
46
+ @query = QueryCommands.new(self)
47
+ @bookmarks_cmd = BookmarkCommands.new(self)
48
+ @export = ExportHandler.new(self)
49
+ @help = HelpDisplay.new(self)
50
+
60
51
  setup_readline
61
52
  load_history
62
53
  end
63
54
 
64
- # Start the REPL loop
65
- #
66
- # @return [void]
67
- def start # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
55
+ def start
68
56
  @running = true
69
- display_welcome
57
+ @help.display_welcome
70
58
 
71
59
  while @running
72
60
  begin
73
61
  input = Readline.readline(prompt, true)
74
-
75
- # Exit on Ctrl+D or nil input
76
62
  break if input.nil?
77
-
78
- # Skip empty lines
79
63
  next if input.strip.empty?
80
64
 
81
- # Don't save duplicates in history
82
- if Readline::HISTORY.length > 1 &&
83
- Readline::HISTORY[-2] == input
65
+ if Readline::HISTORY.length > 1 && Readline::HISTORY[-2] == input
84
66
  Readline::HISTORY.pop
85
67
  end
86
68
 
@@ -99,9 +81,103 @@ module Lutaml
99
81
 
100
82
  private
101
83
 
102
- # Generate contextual prompt
103
- #
104
- # @return [String] Formatted prompt
84
+ COMMAND_DISPATCH = {
85
+ # Navigation
86
+ "cd" => :@navigation, "pwd" => :@navigation,
87
+ "ls" => :@navigation, "list" => :@navigation,
88
+ "tree" => :@navigation, "up" => :@navigation,
89
+ "root" => :@navigation, "back" => :@navigation,
90
+ # Query
91
+ "find" => :@query, "f" => :@query,
92
+ "show" => :@query, "s" => :@query,
93
+ "search" => :@query, "?" => :@query,
94
+ "results" => :@query,
95
+ # Bookmarks
96
+ "bookmark" => :@bookmarks_cmd, "bm" => :@bookmarks_cmd,
97
+ # Export
98
+ "export" => :@export,
99
+ # Utilities
100
+ "help" => :@help, "h" => :@help,
101
+ "history" => :@help,
102
+ "clear" => :@help, "cls" => :@help,
103
+ "config" => :@help,
104
+ "stats" => :@help
105
+ }.freeze
106
+
107
+ METHOD_MAP = {
108
+ "cd" => :cmd_cd, "pwd" => :cmd_pwd,
109
+ "ls" => :cmd_ls, "list" => :cmd_ls,
110
+ "tree" => :cmd_tree, "up" => :cmd_up,
111
+ "root" => :cmd_root, "back" => :cmd_back,
112
+ "find" => :cmd_find, "f" => :cmd_find,
113
+ "show" => :cmd_show, "s" => :cmd_show,
114
+ "search" => :cmd_search, "?" => :cmd_search,
115
+ "results" => :cmd_results,
116
+ "bookmark" => :cmd_bookmark, "bm" => :cmd_bookmark,
117
+ "export" => :cmd_export,
118
+ "help" => :cmd_help, "h" => :cmd_help,
119
+ "history" => :cmd_history,
120
+ "clear" => :cmd_clear, "cls" => :cmd_clear,
121
+ "config" => :cmd_config,
122
+ "stats" => :cmd_stats
123
+ }.freeze
124
+
125
+ def execute_command(input)
126
+ parts = input.split(/\s+/)
127
+ command = parts[0].downcase
128
+ args = parts[1..]
129
+
130
+ if ["exit", "quit", "q"].include?(command)
131
+ @running = false
132
+ return
133
+ end
134
+
135
+ handler_var = COMMAND_DISPATCH[command]
136
+ method_name = METHOD_MAP[command]
137
+
138
+ if handler_var && method_name
139
+ instance_variable_get(handler_var).send(method_name, args)
140
+ else
141
+ puts OutputFormatter.warning("Unknown command: #{command}")
142
+ puts "Type 'help' for available commands"
143
+ end
144
+ end
145
+
146
+ # Delegate methods for backward compatibility with tests
147
+ def cmd_cd(args) = @navigation.cmd_cd(args)
148
+ def cmd_pwd(args) = @navigation.cmd_pwd(args)
149
+ def cmd_ls(args) = @navigation.cmd_ls(args)
150
+ def cmd_tree(args) = @navigation.cmd_tree(args)
151
+ def cmd_up(args) = @navigation.cmd_up(args)
152
+ def cmd_root(args) = @navigation.cmd_root(args)
153
+ def cmd_back(args) = @navigation.cmd_back(args)
154
+ def resolve_path(path) = @navigation.resolve_path(path)
155
+ def cmd_find(args) = @query.cmd_find(args)
156
+ def cmd_show(args) = @query.cmd_show(args)
157
+ def cmd_search(args) = @query.cmd_search(args)
158
+ def cmd_results(args) = @query.cmd_results(args)
159
+ def show_class(qname) = @query.show_class(qname)
160
+ def show_package(path) = @query.show_package(path)
161
+ def show_numbered_result(number) = @query.show_numbered_result(number)
162
+ def display_search_results(results) = @query.display_search_results(results)
163
+ def cmd_bookmark(args) = @bookmarks_cmd.cmd_bookmark(args)
164
+ def bookmark_add(name) = @bookmarks_cmd.bookmark_add(name)
165
+ def bookmark_list = @bookmarks_cmd.bookmark_list
166
+ def bookmark_go(name) = @bookmarks_cmd.bookmark_go(name)
167
+ def bookmark_remove(name) = @bookmarks_cmd.bookmark_remove(name)
168
+ def cmd_export(args) = @export.cmd_export(args)
169
+ def export_csv(path) = @export.export_csv(path)
170
+ def export_json(path) = @export.export_json(path)
171
+ def export_yaml(path) = @export.export_yaml(path)
172
+ def display_welcome = @help.display_welcome
173
+ def display_general_help = @help.display_general_help
174
+ def display_command_help(cmd) = @help.display_command_help(cmd)
175
+ def cmd_help(args) = @help.cmd_help(args)
176
+ def cmd_history(args) = @help.cmd_history(args)
177
+ def cmd_clear(args) = @help.cmd_clear(args)
178
+ def cmd_config(args) = @help.cmd_config(args)
179
+ def cmd_stats(args) = @help.cmd_stats(args)
180
+
105
181
  def prompt
106
182
  path_display = @current_path == "ModelRoot" ? "/" : "/#{@current_path}"
107
183
  prompt_text = "lutaml[#{path_display}]> "
@@ -113,21 +189,11 @@ module Lutaml
113
189
  end
114
190
  end
115
191
 
116
- # Setup readline with tab completion and history
117
- #
118
- # @return [void]
119
192
  def setup_readline
120
- # Tab completion
121
- Readline.completion_proc = proc do |word|
122
- complete_command(word)
123
- end
124
-
193
+ Readline.completion_proc = proc { |word| complete_command(word) }
125
194
  Readline.completion_append_character = " "
126
195
  end
127
196
 
128
- # Load command history from file
129
- #
130
- # @return [void]
131
197
  def load_history
132
198
  return unless File.exist?(HISTORY_FILE)
133
199
 
@@ -135,13 +201,9 @@ module Lutaml
135
201
  Readline::HISTORY.push(line.chomp)
136
202
  end
137
203
  rescue StandardError => e
138
- # Silently ignore history load errors
139
204
  warn "Warning: Could not load history: #{e.message}" if ENV["DEBUG"]
140
205
  end
141
206
 
142
- # Save command history to file
143
- #
144
- # @return [void]
145
207
  def save_history
146
208
  history_lines = Readline::HISTORY.to_a.last(MAX_HISTORY)
147
209
  File.write(HISTORY_FILE, history_lines.join("\n"))
@@ -149,762 +211,14 @@ module Lutaml
149
211
  warn "Warning: Could not save history: #{e.message}"
150
212
  end
151
213
 
152
- # Display welcome message
153
- #
154
- # @return [void]
155
- def display_welcome # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
156
- puts OutputFormatter.colorize(
157
- "╔═══════════════════════════════════════╗", :cyan
158
- )
159
- puts OutputFormatter.colorize(
160
- "║ LutaML Interactive Shell (REPL) ║", :cyan
161
- )
162
- puts OutputFormatter.colorize(
163
- "╚═══════════════════════════════════════╝", :cyan
164
- )
165
- puts ""
166
- puts "Type 'help' for available commands, 'exit' to quit"
167
- puts ""
168
-
169
- # Show quick stats
170
- stats = @repository.statistics
171
- puts "Repository loaded:"
172
- puts " #{stats[:total_packages]} packages, " \
173
- "#{stats[:total_classes]} classes"
174
- puts ""
175
- end
176
-
177
- # Execute a command
178
- #
179
- # @param input [String] User input
180
- # @return [void]
181
- def execute_command(input) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
182
- parts = input.split(/\s+/)
183
- command = parts[0].downcase
184
- args = parts[1..]
185
-
186
- case command
187
- # Navigation
188
- when "cd"
189
- cmd_cd(args)
190
- when "pwd"
191
- cmd_pwd(args)
192
- when "ls", "list"
193
- cmd_ls(args)
194
- when "tree"
195
- cmd_tree(args)
196
- when "up"
197
- cmd_up(args)
198
- when "root"
199
- cmd_root(args)
200
- when "back"
201
- cmd_back(args)
202
-
203
- # Query
204
- when "find", "f"
205
- cmd_find(args)
206
- when "show", "s"
207
- cmd_show(args)
208
- when "search", "?"
209
- cmd_search(args)
210
-
211
- # Bookmarks
212
- when "bookmark", "bm"
213
- cmd_bookmark(args)
214
-
215
- # Results
216
- when "results"
217
- cmd_results(args)
218
- when "export"
219
- cmd_export(args)
220
-
221
- # Utilities
222
- when "help", "h"
223
- cmd_help(args)
224
- when "history"
225
- cmd_history(args)
226
- when "clear", "cls"
227
- cmd_clear(args)
228
- when "config"
229
- cmd_config(args)
230
- when "stats"
231
- cmd_stats(args)
232
- when "exit", "quit", "q"
233
- @running = false
234
-
235
- else
236
- puts OutputFormatter.warning("Unknown command: #{command}")
237
- puts "Type 'help' for available commands"
238
- end
239
- end
240
-
241
- # Tab completion for commands
242
- #
243
- # @param word [String] Word to complete
244
- # @return [Array<String>] Completion options
245
214
  def complete_command(word)
246
- commands = %w[
215
+ %w[
247
216
  cd pwd ls list tree up root back
248
217
  find show search
249
218
  bookmark bm
250
219
  results export
251
220
  help history clear config stats exit quit
252
- ]
253
-
254
- commands.grep(/^#{Regexp.escape(word)}/)
255
- end
256
-
257
- # Navigation: Change directory
258
- #
259
- # @param args [Array<String>] Command arguments
260
- # @return [void]
261
- def cmd_cd(args) # rubocop:disable Metrics/MethodLength
262
- if args.empty?
263
- puts OutputFormatter.warning("Usage: cd PATH")
264
- return
265
- end
266
-
267
- path = resolve_path(args[0])
268
- pkg = @repository.find_package(path)
269
-
270
- if pkg
271
- unless @path_history.last == @current_path
272
- @path_history << @current_path
273
- end
274
- @current_path = path
275
- puts "Changed to: #{path}"
276
- else
277
- puts OutputFormatter.error("Package not found: #{path}")
278
- end
279
- end
280
-
281
- # Navigation: Print working directory
282
- #
283
- # @param _args [Array<String>] Command arguments (unused)
284
- # @return [void]
285
- def cmd_pwd(_args)
286
- puts @current_path
287
- end
288
-
289
- # Navigation: List packages
290
- #
291
- # @param args [Array<String>] Command arguments
292
- # @return [void]
293
- def cmd_ls(args) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity
294
- path = args.empty? ? @current_path : resolve_path(args[0])
295
- recursive = args.include?("-r") || args.include?("--recursive")
296
-
297
- packages = @repository.list_packages(path, recursive: recursive)
298
-
299
- if packages.empty?
300
- puts OutputFormatter.warning("No packages found at #{path}")
301
- else
302
- packages.each do |pkg|
303
- icon = if @config[:icons]
304
- "#{EnhancedFormatter::ICONS[:package]} "
305
- else
306
- ""
307
- end
308
- puts "#{icon}#{pkg.name}"
309
- end
310
- puts ""
311
- puts "Total: #{packages.size} package(s)"
312
- end
313
- end
314
-
315
- # Navigation: Show tree
316
- #
317
- # @param args [Array<String>] Command arguments
318
- # @return [void]
319
- def cmd_tree(args) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
320
- path = args.empty? ? @current_path : resolve_path(args[0])
321
-
322
- # Parse depth option
323
- max_depth = nil
324
- args.each_with_index do |arg, i|
325
- if arg == "-d" && args[i + 1]
326
- max_depth = args[i + 1].to_i
327
- end
328
- end
329
-
330
- tree_data = @repository.package_tree(path, max_depth: max_depth)
331
-
332
- unless tree_data
333
- puts OutputFormatter.error("Package not found: #{path}")
334
- return
335
- end
336
-
337
- if @config[:icons]
338
- puts EnhancedFormatter.format_tree_with_icons(tree_data, @config)
339
- else
340
- puts OutputFormatter.format_tree(tree_data)
341
- end
342
- end
343
-
344
- # Navigation: Go up one level
345
- #
346
- # @param _args [Array<String>] Command arguments (unused)
347
- # @return [void]
348
- def cmd_up(_args) # rubocop:disable Metrics/MethodLength
349
- if @current_path == "ModelRoot"
350
- puts OutputFormatter.warning("Already at root")
351
- return
352
- end
353
-
354
- parts = @current_path.split("::")
355
- parts.pop
356
- new_path = parts.empty? ? "ModelRoot" : parts.join("::")
357
-
358
- unless @path_history.last == @current_path
359
- @path_history << @current_path
360
- end
361
- @current_path = new_path
362
- puts "Changed to: #{@current_path}"
363
- end
364
-
365
- # Navigation: Go to root
366
- #
367
- # @param _args [Array<String>] Command arguments (unused)
368
- # @return [void]
369
- def cmd_root(_args)
370
- if @current_path == "ModelRoot"
371
- puts "Already at root"
372
- else
373
- unless @path_history.last == @current_path
374
- @path_history << @current_path
375
- end
376
- @current_path = "ModelRoot"
377
- puts "Changed to: ModelRoot"
378
- end
379
- end
380
-
381
- # Navigation: Go back to previous location
382
- #
383
- # @param _args [Array<String>] Command arguments (unused)
384
- # @return [void]
385
- def cmd_back(_args)
386
- if @path_history.size > 1
387
- @path_history.pop
388
- @current_path = @path_history.last
389
- puts "Changed to: #{@current_path}"
390
- else
391
- puts OutputFormatter.warning("No previous location")
392
- end
393
- end
394
-
395
- # Query: Find class by name
396
- #
397
- # @param args [Array<String>] Command arguments
398
- # @return [void]
399
- def cmd_find(args) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
400
- if args.empty?
401
- puts OutputFormatter.warning("Usage: find CLASS_NAME")
402
- return
403
- end
404
-
405
- query = args.join(" ")
406
- results = @repository.search(query, types: [:class])
407
-
408
- if results[:class].empty?
409
- puts OutputFormatter.warning("No classes found matching '#{query}'")
410
- else
411
- @last_results = results[:class]
412
-
413
- puts OutputFormatter.colorize(
414
- "Found #{@last_results.size} class(es):", :cyan
415
- )
416
- @last_results.each_with_index do |qname, i|
417
- puts " #{i + 1}. #{qname}"
418
- end
419
- puts ""
420
- puts "Use 'show NUMBER' to view details"
421
- end
422
- end
423
-
424
- # Query: Show details
425
- #
426
- # @param args [Array<String>] Command arguments
427
- # @return [void]
428
- def cmd_show(args) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
429
- if args.empty?
430
- puts OutputFormatter.warning(
431
- "Usage: show class QNAME | show package PATH | show NUMBER",
432
- )
433
- return
434
- end
435
-
436
- subcommand = args[0].downcase
437
-
438
- case subcommand
439
- when "class"
440
- show_class(args[1..].join(" "))
441
- when "package"
442
- show_package(args[1..].join(" "))
443
- when /^\d+$/
444
- show_numbered_result(subcommand.to_i)
445
- else
446
- # Try as class name
447
- show_class(args.join(" "))
448
- end
449
- end
450
-
451
- # Query: Full-text search
452
- #
453
- # @param args [Array<String>] Command arguments
454
- # @return [void]
455
- def cmd_search(args) # rubocop:disable Metrics/MethodLength
456
- if args.empty?
457
- puts OutputFormatter.warning("Usage: search QUERY")
458
- return
459
- end
460
-
461
- query = args.join(" ")
462
- results = @repository.search(query)
463
-
464
- if results.values.all?(&:empty?)
465
- puts OutputFormatter.warning("No results found for '#{query}'")
466
- else
467
- display_search_results(results)
468
- end
469
- end
470
-
471
- # Bookmarks: Manage bookmarks
472
- #
473
- # @param args [Array<String>] Command arguments
474
- # @return [void]
475
- def cmd_bookmark(args) # rubocop:disable Metrics/MethodLength
476
- return bookmark_list if args.empty?
477
-
478
- subcommand = args[0].downcase
479
-
480
- case subcommand
481
- when "add"
482
- bookmark_add(args[1])
483
- when "list"
484
- bookmark_list
485
- when "go"
486
- bookmark_go(args[1])
487
- when "rm", "remove"
488
- bookmark_remove(args[1])
489
- else
490
- # Quick jump
491
- bookmark_go(subcommand)
492
- end
493
- end
494
-
495
- # Results: Show last search results
496
- #
497
- # @param _args [Array<String>] Command arguments (unused)
498
- # @return [void]
499
- def cmd_results(_args)
500
- if @last_results.nil? || @last_results.empty?
501
- puts OutputFormatter.warning("No previous results")
502
- else
503
- puts OutputFormatter.colorize(
504
- "Last results (#{@last_results.size}):", :cyan
505
- )
506
- @last_results.each_with_index do |item, i|
507
- puts " #{i + 1}. #{item}"
508
- end
509
- end
510
- end
511
-
512
- # Export: Export results
513
- #
514
- # @param args [Array<String>] Command arguments
515
- # @return [void]
516
- def cmd_export(args) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
517
- if @last_results.nil? || @last_results.empty?
518
- puts OutputFormatter.warning("No results to export")
519
- return
520
- end
521
-
522
- if args.size < 3 || args[0] != "last"
523
- puts OutputFormatter.warning("Usage: export last FORMAT FILE")
524
- return
525
- end
526
-
527
- format = args[1].downcase
528
- file_path = args[2]
529
-
530
- case format
531
- when "csv"
532
- export_csv(file_path)
533
- when "json"
534
- export_json(file_path)
535
- when "yaml"
536
- export_yaml(file_path)
537
- else
538
- puts OutputFormatter.error("Unsupported format: #{format}")
539
- end
540
- end
541
-
542
- # Utilities: Show help
543
- #
544
- # @param args [Array<String>] Command arguments
545
- # @return [void]
546
- def cmd_help(args)
547
- if args.empty?
548
- display_general_help
549
- else
550
- display_command_help(args[0])
551
- end
552
- end
553
-
554
- # Utilities: Show command history
555
- #
556
- # @param _args [Array<String>] Command arguments (unused)
557
- # @return [void]
558
- def cmd_history(_args)
559
- history = Readline::HISTORY.to_a.last(20)
560
- history.each_with_index do |line, i|
561
- puts "#{i + 1}. #{line}"
562
- end
563
- end
564
-
565
- # Utilities: Clear screen
566
- #
567
- # @param _args [Array<String>] Command arguments (unused)
568
- # @return [void]
569
- def cmd_clear(_args)
570
- print "\e[2J\e[H"
571
- end
572
-
573
- # Utilities: Show configuration
574
- #
575
- # @param _args [Array<String>] Command arguments (unused)
576
- # @return [void]
577
- def cmd_config(_args)
578
- puts OutputFormatter.colorize("Current Configuration:", :cyan)
579
- @config.each do |key, value|
580
- puts " #{key}: #{value}"
581
- end
582
- end
583
-
584
- # Utilities: Show quick statistics
585
- #
586
- # @param _args [Array<String>] Command arguments (unused)
587
- # @return [void]
588
- def cmd_stats(_args)
589
- stats = @repository.statistics
590
-
591
- if @config[:icons]
592
- puts EnhancedFormatter.format_stats_enhanced(stats)
593
- else
594
- puts OutputFormatter.format_stats(stats, detailed: false)
595
- end
596
- end
597
-
598
- # Show class details
599
- #
600
- # @param qname [String] Qualified class name
601
- # @return [void]
602
- def show_class(qname) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
603
- cls = @repository.find_class(qname)
604
-
605
- unless cls
606
- puts OutputFormatter.error("Class not found: #{qname}")
607
- return
608
- end
609
-
610
- if @config[:icons]
611
- puts EnhancedFormatter.format_class_details_enhanced(cls)
612
- else
613
- puts OutputFormatter.colorize("Class: #{qname}", :cyan)
614
- puts "=" * 50
615
- puts ""
616
- puts "Name: #{cls.name}"
617
-
618
- if cls.respond_to?(:attributes) && cls.attributes &&
619
- !cls.attributes.empty?
620
- puts ""
621
- puts OutputFormatter.colorize("Attributes:", :yellow)
622
- cls.attributes.each do |attr|
623
- puts " - #{attr.name}: #{attr.type}"
624
- end
625
- end
626
- end
627
- end
628
-
629
- # Show package details
630
- #
631
- # @param path [String] Package path
632
- # @return [void]
633
- def show_package(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
634
- path = resolve_path(path)
635
- pkg = @repository.find_package(path)
636
-
637
- unless pkg
638
- puts OutputFormatter.error("Package not found: #{path}")
639
- return
640
- end
641
-
642
- puts OutputFormatter.colorize("Package: #{path}", :cyan)
643
- puts "=" * 50
644
- puts ""
645
- puts "Name: #{pkg.name}"
646
- puts ""
647
-
648
- classes = @repository.classes_in_package(path)
649
- puts OutputFormatter.colorize("Classes (#{classes.size}):", :yellow)
650
- classes.each do |cls|
651
- icon = @config[:icons] ? "#{EnhancedFormatter::ICONS[:class]} " : ""
652
- puts " #{icon}#{cls.name}"
653
- end
654
- end
655
-
656
- # Show numbered result from last search
657
- #
658
- # @param number [Integer] Result number (1-indexed)
659
- # @return [void]
660
- def show_numbered_result(number) # rubocop:disable Metrics/MethodLength
661
- if @last_results.nil? || @last_results.empty?
662
- puts OutputFormatter.warning("No previous results")
663
- return
664
- end
665
-
666
- index = number - 1
667
- if index.negative? || index >= @last_results.size
668
- puts OutputFormatter.error("Invalid result number: #{number}")
669
- return
670
- end
671
-
672
- item = @last_results[index]
673
- show_class(item)
674
- end
675
-
676
- # Display search results
677
- #
678
- # @param results [Hash] Search results by type
679
- # @return [void]
680
- def display_search_results(results) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
681
- results.each do |type, items| # rubocop:disable Metrics/BlockLength
682
- next if items.empty?
683
-
684
- puts ""
685
- puts OutputFormatter.colorize(
686
- "#{type.to_s.capitalize} Results (#{items.size}):", :cyan
687
- )
688
-
689
- case type
690
- when :class
691
- @last_results = items
692
- items.each_with_index do |qname, i|
693
- icon = if @config[:icons]
694
- "#{EnhancedFormatter::ICONS[:class]} "
695
- else
696
- ""
697
- end
698
- puts " #{i + 1}. #{icon}#{qname}"
699
- end
700
- puts ""
701
- puts "Use 'show NUMBER' to view details"
702
- when :attribute
703
- items.each do |item|
704
- puts " - #{item[:class_name]}::#{item[:attribute_name]} : " \
705
- "#{item[:type]}"
706
- end
707
- when :association
708
- items.each do |item|
709
- icon = if @config[:icons]
710
- "#{EnhancedFormatter::ICONS[:association]} "
711
- else
712
- ""
713
- end
714
- puts " #{icon}#{item[:source]} → #{item[:target]}"
715
- end
716
- end
717
- end
718
- end
719
-
720
- # Add bookmark
721
- #
722
- # @param name [String] Bookmark name
723
- # @return [void]
724
- def bookmark_add(name)
725
- if name.nil? || name.empty?
726
- puts OutputFormatter.warning("Usage: bookmark add NAME")
727
- return
728
- end
729
-
730
- target = @last_results&.first || @current_path
731
- @bookmarks[name] = target
732
- puts OutputFormatter.success("Bookmark '#{name}' added: #{target}")
733
- end
734
-
735
- # List bookmarks
736
- #
737
- # @return [void]
738
- def bookmark_list # rubocop:disable Metrics/MethodLength
739
- if @bookmarks.empty?
740
- puts "No bookmarks"
741
- else
742
- puts OutputFormatter.colorize("Bookmarks:", :cyan)
743
- @bookmarks.each do |name, target|
744
- icon = if @config[:icons]
745
- "#{EnhancedFormatter::ICONS[:favorite]} "
746
- else
747
- ""
748
- end
749
- puts " #{icon}#{name} → #{target}"
750
- end
751
- end
752
- end
753
-
754
- # Jump to bookmark
755
- #
756
- # @param name [String] Bookmark name
757
- # @return [void]
758
- def bookmark_go(name) # rubocop:disable Metrics/MethodLength
759
- unless @bookmarks.key?(name)
760
- puts OutputFormatter.error("Bookmark not found: #{name}")
761
- return
762
- end
763
-
764
- target = @bookmarks[name]
765
- if @repository.find_package(target)
766
- unless @path_history.last == @current_path
767
- @path_history << @current_path
768
- end
769
- @current_path = target
770
- puts "Changed to: #{target}"
771
- else
772
- puts OutputFormatter.warning(
773
- "Bookmark target no longer exists: #{target}",
774
- )
775
- end
776
- end
777
-
778
- # Remove bookmark
779
- #
780
- # @param name [String] Bookmark name
781
- # @return [void]
782
- def bookmark_remove(name)
783
- if @bookmarks.delete(name)
784
- puts OutputFormatter.success("Bookmark '#{name}' removed")
785
- else
786
- puts OutputFormatter.error("Bookmark not found: #{name}")
787
- end
788
- end
789
-
790
- # Export results to CSV
791
- #
792
- # @param file_path [String] Output file path
793
- # @return [void]
794
- def export_csv(file_path)
795
- require "csv"
796
-
797
- CSV.open(file_path, "w") do |csv|
798
- csv << ["Qualified Name"]
799
- @last_results.each do |qname|
800
- csv << [qname]
801
- end
802
- end
803
-
804
- puts OutputFormatter.success("Exported #{@last_results.size} " \
805
- "results to #{file_path}")
806
- end
807
-
808
- # Export results to JSON
809
- #
810
- # @param file_path [String] Output file path
811
- # @return [void]
812
- def export_json(file_path)
813
- require "json"
814
-
815
- File.write(file_path, JSON.pretty_generate(@last_results))
816
- puts OutputFormatter.success("Exported #{@last_results.size} " \
817
- "results to #{file_path}")
818
- end
819
-
820
- # Export results to YAML
821
- #
822
- # @param file_path [String] Output file path
823
- # @return [void]
824
- def export_yaml(file_path)
825
- require "yaml"
826
-
827
- File.write(file_path, @last_results.to_yaml)
828
- puts OutputFormatter.success("Exported #{@last_results.size} " \
829
- "results to #{file_path}")
830
- end
831
-
832
- # Display general help
833
- #
834
- # @return [void]
835
- def display_general_help # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
836
- puts OutputFormatter.colorize("Available Commands:", :cyan)
837
- puts ""
838
-
839
- puts OutputFormatter.colorize("Navigation:", :yellow)
840
- puts " cd PATH Change to package path"
841
- puts " pwd Print current path"
842
- puts " ls [PATH] List packages"
843
- puts " tree [PATH] Show package tree"
844
- puts " up Go to parent package"
845
- puts " root Go to ModelRoot"
846
- puts " back Go to previous location"
847
- puts ""
848
-
849
- puts OutputFormatter.colorize("Query:", :yellow)
850
- puts " find CLASS Find class (fuzzy search)"
851
- puts " show class QNAME Show class details"
852
- puts " show package PATH Show package details"
853
- puts " show NUMBER Show numbered result"
854
- puts " search QUERY Full-text search"
855
- puts " ? QUERY Alias for search"
856
- puts ""
857
-
858
- puts OutputFormatter.colorize("Bookmarks:", :yellow)
859
- puts " bookmark add NAME Bookmark current location"
860
- puts " bookmark list List bookmarks"
861
- puts " bookmark go NAME Jump to bookmark"
862
- puts " bookmark rm NAME Remove bookmark"
863
- puts " bm NAME Quick jump"
864
- puts ""
865
-
866
- puts OutputFormatter.colorize("Utilities:", :yellow)
867
- puts " help [COMMAND] Show help"
868
- puts " history Show command history"
869
- puts " clear Clear screen"
870
- puts " config Show configuration"
871
- puts " stats Show statistics"
872
- puts " exit, quit, q Exit shell"
873
- end
874
-
875
- # Display command-specific help
876
- #
877
- # @param command [String] Command name
878
- # @return [void]
879
- def display_command_help(command)
880
- # Command-specific help would go here
881
- puts "Help for '#{command}' not yet implemented"
882
- puts "Use 'help' for general help"
883
- end
884
-
885
- # Resolve path relative to current location
886
- #
887
- # @param path [String] Path to resolve
888
- # @return [String] Resolved path
889
- def resolve_path(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
890
- return path if path.start_with?("ModelRoot")
891
- return @current_path if path == "."
892
- return "ModelRoot" if path == "/"
893
-
894
- if path.start_with?("../")
895
- # Go up and then navigate
896
- parts = @current_path.split("::")
897
- path.scan("../").each { parts.pop }
898
- remaining = path.gsub(/^(\.\.\/)+/, "")
899
- new_path = parts + remaining.split("/")
900
- new_path.join("::")
901
- elsif path.start_with?("./")
902
- # Relative to current
903
- "#{@current_path}::#{path[2..]}"
904
- else
905
- # Append to current
906
- @current_path == "ModelRoot" ? path : "#{@current_path}::#{path}"
907
- end
221
+ ].grep(/^#{Regexp.escape(word)}/)
908
222
  end
909
223
  end
910
224
  end