appydave-tools 0.74.0 → 0.74.1
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/CHANGELOG.md +7 -0
- data/bin/dam +16 -16
- data/bin/zsh_history.rb +2 -14
- data/docs/architecture/cli/cli-pattern-comparison.md +147 -17
- data/docs/architecture/cli/cli-patterns.md +311 -18
- data/docs/architecture/cli/pattern-4-delegated-cli.md +1058 -0
- data/lib/appydave/tools/dam/project_listing.rb +2 -12
- data/lib/appydave/tools/jump/cli.rb +0 -8
- data/lib/appydave/tools/jump/commands/generate.rb +0 -4
- data/lib/appydave/tools/jump/formatters/table_formatter.rb +0 -2
- data/lib/appydave/tools/jump/search.rb +0 -2
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +3 -2
- data/package.json +1 -1
- metadata +3 -2
|
@@ -6,10 +6,11 @@ This guide documents the established CLI architecture patterns used in appydave-
|
|
|
6
6
|
|
|
7
7
|
- [Overview](#overview)
|
|
8
8
|
- [Philosophy](#philosophy)
|
|
9
|
-
- [The
|
|
9
|
+
- [The Four Patterns](#the-four-patterns)
|
|
10
10
|
- [Pattern 1: Single-Command Tools](#pattern-1-single-command-tools)
|
|
11
11
|
- [Pattern 2: Multi-Command with Inline Routing](#pattern-2-multi-command-with-inline-routing)
|
|
12
12
|
- [Pattern 3: Multi-Command with BaseAction](#pattern-3-multi-command-with-baseaction)
|
|
13
|
+
- [Pattern 4: Delegated CLI Class](#pattern-4-delegated-cli-class)
|
|
13
14
|
- [Decision Tree](#decision-tree)
|
|
14
15
|
- [Directory Structure](#directory-structure)
|
|
15
16
|
- [Best Practices](#best-practices)
|
|
@@ -28,7 +29,7 @@ AppyDave Tools follows a **consolidated toolkit philosophy** - multiple independ
|
|
|
28
29
|
- **Maintainable**: Clear separation of concerns between CLI and business logic
|
|
29
30
|
- **Testable**: Business logic separated from CLI interface
|
|
30
31
|
|
|
31
|
-
The architecture supports
|
|
32
|
+
The architecture supports four distinct patterns, each suited for different tool complexity levels.
|
|
32
33
|
|
|
33
34
|
---
|
|
34
35
|
|
|
@@ -692,6 +693,235 @@ YouTubeVideoManagerCLI.new.run
|
|
|
692
693
|
|
|
693
694
|
---
|
|
694
695
|
|
|
696
|
+
### Pattern 4: Delegated CLI Class
|
|
697
|
+
|
|
698
|
+
**Use when:** The tool has 10+ commands and you want to test CLI behavior.
|
|
699
|
+
|
|
700
|
+
**Example:** `jump` - Manage development folder locations
|
|
701
|
+
|
|
702
|
+
#### Structure
|
|
703
|
+
|
|
704
|
+
```
|
|
705
|
+
bin/
|
|
706
|
+
├── jump.rb # Thin wrapper (30 lines)
|
|
707
|
+
lib/appydave/tools/
|
|
708
|
+
├── jump/
|
|
709
|
+
│ ├── cli.rb # Full CLI implementation (400+ lines)
|
|
710
|
+
│ ├── config.rb # Configuration
|
|
711
|
+
│ ├── search.rb # Business logic
|
|
712
|
+
│ ├── crud.rb # Business logic
|
|
713
|
+
│ └── formatters/ # Output formatting
|
|
714
|
+
│ ├── table_formatter.rb
|
|
715
|
+
│ ├── json_formatter.rb
|
|
716
|
+
│ └── paths_formatter.rb
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
#### Implementation Pattern
|
|
720
|
+
|
|
721
|
+
**bin/jump.rb:**
|
|
722
|
+
```ruby
|
|
723
|
+
#!/usr/bin/env ruby
|
|
724
|
+
# frozen_string_literal: true
|
|
725
|
+
|
|
726
|
+
# Jump Location Tool - Manage development folder locations
|
|
727
|
+
#
|
|
728
|
+
# Usage:
|
|
729
|
+
# jump search <terms> # Fuzzy search locations
|
|
730
|
+
# jump get <key> # Get by exact key
|
|
731
|
+
# jump list # List all locations
|
|
732
|
+
# jump add --key <key> # Add new location
|
|
733
|
+
|
|
734
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
735
|
+
|
|
736
|
+
require 'appydave/tools'
|
|
737
|
+
|
|
738
|
+
cli = Appydave::Tools::Jump::CLI.new
|
|
739
|
+
exit_code = cli.run(ARGV)
|
|
740
|
+
exit(exit_code)
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
**lib/appydave/tools/jump/cli.rb:**
|
|
744
|
+
```ruby
|
|
745
|
+
# frozen_string_literal: true
|
|
746
|
+
|
|
747
|
+
module Appydave
|
|
748
|
+
module Tools
|
|
749
|
+
module Jump
|
|
750
|
+
# CLI provides the command-line interface for the Jump tool
|
|
751
|
+
#
|
|
752
|
+
# Uses the Delegated CLI pattern for 10+ commands with
|
|
753
|
+
# dependency injection and comprehensive testing.
|
|
754
|
+
class CLI
|
|
755
|
+
EXIT_SUCCESS = 0
|
|
756
|
+
EXIT_NOT_FOUND = 1
|
|
757
|
+
EXIT_INVALID_INPUT = 2
|
|
758
|
+
EXIT_CONFIG_ERROR = 3
|
|
759
|
+
EXIT_PATH_NOT_FOUND = 4
|
|
760
|
+
|
|
761
|
+
attr_reader :config, :path_validator, :output
|
|
762
|
+
|
|
763
|
+
def initialize(config: nil, path_validator: nil, output: $stdout)
|
|
764
|
+
@path_validator = path_validator || PathValidator.new
|
|
765
|
+
@output = output
|
|
766
|
+
@config = config
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def run(args = ARGV)
|
|
770
|
+
command = args.shift
|
|
771
|
+
|
|
772
|
+
case command
|
|
773
|
+
when nil, '', '--help', '-h'
|
|
774
|
+
show_main_help
|
|
775
|
+
EXIT_SUCCESS
|
|
776
|
+
when '--version', '-v'
|
|
777
|
+
show_version
|
|
778
|
+
EXIT_SUCCESS
|
|
779
|
+
when 'help'
|
|
780
|
+
show_help(args)
|
|
781
|
+
EXIT_SUCCESS
|
|
782
|
+
when 'search'
|
|
783
|
+
run_search(args)
|
|
784
|
+
when 'get'
|
|
785
|
+
run_get(args)
|
|
786
|
+
when 'list'
|
|
787
|
+
run_list(args)
|
|
788
|
+
when 'add'
|
|
789
|
+
run_add(args)
|
|
790
|
+
when 'update'
|
|
791
|
+
run_update(args)
|
|
792
|
+
when 'remove'
|
|
793
|
+
run_remove(args)
|
|
794
|
+
else
|
|
795
|
+
output.puts "Unknown command: #{command}"
|
|
796
|
+
EXIT_INVALID_INPUT
|
|
797
|
+
end
|
|
798
|
+
rescue StandardError => e
|
|
799
|
+
output.puts "Error: #{e.message}"
|
|
800
|
+
EXIT_CONFIG_ERROR
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
private
|
|
804
|
+
|
|
805
|
+
# Command implementations
|
|
806
|
+
|
|
807
|
+
def run_search(args)
|
|
808
|
+
query = args.join(' ')
|
|
809
|
+
search = Search.new(load_config)
|
|
810
|
+
result = search.search(query)
|
|
811
|
+
|
|
812
|
+
format_output(result)
|
|
813
|
+
exit_code_for(result)
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def run_add(args)
|
|
817
|
+
options = {}
|
|
818
|
+
OptionParser.new do |opts|
|
|
819
|
+
opts.on('--key KEY') { |v| options[:key] = v }
|
|
820
|
+
opts.on('--path PATH') { |v| options[:path] = v }
|
|
821
|
+
end.parse!(args)
|
|
822
|
+
|
|
823
|
+
crud = Crud.new(load_config)
|
|
824
|
+
result = crud.add(options)
|
|
825
|
+
|
|
826
|
+
if result[:success]
|
|
827
|
+
output.puts "✅ Added: #{options[:key]}"
|
|
828
|
+
EXIT_SUCCESS
|
|
829
|
+
else
|
|
830
|
+
output.puts "❌ Error: #{result[:message]}"
|
|
831
|
+
exit_code_for(result)
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
# ... more command methods ...
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
end
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
**lib/appydave/tools/jump/search.rb:**
|
|
843
|
+
```ruby
|
|
844
|
+
# frozen_string_literal: true
|
|
845
|
+
|
|
846
|
+
module Appydave
|
|
847
|
+
module Tools
|
|
848
|
+
module Jump
|
|
849
|
+
# Search provides fuzzy search and exact lookup
|
|
850
|
+
class Search
|
|
851
|
+
def initialize(config)
|
|
852
|
+
@config = config
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def search(query)
|
|
856
|
+
# Business logic - no CLI dependencies
|
|
857
|
+
locations = @config.locations
|
|
858
|
+
matches = locations.select { |loc| match_query?(loc, query) }
|
|
859
|
+
|
|
860
|
+
{
|
|
861
|
+
success: !matches.empty?,
|
|
862
|
+
code: matches.empty? ? 'NOT_FOUND' : nil,
|
|
863
|
+
data: matches
|
|
864
|
+
}
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
private
|
|
868
|
+
|
|
869
|
+
def match_query?(location, query)
|
|
870
|
+
# Fuzzy matching logic
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
**Characteristics:**
|
|
879
|
+
- ✅ CLI is a class in lib/ (fully testable via RSpec)
|
|
880
|
+
- ✅ Ultra-thin bin/ wrapper (25-40 lines)
|
|
881
|
+
- ✅ Dependency injection (config, output, validators)
|
|
882
|
+
- ✅ Exit codes (0-4 for different error types)
|
|
883
|
+
- ✅ Case/when command dispatch (scales to 10+ commands)
|
|
884
|
+
- ✅ Professional-grade tool architecture
|
|
885
|
+
- ❌ More setup than Pattern 2/3
|
|
886
|
+
- ❌ Need to understand dependency injection
|
|
887
|
+
|
|
888
|
+
**Testing:**
|
|
889
|
+
```ruby
|
|
890
|
+
# spec/appydave/tools/jump/cli_spec.rb
|
|
891
|
+
RSpec.describe Appydave::Tools::Jump::CLI do
|
|
892
|
+
subject(:cli) { described_class.new(config: mock_config, output: output) }
|
|
893
|
+
|
|
894
|
+
let(:output) { StringIO.new }
|
|
895
|
+
let(:mock_config) { instance_double(Config, locations: mock_locations) }
|
|
896
|
+
|
|
897
|
+
describe '#run' do
|
|
898
|
+
it 'shows help and returns success exit code' do
|
|
899
|
+
exit_code = cli.run(['--help'])
|
|
900
|
+
|
|
901
|
+
expect(output.string).to include('Usage: jump')
|
|
902
|
+
expect(exit_code).to eq(described_class::EXIT_SUCCESS)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
it 'searches for locations' do
|
|
906
|
+
exit_code = cli.run(['search', 'proj'])
|
|
907
|
+
|
|
908
|
+
expect(output.string).to include('proj-1')
|
|
909
|
+
expect(exit_code).to eq(described_class::EXIT_SUCCESS)
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
it 'returns not found exit code when no matches' do
|
|
913
|
+
exit_code = cli.run(['search', 'nonexistent'])
|
|
914
|
+
|
|
915
|
+
expect(exit_code).to eq(described_class::EXIT_NOT_FOUND)
|
|
916
|
+
end
|
|
917
|
+
end
|
|
918
|
+
end
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
**See full guide:** [Pattern 4: Delegated CLI Class](./pattern-4-delegated-cli.md)
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
695
925
|
## Decision Tree
|
|
696
926
|
|
|
697
927
|
Use this flowchart to choose the right pattern:
|
|
@@ -707,20 +937,72 @@ Start: How many distinct operations does your tool perform?
|
|
|
707
937
|
│ └─ Pattern 2: Multi-Command with Inline Routing
|
|
708
938
|
│ Examples: subtitle_processor, configuration
|
|
709
939
|
│
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
940
|
+
├─ 6-9 operations OR commands share validation patterns
|
|
941
|
+
│ └─ Pattern 3: Multi-Command with BaseAction
|
|
942
|
+
│ Examples: youtube_manager
|
|
943
|
+
│ Choose Pattern 3 when:
|
|
944
|
+
│ • Commands share validation/execution patterns
|
|
945
|
+
│ • Want template method enforcement
|
|
946
|
+
│ • Don't need CLI testing
|
|
947
|
+
│
|
|
948
|
+
└─ 10+ operations OR need testable CLI OR complex tool
|
|
949
|
+
└─ Pattern 4: Delegated CLI Class
|
|
950
|
+
Example: jump
|
|
951
|
+
Choose Pattern 4 when:
|
|
952
|
+
• Want to test CLI behavior (exit codes, output)
|
|
953
|
+
• Need dependency injection for testing
|
|
954
|
+
• Building professional-grade tool
|
|
955
|
+
• CLI complexity > 300 lines
|
|
713
956
|
```
|
|
714
957
|
|
|
958
|
+
### Pattern Selection Flowchart
|
|
959
|
+
|
|
960
|
+
```
|
|
961
|
+
How many commands?
|
|
962
|
+
│
|
|
963
|
+
├─ 1 command
|
|
964
|
+
│ └─ Pattern 1 (single-command)
|
|
965
|
+
│
|
|
966
|
+
├─ 2-5 commands
|
|
967
|
+
│ └─ Pattern 2 (inline routing)
|
|
968
|
+
│
|
|
969
|
+
├─ 6-9 commands
|
|
970
|
+
│ ├─ Need CLI testing? ──Yes──> Pattern 4 (delegated CLI)
|
|
971
|
+
│ └─ Share validation? ──Yes──> Pattern 3 (BaseAction)
|
|
972
|
+
│
|
|
973
|
+
└─ 10+ commands
|
|
974
|
+
├─ Need CLI testing? ──Yes──> Pattern 4 (delegated CLI)
|
|
975
|
+
├─ Share validation? ──Yes──> Pattern 3 (BaseAction)
|
|
976
|
+
└─ Default ──────────────────> Pattern 4 (delegated CLI)
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
### Quick Decision Guide
|
|
980
|
+
|
|
981
|
+
| If you need... | Use Pattern |
|
|
982
|
+
|----------------|-------------|
|
|
983
|
+
| One operation with options | 1 |
|
|
984
|
+
| 2-5 simple commands | 2 |
|
|
985
|
+
| 6-9 commands with shared validation | 3 |
|
|
986
|
+
| 10+ commands | 4 |
|
|
987
|
+
| To test CLI behavior | 4 |
|
|
988
|
+
| Professional-grade tool | 4 |
|
|
989
|
+
| Dependency injection | 4 |
|
|
990
|
+
| Template method enforcement | 3 |
|
|
991
|
+
| Simplest possible | 1 or 2 |
|
|
992
|
+
|
|
715
993
|
**Additional Considerations:**
|
|
716
994
|
|
|
717
|
-
| Question | Pattern 1 | Pattern 2 | Pattern 3 |
|
|
718
|
-
|
|
719
|
-
| Commands share business logic? | N/A | ❌ Duplicate code | ✅ Shared via base class |
|
|
720
|
-
| Tool might grow to 10+ commands? | ❌ Wrong pattern | ⚠️ Will need refactor | ✅
|
|
721
|
-
| Need programmatic API? | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
722
|
-
| Commands have different option patterns? | N/A | ✅ Easy | ✅ Easy |
|
|
723
|
-
| Team familiar with OOP patterns? | ✅ Simple | ✅ Simple | ⚠️ Requires understanding |
|
|
995
|
+
| Question | Pattern 1 | Pattern 2 | Pattern 3 | Pattern 4 |
|
|
996
|
+
|----------|-----------|-----------|-----------|-----------|
|
|
997
|
+
| Commands share business logic? | N/A | ❌ Duplicate code | ✅ Shared via base class | ⚠️ Manual (extract to modules) |
|
|
998
|
+
| Tool might grow to 10+ commands? | ❌ Wrong pattern | ⚠️ Will need refactor | ⚠️ Consider Pattern 4 | ✅ Designed for it |
|
|
999
|
+
| Need programmatic API? | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
1000
|
+
| Commands have different option patterns? | N/A | ✅ Easy | ✅ Easy | ✅ Easy |
|
|
1001
|
+
| Team familiar with OOP patterns? | ✅ Simple | ✅ Simple | ⚠️ Requires understanding | ⚠️ Requires DI knowledge |
|
|
1002
|
+
| Want to test CLI behavior? | N/A | ❌ Hard (bin/) | ❌ Hard (bin/) | ✅ Easy (lib/cli.rb) |
|
|
1003
|
+
| Need exit codes? | ⚠️ Manual | ⚠️ Manual | ⚠️ Manual | ✅ Built-in |
|
|
1004
|
+
| CLI complexity > 300 lines? | ❌ Wrong pattern | ⚠️ Consider refactor | ⚠️ Consider Pattern 4 | ✅ Perfect fit |
|
|
1005
|
+
| Professional-grade tool? | ⚠️ For simple tools | ⚠️ For medium tools | ✅ Yes | ✅ Yes (best) |
|
|
724
1006
|
|
|
725
1007
|
---
|
|
726
1008
|
|
|
@@ -1583,22 +1865,33 @@ ResourceManagerCLI.new.run
|
|
|
1583
1865
|
|
|
1584
1866
|
## Summary
|
|
1585
1867
|
|
|
1586
|
-
This guide provides
|
|
1868
|
+
This guide provides four proven patterns for CLI architecture in appydave-tools:
|
|
1587
1869
|
|
|
1588
1870
|
1. **Pattern 1**: Single-command tools - Simple, linear execution
|
|
1589
1871
|
2. **Pattern 2**: Multi-command with inline routing - 2-5 commands, simple routing
|
|
1590
|
-
3. **Pattern 3**: Multi-command with BaseAction - 6
|
|
1872
|
+
3. **Pattern 3**: Multi-command with BaseAction - 6-9 commands, shared patterns
|
|
1873
|
+
4. **Pattern 4**: Delegated CLI Class - 10+ commands, testable CLI, professional-grade
|
|
1591
1874
|
|
|
1592
1875
|
**Key Principles:**
|
|
1593
1876
|
- Separate CLI interface (`bin/`) from business logic (`lib/`)
|
|
1594
|
-
- No CLI code in `lib/` - business logic should be usable programmatically
|
|
1877
|
+
- No CLI code in `lib/` - business logic should be usable programmatically (except Pattern 4)
|
|
1595
1878
|
- Use `frozen_string_literal: true` in all Ruby files
|
|
1596
1879
|
- Follow existing naming conventions
|
|
1597
|
-
- Test business logic, not CLI executables
|
|
1880
|
+
- Test business logic, not CLI executables (Pattern 4 tests CLI too)
|
|
1598
1881
|
- Document with `_doc.md` files
|
|
1599
1882
|
|
|
1600
|
-
|
|
1883
|
+
**Pattern Selection:**
|
|
1884
|
+
- **1-5 commands**: Start with Pattern 1 or Pattern 2
|
|
1885
|
+
- **6-9 commands**: Use Pattern 3 (shared validation) or Pattern 4 (testable CLI)
|
|
1886
|
+
- **10+ commands**: Use Pattern 4 (delegated CLI class)
|
|
1887
|
+
- **Need CLI testing**: Use Pattern 4
|
|
1888
|
+
- **Professional tool**: Use Pattern 4
|
|
1889
|
+
|
|
1890
|
+
**See detailed guides:**
|
|
1891
|
+
- [Pattern 4: Delegated CLI Class](./pattern-4-delegated-cli.md) - Full guide with testing examples
|
|
1892
|
+
- [CLI Pattern Comparison](./cli-pattern-comparison.md) - Quick visual comparison
|
|
1893
|
+
- [exe/bin Convention](./exe-bin-convention.md) - Directory structure explained
|
|
1601
1894
|
|
|
1602
1895
|
---
|
|
1603
1896
|
|
|
1604
|
-
**Last updated:** 2025-
|
|
1897
|
+
**Last updated:** 2025-02-04
|