thor_enhance 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b07825b4f1216de172d6ce7b7ec2052bccee6793c38ceca8b50f094bcbf3deae
4
- data.tar.gz: 4458ad58f0148f4a77da324893a538e40c1def979eb26305cdb2e005afa500f7
3
+ metadata.gz: 6298d393beb8a380215c08bc6fa78c97cfd29b3d804159f97b361628b5dd4707
4
+ data.tar.gz: 15533218c1806a98998f3b66781a9543a22c231f60126c7b98b6a58bc5cf9cd6
5
5
  SHA512:
6
- metadata.gz: a10dfebf316a1ad8a5c6b63a91f90b7fe95391b3f40379bbe37dcfd2e89f9cffe8d48a975fa147210a229376ec736927301eaa042def05a146960646ac0e81ba
7
- data.tar.gz: 0d96db3833f42a605f7c341dd8a0a43788f1d447767df9d632a0950543bec63f05adc620b8c298b46184e44f236ac39692ecafe7cf857c091261ecda005e3031
6
+ metadata.gz: a85cdd00c9939a3f0165fdb41950c9578b56d806cd42cfff90a7058e384fcf33d46ca9f2f79eea35524f305a791a57ee1167e38818d7587fe0b5115112945c84
7
+ data.tar.gz: 38e8c731b8696c6abb82df29fdaabe2468e742ac69b222ac06796b989d0b6de119dfd56af82c6d2cf833522d06c2fdd0802cbb1f2fffce28b6c37c2ce62cb5d3
data/CHANGELOG.md CHANGED
@@ -5,22 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
9
8
 
10
- ## [0.2.0] - 2022-06-18
9
+ ## [0.4.0]
10
+ - Enable Enhancements on a klass basis
11
+ - Add Enable/disable blocks for enhancements to allow for onboarding new tasks
12
+ - Fix required command options
11
13
 
12
- ### Added
13
- - New feature 1
14
- - New feature 2
14
+ ## [0.3.0]
15
+ - remove `warn` hook in favor of enriched `deprecate` hook
16
+ - method name validation on entry
17
+ - Add examples and better documentation
15
18
 
16
- ### Changed
17
- - Changed feature 1
18
- - Changed feature 2
19
-
20
- ### Removed
21
- - Removed feature 1
22
- - Removed feature 2
23
-
24
- ### Fixed
25
- - Bug fix 1
26
- - Bug fix 2
19
+ ## [0.2.0]
20
+ - Stable Release
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in GEMNAME.gemspec
6
6
  gemspec
7
7
 
8
- gem 'faker'
9
- gem 'pry'
10
- gem 'rspec', '~> 3.0'
11
- gem 'rspec_junit_formatter'
12
- gem 'simplecov', require: false, group: :test
8
+ gem "pry"
9
+ gem "pry-byebug"
10
+ gem "rspec", "~> 3.0"
11
+ gem "rspec_junit_formatter"
12
+ gem "simplecov", "~> 0.17.0", require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thor_enhance (0.2.0)
4
+ thor_enhance (0.3.0)
5
5
  thor (~> 1.3)
6
6
 
7
7
  GEM
@@ -9,13 +9,9 @@ GEM
9
9
  specs:
10
10
  byebug (11.1.3)
11
11
  coderay (1.1.3)
12
- concurrent-ruby (1.2.2)
13
12
  diff-lcs (1.5.0)
14
13
  docile (1.4.0)
15
- faker (3.2.2)
16
- i18n (>= 1.8.11, < 2)
17
- i18n (1.14.1)
18
- concurrent-ruby (~> 1.0)
14
+ json (2.6.3)
19
15
  method_source (1.0.0)
20
16
  pry (0.14.2)
21
17
  coderay (~> 1.1)
@@ -23,7 +19,6 @@ GEM
23
19
  pry-byebug (3.10.1)
24
20
  byebug (~> 11.0)
25
21
  pry (>= 0.13, < 0.15)
26
- rake (12.3.3)
27
22
  rspec (3.12.0)
28
23
  rspec-core (~> 3.12.0)
29
24
  rspec-expectations (~> 3.12.0)
@@ -39,25 +34,22 @@ GEM
39
34
  rspec-support (3.12.1)
40
35
  rspec_junit_formatter (0.6.0)
41
36
  rspec-core (>= 2, < 4, != 2.12.0)
42
- simplecov (0.22.0)
37
+ simplecov (0.17.1)
43
38
  docile (~> 1.1)
44
- simplecov-html (~> 0.11)
45
- simplecov_json_formatter (~> 0.1)
46
- simplecov-html (0.12.3)
47
- simplecov_json_formatter (0.1.4)
39
+ json (>= 1.8, < 3)
40
+ simplecov-html (~> 0.10.0)
41
+ simplecov-html (0.10.2)
48
42
  thor (1.3.0)
49
43
 
50
44
  PLATFORMS
51
45
  aarch64-linux
52
46
 
53
47
  DEPENDENCIES
54
- faker
55
48
  pry
56
49
  pry-byebug
57
- rake (~> 12.0)
58
50
  rspec (~> 3.0)
59
51
  rspec_junit_formatter
60
- simplecov
52
+ simplecov (~> 0.17.0)
61
53
  thor_enhance!
62
54
 
63
55
  BUNDLED WITH
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # ThorEnhance
2
2
 
3
+ `ThorEnhance` enhances thor's capabiltiies. It allows customizable method options and task options.
4
+
5
+ Additionally it provides hooks into each method option that allows deprecation dynamically.
3
6
 
