Dropped record syntax for the option specifications to simplify use from escripts.

This commit is contained in:
Juan Jose Comellas 2009-11-16 19:20:32 -03:00
parent 109d9f5f2a
commit 4b03285d9e
8 changed files with 270 additions and 329 deletions

9
ChangeLog Normal file
View File

@ -0,0 +1,9 @@
2009-11-16 Juan Jose Comellas <juanjo@comellas.org>
* *: Released v0.1
* src/getopt.erl:
* src/test/getopt_test.erl:
* src/examples/ex1.erl:
Dropped record syntax for the option specifications to simplify use from escripts.

View File

@ -1,4 +1,4 @@
Copyright (c) 2009, Novamens SA
Copyright (c) 2009 Juan Jose Comellas
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@ -11,8 +11,8 @@ are permitted provided that the following conditions are met:
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Novamens SA nor the names of its contributors may be
used to endorse or promote products derived from this software without
- Neither the name of Juan Jose Comellas nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND

View File

@ -28,81 +28,59 @@ Usage
The *getopt* module provides two functions:
parse([#option{}], Args :: string() | [string()]) ->
parse([{Name, Short, Long, ArgSpec, Help}], Args :: string() | [string()]) ->
{ok, {Options, NonOptionArgs}} | {error, {Reason, Data}}
usage([#option{}], ProgramName :: string()) -> ok
usage([{Name, Short, Long, ArgSpec, Help}], ProgramName :: string()) -> ok
The ``parse/2`` function receives a list of ``#option{}`` records (defined in
``getopt.hrl``) with the command line option specifications. The ``#option{}``
record has the following elements:
The ``parse/2`` function receives a list of tuples with the command line option
specifications. The type specification for the tuple is:
-record(option, {
name :: atom(),
short :: char() | undefined,
long :: string() | undefined,
arg :: getopt_arg_type() | {getopt_arg_type(), getopt_arg()} | undefined.
help :: string() | undefined
}).
-type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'.
-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
-type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined.
-type option_spec() :: {
Name :: atom(),
Short :: char() | undefined,
Long :: string() | undefined,
ArgSpec :: arg_spec(),
Help :: string() | undefined
}.
The fields of the record are:
- ``name``: name of the option.
- ``short``: character for the short option (e.g. $i for -i).
- ``long``: string for the long option (e.g. "info" for --info).
- ``arg``: data type the argument will be converted to with an optional default value. It can either be an atom() (one of: 'atom', 'binary', 'boolean', 'float', 'integer', 'string') or a tuple with an atom() and the default value for that argument.
- ``help``: help message that is shown for the option when usage/2 is called.
- ``Name``: name of the option.
- ``Short``: character for the short option (e.g. $i for -i).
- ``Long``: string for the long option (e.g. "info" for --info).
- ``ArgSpec``: data type and optional default value the argument will be converted to.
- ``Help``: help message that is shown for the option when usage/2 is called.
The second parameter holds the list of arguments as passed to the ``main/1``
function in escripts.
function in escripts. e.g.
e.g.
#option{name = port,
short = $p,
long = "port",
arg = {integer, 5432},
help = "Database server port"
}
{port, $p, "port", {integer, 5432}, "Database server port"}
If the function is successful parsing the command line arguments it will return
a tuple containing the parsed options and the non-option arguments. The options
will be represented by a list of key-value pairs with the ``name`` of the
will be represented by a list of key-value pairs with the ``Name`` of the
option as *key* and the argument from the command line as *value*. If the option
doesn't have an argument, only the atom corresponding to its ``name`` will be
doesn't have an argument, only the atom corresponding to its ``Name`` will be
added to the list of options. For the example given above we could get something
like ``{port, 5432}``. The non-option arguments are just a list of strings with
all the arguments that did not have corresponding options.
e.g. For a program named ``ex1.escript`` with the following option specifications:
e.g. For a program named ``ex.escript`` with the following option specifications:
OptSpec =
[
#option{name = host,
short = $h,
long = "host",
arg = {string, "localhost"},
help = "Database server host"
},
#option{name = port,
short = $p,
long = "port",
arg = integer,
help = "Database server port"
},
#option{name = dbname,
long = "dbname",
arg = {string, "users"},
help = "Database name"
},
#option{name = xml,
short = $x,
help = "Output data in XML"
},
#option{name = file,
arg = string,
help = "Output file"
}
{host, $h, "host", {string, "localhost"}, "Database server host"},
{port, $p, "port", integer, "Database server port"},
{dbname, undefined, "dbname", {string, "users"}, "Database name"},
{xml, $x, undefined, undefined, "Output data in XML"},
{file, undefined, undefined, string, "Output file"}
].
And this command line:
@ -132,7 +110,7 @@ Also, the call to ``getopt:usage/2``:
Will show (on *stdout*):
Usage: ex1 [-h <host>] [-p <port>] [--dbname <dbname>] [-x] <file>
Usage: ex.escript [-h <host>] [-p <port>] [--dbname <dbname>] [-x] <file>
-h, --host Database server host
-p, --port Database server port
@ -146,13 +124,3 @@ Known limitations
- The syntax for non-option arguments that start with '-' (e.g. -a -- -b)
is not supported yet.
Known problems
--------------
Currently, with Erlang R13B (and older versions), escripts cannot use the
``-include()`` preprocessor directive so you're forced to install the *getopt*
package in Erlang's library path for your escripts to be able to include the
``getopt.hrl`` header file that defines the ``#option{}`` record used by the
functions in the *getopt* module.

