toys-core 0.14.7 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/toys/cli.rb CHANGED
@@ -72,7 +72,7 @@ module Toys
72
72
  # * `preload_dir_name`: Name of preload directories in tool directories
73
73
  # * `data_dir_name`: Name of data directories in tool directories
74
74
  #
75
- # @param logger [Logger] A global logger to use for all tools. This may be
75
+ # @param logger [Logger] A global logger to use for all tools. This can be
76
76
  # set if the CLI will call at most one tool at a time. However, it will
77
77
  # behave incorrectly if CLI might run multiple tools at the same time
78
78
  # with different verbosity settings (since the logger cannot have
@@ -80,19 +80,21 @@ module Toys
80
80
  # global logger, but use the `logger_factory` parameter instead.
81
81
  # @param logger_factory [Proc] A proc that takes a {Toys::ToolDefinition}
82
82
  # as an 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}.
83
+ # Optional. If not provided (and no global logger is set),
84
+ # {Toys::CLI.default_logger_factory} is called to get a basic default.
86
85
  # @param base_level [Integer] The logger level that should correspond
87
86
  # to zero verbosity.
88
87
  # Optional. If not provided, defaults to the current level of the
89
88
  # logger (which is often `Logger::WARN`).
90
- # @param error_handler [Proc,nil] A proc that is called when an error is
91
- # caught. The proc should take a {Toys::ContextualError} argument and
92
- # report the error. It should return an exit code (normally nonzero).
93
- # Optional. If not provided, defaults to an instance of
94
- # {Toys::CLI::DefaultErrorHandler}, which displays an error message to
95
- # `STDERR`.
89
+ # @param error_handler [Proc,nil] A proc that is called when an unhandled
90
+ # exception (a normal exception subclassing `StandardError`, an error
91
+ # loading a toys config file subclassing `SyntaxError`, or an unhandled
92
+ # signal subclassing `SignalException`) is detected. The proc should
93
+ # take a {Toys::ContextualError}, whose cause is the unhandled
94
+ # exception, as the sole argument, and report the error. It should
95
+ # return an exit code (normally nonzero) appropriate to the error.
96
+ # Optional. If not provided, {Toys::CLI.default_error_handler} is
97
+ # called to get a basic default handler.
96
98
  # @param executable_name [String] The executable name displayed in help
97
99
  # text. Optional. Defaults to the ruby program name.
98
100
  #
@@ -101,9 +103,8 @@ module Toys
101
103
  # characters are period, colon, and slash.
102
104
  # @param completion [Toys::Completion::Base] A specifier for shell tab
103
105
  # completion for the CLI as a whole.
104
- # Optional. If not provided, defaults to an instance of
105
- # {Toys::CLI::DefaultCompletion}, which delegates completion to the
106
- # relevant tool.
106
+ # Optional. If not provided, {Toys::CLI.default_completion} is called
107
+ # to get a default completion that delegates to the tool.
107
108
  #
108
109
  # @param middleware_stack [Array<Toys::Middleware::Spec>] An array of
109
110
  # middleware that will be used by default for all tools.
@@ -198,8 +199,8 @@ module Toys
198
199
  @mixin_lookup = mixin_lookup || CLI.default_mixin_lookup
199
200
  @middleware_lookup = middleware_lookup || CLI.default_middleware_lookup
200
201
  @template_lookup = template_lookup || CLI.default_template_lookup
201
- @error_handler = error_handler || DefaultErrorHandler.new
202
- @completion = completion || DefaultCompletion.new
202
+ @error_handler = error_handler || CLI.default_error_handler
203
+ @completion = completion || CLI.default_completion
203
204
  @logger = logger
204
205
  @logger_factory = logger ? proc { logger } : logger_factory || CLI.default_logger_factory
205
206
  @base_level = base_level
@@ -461,9 +462,10 @@ module Toys
461
462
  context = build_context(tool, remaining,
462
463
  verbosity: verbosity,
463
464
  delegated_from: delegated_from)
464
- execute_tool(tool, context, &:run)
465
+ run_handler = make_run_handler(tool)
466
+ execute_tool(tool, context, &run_handler)
465
467
  end
466
- rescue ContextualError, ::Interrupt => e
468
+ rescue ContextualError => e
467
469
  @error_handler.call(e).to_i
468
470
  end