4
7
  ## Installation
5
8
 
@@ -11,8 +14,33 @@ gem 'thor_enhance'
11
14
 
12
15
  ## Usage
13
16
 
17
+ ### Hooks
18
+ Hooks allow you to deprecate, warn, or do some other custimizable action when a user calls thor with the specific option
19
+
20
+ [Hook documentation](docs/hooks.md)
21
+ [Hook examples](examples/hooks.md)
22
+
23
+ ### Method option Injection
24
+ Method option injection allows you to enhance specific commands. When used inconjunction with [ThorEnhance::Tree](docs/tree.md), the added fields to the method options are avaialable in your code with ease.
25
+
26
+ [Method option documentation](docs/method_option.md)
27
+
28
+
29
+ ### Command option Injection
30
+ Command option injection is very powerful. This allows add low level documentation in line with the actual code.
31
+
32
+ [Command option documentation](docs/command.md)
33
+
34
+ ### [Future Plans] Automatic ReadMe Generation
35
+ The beauty of ThorEnhance is that it forces all your documentation to live with the code. As your code changes, the documentation naturally changes with it.
36
+
37
+ In the near future, we plan to allow for automatic readme generation based on the enhanced commands provided.
38
+
39
+
14
40
  ### Initialization
15
41
 
42
+ [Refere to documentation](docs/initialization.md)
43
+
16
44
  ## Contributing
17
45
 
18
46
  Bug reports and pull requests are welcome on GitHub at
