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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +91 -101
- data/TODO.cleanups/01-resolve-production-todos.md +65 -0
- data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
- data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
- data/TODO.cleanups/05-replace-marshal-load.md +37 -0
- data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
- data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
- data/TODO.cleanups/10-split-large-files.md +47 -0
- data/bin/console +0 -1
- data/lib/lutaml/cli/element_identifier.rb +3 -6
- data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
- data/lib/lutaml/cli/interactive_shell/command_base.rb +28 -0
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
- data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
- data/lib/lutaml/cli/interactive_shell.rb +116 -802
- data/lib/lutaml/cli/uml/build_command.rb +5 -5
- data/lib/lutaml/cli/uml/verify_command.rb +0 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
- data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
- data/lib/lutaml/formatter/graphviz.rb +1 -2
- data/lib/lutaml/qea/database.rb +1 -47
- data/lib/lutaml/qea/factory/association_builder.rb +187 -0
- data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
- data/lib/lutaml/qea/factory/class_transformer.rb +42 -594
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
- data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
- data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
- data/lib/lutaml/qea/lookup_indexes.rb +54 -0
- data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
- data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
- data/lib/lutaml/uml/has_members.rb +0 -1
- data/lib/lutaml/uml/inheritance_walker.rb +91 -0
- data/lib/lutaml/uml/model_helpers.rb +129 -0
- data/lib/lutaml/uml/node/attribute.rb +3 -1
- data/lib/lutaml/uml/node/class_node.rb +3 -3
- data/lib/lutaml/uml/operation.rb +2 -0
- data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +177 -0
- data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
- data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +25 -538
- data/lib/lutaml/uml_repository/index_builder.rb +3 -271
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
- data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
- data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
- data/lib/lutaml/uml_repository/package_loader.rb +37 -17
- data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
- data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
- data/lib/lutaml/uml_repository/repository.rb +6 -57
- data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
- data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +41 -878
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
- data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +119 -0
- data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +59 -0
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +250 -0
- data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
- data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +87 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +89 -0
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
- data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
- data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
- data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
- data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
- data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
- data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
- data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
- data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
- data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
- data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
- data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
- data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
- data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
- data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
- data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
- data/lib/lutaml/xmi/parsers/xml.rb +7 -120
- data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
- data/lib/lutaml.rb +0 -1
- metadata +48 -21
- data/lib/lutaml/cli/commands/base_command.rb +0 -118
- data/lib/lutaml/command_line.rb +0 -272
- data/lib/lutaml/sysml/allocate.rb +0 -9
- data/lib/lutaml/sysml/allocated.rb +0 -9
- data/lib/lutaml/sysml/binding_connector.rb +0 -9
- data/lib/lutaml/sysml/block.rb +0 -32
- data/lib/lutaml/sysml/constraint_block.rb +0 -14
- data/lib/lutaml/sysml/copy.rb +0 -8
- data/lib/lutaml/sysml/derive_requirement.rb +0 -9
- data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
- data/lib/lutaml/sysml/refine.rb +0 -9
- data/lib/lutaml/sysml/requirement.rb +0 -44
- data/lib/lutaml/sysml/requirement_related.rb +0 -9
- data/lib/lutaml/sysml/satisfy.rb +0 -9
- data/lib/lutaml/sysml/test_case.rb +0 -25
- data/lib/lutaml/sysml/trace.rb +0 -9
- data/lib/lutaml/sysml/verify.rb +0 -8
- data/lib/lutaml/sysml/xmi_file.rb +0 -486
- 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
|
|
117
|
-
|
|
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
|