thor_enhance 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -5
- data/Gemfile.lock +27 -7
- data/README.md +30 -0
- data/bin/thor_enhance +22 -0
- data/docs/autogenerate/Readme.md +152 -0
- data/docs/initialization.md +62 -0
- data/docs/method_option.md +0 -1
- data/examples/basic_example.md +1 -0
- data/examples/example_with_subcommand.md +3 -0
- data/examples/hooks.md +2 -1
- data/lib/thor_enhance/autogenerate/command.rb +240 -0
- data/lib/thor_enhance/autogenerate/configuration.rb +72 -0
- data/lib/thor_enhance/autogenerate/option.rb +44 -0
- data/lib/thor_enhance/autogenerate/templates/aggregate_options.rb.erb +12 -0
- data/lib/thor_enhance/autogenerate/templates/command.rb.erb +49 -0
- data/lib/thor_enhance/autogenerate/templates/footer.rb.erb +3 -0
- data/lib/thor_enhance/autogenerate/templates/option.rb.erb +14 -0
- data/lib/thor_enhance/autogenerate/templates/root.rb.erb +9 -0
- data/lib/thor_enhance/autogenerate/validate.rb +95 -0
- data/lib/thor_enhance/autogenerate.rb +78 -0
- data/lib/thor_enhance/base.rb +35 -0
- data/lib/thor_enhance/command.rb +0 -5
- data/lib/thor_enhance/command_method.rb +139 -17
- data/lib/thor_enhance/configuration.rb +104 -11
- data/lib/thor_enhance/option.rb +15 -11
- data/lib/thor_enhance/sample.rb +40 -0
- data/lib/thor_enhance/thor_auto_generate_inject.rb +64 -0
- data/lib/thor_enhance/tree.rb +4 -2
- data/lib/thor_enhance/version.rb +1 -1
- data/lib/thor_enhance.rb +13 -4
- data/thor_enhance.gemspec +1 -5
- metadata +22 -48
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThorEnhance
|
4
|
+
module Autogenerate
|
5
|
+
class Configuration
|
6
|
+
attr_reader :question_headers, :custom_headers, :configuration, :readme_empty_group, :readme_skip_key, :readme_enums
|
7
|
+
|
8
|
+
DEFAULT_SKIP_KEY = :skip
|
9
|
+
|
10
|
+
def initialize(required: false)
|
11
|
+
@required = required
|
12
|
+
@configuration = { add_option_enhance: {}, add_command_method_enhance: {} }
|
13
|
+
@readme_enums = []
|
14
|
+
@custom_headers = []
|
15
|
+
@question_headers = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_default_required(value)
|
19
|
+
@required = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def default
|
23
|
+
ThorEnhance::Configuration.allow_changes?
|
24
|
+
|
25
|
+
example
|
26
|
+
header
|
27
|
+
title
|
28
|
+
readme
|
29
|
+
end
|
30
|
+
|
31
|
+
def title(required: false)
|
32
|
+
ThorEnhance::Configuration.allow_changes?
|
33
|
+
|
34
|
+
required = required.nil? ? @required : required
|
35
|
+
configuration[:add_command_method_enhance][:title] = { repeatable: false, required: required }
|
36
|
+
end
|
37
|
+
|
38
|
+
def example(required: nil, repeatable: true)
|
39
|
+
ThorEnhance::Configuration.allow_changes?
|
40
|
+
|
41
|
+
required = required.nil? ? @required : required
|
42
|
+
configuration[:add_command_method_enhance][:example] = { repeatable: repeatable, required: required, required_kwargs: [:desc] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def header
|
46
|
+
ThorEnhance::Configuration.allow_changes?
|
47
|
+
|
48
|
+
configuration[:add_command_method_enhance][:header] = { repeatable: true, required: false, required_kwargs: [:name, :desc], optional_kwargs: [:tag] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def custom_header(name, question: false, repeatable: false, required: false)
|
52
|
+
ThorEnhance::Configuration.allow_changes?
|
53
|
+
|
54
|
+
raise ArgumentError, "Custom Header name must be unique. #{name} is already defined as a custom header. " if custom_headers.include?(name.to_sym)
|
55
|
+
|
56
|
+
custom_headers << name.to_sym
|
57
|
+
question_headers << name.to_sym if question
|
58
|
+
configuration[:add_command_method_enhance][name.to_sym] = { repeatable: repeatable, required: required, optional_kwargs: [:tag] }
|
59
|
+
end
|
60
|
+
|
61
|
+
def readme(required: nil, empty_group: :unassigned, skip_key: DEFAULT_SKIP_KEY, enums: [:important, :advanced, skip_key.to_sym].compact)
|
62
|
+
ThorEnhance::Configuration.allow_changes?
|
63
|
+
|
64
|
+
@readme_empty_group = empty_group
|
65
|
+
@readme_skip_key = skip_key
|
66
|
+
@readme_enums = enums.map(&:to_sym)
|
67
|
+
required = required.nil? ? @required : required
|
68
|
+
configuration[:add_option_enhance][:readme] = { enums: enums, required: required }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
module ThorEnhance
|
6
|
+
module Autogenerate
|
7
|
+
class Option
|
8
|
+
TEMPLATE_ERB = "#{File.dirname(__FILE__)}/templates/option.rb.erb"
|
9
|
+
OPTION_TEMPLATE = ERB.new(File.read(TEMPLATE_ERB))
|
10
|
+
|
11
|
+
attr_reader :name, :option
|
12
|
+
|
13
|
+
def initialize(name:, option:)
|
14
|
+
@name = name
|
15
|
+
@option = option
|
16
|
+
end
|
17
|
+
|
18
|
+
def template_text
|
19
|
+
text = []
|
20
|
+
text << "# What: #{option.description}"
|
21
|
+
text << "# Type: #{option.type}"
|
22
|
+
text << "# Required: #{option.required}"
|
23
|
+
text << "# Allowed Inputs: #{option.enum}" if option.enum
|
24
|
+
text << invocations.map { "#{_1}"}.join(" | ")
|
25
|
+
|
26
|
+
text.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def invocations
|
30
|
+
base = [option.switch_name] + option.aliases
|
31
|
+
if option.type == :boolean
|
32
|
+
counter = option.switch_name.sub("--", "--no-")
|
33
|
+
base << counter
|
34
|
+
end
|
35
|
+
|
36
|
+
base
|
37
|
+
end
|
38
|
+
|
39
|
+
def readme_type
|
40
|
+
option.readme || ThorEnhance.configuration.autogenerated_config.readme_empty_group
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% ThorEnhance.configuration.autogenerated_config.readme_enums.each_with_index do |group_name, index| %>
|
2
|
+
<% next if method_options[group_name].nil? %>
|
3
|
+
<details <%= "open" if index == 0 %>>
|
4
|
+
<summary> <h3> <%= group_name.to_s.titlecase %> options </h3> </summary>
|
5
|
+
|
6
|
+
```bash
|
7
|
+
<%= method_options[group_name].map { _1.template_text }.join("\n") %>
|
8
|
+
```
|
9
|
+
|
10
|
+
</details>
|
11
|
+
|
12
|
+
<% end %>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# <%= title.to_s.titlecase %>
|
2
|
+
|
3
|
+
## Description
|
4
|
+
<%= command.long_description || command.description %>
|
5
|
+
|
6
|
+
```bash
|
7
|
+
# Base command for `<%= command.usage %>`
|
8
|
+
<%= basename_string %>
|
9
|
+
```
|
10
|
+
|
11
|
+
<%= custom_headers %>
|
12
|
+
|
13
|
+
<% headers.each do |header| %>
|
14
|
+
## <%= header[:name] %>
|
15
|
+
<%= header[:desc]%>
|
16
|
+
<% end %>
|
17
|
+
<% if drawn_out_examples %>
|
18
|
+
---
|
19
|
+
|
20
|
+
## Examples
|
21
|
+
<% drawn_out_examples.each do |ex|%>
|
22
|
+
```bash
|
23
|
+
<%= ex %>
|
24
|
+
```
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<% if !method_options_erb.strip.empty? %>
|
28
|
+
---
|
29
|
+
|
30
|
+
<% if children_descriptors.empty? %>
|
31
|
+
## Method Options
|
32
|
+
|
33
|
+
<%= method_options_erb %>
|
34
|
+
<% end %>
|
35
|
+
<% else %>
|
36
|
+
<% children_descriptors.each do |child| %>
|
37
|
+
### [<%= child[:title]%>](<%= child[:link] %>)
|
38
|
+
|
39
|
+
<%= child[:description] %>
|
40
|
+
|
41
|
+
```bash
|
42
|
+
<%= child[:basename_string]%> <options>
|
43
|
+
<%= child[:examples].map { _1 }.join("\n") %>
|
44
|
+
```
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
---
|
48
|
+
|
49
|
+
<%= footer_erb %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# What: <%= option.description %>
|
2
|
+
# Type: <%= option.type %>
|
3
|
+
# Required: <%= option.required %>
|
4
|
+
# <%= "**Allowed inputs:** #{option.enum}" if option.enum %>
|
5
|
+
# <%= name.to_s.titlecase %>
|
6
|
+
<%= invocations.map { "`#{_1}`"}.join(" | ") %>
|
7
|
+
|
8
|
+
**What:** <%= option.description %><br>
|
9
|
+
**Invocation:** <br>
|
10
|
+
**Type:** <%= option.type %><br>
|
11
|
+
**Default:** <%= option.default || "none" %><br>
|
12
|
+
**Required:** <%= option.required %><br>
|
13
|
+
<%= "**Allowed inputs:** #{option.enum}" if option.enum %>
|
14
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThorEnhance
|
4
|
+
module Autogenerate
|
5
|
+
module Validate
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def validate(options:, root: nil)
|
9
|
+
root_result = validate_root(options: options, root: root)
|
10
|
+
return root_result if root_result[:status] != :pass
|
11
|
+
|
12
|
+
trunk = root_result[:trunk]
|
13
|
+
constant = root_result[:constant]
|
14
|
+
|
15
|
+
subcommand_result = validate_subcommand(options: options, trunk: trunk)
|
16
|
+
return subcommand_result if subcommand_result[:status] != :pass
|
17
|
+
|
18
|
+
trunk = subcommand_result[:trunk]
|
19
|
+
command_result = validate_command(options: options, trunk: trunk, constant: constant)
|
20
|
+
return command_result if command_result[:status] != :pass
|
21
|
+
command = command_result[:command]
|
22
|
+
{ command: command, trunk: trunk, constant: constant, status: :pass }
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_root(options:, root:)
|
26
|
+
begin
|
27
|
+
constant = root || Object.const_get(options.root.to_s)
|
28
|
+
rescue NameError => e
|
29
|
+
msg_array = [
|
30
|
+
"Unable to load provided --root|-r option `#{options.root}`",
|
31
|
+
"Please check the spelling and ensure the klass has loaded"
|
32
|
+
]
|
33
|
+
return { error: e, msg_array: msg_array, status: :fail }
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
trunk = ThorEnhance::Tree.tree(base: constant)
|
38
|
+
rescue TreeFailure => e
|
39
|
+
msg_array = [
|
40
|
+
"--root|-r option is not a Thor klass.",
|
41
|
+
"Please ensure that the provided klass is a child of Thor"
|
42
|
+
]
|
43
|
+
return { error: e, msg_array: msg_array, status: :fail }
|
44
|
+
end
|
45
|
+
|
46
|
+
{ trunk: trunk, constant: constant, status: :pass }
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_command(options:, trunk:, constant:)
|
50
|
+
# Return early when command is not present in the options object
|
51
|
+
command = options.command
|
52
|
+
return { status: :pass, trunk: trunk, command: nil } if command.nil?
|
53
|
+
|
54
|
+
# Return early when command is found in the tree trunk
|
55
|
+
command = trunk.children[options.command] rescue trunk[options.command]
|
56
|
+
return { status: :pass, trunk: trunk, command: command } if command
|
57
|
+
|
58
|
+
# Command option was available but command was not found in the trunk
|
59
|
+
msg_array = ["Failed to find --command|-c `#{options.command}`"]
|
60
|
+
msg_array << "Provided root command `#{constant}`"
|
61
|
+
msg_array << "With Provided subcommand `#{options.subcommand}`" if options.subcommand
|
62
|
+
msg_array << "does not have command `#{options.command}` as a child" if options.subcommand
|
63
|
+
|
64
|
+
{ msg_array: msg_array, status: :fail }
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_subcommand(options:, trunk:)
|
68
|
+
subcommands = options.subcommand
|
69
|
+
return { trunk: trunk, status: :pass } if subcommands.nil?
|
70
|
+
|
71
|
+
subcommands = subcommands.dup
|
72
|
+
subcommand = subcommands.shift
|
73
|
+
temp_trunk = trunk[subcommand]
|
74
|
+
while subcommand != nil
|
75
|
+
if temp_trunk.nil? || !temp_trunk.children?
|
76
|
+
msg_array = [
|
77
|
+
"Order is important with --subcommands|-s options",
|
78
|
+
"Provided with: #{options.subcommand}",
|
79
|
+
"Subcommand `#{subcommand}` does not have any child commands",
|
80
|
+
"Every provided subcommand must have children",
|
81
|
+
"If the subcommand `#{subcommand}` is meant to be a command",
|
82
|
+
"Pass `#{subcommand}` in as `--command|-c #{subcommand}` instead",
|
83
|
+
]
|
84
|
+
return { msg_array: msg_array, status: :fail }
|
85
|
+
end
|
86
|
+
subcommand = subcommands.shift
|
87
|
+
# Will always be in the child hash at this point if subcommand exists
|
88
|
+
temp_trunk = temp_trunk.children[subcommand] if subcommand
|
89
|
+
end
|
90
|
+
|
91
|
+
{ trunk: temp_trunk, subcommands: options.subcommand, status: :pass }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor_enhance/autogenerate/configuration"
|
4
|
+
require "thor_enhance/autogenerate/validate"
|
5
|
+
require "thor_enhance/autogenerate/option"
|
6
|
+
require "thor_enhance/autogenerate/command"
|
7
|
+
|
8
|
+
module ThorEnhance
|
9
|
+
module Autogenerate
|
10
|
+
module_function
|
11
|
+
|
12
|
+
ROOT_ERB = "#{File.dirname(__FILE__)}/autogenerate/templates/root.rb.erb"
|
13
|
+
ROOT_TEMPLATE = ERB.new(File.read(ROOT_ERB))
|
14
|
+
|
15
|
+
def execute!(options:, basename: File.basename($0), root: nil)
|
16
|
+
validate_result = Validate.validate(options: options, root: root)
|
17
|
+
return validate_result if validate_result[:status] != :pass
|
18
|
+
|
19
|
+
command = validate_result[:command]
|
20
|
+
trunk = validate_result[:trunk]
|
21
|
+
leaves =
|
22
|
+
if command
|
23
|
+
{ options.command => command }
|
24
|
+
elsif Hash === trunk
|
25
|
+
# Parent trunk is a hash -- structure needs to change
|
26
|
+
trunk
|
27
|
+
else
|
28
|
+
# if not parent, grab the children
|
29
|
+
trunk.children
|
30
|
+
end
|
31
|
+
|
32
|
+
command_structure = leaves.map do |name, leaf|
|
33
|
+
parent = Command.new(name: name, leaf: leaf, basename: basename)
|
34
|
+
end
|
35
|
+
|
36
|
+
# flatten_children returns all kids, grandkids, great grandkids etc of the commands returned from the above mapping
|
37
|
+
youthful_kids = command_structure.map(&:flatten_children).flatten
|
38
|
+
|
39
|
+
# this is a flat map of the entire family tree. Each node knows where it is so we can flatten it
|
40
|
+
family_tree = command_structure + youthful_kids
|
41
|
+
|
42
|
+
save_generated_readmes!(commands: family_tree, generated_root: options.generated_root, apply: options.apply)
|
43
|
+
end
|
44
|
+
|
45
|
+
def save_generated_readmes!(commands:, generated_root:, apply:)
|
46
|
+
parent = generated_root || ENV["THOR_ENHANCE_GENERATED_ROOT_PATH"]
|
47
|
+
full_root = "#{parent}/commands"
|
48
|
+
saved_status = commands.map do |command|
|
49
|
+
command.save_self!(root: full_root, apply: apply)
|
50
|
+
end
|
51
|
+
self_for_roots = saved_status.collect { _1[:self_for_root] }
|
52
|
+
saved_status << root_savior!(apply: apply, full_root: full_root, self_for_roots: self_for_roots)
|
53
|
+
|
54
|
+
{ status: :pass, saved_status: saved_status }
|
55
|
+
end
|
56
|
+
|
57
|
+
def root_savior!(full_root:, self_for_roots:, apply:)
|
58
|
+
full_path = "#{full_root}/Readme.md"
|
59
|
+
root_erb_result = self_for_roots.map do |root_child|
|
60
|
+
ROOT_TEMPLATE.result_with_hash({ root_child: root_child })
|
61
|
+
end.join("\n")
|
62
|
+
|
63
|
+
FileUtils.mkdir_p(full_root)
|
64
|
+
if File.exist?(full_path)
|
65
|
+
content = File.read(full_path)
|
66
|
+
diff = root_erb_result == content ? :same : :overwite
|
67
|
+
else
|
68
|
+
diff = :new
|
69
|
+
end
|
70
|
+
|
71
|
+
if apply
|
72
|
+
File.write(full_path, root_erb_result)
|
73
|
+
end
|
74
|
+
|
75
|
+
{ path: full_path, diff: diff, apply: apply }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -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
|
+
|
data/lib/thor_enhance/command.rb
CHANGED
@@ -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
|
@@ -8,29 +8,52 @@ 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
|
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
|
-
#
|
15
|
-
#
|
16
|
-
ClassMethods.define_method("#{name}") do |input|
|
17
|
-
|
18
|
-
|
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
|
17
|
+
ClassMethods.define_method("#{name}") do |input = nil, *args, **kwargs|
|
18
|
+
return nil unless ThorEnhance.configuration.allowed?(self)
|
19
|
+
|
20
|
+
# Usage is nil when the `desc` has not been defined yet -- Under normal circumstance this will never happen
|
19
21
|
if @usage.nil?
|
20
22
|
raise ArgumentError, "Usage is not set. Please ensure `#{name}` is defined after usage is set"
|
21
23
|
end
|
24
|
+
|
25
|
+
__thor_enhance_validate_arguments!(object, input, args, kwargs)
|
26
|
+
value = instance_variable_get("@#{name}")
|
27
|
+
value ||= {}
|
28
|
+
|
29
|
+
# Required check gets done on command initialization (defined below in ClassMethods)
|
30
|
+
if input.nil?
|
31
|
+
# no op when it is nil
|
32
|
+
elsif !object[:enums].nil?
|
33
|
+
unless object[:enums].include?(input)
|
34
|
+
raise ValidationFailed, "#{@usage} recieved command method `#{name}` with incorrect enum. Received: [#{input}]. Expected: [#{object[:enums]}]"
|
35
|
+
end
|
36
|
+
elsif !object[:allowed_klasses].nil?
|
37
|
+
unless object[:allowed_klasses].include?(value.class)
|
38
|
+
raise ValidationFailed, "#{@usage} recieved command method `#{name}` with incorrect class type. Received: [#{input.class}]. Expected: #{object[:allowed_klasses]}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
22
42
|
if object[:repeatable]
|
23
43
|
value[@usage] ||= []
|
24
|
-
value[@usage] << input
|
44
|
+
value[@usage] << { input: input, arguments: { kwargs: kwargs, positional: args } }
|
25
45
|
else
|
26
|
-
value[@usage]
|
46
|
+
if value[@usage]
|
47
|
+
raise ValidationFailed, "#{@usage} recieved command method `#{name}` with repeated invocations of " \
|
48
|
+
"`#{name}`. Please remove the secondary invocation. Or set `#{name}` as a repeatable command method"
|
49
|
+
end
|
50
|
+
|
51
|
+
value[@usage] = { input: input, arguments: { kwargs: kwargs, positional: args } }
|
27
52
|
end
|
28
53
|
|
29
54
|
instance_variable_set("@#{name}", value)
|
30
55
|
end
|
31
56
|
end
|
32
|
-
|
33
|
-
::Thor.include ThorEnhance::CommandMethod
|
34
57
|
end
|
35
58
|
|
36
59
|
def self.included(base)
|
@@ -38,20 +61,119 @@ module ThorEnhance
|
|
38
61
|
end
|
39
62
|
|
40
63
|
module ClassMethods
|
64
|
+
THOR_ENHANCE_ENABLE = :enable
|
65
|
+
THOR_ENHANCE_DISABLE = :disable
|
66
|
+
|
67
|
+
def disable_thor_enhance!(&block)
|
68
|
+
__thor_enhance_access(type: THOR_ENHANCE_DISABLE, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def enable_thor_enhance!(&block)
|
72
|
+
__thor_enhance_access(type: THOR_ENHANCE_ENABLE, &block)
|
73
|
+
end
|
41
74
|
|
42
75
|
# Call all things super for it (super in thor also calls super as well)
|
43
|
-
# If the command exists, then
|
76
|
+
# If the command exists, then set the instance variable
|
44
77
|
def method_added(meth)
|
45
|
-
super(meth)
|
78
|
+
value = super(meth)
|
46
79
|
|
47
|
-
# Skip if the
|
80
|
+
# Skip if the command does not exist -- Super creates the command
|
48
81
|
if command = all_commands[meth.to_s]
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
82
|
+
|
83
|
+
if ThorEnhance.configuration.allowed?(self)
|
84
|
+
ThorEnhance.configuration.command_method_enhance.each do |name, object|
|
85
|
+
|
86
|
+
instance_variable = instance_variable_get("@#{name}")
|
87
|
+
# instance variable was correctly assigned and exists as a hash
|
88
|
+
if Hash === instance_variable
|
89
|
+
# Expected key exists in the hash
|
90
|
+
# This key already passed validation for type and enum
|
91
|
+
# Set it and move on
|
92
|
+
if instance_variable.key?(meth.to_s)
|
93
|
+
value = instance_variable[meth.to_s]
|
94
|
+
command.send("#{name}=", value)
|
95
|
+
next
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# At this point, the key command method was never invoked on for the `name` thor task
|
100
|
+
# The value is nil/unset
|
101
|
+
|
102
|
+
# If we have disabled required operations, go ahead and skip this
|
103
|
+
next if ::Thor.__thor_enhance_definition == ThorEnhance::CommandMethod::ClassMethods::THOR_ENHANCE_DISABLE
|
104
|
+
|
105
|
+
# Skip if the expected command method was not required
|
106
|
+
next unless object[:required]
|
107
|
+
|
108
|
+
# Skip if the method is part of the ignore list
|
109
|
+
next if ThorEnhance::Tree.ignore_commands.include?(meth.to_s)
|
110
|
+
|
111
|
+
# subcommands/subtasks need not require things that regular commands need
|
112
|
+
# If user wants them on the sucommand, thats cool, but we will never enforce it
|
113
|
+
next if subcommands.map(&:to_s).include?(meth.to_s)
|
114
|
+
|
115
|
+
# At this point, the command method is missing, we are not in disable mode, and the command method was required
|
116
|
+
# raise all hell
|
117
|
+
raise ThorEnhance::RequiredOption, "`#{meth}` does not have required command method #{name} invoked. " \
|
118
|
+
"Ensure it is added after the `desc` task is invoked"
|
119
|
+
end
|
53
120
|
end
|
54
121
|
end
|
122
|
+
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
def __thor_enhance_definition
|
127
|
+
@__thor_enhance_definition
|
128
|
+
end
|
129
|
+
|
130
|
+
def __thor_enhance_definition=(value)
|
131
|
+
@__thor_enhance_definition = value
|
132
|
+
end
|
133
|
+
|
134
|
+
def __thor_enhance_definition_stack
|
135
|
+
@__thor_enhance_definition_stack ||= []
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
|
141
|
+
def __thor_enhance_validate_arguments!(object, input, args, kwargs)
|
142
|
+
expected_arity = object.dig(:arity)
|
143
|
+
if args.length != expected_arity
|
144
|
+
raise ArgumentError, "Excluding #{input}, the expected arity command method `#{name}` for #{@usage} is #{expected_arity}. Provided #{args.length}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# checks if there are extra kwargs present that are not expected
|
148
|
+
available_kwargs = object.dig(:kwargs).keys
|
149
|
+
extra_keys = kwargs.keys - available_kwargs
|
150
|
+
unless extra_keys.empty?
|
151
|
+
raise ArgumentError, "#{@usage} received command method `#{name}` with unknown KWargs #{extra_keys}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Checks if all the required kwargs are present
|
155
|
+
req_kwargs = object.dig(:kwargs).select { _2 }.keys
|
156
|
+
missing_required_kwargs = req_kwargs - kwargs.keys
|
157
|
+
|
158
|
+
# binding.pry if expected_arity > 0 || available_kwargs.length > 0
|
159
|
+
return if missing_required_kwargs.empty?
|
160
|
+
|
161
|
+
raise ArgumentError, "#{@usage} received command method `#{name}` with missing KWargs #{missing_required_kwargs}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def __thor_enhance_access(type:, &block)
|
165
|
+
raise ArgumentError, "Expected to receive block. No block given" unless block_given?
|
166
|
+
|
167
|
+
# capture original value. This allows us to do nested enable/disables
|
168
|
+
::Thor.__thor_enhance_definition_stack << ::Thor.__thor_enhance_definition.dup
|
169
|
+
::Thor.__thor_enhance_definition = type
|
170
|
+
|
171
|
+
yield
|
172
|
+
|
173
|
+
nil
|
174
|
+
ensure
|
175
|
+
# Return the state to the most recently set stack
|
176
|
+
::Thor.__thor_enhance_definition = ::Thor.__thor_enhance_definition_stack.pop
|
55
177
|
end
|
56
178
|
end
|
57
179
|
end
|