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.
- 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
|