51
examples/ex1.escript Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -sname ex1 -pz ebin
%%%-------------------------------------------------------------------
%%% @author Juan Jose Comellas <juanjo@comellas.org>
%%% @copyright (C) 2009 Juan Jose Comellas
%%% @doc Example file for the getopt module.
%%% @end
%%%
%%% This source file is subject to the New BSD License. You should have received
%%% a copy of the New BSD license with this software. If not, it can be
%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php
%%%-------------------------------------------------------------------
-module(ex1).
-author('juanjo@comellas.org').
main([]) ->
getopt:usage(option_spec_list(), "ex1.escript");
main(Args) ->
OptSpecList = option_spec_list(),
io:format("For command line: ~p~n"
"getopt:parse/2 returns:~n~n", [Args]),
case getopt:parse(OptSpecList, Args) of
{ok, {Options, NonOptArgs}} ->
io:format("Options:~n ~p~n~nNon-option arguments:~n ~p~n", [Options, NonOptArgs]);
{error, {Reason, Data}} ->
io:format("Error: ~s ~p~n~n", [Reason, Data]),
getopt:usage(OptSpecList, "ex1.escript")
end.
option_spec_list() ->
CurrentUser = case os:getenv("USER") of
false ->
"user";
User ->
User
end,
[
%% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
{help, $?, "help", undefined, "Show the program options"},
{username, $U, "username", string, "Username to connect to the database"},
{password, $P, "password", {string, CurrentUser}, "Password to connect to the database"},
{host, $h, "host", {string, "localhost"}, "Database server host name or IP address"},
{port, $p, "port", {integer, 1000}, "Database server port"},
{output_file, $o, "output-file", string, "File where the data will be saved to"},
{xml, $x, "xml", undefined, "Output data as XML"},
{dbname, undefined, undefined, string, "Database name"}
].

View File

@ -1,23 +0,0 @@
%% @type getopt_arg_type() = 'atom' | 'binary' | 'bool' | 'float' | 'integer' | 'string'.
%% Atom indicating the data type that an argument can be converted to.
-type getopt_arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'.
%% @type getopt_arg() = atom() | binary() | bool() | float() | integer() | string().
%% Data type that an argument can be converted to.
-type getopt_arg() :: atom() | binary() | boolean() | float() | integer() | string().
%% @type getopt_arg_spec() = getopt_arg_type() | {getopt_arg_type(), getopt_arg()} | undefined.
%% Argument specification.
-type getopt_arg_spec() :: getopt_arg_type() | {getopt_arg_type(), getopt_arg()} | undefined.
%% @doc Record that defines the option specifications.
-record(option, {
%% @doc Name of the option
name :: atom(),
%% @doc Character for the short option (e.g. $i for -i)
short :: char() | undefined,
%% @doc String for the long option (e.g. "info" for --info)
long :: string() | undefined,
%% @doc Data type the argument will be converted to with an optional default value
arg :: getopt_arg_spec(),
%% @doc Help message that is shown for the option when usage/2 is called.
help :: string() | undefined
}).

View File

