toys-core 0.14.7 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/toys/cli.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rbconfig"
4
- require "logger"
5
- require "toys/completion"
6
-
7
3
  module Toys
8
4
  ##
9
5
  # A Toys-based CLI.
@@ -72,7 +68,7 @@ module Toys
72
68
  # * `preload_dir_name`: Name of preload directories in tool directories
73
69
  # * `data_dir_name`: Name of data directories in tool directories
74
70
  #
75
- # @param logger [Logger] A global logger to use for all tools. This may be
71
+ # @param logger [Logger] A global logger to use for all tools. This can be
76
72
  # set if the CLI will call at most one tool at a time. However, it will
77
73
  # behave incorrectly if CLI might run multiple tools at the same time
78
74
  # with different verbosity settings (since the logger cannot have
@@ -80,19 +76,21 @@ module Toys
80
76
  # global logger, but use the `logger_factory` parameter instead.
81
77
  # @param logger_factory [Proc] A proc that takes a {Toys::ToolDefinition}
82
78
  # 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}.
79
+ # Optional. If not provided (and no global logger is set),
80
+ # {Toys::CLI.default_logger_factory} is called to get a basic default.
86
81
  # @param base_level [Integer] The logger level that should correspond
87
82
  # to zero verbosity.
88
83
  # Optional. If not provided, defaults to the current level of the
89
84
  # 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`.
85
+ # @param error_handler [Proc,nil] A proc that is called when an unhandled
86
+ # exception (a normal exception subclassing `StandardError`, an error
87
+ # loading a toys config file subclassing `SyntaxError`, or an unhandled
88
+ # signal subclassing `SignalException`) is detected. The proc should
89
+ # take a {Toys::ContextualError}, whose cause is the unhandled
90
+ # exception, as the sole argument, and report the error. It should
91
+ # return an exit code (normally nonzero) appropriate to the error.
92
+ # Optional. If not provided, {Toys::CLI.default_error_handler} is
93
+ # called to get a basic default handler.
96
94
  # @param executable_name [String] The executable name displayed in help
97
95
  # text. Optional. Defaults to the ruby program name.
98
96
  #
@@ -101,9 +99,8 @@ module Toys
101
99
  # characters are period, colon, and slash.
102
100
  # @param completion [Toys::Completion::Base] A specifier for shell tab
103
101
  # 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.
102
+ # Optional. If not provided, {Toys::CLI.default_completion} is called
103
+ # to get a default completion that delegates to the tool.
107
104
  #
108
105
  # @param middleware_stack [Array<Toys::Middleware::Spec>] An array of
109
106
  # middleware that will be used by default for all tools.
@@ -198,8 +195,8 @@ module Toys
198
195
  @mixin_lookup = mixin_lookup || CLI.default_mixin_lookup
199
196
  @middleware_lookup = middleware_lookup || CLI.default_middleware_lookup
200
197
  @template_lookup = template_lookup || CLI.default_template_lookup
201
- @error_handler = error_handler || DefaultErrorHandler.new
202
- @completion = completion || DefaultCompletion.new
198
+ @error_handler = error_handler || CLI.default_error_handler
199
+ @completion = completion || CLI.default_completion
203
200
  @logger = logger
204
201
  @logger_factory = logger ? proc { logger } : logger_factory || CLI.default_logger_factory
205
202
  @base_level = base_level
@@ -461,9 +458,10 @@ module Toys
461
458
  context = build_context(tool, remaining,
462
459
  verbosity: verbosity,
463
460
  delegated_from: delegated_from)
464
- execute_tool(tool, context, &:run)
461
+ run_handler = make_run_handler(tool)
462
+ execute_tool(tool, context, &run_handler)
465
463
  end
466
- rescue ContextualError, ::Interrupt => e
464
+ rescue ContextualError => e
467
465
  @error_handler.call(e).to_i
468
466
  end
469
467
 
@@ -487,102 +485,6 @@ module Toys
487
485
  end
488
486
  end
489
487
 
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
488
  class << self
587
489
  ##
588
490
  # Returns a default set of middleware that may be used as a starting
@@ -637,56 +539,44 @@ module Toys
637
539
  end
638
540
 
