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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +30 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +3 -3
  6. data/lib/toys-core.rb +11 -21
  7. data/lib/toys/acceptor.rb +0 -21
  8. data/lib/toys/arg_parser.rb +1 -22
  9. data/lib/toys/cli.rb +102 -70
  10. data/lib/toys/compat.rb +49 -41
  11. data/lib/toys/completion.rb +0 -21
  12. data/lib/toys/context.rb +0 -23
  13. data/lib/toys/core.rb +1 -22
  14. data/lib/toys/dsl/flag.rb +0 -21
  15. data/lib/toys/dsl/flag_group.rb +0 -21
  16. data/lib/toys/dsl/positional_arg.rb +0 -21
  17. data/lib/toys/dsl/tool.rb +135 -51
  18. data/lib/toys/errors.rb +0 -21
  19. data/lib/toys/flag.rb +0 -21
  20. data/lib/toys/flag_group.rb +0 -21
  21. data/lib/toys/input_file.rb +0 -21
  22. data/lib/toys/loader.rb +41 -78
  23. data/lib/toys/middleware.rb +146 -77
  24. data/lib/toys/mixin.rb +0 -21
  25. data/lib/toys/module_lookup.rb +3 -26
  26. data/lib/toys/positional_arg.rb +0 -21
  27. data/lib/toys/source_info.rb +49 -38
  28. data/lib/toys/standard_middleware/add_verbosity_flags.rb +0 -23
  29. data/lib/toys/standard_middleware/apply_config.rb +42 -0
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +7 -28
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +0 -23
  32. data/lib/toys/standard_middleware/show_help.rb +0 -23
  33. data/lib/toys/standard_middleware/show_root_version.rb +0 -23
  34. data/lib/toys/standard_mixins/bundler.rb +89 -0
  35. data/lib/toys/standard_mixins/exec.rb +124 -35
  36. data/lib/toys/standard_mixins/fileutils.rb +0 -21
  37. data/lib/toys/standard_mixins/gems.rb +2 -24
  38. data/lib/toys/standard_mixins/highline.rb +0 -21
  39. data/lib/toys/standard_mixins/terminal.rb +0 -21
  40. data/lib/toys/template.rb +0 -21
  41. data/lib/toys/tool.rb +22 -34
  42. data/lib/toys/utils/completion_engine.rb +0 -21
  43. data/lib/toys/utils/exec.rb +1 -21
  44. data/lib/toys/utils/gems.rb +174 -63
  45. data/lib/toys/utils/help_text.rb +0 -21
  46. data/lib/toys/utils/terminal.rb +46 -37
  47. data/lib/toys/wrappable_string.rb +0 -21
  48. metadata +25 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13ebab12cc43b3347b2a350bda3b376cd22d86b61f29551146698acf7aff0725
4
- data.tar.gz: 9185077682bbc7cf674efc5f83a1cb8ee76f8b87d8d72a35d884f82da5bde38d
3
+ metadata.gz: 3a9f077375706e67827afa561f71081c121d0eb00c30be6586501e6c76ab47c5
4
+ data.tar.gz: 305e31b023d22f687a2406efdd0cea377a1c9725626c8320cebbf013b10f145b
5
5
  SHA512:
6
- metadata.gz: 412c26dfba427a00e499779fe7edfcbceea0c1579e83953eb9474bf72544842b3fc33f7ffb98ca7cb9a8a935f3b0886d6a55bf593ca964610ba8b4f988fd1f9c
7
- data.tar.gz: d07b2d164ad561cd260145dd768c434ea175c139c0b187438ae4396231f7b46ed2e293fa2594ac7629490d8b6e840c3fde111c425364862652a46e1e0265a901
6
+ metadata.gz: 661b472175296ea3224e3dee08902ca8b2bf04e78f949109a91f4fa471c0b7904a454e0b18e845cf46d8cf04458872c84f55fd3f43bcbb2f3ecd40478783b8a5
7
+ data.tar.gz: f9761ab43fffd295af8629906ae3fdee621c42d5df49a92b3539cc68a9588546338a4fdd3a70459639c3127c261de443c5f2359778807d281b451b4c2655316a
data/.yardopts CHANGED
@@ -3,7 +3,8 @@
3
3
  --markup=markdown
4
4
  --markup-provider redcarpet
