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.
@@ -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 Three Patterns](#the-three-patterns)
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 three distinct patterns, each suited for different tool complexity levels.
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
- └─ 6+ operations OR commands share validation/execution patterns
711
- └─ Pattern 3: Multi-Command with BaseAction
712
- Examples: youtube_manager
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 | ✅ Scales well |
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 three proven patterns for CLI architecture in appydave-tools:
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+ commands, shared patterns
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
- When in doubt, start with **Pattern 1** or **Pattern 2** and refactor to **Pattern 3** if the tool grows to 6+ commands.
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-11-08
1897
+ **Last updated:** 2025-02-04