data/docs/command.md ADDED
@@ -0,0 +1,54 @@
1
+ # Command Method Injection
2
+
3
+ Command Method injection allows you to easily enrich Thor Commands with additional data.
4
+
5
+ ## When to use:
6
+ This should be used when you want to enrich your command data. This data can then be retrieved by using the [ThorEnhance::Tree](tree.md) class.
7
+
8
+ ## How to use
9
+
10
+ Additional configuration is needed prior to loading the your Thor instance
11
+
12
+ ```ruby
13
+ # thor_enhance_config.rb
14
+ ThorEnhance.configure do |c|
15
+ # Adds `example` method to the command
16
+ # Value can be anything and is a repeatable command
17
+ c.add_command_method_enhance "example", repeatable: true
18
+
19
+ # Adds `validate` method to the command
20
+ # Value must be one of :some or :thing
21
+ c.add_command_method_enhance "validate", enum: [:some, :thing]
22
+
23
+ # Adds `enhace` method to the command
24
+ # Value must be of type CustomClass
25
+ # It is repeatble and required
26
+ c.add_command_method_enhance "enhance", allowed_klasses: [CustomClass], repeatable: true, required: true
27
+ end
28
+ ```
29
+ The `add_option_enhance` takes the name as argument 1 followed by options.
30
+
31
+ The available options are:
32
+
33
+ **enums**:
34
+ - When provided, the value of the `name` must be a value in the `enums` array
35
+ EX: `publish: true` succeeds. `publish: :fail` fails.
36
+ - Default: `nil`
37
+
38
+ **allowed_klasses**
39
+ - When provided, this is expected to be an array of class types the value can be.
40
+ - Default: `nil`
41
+
42
+ **required**
43
+ - When flag is set to true, this option will be required on all `method_option` immediately. An error is raised if validation fails
44
+ - Default: `false`
45
+
46
+ **repeatable**
47
+ - When flag is set to true, this option will allow the same command to reference the method multiple times. The value will be retreable as an Array from [ThorEnhance::Tree](tree.md) class.
48
+ - Default: `false`
49
+
50
+ # Examples:
51
+
52
+ [Basic Example](../examples/basic_example.md)
53
+ [Basic Example with Subcommand](../examples/basic_example_with_subcommand.md)
54
+
data/docs/hooks.md ADDED
@@ -0,0 +1,59 @@
1
+ # Hooks:
2
+
3
+ Hooks allow you to slowly deprecate options over time. There are three basic Hooks available on every method_option
4
+
5
+ ## Arguments:
6
+ Both `deprecate` and `hook` are expected to be a `proc`. The input arguments will be `input, options`
7
+
8
+ ### Argument: Input
9
+ The `input` argument is the first argument. It is the value that the user inputted. When there is no user input, the hook will not run.
10
+
11
+ ### Argument: Option
12
+ The `option` argument is the second argument. It is the `Thor::Option` struct for that existing method. The instance will have all ThorEnhance methods available.
13
+
14
+ ## Deprecate:
15
+ The `deprecate` hook is available on every `method_option`. It expects a `Proc` class. The returned value from the proc is expected as either an Hash or a String.
16
+
17
+ This hook should be used when you want to either immediately depreacte a method or over time deprecate a command while migrating to a new command. The decision to raise or warn is dynamic in real time.
18
+
19
+ ### When returned an String
20
+ If the method option is provided, it will raise `ThorEnhance::OptionDeprecated` every time
21
+
22
+ ### When returned a Hash
23
+ **hash[:raise]**: Expected a boolean value on weather to raise or warn. When true, it will raise `ThorEnhance::OptionDeprecated`. When false it will send WARNING to console
24
+ **hash[:warn]**: Console message used when `:raise` is false. Does not output when `:raise` is true
25
+ **hash[:msg]**: Message for both raise and warn. This should a short message on how to migrate to new command or syntax
26
+
27
+ ```ruby
28
+ # When provided `--value1 "string"`:
29
+ # This will raise `ThorEnhance::OptionDeprecated` with a message that looks like:
30
+ # Passing value for option --value1 is deprecated. Provided `string`. Please migrate to `--value4`
31
+ method_option :value1, type: :string, deprecate: ->(input, option) { "Please migrate to `--value4`" }
32
+
33
+ # When provided `--value2 "string"` and deprecated criteria is not met:
34
+ # This will send a WARNING message to the console. It will resemble:
35
+ # Passing value for option --value1 is deprecated. Provided `string`. Please migrate to `--value4`
36
+ method_option :value2, type: :string, deprecate: ->(input, option) { { raise: Date.today > Date.today, warn: "This option will deprecate after #{Date.today}", msg: "Please migrate to --value4"} }
37
+
38
+ # When provided `--value3 "string"` and deprecated criteria is:
39
+ # This will raise `ThorEnhance::OptionDeprecated` with a message that looks like:
40
+ # Passing value for option --value3 is deprecated. Provided `string`. Please migrate to `--value4`
41
+ method_option :value3, type: :string, deprecate: ->(input, option) { { raise: true, warn: "This option will deprecate after #{Date.today}", msg: "Please migrate to --value4"} }
42
+ ```
43
+
44
+ ## Hook
45
+ The `hook` hook is available on every `method_option`. It expects a `Proc` class. No return value is expected. When the input is provided, custom code can get executed
46
+
47
+ ```ruby
48
+ # When provided `--value1 "string"`:
49
+ # This will raise `ThorEnhance::OptionDeprecated` with a message that looks like:
50
+ # Passing value for option --value1 is deprecated. Provided `string`. Please migrate to `--value4`
51
+ method_option :value1, type: :string, hook: ->(input, option) { Kernel.puts "#{input} is a good choice for #{option.name}" }
52
+ ```
53
+
54
+ ---
55
+
56
+ # Examples
57
+
58
+ For examples, [please navigate here](../examples/hooks.md)
59
+
@@ -0,0 +1,62 @@
1
+ # Initialize ThorEnhance
2
+
3
+ ThorEnhance requires initialization prior to your custom Thor classes loading.
4
+
5
+ ## How to initialize
6
+
7
+ ThorEnhance provides several ways to enforce options on downstream classes.
8
+
9
+ ### Preferred route
10
+ When creating the Thor Class, set `thor_enhance_allow!` at the top of the class. This will allow `ThorEnhance` to know that what class to allow and enforce enhancments for
11
+ ```ruby
12
+ class Enhance < Thor
13
+ thor_enhance_allow!
14
+ ...
15
+ end
16
+ ```
17
+
18
+ If you have methods are are not ready to abide by the enformence, No worries. Simple use the enable/disable wrappers.
19
+
20
+ ```ruby
21
+ class Enhance < Thor
22
+ thor_enhance_allow!
23
+
24
+ disable_thor_enhance! do
25
+ desc task1 # No enhancements are required
26
+ def task1;end;
27
+
28
+ enable_thor_enhance! do
29
+ desc task2 # All enhancements are required
30
+ def task2;end;
31
+ end
32
+ end
33
+
34
+ desc task3 # All enhancements are required
35
+ def task3;end;
36
+ end
37
+ ```
38
+
39
+
40
+ ### Alternate route
41
+ When initializing the `ThorEnhance` gem in the configuration, add the following code:
42
+ ```ruby
43
+ ThorEnhance.configure do |c|
44
+ ...
45
+ c.allowed = :all
46
+ ...
47
+ end
48
+ ```
49
+
50
+ The above code will enforce all Thor classes have required ThorEnhanced enhancements.
51
+
52
+ **Caution**: Other gems that utilize thor like `Rake` `RSpec` `Rails` may fail on boot when utilizing the `:all` option. Use with caution
53
+
54
+
55
+
56
+ [Method Options](method_option.md)
57
+ [Command Options](command.md)
58
+
59
+ ## Example
60
+
61
+ [Basic Example](../examples/basic_example.md)
62
+ [Basic Example with Subcommand](../examples/basic_example_with_subcommand.md)
@@ -0,0 +1,51 @@
1
+ # Method Option Injection
2
+
3
+ Method option injection allows you to easily add additional flags to the `method_option` for commands. You can set them as required for every option. You can even set allowable values or allowable classes
4
+
5
+ ## When to use:
6
+ This should be used when you want to enrich your command data. This data can then be retrieved by using the [ThorEnhance::Tree](tree.md) class.
7
+
8
+ ## How to use
9
+
10
+ Additional configuration is needed prior to loading the your Thor instance
11
+
12
+ ```ruby
13
+ # thor_enhance_config.rb
14
+ ThorEnhance.configure do |c|
15
+ # Adds `classify` method to the option class
16
+ # Value of classify must be one of the enums
17
+ # It is not a required field on every method
18
+ c.add_option_enhance "classify", allowed_klasses: [String, Symbol, NilClass], required: false
19
+
20
+ # Adds `publish` method to the option
21
+ # Value of classify must be one of the enums
22
+ # It is a required field on every option
23
+ c.add_option_enhance :publish, enums: [true, false], required: true
24
+
25
+ # Adds `avoid method to the option
26
+ # Value is not restricted to any type or enum
27
+ # Not a required field
28
+ c.add_option_enhance :avoid
29
+ end
30
+ ```
31
+ The `add_option_enhance` takes the name as argument 1 followed by options.
32
+
33
+ The available options are:
34
+
35
+ **enums**:
36
+ - When provided, the value of the `name` must be a value in the `enums` array
37
+ EX: `publish: true` succeeds. `publish: :fail` fails.
38
+ - Default: `nil`
39
+
40
+ **allowed_klasses**
41
+ - When provided, this is expected to be an array of class types the value can be.
42
+ - Default: `nil`
43
+
44
+ **required**
45
+ - When flag is set to true, this option will be required on all `method_option` immediately. An error is raised if validation fails
46
+ - Default: `false`
47
+
48
+ # Examples:
49
+
50
+ [Basic Example](../examples/basic_example.md)
51
+ [Basic Example with Subcommand](../examples/basic_example_with_subcommand.md)
data/docs/tree.md ADDED
@@ -0,0 +1,42 @@
1
+ # ThorEnhance::Tree
2
+
3
+ The thor tree is a powerful tool to do introspection on all Thor commands within a repo.
4
+
5
+ ## How to use:
6
+
7
+ MyTestClass definitiona can be found in [spec_helper.rb](../spec/spec_helper.rb)
8
+
9
+ ```ruby
10
+ require "thor_enhance"
11
+
12
+ tree = ThorEnhance::Tree.tree(base: MyTestClass)
13
+
14
+ # View all commands on parent tree:
15
+ tree.children.map(&:command)
16
+
17
+ # View all commands including subcommands
18
+
19
+ def iterate_base(base:)
20
+ tree.map do |name, object|
21
+ if object.children?
22
+ iterate_base(base: object)
23
+ else
24
+ object.command
25
+ end
26
+ end.flatten
27
+ end
28
+
29
+ iterate_base(base: tree)
30
+
31
+ # View specific command
32
+ command = tree["test_meth"].command
33
+
34
+ # View specific sub command
35
+ sub_command_innard = tree["sub"].children["innard"].command
36
+
37
+ # View Options on specific command
38
+ command.options
39
+
40
+ # View specific Option on specific command
41
+ sub_command_innard.options[:t]
42
+ ```
@@ -0,0 +1,48 @@
1
+ ```ruby
2
+ # thor_enhance_config.rb
3
+
4
+ require "thor_enhance"
5
+
6
+ ThorEnhance.configure do |c|
7
+ # Adds `classify` method to the option class
8
+ # Value of classify must be one of the enums
9
+ # It is not a required field on every method
10
+ c.add_option_enhance "classify", enums: [:allowed, :future, :existing], required: false
11
+
12
+ # Adds `publish` method to the option
13
+ # Value of classify must be one of the enums
14
+ # It is a required field on every option
15
+ c.add_option_enhance :publish, enums: [true, false], required: true
16
+
17
+ # Adds `example` method to the command
18
+ # Value can be anything and is a repeatable command
19
+ c.add_command_method_enhance "example", repeatable: true
20
+ end
21
+ ```
22
+
23
+ ```ruby
24
+ # thor_cli.rb
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+ require "thor_enhance_config"
29
+
30
+ class ThorEnhancement < Thor
31
+ thor_enhance_allow!
32
+
33
+ dec "test", "Testing method"
34
+ example "thor_cli.rb test --value 'This is rad'"
35
+ example "thor_cli.rb test"
36
+ method_option :value, type: :string, publish: true
37
+ def test
38
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
39
+ # example was set as `repeatable` so it gets returned as an array
40
+ command.example.each { puts _1 }
41
+
42
+ command.options[:value].publish == true
43
+ command.options[:value].classify == nil
44
+ end
45
+ end
46
+
47
+ ThorEnhancement.start
48
+ ```
@@ -0,0 +1,76 @@
1
+ ```ruby
2
+ # thor_enhance_config.rb
3
+
4
+ require "thor_enhance"
5
+
6
+ ThorEnhance.configure do |c|
7
+ # Adds `classify` method to the option class
8
+ # Value of classify must be one of the enums
9
+ # It is not a required field on every method
10
+ c.add_option_enhance :classify, allowed_klasses: [Symbol], required: false
11
+
12
+ # Adds `publish` method to the option
13
+ # Value of classify must be one of the enums
14
+ # It is a required field on every option
15
+ c.add_option_enhance "publish", allowed_klasses: [TrueClass, FalseClass], required: true
16
+
17
+ # Adds `example` method to the command
18
+ # Value can be anything and is a repeatable command
19
+ c.add_command_method_enhance :example, repeatable: true
20
+ end
21
+ ```
22
+
23
+ ```ruby
24
+ # thor_cli.rb
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+ require "thor_enhance_config"
29
+
30
+ class ThorEnhancement < Thor
31
+ thor_enhance_allow!
32
+
33
+ dec "test", "Testing method"
34
+ example "thor_cli.rb test --value 'This is rad'"
35
+ example "thor_cli.rb test"
36
+ method_option :value, type: :string, publish: true
37
+ def test
38
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
39
+ # example was set as `repeatable` so it gets returned as an array
40
+ command.example.each { puts _1 }
41
+
42
+ command.options[:value].publish == true
43
+ command.options[:value].classify == nil
44
+ end
45
+
46
+ class SubCommand < Thor
47
+ thor_enhance_allow!
48
+
49
+ desc "sub_command", "Command for SubCommand"
50
+ example "bin/thor sub_command innard -t something -s better"
51
+ example "bin/thor sub_command innard -s better"
52
+ example "bin/thor sub_command innard"
53
+
54
+ method_option :t, type: :string, classify: :allowed, publish: true
55
+ method_option :s, type: :string, classify: :erase, publish: false
56
+ def sub_command
57
+ parent = ThorEnhance::Tree.tree(base: self.class)["sub"]
58
+ parent.example.each { puts _1 } # 1 example
59
+
60
+ command = parent.children["sub_command"].command
61
+ command.children? == false
62
+
63
+ command.example.each { puts _1 } # 3 examples
64
+
65
+ command.options[:t].publish == true
66
+ command.options[:t].classify == :allowed
67
+ end
68
+ end
69
+
70
+ desc "sub", "Submodule command line"
71
+ example "bin/thor sub ***"
72
+ subcommand "sub", SubCommand
73
+ end
74
+
75
+ ThorEnhancement.start
76
+ ```
data/examples/hooks.md ADDED
@@ -0,0 +1,58 @@
1
+ # Hook Examples
2
+
3
+ For more information on how to use hooks, [view documentation here](../docs/hooks.md)
4
+
5
+ ```ruby
6
+ # thor_enhance_config.rb
7
+
8
+ require "thor_enhance"
9
+
10
+ ThorEnhance.configure do |c|
11
+ # Adds `classify` method to the option class
12
+ # Value of classify must be one of the enums
13
+ # It is not a required field on every method
14
+ c.add_option_enhance "classify", enums: [:allowed, :future, :existing], required: false
15
+
16
+ # Adds `publish` method to the option
17
+ # Value of classify must be one of the enums
18
+ # It is a required field on every option
19
+ c.add_option_enhance :publish, enums: [true, false], required: true
20
+
21
+ # Adds `example` method to the command
22
+ # Value can be anything and is a repeatable command
23
+ c.add_command_method_enhance "example", repeatable: true
24
+ end
25
+ ```
26
+
27
+ ```ruby
28
+ # thor_cli.rb
29
+
30
+ require "rubygems"
31
+ require "bundler/setup"
32
+ require "thor_enhance_config"
33
+
34
+ VERSION = Gem::Version.new("1.2.3")
35
+ MAX_VERSION = Gem::Version.new("2.0.0")
36
+
37
+ class ThorEnhancement < Thor
38
+ thor_enhance_allow!
39
+ dec "test", "Testing method"
40
+ example "thor_cli.rb test --value 'This is rad'"
41
+ example "thor_cli.rb test"
42
+ method_option :value, type: :string, publish: true, deprecate: ->(input, option) { { raise: VERSION > MAX_VERSION, warn: "This option will deprecate with version #{MAX_VERSION}", msg: "Please migrate to --value4"} }
43
+
44
+ # Deprecate hook
45
+ method_option :option, type: :string, publish: false,
46
+ method_option :test, type: :string, publish: false,
47
+ def test
48
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
49
+ # example was set as `repeatable` so it gets returned as an array
50
+ command.example.each { puts _1 }
51
+
52
+ command.options[:value].publish == true
53
+ command.options[:value].classify == nil
54
+ end
55
+ end
56
+
57
+ ThorEnhancement.start
58
+ ```
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ module Base
7
+ module BuildOption
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def build_option(name, options, scope)
14
+ # Method is monkey patched in order to make options aware of the klass that creates it if available
15
+ # This allows us to enable and disable required options based on the class
16
+ scope[name] = Thor::Option.new(name, {check_default_type: check_default_type}.merge!(options), self)
17
+ end
18
+ end
19
+ end
20
+
21
+ module AllowedKlass
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
25
+
26
+ module ClassMethods
27
+ def thor_enhance_allow!
28
+ ThorEnhance.configuration.allowed_klasses << self
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -12,11 +12,6 @@ module ThorEnhance
12
12
  define_method(name) { instance_variable_get("@#{name}") }
13
13
  define_method("#{name}=") { instance_variable_set("@#{name}", _1) }
14
14
  end
15
-
16
- # Prepend it so we can call this run method first
17
- ::Thor::Command.prepend ThorEnhance::CommandHook
18
-
19
- ::Thor::Command.include ThorEnhance::Command
20
15
  end
21
16
  end
22
17
  end
@@ -23,13 +23,23 @@ module ThorEnhance
23
23
  input_tags = [option.switch_name] + option.aliases
24
24
  next unless input_tags.any? { raw_args.include?(_1) }
25
25
 
26
- proc_value = proclamation.(given_value)
26
+ proc_value = proclamation.(given_value, option)
27
27
 
28
- case object[:behavior]
29
- when :raise
30
- raise ThorEnhance::OptionDeprecated, "Passing value for option #{option.switch_name} is deprecated. Provided `#{given_value}`. #{proc_value}"
31
- when :warn
32
- Kernel.warn("WARNING: Provided `#{given_value}` for option #{option.switch_name}. #{proc_value}")
28
+ if object[:behavior] == :request
29
+ if Hash === proc_value && proc_value.keys.sort == [:raise, :warn, :msg].sort
30
+ warn_msg = proc_value[:warn].to_s
31
+ msg = proc_value[:msg].to_s
32
+ if proc_value[:raise]
33
+ raise ThorEnhance::OptionDeprecated, "Passing value for option #{option.switch_name} is deprecated. " \
34
+ "Provided `#{given_value}`. #{proc_value[:msg]}"
35
+ else
36
+ Kernel.warn("WARNING: Provided `#{given_value}` for option #{option.switch_name}. " \
37
+ "#{proc_value[:warn]}. #{proc_value[:msg]}")
38
+ end
39
+ else
40
+ raise ThorEnhance::OptionDeprecated, "Passing value for option #{option.switch_name} is deprecated. " \
41
+ "Provided `#{given_value}`. #{proc_value}"
42
+ end
33
43
  end
