thor_enhance 0.1.0 → 0.2.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: 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: []