Class: Toys::Runner
- Inherits:
-
Object
- Object
- Toys::Runner
- Defined in:
- lib/toys/runner.rb
Overview
An object that runs tools.
A Runner holds the environment in which tools run. This includes the Loader that resolves a tool name to a tool definition, along with run policy such as how to obtain a logger for a tool and what to do with an error the tool did not handle. This environment is fixed when the Runner is constructed.
Everything specific to a single invocation, such as which tool to run and
the arguments to pass to it, is passed to #run. A Runner is thus itself
immutable and holds no per-run state, so one Runner can run any number of
tools and is safe to share. This says nothing about the tools it runs,
however; running two tools at the same time is safe only if the tools
themselves are, since a tool can modify global state such as the load path
or a shared logger. See the logger parameter of CLI#initialize
for one such caveat.
The Runner that is running a tool is available to that tool as Context#runner, so a tool can use it to run a sibling tool in the same process. For example:
# My .toys.rb
tool "foo" do
def run
puts "in foo"
end
end
tool "bar" do
def run
puts "in bar"
runner.run(["foo"])
end
end
While a tool runs, the Runner sets the level of the tool's logger to reflect
the verbosity, as described under the base_level parameter of
#initialize. A logger can also receive the verbosity itself, by
implementing a verbosity attribute: if the logger responds to both
verbosity and verbosity=, the Runner sets it to the verbosity (an
Integer) for the duration of the run, and restores the previous value
afterward. This lets a logger vary its behavior, such as its formatting, by
verbosity independently of its level.
Constant Summary collapse
- DEFAULT_LOGGER_FACTORY =
The singleton default logger_factory Proc, which simply returns a new logger writing to the current stderr.
proc { |_tool| logger = ::Logger.new($stderr) logger.level = ::Logger::WARN logger }.freeze
- DEFAULT_ERROR_HANDLER =
The singleton default error_handler Proc, which simply reraises the error it is given. A ContextualError is reraised as itself, so that a rescue block has access to the context information. An unhandled
SignalException(or a subclass such asInterrupt) is also reraised as itself, so that the Ruby VM has a chance to handle it normally. proc { |error| raise(error) }.freeze
Instance Method Summary collapse
-
#initialize(loader, logger_factory: nil, base_level: nil, error_handler: nil, executable_name: nil, external_data: {}) ⇒ Runner
constructor
Create a Runner.
-
#run(args, verbosity: 0, wrap_errors: true, handle_errors: true) {|context| ... } ⇒ Integer
Run a tool.
Constructor Details
#initialize(loader, logger_factory: nil, base_level: nil, error_handler: nil, executable_name: nil, external_data: {}) ⇒ Runner
Create a Runner.
This performs no I/O and raises nothing. Tools are looked up, loaded, and run only when #run is called.
114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/toys/runner.rb', line 114 def initialize(loader, logger_factory: nil, base_level: nil, error_handler: nil, executable_name: nil, external_data: {}) @loader = loader @logger_factory = logger_factory || DEFAULT_LOGGER_FACTORY @base_level = base_level @error_handler = error_handler || DEFAULT_ERROR_HANDLER @executable_name = executable_name || ::File.basename($PROGRAM_NAME) @external_data = external_data end |
Instance Method Details
#run(args, verbosity: 0, wrap_errors: true, handle_errors: true) {|context| ... } ⇒ Integer
Run a tool.
The tool is looked up by matching a tool name at the beginning of the given arguments. The remaining arguments are then parsed into a Context, the tool's middleware is applied, and the tool is run.
If a block is passed, the runtime context is simply yielded to it in place of the tool's run handler, with the tool's middleware still applied. This is useful for testing parts of the tool runtime in isolation.
If the tool raises a SignalException that it does not handle itself,
that exception propagates unwrapped, even when wrap_errors is enabled.
This lets each tool in a nested execution dispatch it to its own
on_interrupt or on_signal handler, and lets the error handler, and
ultimately the Ruby VM, recognize it as a signal.
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/toys/runner.rb', line 170 def run(args, verbosity: 0, wrap_errors: true, handle_errors: true, &block) unless args.is_a?(::Array) raise ::ArgumentError, "Tool arguments must be an array of strings: #{args.inspect}" end Invocation.new(runner: self, args: args, verbosity: verbosity, wrap_errors: wrap_errors, handle_errors: handle_errors, delegated_from: nil, block: block).run end |