34
44
  end
35
45
  end
@@ -8,29 +8,50 @@ module ThorEnhance
8
8
  def self.thor_enhance_injection!
9
9
  return false unless ThorEnhance::Configuration.allow_changes?
10
10
 
11
- # This will dynamically define a class on the Thor module
11
+ # This will dynamically define class metohds on the Thor Base class
12
12
  # This allows us to add convenience helpers per method
13
+ # Allows us to add inline helper class functions for each desc task
13
14
  ThorEnhance.configuration.command_method_enhance.each do |name, object|
14
- # This is how thor works -- at the class level using memoization
15
- # Interesting approach and it works because thor should boot before everything else -- and only boots once
15
+ # Define a new method based on the name of each enhanced command method
16
+ # Method takes care all validation except requirment -- Requirement is done during command initialization
16
17
  ClassMethods.define_method("#{name}") do |input|
18
+ return nil unless ThorEnhance.configuration.allowed?(self)
19
+
17
20
  value = instance_variable_get("@#{name}")
18
21
  value ||= {}
22
+ # Usage is nil when the `desc` has not been defined yet -- Under normal circumstance this will never happen
19
23
  if @usage.nil?
20
24
  raise ArgumentError, "Usage is not set. Please ensure `#{name}` is defined after usage is set"
