Climate
Yet another bloody CLI library for ruby, based on, and inspired by, the magnificence that is trollop, with more of a mind for building up a git-like CLI to access your application without enforcing a particular style of project structure.
Designed for both simple and more complex cases.
- Embed a CLI in to your application
- Builds on trollop, a refreshingly sane option parsing library
- N-levels of subcommands
- Nice help output
Easy
Useful for one-shot scripts:
#! /usr/bin/env climate
# the shebang is optional, you can just load the script with
# `ruby -r rubygems -r climate script.rb`
extend Climate::Script
description "Do something arbitrary to a file"
opt :log, "Whether to log to stdout" :default => false
arg :path "Path to input file"
def run
file = File.open(arguments[:path], 'r')
puts("loaded #{file}") if options[:log]
end
Medium
This style is intended for embedding a CLI in to your existing application.
class Parent < Climate::Command('thing')
description "App that does it all, yet without fuss"
opt :log, "Whether to log to stdout" :default => false
end
class Arbitrary < Climate::Command
set_name 'arbitrary'
subcommand_of, Parent
description "Do something arbitrary to a file"
arg :path "Path to input file"
def run
file = File.open(arguments[:path], 'r')
puts("loaded #{file}") if parent.options[:log]
end
end
Climate.with_standard_exception_handling do
Parent.run(ARGV)
end
There is a working example, example.rb
that you can test out with
ruby -rrubygems -rclimate example.rb --log /tmp/file
or
ruby -rrubygems -rclimate example.rb --help
rack-middleware like filtering
Quite often in commands you might want some shared logic to do with setting up or tearing down an environment for your child commands. For instance, you might want to setup an application environment, start a database transaction, or check some arguments are semantically correct in a way that is common based on a parent command, i.e:
class Parent < Climate::Command('parent')
def run(chain)
setup_logger
begin
chain.run
rescue => e
logger.error("oh noes!")
raise
end
end
end
class Child < Climate::Command('child')
subcommand_of Parent
def run
if all_ok?
do_the_thing
else
raise 'it went badly'
end
end
end
The run
method on non-leaf commands can also omit the chain argument, in which
case you are not responsible for calling the next link in the chain, but you can
still do some setup.
Accessing your parent command at execution time
In the case where you have some parent command that gets all the top level
options for your application, (i.e. config location, log mode), you may need to
access this from your child commands. You can do this with the ancestor
instance method, or by proxying the method to your child class with
expose_ancestor_method
. For example:
class Parent < Climate::Command('parent')
def useful_stuff ; end
def handy_service ; end
end
class Child < Climate::Command('child')
subcommand_of Parent
expose_ancestor_method Parent, :handy_service
def run
# finds the parent command instance so you can interrogate it
ancestor(Parent).useful_stuff
# handy_service was defined as an instance method using the
# #expose_ancestor_method above, but ultimately it is syntactic sugar
# for the above
handy_service.send_the_things
end
end