anchor.diff:    ].
   
   option_name_test_() ->
  -  [ ?_assertEqual("-h", option_name(#option{short="-h"}))
  +  [ ?_assertEqual("-h"    , option_name(#option{short="-h"}))
     , ?_assertEqual("--help", option_name(#option{short="-h", long="--help"}))
     , ?_assertEqual("--help", option_name(#option{long="--help"}))
     ].
  
positive.code:   [option_parse("-" ++ S) || S <- OptStrings].
  
  parse_args(Args, Options) ->
    Tokens = string:tokens(Args, " "),
    parse_args_tokens(Tokens, Options).
  
  parse_args_tokens([], _Options)            -> [];
  parse_args_tokens([Current|Rest]=Tokens, Options) ->
    case Current of
      "--"           -> [#argument{value=Arg} || Arg <- Tokens];
      [$-,$-|_]      ->
        {Opt, NewRest} = parse_long(Current, Rest, Options),
        [Opt|parse_args_tokens(NewRest, Options)];
      [$-|[_|_]=Raw] ->
        {Opts, NewRest} = parse_shorts(Raw, Rest, Options, []),
        Opts ++ parse_args_tokens(NewRest, Options);
      _              ->
        [#argument{value=Current}|parse_args_tokens(Rest, Options)]
    end.
  
  parse_long(Current, Rest, Options) ->
    {Raw, Value} = partition(Current, "="),
    Opt =
      case [O || O <- Options, O#option.long == Raw] of
        [O] -> O;
        []  ->
          case [O || O <- Options, starts_with(O#option.long, Raw)] of
            []                         -> throw({Raw, "not recognized"});
            Opts when length(Opts) > 1 -> throw({Raw, "is not a unique prefix"});
            [O]                        -> O
          end
      end,
    case Opt#option.argcount of
      1 when Value == [], Rest == [] -> throw({Raw, "requires an argument"});
      1 when Value == []             -> {Opt#option{value = hd(Rest)}, tl(Rest)};
      1 when Value /= []             -> {Opt#option{value = Value}, Rest};
      0 when Value /= []             -> throw({Raw, "must not have an argument"});
      0 when Value == []             -> {Opt#option{value = true}, Rest}
    end.
  
  starts_with(Str, SubStr) when is_list(Str), is_list(SubStr) ->
    string:str(Str, SubStr) == 1;
  starts_with(_, _) -> false.
  
  parse_shorts([], Rest, _Options, Acc) -> {lists:reverse(Acc), Rest};
  parse_shorts([H|T], Rest, Options, Acc) ->
   case [O || O <- Options, tl(O#option.short) == [H]] of
     []                       -> throw({[$-, H], "not recognized"});
     Opt when length(Opt) > 1 -> throw({[$-, H], "specified ambiguously"});
     [Opt] when Opt#option.argcount == 0 ->
       parse_shorts(T, Rest, Options, [Opt#option{value = true}|Acc]);
     [Opt] ->
       {Value, NewRest} = case Opt#option.argcount of
                            0 -> {true, Rest};
                            1 -> get_value_shorts(H, T, Rest)
                          end,
       {[Opt#option{value = Value}], NewRest}
   end.
  
  get_value_shorts(H, [], [])         -> throw({H, "requires an argument"});
  get_value_shorts(_, [], [Arg|Rest]) -> {Arg, Rest};
  get_value_shorts(_, Arg, Rest)      -> {Arg, Rest}.
  
  parse_args_test_() ->
    HelpOpt    = #option{short="-h", value=true},
  
negative.code:                   parse_args("-h arg -- -v", O))
    ].
  
  parse_pattern_test_() ->
    HelpOpt    = #option{short="-h", value=true},
    FileOpt    = #option{short="-f", long="--file", argcount=1, value="<f>"},
    VerboseOpt = #option{short="-v", long="--verbose", value=true},
    O = [option("-h"), option("-v", "--verbose"), option("-f", "--file", 1)],
    [ ?_assertEqual(
         required([optional([HelpOpt])]),
         parse_pattern("[ -h ]", O))
    , ?_assertEqual(
         required([optional([one_or_more([argument("ARG")])])]),
         parse_pattern("[ ARG ... ]", O))
    , ?_assertEqual(
         required([optional([either([HelpOpt, VerboseOpt])])]),
         parse_pattern("[ -h | -v ]", O))
    , ?_assertEqual(
         required([VerboseOpt, optional([FileOpt])]),
         parse_pattern("-v [ --file <f> ]", O))
    , ?_assertEqual(
         required([optional([either([argument("M"),
                                     required([either([argument("K"),
                                                       argument("L")])])])])]),
         parse_pattern("[M | (K | L)]", O))
    , ?_assertEqual(
         required([argument("N"), argument("M")]),
         parse_pattern("N M", O))
    , ?_assertEqual(
         required([argument("N"), optional([argument("M")])]),
         parse_pattern("N [M]", O))
    , ?_assertEqual(
         required([argument("N"), optional([either([argument("M"),
                                                    argument("K"),
                                                    argument("L")])])]),
        parse_pattern("N [M | K | L]", O))
    , ?_assertEqual(
         required([optional([HelpOpt]), optional([argument("N")])]),
         parse_pattern("[ -h ] [N]", O))
    , ?_assertEqual(
         required([optional(lists:reverse(O))]),
         parse_pattern("[options]", O))
    , ?_assertEqual(
         required([argument("ADD")]),
         parse_pattern("ADD", O))
    , ?_assertEqual(
         required([argument("<add>")]),
         parse_pattern("<add>", O))
    , ?_assertEqual(
         required([command("add")]),
         parse_pattern("add", O))
    , ?_assertEqual(
         required([required([either([HelpOpt,
                                     required([VerboseOpt,
                                               optional([argument("A")])])])])]),
         parse_pattern("( -h | -v [ A ] )", O))
    , ?_assertEqual(
         required(
           [required(
              [either(
                 [required([argument("N"),
                            optional([either([argument("M"),
                                              required([either([argument("K"),
                                                                argument("L")
                                                               ])])])])]),
                  required([argument("O"), argument("P")])])])]),
         parse_pattern("(N [M | (K | L)] | O P)", O))
    ].
  
  argument(Arg)         -> #argument{name=Arg}.
  command(Cmd)          -> #command{name=Cmd}.
  required(Children)    -> #required{children=Children}.
  either(Children)      -> #either{children=Children}.
  optional(Children)    -> #optional{children=Children}.
  one_or_more(Children) -> #one_or_more{children=Children}.
  
  option(Short)           -> #option{short=Short}.
  option(Short, Long)     -> #option{short=Short, long=Long}.
  option(Short, Long, Ac) -> #option{short=Short, long=Long, argcount=Ac}.
  
  partition_test_() ->
    [ ?_assertEqual({"foobar", ""}     , partition("foobar"      , "abc"))
  