toys-core 0.14.7 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
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
  ##