command_kit

Description

A Ruby toolkit for building clean, correct, and robust CLI commands as plain-old Ruby classes.

Features

  • Supports defining commands as Classes.
  • Supports defining options and arguments as attributes.
  • Supports extending commands via inheritance.
  • Supports subcommands (explicit or lazy-loaded) and command aliases.
  • Correctly handles Ctrl^C and SIGINT interrupts (aka exit 130).
  • Correctly handles broken pipes (aka mycmd | head).
  • Correctly handles when stdout or stdin is redirected to a file.
  • Uses OptionParser for POSIX option parsing.
  • Supports optionally displaying a man-page instead of --help (see CommandKit::Help::Man).
  • Supports optional ANSI coloring.
  • Supports interactively prompting for user input.
  • Supports easily detecting the terminal size.
  • Supports paging output with less or more.
  • Supports XDG directories (~/.config/, ~/.local/share/, ~/.cache/).
  • Easy to test (ex: MyCmd.main(arg1, arg2, options: {foo: foo}) # => 0)

API

Anti-Features

  • No additional runtime dependencies.
  • Does not implement it's own option parser.
  • Not named after a comic-book Superhero.

Examples

lib/foo/cli/my_cmd.rb

require 'command_kit'

module Foo
  module CLI
    class MyCmd < CommandKit::Command

      usage '[OPTIONS] [-o OUTPUT] FILE'

      option :count, short: '-c',
                     value: {
                       type: Integer,
                       default: 1
                     },
                     desc: "Number of times"

      option :output, short: '-o',
                      value: {
                        type: String,
                        usage: 'FILE'
                      },
                      desc: "Optional output file"

      option :verbose, short: '-v', desc: "Increase verbose level" do
        @verbose += 1
      end

      argument :file, required: true,
                      usage: 'FILE',
                      desc: "Input file"

      examples [
        '-o path/to/output.txt path/to/input.txt',
        '-v -c 2 -o path/to/output.txt path/to/input.txt',
      ]

      description 'Example command'

      def initialize(**kwargs)
        super(**kwargs)

        @verbose = 0
      end

      def run(file)
        puts "count=#{options[:count].inspect}"
        puts "output=#{options[:output].inspect}"
        puts "file=#{file.inspect}"
        puts "verbose=#{@verbose.inspect}"
      end

    end
  end
end

bin/my_cmd

#!/usr/bin/env ruby

$LOAD_PATH.unshift(File.expand_path('../../lib',__FILE__))
require 'foo/cli/my_cmd'

Foo::CLI::MyCmd.start

--help

Usage: my_cmd [OPTIONS] [-o OUTPUT] FILE

Options:
    -c, --count INT                  Number of times (Default: 1)
    -o, --output FILE                Optional output file
    -v, --verbose                    Increase verbose level
    -h, --help                       Print help information

Arguments:
    FILE                             Input file

Examples:
    my_cmd -o path/to/output.txt path/to/input.txt
    my_cmd -v -c 2 -o path/to/output.txt path/to/input.txt

Example command

Requirements

Install

$ gem install command_kit

Gemfile

gem 'command_kit', '~> 0.1'

Alternatives

Special Thanks

Special thanks to everyone who answered my questions and gave feedback on Twitter.

Copyright (c) 2021 Hal Brodigan

See LICENSE for details.