thor_enhance 0.1.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29e06b1db0b1f965fc4896f15781ab95d11999a03b3c8d87d3bde9a7ce7505b4
4
- data.tar.gz: 503883577bd91606b5007adecc6592077b571978abd02c017b7222144fb45c62
3
+ metadata.gz: 921021cfeb7e6d2528aa29efeeeb0b282208b08cecef9dff992dd421696f13d6
4
+ data.tar.gz: e43f824f5d32922d71c385722b50f31f34a7c7912eb2e6b93e1cb507e7e9f8e3
5
5
  SHA512:
6
- metadata.gz: 9640892f989be7e49445b89c5345b4993b29203bd0d58cf638244bb92f861154e11afa19408a4b69f6e09b828369c9d7fee55f81725639bf6cf8ef34c58ca1a0
7
- data.tar.gz: a1dbc442d2a09e0aed8a50f08eee393e9b2a67624dea27629d4951d88472c060cbff102364a7953310d74af531fe75eb11bb680f1488a0faa62b023ab3717c0b
6
+ metadata.gz: 74090f01c075d963fbffd046cb4f3968901cb5683d8bf1f1f91a181de618e75f1ec20368fd0d04815ed76962f00c12a409c24639e03e7c80b647e6ad980ddafd
7
+ data.tar.gz: 2d815c6bc7957019524de7785b4678d8b10ccaa38f9d172ea383f21241cfaf80cf9601573d859140c7e4b29b9d14cdc3ce5f492c3a2bd282f8b5be33100e81eb
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
 
14
14
  # generated gems
15
15
  *.gem
16
+
17
+ spec/examples.txt
data/CHANGELOG.md CHANGED
@@ -5,22 +5,11 @@ 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.3.0]
10
+ - remove `warn` hook in favor of enriched `deprecate` hook
11
+ - method name validation on entry
12
+ - Add examples and better documentation
11
13
 
12
- ### Added
13
- - New feature 1
14
- - New feature 2
15
-
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
14
+ ## [0.2.0]
15
+ - Stable Release
data/Gemfile.lock CHANGED
@@ -1,27 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thor_enhance (0.1.0)
5
- class_composer
6
- faraday
4
+ thor_enhance (0.3.0)
5
+ thor (~> 1.3)
7
6
 
8
7
  GEM
9
8
  remote: https://rubygems.org/
10
9
  specs:
11
- base64 (0.2.0)
12
10
  byebug (11.1.3)
13
- class_composer (1.0.2)
14
11
  coderay (1.1.3)
15
12
  concurrent-ruby (1.2.2)
16
13
  diff-lcs (1.5.0)
17
14
  docile (1.4.0)
18
15
  faker (3.2.2)
19
16
  i18n (>= 1.8.11, < 2)
20
- faraday (2.7.11)
21
- base64
22
- faraday-net_http (>= 2.0, < 3.1)
23
- ruby2_keywords (>= 0.0.4)
24
- faraday-net_http (3.0.2)
25
17
  i18n (1.14.1)
26
18
  concurrent-ruby (~> 1.0)
27
19
  method_source (1.0.0)
@@ -47,13 +39,13 @@ GEM
47
39
  rspec-support (3.12.1)
48
40
  rspec_junit_formatter (0.6.0)
49
41
  rspec-core (>= 2, < 4, != 2.12.0)
50
- ruby2_keywords (0.0.5)
51
42
  simplecov (0.22.0)
52
43
  docile (~> 1.1)
53
44
  simplecov-html (~> 0.11)
54
45
  simplecov_json_formatter (~> 0.1)
55
46
  simplecov-html (0.12.3)
56
47
  simplecov_json_formatter (0.1.4)
48
+ thor (1.3.0)
57
49
 
58
50
  PLATFORMS
