Skip to content

piotrmurach/tty-runner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TTY Toolkit logo

TTY::Runner

Gem Version Actions CI Build status Maintainability Coverage Status Inline docs

A command routing tree for terminal applications.

TTY::Runner provides independent command running component for TTY toolkit.

Installation

Add this line to your application's Gemfile:

gem "tty-runner"

And then execute:

$ bundle install

Or install it yourself as:

$ gem install tty-runner

Contents

1. Usage

Here's an example of an application showing routing of commands and subcommands:

# app.rb
require "tty-runner"

class App < TTY::Runner
  # The command line application commands are declared with the 'commands' method.
  commands do
    # Runs code inside a block when no commands are given. This is not
    # required as by default all commands will be listed instead.
    run do
      def call(argv)
        puts "root"
      end
    end

    # Matches when bare 'config' command is issued and by default
    # lists all immediate subcommands.
    on "config" do
      # Matches 'config add' subcommand and loads 'Config::AddCommand' object
      # based on the snake case name from the ':run' value. The 'Config::AddCommand'
      # needs to only implement a 'call' method that will be automatically invoked.
      on "add", "Add a new entry", run: "add_command"

      # The :run keyword accepts any callable object like a proc that will be
      # lazily evaluated when the 'config remove' command or 'config rm' alias
      # are matched.
      on "remove", aliases: %w[rm], run: -> { puts "removing from config..." }

      # The command can be given in an "command#action" format either via :run
      # keyword or using the 'run' helper method.
      # This will automatically convert 'get_command' into 'Config::GetCommand'
      # when 'config get' command is entered and invoke the 'execute' method.
      on "get" do
        run "get_command#execute"
      end

      # The 'run' helper can also accept a block that will be converted to
      # a command object when 'edit' subcommand is matched. It expects
      # a 'call' method implementation that optionally gets the rest of
      # unparsed command line arguments as a parameter.
      on "edit" do
        run do
          def call(argv)
            puts "editing with #{argv}"
          end
        end
      end
    end

    on "tag" do
      # This will match all commands starting with 'tag' and continue
      # matching process with subcommands from 'TagCommands' runner that
      # needs to be an instance of 'TTY::Runner'. This way you can compose
      # complex applications from smaller routing pieces.
      mount TagCommands
    end
  end
end

# Another 'TTY::Runner' application with commands that can be mounted
# inside another runner application. This way you can build complex
# command line applications from smaller parts.
class TagCommands < TTY::Runner
  commands do
    on "create" do
      run -> { puts "tag creating..." }
    end

    on "delete" do
      run -> { puts "tag deleting..." }
    end
  end
end

Then run your application with run:

App.run

When no arguments are provided, the top level run block will trigger:

app.rb
# =>
# root

Supplying config command will list all the subcommands:

app.rb config
# =>
#  config add
#  config edit
#  config get
#  config remove

And when specific subcommand rm within the config scope is given:

app.rb config rm
# =>
# removing from config...

We can also run mounted create subcommand from TagCommands runner under the tag command:

app.rb tag create
# =>
# tag creating...

2. API

2.1 on

Using the on you can specify the name for the command that will match the command line input. With the :run parameter you can specify a command object to run. Supported values include an object that respond to call method or a string given as a snake case representing an object with corresponding action.

Here are few examples how to specify a command to run:

on "cmd", run: -> { }                      # a proc to call
on "cmd", run: Command                     # a Command object to instantiate and call
on "cmd", run: "command"                   # invokes 'call' method by default
on "cmd", run: "command#action"            # specified custom 'action' method
on "cmd", run: "command", action: "action" # specifies custom 'action'

The same values can be provided to the run method inside the block:

on "cmd" do
  run "command#action"
end

The on method also serves as a namespace for other (sub)commands. There is no limit on how deeply you can nest commands.

on "foo", run: FooCommand do       # matches 'foo' and invokes 'call' on FooCommand instance
  on "bar", run: "bar_command" do  # matches 'foo bar' and invokes 'call' on Foo::BarCommand instance
    on "baz" do                    # matches 'foo bar baz' and
      run "baz_command#execute"    # invokes 'execute' on Foo::Bar::BazCommand instance
    end
  end
end

2.2 run

There are two ways to specify a command, with a :run keyword or a run helper.

The :run keyword is used by on method and accepts the following values as a command:

on "cmd", run: -> { ... }            # a proc object
on "cmd", run: ->(argv) { ... }      # a proc object with optional unparsed arguments
on "cmd", run: FooCommand            # a FooCommand object with 'call' method
on "cmd", run: "foo_command"         # expands name to a FooCommand object
on "cmd", run: "foo_command#action"  # expands name to a FooCommand object with 'action' method

The run helper supports all of the above values but differs with the ability to create a more complex command on-the-fly by specifying it inside a block.

For example, the following creates a command that will be run when 'foo' is entered in the terminal:

on "foo" do
  run do
    def call(argv)
      ...
    end
  end
end

The tty-option supercharges the run helper with many methods for argument and option parsing as well as generating command documentation. Please read the documentation to learn what is possible.

For a quick example, to add 'foo' command with one argument and --baz option, we can do:

on "foo" do
  run do
    program "app"

    command "foo"

    desc "Run foo command"

    argument :bar do
      required
      desc "The bar argument"
    end

    option :baz do
      short "-b"
      long "--baz list"
      arity one_or_more
      convert :int_list
      desc "The baz option"
    end

    def call
      puts params["bar"]
      puts params["baz"]
    end
  end
end

When run with the following command line inputs:

app foo one --baz 11 12

The output would produce:

one
[11, 12]

You will automatically get -h and --help options for free, so running:

app foo --help

Will output:

Usage: app foo [OPTIONS] BAR

Run foo command

Arguments:
  BAR  The bar argument

Options:
  -b, --baz   The baz option
  -h, --help  Print usage

2.3 mount

In cases when your application grows in complexity and has many commands and each of these in turn has many subcommands, you can split and group commands into separate runner applications.

For example, given a FooSubcommands runner application that groups all foo related subcommands:

# foo_subcommands.rb

class FooSubcommands < TTY::Runner
  commands do
    on "bar", run: -> { puts "run bar" }
    on "baz", run: -> { puts "run baz" }
  end
end

Using mount, we can nest our subcommands inside the foo command in the main application runner like so:

require_relative "foo_subcommands"

class App < TTY::Runner
  commands do
    on "foo" do
      mount FooSubcommands
    end
  end
end

See mount example.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-runner. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the TTY::Runner project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Copyright

Copyright (c) 2020 Piotr Murach. See LICENSE for further details.

Releases

No releases published

Sponsor this project

 

Packages

No packages published