Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Cleanup option parsing #23

Merged
6 commits merged into from

1 participant

@jlouis

This patch set cleans up option parsing and makes the agner command output a list of commands it understands. It somewhat makes sense that it is a merge rather than a rebase IMO as it has to take into account that something happened on the main branch.

jlouis added some commits
@jlouis jlouis Split agner:main/1 into its constituents.
The agner:main/1 function actually did several things at once:

* Provided parsing of command-line options
* Provided handling of commands
* Printed out general usage

This change splits the concerns. First, we add a function to parse the
command line options and internalize them. Next, we alter the
command-handlers to match on the parsed variant. The change also
allows us to hoist the start()/stop() invocation to main/1 and only do
that in one place.

Further, the change paves the way for a more consistent option parsing
and argument handling further down the road.
3990fe9
@jlouis jlouis Gather option parsing into the parsing primitives.
Rather than have option parsing spread out over the command handler
function, gather all calls into a single point. This means that all
option parsing is now handled in one place.
2483ee4
@jlouis jlouis Don't use our own tmpfile(3) implementation
Even though the one in the stdlib is not much better.
5a8c30a
@jlouis jlouis Transform the command-line parser into data.
Rather than write code which acts as data, transform the code into
data and write code acting on the data. This paves the way for
outputting the commands in the usage.
3d151ff
@jlouis jlouis Make 'agner' output a useful usage listing.
Reap the benefits of the preceding commits: Output a usage list where
the valid, supported, commands are included. Note that adding a
command to the parser will automatically extend the usage output.
ebbf23e
@jlouis jlouis Merge remote branch 'upstream/master' into cleanup-option-parsing-old
Conflicts:
	src/agner.erl
4b9a49d
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 28, 2011
  1. @jlouis

    Split agner:main/1 into its constituents.

    jlouis authored
    The agner:main/1 function actually did several things at once:
    
    * Provided parsing of command-line options
    * Provided handling of commands
    * Printed out general usage
    
    This change splits the concerns. First, we add a function to parse the
    command line options and internalize them. Next, we alter the
    command-handlers to match on the parsed variant. The change also
    allows us to hoist the start()/stop() invocation to main/1 and only do
    that in one place.
    
    Further, the change paves the way for a more consistent option parsing
    and argument handling further down the road.
  2. @jlouis

    Gather option parsing into the parsing primitives.

    jlouis authored
    Rather than have option parsing spread out over the command handler
    function, gather all calls into a single point. This means that all
    option parsing is now handled in one place.
  3. @jlouis

    Don't use our own tmpfile(3) implementation

    jlouis authored
    Even though the one in the stdlib is not much better.
  4. @jlouis

    Transform the command-line parser into data.

    jlouis authored
    Rather than write code which acts as data, transform the code into
    data and write code acting on the data. This paves the way for
    outputting the commands in the usage.
  5. @jlouis

    Make 'agner' output a useful usage listing.

    jlouis authored
    Reap the benefits of the preceding commits: Output a usage list where
    the valid, supported, commands are included. Note that adding a
    command to the parser will automatically extend the usage output.
  6. @jlouis
This page is out of date. Refresh to see the latest.
Showing with 106 additions and 80 deletions.
  1. +106 −80 src/agner_main.erl
