Add support for repetitions of options with integer arguments

This commit adds support for a type of command-line options that are
commonly used by many GNU tools. Now with this modification if you
have an option named 'verbose' with an integer argument and you enter
"-vvv" as in the command line you'll get {verbose, 3} as a result.
This commit is contained in:
Juan Jose Comellas
2012-01-17 00:33:40 -03:00
parent 2379dc96b2
commit 9283bc0697
5 changed files with 122 additions and 84 deletions
+99 -49
View File
@@ -93,7 +93,7 @@ parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptS
parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) ->
case find_non_option_arg(OptSpecList, ArgPos) of
{value, OptSpec} when ?IS_OPT_SPEC(OptSpec) ->
parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc],
parse(OptSpecList, add_option_arg(OptSpec, Arg, OptAcc),
ArgAcc, ArgPos + 1, Tail);
false ->
parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail)
@@ -147,7 +147,7 @@ parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Lon
undefined ->
throw({error, {invalid_option_arg, OptStr}});
_ ->
parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Args)
parse(OptSpecList, add_option_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args)
end;
false ->
throw({error, {invalid_option, OptStr}})
@@ -176,6 +176,7 @@ split_assigned_arg(OptStr, [], _Acc) ->
%% -afoo Single option 'a', argument "foo"
%% -abc Multiple options: 'a'; 'b'; 'c'
%% -bcafoo Multiple options: 'b'; 'c'; 'a' with argument "foo"
%% -aaa Multiple repetitions of option 'a' (only valid for options with integer arguments)
-spec parse_option_short([option_spec()], [option()], [string()], integer(), [string()], string(), string()) ->
{ok, {[option()], [string()]}}.
parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, [Short | Arg]) ->
@@ -186,16 +187,22 @@ parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, [Short | A
{_Name, Short, _Long, ArgSpec, _Help} = OptSpec ->
case Arg of
[] ->
% The option argument string is empty, but the option requires
% an argument, so we look into the next string in the list.
%% The option argument string is empty, but the option requires
%% an argument, so we look into the next string in the list.
parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec);
_ ->
case is_valid_arg(ArgSpec, Arg) of
true ->
parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Args);
parse(OptSpecList, add_option_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args);
_ ->
parse_option_short(OptSpecList, [convert_option_no_arg(OptSpec) | OptAcc], ArgAcc, ArgPos, Args, OptStr, Arg)
%% There are 2 valid cases in which we may not receive the expected argument:
%% 1) When the expected argument is a boolean: in this case the presence
%% of the option makes the argument true.
%% 2) When the expected argument is an integer: in this case the presence
%% of the option sets the value to 1 and any additional appearances of
%% the option increment it by 1 (e.g. "-vvv" would return {verbose, 3}).
parse_option_short(OptSpecList, add_option_no_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args, OptStr, Arg)
end
end;
@@ -207,25 +214,34 @@ parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, []) ->
%% @doc Retrieve the argument for an option from the next string in the list of
%% command-line parameters.
%% command-line parameters or set the value of the argument from the argument
%% specification (for boolean and integer arguments), if possible.
parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail] = Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) ->
% Special case for booleans: when the next string is an option we assume
% the value is 'true'.
case (arg_spec_type(ArgSpec) =:= boolean) andalso not is_boolean_arg(Arg) of
ArgSpecType = arg_spec_type(ArgSpec),
case (ArgSpecType =:= boolean) andalso not is_boolean_arg(Arg) of
true ->
parse(OptSpecList, [{Name, true} | OptAcc], ArgAcc, ArgPos, Args);
_ ->
parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Tail)
%% Special case for booleans: when the next string is not a boolean
%% argument we assume the value is 'true'.
parse(OptSpecList, add_arg(OptSpec, true, OptAcc), ArgAcc, ArgPos, Args);
false ->
case (ArgSpecType =:= integer) andalso not is_integer_arg(Arg) of
true ->
%% Special case for integer arguments: if the option had not been set
%% before we set the value to 1; if not we increment the previous value
%% the option had. This is needed to support options like "-vvv" to
%% return something like {verbose, 3}.
parse(OptSpecList, add_implicit_integer_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
false ->
try
parse(OptSpecList, add_arg(OptSpec, to_type(ArgSpecType, Arg), OptAcc), ArgAcc, ArgPos, Tail)
catch
error:_ ->
throw({error, {invalid_option_arg, {Name, Arg}}})
end
end
end;
parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [] = Args, {Name, _Short, _Long, ArgSpec, _Help}) ->
% Special case for booleans: when the next string is missing we assume the
% value is 'true'.
case arg_spec_type(ArgSpec) of
boolean ->
parse(OptSpecList, [{Name, true} | OptAcc], ArgAcc, ArgPos, Args);
_ ->
throw({error, {missing_option_arg, Name}})
end.
parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [] = Args, OptSpec) ->
parse(OptSpecList, add_option_no_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args).
%% @doc Find the option for the discrete argument in position specified in the
@@ -259,32 +275,72 @@ append_default_options([], OptAcc) ->
OptAcc.
-spec convert_option_no_arg(option_spec()) -> compound_option().
convert_option_no_arg({Name, _Short, _Long, ArgSpec, _Help}) ->
case ArgSpec of
% Special case for booleans: if there is no argument we assume
% the value is 'true'.
{boolean, _DefaultValue} ->
{Name, true};
%% @doc Add an option with no argument.
-spec add_option_no_arg(option_spec(), [option()]) -> [option()].
add_option_no_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptAcc) ->
case arg_spec_type(ArgSpec) of
boolean ->
{Name, true};
_ ->
%% Special case for boolean arguments: if there is no argument we
%% set the value to 'true'.
add_arg(OptSpec, true, OptAcc);
integer ->
%% Special case for integer arguments: if the option had not been set
%% before we set the value to 1; if not we increment the previous value
%% the option had. This is needed to support options like "-vvv" to
%% return something like {verbose, 3}.
add_implicit_integer_arg(OptSpec, OptAcc);
true ->
throw({error, {missing_option_arg, Name}})
end.
%% @doc Convert the argument passed in the command line to the data type
%% indicated by the argument specification.
-spec convert_option_arg(option_spec(), string()) -> compound_option().
convert_option_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg) ->
try
{Name, to_type(arg_spec_type(ArgSpec), Arg)}
catch
error:_ ->
throw({error, {invalid_option_arg, {Name, Arg}}})
%% @doc Add an option with argument converting it to the data type indicated by the
%% argument specification.
-spec add_option_arg(option_spec(), string(), [option()]) -> [option()].
add_option_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) ->
ArgSpecType = arg_spec_type(ArgSpec),
case (ArgSpecType =:= boolean) andalso not is_boolean_arg(Arg) of
true ->
%% Special case for booleans: when the next string is not a boolean
%% argument we assume the value is 'true'.
add_arg(OptSpec, true, OptAcc);
false ->
case (ArgSpecType =:= integer) andalso not is_integer_arg(Arg) of
true ->
%% Special case for integer arguments: if the option had not been set
%% before we set the value to 1; if not we increment the previous value
%% the option had. This is needed to support options like "-vvv" to
%% return something like {verbose, 3}.
add_implicit_integer_arg(OptSpec, OptAcc);
false ->
try
add_arg(OptSpec, to_type(ArgSpecType, Arg), OptAcc)
catch
error:_ ->
throw({error, {invalid_option_arg, {Name, Arg}}})
end
end
end.
%% Add an option with an integer argument.
-spec add_implicit_integer_arg(option_spec(), [option()]) -> [option()].
add_implicit_integer_arg({Name, _Short, _Long, _ArgSpec, _Help}, OptAcc) ->
case lists:keyfind(Name, 1, OptAcc) of
{Name, Count} ->
lists:keyreplace(Name, 1, OptAcc, {Name, Count + 1});
false ->
[{Name, 1} | OptAcc]
end.
%% @doc Add an option with an argument and convert it to the data type corresponding
%% to the argument specification.
-spec add_arg(option_spec(), string(), [option()]) -> [option()].
add_arg({Name, _Short, _Long, _ArgSpec, _Help}, Arg, OptAcc) ->
[{Name, Arg} | lists:keydelete(Name, 1, OptAcc)].
%% @doc Retrieve the data type form an argument specification.
-spec arg_spec_type(arg_spec()) -> arg_type() | undefined.
arg_spec_type({Type, _DefaultArg}) ->
@@ -382,16 +438,13 @@ usage(OptSpecList, ProgramName) ->
%% @doc Show a message on stderr or stdout indicating the command line options and
%% arguments that are supported by the program.
-spec usage([option_spec()], string(), output_stream()) -> ok.
-spec usage([option_spec()], string(), output_stream() | string()) -> ok.
usage(OptSpecList, ProgramName, OutputStream) when is_atom(OutputStream) ->
io:format(OutputStream, "Usage: ~s~s~n~n~s~n",
[ProgramName, usage_cmd_line(OptSpecList), usage_options(OptSpecList)]);
%% @doc Show a message on stderr indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail argument
%% is a string that is added to the end of the usage command line.
-spec usage([option_spec()], string(), string()) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail) ->
usage(OptSpecList, ProgramName, CmdLineTail, standard_error).
@@ -399,17 +452,14 @@ usage(OptSpecList, ProgramName, CmdLineTail) ->
%% @doc Show a message on stderr or stdout indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail argument
%% is a string that is added to the end of the usage command line.
-spec usage([option_spec()], string(), string(), output_stream()) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail) when is_atom(OutputStream) ->
-spec usage([option_spec()], string(), string(), output_stream() | [{string(), string()}]) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) ->
io:format(OutputStream, "Usage: ~s~s ~s~n~n~s~n",
[ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, usage_options(OptSpecList)]);
%% @doc Show a message on stderr indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail and OptionsTail
%% arguments are a string that is added to the end of the usage command line
%% and a list of tuples that are added to the end of the options' help lines.
-spec usage([option_spec()], string(), string(), [{string(), string()}]) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) ->
usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error).