A simple Ruby command-line application skeleton

To write a command-line application in Ruby is very simple, the following two-line application converts everything in the standard input to upper case and then outputs it:
#!/usr/bin/env ruby
puts STDIN.read.upcase
Although complete, this is hardly a proper application, which should include options, arguments, help, input error trapping, etc. I've created a skeleton for such a command-line application.
Hoe and Gems
I intentionally put the entire application in one file, to make it easy for you to distribute. With this setup, your users only need the file in their path, and Ruby installed; no gems or anything else are required
Although a single file is convenient, it is proper to package your Ruby application as a gem, and distribute it via RubyForge. This way, someone can install your application with gem install YourApp.
If your users will have gems installed and you prefer to distribute your application that way, I recommend using the Hoe project to setup your directory structure:
sudo gem install hoe sow your_app
You can also distribute your application in a variety of other ways, such as apt-get in Debian/Ubuntu or Ports in OS X/BSD. I chose the simplest route to start, which is one file, in your path.
Helping your users
There are many ways to provide help to your users, including simply puts "Your help...". The one I prefer is to use rdoc/usage , which uses the RDoc documentation you should already have in your code. This way, you only have to document your options once. RDoc documentation is very readable in the actual code, as well as when it's rendered to HTML.
In the following example, I list all the sections of my help using RDoc notation:
# == Synopsis
# This is a sample description of the application.
# Blah blah blah.
#
# == Examples
# This command does blah blah blah.
# ruby_cl_skeleton foo.txt
#
# Other examples:
# ruby_cl_skeleton -q bar.doc
# ruby_cl_skeleton --verbose foo.html
#
# == Usage
# ruby_cl_skeleton [options] source_file
#
# For help use: ruby_cl_skeleton -h
#
# == Options
# -h, --help Displays help message
# -v, --version Display the version, then exit
# -q, --quiet Output as little as possible, overrides verbose
# -V, --verbose Verbose output
# TO DO - add additional options
#
# == Author
# YourName
#
# == Copyright
# Copyright (c) 2007 YourName. Licensed under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
To create RDoc documentation from these comments use the following command:
rdoc my_file
The documentation that is produced can be seen here.
You can output help, to your users at runtime, using the RDoc documentation, with the following method:
RDoc::usage() #exits app
This will format your RDoc comments as plain text, output it, then close the application.
If you just want to output the usage section, you can do so with the following method call:
RDoc::usage('usage')
Many options for options
There are many ways to parse options (also known as a switch, flag, or parameter). I chose to use OptionParser. To parse basic options is as simple as the following:
opts = OptionParser.new
opts.on('-v', '--version') {output_version ; exit 0 }
opts.on('-h', '--help') {output_help }
opts.on('-V', '--verbose') {@options.verbose = true }
opts.on('-q', '--quiet') {@options.quiet = true }
# TO DO - add additional options
opts.parse!(@arguments)
You can use OptionParser to parse complex options also, including mandatory options, a list of values {start | stop | restart}, boolean switches --[no-]verbose, etc.
I chose to store my options using an OpenStruct object, which is a very convenient and clever alternative to a hash. I initialize them like so:
# Set defaults
@options = OpenStruct.new
@options.verbose = false
@options.quiet = false
# TO DO - add additional defaults
Creating a command-line application
To create your application, follow these steps:
- Make a copy of the skeleton code
- Make your file executable ( chmod +x your_file )
- Replace all instances of ruby_cl_skeleton with your application name
- Locate all the "# TO DO"s and make your changes
- Insert your functionality
- Distribute your application by placing the file anywhere in the user's path ( ~/bin /usr/bin )
A nice addition would be to make a rails type build command that creates the file for you, inserting the application name, etc.
Grab the skeleton code below. Happy coding.
Plain text file here.
#!/usr/bin/env ruby
# == Synopsis
# This is a sample description of the application.
# Blah blah blah.
#
# == Examples
# This command does blah blah blah.
# ruby_cl_skeleton foo.txt
#
# Other examples:
# ruby_cl_skeleton -q bar.doc
# ruby_cl_skeleton --verbose foo.html
#
# == Usage
# ruby_cl_skeleton [options] source_file
#
# For help use: ruby_cl_skeleton -h
#
# == Options
# -h, --help Displays help message
# -v, --version Display the version, then exit
# -q, --quiet Output as little as possible, overrides verbose
# -V, --verbose Verbose output
# TO DO - add additional options
#
# == Author
# YourName
#
# == Copyright
# Copyright (c) 2007 YourName. Licensed under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
# TO DO - replace all ruby_cl_skeleton with your app name
# TO DO - replace all YourName with your actual name
# TO DO - update Synopsis, Examples, etc
# TO DO - change license if necessary
VERSION = '0.0.1'
attr_reader :options
@arguments = arguments
@stdin = stdin
# Set defaults
@options = OpenStruct.new
@options.verbose = false
@options.quiet = false
# TO DO - add additional defaults
end
# Parse options, check arguments, then process the command
if parsed_options? && arguments_valid?
puts "Start at #{DateTime.now}\\n\\n" if @options.verbose
output_options if @options.verbose # [Optional]
process_arguments
process_command
puts "\\nFinished at #{DateTime.now}" if @options.verbose
else
output_usage
end
end
protected
# Specify options
opts = OptionParser.new
opts.on('-v', '--version') {output_version ; exit 0 }
opts.on('-h', '--help') {output_help }
opts.on('-V', '--verbose') {@options.verbose = true }
opts.on('-q', '--quiet') {@options.quiet = true }
# TO DO - add additional options
opts.parse!(@arguments) rescue return false
process_options
true
end
# Performs post-parse processing on options
@options.verbose = false if @options.quiet
end
puts "Options:\\n"
@options.marshal_dump.each do |name, val|
puts " #{name} = #{val}"
end
end
# True if required arguments were provided
# TO DO - implement your real logic here
true if @arguments.length == 1
end
# Setup the arguments
# TO DO - place in local vars, etc
end
output_version
RDoc::usage() #exits app
end
RDoc::usage('usage') # gets usage from comments above
end
puts "#{File.basename(__FILE__)} version #{VERSION}"
end
# TO DO - do whatever this app does
#process_standard_input # [Optional]
end
input = @stdin.read
# TO DO - process input
# [Optional]
# @stdin.each do |line|
# # TO DO - process each line
#end
end
end
# TO DO - Add your Modules, Classes, etc
# Create and run the application
app = App.new(ARGV, STDIN)
app.run