469
471
 
@@ -487,102 +489,6 @@ module Toys
487
489
  end
488
490
  end
489
491
 
490
- ##
491
- # A basic error handler that prints out captured errors to a stream or
492
- # a logger.
493
- #
494
- class DefaultErrorHandler
495
- ##
496
- # Create an error handler.
497
- #
498
- # @param output [IO,nil] Where to write errors. Default is `$stderr`.
499
- #
500
- def initialize(output: $stderr)
501
- require "toys/utils/terminal"
502
- @terminal = Utils::Terminal.new(output: output)
503
- end
504
-
505
- ##
506
- # The error handler routine. Prints out the error message and backtrace,
507
- # and returns the correct result code.
508
- #
509
- # @param error [Exception] The error that occurred.
510
- # @return [Integer] The result code for the execution.
511
- #
512
- def call(error)
513
- cause = error
514
- case error
515
- when ContextualError
516
- cause = error.cause
517
- @terminal.puts(cause_string(cause))
518
- @terminal.puts(context_string(error), :bold)
519
- when ::Interrupt
520
- @terminal.puts
521
- @terminal.puts("INTERRUPTED", :bold)
522
- else
523
- @terminal.puts(cause_string(error))
524
- end
525
- exit_code_for(cause)
526
- end
527
-
528
- private
529
-
530
- def exit_code_for(error)
531
- case error
532
- when ArgParsingError
533
- 2
534
- when NotRunnableError
535
- 126
536
- when ::Interrupt
537
- 130
538
- else
539
- 1
540
- end
541
- end
542
-
543
- def cause_string(cause)
544
- lines = ["#{cause.class}: #{cause.message}"]
545
- cause.backtrace.each_with_index.reverse_each do |bt, i|
546
- lines << " #{(i + 1).to_s.rjust(3)}: #{bt}"
547
- end
548
- lines.join("\n")
549
- end
550
-
551
- def context_string(error)
552
- lines = [
553
- error.banner || "Unexpected error!",
554
- " #{error.cause.class}: #{error.cause.message}",
555
- ]
556
- if error.config_path
557
- lines << " in config file: #{error.config_path}:#{error.config_line}"
558
- end
559
- if error.tool_name
560
- lines << " while executing tool: #{error.tool_name.join(' ').inspect}"
561
- if error.tool_args
562
- lines << " with arguments: #{error.tool_args.inspect}"
563
- end
564
- end
565
- lines.join("\n")
566
- end
567
- end
568
-
569
- ##
570
- # A Completion that implements the default algorithm for a CLI. This
571
- # algorithm simply determines the tool and uses its completion.
572
- #
573
- class DefaultCompletion < Completion::Base
574
- ##
575
- # Returns candidates for the current completion.
576
- #
577
- # @param context [Toys::Completion::Context] the current completion
578
- # context including the string fragment.
579
- # @return [Array<Toys::Completion::Candidate>] an array of candidates
580
- #
581
- def call(context)
582
- context.tool.completion.call(context)
583
- end
584
- end
585
-
586
492
  class << self
587
493
  ##
588
494
  # Returns a default set of middleware that may be used as a starting
@@ -637,56 +543,43 @@ module Toys
637
543
  end
638
544
 
639
545
  ##
640
- # Returns a logger factory that generates loggers that write to stderr.
641
- # All loggers generated by this factory share a single
642
- # {Toys::Utils::Terminal}, so log entries may interleave but will not
643
- # interrupt one another.
546
+ # Returns a bare-bones error handler that takes simply reraises the
547
+ # error. If the original error (the cause of the {Toys::ContextualError})
548
+ # was a `SignalException` (or a subclass such as `Interrupted`), that
549
+ # `SignalException` itself is reraised so that the Ruby VM has a chance
550
+ # to handle it. Otherwise, for any other error, the
551
+ # {Toys::ContextualError} is reraised.
552
+ #
553
+ # @return [Proc]
554
+ #
555
+ def default_error_handler
556
+ proc do |error|
557
+ cause = error.cause
558
+ raise cause.is_a?(::SignalException) ? cause : error
559
+ end
560
+ end
561
+
562
+ ##
563
+ # Returns a default logger factory that generates simple loggers that
564
+ # write to STDERR.
644
565
  #
645
566
  # @return [Proc]
646
567
  #
