thor_enhance 0.3.0 → 0.5.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.
@@ -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
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,13 +30,92 @@ 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
 
61
+ def readme_enhance!(required: false, &block)
62
+ # only inject readme things if it is required client side
63
+ require "thor_enhance/thor_auto_generate_inject"
64
+ require "thor_enhance/autogenerate"
65
+
66
+ if defined?(@autogenerated_config)
67
+ raise ValidationFailed, "ReadMe Enhance has already been initialized."
68
+ end
69
+
70
+ autogenerated_config.set_default_required(required)
71
+ autogenerated_config.default
72
+ yield(autogenerated_config) if block_given?
73
+
74
+ autogenerated_config.configuration.each do |meth, object|
75
+ object.each do |name, args|
76
+ public_send(meth, name, **args)
77
+ end
78
+ end
79
+ end
80
+
81
+ def autogenerated_config
82
+ @autogenerated_config ||= Autogenerate::Configuration.new
83
+ end
84
+
38
85
  def command_method_enhance
39
86
  @command_method_enhance ||= {}
40
87
  end
41
88
 
89
+ def allowed_klasses
90
+ @klass_procs ||= []
91
+ end
92
+
93
+ def basename
94
+ @basename
95
+ end
96
+
97
+ def basename=(name)
98
+ @basename = name
99
+ end
100
+
101
+ def allowed
102
+ @allowed ||= DEFAULT_ALLOWED
103
+ end
104
+
105
+ def allowed=(value)
106
+ raise ArgumentError, "Unexpected value for `allowed =`. Given: #{value}. Expected one of #{ALLOWED_VALUES}" unless ALLOWED_VALUES.include?(value)
107
+
108
+ @allowed = value
109
+ end
110
+
111
+ def allowed?(klass)
112
+ return true if allowed == ALLOW_ALL
113
+ return true if allowed_klasses.include?(klass)
114
+
115
+ # At this point, allowed is false and not an includable klass -- dont allow
116
+ false
117
+ end
118
+
42
119
  def option_enhance
