thor_enhance 0.1.0 → 0.2.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: b07825b4f1216de172d6ce7b7ec2052bccee6793c38ceca8b50f094bcbf3deae
4
+ data.tar.gz: 4458ad58f0148f4a77da324893a538e40c1def979eb26305cdb2e005afa500f7
5
5
  SHA512:
6
- metadata.gz: 9640892f989be7e49445b89c5345b4993b29203bd0d58cf638244bb92f861154e11afa19408a4b69f6e09b828369c9d7fee55f81725639bf6cf8ef34c58ca1a0
7
- data.tar.gz: a1dbc442d2a09e0aed8a50f08eee393e9b2a67624dea27629d4951d88472c060cbff102364a7953310d74af531fe75eb11bb680f1488a0faa62b023ab3717c0b
6
+ metadata.gz: a10dfebf316a1ad8a5c6b63a91f90b7fe95391b3f40379bbe37dcfd2e89f9cffe8d48a975fa147210a229376ec736927301eaa042def05a146960646ac0e81ba
7
+ data.tar.gz: 0d96db3833f42a605f7c341dd8a0a43788f1d447767df9d632a0950543bec63f05adc620b8c298b46184e44f236ac39692ecafe7cf857c091261ecda005e3031
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
 
14
14
  # generated gems
15
15
  *.gem
16
+
17
+ spec/examples.txt
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.2.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
@@ -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,40 @@
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)
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}")
33
+ end
34
+ end
35
+ end
36
+
37
+ super
38
+ end
39
+ end
40
+ 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,83 @@
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, WARNING = :warn, 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
+ WARNING => { allowed_klasses: [Proc], behavior: :warn, required: false },
45
+ DEPRECATE => { allowed_klasses: [Proc], behavior: :raise, required: false },
46
+ HOOK => { allowed_klasses: [Proc], behavior: nil, required: false },
47
+ }
48
+ end
49
+
50
+ # Adding a new method to enhance the overall command
51
+ def add_command_method_enhance(name, allowed_klasses: nil, enums: nil, required: false, repeatable: false)
52
+ self.class.allow_changes?
53
+
54
+ add_to_variable(command_method_enhance, ::Thor::Command.instance_methods, name, allowed_klasses, enums, required, repeatable)
55
+ end
56
+
57
+ # add a new flag on the command option
58
+ def add_option_enhance(name, allowed_klasses: nil, enums: nil, required: false)
59
+ self.class.allow_changes?
60
+
61
+ add_to_variable(option_enhance, ::Thor::Option.instance_methods, name, allowed_klasses, enums, required)
62
+ end
63
+
64
+ private
65
+
66
+ def add_to_variable(storage, methods, name, allowed_klasses, enums, required, repeatable = false)
67
+ if methods.include?(name.to_sym)
68
+ raise OptionNotAllowed, "[#{name}] is not allowed as an enhancement"
69
+ end
70
+
71
+ if storage.key?(name.to_sym)
72
+ raise OptionNotAllowed, "Duplicate detected. [#{name}] was already added."
73
+ end
74
+
75
+ # if enums is present and not an array
76
+ if !enums.nil? && !enums.is_a?(Array)
77
+ raise ArgumentError, "Recieved enum of #{enums}. When present, it is expected to be an Array"
78
+ end
79
+
80
+ storage[name.to_sym] = { allowed_klasses: allowed_klasses, enums: enums, required: required, repeatable: repeatable }
81
+ end
82
+ end
83
+ 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.2.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.2.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-21 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: []
@@ -115,6 +103,12 @@ files:
115
103
  - bin/setup
116
104
  - docker-compose.yml
117
105
  - lib/thor_enhance.rb
106
+ - lib/thor_enhance/command.rb
107
+ - lib/thor_enhance/command_hook.rb
108
+ - lib/thor_enhance/command_method.rb
109
+ - lib/thor_enhance/configuration.rb
110
+ - lib/thor_enhance/option.rb
111
+ - lib/thor_enhance/tree.rb
118
112
  - lib/thor_enhance/version.rb
119
113
  - thor_enhance.gemspec
120
114
  homepage: https://github.com/matt-taylor/thor_enhance
@@ -131,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
125
  requirements:
132
126
  - - ">="
133
127
  - !ruby/object:Gem::Version
134
- version: '2.7'
128
+ version: '3'
135
129
  required_rubygems_version: !ruby/object:Gem::Requirement
136
130
  requirements:
137
131
  - - ">="
@@ -141,5 +135,5 @@ requirements: []
141
135
  rubygems_version: 3.4.19
142
136
  signing_key:
143
137
  specification_version: 4
144
- summary: ''
138
+ summary: Add hooks and human readable components to Thor Options and Thor Commands
145
139
  test_files: []