647
568
  def default_logger_factory
648
- require "toys/utils/terminal"
649
- shared_terminal = Utils::Terminal.new(output: $stderr)
650
569
  proc do
651
- logger = ::Logger.new(shared_terminal)
652
- logger.formatter = proc do |severity, time, _progname, msg|
653
- msg_str =
654
- case msg
655
- when ::String
656
- msg
657
- when ::Exception
658
- "#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
659
- else
660
- msg.inspect
661
- end
662
- format_log(shared_terminal, time, severity, msg_str)
663
- end
570
+ logger = ::Logger.new($stderr)
664
571
  logger.level = ::Logger::WARN
665
572
  logger
666
573
  end
667
574
  end
668
575
 
669
- private
670
-
671
- def format_log(terminal, time, severity, msg)
672
- timestr = time.strftime("%Y-%m-%d %H:%M:%S")
673
- header = format("[%<time>s %<sev>5s]", time: timestr, sev: severity)
674
- styled_header =
675
- case severity
676
- when "FATAL"
677
- terminal.apply_styles(header, :bright_magenta, :bold, :underline)
678
- when "ERROR"
679
- terminal.apply_styles(header, :bright_red, :bold)
680
- when "WARN"
681
- terminal.apply_styles(header, :bright_yellow)
682
- when "INFO"
683
- terminal.apply_styles(header, :bright_cyan)
684
- when "DEBUG"
685
- terminal.apply_styles(header, :white)
686
- else
687
- header
688
- end
689
- "#{styled_header} #{msg}\n"
576
+ ##
577
+ # Returns a default Completion that simply uses the tool's completion.
578
+ #
579
+ def default_completion
580
+ proc do |context|
581
+ context.tool.completion.call(context)
582
+ end
690
583
  end
691
584
  end
692
585
 
@@ -704,7 +597,20 @@ module Toys
704
597
  tool.tool_class.new(arg_parser.data)
705
598
  end
706
599
 
707
- def execute_tool(tool, context)
600
+ def make_run_handler(tool)
601
+ run_handler = tool.run_handler
602
+ if run_handler.is_a?(::Symbol)
603
+ proc do |context|
604
+ context.send(run_handler)
605
+ end
606
+ else
607
+ proc do |context|
608
+ context.instance_exec(&run_handler)
609
+ end
610
+ end
611
+ end
612
+
613
+ def execute_tool(tool, context, &block)
708
614
  tool.source_info&.apply_lib_paths
709
615
  tool.run_initializers(context)
710
616
  cur_logger = context[Context::Key::LOGGER]
@@ -713,9 +619,7 @@ module Toys
713
619
  cur_logger.level = (base_level || original_level) - context[Context::Key::VERBOSITY].to_i
714
620
  end
715
621
  begin
716
- executor = build_executor(tool, context) do
717
- yield context
718
- end
622
+ executor = build_executor(tool, context, &block)
719
623
  catch(:result) do
720
624
  executor.call
721
625
  0
@@ -733,11 +637,10 @@ module Toys
733
637
  elsif !tool.runnable?
734
638
  raise NotRunnableError, "No implementation for tool #{tool.display_name.inspect}"
735
639
  else
736
- yield
640
+ yield context
737
641
  end
738
- rescue ::Interrupt => e
739
- raise e unless tool.handles_interrupts?
740
- handle_interrupt(context, tool.interrupt_handler, e)
642
+ rescue ::SignalException => e
643
+ handle_signal_by_tool(context, tool, e)
741
644
  end
742
645
  end
743
646
  tool.built_middleware.reverse_each do |middleware|
@@ -750,24 +653,25 @@ module Toys
750
653
  usage_errors = context[Context::Key::USAGE_ERRORS]
751
654
  handler = tool.usage_error_handler
752
655
  raise ArgParsingError, usage_errors if handler.nil?
753
- handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
754
- if handler.arity.zero?
755
- context.instance_exec(&handler)
756
- else
757
- context.instance_exec(usage_errors, &handler)
758
- end
656
+ call_handler(context, handler, usage_errors)
759
657
  end
760
658
 
