cmdargs-0.2: cmdargs.htm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>CmdArgs: Easy Command Line Processing</title>
<style type="text/css">
pre, .cmdargs {
border: 2px solid gray;
padding: 1px;
padding-left: 5px;
margin-left: 10px;
background-color: #eee;
}
pre.define {
background-color: #ffb;
border-color: #cc0;
}
body {
font-family: sans-serif;
}
h1, h2, h3 {
font-family: serif;
}
h1 {
color: rgb(23,54,93);
border-bottom: 1px solid rgb(79,129,189);
padding-bottom: 2px;
font-variant: small-caps;
text-align: center;
}
a {
color: rgb(54,95,145);
}
h2 {
color: rgb(54,95,145);
}
h3 {
color: rgb(79,129,189);
}
p.rule {
background-color: #ffb;
padding: 3px;
margin-left: 50px;
margin-right: 50px;
}
.cmdargs {
display: block;
font-family: monospace;
}
.cmdargs td {
padding: 0px;
margin: 0px;
}
</style>
</head>
<body>
<h1>CmdArgs: Easy Command Line Processing</h1>
<p style="text-align:right;margin-bottom:25px;">
by <a href="http://community.haskell.org/~ndm/">Neil Mitchell</a>
</p>
<p>
<a href="http://community.haskell.org/~ndm/cmdargs/">CmdArgs</a> is a library for defining and parsing command lines. The focus of CmdArgs is allowing the concise definition of fully-featured command line argument processors, in a mainly declarative manner (i.e. little coding needed). CmdArgs also supports multiple mode programs, for example as used in <a href="http://darcs.net/">darcs</a> and <a href="http://haskell.org/cabal/">Cabal</a>.
</p><p>
This document explains how to write the "hello world" of command line processors, then how to extend it with features into a complex command line processor. Finally this document gives three samples, which the <tt>cmdargs</tt> program can run. The three samples are:
</p>
<ol>
<li><tt>hlint</tt> - the <a href="http://community.haskell.org/~ndm/hlint/">HLint</a> program.</li>
<li><tt>diffy</tt> - a program to compare the differences between directories.</li>
<li><tt>maker</tt> - a make style program.</li>
</ol>
<p>
For each example you are encouraged to look at it's source (see the <a href="http://community.haskell.org/~ndm/darcs/hlint">darcs repo</a>, or the bottom of this document) and run it (try <tt>cmdargs hlint --help</tt>). The HLint program is fairly standard in terms of it's argument processing, and previously used the <a href="http://haskell.org/ghc/docs/latest/html/libraries/base/System-Console-GetOpt.html">System.Console.GetOpt</a> library. Using GetOpt required 90 lines and a reasonable amount of duplication. Using CmdArgs the code requires 30 lines, and the logic is much simpler.
</p>
<h3>Acknowledgements</h3>
<p>
Thanks to Kevin Quick for substantial patches, and additional code contributions from Sebastian Fischer.
</p>
<h2>Hello World Example</h2>
<p>
The following code defines a complete command line argument processor:
</p>
<pre>
{-# LANGUAGE DeriveDataTypeable #-}
module Sample where
import System.Console.CmdArgs
data Sample = Sample {hello :: String}
deriving (Show, Data, Typeable)
sample = Sample{hello = def}
main = print =<< cmdArgs sample
</pre>
<p>
To use the CmdArgs library there are three steps:
</p>
<ol>
<li>Define a record data type (<tt>Sample</tt>) that contains a field for each argument. This type needs to have instances for <tt>Show</tt>, <tt>Data</tt> and <tt>Typeable</tt>.</li>
<li>Give a value of that type (<tt>sample</tt>) with default values (<tt>def</tt> is a default value of any type, but I could also have written <tt>""</tt>). This value is turned into a command line by calling the <tt>cmdArgs</tt> function.</li>
</ol>
<p>
Now we have a reasonably functional command line argument processor. Some sample interactions are:
</p>
<pre>
$ runhaskell Sample.hs --hello=world
Sample {hello = "world"}
$ runhaskell Sample.hs --version
The sample program
$ runhaskell Sample.hs --help
The sample program
sample [OPTIONS]
-? --help Display help message
-V --version Print version information
-h --hello=ITEM
</pre>
<p>
CmdArgs uses defaults to automatically infer a command line parser for a value, and provides annotations to override any of the the defaults. CmdArgs automatically supports <tt>--help</tt> and <tt>--version</tt> flags, and optionally supports verbosity flags.
</p>
<h2>Specifying Attributes</h2>
<p>
In order to control the behaviour we can add attributes. For example to add an attribute specifying the help text for the <tt>--hello</tt> argument we can write:
</p>
<pre>
sample = Sample{hello = def &= help "Who to say hello to"}
</pre>
<p>
We can add additional attributes, for example to specify the type of the value expected by hello:
</p>
<pre>
sample = Sample {hello = def &= help "Who to say hello to" &= typ "WORLD"}
</pre>
<p>
Now when running <tt>--help</tt> the final line is:
</p>
<pre>
-h --hello=WORLD Who to say hello to
</pre>
<p>
There are many more attributes, detailed in the <a href="http://hackage.haskell.org/packages/archive/cmdargs/latest/doc/html/System-Console-CmdArgs.html#2">Haddock documentation</a>.
</p>
<h2>Multiple Modes</h2>
<p>
To specify a program with multiple modes, similar to <a href="http://darcs.net/">darcs</a>, we can supply a data type with multiple constructors, for example:
</p>
<pre>
data Sample = Hello {whom :: String}
| Goodbye
deriving (Show, Data, Typeable)
hello = Hello{whom = def}
goodbye = Goodbye
main = print =<< cmdArgs (modes [hello,goodbye])
</pre>
<p>
Compared to the first example, we now have multiple constructors, and a sample value for each constructor is passed to <tt>cmdArgs</tt>. Some sample interactions with this command line are:
</p>
<pre>
$ runhaskell Sample.hs hello --whom=world
Hello {whom = "world"}
$ runhaskell Sample.hs goodbye
Goodbye
$ runhaskell Sample.hs --help
The sample program
sample [OPTIONS]
Common flags
-? --help Display help message
-V --version Print version information
sample hello [OPTIONS]
-w --whom=ITEM
sample goodbye [OPTIONS]
</pre>
<p>
As before, the behaviour can be customised using attributes.
</p>
<h2>Larger Examples</h2>
<p>
For each of the following examples we first explain the purpose of the program, then give the source code, and finally the output of <tt>--help=HTML</tt>. The programs are intended to show sample uses of CmdArgs, and are available to experiment with through <tt>cmdargs <i>progname</i></tt>.
</p>
<h3>HLint</h3>
<p>
The <a href="http://community.haskell.org/~ndm/hlint/">HLint</a> program analyses a list of files, using various options to control the analysis. The command line processing is simple, but a few interesting points are:
</p>
<ul>
<li>The <tt>--report</tt> flag can be used to output a report in a standard location, but giving the flag a value changes where the file is output.</li>
<li>The <tt>color</tt> field is assigned two flag aliases, <tt>--colour</tt> and <tt>-c</tt>. Assigning the <tt>-c</tt> short flag explicitly stops either of the CPP fields using it.</li>
<li>The <tt>show_</tt> field would clash with <tt>show</tt> if given the expected name, but CmdArgs automatically strips the trailing underscore.</li>
<li>The <tt>cpp_define</tt> field has an underscore in it's name, which is transformed into a hyphen for the flag name.</li>
</ul>
<!-- BEGIN code hlint -->
<pre>
{-# LANGUAGE DeriveDataTypeable #-}
module HLint where
import System.Console.CmdArgs
data HLint = HLint
{report :: [FilePath]
,hint :: [FilePath]
,color :: Bool
,ignore :: [String]
,show_ :: Bool
,extension :: [String]
,language :: [String]
,utf8 :: Bool
,encoding :: String
,find :: [FilePath]
,test_ :: Bool
,datadir :: [FilePath]
,cpp_define :: [String]
,cpp_include :: [FilePath]
,files :: [FilePath]
}
deriving (Data,Typeable,Show,Eq)
hlint = HLint
{report = def &= opt "report.html" &= typFile &= help "Generate a report in HTML"
,hint = def &= typFile &= help "Hint/ignore file to use"
,color = def &= name "c" &= name "colour" &= help "Color the output (requires ANSI terminal)"
,ignore = def &= typ "MESSAGE" &= help "Ignore a particular hint"
,show_ = def &= help "Show all ignored ideas"
,extension = def &= typ "EXT" &= help "File extensions to search (defaults to hs and lhs)"
,language = def &= name "X" &= typ "LANG" &= help "Language extension (Arrows, NoCPP)"
,utf8 = def &= help "Use UTF-8 text encoding"
,encoding = def &= typ "ENC" &= help "Choose the text encoding"
,find = def &= typFile &= help "Find hints in a Haskell file"
,test_ = def &= help "Run in test mode"
,datadir = def &= typDir &= help "Override the data directory"
,cpp_define = def &= typ "NAME[=VALUE]" &= help "CPP #define"
,cpp_include = def &= typDir &= help "CPP include path"
,files = def &= args &= typ "FILES/DIRS"
} &=
verbosity &=
help "Suggest improvements to Haskell source code" &=
summary "HLint v0.0.0, (C) Neil Mitchell 2006-2010" &=
details ["Hlint gives hints on how to improve Haskell code",""
,"To check all Haskell files in 'src' and generate a report type:"," hlint src --report"]
mode = cmdArgsMode hlint
</pre>
<!-- END -->
<!-- BEGIN help hlint -->
<table class='cmdargs'>
<tr><td colspan='3'>HLint v0.0.0, (C) Neil Mitchell 2006-2010</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>hlint [OPTIONS] [FILES/DIRS]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Suggest improvements to Haskell source code</td></tr>
<tr><td colspan='3'> </tr>
<tr><td style='padding-left:2ex;'>-?</td><td style='padding-left:1ex;'>--help</td><td style='padding-left:2ex;'>Display help message</td></tr>
<tr><td style='padding-left:2ex;'>-V</td><td style='padding-left:1ex;'>--version</td><td style='padding-left:2ex;'>Print version information</td></tr>
<tr><td style='padding-left:2ex;'>-v</td><td style='padding-left:1ex;'>--verbose</td><td style='padding-left:2ex;'>Loud verbosity</td></tr>
<tr><td style='padding-left:2ex;'>-q</td><td style='padding-left:1ex;'>--quiet</td><td style='padding-left:2ex;'>Quiet verbosity</td></tr>
<tr><td style='padding-left:2ex;'>-r</td><td style='padding-left:1ex;'>--report[=FILE]</td><td style='padding-left:2ex;'>Generate a report in HTML (default=report.html)</td></tr>
<tr><td style='padding-left:2ex;'>-h</td><td style='padding-left:1ex;'>--hint=FILE</td><td style='padding-left:2ex;'>Hint/ignore file to use</td></tr>
<tr><td style='padding-left:2ex;'>-c</td><td style='padding-left:1ex;'>--color --colour</td><td style='padding-left:2ex;'>Color the output (requires ANSI terminal)</td></tr>
<tr><td style='padding-left:2ex;'>-i</td><td style='padding-left:1ex;'>--ignore=MESSAGE</td><td style='padding-left:2ex;'>Ignore a particular hint</td></tr>
<tr><td style='padding-left:2ex;'>-s</td><td style='padding-left:1ex;'>--show</td><td style='padding-left:2ex;'>Show all ignored ideas</td></tr>
<tr><td style='padding-left:2ex;'> <td style='padding-left:1ex;'>--extension=EXT</td><td style='padding-left:2ex;'>File extensions to search (defaults to hs and lhs)</td></tr>
<tr><td style='padding-left:2ex;'>-X</td><td style='padding-left:1ex;'>--language=LANG</td><td style='padding-left:2ex;'>Language extension (Arrows, NoCPP)</td></tr>
<tr><td style='padding-left:2ex;'>-u</td><td style='padding-left:1ex;'>--utf8</td><td style='padding-left:2ex;'>Use UTF-8 text encoding</td></tr>
<tr><td style='padding-left:2ex;'> <td style='padding-left:1ex;'>--encoding=ENC</td><td style='padding-left:2ex;'>Choose the text encoding</td></tr>
<tr><td style='padding-left:2ex;'>-f</td><td style='padding-left:1ex;'>--find=FILE</td><td style='padding-left:2ex;'>Find hints in a Haskell file</td></tr>
<tr><td style='padding-left:2ex;'>-t</td><td style='padding-left:1ex;'>--test</td><td style='padding-left:2ex;'>Run in test mode</td></tr>
<tr><td style='padding-left:2ex;'>-d</td><td style='padding-left:1ex;'>--datadir=DIR</td><td style='padding-left:2ex;'>Override the data directory</td></tr>
<tr><td style='padding-left:2ex;'> <td style='padding-left:1ex;'>--cpp-define=NAME[=VALUE]</td><td style='padding-left:2ex;'>CPP #define</td></tr>
<tr><td style='padding-left:2ex;'> <td style='padding-left:1ex;'>--cpp-include=DIR</td><td style='padding-left:2ex;'>CPP include path</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3' style='padding-left:1ex;'>Hlint gives hints on how to improve Haskell code</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'> </tr>
<tr><td colspan='3' style='padding-left:1ex;'>To check all Haskell files in 'src' and generate a report type:</td></tr>
<tr><td colspan='3' style='padding-left:3ex;'>hlint src --report</td></tr>
</table>
<!-- END -->
<h3>Diffy</h3>
<p>
The Diffy sample is a based on the idea of creating directory listings and comparing them. The tool can operate in two separate modes, <tt>create</tt> or <tt>diff</tt>. This sample is fictional, but the ideas are drawn from a real program. A few notable features:
</p>
<ul>
<li>There are multiple modes of execution, creating and diffing.</li>
<li>The diff mode takes exactly two arguments, the old file and the new file.</li>
<li>Default values are given for the <tt>out</tt> field, which are different in both modes.</li>
</ul>
<!-- BEGIN code diffy -->
<pre>
{-# LANGUAGE DeriveDataTypeable #-}
module Diffy where
import System.Console.CmdArgs
data Diffy = Create {src :: Maybe FilePath, out :: FilePath}
| Diff {old :: FilePath, new :: FilePath, out :: FilePath}
deriving (Data,Typeable,Show,Eq)
outFlags x = x &= help "Output file" &= typFile
create = Create
{src = def &= help "Source directory" &= typDir
,out = outFlags "ls.txt"
} &= help "Create a fingerprint"
diff = Diff
{old = def &= typ "OLDFILE" &= argPos 0
,new = def &= typ "NEWFILE" &= argPos 1
,out = outFlags "diff.txt"
} &= help "Perform a diff"
mode = cmdArgsMode $ modes [create,diff] &= help "Create and compare differences" &= program "diffy" &= summary "Diffy v1.0"
</pre>
<!-- END -->
<!-- BEGIN help diffy -->
<table class='cmdargs'>
<tr><td colspan='3'>Diffy v1.0</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>diffy [OPTIONS]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Create and compare differences</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3' style='padding-left:1ex;'>Common flags</td></tr>
<tr><td style='padding-left:2ex;'>-?</td><td style='padding-left:1ex;'>--help</td><td style='padding-left:2ex;'>Display help message</td></tr>
<tr><td style='padding-left:2ex;'>-V</td><td style='padding-left:1ex;'>--version</td><td style='padding-left:2ex;'>Print version information</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>diffy create [OPTIONS]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Create a fingerprint</td></tr>
<tr><td colspan='3'> </tr>
<tr><td style='padding-left:2ex;'>-s</td><td style='padding-left:1ex;'>--src=DIR</td><td style='padding-left:2ex;'>Source directory</td></tr>
<tr><td style='padding-left:2ex;'>-o</td><td style='padding-left:1ex;'>--out=FILE</td><td style='padding-left:2ex;'>Output file</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>diffy diff [OPTIONS] OLDFILE NEWFILE</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Perform a diff</td></tr>
<tr><td colspan='3'> </tr>
<tr><td style='padding-left:2ex;'>-o</td><td style='padding-left:1ex;'>--out=FILE</td><td style='padding-left:2ex;'>Output file</td></tr>
</table>
<!-- END -->
<h3>Maker</h3>
<p>
The Maker sample is based around a build system, where we can either build a project, clean the temporary files, or run a test. Some interesting features are:
</p>
<ul>
<li>The build mode is the default, so <tt>maker</tt> on it's own will be interpretted as a build command.</li>
<li>The build method is an enumeration.</li>
<li>The <tt>threads</tt> field is in two of the constructors, but not all three. It is given the short flag <tt>-j</tt>, rather than the default <tt>-t</tt>.</li>
</ul>
<!-- BEGIN code maker -->
<pre>
{-# LANGUAGE DeriveDataTypeable #-}
module Maker where
import System.Console.CmdArgs
data Method = Debug | Release | Profile
deriving (Data,Typeable,Show,Eq)
data Maker
= Wipe
| Test {threads :: Int, extra :: [String]}
| Build {threads :: Int, method :: Method, files :: [FilePath]}
deriving (Data,Typeable,Show,Eq)
threadsMsg x = x &= help "Number of threads to use" &= name "j" &= typ "NUM"
wipe = Wipe &= help "Clean all build objects"
</pre>
<!-- END -->
<!-- BEGIN help maker -->
<table class='cmdargs'>
<tr><td colspan='3'>Maker v1.0</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>maker [OPTIONS] </td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Build helper program</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3' style='padding-left:1ex;'>Common flags</td></tr>
<tr><td style='padding-left:2ex;'>-?</td><td style='padding-left:1ex;'>--help</td><td style='padding-left:2ex;'>Display help message</td></tr>
<tr><td style='padding-left:2ex;'>-V</td><td style='padding-left:1ex;'>--version</td><td style='padding-left:2ex;'>Print version information</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>maker [build] [OPTIONS] [ITEM]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Build the project</td></tr>
<tr><td colspan='3'> </tr>
<tr><td style='padding-left:2ex;'>-j</td><td style='padding-left:1ex;'>--threads=NUM</td><td style='padding-left:2ex;'>Number of threads to use</td></tr>
<tr><td style='padding-left:2ex;'>-r</td><td style='padding-left:1ex;'>--release</td><td style='padding-left:2ex;'>Release build</td></tr>
<tr><td style='padding-left:2ex;'>-d</td><td style='padding-left:1ex;'>--debug</td><td style='padding-left:2ex;'>Debug build</td></tr>
<tr><td style='padding-left:2ex;'>-p</td><td style='padding-left:1ex;'>--profile</td><td style='padding-left:2ex;'>Profile build</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>maker wipe [OPTIONS]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Clean all build objects</td></tr>
<tr><td colspan='3'> </tr>
<tr><td colspan='3'>maker test [OPTIONS] [ANY]</td></tr>
<tr><td colspan='3' style='padding-left:1ex;'>Run the test suite</td></tr>
<tr><td colspan='3'> </tr>
<tr><td style='padding-left:2ex;'>-j</td><td style='padding-left:1ex;'>--threads=NUM</td><td style='padding-left:2ex;'>Number of threads to use</td></tr>
</table>
<!-- END -->
</body>
</html>