639
541
  ##
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.
542
+ # Returns a bare-bones error handler that takes simply reraises the
543
+ # error. If the original error (the cause of the {Toys::ContextualError})
544
+ # was a `SignalException` (or a subclass such as `Interrupted`), that
545
+ # `SignalException` itself is reraised so that the Ruby VM has a chance
546
+ # to handle it. Otherwise, for any other error, the
547
+ # {Toys::ContextualError} is reraised.
548
+ #
549
+ # @return [Proc]
550
+ #
551
+ def default_error_handler
552
+ proc do |error|
553
+ cause = error.cause
554
+ raise cause.is_a?(::SignalException) ? cause : error
555
+ end
556
+ end
557
+
558
+ ##
559
+ # Returns a default logger factory that generates simple loggers that
560
+ # write to STDERR.
644
561
  #
645
562
  # @return [Proc]
646
563
  #
647
564
  def default_logger_factory
648
- require "toys/utils/terminal"
649
- shared_terminal = Utils::Terminal.new(output: $stderr)
565
+ require "logger"
650
566
  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
567
+ logger = ::Logger.new($stderr)
664
568
  logger.level = ::Logger::WARN
665
569
  logger
666
570
  end
667
571
  end
668
572
 
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"
573
+ ##
574
+ # Returns a default Completion that simply uses the tool's completion.
575
+ #
576
+ def default_completion
577
+ proc do |context|
578
+ context.tool.completion.call(context)
579
+ end
690
580
  end
691
581
  end
692
582
 
@@ -704,7 +594,20 @@ module Toys
704
594
  tool.tool_class.new(arg_parser.data)
705
595
  end
706
596
 
707
- def execute_tool(tool, context)
597
+ def make_run_handler(tool)
598
+ run_handler = tool.run_handler
599
+ if run_handler.is_a?(::Symbol)
600
+ proc do |context|
601
+ context.send(run_handler)
602
+ end
603
+ else
604
+ proc do |context|
605
+ context.instance_exec(&run_handler)
606
+ end
607
+ end
608
+ end
609
+
610
+ def execute_tool(tool, context, &block)
708
611
  tool.source_info&.apply_lib_paths
709
612
  tool.run_initializers(context)
710
613
  cur_logger = context[Context::Key::LOGGER]
@@ -713,9 +616,7 @@ module Toys
713
616
  cur_logger.level = (base_level || original_level) - context[Context::Key::VERBOSITY].to_i
714
617
  end
715
618
  begin
716
- executor = build_executor(tool, context) do
717
- yield context
718
- end
619
+ executor = build_executor(tool, context, &block)
719
620
  catch(:result) do
720
621
  executor.call
721
622
  0
@@ -733,11 +634,10 @@ module Toys
733
634
  elsif !tool.runnable?
734
635
  raise NotRunnableError, "No implementation for tool #{tool.display_name.inspect}"
735
636
  else
736
- yield
637
+ yield context
737
638
  end
738
- rescue ::Interrupt => e
739
- raise e unless tool.handles_interrupts?
740
- handle_interrupt(context, tool.interrupt_handler, e)
639
+ rescue ::SignalException => e
640
+ handle_signal_by_tool(context, tool, e)
741
641
  end
742
642
  end
743
643
  tool.built_middleware.reverse_each do |middleware|
@@ -750,24 +650,25 @@ module Toys
750
650
  usage_errors = context[Context::Key::USAGE_ERRORS]
751
651
  handler = tool.usage_error_handler
752
652
  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
653
+ call_handler(context, handler, usage_errors)
759
654
  end
760
655
 
761
- def handle_interrupt(context, handler, exception)
656
+ def handle_signal_by_tool(context, tool, exception)
657
+ handler = tool.signal_handler(exception.signo)
658
+ raise exception unless handler
659
+ call_handler(context, handler, exception)
660
+ rescue ::SignalException => e
661
+ raise e if e.equal?(exception)
662
+ handle_signal_by_tool(context, tool, e)
663
+ end
664
+
665
+ def call_handler(context, handler, argument)
762
666
  handler = context.method(handler).to_proc if handler.is_a?(::Symbol)
763
667
  if handler.arity.zero?
764
668
  context.instance_exec(&handler)
765
669
  else
766
- context.instance_exec(exception, &handler)
670
+ context.instance_exec(argument, &handler)
767
671
  end
768
- rescue ::Interrupt => e
769
- raise e if e.equal?(exception)
770
- handle_interrupt(context, handler, e)
771
672
  end
772
673
 
773
674
  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.1"
13
13
  end
14
14
 
15
15
  ##