Class: Toys::CLI
- Inherits:
-
Object
- Object
- Toys::CLI
- Defined in:
- lib/toys/cli.rb
Overview
A Toys-based CLI.
This is the entry point for command line execution. It includes the set of tool definitions (and/or information on how to load them from the file system), configuration parameters such as logging and error handling, and a method to call to invoke a command.
This is the class to instantiate to create a Toys-based command line executable. For example:
#!/usr/bin/env ruby
require "toys-core"
cli = Toys::CLI.new
cli.add_config_block do
def run
puts "Hello, world!"
end
end
exit(cli.run(*ARGV))
The currently running CLI is also available at runtime, and can be used by tools that want to invoke other tools. For example:
# My .toys.rb
tool "foo" do
def run
puts "in foo"
end
end
tool "bar" do
def run
puts "in bar"
cli.run "foo"
end
end
Defined Under Namespace
Classes: DefaultCompletion, DefaultErrorHandler
Instance Attribute Summary collapse
-
#base_level ⇒ Integer
readonly
The initial logger level in this CLI, used as the level for verbosity 0.
-
#completion ⇒ Toys::Completion::Base, Proc
readonly
The overall completion strategy for this CLI.
-
#executable_name ⇒ String
readonly
The effective executable name used for usage text in this CLI.
-
#extra_delimiters ⇒ String
readonly
The string of tool name delimiter characters (besides space).
-
#loader ⇒ Toys::Loader
readonly
The current loader for this CLI.
-
#logger ⇒ Logger
readonly
The logger used by this CLI.
Class Method Summary collapse
-
.default_logger(output: nil) ⇒ Logger
Returns a default logger that writes formatted logs to a given stream.
-
.default_middleware_lookup ⇒ Toys::ModuleLookup
Returns a default ModuleLookup for middleware that points at the StandardMiddleware module.
-
.default_middleware_stack ⇒ Array<Toys::Middleware>
Returns a default set of middleware that may be used as a starting point for a typical CLI.
-
.default_mixin_lookup ⇒ Toys::ModuleLookup
Returns a default ModuleLookup for mixins that points at the StandardMixins module.
-
.default_template_lookup ⇒ Toys::ModuleLookup
Returns a default empty ModuleLookup for templates.
Instance Method Summary collapse
-
#add_config_block(high_priority: false, name: nil, &block) ⇒ self
Add a configuration block to the loader.
-
#add_config_path(path, high_priority: false) ⇒ self
Add a specific configuration file or directory to the loader.
-
#add_search_path(search_path, high_priority: false) ⇒ self
Checks the given directory path.
-
#add_search_path_hierarchy(start: nil, terminate: [], high_priority: false) ⇒ self
Walk up the directory hierarchy from the given start location, and add to the loader any config files and directories found.
-
#child(**_opts) {|cli| ... } ⇒ Toys::CLI
Make a clone with the same settings but no paths in the loader.
-
#initialize(executable_name: nil, middleware_stack: nil, extra_delimiters: "", config_dir_name: nil, config_file_name: nil, index_file_name: nil, preload_file_name: nil, preload_dir_name: nil, data_dir_name: nil, mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, logger: nil, base_level: nil, error_handler: nil, completion: nil) ⇒ CLI
constructor
Create a CLI.
-
#run(*args, verbosity: 0, delegated_from: nil) ⇒ Integer
Run the CLI with the given command line arguments.
Constructor Details
#initialize(executable_name: nil, middleware_stack: nil, extra_delimiters: "", config_dir_name: nil, config_file_name: nil, index_file_name: nil, preload_file_name: nil, preload_dir_name: nil, data_dir_name: nil, mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, logger: nil, base_level: nil, error_handler: nil, completion: nil) ⇒ CLI
Create a CLI.
Most configuration parameters (besides tool definitions and tool lookup paths) are set as options passed to the constructor. These options fall roughly into four categories:
- Options affecting output behavior:
-
logger: The logger -
base_level: The default log level -
error_handler: Callback for handling exceptions -
executable_name: The name of the executable
-
- Options affecting tool specification
-
extra_delimibers: Tool name delimiters besides space -
completion: Tab completion handler
-
- Options affecting tool definition
-
middleware_stack: The middleware applied to all tools -
mixin_lookup: Where to find well-known mixins -
middleware_lookup: Where to find well-known middleware -
template_lookup: Where to find well-known templates
-
- Options affecting tool files and directories
-
config_dir_name: Directory name containing tool files -
config_file_name: File name for tools -
index_file_name: Name of index files in tool directories -
preload_file_name: Name of preload files in tool directories -
preload_dir_name: Name of preload directories in tool directories -
data_dir_name: Name of data directories in tool directories
-
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/toys/cli.rb', line 185 def initialize( executable_name: nil, middleware_stack: nil, extra_delimiters: "", config_dir_name: nil, config_file_name: nil, index_file_name: nil, preload_file_name: nil, preload_dir_name: nil, data_dir_name: nil, mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, logger: nil, base_level: nil, error_handler: nil, completion: nil ) @executable_name = executable_name || ::File.basename($PROGRAM_NAME) @middleware_stack = middleware_stack || CLI.default_middleware_stack @mixin_lookup = mixin_lookup || CLI.default_mixin_lookup @middleware_lookup = middleware_lookup || CLI.default_middleware_lookup @template_lookup = template_lookup || CLI.default_template_lookup @error_handler = error_handler || DefaultErrorHandler.new @completion = completion || DefaultCompletion.new @logger = logger || CLI.default_logger @base_level = base_level || @logger.level @extra_delimiters = extra_delimiters @config_dir_name = config_dir_name @config_file_name = config_file_name @index_file_name = index_file_name @preload_file_name = preload_file_name @preload_dir_name = preload_dir_name @data_dir_name = data_dir_name @loader = Loader.new( index_file_name: @index_file_name, extra_delimiters: @extra_delimiters, preload_dir_name: @preload_dir_name, preload_file_name: @preload_file_name, data_dir_name: @data_dir_name, mixin_lookup: @mixin_lookup, template_lookup: @template_lookup, middleware_lookup: @middleware_lookup, middleware_stack: @middleware_stack ) end |
Instance Attribute Details
#base_level ⇒ Integer (readonly)
The initial logger level in this CLI, used as the level for verbosity 0.
276 277 278 |
# File 'lib/toys/cli.rb', line 276 def base_level @base_level end |
#completion ⇒ Toys::Completion::Base, Proc (readonly)
The overall completion strategy for this CLI.
282 283 284 |
# File 'lib/toys/cli.rb', line 282 def completion @completion end |
#executable_name ⇒ String (readonly)
The effective executable name used for usage text in this CLI.
258 259 260 |
# File 'lib/toys/cli.rb', line 258 def executable_name @executable_name end |
#extra_delimiters ⇒ String (readonly)
The string of tool name delimiter characters (besides space).
264 265 266 |
# File 'lib/toys/cli.rb', line 264 def extra_delimiters @extra_delimiters end |
#loader ⇒ Toys::Loader (readonly)
The current loader for this CLI.
252 253 254 |
# File 'lib/toys/cli.rb', line 252 def loader @loader end |
#logger ⇒ Logger (readonly)
The logger used by this CLI.
270 271 272 |
# File 'lib/toys/cli.rb', line 270 def logger @logger end |
Class Method Details
.default_logger(output: nil) ⇒ Logger
Returns a default logger that writes formatted logs to a given stream.
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 |
# File 'lib/toys/cli.rb', line 649 def default_logger(output: nil) require "toys/utils/terminal" output ||= $stderr logger = ::Logger.new(output) terminal = Utils::Terminal.new(output: output) logger.formatter = proc do |severity, time, _progname, msg| msg_str = case msg when ::String msg when ::Exception "#{msg.} (#{msg.class})\n" << (msg.backtrace || []).join("\n") else msg.inspect end format_log(terminal, time, severity, msg_str) end logger.level = ::Logger::WARN logger end |
.default_middleware_lookup ⇒ Toys::ModuleLookup
Returns a default ModuleLookup for middleware that points at the StandardMiddleware module.
630 631 632 |
# File 'lib/toys/cli.rb', line 630 def default_middleware_lookup ModuleLookup.new.add_path("toys/standard_middleware") end |
.default_middleware_stack ⇒ Array<Toys::Middleware>
Returns a default set of middleware that may be used as a starting point for a typical CLI. This set includes the following in order:
- StandardMiddleware::SetDefaultDescriptions providing defaults for description fields.
- StandardMiddleware::ShowHelp adding the
--helpflag and providing default behavior for namespaces. - StandardMiddleware::HandleUsageErrors
- StandardMiddleware::AddVerbosityFlags adding the
--verboseand--quietflags for managing the logger level.
605 606 607 608 609 610 611 612 |
# File 'lib/toys/cli.rb', line 605 def default_middleware_stack [ [:set_default_descriptions], [:show_help, help_flags: true, fallback_execution: true], [:handle_usage_errors], [:add_verbosity_flags], ] end |
.default_mixin_lookup ⇒ Toys::ModuleLookup
Returns a default ModuleLookup for mixins that points at the StandardMixins module.
620 621 622 |
# File 'lib/toys/cli.rb', line 620 def default_mixin_lookup ModuleLookup.new.add_path("toys/standard_mixins") end |
.default_template_lookup ⇒ Toys::ModuleLookup
Returns a default empty ModuleLookup for templates.
639 640 641 |
# File 'lib/toys/cli.rb', line 639 def default_template_lookup ModuleLookup.new end |
Instance Method Details
#add_config_block(high_priority: false, name: nil, &block) ⇒ self
Add a configuration block to the loader.
This is used to create tools "inline", and is useful for simple command line executables based on Toys.
319 320 321 322 |
# File 'lib/toys/cli.rb', line 319 def add_config_block(high_priority: false, name: nil, &block) @loader.add_block(high_priority: high_priority, name: name, &block) self end |
#add_config_path(path, high_priority: false) ⇒ self
Add a specific configuration file or directory to the loader.
This is generally used to load a static or "built-in" set of tools, either for a standalone command line executable based on Toys, or to provide a "default" set of tools for a dynamic executable. For example, the main Toys executable uses this to load the builtin tools from its "builtins" directory.
299 300 301 302 |
# File 'lib/toys/cli.rb', line 299 def add_config_path(path, high_priority: false) @loader.add_path(path, high_priority: high_priority) self end |
#add_search_path(search_path, high_priority: false) ⇒ self
Checks the given directory path. If it contains a config file and/or config directory, those are added to the loader.
The main Toys executable uses this method to load tools from directories
in the TOYS_PATH.
336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/toys/cli.rb', line 336 def add_search_path(search_path, high_priority: false) paths = [] if @config_file_name file_path = ::File.join(search_path, @config_file_name) paths << file_path if !::File.directory?(file_path) && ::File.readable?(file_path) end if @config_dir_name dir_path = ::File.join(search_path, @config_dir_name) paths << dir_path if ::File.directory?(dir_path) && ::File.readable?(dir_path) end @loader.add_path(paths, high_priority: high_priority) self end |
#add_search_path_hierarchy(start: nil, terminate: [], high_priority: false) ⇒ self
Walk up the directory hierarchy from the given start location, and add to the loader any config files and directories found.
The main Toys executable uses this method to load tools from the current directory and its ancestors.
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/toys/cli.rb', line 367 def add_search_path_hierarchy(start: nil, terminate: [], high_priority: false) path = start || ::Dir.pwd paths = [] loop do break if terminate.include?(path) paths << path next_path = ::File.dirname(path) break if next_path == path path = next_path end paths.reverse! if high_priority paths.each do |p| add_search_path(p, high_priority: high_priority) end self end |
#child(**_opts) {|cli| ... } ⇒ Toys::CLI
Make a clone with the same settings but no paths in the loader. This is sometimes useful for running sub-tools that have to be loaded from a different configuration.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/toys/cli.rb', line 227 def child(**_opts) cli = CLI.new(executable_name: @executable_name, config_dir_name: @config_dir_name, config_file_name: @config_file_name, index_file_name: @index_file_name, preload_dir_name: @preload_dir_name, preload_file_name: @preload_file_name, data_dir_name: @data_dir_name, middleware_stack: @middleware_stack, extra_delimiters: @extra_delimiters, mixin_lookup: @mixin_lookup, middleware_lookup: @middleware_lookup, template_lookup: @template_lookup, logger: @logger, base_level: @base_level, error_handler: @error_handler, completion: @completion) yield cli if block_given? cli end |
#run(*args, verbosity: 0, delegated_from: nil) ⇒ Integer
Run the CLI with the given command line arguments. Handles exceptions using the error handler.
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/toys/cli.rb', line 395 def run(*args, verbosity: 0, delegated_from: nil) tool, remaining = ContextualError.capture("Error finding tool definition") do @loader.lookup(args.flatten) end ContextualError.capture_path( "Error during tool execution!", tool.source_info&.source_path, tool_name: tool.full_name, tool_args: remaining ) do default_data = { Context::Key::VERBOSITY => verbosity, Context::Key::DELEGATED_FROM => delegated_from, } run_tool(tool, remaining, default_data) end rescue ContextualError, ::Interrupt => e @error_handler.call(e).to_i end |