/methadone

Kick the bash habit - start your command line scripts off right in Ruby

Primary LanguageRubyApache License 2.0Apache-2.0

Methadone - kick the bash habit and start your command line apps off right

Author

Dave Copeland (davetron5000 at g mail dot com)

Copyright

Copyright © 2011 by Dave Copeland

License

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

A smattering of tools to make your command-line apps easily awesome; kick the bash habit without sacrificing any of the power.

The overall goal of this project is to allow you to write a command-line app in Ruby that is as close as possible to the expedience and concisenes of bash, but with all the power of Ruby available when you need it.

Currently, this library is under development and has the following to offer:

  • Bootstrapping a new CLI app

  • Lightweight DSL to structure your bin file

  • Utility Classes

    • Methadone::CLILogger - a logger subclass that sends messages to standard error standard out as appropriate

    • Methadone::CLILogging - a module that, when included in any class, provides easy access to a shared logger

  • Cucumber Steps

Bootstrapping

The methadone command-line app will bootstrap a new command-line app, setting up a proper gem structure, unit tests, and cucumber-based tests with aruba:

$ methadone --help
Usage: methadone [options] app_name
        --force                      Overwrite files if they exist
$ methadone newgem
$ cd newgem
$ rake
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
1 scenario (1 passed)
3 steps (3 passed)
$ cat features/newgem.feature
Feature: My bootstrapped app kinda works
  In order to get going on coding my awesome app
  I want to have aruba and cucumber setup
  So I don't have to do it myself

  Scenario: App just runs
    When I run `newgem --help`
    Then the exit status should be 0
    And the output should contain:
    """
    Usage: newgem [options]
    """

Basically, this sets you up with all the boilerplate that you should be using to write a command-line app.

DSL for your ‘bin` file

A canonical ‘OptionParser` driven app has a few problems with it structurally that methadone can solve

  • Backwards organization - main logic is at the bottom of the file, not the top

  • Verbose to use opts.on just to set a value in a Hash

  • No exception handling

Methadone gives you a simple,lightweight DSL to help. It’s important to note that we’re taking a light touch here; this is all a thin wrapper around OptionParser and you still have complete access to it if you’d like. We’re basically wrapping up some canonical boilerplate into more expedient code

#!/usr/bin/env ruby

require 'optparse'
require 'methadone'

include Methadone::Main

main do |name,password|
  name # => guaranteed to be non-nil
  password # => nil if user omitted on command line
  options[:switch] # => true if user used --switch or -s
  options[:s]      # => ALSO true if user used --switch or -s
  options[:f]      # => value of FILE if used on command-line
  options[:flag]   # => ALSO value of FILE if used on command-line

  # If something goes wrong, you can just raise an exception
  # or call exit_now! if you want to control the exit status
end

description "One line summary of your awesome app"

on("--[no-]switch","-s","Some switch")
on("-f FILE","--flag","Some flag")
on("-x FOO") do |foo|
  # something more complex; this is exactly OptionParser opts.on
end

arg :name
arg :password, :optional

# If you want to avoid automatic exception catching/logging do:
#
# leak_exceptions true
#
# Note that Methadone::Error exceptions are always caught and logged

go!

go! runs the block you gave to main, passing it the unparsed ARGV as parameters to the block. It will also parse the command-line via OptionParser and do a check on the remaining arguments to see if there’s enough to satisfy the :required args you’ve specified.

Finally, the banner/help string will be full constructed based on the interface you’ve declared. If you don’t accept options, [options] won’t appear in the help. The names of your arguments will appear in proper order and :optional ones will be in square brackets. You don’t have to touch a thing.

Utility Classes

Currently, there are classes the assist in directing output logger-style to the right place; basically ensuring that errors go to STDERR and everything else goes to STDOUT. All of this is, of course, configurable

Examples

Using STDOUT as a log, respecting STDERR

require 'methadone'

include Methadone::CLILogging

command = "rm -rf /tmp/*"
debug("About to run #{command}") # => goes only to STDOUT, no logging format
if system(command)
  info("Succesfully ran #{command}") # => goes only to STDOUT, no logging format
else
  error("There was a problem running #{command}") # => goes only to STDERR, no logging format
end

Using a log file, but respecting STDERR

Here, since we have a logfile, that logfile gets ALL messages and they have the default logger format.

require 'methadone'

include Methadone::CLILogging

self.logger = CLILogger.new("logfile.txt")
command = "rm -rf /tmp/*"
debug("About to run #{command}") # => goes only to logfile.txt, in the logger-style format
if system(command)
  info("Succesfully ran #{command}") # => goes only to logfile.txt, in the logger-style format
else
  error("There was a problem running #{command}") 
  # => goes to logfile.txt in the logger-style format, and
  #    to STDERR in a plain format
end

Cucumber Steps

Methadone uses aruba for BDD-style testing with cucumber. This library has some awesome steps, and methadone provides additional, more opinionated, steps.

Example

Here’s an example from methadone’s own tests:

Scenario: Help is properly documented
  When I get help for "methadone"
  Then the exit status should be 0
  And the following options should be documented:
    |--force|
  And the banner should be present
  And the banner should document that this app takes options
  And the banner should document that this app's arguments are:
    |app_name|which is required|
    |dir_name|which is optional|

Steps Provided

  • Run command_to_run --help using aruba

    When I get help for "command_to_run"
  • Make sure that each option shows up in the help and has some sort of documentation

    Then the following options should be documented:
      |--force|
      |-x     |
  • Check an individual option for documentation:

    Then the option "--force" should be documented
  • Checks that the help has a proper usage banner

    Then the banner should be present
    
  • Checks that the usage banner indicates it takes options via [options]

    Then the banner should document that this app takes options
    
  • Do the opposite; check that you don’t indicate options are accepted

    Then the banner should document that this app takes no options
    
  • Checks that the app’s usage banner documents that its arguments are args

    Then the banner should document that this app's arguments are "args"
  • Do the opposite; check that your app doesn’t take any arguments

    Then the banner should document that this app takes no arguments
    
  • Check for a usage description which occurs after the banner and a blank line

    Then there should be a one-line summary of what the app does

What might be

See the roadmap