59
51
  aarch64-linux
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,52 @@
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)
52
+
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,47 @@
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
+
32
+ dec "test", "Testing method"
33
+ example "thor_cli.rb test --value 'This is rad'"
34
+ example "thor_cli.rb test"
35
+ method_option :value, type: :string, publish: true
36
+ def test
37
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
38
+ # example was set as `repeatable` so it gets returned as an array
39
+ command.example.each { puts _1 }
40
+
41
+ command.options[:value].publish == true
42
+ command.options[:value].classify == nil
43
+ end
44
+ end
45
+
46
+ ThorEnhancement.start
47
+ ```
@@ -0,0 +1,73 @@
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
+
32
+ dec "test", "Testing method"
33
+ example "thor_cli.rb test --value 'This is rad'"
34
+ example "thor_cli.rb test"
35
+ method_option :value, type: :string, publish: true
36
+ def test
37
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
38
+ # example was set as `repeatable` so it gets returned as an array
39
+ command.example.each { puts _1 }
40
+
41
+ command.options[:value].publish == true
42
+ command.options[:value].classify == nil
43
+ end
44
+
45
+ class SubCommand < Thor
46
+ desc "sub_command", "Command for SubCommand"
47
+ example "bin/thor sub_command innard -t something -s better"
48
+ example "bin/thor sub_command innard -s better"
49
+ example "bin/thor sub_command innard"
50
+
51
+ method_option :t, type: :string, classify: :allowed, publish: true
52
+ method_option :s, type: :string, classify: :erase, publish: false
53
+ def sub_command
54
+ parent = ThorEnhance::Tree.tree(base: self.class)["sub"]
55
+ parent.example.each { puts _1 } # 1 example
56
+
57
+ command = parent.children["sub_command"].command
58
+ command.children? == false
59
+
60
+ command.example.each { puts _1 } # 3 examples
61
+
62
+ command.options[:t].publish == true
63
+ command.options[:t].classify == :allowed
64
+ end
65
+ end
66
+
67
+ desc "sub", "Submodule command line"
68
+ example "bin/thor sub ***"
69
+ subcommand "sub", SubCommand
70
+ end
71
+
72
+ ThorEnhancement.start
73
+ ```
data/examples/hooks.md ADDED
@@ -0,0 +1,57 @@
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
+ class ThorEnhancement < Thor
37
+
38
+ dec "test", "Testing method"
39
+ example "thor_cli.rb test --value 'This is rad'"
40
+ example "thor_cli.rb test"
41
+ 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"} }
42
+
43
+ # Deprecate hook
44
+ method_option :option, type: :string, publish: false,
45
+ method_option :test, type: :string, publish: false,
46
+ def test
47
+ command = ThorEnhance::Tree.tree(base: self.class)["test"].command
48
+ # example was set as `repeatable` so it gets returned as an array
49
+ command.example.each { puts _1 }
50
+
51
+ command.options[:value].publish == true
52
+ command.options[:value].classify == nil
53
+ end
54
+ end
55
+
56
+ ThorEnhancement.start
57
+ ```
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ module Command
7
+ def self.thor_enhance_injection!
8
+ return false unless ThorEnhance::Configuration.allow_changes?
9
+
10
+ # Create Thor::Command getter and setter methods -- Validation gets done on setting
11
+ ThorEnhance.configuration.command_method_enhance.each do |name, object|
12
+ define_method(name) { instance_variable_get("@#{name}") }
13
+ define_method("#{name}=") { instance_variable_set("@#{name}", _1) }
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
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ module CommandHook
7
+ def run(instance, args = [])
8
+ raw_args = instance.instance_variable_get(:@_initializer)[1]
9
+ ThorEnhance::Configuration::HOOKERS.each do |hook|
10
+ object = ThorEnhance.configuration.option_enhance[hook]
11
+ # Iterate the options list based on each hook type
12
+ instance.options.each do |name, given_value|
13
+ option = options[name.to_s] || options[name.to_sym]
14
+ next if option.nil?
15
+
16
+ # If the hook exists on the method option, retreive it
17
+ # if not, move on
18
+ proclamation = option.send(hook)
19
+ next if proclamation.nil?
20
+
21
+ # if input tags is included in raw args, the user inputted the value
22
+ # hooks should only get called if the value was inputted
23
+ input_tags = [option.switch_name] + option.aliases
24
+ next unless input_tags.any? { raw_args.include?(_1) }
25
+
26
+ proc_value = proclamation.(given_value, option)
27
+
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
43
+ end
44
+ end
45
+ end
46
+
47
+ super
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ module CommandMethod
7
+
8
+ def self.thor_enhance_injection!
9
+ return false unless ThorEnhance::Configuration.allow_changes?
10
+
11
+ # This will dynamically define a class on the Thor module
12
+ # This allows us to add convenience helpers per method
13
+ 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
16
+ ClassMethods.define_method("#{name}") do |input|
17
+ value = instance_variable_get("@#{name}")
18
+ value ||= {}
19
+ if @usage.nil?
20
+ raise ArgumentError, "Usage is not set. Please ensure `#{name}` is defined after usage is set"
21
+ end
22
+ if object[:repeatable]
23
+ value[@usage] ||= []
24
+ value[@usage] << input
25
+ else
26
+ value[@usage] = input
27
+ end
28
+
29
+ instance_variable_set("@#{name}", value)
30
+ end
31
+ end
32
+
33
+ ::Thor.include ThorEnhance::CommandMethod
34
+ end
35
+
36
+ def self.included(base)
37
+ base.extend(ClassMethods)
38
+ end
39
+
40
+ module ClassMethods
41
+
42
+ # Call all things super for it (super in thor also calls super as well)
43
+ # If the command exists, then add the initi
44
+ def method_added(meth)
45
+ super(meth)
46
+
47
+ # Skip if the we should not be creating the command
48
+ 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)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ class Configuration
7
+
8
+ # Order is important -- Ensure deoreacte is first
9
+ HOOKERS = [DEPRECATE = :deprecate, HOOK = :hook]
10
+
11
+ class << self
12
+ attr_accessor :allow_changes
13
+
14
+ def allow_changes?(raise_error: true)
15
+ return true if allow_changes.nil?
16
+
17
+ if raise_error
18
+ raise BaseError, "Configuration changes are halted. Unable to change ThorEnhancements"
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def disallow_changes!
25
+ allow_changes = true
26
+ end
27
+ end
28
+
29
+ def inject_thor!
30
+ self.class.allow_changes?
31
+
32
+ ThorEnhance::Option.thor_enhance_injection!
33
+ ThorEnhance::Command.thor_enhance_injection!
34
+ ThorEnhance::CommandMethod.thor_enhance_injection!
35
+ self.class.disallow_changes!
36
+ end
37
+
38
+ def command_method_enhance
39
+ @command_method_enhance ||= {}
40
+ end
41
+
42
+ def option_enhance
43
+ @option_enhance ||= {
44
+ DEPRECATE => { allowed_klasses: [Proc], behavior: :request, required: false },
45
+ HOOK => { allowed_klasses: [Proc], behavior: nil, required: false },
46
+ }
47
+ end
48
+
49
+ # Adding a new method to enhance the overall command
50
+ def add_command_method_enhance(name, allowed_klasses: nil, enums: nil, required: false, repeatable: false)
51
+ self.class.allow_changes?
52
+
53
+ add_to_variable(command_method_enhance, ::Thor::Command.instance_methods, name, allowed_klasses, enums, required, repeatable)
54
+ end
55
+
56
+ # add a new flag on the command option
57
+ def add_option_enhance(name, allowed_klasses: nil, enums: nil, required: false)
58
+ self.class.allow_changes?
59
+
60
+ add_to_variable(option_enhance, ::Thor::Option.instance_methods, name, allowed_klasses, enums, required)
61
+ end
62
+
63
+ private
64
+
65
+ def add_to_variable(storage, methods, name, allowed_klasses, enums, required, repeatable = false)
66
+ # Reject if the name is not a Symbol or a string
67
+ if [String, Symbol].none? { _1 === name }
68
+ raise ArgumentError, "Invalid name type received. Received [#{name}] of type [#{name.class}]. Expected to be of type String or Symbol"
69
+ end
70
+
71
+ # If name contains characters other than upper or lower case letters and _ FAIL
72
+ unless name =~ /^[A-Za-z_]+$/
73
+ raise ArgumentError, "Invalid name received. Received [#{name}] does not match /^[A-Za-z_]+$/."
74
+ end
75
+
76
+ if methods.include?(name.to_sym)
77
+ raise OptionNotAllowed, "[#{name}] is not allowed as an enhancement"
78
+ end
79
+
80
+ if storage.key?(name.to_sym)
81
+ raise OptionNotAllowed, "Duplicate detected. [#{name}] was already added."
82
+ end
83
+
84
+ # if enums is present and not an array
85
+ if !enums.nil? && !enums.is_a?(Array)
86
+ raise ArgumentError, "Recieved enum of #{enums}. When present, it is expected to be an Array"
87
+ end
88
+
89
+ storage[name.to_sym] = { allowed_klasses: allowed_klasses, enums: enums, required: required, repeatable: repeatable }
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module ThorEnhance
6
+ module Option
7
+ def self.thor_enhance_injection!
8
+ return false unless ThorEnhance::Configuration.allow_changes?
9
+
10
+ # Create getter method for the enhance instance variable
11
+ ThorEnhance.configuration.option_enhance.each do |name, object|
12
+ define_method(name) { instance_variable_get("@#{name}") }
13
+ end
14
+
15
+ ::Thor::Option.include ThorEnhance::Option
16
+ end
17
+
18
+ def initialize(name, options = {})
19
+ super
20
+
21
+ thor_enhance_definitions(options)
22
+ end
23
+
24
+ def thor_enhance_definitions(options)
25
+ 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"
28
+ end
29
+
30
+ value = options[name.to_sym]
31
+ if value.nil? && object[:required] == false
32
+ # no op when it is nil and not required
33
+ elsif !object[:enums].nil?
34
+ unless object[:enums].include?(value)
35
+ raise ValidationFailed, "#{@name} recieved option #{name} with incorrect enum. Received: [#{value}]. Expected: [#{object[:enums]}]"
36
+ end
37
+ elsif !object[:allowed_klasses].nil?
38
+ unless object[:allowed_klasses].include?(value.class)
39
+ raise ValidationFailed, "#{@name} recieved option #{name} with incorrect class type. Received: [#{value.class}]. Expected: [#{object[:allowed_klasses]}]"
40
+ end
41
+ end
42
+
43
+ instance_variable_set("@#{name}", value)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThorEnhance
4
+ class Tree
5
+ DEFAULT_IGNORE_COMMANDS = ["help"]
6
+
7
+ def self.add_ignore_commands(command)
8
+ return false if ignore_commands.include?(command)
9
+
10
+ ignore_commands << command
11
+ true
12
+ end
13
+
14
+ def self.ignore_commands
15
+ @ignore_commands ||= DEFAULT_IGNORE_COMMANDS.dup
16
+ end
17
+
18
+ def self.reset_ignore_commands!
19
+ @ignore_commands = DEFAULT_IGNORE_COMMANDS.dup
20
+ end
21
+
22
+ def self.tree(base:, parent: nil)
23
+ base.all_commands.map do |k, command|
24
+ next if ignore_commands.include?(k)
25
+
26
+ [k, new(command: command, base: base, parent: parent)]
27
+ end.compact.to_h
28
+ end
29
+
30
+ attr_reader :command, :base, :parent, :children
31
+
32
+ # command: Thor::Command struct
33
+ # base: Root level class where the command is from
34
+ # parent: 1 level up if nested subcommand
35
+ def initialize(command:, base:, parent: nil)
36
+ @parent = parent
37
+ @base = base
38
+ @command = command
39
+ @children = []
40
+
41
+ if !base.subcommand_classes.nil? && base.subcommand_classes[command.name]
42
+ @children = self.class.tree(parent: self, base: base.subcommand_classes[command.name])
43
+ end
44
+ end
45
+
46
+ def children?
47
+ children.count > 0
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThorEnhance
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/thor_enhance.rb CHANGED
@@ -1,24 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
3
+ require "thor_enhance/command"
4
+ require "thor_enhance/command_hook"
5
+ require "thor_enhance/command_method"
6
+ require "thor_enhance/configuration"
7
+ require "thor_enhance/option"
8
+ require "thor_enhance/tree"
4
9
 
