Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Pulled getopt.erl from HEAD of github.com/jcomellas/getopt #117

Closed
wants to merge 3 commits into from

6 participants

Serge Aleynikov Tuncer Ayaz Alex Carol Jared Morrow Tristan Sloughter Fred Hebert
Serge Aleynikov

No description provided.

Serge Aleynikov saleyn referenced this pull request
Closed

getopt #116

Tuncer Ayaz

+1

Serge Aleynikov

Please hold on applying this request - I have another getopt improvement pending: jcomellas/getopt#25.
Will submit an update when Juan applies it to his repos.

Alex Carol

Why is the file not being required as a dependency? Is it a rebar policy to not fetch dependencies using rebar? (i understand that it could be an issue if rebar was broken, but could be easily solved using the last stable build, wouldn't it?)

Jared Morrow
Owner

@saleyn are we still waiting on this, or should we close this?

Serge Aleynikov

Yes, https://github.com/jcomellas promised to review the pull request jcomellas/getopt#25 a while back, but I haven't seen an update from him.

Tristan Sloughter
Owner

Is this still needed? If so, please rebase and push so it can be merged cleanly.

saleyn added some commits
Serge Aleynikov saleyn Merge branch 'master' of https://github.com/rebar/rebar 916a883
Serge Aleynikov saleyn Added convenience parsing and checking getopt functions
See documentation of `Checking required options` and
`Converting parsed list of arguments to a record` sections here:
https://github.com/saleyn/getopt/tree/format_error
367812e
Serge Aleynikov

I rebased. Since you guys renamed original getopt to rebar_getopt module, it doesn't seem there is any more dependency on the jcomellas/getopt#25 pull request. It's up to you if you want to add this functionality to rebar_getopt, but I find it quite useful. Read "Checking required options" and "Converting parsed list of arguments to a record" in https://github.com/saleyn/getopt/blob/format_error/README.md.

Tristan Sloughter tsloughter closed this
Tuncer Ayaz

@tsloughter why did you close this?

Fred Hebert
Owner

Hi, this issue was closed in an attempt to do quick basic filtering, with the benediction of rebar project owners. These issues and pull requests are not issues or code we're spitting on, but given the burden of the task and how much code rot may have happened since these were open is unknown from maintainers at this time. All tickets prior to March 2014 were closed and will be reopened on a per-request basis if we see interest from the reporter or contributor, or if some of the issues reported are still valid after the various patches that have made it since they were opened.

This is a fairly brutal first step to help us get a proper understanding of what is still valid or not, but that has been proven efficient in the past. Sorry for the inconvenience, things should go smoother from there on.

Tuncer Ayaz

The new functionality looks useful to me for -j and -v, so once that patch has been merged (cc @jcomellas), we should update our copy.

Serge Aleynikov

It seems to me that there's no longer a need to wait for merging of the patch by jcomellas since rebar_getopt was renamed from getopt and now lives in rebar by its own. Though the proposed additions to getopt don't functionally add anything to rebar, they do add ability to validate options and throw exceptions, as well as convert the parsed options to a record for easier processing, which is something rebar could use at some point down the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 19, 2013
  1. Serge Aleynikov
Commits on Jun 14, 2014
  1. Serge Aleynikov
  2. Serge Aleynikov

    Added convenience parsing and checking getopt functions

    saleyn authored
    See documentation of `Checking required options` and
    `Converting parsed list of arguments to a record` sections here:
    https://github.com/saleyn/getopt/tree/format_error
This page is out of date. Refresh to see the latest.
Showing with 103 additions and 10 deletions.
  1. +103 −10 src/rebar_getopt.erl
113 src/rebar_getopt.erl
View
@@ -11,7 +11,10 @@
-module(rebar_getopt).
-author('juanjo@comellas.org').
--export([parse/2, check/2, parse_and_check/2, format_error/2,
+-export([parse/2, check/2, check/3,
+ parse_and_check/2, parse_and_check/3,
+ to_record/3, to_record/4,
+ format_error/2,
usage/2, usage/3, usage/4, tokenize/1]).
-export([usage_cmd_line/2]).
@@ -39,6 +42,10 @@
-type simple_option() :: atom().
-type compound_option() :: {atom(), arg_value()}.
-type option() :: simple_option() | compound_option().
+%% Option types for configuration option checking.
+-type check_option() :: help | {skip, [Name::atom()]}.
+-type check_options() :: [ check_option() ].
+
%% Command line option specification.
-type option_spec() :: {
Name :: atom(),
@@ -66,14 +73,28 @@
-spec parse_and_check([option_spec()], string() | [string()]) ->
{ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: term()}}.
parse_and_check(OptSpecList, CmdLine) when is_list(OptSpecList), is_list(CmdLine) ->
+ parse_and_check(OptSpecList, CmdLine, []).
+
+%% @doc Parse the command line options and arguments returning a list of tuples
+%% and/or atoms using the Erlang convention for sending options to a
+%% function. Additionally perform check if all required options (the ones
+%% without default values) are present. The function is a combination of
+%% two calls: parse/2 and check/2. The `CheckOpts' argument allows to specify
+%% a list of options to exclude from the required check (via `{skip, [Name::atom()]}')
+%% and to specify `help', which will ignore required options check if
+%% `CmdLine' contains help switch and `help' option is present in the `OptSpecList'.
+-spec parse_and_check([option_spec()], string() | [string()], check_options()) ->
+ {ok, {[option()], [string()]}} | help | {error, {Reason :: atom(), Data :: term()}}.
+parse_and_check(OptSpecList, CmdLine, CheckOpts)
+ when is_list(OptSpecList), is_list(CmdLine), is_list(CheckOpts) ->
case parse(OptSpecList, CmdLine) of
{ok, {Opts, _}} = Result ->
- case check(OptSpecList, Opts) of
+ case check(OptSpecList, Opts, CheckOpts) of
ok -> Result;
- Error -> Error
+ Other -> Other
end;
- Error ->
- Error
+ Other ->
+ Other
end.
%% @doc Check the parsed command line arguments returning ok if all required
@@ -82,9 +103,32 @@ parse_and_check(OptSpecList, CmdLine) when is_list(OptSpecList), is_list(CmdLine
-spec check([option_spec()], [option()]) ->
ok | {error, {Reason :: atom(), Option :: atom()}}.
check(OptSpecList, ParsedOpts) when is_list(OptSpecList), is_list(ParsedOpts) ->
+ check(OptSpecList, ParsedOpts, []).
+
+%% @doc Check the parsed command line arguments returning ok if all required
+%% options (i.e. that don't have defaults) are present, and returning
+%% error otherwise. The `CheckOpts' argument allows to specify
+%% a list of options to exclude from the required check (via `{skip, [Name::atom()]}')
+%% and to specify `help', which will ignore required options check if
+%% `CmdLine' contains help switch and `help' option is present in the `OptSpecList'.
+
+-spec check([option_spec()], [option()], check_options()) ->
+ ok | help | {error, {Reason :: atom(), Option :: atom()}}.
+check(OptSpecList, ParsedOpts, CheckOpts)
+ when is_list(OptSpecList), is_list(ParsedOpts), is_list(CheckOpts) ->
try
+ CheckHelp = proplists:get_value(help, CheckOpts, false),
+ HasHelp = lists:keymember(help, 1, OptSpecList)
+ andalso proplists:get_value(help, ParsedOpts, false),
+ SkipOpts = proplists:get_value(skip, CheckOpts, []),
+
+ {CheckHelp, HasHelp} =:= {true, true}
+ andalso throw(help),
+
+ % Ignore checking of options present in the {skip, Skip} list
RequiredOpts = [Name || {Name, _, _, Arg, _} <- OptSpecList,
- not is_tuple(Arg) andalso Arg =/= undefined],
+ not is_tuple(Arg), Arg =/= undefined,
+ not lists:member(Name, SkipOpts)],
lists:foreach(fun (Option) ->
case proplists:is_defined(Option, ParsedOpts) of
true ->
@@ -94,6 +138,8 @@ check(OptSpecList, ParsedOpts) when is_list(OptSpecList), is_list(ParsedOpts) ->
end
end, RequiredOpts)
catch
+ throw:help ->
+ help;
_:Error ->
Error
end.
@@ -142,6 +188,45 @@ parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
%% not present but had default arguments in the specification.
{ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}.
+%% @doc Convert options from a list into a record.
+%% The `FieldNames' list is the result of a call to `record_info(fields, Record)', and
+%% `Record' is the initial value of the record to be populated with option values.
+-spec to_record([option()], [atom()], tuple()) -> tuple().
+to_record(Options, FieldNames, Record) ->
+ to_record(Options, FieldNames, Record, fun(_,_,V) -> {ok, V} end).
+
+%% @doc Convert options from a list into a record.
+%% This function is equivalent to `opts_to_record/3' except for taking another
+%% `Validate' argument, which is a function
+%% `(Field, OldFieldValue, OptionValue) -> {ok, NewFieldValue} | ignore'
+%% used for validating the `Field' before it's assigned to the corresponding field
+%% in the `Record'. Options with `undefined' values are skipped.
+-spec to_record([option()], [atom()], tuple(),
+ fun((atom(), term(), term()) -> {ok, term()} | ignore)) -> tuple().
+to_record(Options, RecordFieldNames, Record, Validate) when is_function(Validate, 3) ->
+ lists:foldl(fun
+ ({_Opt,undefined}, Rec) ->
+ Rec;
+ (Opt, Rec) when is_atom(Opt) ->
+ set_val(Opt, true, Rec, RecordFieldNames, Validate);
+ ({Opt, Value}, Rec) ->
+ set_val(Opt, Value, Rec, RecordFieldNames, Validate)
+ end, Record, Options).
+
+set_val(Opt, Value, Rec, RecordFieldNames, Validate) ->
+ I = pos(RecordFieldNames, Opt, 2),
+ case Validate(Opt, old_val(I, Rec), Value) of
+ {ok, V} when I > 1 -> setelement(I, Rec, V);
+ {ok, _} -> throw({field_not_found, Opt, RecordFieldNames});
+ ignore -> Rec
+ end.
+
+old_val(0,_Rec) -> undefined;
+old_val(N, Rec) -> element(N, Rec).
+
+pos([], _, _) -> 0;
+pos([H|_], H, N) -> N;
+pos([_|T], H, N) -> pos(T, H, N+1).
%% @doc Format the error code returned by prior call to parse/2 or check/2.
-spec format_error([option_spec()], {error, {Reason :: atom(), Data :: term()}} |
@@ -151,12 +236,20 @@ format_error(OptSpecList, {error, Reason}) ->
format_error(OptSpecList, {missing_required_option, Name}) ->
{_Name, Short, Long, _Type, _Help} = lists:keyfind(Name, 1, OptSpecList),
lists:flatten(["missing required option: -", [Short], " (", to_string(Long), ")"]);
-format_error(_OptSpecList, {invalid_option, OptStr}) ->
- lists:flatten(["invalid option: ", to_string(OptStr)]);
-format_error(_OptSpecList, {invalid_option_arg, {Name, Arg}}) ->
- lists:flatten(["option \'", to_string(Name) ++ "\' has invalid argument: ", to_string(Arg)]);
+format_error(OptSpecList, {missing_option_arg, Name}) ->
+ Opt = lists:keyfind(Name, 1, OptSpecList),
+ lists:flatten(["missing required option argument: -", [element(2,Opt)], " (",
+ to_string(Name), ")"]);
+format_error(OptSpecList, {invalid_option_arg, {Name, Arg}}) ->
+ L = case lists:keyfind(Name, 1, OptSpecList) of
+ {_, Short, undefined, _, _} -> [$-, Short, $ , to_string(Arg)];
+ {_, _, Long, _, _} -> ["--", Long, $=, to_string(Arg)]
+ end,
+ lists:flatten(["option \'", to_string(Name) ++ "\' has invalid argument: ", L]);
format_error(_OptSpecList, {invalid_option_arg, OptStr}) ->
lists:flatten(["invalid option argument: ", to_string(OptStr)]);
+format_error(_OptSpecList, {invalid_option, OptStr}) ->
+ lists:flatten(["invalid option: ", to_string(OptStr)]);
format_error(_OptSpecList, {Reason, Data}) ->
lists:flatten([to_string(Reason), " ", to_string(Data)]).
Something went wrong with that request. Please try again.