5
5
  --main=README.md
6
- ./lib/**/*.rb
6
+ ./lib/toys/**/*.rb
7
+ ./lib/toys-core.rb
7
8
  -
8
9
  README.md
9
10
  LICENSE.md
@@ -1,5 +1,35 @@
1
1
  # Release History
2
2
 
3
+ ### 0.10.0 / 2020-02-24
4
+
5
+ Functional changes:
6
+
7
+ * ADDED: `:bundler` mixin that installs and sets up a bundle for the tool
8
+ * ADDED: `bundle` method to `Toys::Utils::Gems` that performs bundler install and setup
9
+ * ADDED: `subtool_apply` directive which applies a block to all subtools.
10
+ * ADDED: Add `.lib` directories to the Ruby load path when executing a tool.
11
+ * ADDED: `toys_version?` and `toys_version!` directives that check against version requirements.
12
+ * ADDED: `exec_separate_tool` and `capture_separate_tool` methods in the `:exec` mixin, to support executing tools in a separate process without forking
13
+ * IMPROVED: `long_desc` directive can now read the description from a text file.
14
+ * IMPROVED: The `tool` directive can take delimited strings as tool names.
15
+ * IMPROVED: Subtool blocks aren't actually executed unless the tool is needed.
16
+ * CHANGED: Added `on_missing` and `on_conflict` arguments to `Toys::Utils::Gems` constructor (which also affects the `:gems` mixin), and deprecated `suppress_confirm` and `default_confirm`.
17
+
18
+ Internal interface changes:
19
+
20
+ * ADDED: `Toys::Tool#subtool_middleware_stack` allowing a tool to modify the middleware stack for its subtools.
21
+ * ADDED: The `Toys::Middleware::Stack` class represents a stack of middleware specs, and distinguishes the default set from those added afterward.
22
+ * ADDED: `Toys.executable_path` attribute allowing an executable to provide the executable for running tools separately.
23
+ * ADDED: `Toys::CLI` now has a `logger_factory` property, to generate separate loggers per tool execution.
24
+ * ADDED: `Toys::CLI` and `Toys::Loader` now let you set `:lib_dir_name`.
25
+ * IMPROVED: Toys-core no longer has a general dependency on rubygems. (Parts that do depend on rubygems, such as the `:gems` mixin, do an explicit `require "rubygems"`.) This makes it possible to write an executable with `ruby --disable=gems` which improves startup time.
26
+ * IMPROVED: Middleware objects no longer have to respond to all middleware methods. If a method is not implemented, it is simply considered a nop.
27
+ * IMPROVED: `Toys::Utils::Terminal` is now thread-safe.
28
+ * CHANGED: `Toys::Utils::Terminal#styled` is no longer mutable.
29
+ * CHANGED: `Toys::Tool#middleware_stack` renamed to `Toys::Tool#built_middleware` to clarify that it is an array of middleware objects rather than specs.
30
+ * CHANGED: `Toys::CLI.default_logger` removed and replaced with `Toys::CLI.default_logger_factory`. In general, global loggers for CLI are now discouraged because they are not thread-safe.
31
+ * CHANGED: `Toys::Loader` uses an internal monitor rather than including `MonitorMixin`.
32
+
3
33
  ### 0.9.4 / 2020-01-26
4
34
 
5
35
  * FIXED: Crash in the loader when a non-ruby file appears in a toys directory
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # License
2
2
 
3
- Copyright 2019 Daniel Azuma
3
+ Copyright 2019-2020 Daniel Azuma and the Toys contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -74,7 +74,7 @@ use to write Toys files. You could point your executable at a directory
74
74
  containing actual Toys files, but the simplest option is to provide the
75
75
  information to the Toys CLI object in a block.
76
76
 
77
- Let's add some functionality.
77
+ Let's add some functionality.
78
78
 
79
79
  #!/usr/bin/env ruby
80
80
 
@@ -150,7 +150,7 @@ available tools.
150
150
  $ ./mycmd
151
151
 
152
152
  Notice that the description set at the "root" of the config block (outside the
153
- tool blocks) shows up here.
153
+ tool blocks) shows up here.
154
154
 
155
155
  ### Configuring the CLI
156
156
 
@@ -338,7 +338,7 @@ templates, and middleware, in the
338
338
 