5
10
  module ThorEnhance
11
+ class BaseError < StandardError; end
12
+ class OptionNotAllowed < StandardError; end
13
+ class ValidationFailed < StandardError; end
14
+ class RequiredOption < StandardError; end
15
+ class OptionDeprecated < StandardError; end
6
16
 
7
17
  def self.configure
8
18
  yield configuration if block_given?
19
+
20
+ configuration.inject_thor!
9
21
  end
10
22
 
11
23
  def self.configuration
12
24
  @configuration ||= ThorEnhance::Configuration.new
13
25
  end
14
-
15
- def self.configuration=(object)
16
- raise ConfigError, "Expected configuration to be a ThorEnhance::Configuration" unless object.is_a?(ThorEnhance::Configuration)
17
-
18
- @configuration = object
19
- end
20
-
21
- def self.reset_configuration!
22
- @configuration = ThorEnhance::Configuration.new
23
- end
24
26
  end
data/thor_enhance.gemspec CHANGED
@@ -8,12 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Matt Taylor"]
9
9
  spec.email = ["mattius.taylor@gmail.com"]
10
10
 
11
- spec.summary = ""
12
- spec.description = ""
11
+ spec.summary = "Add hooks and human readable components to Thor Options and Thor Commands"
12
+ spec.description = "Have you ever wanted your thor commands to tell a story of what they are? Or have you ever wanted to deprecate an option over time easily? ThorEnhance allows to to annote methods and commands in a human readable way"
13
13
  spec.homepage = "https://github.com/matt-taylor/thor_enhance"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