@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Juan Jose Comellas <jcomellas@novamens.com>
%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com)
%%% @author Juan Jose Comellas <juanjo@comellas.org>
%%% @copyright (C) 2009 Juan Jose Comellas
%%% @doc Example file for the getopt module.
%%% @end
%%%
@ -9,9 +9,7 @@
%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php
%%%-------------------------------------------------------------------
-module(ex1).
-author('Juan Jose Comellas <jcomellas@novamens.com>').
-include("include/getopt.hrl").
-author('juanjo@comellas.org').
-export([test/0, test/1]).
@ -26,7 +24,7 @@ test(CmdLine) ->
"getopt:parse/2 returns:~n~n", [CmdLine]),
case getopt:parse(OptSpecList, CmdLine) of
{ok, {Options, NonOptArgs}} ->
io:format("Options:~n ~p~nNon-option arguments:~n ~p~n", [Options, NonOptArgs]);
io:format("Options:~n ~p~n~nNon-option arguments:~n ~p~n", [Options, NonOptArgs]);
{error, {Reason, Data}} ->
io:format("Error: ~s ~p~n~n", [Reason, Data]),
getopt:usage(OptSpecList, "ex1")
@ -36,48 +34,13 @@ test(CmdLine) ->
option_spec() ->
CurrentUser = os:getenv("USER"),
[
#option{name = help,
short = $?,
long = "help",
help = "Show the program options"
},
#option{name = username,
short = $U,
long = "username",
arg = string,
help = "Username to connect to the database"
},
#option{name = password,
short = $P,
long = "password",
arg = {string, CurrentUser},
help = "Password to connect to the database"
},
#option{name = host,
short = $h,
long = "host",
arg = {string, "localhost"},
help = "Database server host name or IP address"
},
#option{name = port,
short = $p,
long = "port",
arg = {integer, 1000},
help = "Database server port"
},
#option{name = output_file,
short = $o,
long = "output-file",
arg = string,
help = "File where the data will be saved to"
},
#option{name = xml,
short = $x,
long = "xml",
help = "Output data as XML"
},
#option{name = dbname,
arg = string,
help = "Database name"
}
%% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
{help, $?, "help", undefined, "Show the program options"},
{username, $U, "username", string, "Username to connect to the database"},
{password, $P, "password", {string, CurrentUser}, "Password to connect to the database"},
{host, $h, "host", {string, "localhost"}, "Database server host name or IP address"},
{port, $p, "port", {integer, 1000}, "Database server port"},
{output_file, $o, "output-file", string, "File where the data will be saved to"},
{xml, $x, "xml", undefined, "Output data as XML"},
{dbname, undefined, undefined, string, "Database name"}
].

View File