339
339
  ## License
340
340
 
341
- Copyright 2019 Daniel Azuma
341
+ Copyright 2019-2020 Daniel Azuma and the Toys contributors
342
342
 
343
343
  Permission is hereby granted, free of charge, to any person obtaining a copy
344
344
  of this software and associated documentation files (the "Software"), to deal
@@ -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
  # Toys is a configurable command line tool. Write commands in config files
26
5
  # using a simple DSL, and Toys will provide the command line executable and
@@ -68,6 +47,17 @@ module Toys
68
47
  # `require "toys/utils/exec"`.
69
48
  #
70
49
  module Utils; end
50
+
51
+ class << self
52
+ ##
53
+ # Path to the executable. This can, for example, be invoked to run a subtool
54
+ # in a clean environment.
55
+ #
56
+ # @return [String] if there is an executable
57
+ # @return [nil] if there is no such executable
58
+ #
59
+ attr_accessor :executable_path
60
+ end
71
61
  end
72
62
 
73
63
  require "toys/acceptor"
@@ -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 Acceptor validates and converts arguments. It is designed to be
@@ -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 internal class that parses command line arguments for a tool.
@@ -444,7 +423,7 @@ module Toys
444
423
  Context::Key::ARGS => nil,
445
424
  Context::Key::CLI => cli,
446
425
  Context::Key::CONTEXT_DIRECTORY => tool.context_directory,
447
- Context::Key::LOGGER => cli.logger,
426
+ Context::Key::LOGGER => cli.logger_factory.call(tool),
448
427
  Context::Key::TOOL => tool,
449
428
  Context::Key::TOOL_SOURCE => tool.source_info,
450
429
  Context::Key::TOOL_NAME => tool.full_name,
@@ -1,26 +1,6 @@
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
-
3
+ require "rbconfig"
24
4
  require "logger"
25
5
  require "toys/completion"
26
6
 
@@ -71,7 +51,8 @@ module Toys
71
51
  # roughly into four categories:
72
52
  #
73
53
  # * Options affecting output behavior:
74
- # * `logger`: The logger
54
+ # * `logger`: A global logger for all tools to use
55
+ # * `logger_factory`: A proc that returns a logger to use
75
56
  # * `base_level`: The default log level
76
57
  # * `error_handler`: Callback for handling exceptions
77
58
  # * `executable_name`: The name of the executable
@@ -91,10 +72,17 @@ module Toys
91
72
  # * `preload_dir_name`: Name of preload directories in tool directories
92
73
  # * `data_dir_name`: Name of data directories in tool directories
93
74
  #
94
- # @param logger [Logger] The logger to use.
95
- # Optional. If not provided, will use a default logger that writes
96
- # formatted output to `STDERR`, as defined by
97
- # {Toys::CLI.default_logger}.
75
+ # @param logger [Logger] A global logger to use for all tools. This may be
76
+ # set if the CLI will call at most one tool at a time. However, it will
77
+ # behave incorrectly if CLI might run multiple tools at the same time
78
+ # with different verbosity settings (since the logger cannot have
79
+ # multiple level settings simultaneously). In that case, do not set a
80
+ # global logger, but use the `logger_factory` parameter instead.
81
+ # @param logger_factory [Proc] A proc that takes a {Toys::Tool} as an
82
+ # argument, and returns a `Logger` to use when running that tool.
83
+ # Optional. If not provided (and no global logger is set), CLI will use
84
+ # a default factory that writes generates loggers writing formatted
85
+ # output to `STDERR`, as defined by {Toys::CLI.default_logger_factory}.
98
86
  # @param base_level [Integer] The logger level that should correspond
99
87
  # to zero verbosity.
100
88
  # Optional. If not provided, defaults to the current level of the
@@ -181,13 +169,31 @@ module Toys
181
169
  # path for any tool file in that directory.
182
170
  # Optional. If not provided, data directories are disabled.
183
171
  # Note: the standard toys executable sets this to `".data"`.
