Git-Like Interface Command Line Parser

Author

Dave Copeland (davetron5000 at g mail dot com)

Copyright

Copyright © 2009 by Dave Copeland

License

Distributes under the Apache License, see LICENSE.txt in the source distro

This is a DSL you can use to create a command line interface like git, gem or svn, in that the first argument is a command, and there are global and command specific flags.

Use

Install if you need to:

sudo gem install gli

The simplest way to get started is to create a scaffold project

gli init my_proj command_name other_command_name

This will create a basic scaffold project in ./my_proj with:

  • executable in ./my_proj/bin/my_proj. This file demonstrates most of what you need to describe your command line interface.

  • an empty test in ./my_proj/test/tc_nothing.rb that can bootstrap your tests

  • a gemspec shell

  • a README shell

  • Rakefile that can generate RDoc, package your Gem and run tests

Example

This example demonstrates most of the features of GLI.

This sets you up to use the DSL that GLI defines:

#!/usr/bin/ruby
$: << File.expand_path(File.dirname(__FILE__) + '/../lib') 

require 'gli'

include GLI

This sets a description of your program. This can be as long as you want.

program_description 'Support program for bootstrapping GLI-based programs'

This sets a config file for your program. The config file can be used to store default values for command line options and command-specific options on a per-user (or per-site) basis. The format is YAML-based.

Using an absolute path will result in the configuraiton file being located there. Without an absolute path, the file will be located relative to the current user’s home directory (which is what is being done here).

config_file '.glirc'

the configuration file This describes a command line switch “-n” that is global to all commands and specified before the command name on the command line.

desc 'Dry run; don\'t change the disk'
switch :n

The following describes a command line flag that is global and has a default value of ‘.’. It also specifies a short description of its argument. This is used to print command line help. Note that we have specified two different aliases for this flag. -r (because it is listed first) is the default one and --root (note two-dash syntax) is also supported. This means that -r some_dir and --root=some_dir mean the same thing to the application.

desc 'Root dir in which to create project'
long_desc 'This is the location where your project ill be created.  A subdirectory named for your project will be created here, and THAT directory will contain the generated files'
default_value '.'
arg_name 'root_dir'
flag [:r,:root]

Here we specify a command. Inside the block we can use the same sorts of things as we did above to define flags and switches specific to the command. These must come after the command name. Also note that we use arg_name here to describe the arguments this command accepts.

desc 'Create a new GLI-based project'
arg_name 'project_name [command[ command]*]'
command [:init,:scaffold] do |c|

  c.desc 'Create an ext dir'
  c.switch [:e,:ext]

  c.desc 'Overwrite/ignore existing files and directories'
  c.switch [:force]

Here we specify the actual actions to take when the command is executed. We define a block that will be given the global options (as a Hash), the command-specific options (as a hash) and the command line arguments

  c.action do |global_options,options,args|
    if args.length < 1
      raise 'You must specify the name of your project'
    end
    Scaffold.create_scaffold(g[:r],!o[:notest],o[:e],args[0],args[1..-1],o[:force],g[:n])
  end
end

You can also specify some global code to run before, after and on errors:

pre do |global_options,command,options,args|
  puts "After parsing, but before #{command.name} is run"
  return true
  # return false if we want to skip command execution for some reason
end

post do |global_options,command,options,args|
  puts "After successful execution of #{command.name}"
end

on_error do |ex|
  puts "We got an error"
  return true    # does the standard error handling code
  # return false # this would skip standard error handling code
end

Now, we run the program using the arguments the user provided on the command line

run(ARGV)

Note that by using gli init you can create a shell with all of this already there for you.

What this gives you:

  • A reasonably useful help system. your_program help will list all the global options and commands (along with command aliases) and your_program help command_name will list help for that given command.

  • Error handling when flags do not receive arguments or unknown flags or switches are given

  • Error handling when an unknown command is specified

  • Default values for flags if they are not specified by the user (switches all default to false)

  • An easy way to allow location-specific defaults for options via a config file for your app

What this doesn’t give you:

  • A way to indicate required flags

  • A way to indicate a required argument or required number of arguments

  • A way to do default switches to ‘true’ and therefore accept things like --no-force

  • A way to have repeated flags turn into an array or other type-transforming things

Configuration File

The configuration file format is a very simple means of customizing the execution of your command on a per-user or per-site basis. The idea is that commonly used values that aren’t the commands’ default can be stored in the configuration file so that users do not need to specify them on the command line. The search order for the value of a particular flag then becomes:

  1. Command line invocation

  2. Configuration File value

  3. Default value in the application

