lutaml 0.10.4 → 0.10.5

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -0
  3. data/.rubocop_todo.yml +91 -101
  4. data/TODO.cleanups/01-resolve-production-todos.md +65 -0
  5. data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
  6. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
  7. data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
  8. data/TODO.cleanups/05-replace-marshal-load.md +37 -0
  9. data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
  10. data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
  11. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
  12. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
  13. data/TODO.cleanups/10-split-large-files.md +47 -0
  14. data/bin/console +0 -1
  15. data/lib/lutaml/cli/element_identifier.rb +3 -6
  16. data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
  17. data/lib/lutaml/cli/interactive_shell/command_base.rb +28 -0
  18. data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
  19. data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
  20. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
  21. data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
  22. data/lib/lutaml/cli/interactive_shell.rb +116 -802
  23. data/lib/lutaml/cli/uml/build_command.rb +5 -5
  24. data/lib/lutaml/cli/uml/verify_command.rb +0 -1
  25. data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
  26. data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
  27. data/lib/lutaml/formatter/graphviz.rb +1 -2
  28. data/lib/lutaml/qea/database.rb +1 -47
  29. data/lib/lutaml/qea/factory/association_builder.rb +187 -0
  30. data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
  31. data/lib/lutaml/qea/factory/class_transformer.rb +42 -594
  32. data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
  33. data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
  34. data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
  35. data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
  36. data/lib/lutaml/qea/lookup_indexes.rb +54 -0
  37. data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
  38. data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
  39. data/lib/lutaml/uml/has_members.rb +0 -1
  40. data/lib/lutaml/uml/inheritance_walker.rb +91 -0
  41. data/lib/lutaml/uml/model_helpers.rb +129 -0
  42. data/lib/lutaml/uml/node/attribute.rb +3 -1
  43. data/lib/lutaml/uml/node/class_node.rb +3 -3
  44. data/lib/lutaml/uml/operation.rb +2 -0
  45. data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
  46. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +177 -0
  47. data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
  48. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
  49. data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
  50. data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
  51. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +25 -538
  52. data/lib/lutaml/uml_repository/index_builder.rb +3 -271
  53. data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
  54. data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
  55. data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
  56. data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
  57. data/lib/lutaml/uml_repository/package_loader.rb +37 -17
  58. data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
  59. data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
  60. data/lib/lutaml/uml_repository/repository.rb +6 -57
  61. data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
  62. data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
  63. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +41 -878
  64. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
  65. data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
  66. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +119 -0
  67. data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +59 -0
  68. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +250 -0
  69. data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
  70. data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
  71. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +87 -0
  72. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +89 -0
  73. data/lib/lutaml/version.rb +1 -1
  74. data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
  75. data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
  76. data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
  77. data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
  78. data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
  79. data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
  80. data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
  81. data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
  82. data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
  83. data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
  84. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
  85. data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
  86. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
  87. data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
  88. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
  89. data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
  90. data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
  91. data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
  92. data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
  93. data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
  94. data/lib/lutaml/xmi/parsers/xml.rb +7 -120
  95. data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
  96. data/lib/lutaml.rb +0 -1
  97. metadata +48 -21
  98. data/lib/lutaml/cli/commands/base_command.rb +0 -118
  99. data/lib/lutaml/command_line.rb +0 -272
  100. data/lib/lutaml/sysml/allocate.rb +0 -9
  101. data/lib/lutaml/sysml/allocated.rb +0 -9
  102. data/lib/lutaml/sysml/binding_connector.rb +0 -9
  103. data/lib/lutaml/sysml/block.rb +0 -32
  104. data/lib/lutaml/sysml/constraint_block.rb +0 -14
  105. data/lib/lutaml/sysml/copy.rb +0 -8
  106. data/lib/lutaml/sysml/derive_requirement.rb +0 -9
  107. data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
  108. data/lib/lutaml/sysml/refine.rb +0 -9
  109. data/lib/lutaml/sysml/requirement.rb +0 -44
  110. data/lib/lutaml/sysml/requirement_related.rb +0 -9
  111. data/lib/lutaml/sysml/satisfy.rb +0 -9
  112. data/lib/lutaml/sysml/test_case.rb +0 -25
  113. data/lib/lutaml/sysml/trace.rb +0 -9
  114. data/lib/lutaml/sysml/verify.rb +0 -8
  115. data/lib/lutaml/sysml/xmi_file.rb +0 -486
  116. data/lib/lutaml/sysml.rb +0 -11