@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Juan Jose Comellas <jcomellas@novamens.com>
%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com)
%%% @author Juan Jose Comellas <juanjo@comellas.org>
%%% @copyright (C) 2009 Juan Jose Comellas
%%% @doc Parses command line options with a format similar to that of GNU getopt.
%%% @end
%%%
@ -9,19 +9,40 @@
%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php
%%%-------------------------------------------------------------------
-module(getopt).
-author('Juan Jose Comellas <jcomellas@novamens.com>').
-include("getopt.hrl").
%% @headerfile "getopt.hrl"
-author('juanjo@comellas.org').
-define(TAB_LENGTH, 8).
%% Indentation of the help messages in number of tabs.
-define(INDENTATION, 3).
%% @type option() = atom() | {atom(), getopt_arg()}. Option type and optional default argument.
-type option() :: atom() | {atom(), getopt_arg()}.
-define(OPT_NAME, 1).
-define(OPT_SHORT, 2).
-define(OPT_LONG, 3).
-define(OPT_ARG, 4).
-define(OPT_HELP, 5).
-define(IS_OPT_SPEC(Opt), (is_tuple(Opt) andalso (size(Opt) =:= ?OPT_HELP))).
%% @type arg_type() = 'atom' | 'binary' | 'bool' | 'float' | 'integer' | 'string'.
%% Atom indicating the data type that an argument can be converted to.
-type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'.
%% @type arg_value() = atom() | binary() | bool() | float() | integer() | string().
%% Data type that an argument can be converted to.
-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
%% @type arg_spec() = arg_type() | {arg_type(), arg_value()} | undefined.
%% Argument specification.
-type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined.
%% @type option() = atom() | {atom(), arg_value()}. Option type and optional default argument.
-type option() :: atom() | {atom(), arg_value()}.
%% @type option_spec() = #option{}. Command line option specification.
-type option_spec() :: #option{}.
-type option_spec() :: {
Name :: atom(),
Short :: char() | undefined,
Long :: string() | undefined,
ArgSpec :: arg_spec(),
Help :: string() | undefined
}.
-export([parse/2, usage/2]).
@ -51,24 +72,24 @@ parse(OptSpecList, CmdLine) ->
{ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data:: any()}}.
%% Process long options.
parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$-, $- | LongName] = OptStr | Tail]) ->
{Option, Tail1} = get_option(OptSpecList, OptStr, LongName, #option.long, Tail),
{Option, Tail1} = get_option(OptSpecList, OptStr, LongName, ?OPT_LONG, Tail),
parse(OptSpecList, [Option | OptAcc], ArgAcc, ArgPos, Tail1);
%% Process short options.
parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$-, ShortName] = OptStr | Tail]) ->
{Option, Tail1} = get_option(OptSpecList, OptStr, ShortName, #option.short, Tail),
{Option, Tail1} = get_option(OptSpecList, OptStr, ShortName, ?OPT_SHORT, Tail),
parse(OptSpecList, [Option | OptAcc], ArgAcc, ArgPos, Tail1);
%% Process multiple short options with no argument.
parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$- | ShortNameList] = OptStr | Tail]) ->
NewOptAcc =
lists:foldl(
fun (ShortName, OptAcc1) ->
[get_option_no_arg(OptSpecList, OptStr, ShortName, #option.short) | OptAcc1]
[get_option_no_arg(OptSpecList, OptStr, ShortName, ?OPT_SHORT) | OptAcc1]
end, OptAcc, ShortNameList),
parse(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Tail);
%% Process non-option arguments.
parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) ->
case find_non_option_arg(OptSpecList, ArgPos) of
{value, #option{} = OptSpec} ->
{value, OptSpec} when ?IS_OPT_SPEC(OptSpec) ->
parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos + 1, Tail);
false ->
parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail)
@ -85,7 +106,7 @@ parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
%% received on the command line.
get_option(OptSpecList, OptStr, OptName, FieldPos, Tail) ->
case lists:keysearch(OptName, FieldPos, OptSpecList) of
{value, #option{name = Name, arg = ArgSpec} = OptSpec} ->
{value, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec} ->
case ArgSpec of
undefined ->
{Name, Tail};
@ -106,9 +127,9 @@ get_option(OptSpecList, OptStr, OptName, FieldPos, Tail) ->
%% argument and matches a string received on the command line.
get_option_no_arg(OptSpecList, OptStr, OptName, FieldPos) ->
case lists:keysearch(OptName, FieldPos, OptSpecList) of
{value, #option{name = Name, arg = undefined}} ->
{value, {Name, _Short, _Long, undefined, _Help}} ->
Name;
{value, #option{name = Name}} ->
{value, {Name, _Short, _Long, _ArgSpec, _Help}} ->
throw({error, {missing_option_arg, Name}});
false ->
throw({error, {invalid_option, OptStr}})
@ -117,9 +138,9 @@ get_option_no_arg(OptSpecList, OptStr, OptName, FieldPos) ->
-spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false.
%% @doc Find the option for the discrete argument in position specified in the
%% Pos argument.
find_non_option_arg([#option{short = undefined, long = undefined} = Opt | _Tail], 0) ->
{value, Opt};
find_non_option_arg([#option{short = undefined, long = undefined} | Tail], Pos) ->
find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} = OptSpec | _Tail], 0) ->
{value, OptSpec};
find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} | Tail], Pos) ->
find_non_option_arg(Tail, Pos - 1);
find_non_option_arg([_Head | Tail], Pos) ->
find_non_option_arg(Tail, Pos);
@ -129,7 +150,7 @@ find_non_option_arg([], _Pos) ->
-spec append_default_args([option_spec()], [option()]) -> [option()].
%% @doc Appends the default values of the options that are not present.
append_default_args([#option{name = Name, arg = {_Type, DefaultArg}} | Tail], OptAcc) ->
append_default_args([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) ->
append_default_args(Tail,
case lists:keymember(Name, 1, OptAcc) of
false ->
@ -147,7 +168,7 @@ append_default_args([], OptAcc) ->
-spec convert_option_arg(option_spec(), string()) -> [option()].
%% @doc Convert the argument passed in the command line to the data type
%% indicated byt the argument specification.
convert_option_arg(#option{name = Name, arg = ArgSpec}, Arg) ->
convert_option_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg) ->
try
Converted = case ArgSpec of
{Type, _DefaultArg} ->
@ -161,7 +182,7 @@ convert_option_arg(#option{name = Name, arg = ArgSpec}, Arg) ->
throw({error, {invalid_option_arg, {Name, Arg}}})
end.
-spec to_type(atom(), string()) -> getopt_arg().
-spec to_type(atom(), string()) -> arg_value().
to_type(binary, Arg) ->
list_to_binary(Arg);
to_type(atom, Arg) ->
@ -196,21 +217,34 @@ usage(OptSpecList, ProgramName) ->
usage_cmd_line(OptSpecList) ->
usage_cmd_line(OptSpecList, []).
%% For options with short form and no argument.
usage_cmd_line([#option{short = Short, arg = undefined} | Tail], Acc) when Short =/= undefined ->
usage_cmd_line(Tail, [[$\s, $[, $-, Short, $]] | Acc]);
%% For options with only long form and no argument.
usage_cmd_line([#option{long = Long, arg = undefined} | Tail], Acc) when Long =/= undefined ->
usage_cmd_line(Tail, [[$\s, $[, $-, $-, Long, $]] | Acc]);
%% For options with short form and argument.
usage_cmd_line([#option{name = Name, short = Short} | Tail], Acc) when Short =/= undefined ->
usage_cmd_line(Tail, [[$\s, $[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]] | Acc]);
%% For options with only long form and argument.
usage_cmd_line([#option{name = Name, long = Long} | Tail], Acc) when Long =/= undefined ->
usage_cmd_line(Tail, [[$\s, $[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]] | Acc]);
%% For options with neither short nor long form and argument.
usage_cmd_line([#option{name = Name, arg = ArgSpec} | Tail], Acc) when ArgSpec =/= undefined ->
usage_cmd_line(Tail, [[$\s, $<, atom_to_list(Name), $>] | Acc]);
usage_cmd_line([{Name, Short, Long, ArgSpec, _Help} | Tail], Acc) ->
CmdLine =
case ArgSpec of
undefined ->
if
%% For options with short form and no argument.
Short =/= undefined ->
[$\s, $[, $-, Short, $]];
%% For options with only long form and no argument.
Long =/= undefined ->
[$\s, $[, $-, $-, Long, $]];
true ->
[]
end;
_ ->
if
%% For options with short form and argument.
Short =/= undefined ->
[$\s, $[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]];
%% For options with only long form and argument.
Long =/= undefined ->
[$\s, $[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]];
%% For options with neither short nor long form and argument.
true ->
[$\s, $<, atom_to_list(Name), $>]
end
end,
usage_cmd_line(Tail, [CmdLine | Acc]);
usage_cmd_line([], Acc) ->
lists:flatten(lists:reverse(Acc)).
@ -221,26 +255,36 @@ usage_cmd_line([], Acc) ->
usage_options(OptSpecList) ->
usage_options(OptSpecList, []).
%% Neither short nor long form (non-option argument).
usage_options([#option{name = Name, short = undefined, long = undefined} = Opt | Tail], Acc) ->
usage_options(Tail, add_option_help(Opt, [$<, atom_to_list(Name), $>], Acc));
%% Only short form.
usage_options([#option{short = Short, long = undefined} = Opt | Tail], Acc) ->
usage_options(Tail, add_option_help(Opt, [$-, Short], Acc));
%% Only long form.
usage_options([#option{short = undefined, long = Long} = Opt | Tail], Acc) ->
usage_options(Tail, add_option_help(Opt, [$-, $-, Long], Acc));
%% Both short and long form.
usage_options([#option{short = Short, long = Long} = Opt | Tail], Acc) ->
usage_options(Tail, add_option_help(Opt, [$-, Short, $,, $\s, $-, $-, Long], Acc));
usage_options([{Name, Short, Long, _ArgSpec, _Help} = OptSpec | Tail], Acc) ->
Prefix =
case Long of
undefined ->
case Short of
%% Neither short nor long form (non-option argument).
undefined ->
[$<, atom_to_list(Name), $>];
%% Only short form.
_ ->
[$-, Short]
end;
_ ->
case Short of
%% Only long form.
undefined ->
[$-, $-, Long];
%% Both short and long form.
_ ->
[$-, Short, $,, $\s, $-, $-, Long]
end
end,
usage_options(Tail, add_option_help(OptSpec, Prefix, Acc));
usage_options([], Acc) ->
lists:flatten(lists:reverse(Acc)).
-spec add_option_help(option_spec(), Prefix :: string(), Acc :: string()) -> string().
%% @doc Add the help message corresponding to an option specification to a list
%% with the correct indentation.
add_option_help(#option{help = Help}, Prefix, Acc) when is_list(Help), Help =/= [] ->
add_option_help({_Name, _Short, _Long, _ArgSpec, Help}, Prefix, Acc) when is_list(Help), Help =/= [] ->
FlatPrefix = lists:flatten(Prefix),
case ((?INDENTATION * ?TAB_LENGTH) - 2 - length(FlatPrefix)) of
TabSize when TabSize > 0 ->

View File

@ -1,6 +1,6 @@
%%%-------------------------------------------------------------------
%%% @author Juan Jose Comellas <jcomellas@novamens.com>
%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com)
%%% @author Juan Jose Comellas <juanjo@comellas.org>
%%% @copyright (C) 2009 Juan Jose Comellas
%%% @doc Parses command line options with a format similar to that of GNU getopt.
%%%
%%% This source file is subject to the New BSD License. You should have received
@ -9,13 +9,18 @@
%%%-------------------------------------------------------------------
-module(getopt_test).
-author('Juan Jose Comellas <jcomellas@novamens.com>').
-author('juanjo@comellas.org').
-include("getopt.hrl").
-include_lib("eunit/include/eunit.hrl").
-import(getopt, [parse/2, usage/2]).
-define(NAME(Opt), element(1, Opt)).
-define(SHORT(Opt), element(2, Opt)).
-define(LONG(Opt), element(3, Opt)).
-define(ARG_SPEC(Opt), element(4, Opt)).
-define(HELP(Opt), element(5, Opt)).
%%%-------------------------------------------------------------------
%%% UNIT TESTS
@ -23,98 +28,22 @@
%%% Test for the getopt/1 function
parse_1_test_() ->
Short =
#option{name = short,
short = $a,
help = "Option with only short form and no argument"
},
Short2 =
#option{name = short2,
short = $b,
help = "Second option with only short form and no argument"
},
Short3 =
#option{name = short3,
short = $c,
help = "Third ption with only short form and no argument"
},
ShortArg =
#option{name = short_arg,
short = $d,
arg = string,
help = "Option with only short form and argument"
},
ShortDefArg =
#option{name = short_def_arg,
short = $e,
arg = {string, "default-short"},
help = "Option with only short form and default argument"
},
ShortInt =
#option{name = short_int,
short = $f,
arg = integer,
help = "Option with only short form and integer argument"
},
Long =
#option{name = long,
long = "long",
help = "Option with only long form and no argument"
},
LongArg =
#option{name = long_arg,
long = "long-arg",
arg = string,
help = "Option with only long form and argument"
},
LongDefArg =
#option{name = long_def_arg,
long = "long-def-arg",
arg = {string, "default-long"},
help = "Option with only long form and default argument"
},
LongInt =
#option{name = long_int,
long = "long-int",
arg = integer,
help = "Option with only long form and integer argument"
},
ShortLong =
#option{name = short_long,
short = $g,
long = "short-long",
help = "Option with short form, long form and no argument"
},
ShortLongArg =
#option{name = short_long_arg,
short = $h,
long = "short-long-arg",
arg = string,
help = "Option with short form, long form and argument"
},
ShortLongDefArg =
#option{name = short_long_def_arg,
short = $i,
long = "short-long-def-arg",
arg = {string, "default-short-long"},
help = "Option with short form, long form and default argument"
},
ShortLongInt =
#option{name = short_long_int,
short = $j,
long = "short-long-int",
arg = integer,
help = "Option with short form, long form and integer argument"
},
NonOptArg =
#option{name = non_opt_arg,
help = "Non-option argument"
},
NonOptInt =
#option{name = non_opt_int,
arg = integer,
help = "Non-option integer argument"
},
Short = {short, $a, undefined, undefined, "Option with only short form and no argument"},
Short2 = {short2, $b, undefined, undefined, "Second option with only short form and no argument"},
Short3 = {short3, $c, undefined, undefined, "Third option with only short form and no argument"},
ShortArg = {short_arg, $d, undefined, string, "Option with only short form and argument"},
ShortDefArg = {short_def_arg, $e, undefined, {string, "default-short"}, "Option with only short form and default argument"},
ShortInt = {short_int, $f, undefined, integer, "Option with only short form and integer argument"},
Long = {long, undefined, "long", undefined, "Option with only long form and no argument"},
LongArg = {long_arg, undefined, "long-arg", string, "Option with only long form and argument"},
LongDefArg = {long_def_arg, undefined, "long-def-arg", {string, "default-long"}, "Option with only long form and default argument"},
LongInt = {long_int, undefined, "long-int", integer, "Option with only long form and integer argument"},
ShortLong = {short_long, $g, "short-long", undefined, "Option with short form, long form and no argument"},
ShortLongArg = {short_long_arg, $h, "short-long-arg", string, "Option with short form, long form and argument"},
ShortLongDefArg = {short_long_def_arg, $i, "short-long-def-arg", {string, "default-short-long"}, "Option with short form, long form and default argument"},
ShortLongInt = {short_long_int, $j, "short-long-int", integer, "Option with short form, long form and integer argument"},
NonOptArg = {non_opt_arg, undefined, undefined, undefined, "Non-option argument"},
NonOptInt = {non_opt_int, undefined, undefined, integer, "Non-option integer argument"},
CombinedOptSpecs =
[
Short,
@ -136,14 +65,14 @@ parse_1_test_() ->
],
CombinedArgs =
[
[$-, Short#option.short],
[$-, ShortArg#option.short], "value1",
[$-, ShortInt#option.short], "100",
[$-, Short2#option.short, Short3#option.short],
[$-, ?SHORT(Short)],
[$-, ?SHORT(ShortArg)], "value1",
[$-, ?SHORT(ShortInt)], "100",
[$-, ?SHORT(Short2), ?SHORT(Short3)],
"--long",
"--long-arg", "value2",
"--long-int", "101",
[$-, ShortLong#option.short],
[$-, ?SHORT(ShortLong)],
"--short-long-arg", "value3",
"--short-long-int", "103",
"value4",
@ -153,22 +82,22 @@ parse_1_test_() ->
],
CombinedOpts =
[
Short#option.name,
{ShortArg#option.name, "value1"},
{ShortInt#option.name, 100},
Short2#option.name,
Short3#option.name,
Long#option.name,
{LongArg#option.name, "value2"},
{LongInt#option.name, 101},
ShortLong#option.name,
{ShortLongArg#option.name, "value3"},
{ShortLongInt#option.name, 103},
{NonOptArg#option.name, "value4"},
{NonOptInt#option.name, 104},
{ShortDefArg#option.name, "default-short"},
{LongDefArg#option.name, "default-long"},
{ShortLongDefArg#option.name, "default-short-long"}
?NAME(Short),
{?NAME(ShortArg), "value1"},
{?NAME(ShortInt), 100},
?NAME(Short2),
?NAME(Short3),
?NAME(Long),
{?NAME(LongArg), "value2"},
{?NAME(LongInt), 101},
?NAME(ShortLong),
{?NAME(ShortLongArg), "value3"},
{?NAME(ShortLongInt), 103},
{?NAME(NonOptArg), "value4"},
{?NAME(NonOptInt), 104},
{?NAME(ShortDefArg), "default-short"},
{?NAME(LongDefArg), "default-long"},
{?NAME(ShortLongDefArg), "default-short-long"}
],
CombinedRest = ["dummy1", "dummy2"],
@ -184,41 +113,41 @@ parse_1_test_() ->
{"Unused options and arguments",
?_assertMatch({ok, {[], ["arg1", "arg2"]}}, parse([Short], ["arg1", "arg2"]))},
%% Options with only the short form
{Short#option.help, ?_assertMatch({ok, {[short], []}}, parse([Short], [[$-, Short#option.short]]))},
{ShortArg#option.help, ?_assertMatch({ok, {[{short_arg, "value"}], []}}, parse([ShortArg], [[$-, ShortArg#option.short], "value"]))},
{ShortDefArg#option.help, ?_assertMatch({ok, {[{short_def_arg, "default-short"}], []}}, parse([ShortDefArg], []))},
{ShortInt#option.help, ?_assertMatch({ok, {[{short_int, 100}], []}}, parse([ShortInt], [[$-, ShortInt#option.short], "100"]))},
{?HELP(Short), ?_assertEqual({ok, {[short], []}}, parse([Short], [[$-, ?SHORT(Short)]]))},
{?HELP(ShortArg), ?_assertEqual({ok, {[{short_arg, "value"}], []}}, parse([ShortArg], [[$-, ?SHORT(ShortArg)], "value"]))},
{?HELP(ShortDefArg), ?_assertMatch({ok, {[{short_def_arg, "default-short"}], []}}, parse([ShortDefArg], []))},
{?HELP(ShortInt), ?_assertEqual({ok, {[{short_int, 100}], []}}, parse([ShortInt], [[$-, ?SHORT(ShortInt)], "100"]))},
{"Unsorted multiple short form options and arguments in a single string",
?_assertMatch({ok, {[short, short2, short3], ["arg1", "arg2"]}}, parse([Short, Short2, Short3], "arg1 -abc arg2"))},
{"Short form option and arguments",
?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], [[$-, Short#option.short], "arg1", "arg2"]))},
?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], [[$-, ?SHORT(Short)], "arg1", "arg2"]))},
{"Short form option and arguments (unsorted)",
?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], ["arg1", [$-, Short#option.short], "arg2"]))},
?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], ["arg1", [$-, ?SHORT(Short)], "arg2"]))},
%% Options with only the long form
{Long#option.help, ?_assertMatch({ok, {[long], []}}, parse([Long], ["--long"]))},
{LongArg#option.help, ?_assertMatch({ok, {[{long_arg, "value"}], []}}, parse([LongArg], ["--long-arg", "value"]))},
{LongDefArg#option.help, ?_assertMatch({ok, {[{long_def_arg, "default-long"}], []}}, parse([LongDefArg], []))},
{LongInt#option.help, ?_assertMatch({ok, {[{long_int, 100}], []}}, parse([LongInt], ["--long-int", "100"]))},
{?HELP(Long), ?_assertMatch({ok, {[long], []}}, parse([Long], ["--long"]))},
{?HELP(LongArg), ?_assertMatch({ok, {[{long_arg, "value"}], []}}, parse([LongArg], ["--long-arg", "value"]))},
{?HELP(LongDefArg), ?_assertMatch({ok, {[{long_def_arg, "default-long"}], []}}, parse([LongDefArg], []))},
{?HELP(LongInt), ?_assertMatch({ok, {[{long_int, 100}], []}}, parse([LongInt], ["--long-int", "100"]))},
{"Long form option and arguments",
?_assertMatch({ok, {[long], ["arg1", "arg2"]}}, parse([Long], ["--long", "arg1", "arg2"]))},
{"Long form option and arguments (unsorted)",
?_assertMatch({ok, {[long], ["arg1", "arg2"]}}, parse([Long], ["arg1", "--long", "arg2"]))},
%% Options with both the short and long form
{ShortLong#option.help, ?_assertMatch({ok, {[short_long], []}}, parse([ShortLong], [[$-, ShortLong#option.short]]))},
{ShortLong#option.help, ?_assertMatch({ok, {[short_long], []}}, parse([ShortLong], ["--short-long"]))},
{ShortLongArg#option.help, ?_assertMatch({ok, {[{short_long_arg, "value"}], []}}, parse([ShortLongArg], [[$-, ShortLongArg#option.short], "value"]))},
{ShortLongArg#option.help, ?_assertMatch({ok, {[{short_long_arg, "value"}], []}}, parse([ShortLongArg], ["--short-long-arg", "value"]))},
{ShortLongDefArg#option.help, ?_assertMatch({ok, {[{short_long_def_arg, "default-short-long"}], []}}, parse([ShortLongDefArg], []))},
{ShortLongInt#option.help, ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], [[$-, ShortLongInt#option.short], "1234"]))},
{ShortLongInt#option.help, ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], ["--short-long-int", "1234"]))},
{?HELP(ShortLong), ?_assertEqual({ok, {[short_long], []}}, parse([ShortLong], [[$-, ?SHORT(ShortLong)]]))},
{?HELP(ShortLong), ?_assertMatch({ok, {[short_long], []}}, parse([ShortLong], ["--short-long"]))},
{?HELP(ShortLongArg), ?_assertEqual({ok, {[{short_long_arg, "value"}], []}}, parse([ShortLongArg], [[$-, ?SHORT(ShortLongArg)], "value"]))},
{?HELP(ShortLongArg), ?_assertMatch({ok, {[{short_long_arg, "value"}], []}}, parse([ShortLongArg], ["--short-long-arg", "value"]))},
{?HELP(ShortLongDefArg), ?_assertMatch({ok, {[{short_long_def_arg, "default-short-long"}], []}}, parse([ShortLongDefArg], []))},
{?HELP(ShortLongInt), ?_assertEqual({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], [[$-, ?SHORT(ShortLongInt)], "1234"]))},
{?HELP(ShortLongInt), ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], ["--short-long-int", "1234"]))},
%% Non-option arguments
{NonOptArg#option.help, ?_assertMatch({ok, {[{non_opt_arg, "value"}], []}}, parse([NonOptArg], ["value"]))},
{NonOptInt#option.help, ?_assertMatch({ok, {[{non_opt_int, 1234}], []}}, parse([NonOptInt], ["1234"]))},
{?HELP(NonOptArg), ?_assertMatch({ok, {[{non_opt_arg, "value"}], []}}, parse([NonOptArg], ["value"]))},
{?HELP(NonOptInt), ?_assertMatch({ok, {[{non_opt_int, 1234}], []}}, parse([NonOptInt], ["1234"]))},
{"Declared and undeclared non-option arguments",
?_assertMatch({ok, {[{non_opt_arg, "arg1"}], ["arg2", "arg3"]}}, parse([NonOptArg], ["arg1", "arg2", "arg3"]))},
%% Combined
{"Combined short, long and non-option arguments",
?_assertEqual({ok, {CombinedOpts, CombinedRest}}, parse(CombinedOptSpecs, CombinedArgs))},
{"Option with only short form and invalid integer argument",
?_assertEqual({error, {invalid_option_arg, {ShortInt#option.name, "value"}}}, parse([ShortInt], [[$-, ShortInt#option.short], "value"]))}
?_assertEqual({error, {invalid_option_arg, {?NAME(ShortInt), "value"}}}, parse([ShortInt], [[$-, ?SHORT(ShortInt)], "value"]))}
].