761
- def handle_interrupt(context, handler, exception)
659
+ def handle_signal_by_tool(context, tool, exception)
660
+ handler = tool.signal_handler(exception.signo)
661
+ raise exception unless handler
662
+ call_handler(context, handler, exception)
663
+ rescue ::SignalException => e
664
+ raise e if e.equal?(exception)
665
+ handle_signal_by_tool(context, tool, e)
666
+ end
667
+
668
+ def call_handler(context, handler, argument)
762
669
  handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
763
670
  if handler.arity.zero?
764
671
  context.instance_exec(&handler)
765
672
  else
766
- context.instance_exec(exception, &handler)
673
+ context.instance_exec(argument, &handler)
767
674
  end
768
- rescue ::Interrupt => e
769
- raise e if e.equal?(exception)
770
- handle_interrupt(context, handler, e)
771
675
  end
772
676
 
773
677
  def make_executor(middleware, context, next_executor)
data/lib/toys/compat.rb CHANGED
@@ -12,37 +12,27 @@ module Toys
12
12
  parts = ::RUBY_VERSION.split(".")
13
13
  ruby_version = parts[0].to_i * 10000 + parts[1].to_i * 100 + parts[2].to_i
14
14
 
15
- ##
16
15
  # @private
17
- #
18
16
  def self.jruby?
19
17
  ::RUBY_ENGINE == "jruby"
20
18
  end
21
19
 
22
- ##
23
20
  # @private
24
- #
25
21
  def self.truffleruby?
26
22
  ::RUBY_ENGINE == "truffleruby"
27
23
  end
28
24
 
29
- ##
30
25
  # @private
31
- #
32
26
  def self.windows?
33
27
  ::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
34
28
  end
35
29
 
36
- ##
37
30
  # @private
38
- #
39
31
  def self.allow_fork?
40
32
  !jruby? && !truffleruby? && !windows?
41
33
  end
42
34
 
43
- ##
44
35
  # @private
45
- #
46
36
  def self.supports_suggestions?
47
37
  unless defined?(@supports_suggestions)
48
38
  begin
@@ -60,9 +50,7 @@ module Toys
60
50
  @supports_suggestions
61
51
  end
62
52
 
63
- ##
64
53
  # @private
65
- #
66
54
  def self.suggestions(word, list)
67
55
  if supports_suggestions?
68
56
  ::DidYouMean::SpellChecker.new(dictionary: list).correct(word)
@@ -73,16 +61,12 @@ module Toys
73
61
 
74
62
  # The :base argument to Dir.glob requires Ruby 2.5 or later.
75
63
  if ruby_version >= 20500
76
- ##
77
64
  # @private
78
- #
79
65
  def self.glob_in_dir(glob, dir)
80
66
  ::Dir.glob(glob, base: dir)
81
67
  end
82
68
  else
83
- ##
84
69
  # @private
85
- #
86
70
  def self.glob_in_dir(glob, dir)
87
71
  ::Dir.chdir(dir) { ::Dir.glob(glob) }
88
72
  end
@@ -90,16 +74,12 @@ module Toys
90
74
 
91
75
  # Dir.children requires Ruby 2.5 or later.
92
76
  if ruby_version >= 20500
93
- ##
94
77
  # @private
95
- #
96
78
  def self.dir_children(dir)
97
79
  ::Dir.children(dir)
98
80
  end
99
81
  else
100
- ##
101
82
  # @private
102
- #
103
83
  def self.dir_children(dir)
104
84
  ::Dir.entries(dir) - [".", ".."]
105
85
  end
@@ -110,16 +90,12 @@ module Toys
110
90
  # This also hits TruffleRuby
111
91
  # (see https://github.com/oracle/truffleruby/issues/2567)
112
92
  if ruby_version >= 20700 && !truffleruby?
113
- ##
114
93
  # @private
115
- #
116
94
  def self.instantiate(klass, args, kwargs, block)
117
95
  klass.new(*args, **kwargs, &block)
118
96
  end
119
97
  else
120
- ##
121
98
  # @private
122
- #
123
99
  def self.instantiate(klass, args, kwargs, block)
124
100
  formals = klass.instance_method(:initialize).parameters
125
101
  if kwargs.empty? && formals.all? { |arg| arg.first != :key && arg.first != :keyrest }
@@ -133,26 +109,35 @@ module Toys
133
109
  # File.absolute_path? requires Ruby 2.7 or later. For earlier Rubies, use
134
110
  # an ad-hoc mechanism.
135
111
  if ruby_version >= 20700
