{"id":13878561,"url":"https://github.com/piotrmurach/tty-runner","last_synced_at":"2025-06-12T13:09:30.982Z","repository":{"id":66238136,"uuid":"308126284","full_name":"piotrmurach/tty-runner","owner":"piotrmurach","description":"A command routing tree for terminal applications","archived":false,"fork":false,"pushed_at":"2020-11-28T22:54:41.000Z","size":79,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-24T07:33:09.625Z","etag":null,"topics":["command-line-interface","command-line-tool","routing-engine","ruby","ruby-gem"],"latest_commit_sha":null,"homepage":"https://ttytoolkit.org","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piotrmurach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"piotrmurach"}},"created_at":"2020-10-28T19:55:26.000Z","updated_at":"2023-08-13T10:13:22.000Z","dependencies_parsed_at":"2023-02-20T16:45:59.719Z","dependency_job_id":null,"html_url":"https://github.com/piotrmurach/tty-runner","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/piotrmurach/tty-runner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ftty-runner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ftty-runner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ftty-runner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ftty-runner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrmurach","download_url":"https://codeload.github.com/piotrmurach/tty-runner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ftty-runner/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259470951,"owners_count":22862999,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line-interface","command-line-tool","routing-engine","ruby","ruby-gem"],"created_at":"2024-08-06T08:01:53.158Z","updated_at":"2025-06-12T13:09:30.974Z","avatar_url":"https://github.com/piotrmurach.png","language":"Ruby","funding_links":["https://github.com/sponsors/piotrmurach"],"categories":["Ruby"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://ttytoolkit.org\"\u003e\u003cimg width=\"130\" src=\"https://github.com/piotrmurach/tty/raw/master/images/tty.png\" alt=\"TTY Toolkit logo\" /\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n# TTY::Runner\n\n[![Gem Version](https://badge.fury.io/rb/tty-runner.svg)][gem]\n[![Actions CI](https://github.com/piotrmurach/tty-runner/workflows/CI/badge.svg?branch=master)][gh_actions_ci]\n[![Build status](https://ci.appveyor.com/api/projects/status/re0e9nyi6gavni77?svg=true)][appveyor]\n[![Maintainability](https://api.codeclimate.com/v1/badges/03169126a4ba2d031ece/maintainability)][codeclimate]\n[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-runner/badge.svg)][coverage]\n[![Inline docs](http://inch-ci.org/github/piotrmurach/tty-runner.svg?branch=master)][inchpages]\n\n[gem]: http://badge.fury.io/rb/tty-runner\n[gh_actions_ci]: https://github.com/piotrmurach/tty-runner/actions?query=workflow%3ACI\n[appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-runner\n[codeclimate]: https://codeclimate.com/github/piotrmurach/tty-runner/maintainability\n[coverage]: https://coveralls.io/github/piotrmurach/tty-runner\n[inchpages]: http://inch-ci.org/github/piotrmurach/tty-runner\n\n\u003e A command routing tree for terminal applications.\n\n**TTY::Runner** provides independent command running component for [TTY](https://github.com/piotrmurach/tty) toolkit.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"tty-runner\"\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install tty-runner\n\n## Contents\n\n* [1. Usage](#1-usage)\n* [2. API](#2-api)\n  * [2.1 on](#21-on)\n  * [2.2 run](#22-run)\n  * [2.3 mount](#23-mount)\n\n## 1. Usage\n\nHere's an example of an application showing routing of commands and subcommands:\n\n```ruby\n# app.rb\nrequire \"tty-runner\"\n\nclass App \u003c TTY::Runner\n  # The command line application commands are declared with the 'commands' method.\n  commands do\n    # Runs code inside a block when no commands are given. This is not\n    # required as by default all commands will be listed instead.\n    run do\n      def call(argv)\n        puts \"root\"\n      end\n    end\n\n    # Matches when bare 'config' command is issued and by default\n    # lists all immediate subcommands.\n    on \"config\" do\n      # Matches 'config add' subcommand and loads 'Config::AddCommand' object\n      # based on the snake case name from the ':run' value. The 'Config::AddCommand'\n      # needs to only implement a 'call' method that will be automatically invoked.\n      on \"add\", \"Add a new entry\", run: \"add_command\"\n\n      # The :run keyword accepts any callable object like a proc that will be\n      # lazily evaluated when the 'config remove' command or 'config rm' alias\n      # are matched.\n      on \"remove\", aliases: %w[rm], run: -\u003e { puts \"removing from config...\" }\n\n      # The command can be given in an \"command#action\" format either via :run\n      # keyword or using the 'run' helper method.\n      # This will automatically convert 'get_command' into 'Config::GetCommand'\n      # when 'config get' command is entered and invoke the 'execute' method.\n      on \"get\" do\n        run \"get_command#execute\"\n      end\n\n      # The 'run' helper can also accept a block that will be converted to\n      # a command object when 'edit' subcommand is matched. It expects\n      # a 'call' method implementation that optionally gets the rest of\n      # unparsed command line arguments as a parameter.\n      on \"edit\" do\n        run do\n          def call(argv)\n            puts \"editing with #{argv}\"\n          end\n        end\n      end\n    end\n\n    on \"tag\" do\n      # This will match all commands starting with 'tag' and continue\n      # matching process with subcommands from 'TagCommands' runner that\n      # needs to be an instance of 'TTY::Runner'. This way you can compose\n      # complex applications from smaller routing pieces.\n      mount TagCommands\n    end\n  end\nend\n\n# Another 'TTY::Runner' application with commands that can be mounted\n# inside another runner application. This way you can build complex\n# command line applications from smaller parts.\nclass TagCommands \u003c TTY::Runner\n  commands do\n    on \"create\" do\n      run -\u003e { puts \"tag creating...\" }\n    end\n\n    on \"delete\" do\n      run -\u003e { puts \"tag deleting...\" }\n    end\n  end\nend\n```\n\nThen run your application with `run`:\n\n```ruby\nApp.run\n```\n\nWhen no arguments are provided, the top level run block will trigger:\n\n```\napp.rb\n# =\u003e\n# root\n```\n\nSupplying `config` command will list all the subcommands:\n\n```\napp.rb config\n# =\u003e\n#  config add\n#  config edit\n#  config get\n#  config remove\n```\n\nAnd when specific subcommand `rm` within the `config` scope is given:\n\n```\napp.rb config rm\n# =\u003e\n# removing from config...\n```\n\nWe can also run mounted `create` subcommand from `TagCommands` runner under the `tag` command:\n\n```\napp.rb tag create\n# =\u003e\n# tag creating...\n```\n\n## 2. API\n\n### 2.1 on\n\nUsing 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.\n\nHere are few examples how to specify a command to run:\n\n```ruby\non \"cmd\", run: -\u003e { }                      # a proc to call\non \"cmd\", run: Command                     # a Command object to instantiate and call\non \"cmd\", run: \"command\"                   # invokes 'call' method by default\non \"cmd\", run: \"command#action\"            # specified custom 'action' method\non \"cmd\", run: \"command\", action: \"action\" # specifies custom 'action'\n```\n\nThe same values can be provided to the `run` method inside the block:\n\n```ruby\non \"cmd\" do\n  run \"command#action\"\nend\n```\n\nThe `on` method also serves as a namespace for other (sub)commands. There is no limit on how deeply you can nest commands.\n\n```ruby\non \"foo\", run: FooCommand do       # matches 'foo' and invokes 'call' on FooCommand instance\n  on \"bar\", run: \"bar_command\" do  # matches 'foo bar' and invokes 'call' on Foo::BarCommand instance\n    on \"baz\" do                    # matches 'foo bar baz' and\n      run \"baz_command#execute\"    # invokes 'execute' on Foo::Bar::BazCommand instance\n    end\n  end\nend\n```\n\n### 2.2 run\n\nThere are two ways to specify a command, with a `:run` keyword or a `run` helper.\n\nThe `:run` keyword is used by `on` method and accepts the following values as a command:\n\n```ruby\non \"cmd\", run: -\u003e { ... }            # a proc object\non \"cmd\", run: -\u003e(argv) { ... }      # a proc object with optional unparsed arguments\non \"cmd\", run: FooCommand            # a FooCommand object with 'call' method\non \"cmd\", run: \"foo_command\"         # expands name to a FooCommand object\non \"cmd\", run: \"foo_command#action\"  # expands name to a FooCommand object with 'action' method\n```\n\nThe `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.\n\nFor example, the following creates a command that will be run when 'foo' is entered in the terminal:\n\n```ruby\non \"foo\" do\n  run do\n    def call(argv)\n      ...\n    end\n  end\nend\n```\n\nThe [tty-option](https://github.com/piotrmurach/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.\n\nFor a quick example, to add 'foo' command with one argument and `--baz` option, we can do:\n\n```ruby\non \"foo\" do\n  run do\n    program \"app\"\n\n    command \"foo\"\n\n    desc \"Run foo command\"\n\n    argument :bar do\n      required\n      desc \"The bar argument\"\n    end\n\n    option :baz do\n      short \"-b\"\n      long \"--baz list\"\n      arity one_or_more\n      convert :int_list\n      desc \"The baz option\"\n    end\n\n    def call\n      puts params[\"bar\"]\n      puts params[\"baz\"]\n    end\n  end\nend\n```\n\nWhen run with the following command line inputs:\n\n```\napp foo one --baz 11 12\n```\n\nThe output would produce:\n\n```\none\n[11, 12]\n```\n\nYou will automatically get `-h` and `--help` options for free, so running:\n\n```\napp foo --help\n```\n\nWill output:\n\n```\nUsage: app foo [OPTIONS] BAR\n\nRun foo command\n\nArguments:\n  BAR  The bar argument\n\nOptions:\n  -b, --baz   The baz option\n  -h, --help  Print usage\n```\n\n### 2.3 mount\n\nIn 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.\n\nFor example, given a `FooSubcommands` runner application that groups all `foo` related subcommands:\n\n```ruby\n# foo_subcommands.rb\n\nclass FooSubcommands \u003c TTY::Runner\n  commands do\n    on \"bar\", run: -\u003e { puts \"run bar\" }\n    on \"baz\", run: -\u003e { puts \"run baz\" }\n  end\nend\n```\n\nUsing `mount`, we can nest our subcommands inside the `foo` command in the main application runner like so:\n\n```ruby\nrequire_relative \"foo_subcommands\"\n\nclass App \u003c TTY::Runner\n  commands do\n    on \"foo\" do\n      mount FooSubcommands\n    end\n  end\nend\n```\n\nSee [mount example](https://github.com/piotrmurach/tty-runner/blob/master/examples/mount.rb).\n\n## Development\n\nAfter 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.\n\nTo 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](https://rubygems.org).\n\n## Contributing\n\nBug 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](https://github.com/piotrmurach/tty-runner/blob/master/CODE_OF_CONDUCT.md).\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the TTY::Runner project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/tty-runner/blob/master/CODE_OF_CONDUCT.md).\n\n## Copyright\n\nCopyright (c) 2020 Piotr Murach. See LICENSE for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Ftty-runner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrmurach%2Ftty-runner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Ftty-runner/lists"}