21
25
  end
26
+
27
+ # Required check gets done on command initialization (defined below in ClassMethods)
28
+ if input.nil?
29
+ # no op when it is nil
30
+ elsif !object[:enums].nil?
31
+ unless object[:enums].include?(input)
32
+ raise ValidationFailed, "#{@usage} recieved command method `#{name}` with incorrect enum. Received: [#{input}]. Expected: [#{object[:enums]}]"
33
+ end
34
+ elsif !object[:allowed_klasses].nil?
35
+ unless object[:allowed_klasses].include?(value.class)
36
+ raise ValidationFailed, "#{@usage} recieved command method `#{name}` with incorrect class type. Received: [#{input.class}]. Expected: #{object[:allowed_klasses]}"
37
+ end
38
+ end
39
+
22
40
  if object[:repeatable]
23
41
  value[@usage] ||= []
24
42
  value[@usage] << input
25
43
  else
44
+ if value[@usage]
45
+ raise ValidationFailed, "#{@usage} recieved command method `#{name}` with repeated invocations of " \
46
+ "`#{name}`. Please remove the secondary invocation. Or set `#{name}` as a repeatable command method"
47
+ end
48
+
26
49
  value[@usage] = input
27
50
  end
28
51
 
29
52
  instance_variable_set("@#{name}", value)