43
120
  @option_enhance ||= {
44
121
  DEPRECATE => { allowed_klasses: [Proc], behavior: :request, required: false },
@@ -47,14 +124,21 @@ module ThorEnhance
47
124
  end
48
125
 
49
126
  # 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)
127
+ def add_command_method_enhance(name, allowed_klasses: nil, enums: nil, required: false, repeatable: false, arity: 0, required_kwargs: [], optional_kwargs: [])
128
+ return if ENV["SKIP_TESTING_METHOD"]
129
+
51
130
  self.class.allow_changes?
52
131
 
53
- add_to_variable(command_method_enhance, ::Thor::Command.instance_methods, name, allowed_klasses, enums, required, repeatable)
132
+ kwargs = {}
133
+ required_kwargs.each { kwargs[_1.to_sym] = true }
134
+ optional_kwargs.each { kwargs[_1.to_sym] = false }
135
+ add_to_variable(command_method_enhance, ::Thor::Command.instance_methods, name, allowed_klasses, enums, required, repeatable, arity, kwargs)
54
136
  end
55
137
 
56
138
  # add a new flag on the command option
57
139
  def add_option_enhance(name, allowed_klasses: nil, enums: nil, required: false)
140
+ return if ENV["SKIP_TESTING_METHOD"]
141
+
58
142
  self.class.allow_changes?
59
143
 
60
144
  add_to_variable(option_enhance, ::Thor::Option.instance_methods, name, allowed_klasses, enums, required)
@@ -62,7 +146,7 @@ module ThorEnhance
62
146
 
63
147
  private
64
148
 
65
- def add_to_variable(storage, methods, name, allowed_klasses, enums, required, repeatable = false)
149
+ def add_to_variable(storage, methods, name, allowed_klasses, enums, required, repeatable = false, arity = 0, kwargs = false)
66
150
  # Reject if the name is not a Symbol or a string
67
151
  if [String, Symbol].none? { _1 === name }
68
152
  raise ArgumentError, "Invalid name type received. Received [#{name}] of type [#{name.class}]. Expected to be of type String or Symbol"
@@ -83,10 +167,19 @@ module ThorEnhance
83
167
 
84
168
  # if enums is present and not an array
85
169
  if !enums.nil? && !enums.is_a?(Array)
86
- raise ArgumentError, "Recieved enum of #{enums}. When present, it is expected to be an Array"
170
+ raise ArgumentError, "Recieved enums with #{enums}. When present, it is expected to be an Array"
171
+ end
172
+
173
+ # if allowed_klasses is present and not an array
174
+ if !allowed_klasses.nil? && !allowed_klasses.is_a?(Array)
175
+ raise ArgumentError, "Recieved allowed_klasses with #{allowed_klasses}. When present, it is expected to be an Array"
176
+ end
177
+
178
+ if arity < 0
179
+ raise ArgumentError, "Recieved arity with #{arity}. When present, it is expected to be greater than 0"
87
180
  end
88
181
 
89
- storage[name.to_sym] = { allowed_klasses: allowed_klasses, enums: enums, required: required, repeatable: repeatable }
182
+ storage[name.to_sym] = { allowed_klasses: allowed_klasses, enums: enums, required: required, repeatable: repeatable, kwargs: kwargs, arity: arity }
90
183
  end
91
184
  end
92
185
  end
@@ -5,30 +5,34 @@ require "thor"
5
5
  module ThorEnhance
6
6
  module Option
7
7
  def self.thor_enhance_injection!
8
- return false unless ThorEnhance::Configuration.allow_changes?
9
-
10
8
  # Create getter method for the enhance instance variable
11
9
  ThorEnhance.configuration.option_enhance.each do |name, object|
12
10
  define_method(name) { instance_variable_get("@#{name}") }
13
11
  end
14
-
15
- ::Thor::Option.include ThorEnhance::Option
16
12
  end
17
13
 
18
- def initialize(name, options = {})
19
- super
14
+ # Monkey patched initializer
15
+ # Thor Option initializer only takes (name, options) as arguments
16
+ # Thor::Option.new is only called from `build_option` which gets monkey patched in thor_enhance/base
17
+ def initialize(name, options = {}, klass = nil)
18
+ super(name, options)
20
19
 
21
- thor_enhance_definitions(options)
20
+ thor_enhance_definitions(options, klass)
22
21
  end
23
22
 
24
- def thor_enhance_definitions(options)
23
+ def thor_enhance_definitions(options, klass)
24
+ return nil unless ThorEnhance.configuration.allowed?(klass)
25
+
25
26
  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"
27
+ # When disabled, we do not do the required check, if present, it is still required to be a valid option otherwise
28
+ unless ::Thor.__thor_enhance_definition == ThorEnhance::CommandMethod::ClassMethods::THOR_ENHANCE_DISABLE
29
+ if options[name.to_sym].nil? && object[:required]
30
+ raise RequiredOption, "#{@name} does not have required option #{name}. Please add it to the option"
31
+ end
28
32
  end
29
33
 
30
34
  value = options[name.to_sym]
31
- if value.nil? && object[:required] == false
35
+ if value.nil? # This can be nil here because we have already done a required check
32
36
  # no op when it is nil and not required
33
37
  elsif !object[:enums].nil?
34
38
  unless object[:enums].include?(value)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor_enhance"
4
+
5
+ module ThorEnhance
6
+ class Sample < Thor
7
+ thor_enhance_allow!
8
+
9
+ class SubCommand < Thor
10
+ thor_enhance_allow!
11
+
12
+ desc "innard", "Validate that a subcommand works. This description can be as long as you want it to be."
13
+ long_desc "Wow, This longer description will take precedence over the desc above. This is what will be shown in the readme autogenerated page. Try me out!"
14
+ example "innard --count 5", desc: "Innard sub command with a count of 5"
15
+ example "innard --count 35", desc: "Innard sub command with a count of 35"
16
+ header name: "Deprecation warning", desc: "This command will get deprecated in the next major version"
17
+ when_should_i_use_this "Use sub command task to validate that subocommands work as expected"
18
+ method_option :count, type: :numeric, readme: :skip
19
+ def innard;end;
20
+ end
21
+
22
+ desc "sub", "Thor sub command validation for thor enhance"
23
+ subcommand "sub", SubCommand
24
+
25
+ desc "sample", "This Sample command does a lot of nothing"
26
+ when_should_i_use_this <<~README
27
+ Have you ever wanted your code to be useless?
28
+ Well, this command does absolutely nothing.
29
+ This output is to say that this command does absolutely nothing
30
+ README
31
+ how_does_this_help "Honestly, this does not help at all", tag: "h4"
32
+ how_does_this_help "But its cool because it is a repatable command", tag: "h1"
33
+ example "sample", desc: "yo yo ma"
34
+ example "sample --boolean", desc: "yo yo ma"
35
+ method_option :boolean, aliases: "-b", type: :boolean, desc: "Just a normal boolean", readme: :important
36
+ def sample
37
+ Kernel.puts "Executed Sample method"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ ####################
4
+ #
5
+ # Injects a method directly into the thor base class
6
+ # This allows the developer to have this convenience method
7
+ # Just by utilizing ThorEnhance
8
+ #
9
+ ####################
10
+ require "thor"
11
+
12
+ class Thor
13
+ desc "thor_enhance_autogenerate", "Auto Generate ReadMe material for your Thor commands"
14
+ method_option :subcommand, aliases: "-s", type: :string, repeatable: true, desc: "When provided, autogeneration will execute on the subcommand"
15
+ method_option :command, aliases: "-c", type: :string, desc: "When provided, autogeneration will occur only on this method. Note: When used with subcommand, method must exist on subcommand"
16
+ method_option :basename, aliases: "-b", type: :string, desc: "The name of the file that executes the Thor script"
17
+ method_option :generated_root, aliases: "-r", type: :string, default: File.expand_path("generated_readme"), desc: "The root location to store autogenerated files"
18
+ method_option :apply, aliases: "-a", type: :boolean, desc: "When comfortable with the changes made, enabling apply will save changes to generated files"
19
+
20
+ def thor_enhance_autogenerate
21
+ require "thor_enhance/autogenerate"
22
+ basename = options.basename || ThorEnhance.basename || File.basename($0)
23
+
24
+ result = ThorEnhance::Autogenerate.execute!(options: options, root: self.class, basename: basename)
25
+
26
+ if result[:status] == :pass
27
+ __auto_generate_success!(result[:saved_status])
28
+ else
29
+ __auto_generate_fail!(result[:msg_array])
30
+ end
31
+ end
32
+
33
+ no_tasks do
34
+ def __auto_generate_success!(statuses)
35
+ if statuses.all? { _1[:apply] == true }
36
+ say "Readme changes are enabled", [:green, :bold], true
37
+ else
38
+ say "Readme changes are not enabled. To apply changes, add `--apply` to the command", [:on_white, :black], true
39
+ end
40
+ statuses.each do |status|
41
+ case status[:diff]
42
+ when :new
43
+ say " Added : #{status[:path]}", [:green, :bold], true
44
+ when :same
45
+ say " No Change: #{status[:path]}", [:yellow, :bold], true
46
+ when :overwite
47
+ say " Changes : #{status[:path]}", [:cyan, :bold], true
48
+ else
49
+ say " : #{status[:path]}", [:bold], true
50
+ end
51
+ end
52
+ end
53
+
54
+ def __auto_generate_fail!(msg_array)
55
+ say_error set_color("*********************** FAILED OPERATION ***********************", :red, :bold)
56
+ say_error set_color("FAIL: Unable to continue", :red, :bold)
57
+ msg_array.each do |line|
58
+ say_error set_color("FAIL: #{line}", :red, :bold)
59
+ end
60
+ say_error set_color("*********************** FAILED OPERATION ***********************", :red, :bold)
61
+ exit 1
62
+ end
63
+ end
64
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ThorEnhance
4
4
  class Tree
5
- DEFAULT_IGNORE_COMMANDS = ["help"]
5
+ DEFAULT_IGNORE_COMMANDS = ["help", "thor_enhance_autogenerate"]
6
6
 
7
7
  def self.add_ignore_commands(command)
8
8
  return false if ignore_commands.include?(command)
@@ -20,6 +20,8 @@ module ThorEnhance
20
20
  end
21
21
 
22
22
  def self.tree(base:, parent: nil)
23
+ raise TreeFailure, "#{base} does not respond to all_commands. Unable to continue" unless base.respond_to?(:all_commands)
24
+
23
25
  base.all_commands.map do |k, command|
24
26
  next if ignore_commands.include?(k)
25
27
 
@@ -36,7 +38,7 @@ module ThorEnhance
36
38
  @parent = parent
37
39
  @base = base
38
40
  @command = command
39
- @children = []
41
+ @children = {}
40
42
 
41
43
  if !base.subcommand_classes.nil? && base.subcommand_classes[command.name]
42
44
  @children = self.class.tree(parent: self, base: base.subcommand_classes[command.name])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThorEnhance
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/thor_enhance.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support"
4
+
5
+ require "thor_enhance/base"
3
6
  require "thor_enhance/command"
4
7
  require "thor_enhance/command_hook"
5
8
  require "thor_enhance/command_method"
@@ -9,10 +12,16 @@ require "thor_enhance/tree"
9
12
 
10
13
  module ThorEnhance
11
14
  class BaseError < StandardError; end
12
- class OptionNotAllowed < StandardError; end
13
- class ValidationFailed < StandardError; end
14
- class RequiredOption < StandardError; end
15
- class OptionDeprecated < StandardError; end
15
+ class OptionNotAllowed < BaseError; end
16
+ class ValidationFailed < BaseError; end
17
+ class RequiredOption < BaseError; end
18
+ class OptionDeprecated < BaseError; end
19
+ class TreeFailure < BaseError; end
20
+ class AutoGenerateFailure < BaseError; end
21
+
22
+ def self.basename
23
+ configuration.basename
24
+ end
16
25
 
17
26
  def self.configure
18
27
  yield configuration if block_given?
data/thor_enhance.gemspec CHANGED
@@ -30,9 +30,5 @@ 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"
33
+ spec.add_dependency "activesupport", ">=6"
38
34
  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.3.0
4
+ version: 0.5.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-22 00:00:00.000000000 Z
11
+ date: 2023-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -25,61 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: pry-byebug
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
33
+ version: '6'
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
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
40
+ version: '6'
83
41
  description: Have you ever wanted your thor commands to tell a story of what they
84
42
  are? Or have you ever wanted to deprecate an option over time easily? ThorEnhance
85
43
  allows to to annote methods and commands in a human readable way
@@ -101,20 +59,36 @@ files:
101
59
  - README.md
102
60
  - bin/console
103
61
  - bin/setup
62
+ - bin/thor_enhance
104
63
  - docker-compose.yml
64
+ - docs/autogenerate/Readme.md
105
65
  - docs/command.md
106
66
  - docs/hooks.md
67
+ - docs/initialization.md
107
68
  - docs/method_option.md
108
69
  - docs/tree.md
109
70
  - examples/basic_example.md
110
71
  - examples/example_with_subcommand.md
111
72
  - examples/hooks.md
112
73
  - lib/thor_enhance.rb
74
+ - lib/thor_enhance/autogenerate.rb
75
+ - lib/thor_enhance/autogenerate/command.rb
76
+ - lib/thor_enhance/autogenerate/configuration.rb
77
+ - lib/thor_enhance/autogenerate/option.rb
78
+ - lib/thor_enhance/autogenerate/templates/aggregate_options.rb.erb
79
+ - lib/thor_enhance/autogenerate/templates/command.rb.erb
80
+ - lib/thor_enhance/autogenerate/templates/footer.rb.erb
81
+ - lib/thor_enhance/autogenerate/templates/option.rb.erb
82
+ - lib/thor_enhance/autogenerate/templates/root.rb.erb
83
+ - lib/thor_enhance/autogenerate/validate.rb
84
+ - lib/thor_enhance/base.rb
113
85
  - lib/thor_enhance/command.rb
114
86
  - lib/thor_enhance/command_hook.rb
115
87
  - lib/thor_enhance/command_method.rb
116
88
  - lib/thor_enhance/configuration.rb
117
89
  - lib/thor_enhance/option.rb
90
+ - lib/thor_enhance/sample.rb
91
+ - lib/thor_enhance/thor_auto_generate_inject.rb
118
92
  - lib/thor_enhance/tree.rb
119
93
  - lib/thor_enhance/version.rb
120
94
  - thor_enhance.gemspec