toys-core 0.9.4 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -1
- data/CHANGELOG.md +30 -0
- data/LICENSE.md +1 -1
- data/README.md +3 -3
- data/lib/toys-core.rb +11 -21
- data/lib/toys/acceptor.rb +0 -21
- data/lib/toys/arg_parser.rb +1 -22
- data/lib/toys/cli.rb +102 -70
- data/lib/toys/compat.rb +49 -41
- data/lib/toys/completion.rb +0 -21
- data/lib/toys/context.rb +0 -23
- data/lib/toys/core.rb +1 -22
- data/lib/toys/dsl/flag.rb +0 -21
- data/lib/toys/dsl/flag_group.rb +0 -21
- data/lib/toys/dsl/positional_arg.rb +0 -21
- data/lib/toys/dsl/tool.rb +135 -51
- data/lib/toys/errors.rb +0 -21
- data/lib/toys/flag.rb +0 -21
- data/lib/toys/flag_group.rb +0 -21
- data/lib/toys/input_file.rb +0 -21
- data/lib/toys/loader.rb +41 -78
- data/lib/toys/middleware.rb +146 -77
- data/lib/toys/mixin.rb +0 -21
- data/lib/toys/module_lookup.rb +3 -26
- data/lib/toys/positional_arg.rb +0 -21
- data/lib/toys/source_info.rb +49 -38
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +0 -23
- data/lib/toys/standard_middleware/apply_config.rb +42 -0
- data/lib/toys/standard_middleware/handle_usage_errors.rb +7 -28
- data/lib/toys/standard_middleware/set_default_descriptions.rb +0 -23
- data/lib/toys/standard_middleware/show_help.rb +0 -23
- data/lib/toys/standard_middleware/show_root_version.rb +0 -23
- data/lib/toys/standard_mixins/bundler.rb +89 -0
- data/lib/toys/standard_mixins/exec.rb +124 -35
- data/lib/toys/standard_mixins/fileutils.rb +0 -21
- data/lib/toys/standard_mixins/gems.rb +2 -24
- data/lib/toys/standard_mixins/highline.rb +0 -21
- data/lib/toys/standard_mixins/terminal.rb +0 -21
- data/lib/toys/template.rb +0 -21
- data/lib/toys/tool.rb +22 -34
- data/lib/toys/utils/completion_engine.rb +0 -21
- data/lib/toys/utils/exec.rb +1 -21
- data/lib/toys/utils/gems.rb +174 -63
- data/lib/toys/utils/help_text.rb +0 -21
- data/lib/toys/utils/terminal.rb +46 -37
- data/lib/toys/wrappable_string.rb +0 -21
- metadata +25 -9
data/lib/toys/errors.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
module Toys
|
25
4
|
##
|
26
5
|
# An exception indicating an error in a tool definition.
|
data/lib/toys/flag.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
module Toys
|
25
4
|
##
|
26
5
|
# Representation of a formal set of flags that set a particular context
|
data/lib/toys/flag_group.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
module Toys
|
25
4
|
##
|
26
5
|
# A FlagGroup is a group of flags with the same requirement settings.
|
data/lib/toys/input_file.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
##
|
25
4
|
# This module is a namespace for constant scopes. Whenever a configuration file
|
26
5
|
# is parsed, a module is created under this parent for that file's constants.
|
data/lib/toys/loader.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
require "monitor"
|
25
4
|
|
26
5
|
module Toys
|
@@ -28,11 +7,7 @@ module Toys
|
|
28
7
|
# The Loader service loads tools from configuration files, and finds the
|
29
8
|
# appropriate tool given a set of command line arguments.
|
30
9
|
#
|
31
|
-
# This class is not thread-safe.
|
32
|
-
#
|
33
10
|
class Loader
|
34
|
-
include ::MonitorMixin
|
35
|
-
|
36
11
|
##
|
37
12
|
# Create a Loader
|
38
13
|
#
|
@@ -50,6 +25,9 @@ module Toys
|
|
50
25
|
# @param data_dir_name [String,nil] A directory with this name that appears
|
51
26
|
# in any configuration directory is added to the data directory search
|
52
27
|
# path for any tool file in that directory.
|
28
|
+
# @param lib_dir_name [String,nil] A directory with this name that appears
|
29
|
+
# in any configuration directory is added to the Ruby load path for any
|
30
|
+
# tool file in that directory.
|
53
31
|
# @param middleware_stack [Array<Toys::Middleware::Spec>] An array of
|
54
32
|
# middleware that will be used by default for all tools loaded by this
|
55
33
|
# loader.
|
@@ -63,13 +41,22 @@ module Toys
|
|
63
41
|
# @param template_lookup [Toys::ModuleLookup] A lookup for
|
64
42
|
# well-known template classes. Defaults to an empty lookup.
|
65
43
|
#
|
66
|
-
def initialize(
|
67
|
-
|
68
|
-
|
69
|
-
|
44
|
+
def initialize(
|
45
|
+
index_file_name: nil,
|
46
|
+
preload_dir_name: nil,
|
47
|
+
preload_file_name: nil,
|
48
|
+
data_dir_name: nil,
|
49
|
+
lib_dir_name: nil,
|
50
|
+
middleware_stack: [],
|
51
|
+
extra_delimiters: "",
|
52
|
+
mixin_lookup: nil,
|
53
|
+
middleware_lookup: nil,
|
54
|
+
template_lookup: nil
|
55
|
+
)
|
70
56
|
if index_file_name && ::File.extname(index_file_name) != ".rb"
|
71
57
|
raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
|
72
58
|
end
|
59
|
+
@mutex = ::Monitor.new
|
73
60
|
@mixin_lookup = mixin_lookup || ModuleLookup.new
|
74
61
|
@template_lookup = template_lookup || ModuleLookup.new
|
75
62
|
@middleware_lookup = middleware_lookup || ModuleLookup.new
|
@@ -77,11 +64,12 @@ module Toys
|
|
77
64
|
@preload_file_name = preload_file_name
|
78
65
|
@preload_dir_name = preload_dir_name
|
79
66
|
@data_dir_name = data_dir_name
|
67
|
+
@lib_dir_name = lib_dir_name
|
80
68
|
@loading_started = false
|
81
69
|
@worklist = []
|
82
70
|
@tool_data = {}
|
83
71
|
@max_priority = @min_priority = 0
|
84
|
-
@middleware_stack = Middleware.
|
72
|
+
@middleware_stack = Middleware.stack(middleware_stack)
|
85
73
|
@delimiter_handler = DelimiterHandler.new(extra_delimiters)
|
86
74
|
get_tool([], -999_999)
|
87
75
|
end
|
@@ -97,7 +85,7 @@ module Toys
|
|
97
85
|
#
|
98
86
|
def add_path(paths, high_priority: false)
|
99
87
|
paths = Array(paths)
|
100
|
-
synchronize do
|
88
|
+
@mutex.synchronize do
|
101
89
|
raise "Cannot add a path after tool loading has started" if @loading_started
|
102
90
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
103
91
|
paths.each do |path|
|
@@ -123,7 +111,7 @@ module Toys
|
|
123
111
|
#
|
124
112
|
def add_block(high_priority: false, name: nil, &block)
|
125
113
|
name ||= "(Code block #{block.object_id})"
|
126
|
-
synchronize do
|
114
|
+
@mutex.synchronize do
|
127
115
|
raise "Cannot add a block after tool loading has started" if @loading_started
|
128
116
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
129
117
|
source = SourceInfo.create_proc_root(block, name)
|
@@ -238,8 +226,6 @@ module Toys
|
|
238
226
|
##
|
239
227
|
# Get or create the tool definition for the given name and priority.
|
240
228
|
#
|
241
|
-
# @return [Toys::Tool]
|
242
|
-
#
|
243
229
|
# @private
|
244
230
|
#
|
245
231
|
def get_tool(words, priority)
|
@@ -253,12 +239,6 @@ module Toys
|
|
253
239
|
# the active priority, returns `nil`. If the given priority is higher than
|
254
240
|
# the active priority, returns and activates a new tool.
|
255
241
|
#
|
256
|
-
# @param words [Array<String>] The name of the tool.
|
257
|
-
# @param priority [Integer] The priority of the request.
|
258
|
-
#
|
259
|
-
# @return [Toys::Tool] The tool found.
|
260
|
-
# @return [nil] if the given priority is insufficient.
|
261
|
-
#
|
262
242
|
# @private
|
263
243
|
#
|
264
244
|
def activate_tool(words, priority)
|
@@ -269,9 +249,6 @@ module Toys
|
|
269
249
|
# Returns true if the given tool name currently exists in the loader.
|
270
250
|
# Does not load the tool if not found.
|
271
251
|
#
|
272
|
-
# @param words [Array<String>] The name of the tool.
|
273
|
-
# @return [Boolean]
|
274
|
-
#
|
275
252
|
# @private
|
276
253
|
#
|
277
254
|
def tool_defined?(words)
|
@@ -282,29 +259,21 @@ module Toys
|
|
282
259
|
# Build a new tool.
|
283
260
|
# Called only from ToolData.
|
284
261
|
#
|
285
|
-
# @param words [Array<String>] The name of the tool.
|
286
|
-
# @param priority [Integer] The priority of the request.
|
287
|
-
#
|
288
|
-
# @return [Toys::Tool] A new tool object.
|
289
|
-
#
|
290
262
|
# @private
|
291
263
|
#
|
292
264
|
def build_tool(words, priority)
|
293
265
|
parent = words.empty? ? nil : get_tool(words.slice(0..-2), priority)
|
294
|
-
|
295
|
-
Tool.new(self, parent, words, priority,
|
266
|
+
middleware_stack = parent ? parent.subtool_middleware_stack : @middleware_stack
|
267
|
+
Tool.new(self, parent, words, priority, middleware_stack, @middleware_lookup)
|
296
268
|
end
|
297
269
|
|
298
270
|
##
|
299
271
|
# Loads the subtree under the given prefix.
|
300
272
|
#
|
301
|
-
# @param prefix [Array<String>] The name prefix.
|
302
|
-
# @return [self]
|
303
|
-
#
|
304
273
|
# @private
|
305
274
|
#
|
306
275
|
def load_for_prefix(prefix)
|
307
|
-
synchronize do
|
276
|
+
@mutex.synchronize do
|
308
277
|
@loading_started = true
|
309
278
|
cur_worklist = @worklist
|
310
279
|
@worklist = []
|
@@ -323,10 +292,6 @@ module Toys
|
|
323
292
|
##
|
324
293
|
# Attempt to get a well-known mixin module for the given symbolic name.
|
325
294
|
#
|
326
|
-
# @param name [Symbol] Mixin name
|
327
|
-
# @return [Module] The mixin
|
328
|
-
# @return [nil] if not found.
|
329
|
-
#
|
330
295
|
# @private
|
331
296
|
#
|
332
297
|
def resolve_standard_mixin(name)
|
@@ -336,10 +301,6 @@ module Toys
|
|
336
301
|
##
|
337
302
|
# Attempt to get a well-known template class for the given symbolic name.
|
338
303
|
#
|
339
|
-
# @param name [Symbol] Template name
|
340
|
-
# @return [Class] The template.
|
341
|
-
# @return [nil] if not found.
|
342
|
-
#
|
343
304
|
# @private
|
344
305
|
#
|
345
306
|
def resolve_standard_template(name)
|
@@ -350,27 +311,29 @@ module Toys
|
|
350
311
|
# Load configuration from the given path. This is called from the `load`
|
351
312
|
# directive in the DSL.
|
352
313
|
#
|
353
|
-
# @param parent_source [Toys::SourceInfo] The source of the caller.
|
354
|
-
# @param path [String] The file or directory to load.
|
355
|
-
# @param words [Array<String>] The name of the caller, i.e. the context in
|
356
|
-
# which to load.
|
357
|
-
# @param remaining_words [Array<String>] The remaining words.
|
358
|
-
# @param priority [Integer] The priority.
|
359
|
-
#
|
360
314
|
# @private
|
361
315
|
#
|
362
316
|
def load_path(parent_source, path, words, remaining_words, priority)
|
363
317
|
source = parent_source.absolute_child(path)
|
364
|
-
synchronize do
|
318
|
+
@mutex.synchronize do
|
365
319
|
load_validated_path(source, words, remaining_words, priority)
|
366
320
|
end
|
367
321
|
end
|
368
322
|
|
369
323
|
##
|
370
|
-
#
|
324
|
+
# Load a subtool block. Called from the `tool` directive in the DSL.
|
325
|
+
#
|
326
|
+
# @private
|
371
327
|
#
|
372
|
-
|
373
|
-
|
328
|
+
def load_block(parent_source, block, words, remaining_words, priority)
|
329
|
+
source = parent_source.proc_child(block)
|
330
|
+
@mutex.synchronize do
|
331
|
+
load_proc(source, words, remaining_words, priority)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# Determine the next setting for remaining_words, given a word.
|
374
337
|
#
|
375
338
|
# @private
|
376
339
|
#
|
@@ -387,11 +350,11 @@ module Toys
|
|
387
350
|
private
|
388
351
|
|
389
352
|
def tool_data_snapshot
|
390
|
-
synchronize { @tool_data.dup }
|
353
|
+
@mutex.synchronize { @tool_data.dup }
|
391
354
|
end
|
392
355
|
|
393
356
|
def get_tool_data(words)
|
394
|
-
synchronize { @tool_data[words] ||= ToolData.new(words) }
|
357
|
+
@mutex.synchronize { @tool_data[words] ||= ToolData.new(words) }
|
395
358
|
end
|
396
359
|
|
397
360
|
##
|
@@ -443,15 +406,15 @@ module Toys
|
|
443
406
|
|
444
407
|
def load_index_in(source, words, remaining_words, priority)
|
445
408
|
return unless @index_file_name
|
446
|
-
index_source = source.relative_child(@index_file_name, @data_dir_name)
|
409
|
+
index_source = source.relative_child(@index_file_name, @data_dir_name, @lib_dir_name)
|
447
410
|
load_relevant_path(index_source, words, remaining_words, priority) if index_source
|
448
411
|
end
|
449
412
|
|
450
413
|
def load_child_in(source, child, words, remaining_words, priority)
|
451
414
|
return if child.start_with?(".") || child == @index_file_name ||
|
452
415
|
child == @preload_file_name || child == @preload_dir_name ||
|
453
|
-
child == @data_dir_name
|
454
|
-
child_source = source.relative_child(child, @data_dir_name)
|
416
|
+
child == @data_dir_name || child == @lib_dir_name
|
417
|
+
child_source = source.relative_child(child, @data_dir_name, @lib_dir_name)
|
455
418
|
return unless child_source
|
456
419
|
child_word = ::File.basename(child, ".rb")
|
457
420
|
next_words = words + [child_word]
|
data/lib/toys/middleware.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
module Toys
|
25
4
|
##
|
26
5
|
# A middleware is an object that has the opportunity to alter the
|
@@ -39,15 +18,15 @@ module Toys
|
|
39
18
|
# middleware, a Toys middleware can wrap execution with its own code,
|
40
19
|
# replace it outright, or leave it unmodified.
|
41
20
|
#
|
42
|
-
# Generally, a middleware is a class that implements
|
43
|
-
# in this module: {Toys::Middleware#config} and
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
21
|
+
# Generally, a middleware is a class that implements one or more of the
|
22
|
+
# methods defined in this module: {Toys::Middleware#config}, and
|
23
|
+
# {Toys::Middleware#run}. This module provides default implementations that
|
24
|
+
# do nothing, but using them is not required. Middleware objects need respond
|
25
|
+
# only to methods they care about.
|
47
26
|
#
|
48
27
|
module Middleware
|
49
28
|
##
|
50
|
-
# This method is called after a tool has been defined, and gives this
|
29
|
+
# This method is called *after* a tool has been defined, and gives this
|
51
30
|
# middleware the opportunity to modify the tool definition. It is passed
|
52
31
|
# the tool definition object and the loader, and can make any changes to
|
53
32
|
# the tool definition. In most cases, this method should also call
|
@@ -95,12 +74,6 @@ module Toys
|
|
95
74
|
##
|
96
75
|
# Create a middleware spec.
|
97
76
|
#
|
98
|
-
# @overload spec(middleware_object)
|
99
|
-
# Create a spec wrapping an existing middleware object
|
100
|
-
#
|
101
|
-
# @param middleware_object [Toys::Middleware] The middleware object
|
102
|
-
# @return [Toys::Middleware::Spec] A spec
|
103
|
-
#
|
104
77
|
# @overload spec(name, *args, **kwargs, &block)
|
105
78
|
# Create a spec indicating a given middleware name should be
|
106
79
|
# instantiated with the given arguments.
|
@@ -111,27 +84,63 @@ module Toys
|
|
111
84
|
# @param block [Proc,nil] The block to pass to the constructor
|
112
85
|
# @return [Toys::Middleware::Spec] A spec
|
113
86
|
#
|
87
|
+
# @overload spec(array)
|
88
|
+
# Create a middleware spec from an array specification.
|
89
|
+
#
|
90
|
+
# The array must be 1-4 elements long. The first element must be the
|
91
|
+
# middleware name or class. The other three arguments may include any
|
92
|
+
# or all of the following optional elements, in any order:
|
93
|
+
# * An array for the positional arguments to pass to the constructor
|
94
|
+
# * A hash for the keyword arguments to pass to the constructor
|
95
|
+
# * A proc for the block to pass to the constructor
|
96
|
+
#
|
97
|
+
# @param array [Array] The array input
|
98
|
+
# @return [Toys::Middleware::Spec] A spec
|
99
|
+
#
|
100
|
+
# @overload spec(middleware_object)
|
101
|
+
# Create a spec wrapping an existing middleware object
|
102
|
+
#
|
103
|
+
# @param middleware_object [Toys::Middleware] The middleware object
|
104
|
+
# @return [Toys::Middleware::Spec] A spec
|
105
|
+
#
|
114
106
|
def spec(middleware, *args, **kwargs, &block)
|
115
|
-
|
107
|
+
case middleware
|
108
|
+
when ::Array
|
109
|
+
spec_from_array(middleware)
|
110
|
+
when ::String, ::Symbol, ::Class
|
116
111
|
Spec.new(nil, middleware, args, kwargs, block)
|
112
|
+
when Spec
|
113
|
+
middleware
|
117
114
|
else
|
118
115
|
Spec.new(middleware, nil, nil, nil, nil)
|
119
116
|
end
|
120
117
|
end
|
121
118
|
|
122
119
|
##
|
123
|
-
# Create a
|
120
|
+
# Create a {Toys::Middleware::Stack} from an array of middleware specs.
|
121
|
+
# Each element may be one of the following:
|
124
122
|
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# * A proc for the block to pass to the constructor
|
123
|
+
# * A {Toys::Middleware} object
|
124
|
+
# * A {Toys::Middleware::Spec}
|
125
|
+
# * An array whose first element is a middleware name or class, and the
|
126
|
+
# subsequent elements are params that define what to pass to the class
|
127
|
+
# constructor (see {Toys::Middleware.spec})
|
131
128
|
#
|
132
|
-
# @param
|
133
|
-
# @return [Toys::Middleware::
|
129
|
+
# @param input [Array<Toys::Middleware,Toys::Middleware::Spec,Array>]
|
130
|
+
# @return [Toys::Middleware::Stack]
|
134
131
|
#
|
132
|
+
def stack(input)
|
133
|
+
case input
|
134
|
+
when Stack
|
135
|
+
input
|
136
|
+
when ::Array
|
137
|
+
Stack.new(default_specs: input.map { |spec| spec(spec) })
|
138
|
+
else
|
139
|
+
raise ::ArgumentError, "Illegal middleware stack: #{input.inspect}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
## @private
|
135
144
|
def spec_from_array(array)
|
136
145
|
middleware = array.first
|
137
146
|
if !middleware.is_a?(::String) && !middleware.is_a?(::Symbol) && !middleware.is_a?(::Class)
|
@@ -154,32 +163,6 @@ module Toys
|
|
154
163
|
end
|
155
164
|
Spec.new(nil, middleware, args, kwargs, block)
|
156
165
|
end
|
157
|
-
|
158
|
-
##
|
159
|
-
# Resolve all arguments into an array of middleware specs. Each argument
|
160
|
-
# may be one of the following:
|
161
|
-
#
|
162
|
-
# * A {Toys::Middleware} object
|
163
|
-
# * A {Toys::Middleware::Spec}
|
164
|
-
# * An array whose first element is a middleware name or class, and the
|
165
|
-
# subsequent elements are params that define what to pass to the class
|
166
|
-
# constructor (see {Toys::Middleware.spec_from_array})
|
167
|
-
#
|
168
|
-
# @param items [Array<Toys::Middleware,Toys::Middleware::Spec,Array>]
|
169
|
-
# @return [Array<Toys::Middleware::Spec>]
|
170
|
-
#
|
171
|
-
def resolve_specs(*items)
|
172
|
-
items.map do |item|
|
173
|
-
case item
|
174
|
-
when ::Array
|
175
|
-
spec_from_array(item)
|
176
|
-
when Spec
|
177
|
-
item
|
178
|
-
else
|
179
|
-
spec(item)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
166
|
end
|
184
167
|
|
185
168
|
##
|
@@ -216,14 +199,7 @@ module Toys
|
|
216
199
|
else
|
217
200
|
klass = @name
|
218
201
|
end
|
219
|
-
|
220
|
-
# initialize will fail if there are no formal keyword args.
|
221
|
-
formals = klass.instance_method(:initialize).parameters
|
222
|
-
if @kwargs.empty? && formals.all? { |arg| arg.first != :key && arg.first != :keyrest }
|
223
|
-
klass.new(*@args, &@block)
|
224
|
-
else
|
225
|
-
klass.new(*@args, **@kwargs, &@block)
|
226
|
-
end
|
202
|
+
Compat.instantiate(klass, @args, @kwargs, @block)
|
227
203
|
end
|
228
204
|
|
229
205
|
##
|
@@ -270,6 +246,99 @@ module Toys
|
|
270
246
|
@kwargs = kwargs
|
271
247
|
@block = block
|
272
248
|
end
|
249
|
+
|
250
|
+
## @private
|
251
|
+
def ==(other)
|
252
|
+
other.is_a?(Spec) &&
|
253
|
+
object.eql?(other.object) &&
|
254
|
+
name.eql?(other.name) &&
|
255
|
+
args.eql?(other.args) &&
|
256
|
+
kwargs.eql?(other.kwargs) &&
|
257
|
+
block.eql?(other.block)
|
258
|
+
end
|
259
|
+
alias eql? ==
|
260
|
+
|
261
|
+
## @private
|
262
|
+
def hash
|
263
|
+
[object, name, args, kwargs, block].hash
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
##
|
268
|
+
# A stack of middleware specs.
|
269
|
+
#
|
270
|
+
class Stack
|
271
|
+
##
|
272
|
+
# The middleware specs that precede the default set.
|
273
|
+
# @return [Array<Toys::Middleware:Spec>]
|
274
|
+
#
|
275
|
+
attr_reader :pre_specs
|
276
|
+
|
277
|
+
##
|
278
|
+
# The default set of middleware specs.
|
279
|
+
# @return [Array<Toys::Middleware:Spec>]
|
280
|
+
#
|
281
|
+
attr_reader :default_specs
|
282
|
+
|
283
|
+
##
|
284
|
+
# The middleware specs that follow the default set.
|
285
|
+
# @return [Array<Toys::Middleware:Spec>]
|
286
|
+
#
|
287
|
+
attr_reader :post_specs
|
288
|
+
|
289
|
+
##
|
290
|
+
# Add a middleware spec to the stack, in the default location, which is
|
291
|
+
# at the end of pre_specs). See {Toys::Middleware.spec} for a description
|
292
|
+
# of the arguments you can pass.
|
293
|
+
#
|
294
|
+
# @overload add(name, *args, **kwargs, &block)
|
295
|
+
# @overload add(array)
|
296
|
+
# @overload add(middleware_object)
|
297
|
+
#
|
298
|
+
def add(middleware, *args, **kwargs, &block)
|
299
|
+
pre_specs.push(Middleware.spec(middleware, *args, **kwargs, &block))
|
300
|
+
end
|
301
|
+
|
302
|
+
##
|
303
|
+
# Duplicate this stack.
|
304
|
+
#
|
305
|
+
# @return [Toys::Middleware::Stack]
|
306
|
+
#
|
307
|
+
def dup
|
308
|
+
Stack.new(pre_specs: pre_specs.dup,
|
309
|
+
post_specs: post_specs.dup,
|
310
|
+
default_specs: default_specs.dup)
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# Build the middleware in this stack.
|
315
|
+
#
|
316
|
+
# @return [Array<Toys::Middleware>]
|
317
|
+
#
|
318
|
+
def build(middleware_lookup)
|
319
|
+
(@pre_specs + @default_specs + @post_specs).map { |spec| spec.build(middleware_lookup) }
|
320
|
+
end
|
321
|
+
|
322
|
+
## @private
|
323
|
+
def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
|
324
|
+
@pre_specs = pre_specs || []
|
325
|
+
@post_specs = post_specs || []
|
326
|
+
@default_specs = default_specs || []
|
327
|
+
end
|
328
|
+
|
329
|
+
## @private
|
330
|
+
def ==(other)
|
331
|
+
other.is_a?(Stack) &&
|
332
|
+
pre_specs.eql?(other.pre_specs) &&
|
333
|
+
default_specs.eql?(other.default_specs) &&
|
334
|
+
post_specs.eql?(other.post_specs)
|
335
|
+
end
|
336
|
+
alias eql? ==
|
337
|
+
|
338
|
+
## @private
|
339
|
+
def hash
|
340
|
+
[@pre_specs, @default_specs, @post_specs].hash
|
341
|
+
end
|
273
342
|
end
|
274
343
|
end
|
275
344
|
end
|