@@ -0,0 +1,74 @@
1
+ # 07 — Fix Lint offenses (15 + 8 + 4 + 3 + 4 + 1 + 1 = 36 offenses)
2
+
3
+ ## Problem
4
+
5
+ Lint cops catch real bugs and code quality issues. 36 offenses across 7 Lint cops.
6
+
7
+ ## Items
8
+
9
+ ### 1. Lint/DuplicateBranch (8 offenses)
10
+ Identical code in different `case`/`if` branches — likely copy-paste bugs or missing differentiation.
11
+ ```
12
+ lib/lutaml/cli/element_identifier.rb
13
+ lib/lutaml/cli/uml/verify_command.rb
14
+ lib/lutaml/qea/factory/base_transformer.rb
15
+ lib/lutaml/qea/models/ea_datatype.rb
16
+ lib/lutaml/qea/validation/validation_engine.rb
17
+ lib/lutaml/uml_repository/queries/package_query.rb
18
+ lib/lutaml/uml_repository/static_site/configuration.rb
19
+ lib/lutaml/uml_repository/static_site/search_index_builder.rb
20
+ ```
21
+ **Fix**: Merge duplicate branches or differentiate them if they should do different things.
22
+
23
+ ### 2. Lint/IneffectiveAccessModifier (4 offenses)
24
+ `private`/`protected` used inside a `class << self` block — doesn't actually restrict access.
25
+ ```
26
+ lib/lutaml/uml_repository/repository_enhanced.rb
27
+ ```
28
+ **Fix**: Move methods outside the singleton class or use `private_class_method`.
29
+
30
+ ### 3. Lint/ConstantDefinitionInBlock (15 offenses)
31
+ Constants defined inside `it`/`before` blocks leak and can cause test pollution.
32
+ ```
33
+ spec/lutaml/model_transformations/format_registry_spec.rb
34
+ spec/lutaml/model_transformations/parsers/base_parser_spec.rb
35
+ spec/lutaml/model_transformations/transformation_engine_spec.rb
36
+ spec/lutaml/model_transformations_spec.rb
37
+ spec/lutaml/uml_repository/presenters/presenter_factory_spec.rb
38
+ ```
39
+ **Fix**: Extract to top-level constants, use `stub_const`, or define in a shared context.
40
+
41
+ ### 4. Lint/EmptyConditionalBody (3 offenses)
42
+ `if`/`unless` with empty body — likely incomplete logic.
43
+ ```
44
+ spec/integration/qea_xmi_equivalency_spec.rb
45
+ spec/lutaml/qea/verification/equivalence_integration_spec.rb
46
+ ```
47
+ **Fix**: Add the missing body or refactor to use a guard clause.
48
+
49
+ ### 5. Lint/EmptyBlock (4 offenses)
50
+ Empty blocks passed to methods — likely stub placeholders.
51
+ ```
52
+ spec/lutaml/qea/integration/tagged_values_integration_spec.rb
53
+ spec/lutaml/qea/services/database_loader_spec.rb
54
+ spec/lutaml/uml_repository/package_exporter_spec.rb
55
+ spec/lutaml/uml_repository/repository_spec.rb
56
+ ```
57
+ **Fix**: Remove empty blocks or add `{ }` style comment if intentional.
58
+
59
+ ### 6. Lint/BinaryOperatorWithIdenticalOperands (1 offense)
60
+ ```ruby
61
+ # spec/lutaml/qea/lookup_tables_spec.rb
62
+ ```
63
+ **Fix**: Likely a test typo — check if both operands should be the same.
64
+
65
+ ### 7. Lint/MissingSuper (1 offense)
66
+ ```ruby
67
+ # lib/lutaml/uml_repository/lazy_repository.rb
68
+ ```
69
+ **Fix**: Add `super` call or document why it's intentionally omitted.
70
+
71
+ ## Verification
72
+
73
+ - `bundle exec rubocop --only Lint` shows 0 offenses
74
+ - `bundle exec rspec` passes
@@ -0,0 +1,43 @@
1
+ # 08 — Reduce RSpec/MultipleMemoizedHelpers (130) and RSpec/NestedGroups (29)
2
+
3
+ ## Problem
4
+
5
+ 130 examples exceed memoized helper limits (Max: 11) and 29 examples have excessive nesting (Max: 5). Both indicate test organization issues.
6
+
7
+ ## RSpec/MultipleMemoizedHelpers (130)
8
+
9
+ Too many `let`, `let!`, `subject` declarations make test setup hard to follow. Each helper adds cognitive load.
10
+
11
+ ### Approach
12
+ 1. **Group-related helpers into factory methods** — Instead of 10 separate `let` declarations, use one factory:
13
+ ```ruby
14
+ # Instead of:
15
+ let(:name) { "Test" }
16
+ let(:age) { 30 }
17
+ let(:address) { Address.new(city: "London") }
18
+ let(:person) { Person.new(name: name, age: age, address: address) }
19
+
20
+ # Use:
21
+ let(:person) { build(:person) } # via FactoryBot or test factory
22
+ ```
23
+
24
+ 2. **Use `subject!` instead of separate `let` + action** — Combine setup and action.
25
+
26
+ 3. **Extract shared contexts** — Move common setup into `shared_context` blocks.
27
+
28
+ ### Priority files
29
+ These are the worst offenders — check with `bundle exec rubocop --only RSpec/MultipleMemoizedHelpers`.
30
+
31
+ ## RSpec/NestedGroups (29)
32
+
33
+ Deeply nested `describe`/`context` blocks (5+ levels) make tests hard to scan.
34
+
35
+ ### Approach
36
+ 1. **Flatten by using more descriptive top-level descriptions** — Instead of 3 levels of nesting, use a single `describe` with a descriptive name.
37
+ 2. **Extract nested contexts into separate `describe` blocks** — Move `context "when X"` / `context "and Y"` into `context "when X and Y"`.
38
+ 3. **Use shared examples** — Repeated nested structures can become shared examples.
39
+
40
+ ## Verification
41
+
42
+ - `bundle exec rubocop --only RSpec/MultipleMemoizedHelpers,RSpec/NestedGroups` shows reduced counts
43
+ - `bundle exec rspec` passes
@@ -0,0 +1,57 @@
1
+ # 09 — Reduce RSpec/VerifiedDoubles (212) and other RSpec style offenses
2
+
3
+ ## Problem
4
+
5
+ 212 test doubles don't verify their methods exist against real classes. Other RSpec style issues total ~130 additional offenses.
6
+
7
+ ## RSpec/VerifiedDoubles (212 offenses)
8
+
9
+ Currently disabled entirely (`Enabled: false`). Every mock/stub in the test suite uses unverified doubles:
10
+
11
+ ```ruby
12
+ # CURRENT (unverified):
13
+ let(:database) { instance_double("Database") }
14
+ allow(database).to receive(:find_object).with(1).and_return(obj)
15
+
16
+ # SHOULD BE (verified):
17
+ let(:database) { instance_double(Lutaml::Qea::Database) }
18
+ # Now rubocop verifies `find_object` is a real method
19
+ ```
20
+
21
+ ### Approach
22
+ 1. Re-enable the cop in `.rubocop_todo.yml` but with a high initial Max
23
+ 2. Fix in batches per directory:
24
+ - `spec/lutaml/qea/` (highest density)
25
+ - `spec/lutaml/uml_repository/`
26
+ - `spec/lutaml/cli/`
27
+ - rest
28
+ 3. Replace `double(...)` with `instance_double(RealClass, ...)` or `class_double(RealClass, ...)`
29
+ 4. Remove any stubbed methods that don't exist on the real class (they were wrong)
30
+
31
+ ### Risk
32
+ Fixing verified doubles may expose test/implementation drift where tests mock methods that no longer exist. This is valuable — it means the tests were providing false confidence.
33
+
34
+ ## Other RSpec style offenses to fix in same pass
35
+
36
+ | Cop | Count | Fix |
37
+ |-----|-------|-----|
38
+ | RSpec/ContextWording | 25 | Prefix with "when"/"with"/"without" |
39
+ | RSpec/ExpectOutput | 90 | Use `expect { }.to output().to_stdout` |
40
+ | RSpec/InstanceVariable | 48 | Replace `@var` with `let` (except `before(:all)` blocks) |
41
+ | RSpec/RepeatedExample | 16 | Remove or differentiate duplicate test cases |
42
+ | RSpec/LeakyConstantDeclaration | 15 | Use `stub_const` instead of defining constants in tests |
43
+ | RSpec/MessageSpies | 11 | Use `have_received` instead of `receive` with `allow` |
44
+ | RSpec/UnspecifiedException | 6 | Specify exception class in `expect { }.to raise_error(SomeError)` |
45
+ | RSpec/SpecFilePathFormat | 6 | Rename spec files to match their described class |
46
+ | RSpec/IndexedLet | 5 | Rename `thing1`, `thing2` to descriptive names |
47
+ | RSpec/RepeatedExampleGroupDescription | 2 | Rename duplicate describe/context descriptions |
48
+ | RSpec/ExpectActual | 7 | Move expectations out of `let`/`subject` |
49
+ | RSpec/NoExpectationExample | 2 | Add expectations or use `pending` |
50
+ | RSpec/ExpectInLet | 1 | Move expectation out of `let` |
51
+ | RSpec/LeakyLocalVariable | 1 | Use `let` instead of local assignment |
52
+ | RSpec/BeforeAfterAll | 1 | Evaluate if `before(:all)` can be `before(:each)` |
53
+
54
+ ## Verification
55
+
56
+ - `bundle exec rubocop --only RSpec` shows significantly reduced count
57
+ - `bundle exec rspec` passes
@@ -0,0 +1,47 @@
1
+ # 10 — Split large files into focused modules (Layout/LineLength + file organization)
2
+
3
+ ## Problem
4
+
5
+ Several files have grown too large and do too much. The top offenders:
6
+
7
+ | File | Lines | Role |
8
+ |------|-------|------|
9
+ | `lib/lutaml/xmi/parsers/xmi_base.rb` | 1047 | XMI parsing (13 Metrics offenses) |
10
+ | `lib/lutaml/uml_repository/index_builder.rb` | 480 | Index building (8 Metrics offenses) |
11
+ | `lib/lutaml/qea/database.rb` | 477 | QEA database (8 Metrics offenses) |
12
+ | `lib/lutaml/converter/xmi_to_uml.rb` | 474 | XMI→UML conversion (8 Metrics offenses) |
13
+ | `lib/lutaml/command_line.rb` | 272 | CLI option parsing |
14
+ | `lib/lutaml/uml_repository/static_site/data_transformer.rb` | ~950 | Static site generation |
15
+
16
+ Additionally, `Layout/LineLength` is globally disabled (80 offenses).
17
+
18
+ ## Approach
19
+
20
+ ### xmi_base.rb (1047 lines)
21
+ Split into:
22
+ - `xmi_base.rb` — shared module with common methods
23
+ - `connector_parser.rb` — connector-related parsing
24
+ - `diagram_parser.rb` — diagram-related parsing
25
+ - `element_parser.rb` — element-related parsing
26
+
27
+ ### converter/xmi_to_uml.rb (474 lines)
28
+ Split into:
29
+ - `xmi_to_uml.rb` — main converter class
30
+ - Per-element mapping methods extracted into separate modules or classes
31
+
32
+ ### database.rb (477 lines)
33
+ Already well-organized with lazy accessors. Split:
34
+ - `database.rb` — core + public API
35
+ - `lookup_indexes.rb` — the `build_lookup_indexes` method and index logic
36
+
37
+ ### Layout/LineLength (80 offenses)
38
+ Re-enable the cop with a reasonable Max (120) and fix the worst offenders. Long lines often indicate:
39
+ - Deeply nested code → extract to methods
40
+ - Complex string interpolation → use `format` or heredocs
41
+ - Long method chains → use `tap` or break meaningfully
42
+
43
+ ## Verification
44
+
45
+ - No file over ~300 lines in `lib/`
46
+ - `bundle exec rubocop --only Layout/LineLength` with `Max: 120` shows 0 offenses
47
+ - `bundle exec rspec` passes
data/bin/console CHANGED
@@ -6,7 +6,6 @@ require "lutaml"
6
6
  require "lutaml/express"
