Giter Site home page Giter Site logo

cli-kit's Introduction

cli-kit

cli-kit is a ruby Command-Line application framework. Its primary design goals are:

  1. Modularity: The framework tries not to own your application, but rather to live on its edges.
  2. Startup Time: cli-kit encourages heavy use of autoloading (and uses it extensively internally) to reduce the amount of code loaded and evaluated whilst booting your application. We are able to achieve a 130ms runtime in a project with 21kLoC and ~50 commands.

cli-kit is developed and heavily used by the Developer Infrastructure team at Shopify. We use it to build a number of internal developer tools, along with cli-ui.

Getting Started

To begin creating your first cli-kit application, run:

gem install cli-kit
cli-kit new myproject

Where myproject is the name of the application you wish to create. Then, you will be prompted to select how the project consumes cli-kit and cli-ui. The available options are:

  • Vendor (faster execution, more difficult to update dependencies)
  • Bundler (slower execution, easier dependency management)

You're now ready to write your very first cli-kit application!

Upgrading to version 5

Version 5 includes breaking changes and significant new features. Notably:

  • autocall is completely removed;
  • A help system and command-line option parsing system is added.

Migrating away from autocall is as simple as replacing autocall(:X) { y } with X = y.

Using the new help system is not yet well-documented, but some hints can be found in the gen/ilb/gen/commands directory of this repo. Existing commands in existing apps should continue to work.

To use the new help system, commands should all define invoke instead of call. invoke takes name as before, but the first argument is now an instance of Opts, defined in the command class, which must be a subclass of CLI::Kit::Opts. For example:

class MyCommand < CLI::Kit::BaseCommand
  class Opts < CLI::Kit::Opts
    def force
      flag(short: '-f', long: '--force', description: 'Force the operation')
    end
  end
  def invoke(op, _name)
    if op.force
      puts 'Forcing the operation'
    else
      puts 'Not forcing the operation'
    end
  end
end

This particular structure was chosen to allow the code to by fully typed using sorbet, as invoke can be tagged with sig { params(op: Opts, _name: String).void }, and the #force method visibly exists.

-h/--help is installed as a default flag, and running this command with --help is handled before reaching #invoke, to print a help message (for which several other class methods on BaseCommand provide text: command_name, desc, long_desc, usage, example, and help_sections). See gen/lib/gen/commands/new.rb for an example.

How do cli-kit Applications Work?

The executable for your cli-kit app is stored in the "exe" directory. To execute the app, simply run:

./exe/myproject

Folder Structure

  • /exe/ - Location of the executables for your application.
  • /lib/ - Location of the resources for your app (modules, classes, helpers, etc).
    • myproject.rb - This file is the starting point for where to look for all other files. It configures autoload for the app.
    • myproject/ - Stores the various commands/entry points.
      • entry_point.rb - This is the file that is first called from the executable. It handles loading and commands.
      • commands.rb - Registers the various commands that your application is able to handle.
      • commands/ - Stores Ruby files for each command (help, new, add, etc).

Adding a New Command to your App

Registering the Command

Let's say that you'd like your program to be able to handle a specific task, and you'd like to register a new handler for the command for that task, like myproject add to add 2 numbers, like in a calculator app. To do this, open /lib/myproject/commands.rb. Then, add a new line into the module, like this:

register :Add, 'add', 'myproject/commands/add'

The format for this is register :<CommandClass>, '<command-at-cli>', '<path/to/command.rb>'

Creating the Command Action

The action for a specific command is stored in its own Ruby file, in the /lib/myproject/commands/ directory. Here is an example of the add command in our previous to-do app example:

require 'myproject'

module Myproject
  module Commands
    class Add < Myproject::Command
      def call(args, _name)
        # command action goes here
      end

      def self.help
        # help or instructions go here
      end
    end
  end
end

The call(args, _name) method is what actually runs when the myproject add command is executed.

  • Note: The args parameter represents all the arguments the user has specified.

Let's say that you are trying to compute the sum of 2 numbers that the user has specified as arguments. For example:

def call(args, _name)
  sum = args.map(&:to_i).inject(&:+)
  puts sum
end

Getting Help

Above, you'll notice that we also have a self.help method. This method is what runs when the user has incorrectly used the command, or has requested help. For example:

def self.help
  "Print the sum of 2 numbers.\nUsage: {{command:#{Myproject::TOOL_NAME} add}} 5 7"
end

User Interfaces

cli-kit also features cli-ui, another gem from us here at Shopify, which allows for the use of powerful command-line user interface elements. For more details on how to use cli-ui, visit the cli-ui repo.

Examples

cli-kit's People

Contributors

a-chacon avatar andyw8 avatar burke avatar chrisseaton avatar claudiob avatar cursedcoder avatar dependabot-preview[bot] avatar dependabot[bot] avatar donk-shopify avatar george-ma avatar goodforonefare avatar honkfestival avatar joshheinrichs-shopify avatar jules2689 avatar lavoiesl avatar lfroms avatar mistydemeo avatar paulomarg avatar sambostock avatar shanesmith avatar simon-shopify avatar thegedge avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cli-kit's Issues

Questions about `option` and `option!`

I'm trying to code a new command line using cli-kit, and I was wondering if I'm correctly understandingoption and option!.
My understanding is option is an optional parameter, while option! is a required parameter.
But looking at the code they are both not required (here and here).

Also, if I try to set a non Nil default with option, I have the following error message:

ArgumentError: declare options with non-nil defaults using `option!` instead of `option`

I guess it should be option instead of option!.

Let me know if I misunderstood how it works, and also how I can use something same as option with required

Add a default Help command

Pretty much every command-line tool needs help, so perhaps we could provide a template command that can be incorporated into projects and provide a sane default set of help. My experience is that CLI tools often have both short-form and long-form descriptions.

The short-form description gets used generally when showing commands in a list (e.g., mytool help showing all commands and a short description). The long-form will be shown in help for just that command (e.g., mytool help mycommand).

Here's an incredibly rough sketch:

module CLI
  module Kit
    class Help < CLI::Kit::BaseCommand
      def call(args, _command_name)
        if args.empty?
          all_commands.each { |_command_name, command| puts CLI::UI.fmt(command.short_description) }
        else
          command = all_commands.find { |command_name, _command| command_name == args.first }
          puts CLI::UI.fmt(command.long_description) if command
          # else error message for unknown command
        end
      end

      # The subclassed help command will provide the list of all commands
      def all_commands
        raise NotImplementedError
      end
    end
  end
end

Allow configurable description length

Instead of raising an exception if a description is over 80 characters long, what do you think of either:

  • allowing the maximum length to be configured by the user (defaulted to 80)
  • cropping the description to the first 80 characters with an ellipsis at the end

if desc.size > 80
raise(ArgumentError, 'description must be 80 characters or less')

The stats code was removed in v5 causing stats to silently stop working

This commit removed the stats support from base_command.rb with a comment that it is unused.

However, this is a base class that you derive from to build commands, so it's actually part of its public interface - it may not be used directly in the project but it certainly could (and is!) used by downstream projects (at least, it is in ours!).

Upgrading to this version resulted in our CLI tooling silently stopping sending metrics.

To restore this, I've manually reverted the changes to base_command.rb in our vendored copy, but wanted to open a discussion about what a long term solve could be?

cc: @lugray

system output without format

Context

We are using the CLI::Kit::System helper to run commands from our project. One of those commands that we execute outputs the logs using colors and some other formatting.

What

We expect the output from system to keep the format, however, it outputs the logs with no formatting.

Might it be related to the encoding that the system method uses internally?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.