30
53
  end
31
54
  end
32
-
33
- ::Thor.include ThorEnhance::CommandMethod
34
55
  end
35
56
 
36
57
  def self.included(base)
@@ -38,20 +59,91 @@ module ThorEnhance
38
59
  end
39
60
 
40
61
  module ClassMethods
62
+ THOR_ENHANCE_ENABLE = :enable
63
+ THOR_ENHANCE_DISABLE = :disable
64
+
65
+ def disable_thor_enhance!(&block)
66
+ __thor_enhance_access(type: THOR_ENHANCE_DISABLE, &block)
67
+ end
68
+
69
+ def enable_thor_enhance!(&block)
70
+ __thor_enhance_access(type: THOR_ENHANCE_ENABLE, &block)
71
+ end
41
72
 
42
73
  # Call all things super for it (super in thor also calls super as well)
43
- # If the command exists, then add the initi
74
+ # If the command exists, then set the instance variable
44
75
  def method_added(meth)
45
- super(meth)
76
+ value = super(meth)
46
77
 
47
- # Skip if the we should not be creating the command
78
+ # Skip if the command does not exist -- Super creates the command
48
79
  if command = all_commands[meth.to_s]
49
- ThorEnhance.configuration.command_method_enhance.each do |name, object|
50
- # When value is not required and not present, it will not exist. Rescue and return nil
51
- value = instance_variable_get("@#{name}")[meth.to_s] rescue nil
52
- command.send("#{name}=", value)
80
+
81
+ if ThorEnhance.configuration.allowed?(self)
82
+ ThorEnhance.configuration.command_method_enhance.each do |name, object|
83
+
84
+ instance_variable = instance_variable_get("@#{name}")
85
+ # instance variable was correctly assigned and exists as a hash
86
+ if Hash === instance_variable
87
+ # Expected key exists in the hash
88
+ # This key already passed validation for type and enum
89
+ # Set it and move on
90
+ if instance_variable.key?(meth.to_s)
91
+ value = instance_variable[meth.to_s]
92
+ command.send("#{name}=", value)
93
+ next
94
+ end
95
+ end
96
+
97
+ # At this point, the key command method was never invoked on for the `name` thor task
98
+ # The value is nil/unset
99
+
100
+ # If we have disabled required operations, go ahead and skip this
101
+ next if ::Thor.__thor_enhance_definition == ThorEnhance::CommandMethod::ClassMethods::THOR_ENHANCE_DISABLE
102
+
103
+ # Skip if the expected command method was not required
104
+ next unless object[:required]
105
+
106
+ # Skip if the method is part of the ignore list
107
+ next if ThorEnhance::Tree.ignore_commands.include?(meth.to_s)
108
+
109
+ # At this point, the command method is missing, we are not in disable mode, and the command method was required
110
+ # raise all hell
111
+ raise ThorEnhance::RequiredOption, "`#{meth}` does not have required command method #{name} invoked. " \
112
+ "Ensure it is added after the `desc` task is invoked"
113
+ end
53
114
  end