View
186 src/agner_main.erl
@@ -8,16 +8,75 @@ start() ->
stop() ->
error_logger:delete_report_handler(error_logger_tty_h),
agner:stop().
-
-main(["spec"|Args]) ->
+
+arg_proplist() ->
+ [{"spec",
+ {spec,
+ "Output the specification of a package",
+ [
+ {package, undefined, undefined, string, "Package name"},
+ {browser, $b, "browser", boolean, "Show specification in the browser"},
+ {homepage, $h, "homepage", boolean, "Show package homepage in the browser"},
+ {version, $v, "version", {string, "@master"}, "Version"}
+ ]}},
+ {"versions",
+ {versions,
+ "Show the avilable releases and flavours of a package",
+ [
+ {package, undefined, undefined, string, "Package name"}
+ ]}},
+ {"list",
+ {list,
+ "List packages on stdout",
+ [
+ {descriptions, $d, "descriptions", {boolean, false}, "Show package descriptions"},
+ {properties, $p, "properties", string, "Comma-separated list of properties to show"}
+ ]}},
+ {"fetch",
+ {fetch,
+ "Download a package",
+ [
+ {package, undefined, undefined, string, "Package name"},
+ {directory, undefined, undefined, string, "Directory to check package out to"},
+ {version, $v, "version", {string, "@master"}, "Version"}
+ ]}},
+ {"verify",
+ {verify,
+ "Verify the integrity of a .agner configuration file",
+ [
+ {spec, undefined, undefined, {string, "agner.config"}, "Specification file (agner.config by default)"}
+ ]}}].
+
+command_descriptions() ->
+ [{Cmd, Desc} || {Cmd, {_Atom, Desc, _Opts}} <- arg_proplist()].
+
+parse_args([Arg|Args]) ->
+ case proplists:get_value(Arg, arg_proplist()) of
+ undefined -> no_parse;
+ {A, _Desc, OptSpec} -> {arg, A, Args, OptSpec}
+ end;
+parse_args(_) -> no_parse.
+
+usage() ->
OptSpec = [
- {package, undefined, undefined, string, "Package name"},
- {browser, $b, "browser", boolean, "Show specification in the browser"},
- {homepage, $h, "homepage", boolean, "Show package homepage in the browser"},
- {version, $v, "version", {string, "@master"}, "Version"}
- ],
- start(),
- {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
+ {command, undefined, undefined, string, "Command to be executed (e.g. spec)"}
+ ],
+ getopt:usage(OptSpec, "agner", "[options ...]"),
+ io:format("Valid commands are:~n", []),
+ [io:format(" ~-10s ~s~n", [Cmd, Desc]) || {Cmd, Desc} <- command_descriptions()].
+
+main(Args) ->
+ case parse_args(Args) of
+ {arg, Command, ExtraArgs, OptSpec} ->
+ start(),
+ {ok, {Opts, _}} = getopt:parse(OptSpec, ExtraArgs),
+ handle_command(Command, Opts),
+ stop();
+ no_parse ->
+ usage()
+ end.
+
+handle_command(spec, Opts) ->
case proplists:get_value(package, Opts) of
undefined ->
io:format("ERROR: Package name required.~n");
@@ -37,15 +96,9 @@ main(["spec"|Args]) ->
ignore
end,
io:format("~p~n",[Spec])
- end,
- stop();
+ end;
-main(["versions"|Args]) ->
- OptSpec = [
- {package, undefined, undefined, string, "Package name"}
- ],
- start(),
- {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
+handle_command(versions, Opts) ->
case proplists:get_value(package, Opts) of
undefined ->
io:format("ERROR: Package name required.~n");
@@ -54,55 +107,39 @@ main(["versions"|Args]) ->
io_lib:format("~s~n",[agner_spec:version_to_list(Version)])
end,
agner:versions(Package))])
- end,
- stop();
+ end;
-main(["list"|Args]) ->
- OptSpec = [
- {descriptions, $d, "descriptions", {boolean, false}, "Show package descriptions"},
- {properties, $p, "properties", string, "Comma-separated list of properties to show"}
- ],
- start(),
- {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
+handle_command(list, Opts) ->
ShowDescriptions = proplists:get_value(descriptions, Opts),
Properties = lists:map(fun list_to_atom/1, string:tokens(proplists:get_value(properties, Opts,""),",")),
- io:format("~s",[lists:usort(plists:map(fun (Name) ->
- Spec = agner:spec(Name),
- Result0 = case ShowDescriptions of
- true ->
- io_lib:format("~-40s ~s",[Name, proplists:get_value(description, Spec)]);
- false ->
- io_lib:format("~s",[Name])
- end,
- Result = case Properties of
- [] ->
- Result0;
- [_|_] ->
- [Result0|lists:map(fun (Prop) ->
- case lists:keyfind(Prop, 1, Spec) of
- false ->
- [];
- T ->
- Val = list_to_tuple(tl(tuple_to_list(T))),
-
- io_lib:format(" | ~s: ~p",[Prop,
+ io:format("~s",[lists:usort(plists:map(fun (Name) ->
+ Spec = agner:spec(Name),
+ Result0 = case ShowDescriptions of
+ true ->
+ io_lib:format("~-40s ~s",[Name, proplists:get_value(description, Spec)]);
+ false ->
+ io_lib:format("~s",[Name])
+ end,
+ Result = case Properties of
+ [] ->
+ Result0;
+ [_|_] ->
+ [Result0|lists:map(fun (Prop) ->
+ case lists:keyfind(Prop, 1, Spec) of
+ false ->
+ [];
+ T ->
+ Val = list_to_tuple(tl(tuple_to_list(T))),
+ io_lib:format(" | ~s: ~p",[Prop,
Val])
- end
- end, Properties)]
- end,
- Result ++ [$\n]
- end,agner:index()))
- ]),
- stop();
+ end
+ end, Properties)]
+ end,
+ Result ++ [$\n]
+ end,agner:index()))
+ ]);
-main(["fetch"|Args]) ->
- OptSpec = [
- {package, undefined, undefined, string, "Package name"},
- {directory, undefined, undefined, string, "Directory to check package out to"},
- {version, $v, "version", {string, "@master"}, "Version"}
- ],
- start(),
- {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
+handle_command(fetch, Opts) ->
case proplists:get_value(package, Opts) of
undefined ->
io:format("ERROR: Package name required.~n");
@@ -116,24 +153,16 @@ main(["fetch"|Args]) ->
Caveats when is_list(Caveats) ->
io:format("=== CAVEATS ===~n~n~s~n~n",[Caveats])
end
- end,
- stop();
+ end;
-main(["verify"|Args]) ->
- OptSpec = [
- {spec, undefined, undefined, {string, "agner.config"}, "Specification file (agner.config by default)"}
- ],
- start(),
- {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
+handle_command(verify, Opts) ->
SpecFile = proplists:get_value(spec, Opts),
case file:consult(SpecFile) of
{error, Reason} ->
io:format("ERROR: Can't read ~s: ~p~n",[SpecFile, Reason]);
{ok, Spec} ->
URL = proplists:get_value(url, Spec),
- {A,B,C} = now(),
- N = node(),
- TmpFile = lists:flatten(io_lib:format("/tmp/agner-~p-~p.~p.~p",[N,A,B,C])),
+ TmpFile = temp_name(),
case (catch agner_download:fetch(URL,TmpFile)) of
ok ->
io:format("~nPASSED~n");
@@ -143,13 +172,10 @@ main(["verify"|Args]) ->
io:format("~nEROR: Can't fetch ~p: ~p~n",[URL, Reason])
end,
os:cmd("rm -rf " ++ TmpFile)
- end,
- stop();
-
+ end.
+
+temp_name() ->
+ %% Yes, the temp_name function lives in the test_server, go figure!
+ test_server:temp_name("/tmp/agner").
-main(_) ->
- OptSpec = [
- {command, undefined, undefined, string, "Command to be executed (e.g. spec)"}
- ],
- getopt:usage(OptSpec, "agner", "[options ...]").
Something went wrong with that request. Please try again.