184
- #
185
- def initialize(
186
- executable_name: nil, middleware_stack: nil, extra_delimiters: "",
187
- config_dir_name: nil, config_file_name: nil, index_file_name: nil,
188
- preload_file_name: nil, preload_dir_name: nil, data_dir_name: nil,
189
- mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil,
190
- logger: nil, base_level: nil, error_handler: nil, completion: nil
172
+ # @param lib_dir_name [String] A directory with this name that appears in
173
+ # any configuration directory is added to the Ruby load path when
174
+ # executing any tool file in that directory.
175
+ # Optional. If not provided, lib directories are disabled.
176
+ # Note: the standard toys executable sets this to `".lib"`.
177
+ #
178
+ def initialize( # rubocop:disable Metrics/MethodLength
179
+ executable_name: nil,
180
+ middleware_stack: nil,
181
+ extra_delimiters: "",
182
+ config_dir_name: nil,
183
+ config_file_name: nil,
184
+ index_file_name: nil,
185
+ preload_file_name: nil,
186
+ preload_dir_name: nil,
187
+ data_dir_name: nil,
188
+ lib_dir_name: nil,
189
+ mixin_lookup: nil,
190
+ middleware_lookup: nil,
191
+ template_lookup: nil,
192
+ logger_factory: nil,
193
+ logger: nil,
194
+ base_level: nil,
195
+ error_handler: nil,
196
+ completion: nil
191
197
  )
192
198
  @executable_name = executable_name || ::File.basename($PROGRAM_NAME)
193
199
  @middleware_stack = middleware_stack || CLI.default_middleware_stack
@@ -196,8 +202,9 @@ module Toys
196
202
  @template_lookup = template_lookup || CLI.default_template_lookup
197
203
  @error_handler = error_handler || DefaultErrorHandler.new
198
204
  @completion = completion || DefaultCompletion.new
199
- @logger = logger || CLI.default_logger
200
- @base_level = base_level || @logger.level
205
+ @logger = logger
206
+ @logger_factory = logger ? proc { logger } : logger_factory || CLI.default_logger_factory
207
+ @base_level = base_level
201
208
  @extra_delimiters = extra_delimiters
202
209
  @config_dir_name = config_dir_name
203
210
  @config_file_name = config_file_name
@@ -205,12 +212,18 @@ module Toys
205
212
  @preload_file_name = preload_file_name
206
213
  @preload_dir_name = preload_dir_name
207
214
  @data_dir_name = data_dir_name
215
+ @lib_dir_name = lib_dir_name
208
216
  @loader = Loader.new(
209
- index_file_name: @index_file_name, extra_delimiters: @extra_delimiters,
210
- preload_dir_name: @preload_dir_name, preload_file_name: @preload_file_name,
217
+ index_file_name: @index_file_name,
218
+ preload_dir_name: @preload_dir_name,
219
+ preload_file_name: @preload_file_name,
211
220
  data_dir_name: @data_dir_name,
212
- mixin_lookup: @mixin_lookup, template_lookup: @template_lookup,
213
- middleware_lookup: @middleware_lookup, middleware_stack: @middleware_stack
221
+ lib_dir_name: @lib_dir_name,
222
+ middleware_stack: @middleware_stack,
223
+ extra_delimiters: @extra_delimiters,
224
+ mixin_lookup: @mixin_lookup,
225
+ template_lookup: @template_lookup,
226
+ middleware_lookup: @middleware_lookup
214
227
  )
215
228
  end
216
229
 
@@ -235,12 +248,14 @@ module Toys
235
248
  preload_dir_name: @preload_dir_name,
236
249
  preload_file_name: @preload_file_name,
237
250
  data_dir_name: @data_dir_name,
251
+ lib_dir_name: @lib_dir_name,
238
252
  middleware_stack: @middleware_stack,
239
253
  extra_delimiters: @extra_delimiters,
240
254
  mixin_lookup: @mixin_lookup,
241
255
  middleware_lookup: @middleware_lookup,
242
256
  template_lookup: @template_lookup,
243
257
  logger: @logger,
258
+ logger_factory: @logger_factory,
244
259
  base_level: @base_level,
245
260
  error_handler: @error_handler,
246
261
  completion: @completion,
@@ -269,14 +284,21 @@ module Toys
269
284
  attr_reader :extra_delimiters
270
285
 
271
286
  ##
272
- # The logger used by this CLI.
273
- # @return [Logger]
287
+ # The global logger, if any.
288
+ # @return [Logger,nil]
274
289
  #
275
290
  attr_reader :logger
276
291
 
292
+ ##
293
+ # The logger factory.
294
+ # @return [Proc]
295
+ #
296
+ attr_reader :logger_factory
297
+
277
298
  ##
278
299
  # The initial logger level in this CLI, used as the level for verbosity 0.
279
- # @return [Integer]
300
+ # May be `nil`, indicating it will use the initial logger setting.
301
+ # @return [Integer,nil]
280
302
  #
281
303
  attr_reader :base_level
282
304
 
@@ -432,19 +454,22 @@ module Toys
432
454
  require_exact_flag_match: tool.exact_flag_match_required?)