54
115
  end
116
+
117
+ value
118
+ end
119
+
120
+ def __thor_enhance_definition
121
+ @__thor_enhance_definition
122
+ end
123
+
124
+ def __thor_enhance_definition=(value)
125
+ @__thor_enhance_definition = value
126
+ end
127
+
128
+ def __thor_enhance_definition_stack
129
+ @__thor_enhance_definition_stack ||= []
130
+ end
131
+
132
+ private
133
+
134
+ def __thor_enhance_access(type:, &block)
135
+ raise ArgumentError, "Expected to receive block. No block given" unless block_given?
136
+
137
+ # capture original value. This allows us to do nested enable/disables
138
+ ::Thor.__thor_enhance_definition_stack << ::Thor.__thor_enhance_definition.dup
139
+ ::Thor.__thor_enhance_definition = type
140
+
141
+ yield
142
+
143
+ nil
144
+ ensure
145
+ # Return the state to the most recently set stack
146
+ ::Thor.__thor_enhance_definition = ::Thor.__thor_enhance_definition_stack.pop
55
147
  end
56
148
  end
57
149
  end
@@ -4,15 +4,13 @@ require "thor"
4
4
 
5
5
  module ThorEnhance
6
6
  class Configuration
7
-
8
- # Order is important -- Ensure deoreacte is first
9
- HOOKERS = [DEPRECATE = :deprecate, WARNING = :warn, HOOK = :hook]
7
+ HOOKERS = [DEPRECATE = :deprecate, HOOK = :hook]
8
+ ALLOWED_VALUES = [DEFAULT_ALLOWED = nil, ALLOW_ALL = :all]
10
9
 
11
10
  class << self
12
- attr_accessor :allow_changes
13
-
14
11
  def allow_changes?(raise_error: true)
15
- return true if allow_changes.nil?
12
+ return true unless defined?(@@allow_changes)
13
+ return true if @@allow_changes.nil?
16
14
 
17
15
  if raise_error
18
16
  raise BaseError, "Configuration changes are halted. Unable to change ThorEnhancements"
@@ -22,7 +20,7 @@ module ThorEnhance
22
20
  end
23
21
 
24
22
  def disallow_changes!
25
- allow_changes = true
23
+ @@allow_changes = :prevent
26
24
  end
27
25
  end
28
26
 
@@ -32,6 +30,31 @@ module ThorEnhance
32
30
  ThorEnhance::Option.thor_enhance_injection!
33
31
  ThorEnhance::Command.thor_enhance_injection!
34
32
  ThorEnhance::CommandMethod.thor_enhance_injection!
33
+
34
+ ############
35
+ # Commands #
36
+ ############
37
+ # Prepend it so we can call our run method first
38
+ ::Thor::Command.prepend ThorEnhance::CommandHook
39
+ ::Thor::Command.include ThorEnhance::Command
40
+
41
+ ##################
42
+ # Command Method #
43
+ ##################
44
+ ::Thor.include ThorEnhance::CommandMethod
45
+
46
+ ###########
47
+ # Options #
48
+ ###########
49
+ # Must be prepended because we change the arity of the initializer here
50
+ ::Thor::Option.prepend ThorEnhance::Option
51
+ ::Thor.include ThorEnhance::Base::BuildOption
52
+
53
+ ####################
54
+ # Other Injections #
55
+ ####################
56
+ ::Thor.include ThorEnhance::Base::AllowedKlass
57
+
35
58
  self.class.disallow_changes!
36
59
  end
37
60
 
@@ -39,10 +62,31 @@ module ThorEnhance
39
62
  @command_method_enhance ||= {}
40
63
  end
41
64
 
65
+ def allowed_klasses
66
+ @klass_procs ||= []
67
+ end
68
+
69
+ def allowed
70
+ @allowed ||= DEFAULT_ALLOWED
71
+ end
72
+
73
+ def allowed=(value)
74
+ raise ArgumentError, "Unexpected value for `allowed =`. Given: #{value}. Expected one of #{ALLOWED_VALUES}" unless ALLOWED_VALUES.include?(value)
75
+
76
+ @allowed = value
77
+ end
78
+
79
+ def allowed?(klass)
80
+ return true if allowed == ALLOW_ALL
81
+ return true if allowed_klasses.include?(klass)
82
+
83
+ # At this point, allowed is false and not an includable klass -- dont allow
84
+ false
85
+ end
86
+
42
87
  def option_enhance
43
88
  @option_enhance ||= {
44
- WARNING => { allowed_klasses: [Proc], behavior: :warn, required: false },
45
- DEPRECATE => { allowed_klasses: [Proc], behavior: :raise, required: false },
89
+ DEPRECATE => { allowed_klasses: [Proc], behavior: :request, required: false },
46
90
  HOOK => { allowed_klasses: [Proc], behavior: nil, required: false },
47
91
  }
48
92
  end
@@ -64,6 +108,16 @@ module ThorEnhance
64
108
  private
65
109
 
66
110
  def add_to_variable(storage, methods, name, allowed_klasses, enums, required, repeatable = false)
