toys 0.3.0 → 0.3.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/.yardopts +11 -0
- data/README.md +5 -2
- data/lib/toys.rb +2 -2
- data/lib/toys/builtins/system.rb +4 -0
- data/lib/toys/cli.rb +90 -3
- data/lib/toys/config_dsl.rb +432 -0
- data/lib/toys/context.rb +99 -0
- data/lib/toys/helpers.rb +11 -0
- data/lib/toys/helpers/exec.rb +180 -2
- data/lib/toys/loader.rb +165 -27
- data/lib/toys/middleware.rb +14 -0
- data/lib/toys/middleware/base.rb +7 -1
- data/lib/toys/middleware/set_verbosity.rb +3 -0
- data/lib/toys/middleware/{group_default.rb → show_group_usage.rb} +16 -5
- data/lib/toys/middleware/{show_tool_help.rb → show_tool_usage.rb} +8 -1
- data/lib/toys/middleware/show_usage_errors.rb +6 -0
- data/lib/toys/template.rb +73 -0
- data/lib/toys/templates.rb +14 -0
- data/lib/toys/templates/clean.rb +13 -2
- data/lib/toys/templates/gem_build.rb +19 -1
- data/lib/toys/templates/minitest.rb +17 -2
- data/lib/toys/templates/rubocop.rb +14 -1
- data/lib/toys/templates/yardoc.rb +15 -1
- data/lib/toys/tool.rb +405 -77
- data/lib/toys/utils/usage.rb +55 -11
- data/lib/toys/version.rb +1 -1
- metadata +6 -5
- data/lib/toys/builder.rb +0 -227
data/lib/toys/templates.rb
CHANGED
@@ -34,6 +34,20 @@ module Toys
|
|
34
34
|
# Namespace for common templates
|
35
35
|
#
|
36
36
|
module Templates
|
37
|
+
##
|
38
|
+
# Return a template class by name.
|
39
|
+
#
|
40
|
+
# Currently recognized template names are:
|
41
|
+
#
|
42
|
+
# * `:clean` : Creates a tool that cleans build artifacts.
|
43
|
+
# * `:gem_build` : Creates a tool that builds and/or releases gems.
|
44
|
+
# * `:minitest` : Creates a tool that runs unit tests.
|
45
|
+
# * `:rubocop` : Creates a tool that runs rubocop.
|
46
|
+
# * `:yardoc` : Creates a tool that generates YARD documentation.
|
47
|
+
#
|
48
|
+
# @param [String,Symbol] name Name of the template class to return
|
49
|
+
# @return [Class,nil] The class, or `nil` if not found
|
50
|
+
#
|
37
51
|
def self.lookup(name)
|
38
52
|
Utils::ModuleLookup.lookup(:templates, name)
|
39
53
|
end
|
data/lib/toys/templates/clean.rb
CHANGED
@@ -35,6 +35,18 @@ module Toys
|
|
35
35
|
class Clean
|
36
36
|
include Template
|
37
37
|
|
38
|
+
##
|
39
|
+
# Create the template settings for the Clean template.
|
40
|
+
#
|
41
|
+
# You may provide a hash of options when expanding this template.
|
42
|
+
# Supported options include:
|
43
|
+
#
|
44
|
+
# * `:name` (String) Name of the tool to create. Defaults to "clean".
|
45
|
+
# * `:paths` (Array<String>) An array of glob patterns indicating what
|
46
|
+
# to clean.
|
47
|
+
#
|
48
|
+
# @param [Hash] opts Options.
|
49
|
+
#
|
38
50
|
def initialize(opts = {})
|
39
51
|
@name = opts[:name] || "clean"
|
40
52
|
@paths = opts[:paths] || []
|
@@ -44,7 +56,7 @@ module Toys
|
|
44
56
|
attr_accessor :paths
|
45
57
|
|
46
58
|
to_expand do |template|
|
47
|
-
|
59
|
+
tool(template.name) do
|
48
60
|
desc "Clean built files and directories."
|
49
61
|
|
50
62
|
use :file_utils
|
@@ -52,7 +64,6 @@ module Toys
|
|
52
64
|
execute do
|
53
65
|
files = []
|
54
66
|
patterns = Array(template.paths)
|
55
|
-
patterns = ["lib/**/*.rb"] if patterns.empty?
|
56
67
|
patterns.each do |pattern|
|
57
68
|
files.concat(::Dir.glob(pattern))
|
58
69
|
end
|
@@ -37,6 +37,24 @@ module Toys
|
|
37
37
|
class GemBuild
|
38
38
|
include Template
|
39
39
|
|
40
|
+
##
|
41
|
+
# Create the template settings for the GemBuild template.
|
42
|
+
#
|
43
|
+
# You may provide a hash of options when expanding this template.
|
44
|
+
# Supported options include:
|
45
|
+
#
|
46
|
+
# * `:name` (String) Name of the tool to create. Defaults to "build".
|
47
|
+
# * `:gem_name` (String) Name of the gem to build. If not provided,
|
48
|
+
# defaults to the first gemspec file it finds.
|
49
|
+
# * `:push_gem` (Boolean) If true, pushes the built gem to rubygems.
|
50
|
+
# * `:tag` (Boolean) If true, tags the git repo with the gem version.
|
51
|
+
# * `:push_tag` (Boolean,String) If truthy, pushes the new tag to
|
52
|
+
# a git remote. You may specify which remote by setting the value to
|
53
|
+
# a string. Otherwise, if the value is simply `true`, the "origin"
|
54
|
+
# remote is used by default.
|
55
|
+
#
|
56
|
+
# @param [Hash] opts Options.
|
57
|
+
#
|
40
58
|
def initialize(opts = {})
|
41
59
|
@name = opts[:name] || "build"
|
42
60
|
@gem_name = opts[:gem_name]
|
@@ -61,7 +79,7 @@ module Toys
|
|
61
79
|
end
|
62
80
|
task_type = template.push_gem ? "Release" : "Build"
|
63
81
|
|
64
|
-
|
82
|
+
tool(template.name) do
|
65
83
|
desc "#{task_type} the gem: #{template.gem_name}"
|
66
84
|
|
67
85
|
use :file_utils
|
@@ -35,10 +35,25 @@ module Toys
|
|
35
35
|
class Minitest
|
36
36
|
include Template
|
37
37
|
|
38
|
+
##
|
39
|
+
# Create the template settings for the Minitest template.
|
40
|
+
#
|
41
|
+
# You may provide a hash of options when expanding this template.
|
42
|
+
# Supported options include:
|
43
|
+
#
|
44
|
+
# * `:name` (String) Name of the tool to create. Defaults to "test".
|
45
|
+
# * `:lib` (Array<String>) An array of library paths to add to the
|
46
|
+
# ruby require path.
|
47
|
+
# * `:files` (Array<String>) An array of globs indicating the test
|
48
|
+
# files to load.
|
49
|
+
# * `:warnings` (Boolean) If true, runs tests with Ruby warnings.
|
50
|
+
#
|
51
|
+
# @param [Hash] opts Options.
|
52
|
+
#
|
38
53
|
def initialize(opts = {})
|
39
54
|
@name = opts[:name] || "test"
|
40
55
|
@libs = opts[:libs] || ["lib"]
|
41
|
-
@files = opts[:files] || ["test
|
56
|
+
@files = opts[:files] || ["test/**/test*.rb"]
|
42
57
|
@warnings = opts.include?(:warnings) ? opts[:warnings] : true
|
43
58
|
end
|
44
59
|
|
@@ -48,7 +63,7 @@ module Toys
|
|
48
63
|
attr_accessor :warnings
|
49
64
|
|
50
65
|
to_expand do |template|
|
51
|
-
|
66
|
+
tool(template.name) do
|
52
67
|
desc "Run minitest on the current project."
|
53
68
|
|
54
69
|
use :exec
|
@@ -35,6 +35,19 @@ module Toys
|
|
35
35
|
class Rubocop
|
36
36
|
include Template
|
37
37
|
|
38
|
+
##
|
39
|
+
# Create the template settings for the Rubocop template.
|
40
|
+
#
|
41
|
+
# You may provide a hash of options when expanding this template.
|
42
|
+
# Supported options include:
|
43
|
+
#
|
44
|
+
# * `:name` (String) Name of the tool to create. Defaults to "rubocop".
|
45
|
+
# * `:fail_on_error` (Boolean) If true, exits with a nonzero code if
|
46
|
+
# Rubocop fails. Defaults to true.
|
47
|
+
# * `:options` (Hash) Additional options passed to the Rubocop CLI.
|
48
|
+
#
|
49
|
+
# @param [Hash] opts Options.
|
50
|
+
#
|
38
51
|
def initialize(opts = {})
|
39
52
|
@name = opts[:name] || "rubocop"
|
40
53
|
@fail_on_error = opts.include?(:fail_on_error) ? opts[:fail_on_error] : true
|
@@ -46,7 +59,7 @@ module Toys
|
|
46
59
|
attr_accessor :options
|
47
60
|
|
48
61
|
to_expand do |template|
|
49
|
-
|
62
|
+
tool(template.name) do
|
50
63
|
desc "Run rubocop on the current project."
|
51
64
|
|
52
65
|
use :exec
|
@@ -35,6 +35,20 @@ module Toys
|
|
35
35
|
class Yardoc
|
36
36
|
include Template
|
37
37
|
|
38
|
+
##
|
39
|
+
# Create the template settings for the Yardoc template.
|
40
|
+
#
|
41
|
+
# You may provide a hash of options when expanding this template.
|
42
|
+
# Supported options include:
|
43
|
+
#
|
44
|
+
# * `:name` (String) Name of the tool to create. Defaults to "yardoc".
|
45
|
+
# * `:files` (Array<String>) An array of globs indicating the files
|
46
|
+
# to document.
|
47
|
+
# * `:options` (Hash) Additional options passed to YARD
|
48
|
+
# * `:stats_options` (Hash) Additional options passed to YARD stats
|
49
|
+
#
|
50
|
+
# @param [Hash] opts Options.
|
51
|
+
#
|
38
52
|
def initialize(opts = {})
|
39
53
|
@name = opts[:name] || "yardoc"
|
40
54
|
@files = opts[:files] || []
|
@@ -48,7 +62,7 @@ module Toys
|
|
48
62
|
attr_accessor :stats_options
|
49
63
|
|
50
64
|
to_expand do |template|
|
51
|
-
|
65
|
+
tool(template.name) do
|
52
66
|
desc "Run yardoc on the current project."
|
53
67
|
|
54
68
|
use :exec
|
data/lib/toys/tool.rb
CHANGED
@@ -31,12 +31,21 @@ require "optparse"
|
|
31
31
|
|
32
32
|
module Toys
|
33
33
|
##
|
34
|
-
# A
|
34
|
+
# A Tool is a single command that can be invoked using Toys.
|
35
|
+
# It has a name, a series of one or more words that you use to identify
|
36
|
+
# the tool on the command line. It also has a set of formal switches and
|
37
|
+
# command line arguments supported, and a block that gets run when the
|
38
|
+
# tool is executed.
|
35
39
|
#
|
36
40
|
class Tool
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
##
|
42
|
+
# Create a new tool.
|
43
|
+
#
|
44
|
+
# @param [Array<String>] full_name The name of the tool
|
45
|
+
#
|
46
|
+
def initialize(full_name)
|
47
|
+
@full_name = full_name.dup.freeze
|
48
|
+
@middleware_stack = []
|
40
49
|
|
41
50
|
@definition_path = nil
|
42
51
|
@cur_path = nil
|
@@ -47,99 +56,200 @@ module Toys
|
|
47
56
|
@long_desc = nil
|
48
57
|
|
49
58
|
@default_data = {}
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@
|
59
|
+
@switch_definitions = []
|
60
|
+
@required_arg_definitions = []
|
61
|
+
@optional_arg_definitions = []
|
62
|
+
@remaining_args_definition = nil
|
54
63
|
|
55
64
|
@helpers = {}
|
56
65
|
@modules = []
|
57
66
|
@executor = nil
|
58
67
|
end
|
59
68
|
|
69
|
+
##
|
70
|
+
# Return the name of the tool as an array of strings.
|
71
|
+
# This array may not be modified.
|
72
|
+
# @return [Array<String>]
|
73
|
+
#
|
60
74
|
attr_reader :full_name
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
75
|
+
|
76
|
+
##
|
77
|
+
# Return a list of all defined switches.
|
78
|
+
# @return [Array<Toys::Tool::SwitchDefinition>]
|
79
|
+
#
|
80
|
+
attr_reader :switch_definitions
|
81
|
+
|
82
|
+
##
|
83
|
+
# Return a list of all defined required positional arguments.
|
84
|
+
# @return [Array<Toys::Tool::ArgDefinition>]
|
85
|
+
#
|
86
|
+
attr_reader :required_arg_definitions
|
87
|
+
|
88
|
+
##
|
89
|
+
# Return a list of all defined optional positional arguments.
|
90
|
+
# @return [Array<Toys::Tool::ArgDefinition>]
|
91
|
+
#
|
92
|
+
attr_reader :optional_arg_definitions
|
93
|
+
|
94
|
+
##
|
95
|
+
# Return the remaining arguments specification, or `nil` if remaining
|
96
|
+
# arguments are currently not supported by this tool.
|
97
|
+
# @return [Toys::Tool::ArgDefinition,nil]
|
98
|
+
#
|
99
|
+
attr_reader :remaining_args_definition
|
100
|
+
|
101
|
+
##
|
102
|
+
# Return the default argument data.
|
103
|
+
# @return [Hash]
|
104
|
+
#
|
65
105
|
attr_reader :default_data
|
106
|
+
|
107
|
+
##
|
108
|
+
# Return a list of modules that will be available during execution.
|
109
|
+
# @return [Array<Module>]
|
110
|
+
#
|
66
111
|
attr_reader :modules
|
112
|
+
|
113
|
+
##
|
114
|
+
# Return a list of helper methods that will be available during execution.
|
115
|
+
# @return [Hash{Symbol => Proc}]
|
116
|
+
#
|
67
117
|
attr_reader :helpers
|
118
|
+
|
119
|
+
##
|
120
|
+
# Return the executor block, or `nil` if not present.
|
121
|
+
# @return [Proc,nil]
|
122
|
+
#
|
68
123
|
attr_reader :executor
|
124
|
+
|
125
|
+
##
|
126
|
+
# If this tool is an alias, return the alias target as a local name (i.e.
|
127
|
+
# a single word identifying a sibling of this tool). Returns `nil` if this
|
128
|
+
# tool is not an alias.
|
129
|
+
# @return [String,nil]
|
130
|
+
#
|
69
131
|
attr_reader :alias_target
|
132
|
+
|
133
|
+
##
|
134
|
+
# Returns the middleware stack
|
135
|
+
# @return [Array<Object>]
|
136
|
+
#
|
70
137
|
attr_reader :middleware_stack
|
138
|
+
|
139
|
+
##
|
140
|
+
# Returns the path to the file that contains the definition of this tool.
|
141
|
+
# @return [String]
|
142
|
+
#
|
71
143
|
attr_reader :definition_path
|
72
144
|
|
145
|
+
##
|
146
|
+
# Returns the local name of this tool.
|
147
|
+
# @return [String]
|
148
|
+
#
|
73
149
|
def simple_name
|
74
150
|
full_name.last
|
75
151
|
end
|
76
152
|
|
153
|
+
##
|
154
|
+
# Returns a displayable name of this tool, generally the full name
|
155
|
+
# delimited by spaces.
|
156
|
+
# @return [String]
|
157
|
+
#
|
77
158
|
def display_name
|
78
159
|
full_name.join(" ")
|
79
160
|
end
|
80
161
|
|
162
|
+
##
|
163
|
+
# Returns true if this tool is a root tool.
|
164
|
+
# @return [Boolean]
|
165
|
+
#
|
81
166
|
def root?
|
82
167
|
full_name.empty?
|
83
168
|
end
|
84
169
|
|
170
|
+
##
|
171
|
+
# Returns true if this tool has an executor defined.
|
172
|
+
# @return [Boolean]
|
173
|
+
#
|
85
174
|
def includes_executor?
|
86
175
|
executor.is_a?(::Proc)
|
87
176
|
end
|
88
177
|
|
178
|
+
##
|
179
|
+
# Returns true if this tool is an alias.
|
180
|
+
# @return [Boolean]
|
181
|
+
#
|
89
182
|
def alias?
|
90
183
|
!alias_target.nil?
|
91
184
|
end
|
92
185
|
|
186
|
+
##
|
187
|
+
# Returns the effective short description for this tool. This will be
|
188
|
+
# displayed when this tool is listed in a command list.
|
189
|
+
# @return [String]
|
190
|
+
#
|
93
191
|
def effective_desc
|
94
192
|
@desc || default_desc
|
95
193
|
end
|
96
194
|
|
195
|
+
##
|
196
|
+
# Returns the effective long description for this tool. This will be
|
197
|
+
# displayed as part of the usage for this particular tool.
|
198
|
+
# @return [String]
|
199
|
+
#
|
97
200
|
def effective_long_desc
|
98
201
|
@long_desc || @desc || default_desc
|
99
202
|
end
|
100
203
|
|
204
|
+
##
|
205
|
+
# Returns true if there is a specific description set for this tool.
|
206
|
+
# @return [Boolean]
|
207
|
+
#
|
101
208
|
def includes_description?
|
102
209
|
!@long_desc.nil? || !@desc.nil?
|
103
210
|
end
|
104
211
|
|
212
|
+
##
|
213
|
+
# Returns true if at least one switch or positional argument is defined
|
214
|
+
# for this tool.
|
215
|
+
# @return [Boolean]
|
216
|
+
#
|
105
217
|
def includes_arguments?
|
106
|
-
!default_data.empty? || !
|
107
|
-
!
|
218
|
+
!default_data.empty? || !switch_definitions.empty? ||
|
219
|
+
!required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
|
220
|
+
!remaining_args_definition.nil?
|
108
221
|
end
|
109
222
|
|
223
|
+
##
|
224
|
+
# Returns true if at least one helper method or module is added to this
|
225
|
+
# tool.
|
226
|
+
# @return [Boolean]
|
227
|
+
#
|
110
228
|
def includes_helpers?
|
111
229
|
!helpers.empty? || !modules.empty?
|
112
230
|
end
|
113
231
|
|
232
|
+
##
|
233
|
+
# Returns true if this tool has any definition information.
|
234
|
+
# @return [Boolean]
|
235
|
+
#
|
114
236
|
def includes_definition?
|
115
237
|
alias? || includes_arguments? || includes_executor? || includes_helpers?
|
116
238
|
end
|
117
239
|
|
240
|
+
##
|
241
|
+
# Returns a list of switch flags used by this tool.
|
242
|
+
# @return [Array<String>]
|
243
|
+
#
|
118
244
|
def used_switches
|
119
|
-
@
|
120
|
-
end
|
121
|
-
|
122
|
-
def defining_from(path)
|
123
|
-
raise ToolDefinitionError, "Already being defined" if @cur_path
|
124
|
-
@cur_path = path
|
125
|
-
begin
|
126
|
-
yield
|
127
|
-
ensure
|
128
|
-
@definition_path = @cur_path if includes_description? || includes_definition?
|
129
|
-
@cur_path = nil
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def yield_definition
|
134
|
-
saved_path = @cur_path
|
135
|
-
@cur_path = nil
|
136
|
-
begin
|
137
|
-
yield
|
138
|
-
ensure
|
139
|
-
@cur_path = saved_path
|
140
|
-
end
|
245
|
+
@switch_definitions.reduce([]) { |used, sdef| used + sdef.switches }.uniq
|
141
246
|
end
|
142
247
|
|
248
|
+
##
|
249
|
+
# Make this tool an alias of the sibling tool with the given local name.
|
250
|
+
#
|
251
|
+
# @param [String] target_word The name of the alias target
|
252
|
+
#
|
143
253
|
def make_alias_of(target_word)
|
144
254
|
if root?
|
145
255
|
raise ToolDefinitionError, "Cannot make the root tool an alias"
|
@@ -152,16 +262,33 @@ module Toys
|
|
152
262
|
self
|
153
263
|
end
|
154
264
|
|
265
|
+
##
|
266
|
+
# Set the short description.
|
267
|
+
#
|
268
|
+
# @param [String] str The short description
|
269
|
+
#
|
155
270
|
def desc=(str)
|
156
271
|
check_definition_state
|
157
272
|
@desc = str
|
158
273
|
end
|
159
274
|
|
275
|
+
##
|
276
|
+
# Set the long description.
|
277
|
+
#
|
278
|
+
# @param [String] str The long description
|
279
|
+
#
|
160
280
|
def long_desc=(str)
|
161
281
|
check_definition_state
|
162
282
|
@long_desc = str
|
163
283
|
end
|
164
284
|
|
285
|
+
##
|
286
|
+
# Define a helper method that will be available during execution.
|
287
|
+
# Pass the name of the method in the argument, and provide a block with
|
288
|
+
# the method body. Note the method name may not start with an underscore.
|
289
|
+
#
|
290
|
+
# @param [String] name The method name
|
291
|
+
#
|
165
292
|
def add_helper(name, &block)
|
166
293
|
check_definition_state
|
167
294
|
name_str = name.to_s
|
@@ -172,6 +299,12 @@ module Toys
|
|
172
299
|
self
|
173
300
|
end
|
174
301
|
|
302
|
+
##
|
303
|
+
# Mix in the given module during execution. You may provide the module
|
304
|
+
# itself, or the name of a well-known module under {Toys::Helpers}.
|
305
|
+
#
|
306
|
+
# @param [Module,String] name The module or module name.
|
307
|
+
#
|
175
308
|
def use_module(name)
|
176
309
|
check_definition_state
|
177
310
|
case name
|
@@ -189,49 +322,174 @@ module Toys
|
|
189
322
|
self
|
190
323
|
end
|
191
324
|
|
325
|
+
##
|
326
|
+
# Add a switch to the current tool. Each switch must specify a key which
|
327
|
+
# the executor may use to obtain the switch value from the context.
|
328
|
+
# You may then provide the switches themselves in `OptionParser` form.
|
329
|
+
#
|
330
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
331
|
+
# execution context.
|
332
|
+
# @param [String...] switches The switches in OptionParser format.
|
333
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
334
|
+
# @param [Object] default The default value. This is the value that will
|
335
|
+
# be set in the context if this switch is not provided on the command
|
336
|
+
# line. Defaults to `nil`.
|
337
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
338
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
339
|
+
# @param [Boolean] only_unique If true, any switches that are already
|
340
|
+
# defined in this tool are removed from this switch. For example, if
|
341
|
+
# an earlier switch uses `-a`, and this switch wants to use both
|
342
|
+
# `-a` and `-b`, then only `-b` will be assigned to this switch.
|
343
|
+
# Defaults to false.
|
344
|
+
# @param [Proc,nil] handler An optional handler for setting/updating the
|
345
|
+
# value. If given, it should take two arguments, the new given value
|
346
|
+
# and the previous value, and it should return the new value that
|
347
|
+
# should be set. The default handler simply replaces the previous
|
348
|
+
# value. i.e. the default is effectively `-> (val, _prev) { val }`.
|
349
|
+
#
|
192
350
|
def add_switch(key, *switches,
|
193
351
|
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
194
352
|
check_definition_state
|
195
353
|
switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
|
196
|
-
switches
|
197
|
-
|
198
|
-
|
354
|
+
bad_switch = switches.find { |s| Tool.extract_switch(s).empty? }
|
355
|
+
if bad_switch
|
356
|
+
raise ToolDefinitionError, "Illegal switch: #{bad_switch.inspect}"
|
357
|
+
end
|
358
|
+
switch_info = SwitchDefinition.new(key, switches + Array(accept) + Array(doc), handler)
|
199
359
|
if only_unique
|
200
360
|
switch_info.remove_switches(used_switches)
|
201
361
|
end
|
202
362
|
if switch_info.active?
|
203
363
|
@default_data[key] = default
|
204
|
-
@
|
364
|
+
@switch_definitions << switch_info
|
205
365
|
end
|
206
366
|
self
|
207
367
|
end
|
208
368
|
|
369
|
+
##
|
370
|
+
# Add a required positional argument to the current tool. You must specify
|
371
|
+
# a key which the executor may use to obtain the argument value from the
|
372
|
+
# context.
|
373
|
+
#
|
374
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
375
|
+
# execution context.
|
376
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
377
|
+
# @param [String,nil] doc The documentation for the switch, which appears
|
378
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
379
|
+
#
|
209
380
|
def add_required_arg(key, accept: nil, doc: nil)
|
210
381
|
check_definition_state
|
211
382
|
@default_data[key] = nil
|
212
|
-
@
|
383
|
+
@required_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
213
384
|
self
|
214
385
|
end
|
215
386
|
|
387
|
+
##
|
388
|
+
# Add an optional positional argument to the current tool. You must specify
|
389
|
+
# a key which the executor may use to obtain the argument value from the
|
390
|
+
# context. If an optional argument is not given on the command line, the
|
391
|
+
# value is set to the given default.
|
392
|
+
#
|
393
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
394
|
+
# execution context.
|
395
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
396
|
+
# @param [Object] default The default value. This is the value that will
|
397
|
+
# be set in the context if this argument is not provided on the command
|
398
|
+
# line. Defaults to `nil`.
|
399
|
+
# @param [String,nil] doc The documentation for the argument, which appears
|
400
|
+
# in the usage documentation. Defaults to `nil` for no documentation.
|
401
|
+
#
|
216
402
|
def add_optional_arg(key, accept: nil, default: nil, doc: nil)
|
217
403
|
check_definition_state
|
218
404
|
@default_data[key] = default
|
219
|
-
@
|
405
|
+
@optional_arg_definitions << ArgDefinition.new(key, accept, Array(doc))
|
220
406
|
self
|
221
407
|
end
|
222
408
|
|
409
|
+
##
|
410
|
+
# Specify what should be done with unmatched positional arguments. You must
|
411
|
+
# specify a key which the executor may use to obtain the remaining args
|
412
|
+
# from the context.
|
413
|
+
#
|
414
|
+
# @param [Symbol] key The key to use to retrieve the value from the
|
415
|
+
# execution context.
|
416
|
+
# @param [Object,nil] accept An OptionParser acceptor. Optional.
|
417
|
+
# @param [Object] default The default value. This is the value that will
|
418
|
+
# be set in the context if no unmatched arguments are provided on the
|
419
|
+
# command line. Defaults to the empty array `[]`.
|
420
|
+
# @param [String,nil] doc The documentation for the remaining arguments,
|
421
|
+
# which appears in the usage documentation. Defaults to `nil` for no
|
422
|
+
# documentation.
|
423
|
+
#
|
223
424
|
def set_remaining_args(key, accept: nil, default: [], doc: nil)
|
224
425
|
check_definition_state
|
225
426
|
@default_data[key] = default
|
226
|
-
@
|
427
|
+
@remaining_args_definition = ArgDefinition.new(key, accept, Array(doc))
|
227
428
|
self
|
228
429
|
end
|
229
430
|
|
431
|
+
##
|
432
|
+
# Set the executor for this tool. This is a proc that will be called,
|
433
|
+
# with `self` set to a {Toys::Context}.
|
434
|
+
#
|
435
|
+
# @param [Proc] executor The executor for this tool.
|
436
|
+
#
|
230
437
|
def executor=(executor)
|
231
438
|
check_definition_state
|
232
439
|
@executor = executor
|
233
440
|
end
|
234
441
|
|
442
|
+
##
|
443
|
+
# Execute this tool in the given context.
|
444
|
+
#
|
445
|
+
# @param [Toys::Context::Base] context_base The execution context
|
446
|
+
# @param [Array<String>] args The arguments to pass to the tool. Should
|
447
|
+
# not include the tool name.
|
448
|
+
# @param [Integer] verbosity The starting verbosity. Defaults to 0.
|
449
|
+
#
|
450
|
+
# @return [Integer] The result code.
|
451
|
+
#
|
452
|
+
def execute(context_base, args, verbosity: 0)
|
453
|
+
finish_definition unless @definition_finished
|
454
|
+
Execution.new(self).execute(context_base, args, verbosity: verbosity)
|
455
|
+
end
|
456
|
+
|
457
|
+
##
|
458
|
+
# Declare that this tool is now defined in the given path
|
459
|
+
#
|
460
|
+
# @private
|
461
|
+
#
|
462
|
+
def defining_from(path)
|
463
|
+
raise ToolDefinitionError, "Already being defined" if @cur_path
|
464
|
+
@cur_path = path
|
465
|
+
begin
|
466
|
+
yield
|
467
|
+
ensure
|
468
|
+
@definition_path = @cur_path if includes_description? || includes_definition?
|
469
|
+
@cur_path = nil
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
##
|
474
|
+
# Relinquish the current path declaration
|
475
|
+
#
|
476
|
+
# @private
|
477
|
+
#
|
478
|
+
def yield_definition
|
479
|
+
saved_path = @cur_path
|
480
|
+
@cur_path = nil
|
481
|
+
begin
|
482
|
+
yield
|
483
|
+
ensure
|
484
|
+
@cur_path = saved_path
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
##
|
489
|
+
# Complete definition and run middleware configs
|
490
|
+
#
|
491
|
+
# @private
|
492
|
+
#
|
235
493
|
def finish_definition
|
236
494
|
if !alias? && !@definition_finished
|
237
495
|
config_proc = proc {}
|
@@ -244,11 +502,6 @@ module Toys
|
|
244
502
|
self
|
245
503
|
end
|
246
504
|
|
247
|
-
def execute(context_base, args, verbosity: 0)
|
248
|
-
finish_definition unless @definition_finished
|
249
|
-
Execution.new(self).execute(context_base, args, verbosity: verbosity)
|
250
|
-
end
|
251
|
-
|
252
505
|
private
|
253
506
|
|
254
507
|
def make_config_proc(middleware, next_config)
|
@@ -282,69 +535,148 @@ module Toys
|
|
282
535
|
end
|
283
536
|
|
284
537
|
class << self
|
538
|
+
## @private
|
285
539
|
def canonical_switch(name)
|
286
540
|
name.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "")
|
287
541
|
end
|
542
|
+
|
543
|
+
## @private
|
544
|
+
def extract_switch(str)
|
545
|
+
if !str.is_a?(String)
|
546
|
+
[]
|
547
|
+
elsif str =~ /^(-[\?\w])(\s?\w+)?$/
|
548
|
+
[$1]
|
549
|
+
elsif str =~ /^--\[no-\](\w[\?\w-]*)$/
|
550
|
+
["--#{$1}", "--no-#{$1}"]
|
551
|
+
elsif str =~ /^(--\w[\?\w-]*)([=\s]\w+)?$/
|
552
|
+
[$1]
|
553
|
+
else
|
554
|
+
[]
|
555
|
+
end
|
556
|
+
end
|
288
557
|
end
|
289
558
|
|
290
559
|
##
|
291
|
-
# Representation of a formal switch
|
560
|
+
# Representation of a formal switch.
|
292
561
|
#
|
293
|
-
class
|
562
|
+
class SwitchDefinition
|
563
|
+
##
|
564
|
+
# Create a SwitchDefinition
|
565
|
+
#
|
566
|
+
# @param [Symbol] key This switch will set the given context key.
|
567
|
+
# @param [Array<String>] optparse_info The switch definition in
|
568
|
+
# OptionParser format
|
569
|
+
# @param [Proc,nil] handler An optional handler for setting/updating the
|
570
|
+
# value. If given, it should take two arguments, the new given value
|
571
|
+
# and the previous value, and it should return the new value that
|
572
|
+
# should be set. If `nil`, uses a default handler that just replaces
|
573
|
+
# the previous value. i.e. the default is effectively
|
574
|
+
# `-> (val, _prev) { val }`.
|
575
|
+
#
|
294
576
|
def initialize(key, optparse_info, handler = nil)
|
295
577
|
@key = key
|
296
578
|
@optparse_info = optparse_info
|
297
|
-
@handler = handler || ->(val,
|
579
|
+
@handler = handler || ->(val, _prev) { val }
|
298
580
|
@switches = nil
|
299
581
|
end
|
300
582
|
|
583
|
+
##
|
584
|
+
# Returns the key.
|
585
|
+
# @return [Symbol]
|
586
|
+
#
|
301
587
|
attr_reader :key
|
588
|
+
|
589
|
+
##
|
590
|
+
# Returns the OptionParser definition.
|
591
|
+
# @return [Array<String>]
|
592
|
+
#
|
302
593
|
attr_reader :optparse_info
|
594
|
+
|
595
|
+
##
|
596
|
+
# Returns the handler.
|
597
|
+
# @return [Proc]
|
598
|
+
#
|
303
599
|
attr_reader :handler
|
304
600
|
|
601
|
+
##
|
602
|
+
# Returns the list of switches used.
|
603
|
+
# @return [Array<String>]
|
604
|
+
#
|
305
605
|
def switches
|
306
|
-
@switches ||= optparse_info.map { |s| extract_switch(s) }.flatten
|
606
|
+
@switches ||= optparse_info.map { |s| Tool.extract_switch(s) }.flatten
|
307
607
|
end
|
308
608
|
|
609
|
+
##
|
610
|
+
# Returns true if this switch is active. That is, it has a nonempty
|
611
|
+
# switches list.
|
612
|
+
# @return [Boolean]
|
613
|
+
#
|
309
614
|
def active?
|
310
615
|
!switches.empty?
|
311
616
|
end
|
312
617
|
|
618
|
+
##
|
619
|
+
# Removes the given switches.
|
620
|
+
# @param [Array<String>] switches
|
621
|
+
#
|
313
622
|
def remove_switches(switches)
|
314
623
|
@optparse_info.select! do |s|
|
315
|
-
extract_switch(s).all? { |ss| !switches.include?(ss) }
|
624
|
+
Tool.extract_switch(s).all? { |ss| !switches.include?(ss) }
|
316
625
|
end
|
317
626
|
@switches = nil
|
318
627
|
self
|
319
628
|
end
|
320
|
-
|
321
|
-
def extract_switch(str)
|
322
|
-
if str =~ /^(-[\?\w])/
|
323
|
-
[$1]
|
324
|
-
elsif str =~ /^--\[no-\](\w[\w-]*)/
|
325
|
-
["--#{$1}", "--no-#{$1}"]
|
326
|
-
elsif str =~ /^(--\w[\w-]*)/
|
327
|
-
[$1]
|
328
|
-
else
|
329
|
-
[]
|
330
|
-
end
|
331
|
-
end
|
332
629
|
end
|
333
630
|
|
334
631
|
##
|
335
|
-
# Representation of a formal argument
|
632
|
+
# Representation of a formal positional argument
|
336
633
|
#
|
337
|
-
class
|
634
|
+
class ArgDefinition
|
635
|
+
##
|
636
|
+
# Create an ArgDefinition
|
637
|
+
#
|
638
|
+
# @param [Symbol] key This argument will set the given context key.
|
639
|
+
# @param [Object] accept An OptionParser acceptor
|
640
|
+
# @param [Array<String>] doc An array of documentation strings
|
641
|
+
#
|
338
642
|
def initialize(key, accept, doc)
|
339
643
|
@key = key
|
340
644
|
@accept = accept
|
341
645
|
@doc = doc
|
342
646
|
end
|
343
647
|
|
648
|
+
##
|
649
|
+
# Returns the key.
|
650
|
+
# @return [Symbol]
|
651
|
+
#
|
344
652
|
attr_reader :key
|
653
|
+
|
654
|
+
##
|
655
|
+
# Returns the acceptor.
|
656
|
+
# @return [Object]
|
657
|
+
#
|
345
658
|
attr_reader :accept
|
659
|
+
|
660
|
+
##
|
661
|
+
# Returns the documentation strings.
|
662
|
+
# @return [Array<String>]
|
663
|
+
#
|
346
664
|
attr_reader :doc
|
347
665
|
|
666
|
+
##
|
667
|
+
# Return a canonical name for this arg. Used in usage documentation.
|
668
|
+
#
|
669
|
+
# @return [String]
|
670
|
+
#
|
671
|
+
def canonical_name
|
672
|
+
Tool.canonical_switch(key)
|
673
|
+
end
|
674
|
+
|
675
|
+
##
|
676
|
+
# Process the given value through the acceptor.
|
677
|
+
#
|
678
|
+
# @private
|
679
|
+
#
|
348
680
|
def process_value(val)
|
349
681
|
return val unless accept
|
350
682
|
n = canonical_name
|
@@ -354,10 +686,6 @@ module Toys
|
|
354
686
|
optparse.parse(["--#{n}", val])
|
355
687
|
result
|
356
688
|
end
|
357
|
-
|
358
|
-
def canonical_name
|
359
|
-
Tool.canonical_switch(key)
|
360
|
-
end
|
361
689
|
end
|
362
690
|
|
363
691
|
##
|
@@ -409,7 +737,7 @@ module Toys
|
|
409
737
|
optparse.remove
|
410
738
|
optparse.new
|
411
739
|
optparse.new
|
412
|
-
@tool.
|
740
|
+
@tool.switch_definitions.each do |switch|
|
413
741
|
optparse.on(*switch.optparse_info) do |val|
|
414
742
|
@data[switch.key] = switch.handler.call(val, @data[switch.key])
|
415
743
|
end
|
@@ -418,7 +746,7 @@ module Toys
|
|
418
746
|
end
|
419
747
|
|
420
748
|
def parse_required_args(remaining, args)
|
421
|
-
@tool.
|
749
|
+
@tool.required_arg_definitions.each do |arg_info|
|
422
750
|
if remaining.empty?
|
423
751
|
reason = "No value given for required argument named <#{arg_info.canonical_name}>"
|
424
752
|
raise create_parse_error(args, reason)
|
@@ -429,7 +757,7 @@ module Toys
|
|
429
757
|
end
|
430
758
|
|
431
759
|
def parse_optional_args(remaining)
|
432
|
-
@tool.
|
760
|
+
@tool.optional_arg_definitions.each do |arg_info|
|
433
761
|
break if remaining.empty?
|
434
762
|
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
435
763
|
end
|
@@ -438,15 +766,15 @@ module Toys
|
|
438
766
|
|
439
767
|
def parse_remaining_args(remaining, args)
|
440
768
|
return if remaining.empty?
|
441
|
-
unless @tool.
|
769
|
+
unless @tool.remaining_args_definition
|
442
770
|
if @tool.includes_executor?
|
443
771
|
raise create_parse_error(remaining, "Extra arguments provided")
|
444
772
|
else
|
445
773
|
raise create_parse_error(@tool.full_name + args, "Tool not found")
|
446
774
|
end
|
447
775
|
end
|
448
|
-
@data[@tool.
|
449
|
-
remaining.map { |arg| @tool.
|
776
|
+
@data[@tool.remaining_args_definition.key] =
|
777
|
+
remaining.map { |arg| @tool.remaining_args_definition.process_value(arg) }
|
450
778
|
end
|
451
779
|
|
452
780
|
def create_parse_error(path, reason)
|