Note that since there is no way to switch off switches, setting them to default to true in the configuration file cannot be “undone” on the command line. A future version may allow this.

The configuration file format is YAML based and can be bootstrapped via the initconfig command to your application.

This command is automatically created and added to your application’s commands when you declare that there is a config file. When invoked, all global options set on the command line are configured inside the configuration file. Further, a blank area for each command of your application is created, to allow the user edit the config file ith command-specific default values.

--- 
# Global options are here
:f: foo
:g: blah
# Command-specific options are under 'commands'
commands: 
  # defaults for the "doit" command
  :doit: 
    :g: bar
    :s: true
  # defaults for the "gonow" command
  :gonow: 
    :g: foobar
    :f: barfoo

This allows you to design your application to have it’s behavior entirely affected by command line options, with sensible defaults stored in a configuration file.

Reference

action

Specify the action to take when a command is executed from the command line. This is only usable in a command block on the command object (e.g. c.action). This takes a block that yields three parameters: a hash of global options specified on the commandline, a hash of command-specific options specified on the command line, and an array of arguments parsed after the options were set on the command line. So, a command like git --git-dir=/tmp commit -a -m 'Foo bar' foo.c bar.c would result in the global hash containing :'git-dir' => '/tmp', the options hash containing :a => true, :m => 'Foo bar' and the arguments array being ['foo.c', 'bar.c']

arg_name

Describe the name of the argument to the next flag or command. This can be used at the global level or inside a command block on the command object (e.g. c.arg_name)

config_file

Name the configuration file for your applicaiton. This can either be an absolute path to where the applicaiton will find the configuration file, or a relative path, that will be interpretted as relative to the user’s home directory. Default is nil, which means no configuration file will be used. Declaring this creates a special initconfig command that can bootstrap this configuration file for your users.

command

Declare a command. This takes a symbol or array of symbols and a block. The block yields one argument, the command itself.

default_value

Indicate the default value of the next flag. This can be used at the global level or inside a command block on the command object (e.g. c.default_value)

desc

Describe the next flag, switch, or command you will declare. This can be used at the global level or inside a command block on the command object (e.g. c.desc)

flag

Declare a flag, which is a command line switch that takes an argument. This takes either a symbol or an array of symbols. The first symbol decared is used in your program to determine the flag’s value at runtime. This can be used at the global level or inside a command block on the command object (e.g. c.flag)

long_desc

Provide a more lengthy description of the next flag, switch, or command you will declare. This will appear in command line output for commands when you get help for a command. For flags and switches, this will only appear in the generated rdoc and not on the command line. This can be used at the global level or inside a command block on the command object (e.g. c.long_desc)

on_error

Declare an error handling routine that will be called if any command (or other GLI processing) encouters an exception. This is a block that will receive the exception that was caught. All exceptions are routed through this block. If the block evaluates to true, the built-in error handling will be called after, otherwise, nothing will happen.

post

Declare code to run after every command that didn’t experience an error. This is not available inside a command block. This takes a block that will receive four arguments: the global argument hash (as in action), the command (instance of Command), the command-specific options (as in action, and the parsed command line arguments (as in action).

pre

Declare code to run before every command. This is not available inside a command block. This takes a block that will receive four arguments: the global argument hash (as in action), the command (instance of Command), the command-specific options (as in action, and the parsed command line arguments (as in action). If this block evaluates to false, the command will not be executed and the program will stop.

switch

Declare a switch, which is a command-line switch taking no argument that indicates a boolean “true” when specified on the command line. This takes either a symbol or array of symbols. The first symbol declared is used in your program to determine if the switch was set. This can be used at the global level or inside a command block on the command object (e.g. c.switch)

Interface Generated

The command line interface that is created with the GLI DSL is:

executable global options and flags command command specific options and flags ‘arguments`

switch

a command line control string that takes no argument. The -l in ls -l

flag

a switch that takes an argument. The -d' ' in cut -d' ' file

command

the command to execute. The rebase in git rebase

arguments

Anything that’s not a switch, flag, or command. The main.c in git add main.c

Switches

Switches can be specified one at a time in either a long or short format:

git add -i
git add --interactive

Switches can also be combined in their short form:

ls -l -a    
ls -la

Flags

Flags can be specified in long or short form, and with or without an equals:

git merge -s resolve
git merge --strategy=resolve

Stop Switch

A -- at any time stops processing and sends the rest of the argument to the command as arguments, even if they start with a “–”

:include:gli.rdoc