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
@@ -0,0 +1,54 @@
1
+ # 03 — Reduce RSpec/MultipleExpectations (946 offenses)
2
+
3
+ ## Problem
4
+
5
+ 946 test examples have more than 1 expectation. This is the single largest rubocop offense. It makes tests brittle (failures stop at the first failed expectation, masking other issues) and harder to understand what's being verified.
6
+
7
+ ## Approach
8
+
9
+ Split multi-expectation examples into focused single-expectation examples:
10
+
11
+ ```ruby
12
+ # BEFORE:
13
+ it "validates the widget" do
14
+ expect(widget.name).to eq("Test")
15
+ expect(widget.size).to eq(10)
16
+ expect(widget.active).to be true
17
+ end
18
+
19
+ # AFTER:
20
+ it { expect(widget.name).to eq("Test") }
21
+ it { expect(widget.size).to eq(10) }
22
+ it { expect(widget.active).to be true }
23
+ ```
24
+
25
+ For cases where multiple expectations are genuinely related (e.g., checking a hash structure), use compound matchers:
26
+
27
+ ```ruby
28
+ expect(result).to include("name" => "Test", "size" => 10, "active" => true)
29
+ ```
30
+
31
+ Or aggregate failures with `aggregate_failures` when the expectations are about the same concern:
32
+
33
+ ```ruby
34
+ it "has correct address fields" do
35
+ aggregate_failures do
36
+ expect(address.street).to eq("Main St")
37
+ expect(address.city).to eq("London")
38
+ end
39
+ end
40
+ ```
41
+
42
+ ## Priority files (highest offense density)
43
+
44
+ Run `bundle exec rubocop --only RSpec/MultipleExpectations` to see the full list. Focus on:
45
+ 1. `spec/lutaml/qea/verification/comprehensive_equivalence_spec.rb`
46
+ 2. `spec/lutaml/qea/verification/equivalence_integration_spec.rb`
47
+ 3. `spec/lutaml/cli/` command specs
48
+ 4. `spec/lutaml/uml_repository/` specs
49
+
50
+ ## Verification
51
+
52
+ - `bundle exec rubocop --only RSpec/MultipleExpectations` shows reduced count
53
+ - `bundle exec rspec` passes
54
+ - Reduce Max from 26 toward 5 (default)
@@ -0,0 +1,45 @@
1
+ # 04 — Reduce RSpec/ExampleLength (657 offenses)
2
+
3
+ ## Problem
4
+
5
+ 657 test examples exceed the default 5-line limit (current Max: 108). Long examples are hard to read, hide intent, and often combine setup + action + multiple assertions.
6
+
7
+ ## Approach
8
+
9
+ Common patterns to fix:
10
+
11
+ 1. **Extract test data into `let`/`let!`** — Move setup out of `it` blocks:
12
+ ```ruby
13
+ # BEFORE:
14
+ it "parses correctly" do
15
+ result = described_class.parse(fixture_content)
16
+ expect(result.name).to eq("Test")
17
+ end
18
+
19
+ # AFTER:
20
+ let(:result) { described_class.parse(fixture_content) }
21
+ it { expect(result.name).to eq("Test") }
22
+ ```
23
+
24
+ 2. **Extract shared setup into `before` blocks** — If multiple examples repeat the same setup.
25
+
26
+ 3. **Use subject with one-liner syntax** — Where the subject is the tested value:
27
+ ```ruby
28
+ subject { described_class.new(attrs) }
29
+ it { is_expected.to be_valid }
30
+ ```
31
+
32
+ 4. **Extract helper methods** — Complex assertions that span many lines can become custom matchers or helper methods in `spec/support/`.
33
+
34
+ ## Priority
35
+
36
+ Focus on the longest examples first — run `bundle exec rubocop --only RSpec/ExampleLength` and sort by severity. Files with the worst examples:
37
+ - `spec/lutaml/qea/` verification and factory specs
38
+ - `spec/lutaml/uml_repository/` static site specs
39
+ - `spec/lutaml/cli/` command specs
40
+
41
+ ## Verification
42
+
43
+ - `bundle exec rubocop --only RSpec/ExampleLength` shows reduced count
44
+ - Reduce Max from 108 toward 10
45
+ - `bundle exec rspec` passes
@@ -0,0 +1,37 @@
1
+ # 05 — Replace Marshal.load with safe deserialization (4 offenses)
2
+
3
+ ## Problem
4
+
5
+ `Marshal.load` is flagged by `Security/MarshalLoad` because deserializing untrusted data can lead to remote code execution. The gem uses it in the `.lur` package format (a ZIP file containing serialized Ruby objects).
6
+
7
+ ## Affected files
8
+
9
+ ```ruby
10
+ # lib/lutaml/uml_repository/package_loader.rb:194
11
+ Marshal.load(entry.get_input_stream.read)
12
+
13
+ # lib/lutaml/uml_repository/package_loader.rb:210
14
+ Marshal.load(entry.get_input_stream.read)
15
+
16
+ # spec/lutaml/uml_repository/package_exporter_spec.rb:141
17
+ expect { Marshal.load(serialized) }.not_to raise_error
18
+
19
+ # spec/lutaml/uml_repository/package_exporter_spec.rb:171
20
+ expect { Marshal.load(serialized) }.not_to raise_error
21
+ ```
22
+
23
+ ## Approach
24
+
25
+ **Option A: Switch to JSON serialization** — Replace `Marshal.dump`/`Marshal.load` with `JSON.generate`/`JSON.parse` in `PackageExporter` and `PackageLoader`. This is safe but requires models to be JSON-serializable.
26
+
27
+ **Option B: Use YAML with permitted classes** — `YAML.safe_load` with an explicit allowlist of classes. More flexible but still requires maintaining the class list.
28
+
29
+ **Option C: Keep Marshal but document the trust boundary** — If `.lur` files are only ever created by the gem itself (trusted), the risk is mitigated. Add a clear comment at the load site and suppress the cop with justification.
30
+
31
+ **Recommended**: Option A if feasible (cleanest), Option C as a minimal fix.
32
+
33
+ ## Verification
34
+
35
+ - `bundle exec rubocop --only Security/MarshalLoad` shows 0 offenses
36
+ - `.lur` package round-trip (export → load) works correctly
37
+ - `bundle exec rspec` passes
@@ -0,0 +1,41 @@
1
+ # 06 — Replace eval in tests with StringIO redirection (3 Security/Eval + 2 Style/EvalWithLocation + 2 Style/DocumentDynamicEvalDefinition)
2
+
3
+ ## Problem
4
+
5
+ `spec/lutaml/cli/uml/search_command_spec.rb` uses `eval` to redirect `$stdout`/`$stderr` for capturing output. This triggers 7 rubocop offenses across Security and Style cops.
6
+
7
+ ## Affected code
8
+
9
+ ```ruby
10
+ # spec/lutaml/cli/uml/search_command_spec.rb:61-65
11
+ old_stream = eval(stream_var.to_s)
12
+ eval("#{stream_var} = StringIO.new")
13
+ # ...
14
+ eval("#{stream_var} = old_stream") if defined?(old_stream) && old_stream
15
+ ```
16
+
17
+ ## Fix
18
+
19
+ Replace with a standard output capture pattern:
20
+
21
+ ```ruby
22
+ # Use RSpec's built-in output matcher:
23
+ expect { command.run }.to output(/expected text/).to_stdout
24
+
25
+ # Or use a capture helper:
26
+ def capture_output
27
+ old_stdout = $stdout
28
+ $stdout = StringIO.new
29
+ yield
30
+ $stdout.string
31
+ ensure
32
+ $stdout = old_stdout
33
+ end
34
+ ```
35
+
36
+ No `eval` needed — just use `$stdout` directly instead of dynamically resolving the variable name.
37
+
38
+ ## Verification
39
+
40
+ - `bundle exec rubocop --only Security/Eval,Style/EvalWithLocation,Style/DocumentDynamicEvalDefinition` shows 0 offenses
41
+ - `bundle exec rspec spec/lutaml/cli/uml/search_command_spec.rb` passes
@@ -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.
data/exe/lutaml CHANGED
@@ -5,6 +5,7 @@ require "pathname"
5
5
  bin_file = Pathname.new(__FILE__).realpath
6
6
  $:.unshift File.expand_path("../../lib", bin_file)
7
7
 
8
+ require "lutaml"
8
9
  require "lutaml/cli"
9
10
 
10
11
  Lutaml::CLI.start(ARGV)
@@ -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,32 @@
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
+
16
+ def current_path=(path)
17
+ shell.instance_variable_set(:@current_path, path)
18
+ end
19
+
20
+ def config = shell.config
21
+ def bookmarks = shell.bookmarks
22
+ def last_results = shell.last_results
23
+
24
+ def last_results=(results)
25
+ shell.instance_variable_set(:@last_results, results)
26
+ end
27
+
28
+ def path_history = shell.path_history
29
+ end
30
+ end
31
+ end
32
+ 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