136
- ##
137
112
  # @private
138
- #
139
113
  def self.absolute_path?(path)
140
114
  ::File.absolute_path?(path)
141
115
  end
142
116
  elsif ::Dir.getwd =~ /^[a-zA-Z]:/
143
- ##
144
117
  # @private
145
- #
146
118
  def self.absolute_path?(path)
147
119
  /^[a-zA-Z]:/.match?(path)
148
120
  end
149
121
  else
150
- ##
151
122
  # @private
152
- #
153
123
  def self.absolute_path?(path)
154
124
  path.start_with?("/")
155
125
  end
156
126
  end
127
+
128
+ # The second argument to method_defined? and private_method_defined?
129
+ # requires Ruby 2.6 or later.
130
+ if ruby_version >= 20600
131
+ # @private
132
+ def self.method_defined_without_ancestors?(klass, name)
133
+ klass.method_defined?(name, false) || klass.private_method_defined?(name, false)
134
+ end
135
+ else
136
+ # @private
137
+ def self.method_defined_without_ancestors?(klass, name)
138
+ klass.instance_methods(false).include?(name) ||
139
+ klass.private_instance_methods(false).include?(name)
140
+ end
141
+ end
157
142
  end
158
143
  end
data/lib/toys/context.rb CHANGED
@@ -139,22 +139,30 @@ module Toys
139
139
  #
140
140
  # This is a convenience getter for {Toys::Context::Key::ARGS}.
141
141
  #
142
+ # If the `args` method is overridden by the tool, you can still access it
143
+ # using the name `__args`.
144
+ #
142
145
  # @return [Array<String>]
143
146
  #
144
147
  def args
145
148
  @__data[Key::ARGS]
146
149
  end
150
+ alias __args args
147
151
 
148
152
  ##
149
153
  # The currently running CLI.
150
154
  #
151
155
  # This is a convenience getter for {Toys::Context::Key::CLI}.
152
156
  #
157
+ # If the `cli` method is overridden by the tool, you can still access it
158
+ # using the name `__cli`.
159
+ #
153
160
  # @return [Toys::CLI]
154
161
  #
155
162
  def cli
156
163
  @__data[Key::CLI]
157
164
  end
165
+ alias __cli cli
158
166
 
159
167
  ##
160
168
  # Return the context directory for this tool. Generally, this defaults
@@ -164,71 +172,98 @@ module Toys
164
172
  #
165
173
  # This is a convenience getter for {Toys::Context::Key::CONTEXT_DIRECTORY}.
166
174
  #
175
+ # If the `context_directory` method is overridden by the tool, you can
176
+ # still access it using the name `__context_directory`.
177
+ #
167
178
  # @return [String] Context directory path
168
179
  # @return [nil] if there is no context.
169
180
  #
170
181
  def context_directory
171
182
  @__data[Key::CONTEXT_DIRECTORY]
172
183
  end
184
+ alias __context_directory context_directory
173
185
 
174
186
  ##
175
187
  # The logger for this execution.
176
188
  #
177
189
  # This is a convenience getter for {Toys::Context::Key::LOGGER}.
178
190
  #
191
+ # If the `logger` method is overridden by the tool, you can still access it
192
+ # using the name `__logger`.
193
+ #
179
194
  # @return [Logger]
180
195
  #
181
196
  def logger
182
197
  @__data[Key::LOGGER]
183
198
  end
199
+ alias __logger logger
184
200
 
185
201
  ##
186
202
  # The full name of the tool being executed, as an array of strings.
187
203
  #
188
204
  # This is a convenience getter for {Toys::Context::Key::TOOL_NAME}.
189
205
  #
206
+ # If the `tool_name` method is overridden by the tool, you can still access
207
+ # it using the name `__tool_name`.
208
+ #
190
209
  # @return [Array<String>]
191
210
  #
192
211
  def tool_name
193
212
  @__data[Key::TOOL_NAME]
194
213
  end
214
+ alias __tool_name tool_name
195
215
 
196
216
  ##
197
217
  # The source of the tool being executed.
198
218
  #
199
219
  # This is a convenience getter for {Toys::Context::Key::TOOL_SOURCE}.
200
220
  #
221
+ # If the `tool_source` method is overridden by the tool, you can still
222
+ # access it using the name `__tool_source`.
223
+ #
201
224
  # @return [Toys::SourceInfo]