111
+ # Reject if the name is not a Symbol or a string
112
+ if [String, Symbol].none? { _1 === name }
113
+ raise ArgumentError, "Invalid name type received. Received [#{name}] of type [#{name.class}]. Expected to be of type String or Symbol"
114
+ end
115
+
116
+ # If name contains characters other than upper or lower case letters and _ FAIL
117
+ unless name =~ /^[A-Za-z_]+$/
118
+ raise ArgumentError, "Invalid name received. Received [#{name}] does not match /^[A-Za-z_]+$/."
119
+ end
120
+
67
121
  if methods.include?(name.to_sym)
68
122
  raise OptionNotAllowed, "[#{name}] is not allowed as an enhancement"
69
123
  end
@@ -74,7 +128,12 @@ module ThorEnhance
74
128
 
75
129
  # if enums is present and not an array
76
130
  if !enums.nil? && !enums.is_a?(Array)
77
- raise ArgumentError, "Recieved enum of #{enums}. When present, it is expected to be an Array"
131
+ raise ArgumentError, "Recieved enums with #{enums}. When present, it is expected to be an Array"
132
+ end
133
+
134
+ # if allowed_klasses is present and not an array
135
+ if !allowed_klasses.nil? && !allowed_klasses.is_a?(Array)
136
+ raise ArgumentError, "Recieved allowed_klasses with #{allowed_klasses}. When present, it is expected to be an Array"
78
137
  end
79
138
 
80
139
  storage[name.to_sym] = { allowed_klasses: allowed_klasses, enums: enums, required: required, repeatable: repeatable }
@@ -11,24 +11,30 @@ module ThorEnhance
11
11
  ThorEnhance.configuration.option_enhance.each do |name, object|
12
12
  define_method(name) { instance_variable_get("@#{name}") }
13
13
  end
14
-
15
- ::Thor::Option.include ThorEnhance::Option
16
14
  end
17
15
 
18
- def initialize(name, options = {})
19
- super
16
+ # Monkey patched initializer
17
+ # Thor Option initializer only takes (name, options) as arguments
18
+ # Thor::Option.new is only called from `build_option` which gets monkey patched in thor_enhance/base
19
+ def initialize(name, options = {}, klass = nil)
20
+ super(name, options)
20
21
 
21
- thor_enhance_definitions(options)
22
+ thor_enhance_definitions(options, klass)
22
23
  end
23
24
 
24
- def thor_enhance_definitions(options)
25
+ def thor_enhance_definitions(options, klass)
26
+ return nil unless ThorEnhance.configuration.allowed?(klass)
27
+
25
28
  ThorEnhance.configuration.option_enhance.each do |name, object|
26
- if options[name.to_sym].nil? && object[:required]
27
- raise RequiredOption, "#{@name} does not have required option #{name}. Please add it to the option"
29
+ # When disabled, we do not do the required check, if present, it is still required to be a valid option otherwise
30
+ unless ::Thor.__thor_enhance_definition == ThorEnhance::CommandMethod::ClassMethods::THOR_ENHANCE_DISABLE
31
+ if options[name.to_sym].nil? && object[:required]
32
+ raise RequiredOption, "#{@name} does not have required option #{name}. Please add it to the option"
33
+ end
28
34
  end
29
35
 
30
36
  value = options[name.to_sym]
31
- if value.nil? && object[:required] == false
37
+ if value.nil? # This can be nil here because we have already done a required check
32
38
  # no op when it is nil and not required
33
39
  elsif !object[:enums].nil?
34
40
  unless object[:enums].include?(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThorEnhance
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/thor_enhance.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "thor_enhance/base"
3
4
  require "thor_enhance/command"
4
5
  require "thor_enhance/command_hook"
5
6
  require "thor_enhance/command_method"
data/thor_enhance.gemspec CHANGED
@@ -30,9 +30,4 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  spec.add_dependency "thor", "~> 1.3"
33
-
34
- spec.add_development_dependency "pry-byebug"
35
- spec.add_development_dependency "rake", "~> 12.0"
36
- spec.add_development_dependency "rspec", "~> 3.0"
37
- spec.add_development_dependency "simplecov", "~> 0.17.0"
38
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thor_enhance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Taylor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-21 00:00:00.000000000 Z
11
+ date: 2023-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -24,62 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
- - !ruby/object:Gem::Dependency
28
- name: pry-byebug
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '12.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '12.0'
55
- - !ruby/object:Gem::Dependency
56
- name: rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: simplecov
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.17.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 0.17.0
83
27
  description: Have you ever wanted your thor commands to tell a story of what they
84
28
  are? Or have you ever wanted to deprecate an option over time easily? ThorEnhance
85
29
  allows to to annote methods and commands in a human readable way
@@ -102,7 +46,16 @@ files:
102
46
  - bin/console
103
47
  - bin/setup
104
48
  - docker-compose.yml
49
+ - docs/command.md
50
+ - docs/hooks.md
51
+ - docs/initialization.md
52
+ - docs/method_option.md
53
+ - docs/tree.md
54
+ - examples/basic_example.md
55
+ - examples/example_with_subcommand.md
56
+ - examples/hooks.md
105
57
  - lib/thor_enhance.rb
58
+ - lib/thor_enhance/base.rb
106
59
  - lib/thor_enhance/command.rb
107
60
  - lib/thor_enhance/command_hook.rb
108
61
  - lib/thor_enhance/command_method.rb