16
+ spec.required_ruby_version = Gem::Requirement.new(">= 3")
17
17
 
18
18
  spec.metadata = {
19
19
  "homepage_uri" => spec.homepage,
@@ -29,8 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.add_dependency "class_composer"
33
- spec.add_dependency "faraday"
32
+ spec.add_dependency "thor", "~> 1.3"
34
33
 
35
34
  spec.add_development_dependency "pry-byebug"
36
35
  spec.add_development_dependency "rake", "~> 12.0"
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thor_enhance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.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-19 00:00:00.000000000 Z
11
+ date: 2023-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: class_composer
14
+ name: thor
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: faraday
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
17
+ - - "~>"
32
18
  - !ruby/object:Gem::Version
33
- version: '0'
19
+ version: '1.3'
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - ">="
24
+ - - "~>"
39
25
  - !ruby/object:Gem::Version
40
- version: '0'
26
+ version: '1.3'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: pry-byebug
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,7 +80,9 @@ dependencies:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
82
  version: 0.17.0
97
- description: ''
83
+ description: Have you ever wanted your thor commands to tell a story of what they
84
+ are? Or have you ever wanted to deprecate an option over time easily? ThorEnhance
85
+ allows to to annote methods and commands in a human readable way
98
86
  email:
99
87
  - mattius.taylor@gmail.com
100
88
  executables: []
@@ -114,7 +102,20 @@ files:
114
102
  - bin/console
115
103
  - bin/setup
116
104
  - docker-compose.yml
105
+ - docs/command.md
106
+ - docs/hooks.md
107
+ - docs/method_option.md
108
+ - docs/tree.md
109
+ - examples/basic_example.md
110
+ - examples/example_with_subcommand.md
111
+ - examples/hooks.md
117
112
  - lib/thor_enhance.rb
113
+ - lib/thor_enhance/command.rb
114
+ - lib/thor_enhance/command_hook.rb
115
+ - lib/thor_enhance/command_method.rb
116
+ - lib/thor_enhance/configuration.rb
117
+ - lib/thor_enhance/option.rb
118
+ - lib/thor_enhance/tree.rb
118
119
  - lib/thor_enhance/version.rb
119
120
  - thor_enhance.gemspec
120
121
  homepage: https://github.com/matt-taylor/thor_enhance
@@ -131,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
132
  requirements:
132
133
  - - ">="
133
134
  - !ruby/object:Gem::Version
134
- version: '2.7'
135
+ version: '3'
135
136
  required_rubygems_version: !ruby/object:Gem::Requirement
136
137
  requirements:
137
138
  - - ">="
@@ -141,5 +142,5 @@ requirements: []
141
142
  rubygems_version: 3.4.19
142
143
  signing_key:
143
144
  specification_version: 4
144
- summary: ''
145
+ summary: Add hooks and human readable components to Thor Options and Thor Commands
145
146
  test_files: []