# Optik Tutorial

While Optik is quite flexible and powerful, it's also straightforward to use in most cases. This document covers the code patterns that are common to any Optik-based program.

First, you need to import the OptionParser class; then, early in the main program, create an OptionParser instance:

    from optik import OptionParser
    [...]
    parser = OptionParser()

Then you can start defining options. The basic syntax is:

    parser.add_option(opt_str, ...,
                      attr=value, ...)

Each option has one or more option strings, such as `"-f"` or `"--file"`, and several option attributes that tell Optik what to expect and what to do when it encounters that option on the command line.

Typically, each option will have one short option string and one long option string, e.g.:

    parser.add_option("-f", "--file", ...)

You're free to define as many short option strings and as many long option strings as you like (including zero), as long as there is at least one option string overall.

The option strings passed to `add_option()` are effectively labels for the option defined by that call. For brevity, we will frequently refer to encountering an option on the command line; in reality, Optik encounters option strings and looks up options from them.

Once all of your options are defined, instruct Optik to parse your program's command line:

    (options, args) = parser.parse_args()

(If you like, you can pass a custom argument list to `parse_args()`, but that's rarely necessary: by default it uses `sys.argv[1:]`.)

`parse_args()` returns two values:

- `options`, an object containing values for all of your options -- e.g. if `"--file"` takes a single string argument, then `options.file` will be the filename supplied by the user, or `None` if the user did not supply that option
- `args`, the list of positional arguments leftover after parsing options

This tutorial document only covers the four most important option attributes: `action`, `type`, `dest` (destination), and `help`. Of these, `action` is the most fundamental.

# Understanding option actions

Actions tell Optik what to do when it encounters an option on the command line. There is a fixed set of actions hard-coded into Optik; adding new actions is an advanced topic covered in Extending Optik. Most actions tell Optik to store a value in some variable -- for example, take a string from the command line and store it in an attribute of `options`.

If you don't specify an option action, Optik defaults to `store`.

## The `store` action

The most common option action is `store`, which tells Optik to take the next argument (or the remainder of the current argument), ensure that it is of the correct type, and store it to your chosen destination.

For example:

    parser.add_option("-f", "--file",
                      action="store", type="string", dest="filename")

Now let's make up a fake command line and ask Optik to parse it:

    args = ["-f", "foo.txt"]
    (options, args) = parser.parse_args(args)

When Optik sees the option string `"-f"`, it consumes the next argument, `"foo.txt"`, and stores it in `options.filename`. So, after this call to `parse_args()`, `options.filename` is `"foo.txt"`.

Some other option types supported by Optik are `int` and `float`. Here's an option that expects an integer argument:

    parser.add_option("-n", type="int", dest="num")

Note that this option has no long option string, which is perfectly acceptable. Also, there's no explicit action, since the default is `store`.

Let's parse another fake command-line. This time, we'll jam the option argument right up against the option: since `"-n42"` (one argument) is equivalent to `"-n 42"` (two arguments), the code

    (options, args) = parser.parse_args(["-n42"])
    print options.num

will print `"42"`.

If you don't specify a type, Optik assumes `string`. Combined with the fact that the default action is `store`, that means our first example can be a lot shorter:

    parser.add_option("-f", "--file", dest="filename")

If you don't supply a destination, Optik figures out a sensible default from the option strings: if the first long option string is `"--foo-bar"`, then the default destination is `foo_bar`. If there are no long option strings, Optik looks at the first short option string: the default destination for `"-f"` is `f`.

Optik also includes built-in `long` and `complex` types. Adding types is covered in Extending Optik.

## Handling boolean (flag) options

Flag options -- set a variable to true or false when a particular option is seen -- are quite common. Optik supports them with two separate actions, `store_true` and `store_false`. For example, you might have a `verbose` flag that is turned on with `"-v"` and off with `"-q"`:

    parser.add_option("-v", action="store_true", dest="verbose")
    parser.add_option("-q", action="store_false", dest="verbose")

Here we have two different options with the same destination, which is perfectly OK. (It just means you have to be a bit careful when setting default values -- see below.)

When Optik encounters `"-v"` on the command line, it sets `options.verbose` to `True`; when it encounters `"-q"`, `options.verbose` is set to `False`.

## Other actions

Some other actions supported by Optik are:

`store_const`
  store a constant value
`append`
  append this option's argument to a list
`count`
  increment a counter by one
`callback`
  call a specified function

These are covered in Reference Guide and Option Callbacks.

# Default values

All of the above examples involve setting some variable (the "destination") when certain command-line options are seen. What happens if those options are never seen? Since we didn't supply any defaults, they are all set to `None`. This is usually fine, but sometimes you want more control. Optik lets you supply a default value for each destination, which is assigned before the command line is parsed.

First, consider the verbose/quiet example. If we want Optik to set `verbose` to `True` unless `"-q"` is seen, then we can do this:

    parser.add_option("-v", action="store_true", dest="verbose", default=True)
    parser.add_option("-q", action="store_false", dest="verbose")

Since default values apply to the destination rather than to any particular option, and these two options happen to have the same destination, this is exactly equivalent:

    parser.add_option("-v", action="store_true", dest="verbose")
    parser.add_option("-q", action="store_false", dest="verbose", default=True)

Consider this:

    parser.add_option("-v", action="store_true", dest="verbose", default=False)
    parser.add_option("-q", action="store_false", dest="verbose", default=True)

Again, the default value for `verbose` will be `True`: the last default value supplied for any particular destination is the one that counts.

A clearer way to specify default values is the `set_defaults()` method of OptionParser, which you can call at any time before calling `parse_args()`:

    parser.set_defaults(verbose=True)
    parser.add_option(...)
    (options, args) = parser.parse_args()

As before, the last value specified for a given option destination is the one that counts. For clarity, try to use one method or the other of setting default values, not both.

# Generating help

Optik's ability to generate help and usage text automatically is useful for creating user-friendly command-line interfaces. All you have to do is supply a `help` value for each option, and optionally a short usage message for your whole program. Here's an OptionParser populated with user-friendly (documented) options:

    usage = "usage: %prog [options] arg1 arg2"
    parser = OptionParser(usage=usage)
    parser.add_option("-v", "--verbose",
                      action="store_true", dest="verbose", default=True,
                      help="make lots of noise [default]")
    parser.add_option("-q", "--quiet",
                      action="store_false", dest="verbose",
                      help="be vewwy quiet (I'm hunting wabbits)")
    parser.add_option("-f", "--filename",
                      metavar="FILE", help="write output to FILE"),
    parser.add_option("-m", "--mode",
                      default="intermediate",
                      help="interaction mode: novice, intermediate, "
                           "or expert [default: %default]")

If Optik encounters either `"-h"` or `"--help"` on the command-line, or if you just call `parser.print_help()`, it prints the following to standard output:

    usage: <yourscript> [options] arg1 arg2

    options:
      -h, --help            show this help message and exit
      -v, --verbose         make lots of noise [default]
      -q, --quiet           be vewwy quiet (I'm hunting wabbits)
      -f FILE, --filename=FILE
                            write output to FILE
      -m MODE, --mode=MODE  interaction mode: novice, intermediate, or
                            expert [default: intermediate]

(If the help output is triggered by a help option, Optik exits after printing the help text.)

There's a lot going on here to help Optik generate the best possible help message:

- the script defines its own usage message:

      usage = "usage: %prog [options] arg1 arg2"

  Optik expands `"%prog"` in the usage string to the name of the current program, i.e. `os.path.basename(sys.argv[0])`. The expanded string is then printed before the detailed option help.

  If you don't supply a usage string, Optik uses a bland but sensible default: "`usage: %prog [options]"`, which is fine if your script doesn't take any positional arguments.

- every option defines a help string, and doesn't worry about line- wrapping -- Optik takes care of wrapping lines and making the help output look good.

- options that take a value indicate this fact in their automatically-generated help message, e.g. for the "mode" option:

      -m MODE, --mode=MODE

  Here, "MODE" is called the meta-variable: it stands for the argument that the user is expected to supply to `-m`/`--mode`. By default, Optik converts the destination variable name to uppercase and uses that for the meta-variable. Sometimes, that's not what you want -- for example, the `--filename` option explicitly sets `metavar="FILE"`, resulting in this automatically-generated option description:

      -f FILE, --filename=FILE

  This is important for more than just saving space, though: the manually written help text uses the meta-variable "FILE" to clue the user in that there's a connection between the semi-formal syntax "-f FILE" and the informal semantic description "write output to FILE". This is a simple but effective way to make your help text a lot clearer and more useful for end users.

- options that have a default value can include `%default` in the help string -- Optik will replace it with `str()` of the option's default value. If an option has no default value (or the default value is `None`), `%default` expands to `none`.

# Printing a version string

Similar to the brief usage string, Optik can also print a version string for your program. You have to supply the string as the `version` argument to OptionParser:

    parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.0")

`"%prog"` is expanded just like it is in `usage`. Apart from that, `version` can contain anything you like. When you supply it, Optik automatically adds a `"--version"` option to your parser. If it encounters this option on the command line, it expands your `version` string (by replacing `"%prog"`), prints it to stdout, and exits.

For example, if your script is called `/usr/bin/foo`:

    $ /usr/bin/foo --version
    foo 1.0

# How Optik handles errors

There are two broad classes of errors that Optik has to worry about: programmer errors and user errors. Programmer errors are usually erroneous calls to `parser.add_option()`, e.g. invalid option strings, unknown option attributes, missing option attributes, etc. These are dealt with in the usual way: raise an exception (either `optik.OptionError` or `TypeError`) and let the program crash.

Handling user errors is much more important, since they are guaranteed to happen no matter how stable your code is. Optik can automatically detect some user errors, such as bad option arguments (passing `"-n 4x"` where `-n` takes an integer argument), missing arguments (`"-n"` at the end of the command line, where `-n` takes an argument of any type). Also, you can call `parser.error()` to signal an application-defined error condition:

    (options, args) = parser.parse_args()
    [...]
    if options.a and options.b:
        parser.error("options -a and -b are mutually exclusive")

In either case, Optik handles the error the same way: it prints the program's usage message and an error message to standard error and exits with error status 2.

Consider the first example above, where the user passes `"4x"` to an option that takes an integer:

    $ /usr/bin/foo -n 4x
    usage: foo [options]

    foo: error: option -n: invalid integer value: '4x'

Or, where the user fails to pass a value at all:

    $ /usr/bin/foo -n
    usage: foo [options]

    foo: error: -n option requires an argument

Optik-generated error messages take care always to mention the option involved in the error; be sure to do the same when calling `parser.error()` from your application code.

If Optik's default error-handling behaviour does not suite your needs, you'll need to subclass OptionParser and override `exit()` and/or `error()`.

# Putting it all together

Here's what Optik-based scripts usually look like:

    from optik import OptionParser
    [...]
    def main():
        usage = "usage: %prog [options] arg"
        parser = OptionParser(usage)
        parser.add_option("-f", "--file", dest="filename",
                          help="read data from FILENAME")
        parser.add_option("-v", "--verbose",
                          action="store_true", dest="verbose")
        parser.add_option("-q", "--quiet",
                          action="store_false", dest="verbose")
        [...]
        (options, args) = parser.parse_args()
        if len(args) != 1:
            parser.error("incorrect number of arguments")
        if options.verbose:
            print "reading %s..." % options.filename
        [...]

    if __name__ == "__main__":
        main()