Skip to content
This repository

Cleanup option parsing #23

Merged
6 commits merged into from over 3 years ago

1 participant

Jesper Louis Andersen
Jesper Louis Andersen

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
Jesper Louis Andersen 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
Jesper Louis Andersen 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
Jesper Louis Andersen jlouis Don't use our own tmpfile(3) implementation
Even though the one in the stdlib is not much better.
5a8c30a
Jesper Louis Andersen 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
Jesper Louis Andersen 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
Jesper Louis Andersen 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

Showing 6 unique commits by 1 author.

Jan 28, 2011
Jesper Louis Andersen 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
Jesper Louis Andersen 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
Jesper Louis Andersen jlouis Don't use our own tmpfile(3) implementation
Even though the one in the stdlib is not much better.
5a8c30a
Jesper Louis Andersen 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
Jesper Louis Andersen 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
Jesper Louis Andersen jlouis Merge remote branch 'upstream/master' into cleanup-option-parsing-old
Conflicts:
	src/agner.erl
4b9a49d
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 106 additions and 80 deletions. Show diff stats Hide diff stats

  1. +106 80 src/agner_main.erl
186 src/agner_main.erl
@@ -8,16 +8,75 @@ start() ->
8 8 stop() ->
9 9 error_logger:delete_report_handler(error_logger_tty_h),
10 10 agner:stop().
11   -
12   -main(["spec"|Args]) ->
  11 +
  12 +arg_proplist() ->
  13 + [{"spec",
  14 + {spec,
  15 + "Output the specification of a package",
  16 + [
  17 + {package, undefined, undefined, string, "Package name"},
  18 + {browser, $b, "browser", boolean, "Show specification in the browser"},
  19 + {homepage, $h, "homepage", boolean, "Show package homepage in the browser"},
  20 + {version, $v, "version", {string, "@master"}, "Version"}
  21 + ]}},
  22 + {"versions",
  23 + {versions,
  24 + "Show the avilable releases and flavours of a package",
  25 + [
  26 + {package, undefined, undefined, string, "Package name"}
  27 + ]}},
  28 + {"list",
  29 + {list,
  30 + "List packages on stdout",
  31 + [
  32 + {descriptions, $d, "descriptions", {boolean, false}, "Show package descriptions"},
  33 + {properties, $p, "properties", string, "Comma-separated list of properties to show"}
  34 + ]}},
  35 + {"fetch",
  36 + {fetch,
  37 + "Download a package",
  38 + [
  39 + {package, undefined, undefined, string, "Package name"},
  40 + {directory, undefined, undefined, string, "Directory to check package out to"},
  41 + {version, $v, "version", {string, "@master"}, "Version"}
  42 + ]}},
  43 + {"verify",
  44 + {verify,
  45 + "Verify the integrity of a .agner configuration file",
  46 + [
  47 + {spec, undefined, undefined, {string, "agner.config"}, "Specification file (agner.config by default)"}
  48 + ]}}].
  49 +
  50 +command_descriptions() ->
  51 + [{Cmd, Desc} || {Cmd, {_Atom, Desc, _Opts}} <- arg_proplist()].
  52 +
  53 +parse_args([Arg|Args]) ->
  54 + case proplists:get_value(Arg, arg_proplist()) of
  55 + undefined -> no_parse;
  56 + {A, _Desc, OptSpec} -> {arg, A, Args, OptSpec}
  57 + end;
  58 +parse_args(_) -> no_parse.
  59 +
  60 +usage() ->
13 61 OptSpec = [
14   - {package, undefined, undefined, string, "Package name"},
15   - {browser, $b, "browser", boolean, "Show specification in the browser"},
16   - {homepage, $h, "homepage", boolean, "Show package homepage in the browser"},
17   - {version, $v, "version", {string, "@master"}, "Version"}
18   - ],
19   - start(),
20   - {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
  62 + {command, undefined, undefined, string, "Command to be executed (e.g. spec)"}
  63 + ],
  64 + getopt:usage(OptSpec, "agner", "[options ...]"),
  65 + io:format("Valid commands are:~n", []),
  66 + [io:format(" ~-10s ~s~n", [Cmd, Desc]) || {Cmd, Desc} <- command_descriptions()].
  67 +
  68 +main(Args) ->
  69 + case parse_args(Args) of
  70 + {arg, Command, ExtraArgs, OptSpec} ->
  71 + start(),
  72 + {ok, {Opts, _}} = getopt:parse(OptSpec, ExtraArgs),
  73 + handle_command(Command, Opts),
  74 + stop();
  75 + no_parse ->
  76 + usage()
  77 + end.
  78 +
  79 +handle_command(spec, Opts) ->
21 80 case proplists:get_value(package, Opts) of
22 81 undefined ->
23 82 io:format("ERROR: Package name required.~n");
@@ -37,15 +96,9 @@ main(["spec"|Args]) ->
37 96 ignore
38 97 end,
39 98 io:format("~p~n",[Spec])
40   - end,
41   - stop();
  99 + end;
42 100
43   -main(["versions"|Args]) ->
44   - OptSpec = [
45   - {package, undefined, undefined, string, "Package name"}
46   - ],
47   - start(),
48   - {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
  101 +handle_command(versions, Opts) ->
49 102 case proplists:get_value(package, Opts) of
50 103 undefined ->
51 104 io:format("ERROR: Package name required.~n");
@@ -54,55 +107,39 @@ main(["versions"|Args]) ->
54 107 io_lib:format("~s~n",[agner_spec:version_to_list(Version)])
55 108 end,
56 109 agner:versions(Package))])
57   - end,
58   - stop();
  110 + end;
