From 4b03285d9ef6e84691ef1c4b621958254e4864fb Mon Sep 17 00:00:00 2001 From: Juan Jose Comellas Date: Mon, 16 Nov 2009 19:20:32 -0300 Subject: [PATCH] Dropped record syntax for the option specifications to simplify use from escripts. --- ChangeLog | 9 ++ LICENSE.txt | 6 +- README.markdown | 98 +++++++------------ examples/ex1.escript | 51 ++++++++++ include/getopt.hrl | 23 ----- src/examples/ex1.erl | 63 +++--------- src/getopt.erl | 146 ++++++++++++++++++---------- src/test/getopt_test.erl | 203 +++++++++++++-------------------------- 8 files changed, 270 insertions(+), 329 deletions(-) create mode 100644 ChangeLog create mode 100755 examples/ex1.escript delete mode 100644 include/getopt.hrl diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..18fc180 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,9 @@ +2009-11-16 Juan Jose Comellas + + * *: 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. diff --git a/LICENSE.txt b/LICENSE.txt index 3289c08..755f847 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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 diff --git a/README.markdown b/README.markdown index cbb7b77..be4a100 100644 --- a/README.markdown +++ b/README.markdown @@ -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 ] [-p ] [--dbname ] [-x] + Usage: ex.escript [-h ] [-p ] [--dbname ] [-x] -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. diff --git a/examples/ex1.escript b/examples/ex1.escript new file mode 100755 index 0000000..609084d --- /dev/null +++ b/examples/ex1.escript @@ -0,0 +1,51 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +%%! -sname ex1 -pz ebin + +%%%------------------------------------------------------------------- +%%% @author Juan Jose Comellas +%%% @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"} + ]. diff --git a/include/getopt.hrl b/include/getopt.hrl deleted file mode 100644 index c2054be..0000000 --- a/include/getopt.hrl +++ /dev/null @@ -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 - }). diff --git a/src/examples/ex1.erl b/src/examples/ex1.erl index 19889d8..e84d1a5 100644 --- a/src/examples/ex1.erl +++ b/src/examples/ex1.erl @@ -1,6 +1,6 @@ %%%------------------------------------------------------------------- -%%% @author Juan Jose Comellas -%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com) +%%% @author Juan Jose Comellas +%%% @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 '). - --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"} ]. diff --git a/src/getopt.erl b/src/getopt.erl index 5312f10..b335f0c 100644 --- a/src/getopt.erl +++ b/src/getopt.erl @@ -1,6 +1,6 @@ %%%------------------------------------------------------------------- -%%% @author Juan Jose Comellas -%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com) +%%% @author Juan Jose Comellas +%%% @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 '). - --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 -> diff --git a/src/test/getopt_test.erl b/src/test/getopt_test.erl index f81588c..793d6d7 100644 --- a/src/test/getopt_test.erl +++ b/src/test/getopt_test.erl @@ -1,6 +1,6 @@ %%%------------------------------------------------------------------- -%%% @author Juan Jose Comellas -%%% @copyright (C) 2009, Novamens SA (http://www.novamens.com) +%%% @author Juan Jose Comellas +%%% @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 '). +-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"]))} ].