thor_enhance 0.4.0 → 0.5.1
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 +3 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +36 -8
- data/README.md +4 -2
- data/bin/thor_enhance +22 -0
- data/docs/autogenerate/Readme.md +166 -0
- data/generated_readme/commands/Readme.md +34 -0
- data/generated_readme/commands/autobots.md +69 -0
- data/generated_readme/commands/sample.md +84 -0
- data/generated_readme/commands/sub/Readme.md +37 -0
- data/generated_readme/commands/sub/innard.md +46 -0
- data/lib/thor_enhance/autogenerate/command.rb +334 -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 +11 -0
- data/lib/thor_enhance/autogenerate/templates/class_options.rb.erb +7 -0
- data/lib/thor_enhance/autogenerate/templates/command.rb.erb +54 -0
- data/lib/thor_enhance/autogenerate/templates/footer.rb.erb +2 -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 +87 -0
- data/lib/thor_enhance/command_method.rb +42 -6
- data/lib/thor_enhance/configuration.rb +47 -4
- data/lib/thor_enhance/option.rb +0 -2
- data/lib/thor_enhance/sample.rb +58 -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 +12 -4
- data/thor_enhance.gemspec +1 -0
- metadata +36 -2
@@ -0,0 +1,334 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/inflections"
|
4
|
+
require "thor_enhance/autogenerate/option"
|
5
|
+
require "erb"
|
6
|
+
|
7
|
+
module ThorEnhance
|
8
|
+
module Autogenerate
|
9
|
+
class Command
|
10
|
+
COMMAND_ERB = "#{File.dirname(__FILE__)}/templates/command.rb.erb"
|
11
|
+
COMMAND_TEMPLATE = ERB.new(File.read(COMMAND_ERB))
|
12
|
+
|
13
|
+
AGGREGATE_OPTIONS_ERB = "#{File.dirname(__FILE__)}/templates/aggregate_options.rb.erb"
|
14
|
+
AGGREGATE_OPTIONS_TEMPLATE = ERB.new(File.read(AGGREGATE_OPTIONS_ERB))
|
15
|
+
|
16
|
+
CLASS_OPTIONS_ERB = "#{File.dirname(__FILE__)}/templates/class_options.rb.erb"
|
17
|
+
CLASS_OPTIONS_TEMPLATE = ERB.new(File.read(CLASS_OPTIONS_ERB))
|
18
|
+
|
19
|
+
FOOTER_ERB = "#{File.dirname(__FILE__)}/templates/footer.rb.erb"
|
20
|
+
FOOTER_TEMPLATE = ERB.new(File.read(FOOTER_ERB))
|
21
|
+
|
22
|
+
attr_reader :root, :leaf, :name, :basename, :child_commands, :parent
|
23
|
+
|
24
|
+
def initialize(leaf:, name:, basename:, root: , parent: nil)
|
25
|
+
@leaf = leaf
|
26
|
+
@name = name
|
27
|
+
@basename = basename
|
28
|
+
@child_commands = []
|
29
|
+
@parent = parent
|
30
|
+
@root = root
|
31
|
+
initialize_children!
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_children!
|
35
|
+
return unless children?
|
36
|
+
|
37
|
+
@child_commands = leaf.children.map do |name, child_leaf|
|
38
|
+
self.class.new(root: root, leaf: child_leaf, name: name, basename: basename, parent: self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def method_options
|
43
|
+
@method_options ||= begin
|
44
|
+
_options = options.map { |name, option| Option.new(name: name, option: option) }
|
45
|
+
_options = _options.group_by { _1.readme_type }
|
46
|
+
unless _options.empty?
|
47
|
+
_options.delete(ThorEnhance.configuration.autogenerated_config.readme_skip_key)
|
48
|
+
end
|
49
|
+
|
50
|
+
_options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def command_erb
|
55
|
+
@command_erb ||= begin
|
56
|
+
params = {
|
57
|
+
all_bases: all_bases,
|
58
|
+
basename_string: basename_string,
|
59
|
+
children_descriptors: children_descriptors,
|
60
|
+
class_options_erb: class_options_erb,
|
61
|
+
command: command,
|
62
|
+
command_source: command_source,
|
63
|
+
custom_headers: custom_headers,
|
64
|
+
default_command: default_command,
|
65
|
+
default_command_string: default_command_string,
|
66
|
+
description: description,
|
67
|
+
drawn_out_examples: drawn_out_examples,
|
68
|
+
footer_erb: footer_erb,
|
69
|
+
headers: headers,
|
70
|
+
method_options_erb: method_options_erb,
|
71
|
+
parent_basename_string: parent_basename_string,
|
72
|
+
preferred_basename_string: preferred_basename_string,
|
73
|
+
title: title,
|
74
|
+
}
|
75
|
+
COMMAND_TEMPLATE.result_with_hash(params)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def command_source
|
80
|
+
# if children exists, it is a subcommand
|
81
|
+
# source location for a command does not accurately find the correct source
|
82
|
+
# Return nil and skip output
|
83
|
+
return nil if children?
|
84
|
+
|
85
|
+
file, line = leaf.base.instance_method(name).source_location
|
86
|
+
# This value returns the length of the root up to generated_readme
|
87
|
+
# This allows us to understand how many directories to remove from the local `source_location`
|
88
|
+
remove_length = root.split("/")[0..-2].length
|
89
|
+
|
90
|
+
# this will return the relative location of the command source line
|
91
|
+
relative_source = file.split("/")[remove_length..-1]
|
92
|
+
relative_link = "/#{relative_source.join("/")}#L#{line}"
|
93
|
+
"Source code for this command can be found at: [#{leaf.base.name}##{name}](#{relative_link})"
|
94
|
+
end
|
95
|
+
|
96
|
+
def footer_erb
|
97
|
+
@footer_erb ||= begin
|
98
|
+
regenerate_thor_command = "#{basename} thor_enhance_autogenerate --apply"
|
99
|
+
FOOTER_TEMPLATE.result_with_hash({ regenerate_thor_command: regenerate_thor_command })
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def drawn_out_examples(with_desc: true)
|
104
|
+
case command.example
|
105
|
+
when nil
|
106
|
+
when Array
|
107
|
+
command.example.map do |example|
|
108
|
+
value = []
|
109
|
+
value << "# #{example[:arguments][:kwargs][:desc]}" if with_desc
|
110
|
+
value << "#{parent_basename_string} #{example[:input]}"
|
111
|
+
value.join("\n")
|
112
|
+
end
|
113
|
+
else
|
114
|
+
value = []
|
115
|
+
value << "# #{example[:arguments][:kwargs][:desc]}" if with_desc
|
116
|
+
value << "#{parent_basename_string} #{example[:input]}"
|
117
|
+
[value.join("\n")]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def method_options_erb
|
122
|
+
@method_options_erb ||= AGGREGATE_OPTIONS_TEMPLATE.result_with_hash({ method_options: method_options })
|
123
|
+
end
|
124
|
+
|
125
|
+
def class_options_erb
|
126
|
+
@class_options_erb ||= begin
|
127
|
+
if class_options.empty?
|
128
|
+
nil
|
129
|
+
else
|
130
|
+
CLASS_OPTIONS_TEMPLATE.result_with_hash({ method_options_text_array: class_options.map(&:template_text) })
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def class_options
|
136
|
+
leaf.base.class_options.map do |name, class_option|
|
137
|
+
class_option.hide ? nil : Option.new(name: name, option: class_option)
|
138
|
+
end.compact
|
139
|
+
end
|
140
|
+
|
141
|
+
def basename_string
|
142
|
+
"#{parent_basename_string} #{command.usage}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_command_string
|
146
|
+
parent_basename_string if name == default_command
|
147
|
+
end
|
148
|
+
|
149
|
+
def all_bases
|
150
|
+
[default_command_string, basename_string].compact
|
151
|
+
end
|
152
|
+
|
153
|
+
def preferred_basename_string
|
154
|
+
default_command_string || basename_string
|
155
|
+
end
|
156
|
+
|
157
|
+
def parent_basename_string
|
158
|
+
parent_names = [basename]
|
159
|
+
temp_leaf = leaf
|
160
|
+
while parent = temp_leaf.parent
|
161
|
+
temp_leaf = parent
|
162
|
+
parent_names << parent.command.usage
|
163
|
+
end
|
164
|
+
parent_names.join(" ")
|
165
|
+
end
|
166
|
+
|
167
|
+
def parent_root
|
168
|
+
if parent
|
169
|
+
# Remove the last index of parent because that will be the Readme.md file
|
170
|
+
# We just want the directory of the parent file
|
171
|
+
parent.relative_readme_path[0..-2]
|
172
|
+
else
|
173
|
+
[]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def relative_readme_path
|
178
|
+
if children?
|
179
|
+
# If children exist, this is a subcommand and needs to be a root ReadMe
|
180
|
+
[*parent_root, name, "Readme.md"]
|
181
|
+
else
|
182
|
+
[*parent_root, "#{name}.md"]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# this only returns children and its children
|
187
|
+
# Call this on top most parent to retreive family tree for subcommands
|
188
|
+
def flatten_children
|
189
|
+
return [] if child_commands.empty?
|
190
|
+
|
191
|
+
child_commands.map do |child|
|
192
|
+
[child, child.flatten_children]
|
193
|
+
end.flatten
|
194
|
+
end
|
195
|
+
|
196
|
+
def save_self!(root:, apply:)
|
197
|
+
absolute_path = "#{root}/#{relative_readme_path.join("/")}"
|
198
|
+
pathname = Pathname.new(absolute_path)
|
199
|
+
FileUtils.mkdir_p(pathname.dirname)
|
200
|
+
if File.exist?(absolute_path)
|
201
|
+
content = File.read(absolute_path)
|
202
|
+
diff = command_erb == content ? :same : :overwite
|
203
|
+
else
|
204
|
+
diff = :new
|
205
|
+
end
|
206
|
+
|
207
|
+
if apply
|
208
|
+
File.write(absolute_path, command_erb)
|
209
|
+
end
|
210
|
+
|
211
|
+
{ path: absolute_path, diff: diff, apply: apply, self_for_root: self_for_root }
|
212
|
+
end
|
213
|
+
|
214
|
+
def description
|
215
|
+
command.long_description || command.description
|
216
|
+
end
|
217
|
+
|
218
|
+
def title
|
219
|
+
command.title&.dig(:input) || command.usage
|
220
|
+
end
|
221
|
+
|
222
|
+
def self_for_root
|
223
|
+
params_for_child(self)
|
224
|
+
end
|
225
|
+
|
226
|
+
def default_command
|
227
|
+
if children?
|
228
|
+
leaf.base.subcommand_classes[name].default_task
|
229
|
+
else
|
230
|
+
leaf.base.default_task
|
231
|
+
end
|
232
|
+
rescue
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
def custom_headers
|
239
|
+
ThorEnhance.configuration.autogenerated_config.custom_headers.map do |header|
|
240
|
+
next unless command.respond_to?(header.to_sym)
|
241
|
+
|
242
|
+
header_value = command.public_send(header.to_sym)
|
243
|
+
next if header_value.nil?
|
244
|
+
case header_value
|
245
|
+
when Array
|
246
|
+
header_value.map { header_string(_1, header) }
|
247
|
+
else
|
248
|
+
header_string(header_value, header)
|
249
|
+
end
|
250
|
+
|
251
|
+
end.compact.flatten.join("\n\n")
|
252
|
+
end
|
253
|
+
|
254
|
+
def header_string(header_value, header)
|
255
|
+
header_name = header.to_s.gsub(/[_-]/, " ").titlecase
|
256
|
+
header_name += "?" if ThorEnhance.configuration.autogenerated_config.question_headers.include?(header)
|
257
|
+
header_tag = decipher_header_tag(header_value[:arguments][:kwargs][:tag])
|
258
|
+
header_input = header_value[:input]
|
259
|
+
"#{header_tag} #{header_name}\n\n#{header_input}"
|
260
|
+
end
|
261
|
+
|
262
|
+
def decipher_header_tag(input)
|
263
|
+
case input
|
264
|
+
when String
|
265
|
+
begin
|
266
|
+
input[0..1][-1].to_i.to_s == input[0..1][-1] ? "#" * input[0..1][-1].to_i : "##"
|
267
|
+
rescue
|
268
|
+
"##"
|
269
|
+
end
|
270
|
+
when Integer
|
271
|
+
"#" * input
|
272
|
+
else
|
273
|
+
"##"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def children_descriptors
|
278
|
+
default = child_commands.map { params_for_child(_1) }.select { _1[:default_command] }
|
279
|
+
not_default = child_commands.map { params_for_child(_1) }.select { !_1[:default_command] }
|
280
|
+
default + not_default
|
281
|
+
end
|
282
|
+
|
283
|
+
def params_for_child(child)
|
284
|
+
relative_link = find_iterations_back(child)
|
285
|
+
link = child.relative_readme_path[relative_link..-1].join("/")
|
286
|
+
{
|
287
|
+
all_bases: child.all_bases,
|
288
|
+
basename_string: child.basename_string,
|
289
|
+
default_command: child.name == default_command,
|
290
|
+
default_command_string: default_command_string,
|
291
|
+
description: child.description,
|
292
|
+
examples: child.drawn_out_examples(with_desc: false) || [],
|
293
|
+
link: link,
|
294
|
+
preferred_basename_string: child.preferred_basename_string,
|
295
|
+
title: child.title,
|
296
|
+
}
|
297
|
+
end
|
298
|
+
|
299
|
+
def find_iterations_back(child)
|
300
|
+
# if parent is not present, then start from 0th position
|
301
|
+
return 0 if child.parent.nil?
|
302
|
+
# When there is no children, just take the last
|
303
|
+
return -1 unless child.leaf.children?
|
304
|
+
|
305
|
+
# when parent exists, find out how many parents there are
|
306
|
+
# Return how many parents there are
|
307
|
+
iterations = -1
|
308
|
+
temp_child = child
|
309
|
+
while temp = temp_child.parent
|
310
|
+
iterations -= 1
|
311
|
+
temp_child = temp_child.parent
|
312
|
+
end
|
313
|
+
|
314
|
+
iterations
|
315
|
+
end
|
316
|
+
|
317
|
+
def headers
|
318
|
+
(command.header || []).map { _1[:arguments][:kwargs] }
|
319
|
+
end
|
320
|
+
|
321
|
+
def children?
|
322
|
+
leaf.children?
|
323
|
+
end
|
324
|
+
|
325
|
+
def command
|
326
|
+
leaf.command
|
327
|
+
end
|
328
|
+
|
329
|
+
def options
|
330
|
+
command.options.reject { |name, option| option.hide}
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
@@ -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.to_sym
|
65
|
+
@readme_skip_key = skip_key
|
66
|
+
@readme_enums = enums.map(&:to_sym) << empty_group.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,11 @@
|
|
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}\n" }.join("\n") %>
|
8
|
+
```
|
9
|
+
|
10
|
+
</details>
|
11
|
+
<% end %>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# <%= title.to_s.titlecase %>
|
2
|
+
|
3
|
+
<%= command_source %>
|
4
|
+
|
5
|
+
## Description
|
6
|
+
<%= command.long_description || command.description %>
|
7
|
+
|
8
|
+
```bash
|
9
|
+
# Base command for `<%= command.usage %>`
|
10
|
+
<%= all_bases.join(" <options> \n") %> <options>
|
11
|
+
```
|
12
|
+
|
13
|
+
<%= custom_headers %>
|
14
|
+
|
15
|
+
<% headers.each do |header| %>
|
16
|
+
## <%= header[:name] %>
|
17
|
+
<%= header[:desc]%>
|
18
|
+
<% end %>
|
19
|
+
<% if drawn_out_examples %>
|
20
|
+
---
|
21
|
+
|
22
|
+
## Examples
|
23
|
+
<% drawn_out_examples.each do |ex|%>
|
24
|
+
```bash
|
25
|
+
<%= ex %>
|
26
|
+
```
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
<% if !method_options_erb.strip.empty? %>
|
30
|
+
---
|
31
|
+
|
32
|
+
<% if children_descriptors.empty? %>
|
33
|
+
## Method Options
|
34
|
+
|
35
|
+
<%= method_options_erb %>
|
36
|
+
<% end %>
|
37
|
+
<% else %>
|
38
|
+
<% children_descriptors.each do |child| %>
|
39
|
+
### [<%= child[:title]%>](<%= child[:link] %>)
|
40
|
+
|
41
|
+
<%= "**Default Command** <br>" if child[:default_command] %>
|
42
|
+
<%= child[:description] %>
|
43
|
+
|
44
|
+
```bash
|
45
|
+
<%= child[:all_bases].join(" <options> \n") %> <options>
|
46
|
+
<%= child[:examples].map { _1 }.join("\n") %>
|
47
|
+
```
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
<%= class_options_erb %>
|
52
|
+
---
|
53
|
+
|
54
|
+
<%= 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
|