toys-core 0.11.5 → 0.12.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 +31 -0
- data/README.md +1 -1
- data/lib/toys-core.rb +4 -1
- data/lib/toys/acceptor.rb +3 -3
- data/lib/toys/arg_parser.rb +6 -7
- data/lib/toys/cli.rb +44 -14
- data/lib/toys/compat.rb +19 -22
- data/lib/toys/completion.rb +3 -1
- data/lib/toys/context.rb +2 -2
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/base.rb +85 -0
- data/lib/toys/dsl/flag.rb +3 -3
- data/lib/toys/dsl/flag_group.rb +7 -7
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +3 -3
- data/lib/toys/dsl/tool.rb +174 -216
- data/lib/toys/errors.rb +1 -0
- data/lib/toys/flag.rb +15 -18
- data/lib/toys/flag_group.rb +5 -4
- data/lib/toys/input_file.rb +4 -4
- data/lib/toys/loader.rb +189 -50
- data/lib/toys/middleware.rb +1 -1
- data/lib/toys/mixin.rb +2 -2
- data/lib/toys/positional_arg.rb +3 -3
- data/lib/toys/settings.rb +900 -0
- data/lib/toys/source_info.rb +121 -18
- data/lib/toys/standard_middleware/apply_config.rb +5 -4
- data/lib/toys/standard_middleware/set_default_descriptions.rb +18 -18
- data/lib/toys/standard_middleware/show_help.rb +17 -5
- data/lib/toys/standard_mixins/exec.rb +12 -14
- data/lib/toys/standard_mixins/git_cache.rb +48 -0
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +2 -2
- data/lib/toys/{tool.rb → tool_definition.rb} +100 -41
- data/lib/toys/utils/exec.rb +4 -5
- data/lib/toys/utils/gems.rb +8 -7
- data/lib/toys/utils/git_cache.rb +184 -0
- data/lib/toys/utils/help_text.rb +90 -34
- data/lib/toys/utils/terminal.rb +1 -1
- data/lib/toys/utils/xdg.rb +293 -0
- metadata +14 -7
data/lib/toys/errors.rb
CHANGED
data/lib/toys/flag.rb
CHANGED
@@ -85,11 +85,11 @@ module Toys
|
|
85
85
|
# true.
|
86
86
|
# @param group [Toys::FlagGroup] Group containing this flag.
|
87
87
|
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
88
|
-
# description for the flag. See {Toys::
|
89
|
-
# allowed formats. Defaults to the empty string.
|
88
|
+
# description for the flag. See {Toys::ToolDefinition#desc} for a
|
89
|
+
# description of allowed formats. Defaults to the empty string.
|
90
90
|
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
91
|
-
# Long description for the flag. See {Toys::
|
92
|
-
# description of allowed formats. Defaults to the empty array.
|
91
|
+
# Long description for the flag. See {Toys::ToolDefinition#long_desc}
|
92
|
+
# for a description of allowed formats. Defaults to the empty array.
|
93
93
|
# @param display_name [String] A display name for this flag, used in help
|
94
94
|
# text and error messages.
|
95
95
|
# @param used_flags [Array<String>] An array of flags already in use.
|
@@ -368,7 +368,7 @@ module Toys
|
|
368
368
|
key_str = key.to_s
|
369
369
|
flag_str =
|
370
370
|
if key_str.length == 1
|
371
|
-
"-#{key_str}" if key_str =~ /[a-zA-Z0-9
|
371
|
+
"-#{key_str}" if key_str =~ /[a-zA-Z0-9?]/
|
372
372
|
elsif key_str.length > 1
|
373
373
|
key_str = key_str.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "").sub(/^-+/, "")
|
374
374
|
"--#{key_str}" unless key_str.empty?
|
@@ -455,23 +455,19 @@ module Toys
|
|
455
455
|
#
|
456
456
|
def initialize(str)
|
457
457
|
case str
|
458
|
-
when /\A(-([
|
458
|
+
when /\A(-([?\w]))\z/
|
459
459
|
setup(str, $1, nil, $1, $2, :short, nil, nil, nil, nil)
|
460
|
-
when /\A(-([
|
461
|
-
setup(str, $1, nil, $1, $2, :short, :value, :optional, $3, $
|
462
|
-
when /\A(-([
|
463
|
-
setup(str, $1, nil, $1, $2, :short, :value, :optional, $3, $4)
|
464
|
-
when /\A(-([\?\w]))( ?)(\w+)\z/
|
460
|
+
when /\A(-([?\w]))(?:( ?)\[|\[( ))(\w+)\]\z/
|
461
|
+
setup(str, $1, nil, $1, $2, :short, :value, :optional, $3 || $4, $5)
|
462
|
+
when /\A(-([?\w]))( ?)(\w+)\z/
|
465
463
|
setup(str, $1, nil, $1, $2, :short, :value, :required, $3, $4)
|
466
|
-
when /\A--\[no-\](\w[
|
464
|
+
when /\A--\[no-\](\w[?\w-]*)\z/
|
467
465
|
setup(str, "--#{$1}", "--no-#{$1}", str, $1, :long, :boolean, nil, nil, nil)
|
468
|
-
when /\A(--(\w[
|
466
|
+
when /\A(--(\w[?\w-]*))\z/
|
469
467
|
setup(str, $1, nil, $1, $2, :long, nil, nil, nil, nil)
|
470
|
-
when /\A(--(\w[
|
471
|
-
setup(str, $1, nil, $1, $2, :long, :value, :optional, $3, $
|
472
|
-
when /\A(--(\w[
|
473
|
-
setup(str, $1, nil, $1, $2, :long, :value, :optional, $3, $4)
|
474
|
-
when /\A(--(\w[\?\w-]*))([= ])(\w+)\z/
|
468
|
+
when /\A(--(\w[?\w-]*))(?:([= ])\[|\[([= ]))(\w+)\]\z/
|
469
|
+
setup(str, $1, nil, $1, $2, :long, :value, :optional, $3 || $4, $5)
|
470
|
+
when /\A(--(\w[?\w-]*))([= ])(\w+)\z/
|
475
471
|
setup(str, $1, nil, $1, $2, :long, :value, :required, $3, $4)
|
476
472
|
else
|
477
473
|
raise ToolDefinitionError, "Illegal flag: #{str.inspect}"
|
@@ -731,6 +727,7 @@ module Toys
|
|
731
727
|
# @param include_negative [Boolean] Whether to include `--no-*` forms.
|
732
728
|
#
|
733
729
|
def initialize(flag:, include_short: true, include_long: true, include_negative: true)
|
730
|
+
super()
|
734
731
|
@flag = flag
|
735
732
|
@include_short = include_short
|
736
733
|
@include_long = include_long
|
data/lib/toys/flag_group.rb
CHANGED
@@ -17,11 +17,12 @@ module Toys
|
|
17
17
|
#
|
18
18
|
# @param type [Symbol] The type of group. Default is `:optional`.
|
19
19
|
# @param desc [String,Array<String>,Toys::WrappableString] Short
|
20
|
-
# description for the group. See {Toys::
|
21
|
-
# of allowed formats. Defaults to `"Flags"`.
|
20
|
+
# description for the group. See {Toys::ToolDefinition#desc} for a
|
21
|
+
# description of allowed formats. Defaults to `"Flags"`.
|
22
22
|
# @param long_desc [Array<String,Array<String>,Toys::WrappableString>]
|
23
|
-
# Long description for the flag group. See
|
24
|
-
# a description of allowed
|
23
|
+
# Long description for the flag group. See
|
24
|
+
# {Toys::ToolDefinition#long_desc} for a description of allowed
|
25
|
+
# formats. Defaults to the empty array.
|
25
26
|
# @param name [String,Symbol,nil] The name of the group, or nil for no
|
26
27
|
# name.
|
27
28
|
# @return [Toys::FlagGroup::Base] A flag group of the correct subclass.
|
data/lib/toys/input_file.rb
CHANGED
@@ -11,11 +11,11 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
11
11
|
end
|
12
12
|
|
13
13
|
## @private
|
14
|
-
def self.evaluate(tool_class, remaining_words, source)
|
14
|
+
def self.evaluate(tool_class, words, priority, remaining_words, source, loader)
|
15
15
|
namespace = ::Module.new
|
16
16
|
namespace.module_eval do
|
17
17
|
include ::Toys::Context::Key
|
18
|
-
@
|
18
|
+
@__tool_class = tool_class
|
19
19
|
end
|
20
20
|
path = source.source_path
|
21
21
|
basename = ::File.basename(path).tr(".-", "_").gsub(/\W/, "")
|
@@ -23,7 +23,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
23
23
|
str = build_eval_string(name, ::IO.read(path))
|
24
24
|
if str
|
25
25
|
const_set(name, namespace)
|
26
|
-
::Toys::DSL::
|
26
|
+
::Toys::DSL::Internal.prepare(tool_class, words, priority, remaining_words, source, loader) do
|
27
27
|
::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
|
28
28
|
# rubocop:disable Security/Eval
|
29
29
|
eval(str, __binding, path, -2)
|
@@ -39,7 +39,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
39
39
|
return nil if index.nil?
|
40
40
|
"#{string[0, index]}\n" \
|
41
41
|
"module #{module_name}\n" \
|
42
|
-
"@
|
42
|
+
"@__tool_class.class_eval do\n" \
|
43
43
|
"#{string[index..-1]}\n" \
|
44
44
|
"end\n" \
|
45
45
|
"end\n"
|
data/lib/toys/loader.rb
CHANGED
@@ -54,7 +54,8 @@ module Toys
|
|
54
54
|
extra_delimiters: "",
|
55
55
|
mixin_lookup: nil,
|
56
56
|
middleware_lookup: nil,
|
57
|
-
template_lookup: nil
|
57
|
+
template_lookup: nil,
|
58
|
+
git_cache: nil
|
58
59
|
)
|
59
60
|
if index_file_name && ::File.extname(index_file_name) != ".rb"
|
60
61
|
raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
|
@@ -71,30 +72,82 @@ module Toys
|
|
71
72
|
@loading_started = false
|
72
73
|
@worklist = []
|
73
74
|
@tool_data = {}
|
75
|
+
@roots_by_priority = {}
|
74
76
|
@max_priority = @min_priority = 0
|
75
77
|
@stop_priority = BASE_PRIORITY
|
76
78
|
@min_loaded_priority = 999_999
|
77
79
|
@middleware_stack = Middleware.stack(middleware_stack)
|
78
80
|
@delimiter_handler = DelimiterHandler.new(extra_delimiters)
|
81
|
+
@git_cache = git_cache
|
79
82
|
get_tool([], BASE_PRIORITY)
|
80
83
|
end
|
81
84
|
|
82
85
|
##
|
83
86
|
# Add a configuration file/directory to the loader.
|
84
87
|
#
|
85
|
-
# @param
|
88
|
+
# @param path [String] A single path to add.
|
86
89
|
# @param high_priority [Boolean] If true, add this path at the top of the
|
87
90
|
# priority list. Defaults to false, indicating the new path should be
|
88
91
|
# at the bottom of the priority list.
|
92
|
+
# @param source_name [String] A custom name for the root source. Optional.
|
93
|
+
# @param context_directory [String,nil,:path,:parent] The context directory
|
94
|
+
# for tools loaded from this path. You can pass a directory path as a
|
95
|
+
# string, `:path` to denote the given path, `:parent` to denote the
|
96
|
+
# given path's parent directory, or `nil` to denote no context.
|
97
|
+
# Defaults to `:parent`.
|
89
98
|
# @return [self]
|
90
99
|
#
|
91
|
-
def add_path(
|
92
|
-
|
100
|
+
def add_path(path,
|
101
|
+
high_priority: false,
|
102
|
+
source_name: nil,
|
103
|
+
context_directory: :parent)
|
93
104
|
@mutex.synchronize do
|
94
105
|
raise "Cannot add a path after tool loading has started" if @loading_started
|
95
106
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
96
|
-
|
97
|
-
|
107
|
+
source = SourceInfo.create_path_root(path, priority,
|
108
|
+
context_directory: context_directory,
|
109
|
+
data_dir_name: @data_dir_name,
|
110
|
+
lib_dir_name: @lib_dir_name,
|
111
|
+
source_name: source_name)
|
112
|
+
@roots_by_priority[priority] = source
|
113
|
+
@worklist << [source, [], priority]
|
114
|
+
end
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Add a set of configuration files/directories from a common directory to
|
120
|
+
# the loader. The set of paths will be added at the same priority level and
|
121
|
+
# will share a root.
|
122
|
+
#
|
123
|
+
# @param root_path [String] A root path to be seen as the root source. This
|
124
|
+
# should generally be a directory containing the paths to add.
|
125
|
+
# @param relative_paths [String,Array<String>] One or more paths to add, as
|
126
|
+
# relative paths from the common root.
|
127
|
+
# @param high_priority [Boolean] If true, add the paths at the top of the
|
128
|
+
# priority list. Defaults to false, indicating the new paths should be
|
129
|
+
# at the bottom of the priority list.
|
130
|
+
# @param context_directory [String,nil,:path,:parent] The context directory
|
131
|
+
# for tools loaded from this path. You can pass a directory path as a
|
132
|
+
# string, `:path` to denote the given root path, `:parent` to denote
|
133
|
+
# the given root path's parent directory, or `nil` to denote no context.
|
134
|
+
# Defaults to `:path`.
|
135
|
+
# @return [self]
|
136
|
+
#
|
137
|
+
def add_path_set(root_path, relative_paths,
|
138
|
+
high_priority: false,
|
139
|
+
context_directory: :path)
|
140
|
+
relative_paths = Array(relative_paths)
|
141
|
+
@mutex.synchronize do
|
142
|
+
raise "Cannot add a path after tool loading has started" if @loading_started
|
143
|
+
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
144
|
+
root_source = SourceInfo.create_path_root(root_path, priority,
|
145
|
+
context_directory: context_directory,
|
146
|
+
data_dir_name: @data_dir_name,
|
147
|
+
lib_dir_name: @lib_dir_name)
|
148
|
+
@roots_by_priority[priority] = root_source
|
149
|
+
relative_paths.each do |path, individual_name|
|
150
|
+
source = root_source.relative_child(path, source_name: individual_name)
|
98
151
|
@worklist << [source, [], priority]
|
99
152
|
end
|
100
153
|
end
|
@@ -107,19 +160,65 @@ module Toys
|
|
107
160
|
# @param high_priority [Boolean] If true, add this block at the top of the
|
108
161
|
# priority list. Defaults to false, indicating the block should be at
|
109
162
|
# the bottom of the priority list.
|
110
|
-
# @param
|
111
|
-
# for tools defined in this block. If omitted, a default
|
112
|
-
# will be generated.
|
163
|
+
# @param source_name [String] The source name that will be shown in
|
164
|
+
# documentation for tools defined in this block. If omitted, a default
|
165
|
+
# unique string will be generated.
|
113
166
|
# @param block [Proc] The block of configuration, executed in the context
|
114
167
|
# of the tool DSL {Toys::DSL::Tool}.
|
168
|
+
# @param context_directory [String,nil] The context directory for tools
|
169
|
+
# loaded from this block. You can pass a directory path as a string, or
|
170
|
+
# `nil` to denote no context. Defaults to `nil`.
|
115
171
|
# @return [self]
|
116
172
|
#
|
117
|
-
def add_block(high_priority: false,
|
118
|
-
|
173
|
+
def add_block(high_priority: false,
|
174
|
+
source_name: nil,
|
175
|
+
context_directory: nil,
|
176
|
+
&block)
|
119
177
|
@mutex.synchronize do
|
120
178
|
raise "Cannot add a block after tool loading has started" if @loading_started
|
121
179
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
122
|
-
source = SourceInfo.create_proc_root(block,
|
180
|
+
source = SourceInfo.create_proc_root(block, priority,
|
181
|
+
context_directory: context_directory,
|
182
|
+
source_name: source_name,
|
183
|
+
data_dir_name: @data_dir_name,
|
184
|
+
lib_dir_name: @lib_dir_name)
|
185
|
+
@roots_by_priority[priority] = source
|
186
|
+
@worklist << [source, [], priority]
|
187
|
+
end
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Add a configuration git source to the loader.
|
193
|
+
#
|
194
|
+
# @param git_remote [String] The git repo URL
|
195
|
+
# @param git_path [String] The path to the relevant file or directory in
|
196
|
+
# the repo. Specify the empty string to use the entire repo.
|
197
|
+
# @param git_commit [String] The git ref (i.e. SHA, tag, or branch name)
|
198
|
+
# @param high_priority [Boolean] If true, add this path at the top of the
|
199
|
+
# priority list. Defaults to false, indicating the new path should be
|
200
|
+
# at the bottom of the priority list.
|
201
|
+
# @param update [Boolean] If the commit is not a SHA, pulls any updates
|
202
|
+
# from the remote. Defaults to false, which uses a local cache and does
|
203
|
+
# not update if the commit has been fetched previously.
|
204
|
+
# @param context_directory [String,nil] The context directory for tools
|
205
|
+
# loaded from this source. You can pass a directory path as a string,
|
206
|
+
# or `nil` to denote no context. Defaults to `nil`.
|
207
|
+
# @return [self]
|
208
|
+
#
|
209
|
+
def add_git(git_remote, git_path, git_commit,
|
210
|
+
high_priority: false,
|
211
|
+
update: false,
|
212
|
+
context_directory: nil)
|
213
|
+
@mutex.synchronize do
|
214
|
+
raise "Cannot add a git source after tool loading has started" if @loading_started
|
215
|
+
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
216
|
+
path = git_cache.find(git_remote, path: git_path, commit: git_commit, update: update)
|
217
|
+
source = SourceInfo.create_git_root(git_remote, git_path, git_commit, path, priority,
|
218
|
+
context_directory: context_directory,
|
219
|
+
data_dir_name: @data_dir_name,
|
220
|
+
lib_dir_name: @lib_dir_name)
|
221
|
+
@roots_by_priority[priority] = source
|
123
222
|
@worklist << [source, [], priority]
|
124
223
|
end
|
125
224
|
self
|
@@ -136,7 +235,7 @@ module Toys
|
|
136
235
|
# that are not part of the tool name and should be passed as tool args.
|
137
236
|
#
|
138
237
|
# @param args [Array<String>] Command line arguments
|
139
|
-
# @return [Array(Toys::
|
238
|
+
# @return [Array(Toys::ToolDefinition,Array<String>)]
|
140
239
|
#
|
141
240
|
def lookup(args)
|
142
241
|
orig_prefix, args = @delimiter_handler.find_orig_prefix(args)
|
@@ -157,13 +256,13 @@ module Toys
|
|
157
256
|
# the given name, returns `nil`.
|
158
257
|
#
|
159
258
|
# @param words [Array<String>] The tool name
|
160
|
-
# @return [Toys::
|
259
|
+
# @return [Toys::ToolDefinition] if the tool was found
|
161
260
|
# @return [nil] if no such tool exists
|
162
261
|
#
|
163
262
|
def lookup_specific(words)
|
164
263
|
words = @delimiter_handler.split_path(words.first) if words.size == 1
|
165
264
|
load_for_prefix(words)
|
166
|
-
tool = get_tool_data(words)
|
265
|
+
tool = get_tool_data(words, false)&.cur_definition
|
167
266
|
finish_definitions_in_tree(words) if tool
|
168
267
|
tool
|
169
268
|
end
|
@@ -177,7 +276,7 @@ module Toys
|
|
177
276
|
# rather than just the immediate children (the default)
|
178
277
|
# @param include_hidden [Boolean] If true, include hidden subtools,
|
179
278
|
# e.g. names beginning with underscores.
|
180
|
-
# @return [Array<Toys::
|
279
|
+
# @return [Array<Toys::ToolDefinition>] An array of subtools.
|
181
280
|
#
|
182
281
|
def list_subtools(words, recursive: false, include_hidden: false)
|
183
282
|
load_for_prefix(words)
|
@@ -234,8 +333,8 @@ module Toys
|
|
234
333
|
#
|
235
334
|
# @private
|
236
335
|
#
|
237
|
-
def get_tool(words, priority)
|
238
|
-
get_tool_data(words).get_tool(priority, self)
|
336
|
+
def get_tool(words, priority, tool_class = nil)
|
337
|
+
get_tool_data(words, true).get_tool(priority, self, tool_class)
|
239
338
|
end
|
240
339
|
|
241
340
|
##
|
@@ -248,7 +347,7 @@ module Toys
|
|
248
347
|
# @private
|
249
348
|
#
|
250
349
|
def activate_tool(words, priority)
|
251
|
-
get_tool_data(words).activate_tool(priority, self)
|
350
|
+
get_tool_data(words, true).activate_tool(priority, self)
|
252
351
|
end
|
253
352
|
|
254
353
|
##
|
@@ -267,10 +366,11 @@ module Toys
|
|
267
366
|
#
|
268
367
|
# @private
|
269
368
|
#
|
270
|
-
def build_tool(words, priority)
|
369
|
+
def build_tool(words, priority, tool_class = nil)
|
271
370
|
parent = words.empty? ? nil : get_tool(words.slice(0..-2), priority)
|
272
371
|
middleware_stack = parent ? parent.subtool_middleware_stack : @middleware_stack
|
273
|
-
|
372
|
+
ToolDefinition.new(parent, words, priority, @roots_by_priority[priority],
|
373
|
+
middleware_stack, @middleware_lookup, tool_class)
|
274
374
|
end
|
275
375
|
|
276
376
|
##
|
@@ -335,12 +435,31 @@ module Toys
|
|
335
435
|
# @private
|
336
436
|
#
|
337
437
|
def load_path(parent_source, path, words, remaining_words, priority)
|
438
|
+
if parent_source.git_remote
|
439
|
+
raise LoaderError,
|
440
|
+
"Git source #{parent_source.source_name} tried to load from the local file system"
|
441
|
+
end
|
338
442
|
source = parent_source.absolute_child(path)
|
339
443
|
@mutex.synchronize do
|
340
444
|
load_validated_path(source, words, remaining_words, priority)
|
341
445
|
end
|
342
446
|
end
|
343
447
|
|
448
|
+
##
|
449
|
+
# Load configuration from the given git remote. This is called from the
|
450
|
+
# `load_git` directive in the DSL.
|
451
|
+
#
|
452
|
+
# @private
|
453
|
+
#
|
454
|
+
def load_git(parent_source, git_remote, git_path, git_commit, words, remaining_words, priority,
|
455
|
+
update: false)
|
456
|
+
path = git_cache.find(git_remote, path: git_path, commit: git_commit, update: update)
|
457
|
+
source = parent_source.git_child(git_remote, git_path, git_commit, path)
|
458
|
+
@mutex.synchronize do
|
459
|
+
load_validated_path(source, words, remaining_words, priority)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
344
463
|
##
|
345
464
|
# Load a subtool block. Called from the `tool` directive in the DSL.
|
346
465
|
#
|
@@ -353,6 +472,18 @@ module Toys
|
|
353
472
|
end
|
354
473
|
end
|
355
474
|
|
475
|
+
##
|
476
|
+
# Get a GitCache.
|
477
|
+
#
|
478
|
+
# @private
|
479
|
+
#
|
480
|
+
def git_cache
|
481
|
+
@git_cache ||= begin
|
482
|
+
require "toys/utils/git_cache"
|
483
|
+
Utils::GitCache.new
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
356
487
|
##
|
357
488
|
# Determine the next setting for remaining_words, given a word.
|
358
489
|
#
|
@@ -381,8 +512,10 @@ module Toys
|
|
381
512
|
result
|
382
513
|
end
|
383
514
|
|
384
|
-
def get_tool_data(words)
|
385
|
-
@mutex.synchronize
|
515
|
+
def get_tool_data(words, create)
|
516
|
+
@mutex.synchronize do
|
517
|
+
create ? (@tool_data[words] ||= ToolData.new(words)) : @tool_data[words]
|
518
|
+
end
|
386
519
|
end
|
387
520
|
|
388
521
|
##
|
@@ -403,7 +536,7 @@ module Toys
|
|
403
536
|
if remaining_words
|
404
537
|
update_min_loaded_priority(priority)
|
405
538
|
tool_class = get_tool(words, priority).tool_class
|
406
|
-
DSL::
|
539
|
+
DSL::Internal.prepare(tool_class, words, priority, remaining_words, source, self) do
|
407
540
|
ContextualError.capture("Error while loading Toys config!") do
|
408
541
|
tool_class.class_eval(&source.source_proc)
|
409
542
|
end
|
@@ -425,7 +558,7 @@ module Toys
|
|
425
558
|
if source.source_type == :file
|
426
559
|
update_min_loaded_priority(priority)
|
427
560
|
tool_class = get_tool(words, priority).tool_class
|
428
|
-
InputFile.evaluate(tool_class, remaining_words, source)
|
561
|
+
InputFile.evaluate(tool_class, words, priority, remaining_words, source, self)
|
429
562
|
else
|
430
563
|
do_preload(source.source_path)
|
431
564
|
load_index_in(source, words, remaining_words, priority)
|
@@ -467,16 +600,20 @@ module Toys
|
|
467
600
|
if @preload_dir_name
|
468
601
|
preload_dir = ::File.join(path, @preload_dir_name)
|
469
602
|
if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
|
470
|
-
|
471
|
-
next unless ::File.extname(child) == ".rb"
|
472
|
-
preload_file = ::File.join(preload_dir, child)
|
473
|
-
next if !::File.file?(preload_file) || !::File.readable?(preload_file)
|
474
|
-
require preload_file
|
475
|
-
end
|
603
|
+
preload_dir_contents(preload_dir)
|
476
604
|
end
|
477
605
|
end
|
478
606
|
end
|
479
607
|
|
608
|
+
def preload_dir_contents(preload_dir)
|
609
|
+
::Dir.entries(preload_dir).each do |child|
|
610
|
+
next unless ::File.extname(child) == ".rb"
|
611
|
+
preload_file = ::File.join(preload_dir, child)
|
612
|
+
next if !::File.file?(preload_file) || !::File.readable?(preload_file)
|
613
|
+
require preload_file
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
480
617
|
def sort_tools_by_name(tools)
|
481
618
|
tools.sort! do |a, b|
|
482
619
|
a = a.full_name
|
@@ -520,7 +657,7 @@ module Toys
|
|
520
657
|
class ToolData
|
521
658
|
# @private
|
522
659
|
def initialize(words)
|
523
|
-
@words = words
|
660
|
+
@words = validate_words(words)
|
524
661
|
@definitions = {}
|
525
662
|
@top_priority = @active_priority = nil
|
526
663
|
@mutex = ::Monitor.new
|
@@ -537,12 +674,15 @@ module Toys
|
|
537
674
|
end
|
538
675
|
|
539
676
|
# @private
|
540
|
-
def get_tool(priority, loader)
|
677
|
+
def get_tool(priority, loader, tool_class = nil)
|
541
678
|
@mutex.synchronize do
|
542
679
|
if @top_priority.nil? || @top_priority < priority
|
543
680
|
@top_priority = priority
|
544
681
|
end
|
545
|
-
@definitions
|
682
|
+
if tool_class && @definitions.include?(priority)
|
683
|
+
raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
|
684
|
+
end
|
685
|
+
@definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
|
546
686
|
end
|
547
687
|
end
|
548
688
|
|
@@ -558,6 +698,14 @@ module Toys
|
|
558
698
|
|
559
699
|
private
|
560
700
|
|
701
|
+
def validate_words(words)
|
702
|
+
words.each do |word|
|
703
|
+
if /[[:cntrl:] #"$&'()*;<>\[\\\]\^`{|}]/.match(word)
|
704
|
+
raise ToolDefinitionError, "Illegal characters in name #{word.inspect}"
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
561
709
|
def top_definition
|
562
710
|
@top_priority ? @definitions[@top_priority] : nil
|
563
711
|
end
|
@@ -573,32 +721,23 @@ module Toys
|
|
573
721
|
# @private
|
574
722
|
#
|
575
723
|
class DelimiterHandler
|
576
|
-
## @private
|
577
|
-
ALLOWED_DELIMITERS = %r{^[\./:]*$}.freeze
|
578
|
-
private_constant :ALLOWED_DELIMITERS
|
579
|
-
|
580
|
-
## @private
|
581
724
|
def initialize(extra_delimiters)
|
582
|
-
unless
|
725
|
+
unless %r{^[[:space:]./:]*$}.match?(extra_delimiters)
|
583
726
|
raise ::ArgumentError, "Illegal delimiters in #{extra_delimiters.inspect}"
|
584
727
|
end
|
585
728
|
chars = ::Regexp.escape(extra_delimiters.chars.uniq.join)
|
586
|
-
@
|
729
|
+
@delimiters = ::Regexp.new("[[:space:]#{chars}]")
|
587
730
|
end
|
588
731
|
|
589
|
-
## @private
|
590
732
|
def split_path(str)
|
591
|
-
|
733
|
+
str.split(@delimiters)
|
592
734
|
end
|
593
735
|
|
594
|
-
## @private
|
595
736
|
def find_orig_prefix(args)
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
return [first_split, args]
|
601
|
-
end
|
737
|
+
first_split = (args.first || "").split(@delimiters)
|
738
|
+
if first_split.size > 1
|
739
|
+
args = first_split + args.slice(1..-1)
|
740
|
+
return [first_split, args]
|
602
741
|
end
|
603
742
|
orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
|
604
743
|
[orig_prefix, args]
|