7
7
  require "lutaml/uml"
8
8
  require "lutaml/xmi"
9
- require "lutaml/sysml"
10
9
 
11
10
  # You can add fixtures and/or initialization code here to make experimenting
12
11
  # with your gem easier. You can also use a different console, if you like.
@@ -112,12 +112,9 @@ module Lutaml
112
112
  # Lowercase start - could be attribute or diagram name
113
113
  :diagram
114
114
  end
115
- when 1
116
- # One separator - likely Package::Class
117
- :class
118
- when 2..Float::INFINITY
119
- # Multiple separators - likely fully qualified class name
120
- # e.g., ModelRoot::Package::Class
115
+ when 1..Float::INFINITY
116
+ # One or more separators - likely qualified class name
117
+ # e.g., Package::Class or ModelRoot::Package::Class
121
118
  :class
122
119
  else
123
120
  # Default
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command_base"
4
+ require_relative "../enhanced_formatter"
5
+
6
+ module Lutaml
7
+ module Cli
8
+ class InteractiveShell
9
+ class BookmarkCommands < CommandBase
10
+ def cmd_bookmark(args)
11
+ return bookmark_list if args.empty?
12
+
13
+ subcommand = args[0].downcase
14
+
15
+ case subcommand
16
+ when "add"
17
+ bookmark_add(args[1])
18
+ when "list"
19
+ bookmark_list
20
+ when "go"
21
+ bookmark_go(args[1])
22
+ when "rm", "remove"
23
+ bookmark_remove(args[1])
24
+ else
25
+ bookmark_go(subcommand)
26
+ end
27
+ end
28
+
29
+ def bookmark_add(name)
30
+ if name.nil? || name.empty?
31
+ puts OutputFormatter.warning("Usage: bookmark add NAME")
32
+ return
33
+ end
34
+
35
+ target = last_results&.first || current_path
36
+ bookmarks[name] = target
37
+ puts OutputFormatter.success("Bookmark '#{name}' added: #{target}")
38
+ end
39
+
40
+ def bookmark_list
41
+ if bookmarks.empty?
42
+ puts "No bookmarks"
43
+ else
44
+ puts OutputFormatter.colorize("Bookmarks:", :cyan)
45
+ bookmarks.each do |name, target|
46
+ icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:favorite]} " : ""
47
+ puts " #{icon}#{name} → #{target}"
48
+ end
49
+ end
50
+ end
51
+
52
+ def bookmark_go(name)
53
+ unless bookmarks.key?(name)
54
+ puts OutputFormatter.error("Bookmark not found: #{name}")
55
+ return
56
+ end
57
+
58
+ target = bookmarks[name]
59
+ if repository.find_package(target)
60
+ push_path_history
61
+ self.current_path = target
62
+ puts "Changed to: #{target}"
63
+ else
64
+ puts OutputFormatter.warning(
65
+ "Bookmark target no longer exists: #{target}",
66
+ )
67
+ end
68
+ end
69
+
70
+ def bookmark_remove(name)
71
+ if bookmarks.delete(name)
72
+ puts OutputFormatter.success("Bookmark '#{name}' removed")
73
+ else
74
+ puts OutputFormatter.error("Bookmark not found: #{name}")
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def push_path_history
81
+ return if path_history.last == current_path
82
+
83
+ path_history << current_path
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Cli
5
+ class InteractiveShell
6
+ class CommandBase
7
+ attr_reader :shell
8
+
9
+ def initialize(shell)
10
+ @shell = shell
11
+ end
12
+
13
+ def repository = shell.repository
14
+ def current_path = shell.current_path
15
+ def current_path=(path)
16
+ shell.instance_variable_set(:@current_path, path)
17
+ end
18
+ def config = shell.config
19
+ def bookmarks = shell.bookmarks
20
+ def last_results = shell.last_results
21
+ def last_results=(results)
22
+ shell.instance_variable_set(:@last_results, results)
23
+ end
24
+ def path_history = shell.path_history
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command_base"
4
+
5
+ module Lutaml
6
+ module Cli
7
+ class InteractiveShell
8
+ class ExportHandler < CommandBase
9
+ def cmd_export(args)
10
+ if last_results.nil? || last_results.empty?
11
+ puts OutputFormatter.warning("No results to export")
12
+ return
13
+ end
14
+
15
+ if args.size < 3 || args[0] != "last"
16
+ puts OutputFormatter.warning("Usage: export last FORMAT FILE")
17
+ return
18
+ end
19
+
20
+ format = args[1].downcase
21
+ file_path = args[2]
22
+
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)
30
+ else
31
+ puts OutputFormatter.error("Unsupported format: #{format}")
32
+ end
33
+ end
34
+
35
+ def export_csv(file_path)
36
+ require "csv"
37
+
38
+ CSV.open(file_path, "w") do |csv|
39
+ csv << ["Qualified Name"]
40
+ last_results.each do |qname|
41
+ csv << [qname]
42
+ end
43
+ end
44
+
45
+ puts OutputFormatter.success("Exported #{last_results.size} " \
46
+ "results to #{file_path}")
47
+ end
48
+
49
+ def export_json(file_path)
50
+ require "json"
51
+
52
+ File.write(file_path, JSON.pretty_generate(last_results))
53
+ puts OutputFormatter.success("Exported #{last_results.size} " \
54
+ "results to #{file_path}")
55
+ end
56
+
57
+ def export_yaml(file_path)
58
+ require "yaml"
59
+
60
+ File.write(file_path, last_results.to_yaml)
61
+ puts OutputFormatter.success("Exported #{last_results.size} " \
62
+ "results to #{file_path}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command_base"
4
+ require_relative "../enhanced_formatter"
5
+
6
+ module Lutaml
7
+ module Cli
8
+ class InteractiveShell
9
+ class HelpDisplay < CommandBase
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 ""
23
+
24
+ stats = repository.statistics
25
+ puts "Repository loaded:"
26
+ puts " #{stats[:total_packages]} packages, " \
27
+ "#{stats[:total_classes]} classes"
28
+ puts ""
29
+ end
30
+
31
+ def cmd_help(args)
32
+ if args.empty?
33
+ display_general_help
34
+ else
35
+ display_command_help(args[0])
36
+ end
37
+ end
38
+
39
+ def cmd_history(_args)
40
+ history = Readline::HISTORY.to_a.last(20)
41
+ history.each_with_index do |line, i|
42
+ puts "#{i + 1}. #{line}"
43
+ end
44
+ end
45
+
46
+ def cmd_clear(_args)
47
+ print "\e[2J\e[H"
48
+ end
49
+
50
+ def cmd_config(_args)
51
+ puts OutputFormatter.colorize("Current Configuration:", :cyan)
52
+ config.each do |key, value|
53
+ puts " #{key}: #{value}"
54
+ end
55
+ end
56
+
57
+ def cmd_stats(_args)
58
+ stats = repository.statistics
59
+
60
+ if config[:icons]
61
+ puts EnhancedFormatter.format_stats_enhanced(stats)
62
+ else
63
+ puts OutputFormatter.format_stats(stats, detailed: false)
64
+ end
65
+ end
66
+
67
+ def display_general_help
68
+ puts OutputFormatter.colorize("Available Commands:", :cyan)
69
+ puts ""
70
+
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 ""
80
+
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 ""
89
+
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 ""
97
+
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"
105
+ end
106
+
107
+ def display_command_help(command)
108
+ puts "Help for '#{command}' not yet implemented"
109
+ puts "Use 'help' for general help"
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command_base"
4
+ require_relative "../enhanced_formatter"
5
+
6
+ module Lutaml
7
+ module Cli
8
+ class InteractiveShell
9
+ class NavigationCommands < CommandBase
10
+ def cmd_cd(args)
11
+ if args.empty?
12
+ puts OutputFormatter.warning("Usage: cd PATH")
13
+ return
14
+ end
15
+
16
+ path = resolve_path(args[0])
17
+ pkg = repository.find_package(path)
18
+
19
+ if pkg
20
+ push_path_history
21
+ self.current_path = path
22
+ puts "Changed to: #{path}"
23
+ else
24
+ puts OutputFormatter.error("Package not found: #{path}")
25
+ end
26
+ end
27
+
28
+ def cmd_pwd(_args)
29
+ puts current_path
30
+ end
31
+
32
+ def cmd_ls(args)
33
+ path = args.empty? ? current_path : resolve_path(args[0])
34
+ recursive = args.include?("-r") || args.include?("--recursive")
35
+
36
+ packages = repository.list_packages(path, recursive: recursive)
37
+
38
+ if packages.empty?
39
+ puts OutputFormatter.warning("No packages found at #{path}")
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)"
47
+ end
48
+ end
49
+
50
+ def cmd_tree(args)
51
+ 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
57
+
58
+ tree_data = repository.package_tree(path, max_depth: max_depth)
59
+
60
+ unless tree_data
61
+ puts OutputFormatter.error("Package not found: #{path}")
62
+ return
63
+ end
64
+
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
70
+ end
71
+
72
+ def cmd_up(_args)
73
+ if current_path == "ModelRoot"
74
+ puts OutputFormatter.warning("Already at root")
75
+ return
76
+ end
77
+
78
+ parts = current_path.split("::")
79
+ parts.pop
80
+ new_path = parts.empty? ? "ModelRoot" : parts.join("::")
81
+
82
+ push_path_history
83
+ self.current_path = new_path
84
+ puts "Changed to: #{current_path}"
85
+ end
86
+
87
+ def cmd_root(_args)
88
+ if current_path == "ModelRoot"
89
+ puts "Already at root"
90
+ else
91
+ push_path_history
92
+ self.current_path = "ModelRoot"
93
+ puts "Changed to: ModelRoot"
94
+ end
95
+ end
96
+
97
+ def cmd_back(_args)
98
+ if path_history.size > 1
99
+ path_history.pop
100
+ self.current_path = path_history.last
101
+ puts "Changed to: #{current_path}"
102
+ else
103
+ puts OutputFormatter.warning("No previous location")
104
+ end
105
+ end
106
+
107
+ def resolve_path(path)
108
+ return path if path.start_with?("ModelRoot")
109
+ return current_path if path == "."
110
+ return "ModelRoot" if path == "/"
111
+
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..]}"
120
+ else
121
+ current_path == "ModelRoot" ? path : "#{current_path}::#{path}"
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def push_path_history
128
+ return if path_history.last == current_path
129
+
130
+ path_history << current_path
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end