{"id":15512944,"url":"https://github.com/denisdefreyne/cri","last_synced_at":"2025-04-05T04:12:13.667Z","repository":{"id":55438701,"uuid":"1937260","full_name":"denisdefreyne/cri","owner":"denisdefreyne","description":"A tool for building commandline applications","archived":false,"fork":false,"pushed_at":"2025-02-24T17:17:14.000Z","size":580,"stargazers_count":121,"open_issues_count":9,"forks_count":19,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-29T03:08:27.468Z","etag":null,"topics":["cli","ruby"],"latest_commit_sha":null,"homepage":"","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/denisdefreyne.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2011-06-22T20:04:56.000Z","updated_at":"2025-02-24T17:17:17.000Z","dependencies_parsed_at":"2024-06-09T21:10:13.179Z","dependency_job_id":null,"html_url":"https://github.com/denisdefreyne/cri","commit_stats":{"total_commits":487,"total_committers":12,"mean_commits":"40.583333333333336","dds":"0.061601642710472304","last_synced_commit":"b25555a8070b0eca7dfa93867dcd83a1b947632b"},"previous_names":["ddfreyne/cri"],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisdefreyne%2Fcri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisdefreyne%2Fcri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisdefreyne%2Fcri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisdefreyne%2Fcri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/denisdefreyne","download_url":"https://codeload.github.com/denisdefreyne/cri/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284951,"owners_count":20913704,"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":["cli","ruby"],"created_at":"2024-10-02T09:53:56.871Z","updated_at":"2025-04-05T04:12:13.625Z","avatar_url":"https://github.com/denisdefreyne.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Cri\n\n[![Gem](http://img.shields.io/gem/v/cri.svg)](http://rubygems.org/gems/cri)\n[![Gem downloads](https://img.shields.io/gem/dt/cri.svg)](http://rubygems.org/gems/cri)\n\nCri is a library for building easy-to-use command-line tools with support for\nnested commands.\n\n## Requirements\n\nCri requires Ruby 2.6 or newer (including Ruby 3.x).\n\n## Compatibility policy\n\nCri is guaranteed to be supported on any [officially supported Ruby version](https://www.ruby-lang.org/en/downloads/branches/), as well as the version of Ruby that comes by default on\n\n- the last two [Ubuntu LTS releases](https://wiki.ubuntu.com/Releases)\n- the last two major [macOS releases](https://en.wikipedia.org/wiki/MacOS_version_history)\n\n## Usage\n\nThe central concept in Cri is the _command_, which has option definitions as\nwell as code for actually executing itself. In Cri, the command-line tool\nitself is a command as well.\n\nHere’s a sample command definition:\n\n```ruby\ncommand = Cri::Command.define do\n  name        'dostuff'\n  usage       'dostuff [options]'\n  aliases     :ds, :stuff\n  summary     'does stuff'\n  description 'This command does a lot of stuff. I really mean a lot.'\n\n  flag   :h,  :help,  'show help for this command' do |value, cmd|\n    puts cmd.help\n    exit 0\n  end\n  flag   nil, :more,  'do even more stuff'\n  option :s,  :stuff, 'specify stuff to do', argument: :required\n\n  run do |opts, args, cmd|\n    stuff = opts.fetch(:stuff, 'generic stuff')\n    puts \"Doing #{stuff}!\"\n\n    if opts[:more]\n      puts 'Doing it even more!'\n    end\n  end\nend\n```\n\nTo run this command, invoke the `#run` method with the raw arguments. For\nexample, for a root command (the command-line tool itself), the command could\nbe called like this:\n\n```ruby\ncommand.run(ARGV)\n```\n\nEach command has automatically generated help. This help can be printed using\n`Cri::Command#help`; something like this will be shown:\n\n```\nusage: dostuff [options]\n\ndoes stuff\n\n    This command does a lot of stuff. I really mean a lot.\n\noptions:\n\n    -h --help      show help for this command\n       --more      do even more stuff\n    -s --stuff     specify stuff to do\n```\n\n### General command metadata\n\nLet’s disect the command definition and start with the first five lines:\n\n```ruby\nname        'dostuff'\nusage       'dostuff [options]'\naliases     :ds, :stuff\nsummary     'does stuff'\ndescription 'This command does a lot of stuff. I really mean a lot.'\n```\n\nThese lines of the command definition specify the name of the command (or the\ncommand-line tool, if the command is the root command), the usage, a list of\naliases that can be used to call this command, a one-line summary and a (long)\ndescription. The usage should not include a “usage:” prefix nor the name of\nthe supercommand, because the latter will be automatically prepended.\n\nAliases don’t make sense for root commands, but for subcommands they do.\n\n### Command-line options\n\nThe next few lines contain the command’s option definitions:\n\n```ruby\nflag   :h,  :help,  'show help for this command' do |value, cmd|\n  puts cmd.help\n  exit 0\nend\nflag   nil, :more,  'do even more stuff'\noption :s,  :stuff, 'specify stuff to do', argument: :required\n```\n\nThe most generic way of definition an option is using either `#option` or `#opt`. It takes the following arguments:\n\n1. a short option name\n2. a long option name\n3. a description\n4. optional extra parameters\n   - `argument:` (default: `:forbidden`)\n   - `transform:`\n   - `default:`\n   - `multiple:` (default: `false`)\n5. optionally, a block\n\nIn more detail:\n\n- The short option name is a symbol containing one character, to be used in single-dash options, e.g. `:f` (corresponds to `-f`). The long option name is a symbol containing a string, to be used in double-dash options, e.g. `:force` (corresponds to `--force`). Either the short or the long option name can be nil, but not both.\n\n- The description is a short, one-line text that shows up in the command’s help. For example, the `-v`/`--version` option might have the description `show version information and quit`.\n\n- The extra parameters, `argument:`, `multiple:`, `default:`, and `transform:`, are described in the sections below.\n\n- The block, if given, will be executed when the option is found. The arguments to the block are the option value (`true` in case the option does not have an argument) and the command.\n\nThere are several convenience methods that are alternatives to `#option`/`#opt`:\n\n- `#flag` sets `argument:` to `:forbidden`\n- (**deprecated**) `#required` sets `argument:` to `:required` -- deprecated because `#required` suggests that the option is required, wich is incorrect; the _argument_ is required.)\n- (**deprecated**) `#optional` sets `argument:` to `:optional` -- deprecated because `#optional` looks too similar to `#option`.\n\n#### Forbidden, required, and optional arguments (`argument:`)\n\nThe `:argument` parameter can be set to `:forbidden`, `:required`, or `:optional`.\n\n- `:forbidden` means that when the option is present, the value will be set to `true`, and `false` otherwise. For example:\n\n  ```ruby\n  option :f, :force, 'push with force', argument: :forbidden\n\n  run do |opts, args, cmd|\n    puts \"Force? #{opts[:force]}\"\n  end\n  ```\n\n  ```sh\n  % ./push mypackage.zip\n  Force? false\n\n  % ./push --force mypackage.zip\n  Force? true\n  ```\n\n  `:argument` is set to `:forbidden` by default.\n\n- `:required` means that the option must be followed by an argument, which will then be treated as the value for the option. It does not mean that the option itself is required. For example:\n\n  ```ruby\n  option :o, :output, 'specify output file', argument: :required\n  option :f, :fast, 'fetch faster', argument: :forbidden\n\n  run do |opts, args, cmd|\n    puts \"Output file: #{opts[:output]}\"\n  end\n  ```\n\n  ```sh\n  % ./fetch http://example.com/source.zip\n  Output file: nil\n\n  % ./fetch --output example.zip http://example.com/source.zip\n  Output file: example.zip\n\n  % ./fetch http://example.com/source.zip --output\n  fetch: option requires an argument -- output\n\n  % ./fetch --output --fast http://example.com/source.zip\n  fetch: option requires an argument -- output\n  ```\n\n- `:optional` means that the option can be followed by an argument. If it is, then the argument is treated as the value for the option; if it isn’t, the value for the option will be `true`. For example:\n\n  ```ruby\n  option :o, :output, 'specify output file', argument: :optional\n  option :f, :fast, 'fetch faster', argument: :forbidden\n\n  run do |opts, args, cmd|\n    puts \"Output file: #{opts[:output]}\"\n  end\n  ```\n\n  ```sh\n  % ./fetch http://example.com/source.zip\n  Output file: nil\n\n  % ./fetch --output example.zip http://example.com/source.zip\n  Output file: example.zip\n\n  % ./fetch http://example.com/source.zip --output\n  Output file: true\n\n  % ./fetch --output --fast http://example.com/source.zip\n  Output file: true\n  ```\n\n#### Transforming options (`transform:`)\n\nThe `:transform` parameter specifies how the value should be transformed. It takes any object that responds to `#call`:\n\n```ruby\noption :p, :port, 'set port', argument: :required,\n  transform: -\u003e (x) { Integer(x) }\n```\n\nThe following example uses `#Integer` to transform a string into an integer:\n\n```ruby\noption :p, :port, 'set port', argument: :required, transform: method(:Integer)\n```\n\nThe following example uses a custom object to perform transformation, as well as validation:\n\n```ruby\nclass PortTransformer\n  def call(str)\n    raise ArgumentError unless str.is_a?(String)\n    Integer(str).tap do |int|\n      raise unless (0x0001..0xffff).include?(int)\n    end\n  end\nend\n\noption :p, :port, 'set port', argument: :required, transform: PortTransformer.new\n```\n\nDefault values are not transformed:\n\n```ruby\noption :p, :port, 'set port', argument: :required, default: 8080, transform: PortTransformer.new\n```\n\n#### Options with default values (`default:`)\n\nThe `:default` parameter sets the option value that will be used if the option is passed without an argument or isn't passed at all:\n\n```ruby\noption :a, :animal, 'add animal', default: 'giraffe', argument: :optional\n```\n\nIn the example above, the value for the `--animal` option will be the string\n`\"giraffe\"`, unless otherwise specified:\n\n```\nOPTIONS\n    -a --animal[=\u003cvalue\u003e]      add animal (default: giraffe)\n```\n\nIf the option is not given on the command line, the `options` hash will not have key for this option, but will still have a default value:\n\n```ruby\noption :a, :animal, 'add animal', default: 'giraffe', argument: :required\n\nrun do |opts, args, cmd|\n  puts \"Animal = #{opts[:animal]}\"\n  puts \"Option given? #{opts.key?(:animal)}\"\nend\n```\n\n```sh\n% ./run --animal=donkey\nAnimal = donkey\nOption given? true\n\n% ./run --animal=giraffe\nAnimal = giraffe\nOption given? true\n\n% ./run\nAnimal = giraffe\nOption given? false\n```\n\nThis can be useful to distinguish between an explicitly-passed-in value and a default value. In the example above, the `animal` option is set to `giraffe` in the second and third cases, but it is possible to detect whether the value is a default or not.\n\n#### Multivalued options (`multiple:`)\n\nThe `:multiple` parameter allows an option to be specified more than once on the command line. When set to `true`, multiple option valus are accepted, and the option values will be stored in an array.\n\nFor example, to parse the command line options string `-o foo.txt -o bar.txt`\ninto an array, so that `options[:output]` contains `[ 'foo.txt', 'bar.txt' ]`,\nyou can use an option definition like this:\n\n```ruby\noption :o, :output, 'specify output paths', argument: :required, multiple: true\n```\n\nThis can also be used for flags (options without arguments). In this case, the\nlength of the options array is relevant.\n\nFor example, you can allow setting the verbosity level using `-v -v -v`. The\nvalue of `options[:verbose].size` would then be the verbosity level (three in\nthis example). The option definition would then look like this:\n\n```ruby\nflag :v, :verbose, 'be verbose (use up to three times)', multiple: true\n```\n\n#### Skipping option parsing\n\nIf you want to skip option parsing for your command or subcommand, you can add\nthe `skip_option_parsing` method to your command definition and everything on your\ncommand line after the command name will be passed to your command as arguments.\n\n```ruby\ncommand = Cri::Command.define do\n  name        'dostuff'\n  usage       'dostuff [args]'\n  aliases     :ds, :stuff\n  summary     'does stuff'\n  description 'This command does a lot of stuff, but not option parsing.'\n\n  skip_option_parsing\n\n  run do |opts, args, cmd|\n    puts args.inspect\n  end\nend\n```\n\nWhen executing this command with `dostuff --some=value -f yes`, the `opts` hash\nthat is passed to your `run` block will be empty and the `args` array will be\n`[\"--some=value\", \"-f\", \"yes\"]`.\n\n### Argument parsing\n\nCri supports parsing arguments, as well as parsing options. To define the\nparameters of a command, use `#param`, which takes a symbol containing the name\nof the parameter. For example:\n\n```ruby\ncommand = Cri::Command.define do\n  name        'publish'\n  usage       'publish filename'\n  summary     'publishes the given file'\n  description 'This command does a lot of stuff, but not option parsing.'\n\n  flag :q, :quick, 'publish quicker'\n  param :filename\n\n  run do |opts, args, cmd|\n    puts \"Publishing #{args[:filename]}…\"\n  end\nend\n```\n\nThe command in this example has one parameter named `filename`. This means that\nthe command takes a single argument, named `filename`.\n\nAs with options, parameter definitions take `transform:`, which can be used for transforming and validating arguments:\n\n```ruby\nparam :port, transform: method(:Integer)\n```\n\n(_Why the distinction between argument and parameter?_ A parameter is a name, e.g. `filename`, while an argument is a value for a parameter, e.g. `kitten.jpg`.)\n\n### Allowing arbitrary arguments\n\nIf no parameters are specified, Cri performs no argument parsing or validation;\nany number of arguments is allowed.\n\n```ruby\ncommand = Cri::Command.define do\n  name        'publish'\n  usage       'publish [filename...]'\n  summary     'publishes the given file(s)'\n  description 'This command does a lot of stuff, but not option parsing.'\n\n  flag :q, :quick, 'publish quicker'\n\n  run do |opts, args, cmd|\n    args.each do |arg|\n      puts \"Publishing #{arg}…\"\n    end\n  end\nend\n```\n\n```bash\n% my-tool publish foo.zip bar.zip\nPublishing foo.zip…\nPublishing bar.zip…\n%\n```\n\n### Forbidding any arguments\n\nTo explicitly specify that a command has no parameters, use `#no_params`:\n\n```ruby\nname        'reset'\nusage       'reset'\nsummary     'resets the site'\ndescription '…'\nno_params\n\nrun do |opts, args, cmd|\n  puts \"Resetting…\"\nend\n```\n\n```\n% my-tool reset x\nreset: incorrect number of arguments given: expected 0, but got 1\n% my-tool reset\nResetting…\n%\n```\n\nA future version of Cri will likely make `#no_params` the default behavior.\n\n### The run block\n\nThe last part of the command defines the execution itself:\n\n```ruby\nrun do |opts, args, cmd|\n  stuff = opts.fetch(:stuff, 'generic stuff')\n  puts \"Doing #{stuff}!\"\n\n  if opts[:more]\n    puts 'Doing it even more!'\n  end\nend\n```\n\nThe +Cri::CommandDSL#run+ method takes a block with the actual code to\nexecute. This block takes three arguments: the options, any arguments passed\nto the command, and the command itself.\n\n### The command runner\n\nInstead of defining a run block, it is possible to declare a class, the _command runner_ class that will perform the actual execution of the command. This makes it easier to break up large run blocks into manageable pieces.\n\n```ruby\nname 'push'\noption :f, :force, 'force'\nparam :filename\n\nclass MyRunner \u003c Cri::CommandRunner\n  def run\n    puts \"Pushing #{arguments[:filename]}…\"\n    puts \"… with force!\" if options[:force]\n  end\nend\n\nrunner MyRunner\n```\n\nTo create a command runner, subclass `Cri::CommandRunner`, and define a `#run` method with no params. Inside the `#run` block, you can access `options` and `arguments`. Lastly, to connect the command to the command runner, call `#runner` with the class of the command runner.\n\nHere is an example interaction with the example command, defined above:\n\n```\n% push\npush: incorrect number of arguments given: expected 1, but got 0\n\n% push a\nPushing a…\n\n% push -f\npush: incorrect number of arguments given: expected 1, but got 0\n\n% push -f a\nPushing a…\n… with force!\n```\n\n### Subcommands\n\nCommands can have subcommands. For example, the `git` command-line tool would be\nrepresented by a command that has subcommands named `commit`, `add`, and so on.\nCommands with subcommands do not use a run block; execution will always be\ndispatched to a subcommand (or none, if no subcommand is found).\n\nTo add a command as a subcommand to another command, use the\n`Cri::Command#add_command` method, like this:\n\n```ruby\nroot_cmd.add_command(cmd_add)\nroot_cmd.add_command(cmd_commit)\nroot_cmd.add_command(cmd_init)\n```\n\nYou can also define a subcommand on the fly without creating a class first\nusing `Cri::Command#define_command` (name can be skipped if you set it inside\nthe block instead):\n\n```ruby\nroot_cmd.define_command('add') do\n  # option ...\n  run do |opts, args, cmd|\n    # ...\n  end\nend\n```\n\nYou can specify a default subcommand. This subcommand will be executed when the\ncommand has subcommands, and no subcommands are otherwise explicitly specified:\n\n```ruby\ndefault_subcommand 'compile'\n```\n\n### Loading commands from separate files\n\nYou can use `Cri::Command.load_file` to load a command from a file.\n\nFor example, given the file _commands/check.rb_ with the following contents:\n\n```ruby\nname        'check'\nusage       'check'\nsummary     'runs all checks'\ndescription '…'\n\nrun do |opts, args, cmd|\n  puts \"Running checks…\"\nend\n```\n\nTo load this command:\n\n```ruby\nCri::Command.load_file('commands/check.rb')\n```\n\n`Cri::Command.load_file` expects the file to be in UTF-8.\n\nYou can also use it to load subcommands:\n\n```ruby\nroot_cmd = Cri::Command.load_file('commands/nanoc.rb')\nroot_cmd.add_command(Cri::Command.load_file('commands/comile.rb'))\nroot_cmd.add_command(Cri::Command.load_file('commands/view.rb'))\nroot_cmd.add_command(Cri::Command.load_file('commands/check.rb'))\n```\n\n#### Automatically inferring command names\n\nPass `infer_name: true` to `Cri::Command.load_file` to use the file basename as the name of the command.\n\nFor example, given a file _commands/check.rb_ with the following contents:\n\n```ruby\nusage       'check'\nsummary     'runs all checks'\ndescription '…'\n\nrun do |opts, args, cmd|\n  puts \"Running checks…\"\nend\n```\n\nTo load this command and infer the name:\n\n```ruby\ncmd = Cri::Command.load_file('commands/check.rb', infer_name: true)\n```\n\n`cmd.name` will be `check`, derived from the filename.\n\n## Contributors\n\n- Bart Mesuere\n- Ken Coar\n- Tim Sharpe\n- Toon Willems\n\nThanks for Lee “injekt” Jarvis for [Slop](https://github.com/injekt/slop),\nwhich has inspired the design of Cri 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenisdefreyne%2Fcri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdenisdefreyne%2Fcri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenisdefreyne%2Fcri/lists"}