202
225
  #
203
226
  def tool_source
204
227
  @__data[Key::TOOL_SOURCE]
205
228
  end
229
+ alias __tool_source tool_source
206
230
 
207
231
  ##
208
232
  # The (possibly empty) array of errors detected during argument parsing.
209
233
  #
210
234
  # This is a convenience getter for {Toys::Context::Key::USAGE_ERRORS}.
211
235
  #
236
+ # If the `usage_errors` method is overridden by the tool, you can still
237
+ # access it using the name `__usage_errors`.
238
+ #
212
239
  # @return [Array<Toys::ArgParser::UsageError>]
213
240
  #
214
241
  def usage_errors
215
242
  @__data[Key::USAGE_ERRORS]
216
243
  end
244
+ alias __usage_errors usage_errors
217
245
 
218
246
  ##
219
247
  # The current verbosity setting as an integer.
220
248
  #
221
249
  # This is a convenience getter for {Toys::Context::Key::VERBOSITY}.
222
250
  #
251
+ # If the `verbosity` method is overridden by the tool, you can still access
252
+ # it using the name `__verbosity`.
253
+ #
223
254
  # @return [Integer]
224
255
  #
225
256
  def verbosity
226
257
  @__data[Key::VERBOSITY]
227
258
  end
259
+ alias __verbosity verbosity
228
260
 
229
261
  ##
230
262
  # Fetch an option or other piece of data by key.
231
263
  #
264
+ # If the `get` method is overridden by the tool, you can still access it
265
+ # using the name `__get` or the `[]` operator.
266
+ #
232
267
  # @param key [Symbol]
233
268
  # @return [Object]
234
269
  #
@@ -251,6 +286,9 @@ module Toys
251
286
  ##
252
287
  # Set one or more options or other context data by key.
253
288
  #
289
+ # If the `set` method is overridden by the tool, you can still access it
290
+ # using the name `__set`.
291
+ #
254
292
  # @return [self]
255
293
  #
256
294
  # @overload set(key, value)
@@ -272,6 +310,7 @@ module Toys
272
310
  end
273
311
  self
274
312
  end
313
+ alias __set set
275
314
 
276
315
  ##
277
316
  # The subset of the context that uses string or symbol keys. By convention,
@@ -279,6 +318,9 @@ module Toys
279
318
  # include well-known context values such as verbosity or private context
280
319
  # values used by middleware or mixins.
281
320
  #
321
+ # If the `options` method is overridden by the tool, you can still access
322
+ # it using the name `__options`.
323
+ #
282
324
  # @return [Hash]
283
325
  #
284
326
  def options
@@ -286,10 +328,14 @@ module Toys
286
328
  k.is_a?(::Symbol) || k.is_a?(::String)
287
329
  end
288
330
  end
331
+ alias __options options
289
332
 
290
333
  ##
291
334
  # Find the given data file or directory in this tool's search path.
292
335
  #
336
+ # If the `find_data` method is overridden by the tool, you can still access
337
+ # it using the name `__find_data`.
338
+ #
293
339
  # @param path [String] The path to find
294
340
  # @param type [nil,:file,:directory] Type of file system object to find,
295
341
  # or nil to return any type.
@@ -300,10 +346,14 @@ module Toys
300
346
  def find_data(path, type: nil)
301
347
  @__data[Key::TOOL_SOURCE].find_data(path, type: type)
302
348
  end
349
+ alias __find_data find_data
303
350
 
304
351
  ##
305
352
  # Exit immediately with the given status code.
306
353
  #
354
+ # If the `exit` method is overridden by the tool, you can still access it
355
+ # using the name `__exit` or by calling {Context.exit}.
356
+ #
307
357
  # @param code [Integer] The status code, which should be 0 for no error,
308
358
  # or nonzero for an error condition. Default is 0.
309
359
  # @return [void]
@@ -311,6 +361,7 @@ module Toys
311
361
  def exit(code = 0)
312
362
  Context.exit(code)
313
363
  end
364
+ alias __exit exit
314
365
 
315
366
  ##
316
367
  # Exit immediately with the given status code. This class method can be
data/lib/toys/core.rb CHANGED
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.14.7"
12
+ VERSION = "0.15.0"
13
13
  end
14
14
 
15
15
  ##