59 111
60   -main(["list"|Args]) ->
61   - OptSpec = [
62   - {descriptions, $d, "descriptions", {boolean, false}, "Show package descriptions"},
63   - {properties, $p, "properties", string, "Comma-separated list of properties to show"}
64   - ],
65   - start(),
66   - {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
  112 +handle_command(list, Opts) ->
67 113 ShowDescriptions = proplists:get_value(descriptions, Opts),
68 114 Properties = lists:map(fun list_to_atom/1, string:tokens(proplists:get_value(properties, Opts,""),",")),
69   - io:format("~s",[lists:usort(plists:map(fun (Name) ->
70   - Spec = agner:spec(Name),
71   - Result0 = case ShowDescriptions of
72   - true ->
73   - io_lib:format("~-40s ~s",[Name, proplists:get_value(description, Spec)]);
74   - false ->
75   - io_lib:format("~s",[Name])
76   - end,
77   - Result = case Properties of
78   - [] ->
79   - Result0;
80   - [_|_] ->
81   - [Result0|lists:map(fun (Prop) ->
82   - case lists:keyfind(Prop, 1, Spec) of
83   - false ->
84   - [];
85   - T ->
86   - Val = list_to_tuple(tl(tuple_to_list(T))),
87   -
88   - io_lib:format(" | ~s: ~p",[Prop,
  115 + io:format("~s",[lists:usort(plists:map(fun (Name) ->
  116 + Spec = agner:spec(Name),
  117 + Result0 = case ShowDescriptions of
  118 + true ->
  119 + io_lib:format("~-40s ~s",[Name, proplists:get_value(description, Spec)]);
  120 + false ->
  121 + io_lib:format("~s",[Name])
  122 + end,
  123 + Result = case Properties of
  124 + [] ->
  125 + Result0;
  126 + [_|_] ->
  127 + [Result0|lists:map(fun (Prop) ->
  128 + case lists:keyfind(Prop, 1, Spec) of
  129 + false ->
  130 + [];
  131 + T ->
  132 + Val = list_to_tuple(tl(tuple_to_list(T))),
  133 + io_lib:format(" | ~s: ~p",[Prop,
89 134 Val])
90   - end
91   - end, Properties)]
92   - end,
93   - Result ++ [$\n]
94   - end,agner:index()))
95   - ]),
96   - stop();
  135 + end
  136 + end, Properties)]
  137 + end,
  138 + Result ++ [$\n]
  139 + end,agner:index()))
  140 + ]);
97 141
98   -main(["fetch"|Args]) ->
99   - OptSpec = [
100   - {package, undefined, undefined, string, "Package name"},
101   - {directory, undefined, undefined, string, "Directory to check package out to"},
102   - {version, $v, "version", {string, "@master"}, "Version"}
103   - ],
104   - start(),
105   - {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
  142 +handle_command(fetch, Opts) ->
106 143 case proplists:get_value(package, Opts) of
107 144 undefined ->
108 145 io:format("ERROR: Package name required.~n");
@@ -116,24 +153,16 @@ main(["fetch"|Args]) ->
116 153 Caveats when is_list(Caveats) ->
117 154 io:format("=== CAVEATS ===~n~n~s~n~n",[Caveats])
118 155 end
119   - end,
120   - stop();
  156 + end;
121 157
122   -main(["verify"|Args]) ->
123   - OptSpec = [
124   - {spec, undefined, undefined, {string, "agner.config"}, "Specification file (agner.config by default)"}
125   - ],
126   - start(),
127   - {ok, {Opts, _}} = getopt:parse(OptSpec, Args),
  158 +handle_command(verify, Opts) ->
128 159 SpecFile = proplists:get_value(spec, Opts),
129 160 case file:consult(SpecFile) of
130 161 {error, Reason} ->
131 162 io:format("ERROR: Can't read ~s: ~p~n",[SpecFile, Reason]);
132 163 {ok, Spec} ->
133 164 URL = proplists:get_value(url, Spec),
134   - {A,B,C} = now(),
135   - N = node(),
136   - TmpFile = lists:flatten(io_lib:format("/tmp/agner-~p-~p.~p.~p",[N,A,B,C])),
  165 + TmpFile = temp_name(),
137 166 case (catch agner_download:fetch(URL,TmpFile)) of
138 167 ok ->
139 168 io:format("~nPASSED~n");
@@ -143,13 +172,10 @@ main(["verify"|Args]) ->
143 172 io:format("~nEROR: Can't fetch ~p: ~p~n",[URL, Reason])
144 173 end,
145 174 os:cmd("rm -rf " ++ TmpFile)
146   - end,
147   - stop();
148   -
  175 + end.
  176 +
  177 +temp_name() ->
  178 + %% Yes, the temp_name function lives in the test_server, go figure!
  179 + test_server:temp_name("/tmp/agner").
149 180
150   -main(_) ->
151   - OptSpec = [
152   - {command, undefined, undefined, string, "Command to be executed (e.g. spec)"}
153   - ],
154   - getopt:usage(OptSpec, "agner", "[options ...]").
155 181

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.