433
455
  arg_parser.parse(args).finish
434
456
  context = tool.tool_class.new(arg_parser.data)
457
+ tool.source_info&.apply_lib_paths
435
458
  tool.run_initializers(context)
436
459
 
437
- cur_logger = logger
438
- original_level = cur_logger.level
439
- cur_logger.level = base_level - context[Context::Key::VERBOSITY]
460
+ cur_logger = context[Context::Key::LOGGER]
461
+ if cur_logger
462
+ original_level = cur_logger.level
463
+ cur_logger.level = (base_level || original_level) - context[Context::Key::VERBOSITY].to_i
464
+ end
440
465
  begin
441
- perform_execution(context, tool)
466
+ execute_tool_in_context(context, tool)
442
467
  ensure
443
- cur_logger.level = original_level
468
+ cur_logger.level = original_level if cur_logger
444
469
  end
445
470
  end
446
471
 
447
- def perform_execution(context, tool)
472
+ def execute_tool_in_context(context, tool)
448
473
  executor = proc do
449
474
  begin
450
475
  if !context[Context::Key::USAGE_ERRORS].empty?
@@ -459,7 +484,7 @@ module Toys
459
484
  handle_interrupt(context, tool.interrupt_handler, e)
460
485
  end
461
486
  end
462
- tool.middleware_stack.reverse_each do |middleware|
487
+ tool.built_middleware.reverse_each do |middleware|
463
488
  executor = make_executor(middleware, context, executor)
464
489
  end
465
490
  catch(:result) do
@@ -493,7 +518,11 @@ module Toys
493
518
  end
494
519
 
495
520
  def make_executor(middleware, context, next_executor)
496
- proc { middleware.run(context, &next_executor) }
521
+ if middleware.respond_to?(:run)
522
+ proc { middleware.run(context, &next_executor) }
523
+ else
524
+ next_executor
525
+ end
497
526
  end
498
527
 
499
528
  ##
@@ -646,30 +675,33 @@ module Toys
646
675
  end
647
676
 
648
677
  ##
649
- # Returns a default logger that writes formatted logs to a given stream.
678
+ # Returns a logger factory that generates loggers that write to stderr.
679
+ # All loggers generated by this factory share a single
680
+ # {Toys::Utils::Terminal}, so log entries may interleave but will not
681
+ # interrupt one another.
650
682
  #
651
- # @param output [IO] The stream to output to (defaults to `$stderr`)
652
- # @return [Logger]
683
+ # @return [Proc]
653
684
  #
654
- def default_logger(output: nil)
685
+ def default_logger_factory
655
686
  require "toys/utils/terminal"
656
- output ||= $stderr
657
- logger = ::Logger.new(output)
658
- terminal = Utils::Terminal.new(output: output)
659
- logger.formatter = proc do |severity, time, _progname, msg|
660
- msg_str =
661
- case msg
662
- when ::String
663
- msg
664
- when ::Exception
665
- "#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
666
- else
667
- msg.inspect
668
- end
669
- format_log(terminal, time, severity, msg_str)
687
+ shared_terminal = Utils::Terminal.new(output: $stderr)
688
+ proc do
689
+ logger = ::Logger.new(shared_terminal)
690
+ logger.formatter = proc do |severity, time, _progname, msg|
691
+ msg_str =
692
+ case msg
693
+ when ::String
694
+ msg
695
+ when ::Exception
696
+ "#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
697
+ else
698
+ msg.inspect
699
+ end
700
+ format_log(shared_terminal, time, severity, msg_str)
701
+ end
702
+ logger.level = ::Logger::WARN
703
+ logger
670
704
  end
671
- logger.level = ::Logger::WARN
672
- logger
673
705
  end
674
706
 
675
707
  private