Toys-Core
Toys is a configurable command line tool. Write commands in Ruby using a simple
DSL, and Toys will provide the command line executable and take care of all the
details such as argument parsing, online help, and error reporting.
Toys-Core is the command line tool framework underlying Toys. It can be used
to write command line executables using the Toys DSL and the power of the Toys
classes.
For more detailed information about Toys-Core, see the
Toys-Core User's Guide.
For background information about Toys itself, see the
Toys README and the
Toys User Guide.
Introductory tutorial
Here's a tutorial to help you get a feel for how to write a basic command line
executable using Toys-Core.
It assumes basic familiarity with Toys, so, if you have not done so, I
recommend first walking through the tutorial in the
Toys README. It also assumes
you are running a unix-like system such as Linux or macOS. Some commands might
need to be modified if you're running on Windows.
Install Toys-Core
Install the toys-core gem using:
$ gem install toys-core
You can also install the toys gem, which brings in toys-core as a
dependency.
Create a new executable
We'll start by creating an executable Ruby script. Using your favorite text
editor, create new a file called mycmd
with the following contents:
#!/usr/bin/env ruby
require "toys-core"
cli = Toys::CLI.new
exit(cli.run(*ARGV))
Make sure the file's executable bit is set:
$ chmod a+x mycmd
That's it! This is a fully-functional Toys-based executable! Let's see what
happens when you run it:
$ ./mycmd
Just as with Toys itself, you get a help screen by default (since we haven't
yet actually implemented any behavior.) As you can see, some of the same
features from Toys are present already: online help, and --verbose
and
--quiet
flags. These features can of course all be customized or disabled,
but they're often useful to have to start off.
Add some functionality
You implement the functionality of your executable using the same DSL that you
use to write Toys files. You could point your executable at a directory
containing actual Toys files, but the simplest option is to provide the
information to the Toys CLI object in a block.
Let's add some functionality to mycmd
.
#!/usr/bin/env ruby
require "toys-core"
cli = Toys::CLI.new
#### Insert the following block ...
cli.add_config_block do
desc "My first executable!"
flag :whom, default: "world"
def run
puts "Hello, #{whom}!"
end
end
exit(cli.run(*ARGV))
If you went through the tutorial in the README for the Toys gem, this should
look familiar. Let's run it now, and experiment with passing flags to it.
$ ./mycmd
$ ./mycmd --whom=ruby
$ ./mycmd --bye
$ ./mycmd --help
Notice that we did not create a tool
block, but instead set up description,
flags, and functionality directly in the configuration block. This configures
the "root tool", i.e. what happens when you run the executable without passing
a tool name to it. (In fact, it's legal to do this in Toys as well, by setting
functionality at the "top level" of a .toys.rb
file without including any
tool
block.)
Tool-based executables
But perhaps you want your executable to have multiple "tools", similar to other
familiar executables like git or kubectl. You can define tools, including
nested tools, by writing tool
blocks in your config. Here's an example:
#!/usr/bin/env ruby
require "toys-core"
cli = Toys::CLI.new
#### Change the config block as follows ...
cli.add_config_block do
# Things outside any tool block still apply to the root
desc "My first executable with several tools"
# We'll put the greet function here
tool "greet" do
desc "My first tool!"
flag :whom, default: "world"
def run
puts "Hello, #{whom}!"
end
end
# Try writing a second tool here. You could use the "new-repo"
# example from the Toys tutorial.
end
exit(cli.run(*ARGV))
Now you can run greet
as a tool:
$ ./mycmd greet
The "root" functionality once again shows global help, including a list of the
available tools.
$ ./mycmd
Notice that the description set at the "root" of the config block (outside the
tool blocks) shows up here.
Configuring the CLI
So far, our executable behaves very similarly to Toys itself. Help screens are
shown by default, flags for help and verbosity are provided automatically, and
any exceptions are displayed to the terminal.
These and many more aspects of the behavior of our executable can be customized
by passing options to the Toys::CLI
constructor. Here's an example that
modifies error handling and delimiter parsing.
#!/usr/bin/env ruby
require "toys-core"
#### Pass some additional options to the CLI constructor ...
cli = Toys::CLI.new(
extra_delimiters: ":",
error_handler: ->(err) {
puts "Aww shucks, an error happened: #{err.message}"
return 1
}
)
#### Change the config block as follows ...
cli.add_config_block do
tool "example" do
tool "greet" do
def run
puts "Hello, world!"
end
end
tool "error" do
def run
raise "Whoops!"
end
end
end
end
exit(cli.run(*ARGV))
Try these runs. Do they behave as you expected?
$ ./mycmd example greet
$ ./mycmd example:greet
$ ./mycmd example.greet
$ ./mycmd example error
Configuring middleware
Toys middleware are objects that provide common functionality for all the
tools in your executable. For example, a middleware adds the --help
flag to
your tools by default.
The next example provides a custom middleware stack, resulting in a different
set of common tool functionality.
#!/usr/bin/env ruby
require "toys-core"
#### Change the CLI construction again ...
middlewares = [
[:set_default_descriptions, default_tool_desc: "Hey look, a tool!"],
[:show_help, help_flags: true]
]
cli = Toys::CLI.new middleware_stack: middlewares
#### Use this config block ...
cli.add_config_block do
tool "greet" do
def run
puts "Hello, world!"
end
end
end
exit(cli.run(*ARGV))
We've now modified the default description applied to tools that don't provide
their own description. See the effect with:
$ ./mycmd greet --help
We've also omitted some of the default middleware, including the one that adds
the --verbose
and --quiet
flags to all your tools. Notice those flags are
no longer present.
We've also omitted the middleware that provides default execution behavior
(i.e. displaying the help screen) when there is no run
method. Now, since we
haven't defined a toplevel run
method in this last example, invoking the root
tool will cause an error:
$ ./mycmd
It is even possible to write your own middleware. In general, while the
Toys::CLI
constructor provides defaults that should work for many use cases,
you can also customize it heavily to suit the needs of your executable.
Packaging as a gem
So far we've created simple one-file executables that you could distribute by
itself. However, the toys-core
gem is a dependency, and your users will need
to have it installed. You could alleviate this by wrapping your executable in a
gem that can declare toys-core
as a dependency explicitly.
The examples directory
includes a few simple examples that you can use as a starting point.
To experiment with the examples, clone the Toys repo from GitHub:
$ git clone https://github.com/dazuma/toys.git
$ cd toys
Navigate to the simple-gem example:
$ cd toys-core/examples/simple-gem
This example wraps the simple "greet" executable that we
covered earlier in a gem. You can see the
executable file
in the bin directory.
Try it out by building and installing the gem. From the examples/simple-gem
directory, run:
$ toys install
Once the gem has successfully installed, you can run the executable, which
Rubygems should have added to your path. (Note: if you are using a ruby
installation manager, you may need to "rehash" or "reshim" to gain access to
the executable.)
$ toys-core-simple-example --whom=Toys
Clean up by uninstalling the gem:
$ gem uninstall toys-core-simple-example
If the implementation of your executable is more complex, you might want to
break it up into multiple files. The multi-file gem example demonstrates this.
$ cd ../multi-file-gem
This executable's implementation resides in its
lib directory,
a technique that may be familiar to writers of command line executables. More
interestingly, the tools themselves are no longer defined in a block passed to
the CLI object, but have been moved into a separate
"tools" directory.
This directory has the same structure and supports the same features that are
available when writing complex sets of tools in a .toys
directory. You then
configure the CLI object to look in this directory for its tools definitions,
as you can see in
the code.
Try it out now. From the examples/multi-file-gem
directory, run:
$ toys install
Once the gem has successfully installed, you can run the executable, which
Rubygems should have added to your path. (Note: if you are using a ruby
installation manager, you may need to "rehash" or "reshim" to gain access to
the executable.)
$ toys-core-multi-file-example greet
Clean up by uninstalling the gem:
$ gem uninstall toys-core-multi-file-example
Learning more
This introduction should be enough to get you started. However, Toys-Core is a
deep framework with many more features.
Learn about how to write tools using the Toys DSL, including validating and
interpreting command line arguments, using templates and mixins, controlling
subprocesses, and producing nice styled output, in the
Toys User Guide.
Learn more about how to customize and package your own executable, including
handling errors, controlling log output, and providing your own mixins,
templates, and middleware, in the
Toys-Core User Guide.
Detailed usage information can be found in the
class reference documentation
System requirements
Toys-Core requires Ruby 2.4 or later.
Most parts of Toys-Core work on JRuby. However, JRuby is not recommended
because of JVM boot latency, lack of support for Kernel#fork, and other issues.
Most parts of Toys-Core work on TruffleRuby. However, TruffleRuby is not
recommended because it has a few known bugs that affect Toys.
License
Copyright 2019-2023 Daniel Azuma and the Toys contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.