Hi,
I've written something similar some time back about one-off parser scripts: see saaientist.blogspot.com/2007/07/documenting-one-off-parsers.html for another example of a skeleton script. It also uses OpenStruct and RDoc::usage. It's interesting to see you using an App class, though. I might use that in the future as well.
jan.
The cmdparse gem is pretty awesome. I'd recommend trying that out.
I might use that in the future as well.
To make your script more platform independent it would be good to add:
require 'date'
Ruby on Ubuntu Linux doesn't automatically load that library so any DateTime.now call will fail. Well, I should say my ubuntu installation doesn't automatically do it.
Oh, I almost completely forgot:
Thanks for taking the time to write this! It saved me tons of time and got me off in the right direction, instead of the dirty script direction.
Thanks Matt, for noting the "require 'date'", I've updated the code.
You're welcome, I'm glad you found it useful.
Hi Todd, thanks for this article. I've added credits to your name in the README of a quick little command line app I wrote. See here: github.com/djones/pound-append/tree/master
David, you're welcome. Very cool, I appreciate the credits, it's always nice to see people actually getting some use out of what you create.
Thanks, this is really useful.
hi. i am tryin to interface ruby with xfst that operates on the command line. however, the system command executes without any visible effect on the text files created to store the output. how can i remedy this
Todd,
I tried using your code in a script I'm writing, but nothing except '-h' works. Everything else just prints out the usage statement. I thought maybe I'd messed something up in copying your code, so just created a new file with your code above in it and that's it...and I get the same results.
Using '-V' doesn't return what I think it should, which is the timestamps and list of options used...it just returns the "Usage" message.
Matt,
Check your arguments_valid?
Hi Todd
Thanks so much for this. Its really useful, especially for rapid prototyping ideas.
If I ever manage to build a decent app I will be sure to credit you to.
Many thanks,
Ben...
So if you do use gems in our script what is the proper way to package/deploy a simple script with dependend gems? Can I create a single install package? Do I have to manually install the gems on each workstation?
Jason,
The proper way is to make a gem, which has dependency management. You can install a gem from disk, rather than a repository like RubyForge, and RubyGems handles installing all required gems. Usually if a user has ruby installed, they have RubyGems installed also (in OS X, for example, both of these are installed by default in Leopard).
Another simple option is simply copy the code from your required gems into your project (license permitting). You won't get updates for those gems, and if you have multiple projects, each will have their own copy, but this way you can simply copy the folder to all your user's machines.