Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:ozu
erlang-getopt
getopt-1.0.2.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File getopt-1.0.2.obscpio of Package erlang-getopt
07070100000000000081A4000003E800000064000000015F4A649F0000006F000000000000000000000000000000000000001900000000getopt-1.0.2/.travis.ymllanguage: erlang otp_release: - R13B04 - R14B04 - R15B03 - R16B03 - 17.5 - 18.3 - 19.3 - 20.0 07070100000001000081A4000003E800000064000000015F4A649F000000B7000000000000000000000000000000000000001700000000getopt-1.0.2/Emakefile{"src/*", [debug_info, {outdir, "ebin"}, {i, "include"}]}. {"test/*", [debug_info, {outdir, "ebin"}, {i, "include"}]}. {"examples/*", [debug_info, {outdir, "ebin"}, {i, "include"}]}. 07070100000002000081A4000003E800000064000000015F4A649F000005B7000000000000000000000000000000000000001900000000getopt-1.0.2/LICENSE.txtCopyright 2009 Juan Jose Comellas Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder 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 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 07070100000003000081A4000003E800000064000000015F4A649F000000F2000000000000000000000000000000000000001600000000getopt-1.0.2/MakefileAPPLICATION := getopt .PHONY: all clean compile dialyzer edoc shell test all: compile clean: @rebar3 clean compile: @rebar3 compile dialyzer: compile @rebar3 dialyzer edoc: @rebar3 edoc shell: @rebar3 shell test: @rebar3 eunit 07070100000004000081A4000003E800000064000000015F4A649F000038EA000000000000000000000000000000000000001700000000getopt-1.0.2/README.mdGetopt for Erlang ================= Command-line parsing module that uses a syntax similar to that of GNU *getopt*. Requirements ------------ You should only need a somewhat recent version of Erlang/OTP. The module has been tested with all versions of Erlang starting with R13B and ending with 20. You also need a recent version of [rebar3](http://www.rebar3.org/) in the system path. Installation ------------ To compile the module you simply run `rebar3 compile`. To run the unit tests run `rebar3 eunit`. To build the (very) limited documentation run `rebar edoc`. To use getopt in your project you can just add it as a dependency in your `rebar.config` file in the following way: ```erlang {deps, [ {getopt, "1.0.2"} ] } ``` Usage ----- The `getopt` module provides four functions: ```erlang parse([{Name, Short, Long, ArgSpec, Help}], Args :: string() | [string()]) -> {ok, {Options, NonOptionArgs}} | {error, {Reason, Data}} tokenize(CmdLine :: string()) -> [string()] usage([{Name, Short, Long, ArgSpec, Help}], ProgramName :: string()) -> ok usage([{Name, Short, Long, ArgSpec, Help}], ProgramName :: string(), CmdLineTail :: string()) -> ok usage([{Name, Short, Long, ArgSpec, Help}], ProgramName :: string(), CmdLineTail :: string(), OptionsTail :: [{string(), string}]) -> ok ``` The `parse/2` function receives a list of tuples with the command line option specifications. The type specification for the tuple is: ```erlang -type arg_type() :: 'atom' | 'binary' | 'utf8_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 elements of the tuple 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). - `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. e.g. ```erlang {port, $p, "port", {integer, 5432}, "Database server port"} ``` The second parameter receives the list of arguments as passed to the `main/1` function in escripts or the unparsed command line as a string. 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 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 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. Given the following option specifications: ```erlang OptSpecList = [ {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"}, {verbose, $v, "verbose", integer, "Verbosity level"}, {file, undefined, undefined, string, "Output file"} ]. ``` And this command line: ```erlang Args = "-h myhost --port=1000 -x myfile.txt -vvv dummy1 dummy2" ``` Which could also be passed in the format the `main/1` function receives the arguments in escripts: ```erlang Args = ["-h", "myhost", "--port=1000", "-x", "file.txt", "-vvv", "dummy1", "dummy2"]. ``` The call to `getopt:parse/2`: ```erlang getopt:parse(OptSpecList, Args). ``` Will return: ```erlang {ok,{[{host,"myhost"}, {port,1000}, xml, {file,"file.txt"}, {dbname,"users"}, {verbose,3}], ["dummy1","dummy2"]}} ``` The `tokenize/1` function will separate a command line string into tokens, taking into account whether an argument is single or double quoted, a character is escaped or if there are environment variables to be expanded. e.g.: ```erlang getopt:tokenize(" --name John\\ Smith --path \"John's Files\" -u ${USER}"). ``` Will return something like: ```erlang ["--name","John Smith","--path","John's Files","-u","jsmith"] ``` The other functions exported by the `getopt` module (`usage/2`, `usage/3` and `usage/4`) are used to show the command line syntax for the program. For example, given the above-mentioned option specifications, the call to `getopt:usage/2`: ```erlang getopt:usage(OptSpecList, "ex1"). ``` Will show (on *standard_error*): Usage: ex1 [-h <host>] [-p <port>] [--dbname <dbname>] [-x] [-v] <file> -h, --host Database server host -p, --port Database server port --dbname Database name -x Output data in XML -v Verbosity level <file> Output file This call to `getopt:usage/3` will add a string after the usage command line: ```erlang getopt:usage(OptSpecList, "ex1", "[var=value ...] [command ...]"). ``` Will show (on *standard_error*): Usage: ex1 [-h <host>] [-p <port>] [--dbname <dbname>] [-x] [-v <verbose>] <file> [var=value ...] [command ...] -h, --host Database server host -p, --port Database server port --dbname Database name -x Output data in XML -v, --verbose Verbosity level <file> Output file Whereas this call to `getopt:usage/3` will also add some lines to the options help text: ```erlang getopt:usage(OptSpecList, "ex1", "[var=value ...] [command ...]", [{"var=value", "Variables that will affect the execution (e.g. debug=1)"}, {"command", "Commands that will be executed (e.g. count)"}]). ``` Will show (on *standard_error*): Usage: ex1 [-h <host>] [-p <port>] [--dbname <dbname>] [-x] [-v <verbose>] <file> [var=value ...] [command ...] -h, --host Database server host -p, --port Database server port --dbname Database name -x Output data in XML -v, --verbose Verbosity level <file> Output file var=value Variables that will affect the execution (e.g. debug=1) command Commands that will be executed (e.g. count) Command-line Syntax ------------------- The syntax supported by the `getopt` module is very similar to that followed by GNU programs, which is described [here](http://www.gnu.org/s/libc/manual/html_node/Argument-Syntax.html). Options can have both short (single character) and long (string) option names. A short option can have the following syntax: -a Single option 'a', no argument or implicit boolean argument -a foo Single option 'a', argument "foo" -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' A long option can have the following syntax: --foo Single option 'foo', no argument --foo=bar Single option 'foo', argument "bar" --foo bar Single option 'foo', argument "bar" Argument Types -------------- The arguments allowed for options are: *atom*; *binary*; *utf8_binary*; *boolean*; *float*; *integer*; *string*. The `getopt` module checks every argument to see if it can be converted to its correct type. In the case of boolean arguments, the following values (in lower or upper case) are considered `true`: *true*; *t*; *yes*; *y*; *on*; *enabled*; *1*. These ones are considered `false`: *false*; *f*; *no*; *n*; *off*; *disabled*; *0*. Numeric arguments can only be negative when passed as part of an assignment expression. e.g. `--increment=-100` is a valid expression; whereas `--increment -100` is invalid Arguments of `utf8_binary` type allow proper binary encoding of arguments containing code points greater than 255. The resulting value is a normalized UTF-8 binary. As of Erlang/20, `standard_error` device has `unicode` option set to `false`. It prevents correct printing of usage for arguments containing unicode binaries/strings as default values. To fix this, one needs to enable unicode: ```erlang io:setopts(standard_error, [{unicode, true}]). ``` Implicit Arguments ------------------ The arguments for options with the *boolean* and *integer* data types can sometimes be omitted. In those cases the value assigned to the option is *true* for *boolean* arguments and *1* for integer arguments. Multiple Repetitions -------------------- An option can be repeated several times, in which case there will be multiple appearances of the option in the resulting list. The only exceptions are short options with integer arguments. In that particular case, each appearance of the short option within a single command line argument will increment the number that will be returned for that specific option. e.g. Given an option specification list with the following format: ```erlang OptSpecList = [ {define, $D, "define", string, "Define a variable"}, {verbose, $v, "verbose", integer, "Verbosity level"} ]. ``` The following invocation: ```erlang getopt:parse(OptSpecList, "-DFOO -DVAR1=VAL1 -DBAR --verbose --verbose=3 -v -vvvv dummy"). ``` would return: ```erlang {ok,{[{define,"FOO"}, {define,"VAR1=VAL1"}, {define,"BAR"}, {verbose,1}, {verbose,3}, {verbose,1}, {verbose,4}], ["dummy"]}} ``` Positional Options ------------------ We can also have options with neither short nor long option names. In this case, the options will be taken according to their position in the option specification list passed to `getopt:/parse2`. For example, with the following option specifications: ```erlang OptSpecList = [ {xml, $x, "xml", undefined, "Output data as XML"}, {dbname, undefined, undefined, string, "Database name"}, {output_file, undefined, undefined, string, "File where the data will be saved to"} ]. ``` This call to `getopt:parse/2`: ```erlang getopt:parse(OptSpecList, "-x mydb file.out dummy dummy"). ``` Will return: ```erlang {ok,{[xml,{dbname,"mydb"},{output_file,"file.out"}], ["dummy","dummy"]}} ``` Option Terminators ------------------ The string `--` is considered an option terminator. This means that all the command-line arguments after it are considered non-option arguments and will be returned without being evaluated even if they follow the *getopt* syntax. e.g. This invocation using the first option specification list in the document: ```erlang getopt:parse(OptSpecList, "-h myhost -p 1000 -- --dbname mydb dummy"). ``` will return: ```erlang {ok,{[{host,"myhost"}, {port,1000},{dbname,"users"}], ["--dbname","mydb","dummy"]}} ``` Notice that the *dbname* option was assigned the value `users` instead of `mydb`. This happens because the option terminator prevented *getopt* from evaluating it and the default value was assigned to it. Non-option Arguments -------------------- The single `-` character is always considered as a non-option argument. e.g. This invocation using the specification list from the previous example: ```erlang getopt:parse(OptSpecList, "-h myhost -p 1000 - --dbname mydb dummy"). ``` will return: ```erlang {ok,{[{host,"myhost"}, {port,1000}, {dbname,"mydb"}], ["-","dummy"]}} ``` Arguments with embedded whitespace ---------------------------------- Arguments that have embedded whitespace have to be quoted with either single or double quotes to be considered as a single argument. e.g. Given an option specification list with the following format: ```erlang OptSpecList = [ {define, $D, "define", string, "Define a variable"}, {user, $u, "user", string, "User name"} ]. ``` The following invocation: ```erlang getopt:parse(OptSpecList, "-D'FOO=VAR 123' --define \"VAR WITH SPACES\" -u\"my user name\""). ``` would return: ```erlang {ok,{[{define,"FOO=VAR 123"}, {define,"VAR WITH SPACES"}, {user,"my user name"}], []}} ``` When parsing a command line with unclosed quotes the last argument will be a single string starting at the position where the last quote was entered. e.g. The following invocation: ```erlang getopt:parse(OptSpecList, "--user ' my user ' \"argument with unclosed quotes"). ``` would return: ```erlang {ok,{[{user," my user "}], ["argument with unclosed quotes"]}} ``` Environment variable expansion ------------------------------ `getopt:parse/2` will expand environment variables when used with a command line that is passed as a single string. The formats that are supported for environment variable expansion are: - $VAR (simple Unix/bash format) - ${VAR} (full Unix/bash format) - %VAR% (Windows format) If a variable is not present in the environment it will not be expanded. Variables can be expanded within double-quoted and free arguments. *getopt* will not expand environment variables within single-quoted arguments. e.g. Given the following option specification list: ```erlang OptSpecList = [ {path, $p, "path", string, "File path"} ]. ``` The following invocation: ```erlang getopt:parse(OptSpecList, "--path ${PATH} $NONEXISTENT_DUMMY_VAR"). ``` would return (depending on the value of your PATH variable) something like: ```erlang {ok,{[{path, "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}], ["$NONEXISTENT_DUMMY_VAR"]}} ``` Currently, *getopt* does not perform wildcard expansion of file paths. Escaping arguments ================== Any character can be escaped by prepending the \ (backslash) character to it. e.g. ```erlang getopt:parse(OptSpecList, "--path /john\\'s\\ files dummy"). ``` Will return: ```erlang {ok,{[{path,"/john's files"}],["dummy"]}} ``` 07070100000005000041ED000003E800000064000000025F4A649F00000000000000000000000000000000000000000000001100000000getopt-1.0.2/doc07070100000006000041ED000003E800000064000000025F4A649F00000000000000000000000000000000000000000000001200000000getopt-1.0.2/ebin07070100000007000041ED000003E800000064000000025F4A649F00000000000000000000000000000000000000000000001600000000getopt-1.0.2/examples07070100000008000081A4000003E800000064000000015F4A649F000008A5000000000000000000000000000000000000001E00000000getopt-1.0.2/examples/ex1.erl%%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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'). -export([test/0, test/1]). test() -> test("-U myuser -P mypassword --host myhost -x -o myfile.dump mydb dummy1"). test(CmdLine) -> OptSpecList = option_spec_list(), io:format("For command line: ~p~n" "getopt:parse/2 returns:~n~n", [CmdLine]), case getopt:parse(OptSpecList, CmdLine) 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") end. option_spec_list() -> CurrentUser = os:getenv("USER"), [ %% {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"}, {verbose, $v, "verbose", integer, "Verbosity level"}, {dbname, undefined, undefined, string, "Database name"} ]. 07070100000009000081ED000003E800000064000000015F4A649F00000951000000000000000000000000000000000000002200000000getopt-1.0.2/examples/ex1.escript#!/usr/bin/env escript %% -*- erlang -*- %%! -sname ex1 -pz ebin %%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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(), escript:script_name()); 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"}, {verbose, $v, "verbose", integer, "Verbosity level"}, {dbname, undefined, undefined, string, "Database name"} ]. 0707010000000A000081ED000003E800000064000000015F4A649F00000BD6000000000000000000000000000000000000002F00000000getopt-1.0.2/examples/getopt_long_help.escript#!/usr/bin/env escript %% -*- erlang -*- %%! -sname ex1 -pz ebin %%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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(), escript:script_name(), "<arg1> <arg2> <arg3> <arg4> <arg5> <arg6> <arg7> <arg8> <arg9> <arg10>"); 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, LongHelp = "Password to connect to the database; this is an arbitrarily long string just to check " "whether getopt wraps lines correctly when they become too long for the console. We'll " "make it extra long so that it takes at least 3 lines to show on normal consoles. That " "wasn't enough, so let's try again by adding more and more redundant text to this help " "line. What if we copied a paragraph from a book? That would surely make it easier", [ %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} {help, $?, "help", undefined, "Show the program options"}, {username, $U, "username", {string, CurrentUser}, "Username to connect to the database"}, {password, $P, "password", string, LongHelp}, {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"}, {margin, $m, "margin", {float, 0.66}, "Page margin"}, {xml, $x, "xml", undefined, "Output data as XML"}, {verbose, $v, "verbose", integer, "Verbosity level"}, {dbname, undefined, undefined, string, "Database name"} ]. 0707010000000B000081A4000003E800000064000000015F4A649F000009A3000000000000000000000000000000000000002500000000getopt-1.0.2/examples/rebar_test.erl%%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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(rebar_test). -author('juanjo@comellas.org'). -export([test/0, test/1, test_required/0, test_check/0, usage/0]). test_required() -> test2(fun getopt:parse_and_check/2, "-f"). test_check() -> test2(fun(OptSpecList, CmdLine) -> case getopt:parse(OptSpecList, CmdLine) of {ok, {Opts, _}} -> getopt:check(OptSpecList, Opts); Other -> Other end end, "-f"). test() -> test2(fun getopt:parse/2, "-f verbose=1 --quiet=on -j2 dummy1 dummy2"). test(CmdLine) -> test2(fun getopt:parse/2, CmdLine). test2(Fun, CmdLine) -> OptSpecList = option_spec_list(), io:format("For command line: ~p~n" "getopt:parse/2 returns:~n~n", [CmdLine]), case Fun(OptSpecList, CmdLine) of {ok, {Options, NonOptArgs}} -> io:format("Options:~n ~p~n~nNon-option arguments:~n ~p~n", [Options, NonOptArgs]); {error, {_Reason, _Data}} = Error -> io:format("Error: ~s~n~n", [getopt:format_error(OptSpecList, Error)]), usage(OptSpecList) end. usage() -> usage(option_spec_list()). usage(OptSpecList) -> getopt:usage(OptSpecList, "rebar_test", "[var1=val1 ...] [command1 ...]", [{"var=value", "Variables that will affect the compilation (e.g. debug=1)"}, {"command", "Commands that will be executed by rebar (e.g. compile)"}]). option_spec_list() -> CpuCount = erlang:system_info(logical_processors), [ %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} {help, $h, "help", undefined, "Show the program options"}, {jobs, $j, "jobs", {integer, CpuCount}, "Number of concurrent jobs"}, {verbose, $v, "verbose", boolean, "Be verbose about what gets done"}, {force, $f, "force", {boolean, false}, "Force"} ]. 0707010000000C000081ED000003E800000064000000015F4A649F000007DA000000000000000000000000000000000000002900000000getopt-1.0.2/examples/rebar_test.escript#!/usr/bin/env escript %% -*- erlang -*- %%! -sname ex1 -pz ebin %%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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(rebar_test). -author('juanjo@comellas.org'). main([]) -> usage(); 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]), usage(OptSpecList) end. usage() -> usage(option_spec_list()). usage(OptSpecList) -> getopt:usage(OptSpecList, escript:script_name(), "[var1=val1 ...] [command1 ...]", [{"var=value", "Variables that will affect the compilation (e.g. debug=1)"}, {"command", "Commands that will be executed by rebar (e.g. compile)"}]). option_spec_list() -> CpuCount = erlang:system_info(logical_processors), [ %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} {help, $h, "help", undefined, "Show the program options"}, {jobs, $j, "jobs", {integer, CpuCount}, "Number of concurrent jobs"}, {verbose, $v, "verbose", {boolean, false}, "Be verbose about what gets done"}, {force, $f, "force", {boolean, false}, "Force"} ]. 0707010000000D000081A4000003E800000064000000015F4A649F0000039A000000000000000000000000000000000000001A00000000getopt-1.0.2/rebar.config{erl_opts, [warn_unused_vars, warn_export_all, warn_shadow_vars, warn_unused_import, warn_unused_function, warn_bif_clash, warn_unused_record, warn_deprecated_function, warn_obsolete_guard, strict_validation, warn_export_vars, warn_exported_vars, warn_missing_spec, warn_untyped_record, debug_info, {platform_define, "^2", unicode_str} ]}. {dialyzer, [ {warnings, [no_return, no_undefined_callbacks, no_unused]}, {get_warnings, true}, {plt_apps, top_level_deps}, {plt_location, local}, {base_plt_apps, [kernel, stdlib, sasl, inets, crypto, public_key, ssl, runtime_tools, erts, compiler, tools, syntax_tools, hipe, mnesia]}, {base_plt_location, global} ]}. {xref_checks, [undefined_function_calls]}. 0707010000000E000081A4000003E800000064000000015F4A649F00000004000000000000000000000000000000000000001800000000getopt-1.0.2/rebar.lock[]. 0707010000000F000041ED000003E800000064000000025F4A649F00000000000000000000000000000000000000000000001100000000getopt-1.0.2/src07070100000010000081A4000003E800000064000000015F4A649F00000183000000000000000000000000000000000000002000000000getopt-1.0.2/src/getopt.app.src{application,getopt, [{description,"Command-line options parser for Erlang"}, {vsn,"1.0.2"}, {modules,[]}, {registered,[]}, {maintainers,["Juan Jose Comellas"]}, {licenses,["BSD"]}, {links,[{"GitHub","https://github.com/jcomellas/getopt"}]}, {applications,[kernel,stdlib]}]}. 07070100000011000081A4000003E800000064000000015F4A649F0000B0EA000000000000000000000000000000000000001C00000000getopt-1.0.2/src/getopt.erl%%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @copyright (C) 2009-2017 Juan Jose Comellas %%% @doc Parses command line options with a format similar to that of GNU getopt. %%% @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(getopt). -author('juanjo@comellas.org'). -export([parse/2, check/2, parse_and_check/2, format_error/2, usage/2, usage/3, usage/4, usage/6, tokenize/1]). -export([usage_cmd_line/2, usage_options/1]). -define(LINE_LENGTH, 75). -define(MIN_USAGE_COMMAND_LINE_OPTION_LENGTH, 25). %% Position of each field in the option specification tuple. -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), (tuple_size(Opt) =:= ?OPT_HELP)). -define(IS_WHITESPACE(Char), ((Char) =:= $\s orelse (Char) =:= $\t orelse (Char) =:= $\n orelse (Char) =:= $\r)). %% Atom indicating the data type that an argument can be converted to. -type arg_type() :: 'atom' | 'binary' | 'utf8_binary' | 'boolean' | 'float' | 'integer' | 'string'. %% Data type that an argument can be converted to. -type arg_value() :: atom() | binary() | boolean() | float() | integer() | string(). %% Argument specification. -type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined. %% Option type and optional default argument. -type simple_option() :: atom(). -type compound_option() :: {atom(), arg_value()}. -type option() :: simple_option() | compound_option(). %% Command line option specification. -type option_spec() :: { Name :: atom(), Short :: char() | undefined, Long :: string() | undefined, ArgSpec :: arg_spec(), Help :: string() | undefined }. %% Output streams -type output_stream() :: 'standard_io' | 'standard_error'. %% For internal use -type usage_line() :: {OptionText :: string(), HelpText :: string()}. -type usage_line_with_length() :: {OptionLength :: non_neg_integer(), OptionText :: string(), HelpText :: string()}. -export_type([arg_type/0, arg_value/0, arg_spec/0, simple_option/0, compound_option/0, option/0, option_spec/0]). %% @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. -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) -> case parse(OptSpecList, CmdLine) of {ok, {Opts, _}} = Result -> case check(OptSpecList, Opts) of ok -> Result; Error -> Error end; Error -> Error end. %% @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. -spec check([option_spec()], [option()]) -> ok | {error, {Reason :: atom(), Option :: atom()}}. check(OptSpecList, ParsedOpts) when is_list(OptSpecList), is_list(ParsedOpts) -> try RequiredOpts = [Name || {Name, _, _, Arg, _} <- OptSpecList, not is_tuple(Arg) andalso Arg =/= undefined], lists:foreach(fun (Option) -> case proplists:is_defined(Option, ParsedOpts) of true -> ok; false -> throw({error, {missing_required_option, Option}}) end end, RequiredOpts) catch _:Error -> Error end. %% @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. -spec parse([option_spec()], string() | [string()]) -> {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: term()}}. parse(OptSpecList, CmdLine) when is_list(CmdLine) -> try Args = if is_integer(hd(CmdLine)) -> tokenize(CmdLine); true -> CmdLine end, parse(OptSpecList, [], [], 0, Args) catch throw: {error, {_Reason, _Data}} = Error -> Error end. -spec parse([option_spec()], [option()], [string()], integer(), [string()]) -> {ok, {[option()], [string()]}}. %% Process the option terminator. parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) -> %% Any argument present after the terminator is not considered an option. {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}}; %% Process long options. parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) -> parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); %% Process short options. parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) -> parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); %% Process non-option arguments. parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) -> case find_non_option_arg(OptSpecList, ArgPos) of {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) -> parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos + 1, Tail); false -> parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail) end; parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) -> %% Once we have completed gathering the options we add the ones that were %% not present but had default arguments in the specification. {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}. %% @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()}} | {Reason :: term(), Data :: term()}) -> string(). format_error(OptSpecList, {error, Reason}) -> format_error(OptSpecList, Reason); format_error(OptSpecList, {missing_required_option, Name}) -> OptStr = opt_to_list(lists:keyfind(Name, 1, OptSpecList)), lists:flatten(["missing required option: ", OptStr]); 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, {invalid_option_arg, OptStr}) -> lists:flatten(["invalid option argument: ", to_string(OptStr)]); format_error(OptSpecList, {missing_option_arg, Name}) -> OptStr = opt_to_list(lists:keyfind(Name, 1, OptSpecList)), lists:flatten(["missing option argument: ", OptStr, " <", to_string(Name), $>]); format_error(_OptSpecList, {Reason, Data}) -> lists:flatten([to_string(Reason), " ", to_string(Data)]). opt_to_list({Name, undefined, undefined, _Type, _Help}) -> [$<, to_string(Name), $>]; opt_to_list({_Name, undefined, Long, _Type, _Help}) -> [$-, $-, Long]; opt_to_list({_Name, Short, undefined, _Type, _Help}) -> [$-, Short]; opt_to_list({_Name, Short, Long, _Type, _Help}) -> [$-, Short, $\s, $(, Long, $)]. %% @doc Parse a long option, add it to the option accumulator and continue %% parsing the rest of the arguments recursively. %% A long option can have the following syntax: %% --foo Single option 'foo', no argument %% --foo=bar Single option 'foo', argument "bar" %% --foo bar Single option 'foo', argument "bar" -spec parse_long_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> {ok, {[option()], [string()]}}. parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> case split_assigned_arg(OptArg) of {Long, Arg} -> %% Get option that has its argument within the same string %% separated by an equal ('=') character (e.g. "--port=1000"). parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg); Long -> case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of {Name, _Short, Long, undefined, _Help} -> parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args); {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec -> %% The option argument string is empty, but the option requires %% an argument, so we look into the next string in the list. %% e.g ["--port", "1000"] parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); false -> throw({error, {invalid_option, OptStr}}) end end. %% @doc Parse an option where the argument is 'assigned' in the same string using %% the '=' character, add it to the option accumulator and continue parsing the %% rest of the arguments recursively. This syntax is only valid for long options. -spec parse_long_option_assigned_arg([option_spec()], [option()], [string()], integer(), [string()], string(), string(), string()) -> {ok, {[option()], [string()]}}. parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) -> case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of {_Name, _Short, Long, ArgSpec, _Help} = OptSpec -> case ArgSpec of undefined -> throw({error, {invalid_option_arg, OptStr}}); _ -> parse(OptSpecList, add_option_with_assigned_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args) end; false -> throw({error, {invalid_option, OptStr}}) end. %% @doc Split an option string that may contain an option with its argument %% separated by an equal ('=') character (e.g. "port=1000"). -spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string(). split_assigned_arg(OptStr) -> split_assigned_arg(OptStr, OptStr, []). split_assigned_arg(_OptStr, "=" ++ Tail, Acc) -> {lists:reverse(Acc), Tail}; split_assigned_arg(OptStr, [Char | Tail], Acc) -> split_assigned_arg(OptStr, Tail, [Char | Acc]); split_assigned_arg(OptStr, [], _Acc) -> OptStr. %% @doc Retrieve the argument for an option from the next string in the list of %% command-line parameters or set the value of the argument from the argument %% specification (for boolean and integer arguments), if possible. parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) -> ArgSpecType = arg_spec_type(ArgSpec), case Args =:= [] orelse is_implicit_arg(ArgSpecType, hd(Args)) of true -> parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); false -> [Arg | Tail] = Args, try parse(OptSpecList, [{Name, to_type(ArgSpecType, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) catch error:_ -> throw({error, {invalid_option_arg, {Name, Arg}}}) end end. %% @doc Parse a short option, add it to the option accumulator and continue %% parsing the rest of the arguments recursively. %% A short option can have the following syntax: %% -a Single option 'a', no argument or implicit boolean argument %% -a foo Single option 'a', argument "foo" %% -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_short_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> {ok, {[option()], [string()]}}. parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg). parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptPos, [Short | Arg]) -> case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of {Name, Short, _Long, undefined, _Help} -> parse_short_option(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, first, Arg); {_Name, Short, _Long, ArgSpec, _Help} = OptSpec -> %% The option has a specification, so it requires an argument. 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. parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec, OptPos); _ -> case is_valid_arg(ArgSpec, Arg) of true -> parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args); _ -> NewOptAcc = case OptPos of first -> add_option_with_implicit_arg(OptSpec, OptAcc); _ -> add_option_with_implicit_incrementable_arg(OptSpec, OptAcc) end, parse_short_option(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Args, OptStr, next, Arg) end end; false -> throw({error, {invalid_option, OptStr}}) end; parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, _OptPos, []) -> parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args). %% @doc Retrieve the argument for an option from the next string in the list of %% command-line parameters or set the value of the argument from the argument %% specification (for boolean and integer arguments), if possible. parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptPos) -> case Args =:= [] orelse is_implicit_arg(ArgSpec, hd(Args)) of true when OptPos =:= first -> parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); true -> parse(OptSpecList, add_option_with_implicit_incrementable_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); false -> [Arg | Tail] = Args, try parse(OptSpecList, [{Name, to_type(ArgSpec, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) catch error:_ -> throw({error, {invalid_option_arg, {Name, Arg}}}) end end. %% @doc Find the option for the discrete argument in position specified in the %% Pos argument. -spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false. 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); find_non_option_arg([], _Pos) -> false. %% @doc Append options that were not present in the command line arguments with %% their default arguments. -spec append_default_options([option_spec()], [option()]) -> [option()]. append_default_options([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) -> append_default_options(Tail, case lists:keymember(Name, 1, OptAcc) of false -> [{Name, DefaultArg} | OptAcc]; _ -> OptAcc end); %% For options with no default argument. append_default_options([_Head | Tail], OptAcc) -> append_default_options(Tail, OptAcc); append_default_options([], OptAcc) -> OptAcc. %% @doc Add an option with argument converting it to the data type indicated by the %% argument specification. -spec add_option_with_arg(option_spec(), string(), [option()]) -> [option()]. add_option_with_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) -> case is_valid_arg(ArgSpec, Arg) of true -> try [{Name, to_type(ArgSpec, Arg)} | OptAcc] catch error:_ -> throw({error, {invalid_option_arg, {Name, Arg}}}) end; false -> add_option_with_implicit_arg(OptSpec, OptAcc) end. %% @doc Add an option with argument that was part of an assignment expression %% (e.g. "--verbose=3") converting it to the data type indicated by the %% argument specification. -spec add_option_with_assigned_arg(option_spec(), string(), [option()]) -> [option()]. add_option_with_assigned_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg, OptAcc) -> try [{Name, to_type(ArgSpec, Arg)} | OptAcc] catch error:_ -> throw({error, {invalid_option_arg, {Name, Arg}}}) end. %% @doc Add an option that required an argument but did not have one. Some data %% types (boolean, integer) allow implicit or assumed arguments. -spec add_option_with_implicit_arg(option_spec(), [option()]) -> [option()]. add_option_with_implicit_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> case arg_spec_type(ArgSpec) of boolean -> %% Special case for boolean arguments: if there is no argument we %% set the value to 'true'. [{Name, true} | OptAcc]; integer -> %% Special case for integer arguments: if the option had not been set %% before we set the value to 1. This is needed to support options like %% "-v" to return something like {verbose, 1}. [{Name, 1} | OptAcc]; _ -> throw({error, {missing_option_arg, Name}}) end. %% @doc Add an option with an implicit or assumed argument. -spec add_option_with_implicit_incrementable_arg(option_spec() | arg_spec(), [option()]) -> [option()]. add_option_with_implicit_incrementable_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> case arg_spec_type(ArgSpec) of boolean -> %% Special case for boolean arguments: if there is no argument we %% set the value to 'true'. [{Name, 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}. case OptAcc of [{Name, Count} | Tail] -> [{Name, Count + 1} | Tail]; _ -> [{Name, 1} | OptAcc] end; _ -> throw({error, {missing_option_arg, Name}}) end. %% @doc Retrieve the data type form an argument specification. -spec arg_spec_type(arg_spec()) -> arg_type() | undefined. arg_spec_type({Type, _DefaultArg}) -> Type; arg_spec_type(Type) when is_atom(Type) -> Type. %% @doc Convert an argument string to its corresponding data type. -spec to_type(arg_spec() | arg_type(), string()) -> arg_value(). to_type({Type, _DefaultArg}, Arg) -> to_type(Type, Arg); to_type(binary, Arg) -> list_to_binary(Arg); to_type(utf8_binary, Arg) -> unicode:characters_to_binary(Arg); to_type(atom, Arg) -> list_to_atom(Arg); to_type(integer, Arg) -> list_to_integer(Arg); to_type(float, Arg) -> list_to_float(Arg); to_type(boolean, Arg) -> LowerArg = lowercase(Arg), case is_arg_true(LowerArg) of true -> true; _ -> case is_arg_false(LowerArg) of true -> false; false -> erlang:error(badarg) end end; to_type(_Type, Arg) -> Arg. -spec is_arg_true(string()) -> boolean(). is_arg_true(Arg) -> (Arg =:= "true") orelse (Arg =:= "t") orelse (Arg =:= "yes") orelse (Arg =:= "y") orelse (Arg =:= "on") orelse (Arg =:= "enabled") orelse (Arg =:= "1"). -spec is_arg_false(string()) -> boolean(). is_arg_false(Arg) -> (Arg =:= "false") orelse (Arg =:= "f") orelse (Arg =:= "no") orelse (Arg =:= "n") orelse (Arg =:= "off") orelse (Arg =:= "disabled") orelse (Arg =:= "0"). -spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean(). is_valid_arg({Type, _DefaultArg}, Arg) -> is_valid_arg(Type, Arg); is_valid_arg(boolean, Arg) -> is_boolean_arg(Arg); is_valid_arg(integer, Arg) -> is_non_neg_integer_arg(Arg); is_valid_arg(float, Arg) -> is_non_neg_float_arg(Arg); is_valid_arg(_Type, _Arg) -> true. -spec is_implicit_arg(arg_spec(), nonempty_string()) -> boolean(). is_implicit_arg({Type, _DefaultArg}, Arg) -> is_implicit_arg(Type, Arg); is_implicit_arg(boolean, Arg) -> not is_boolean_arg(Arg); is_implicit_arg(integer, Arg) -> not is_integer_arg(Arg); is_implicit_arg(_Type, _Arg) -> false. -spec is_boolean_arg(string()) -> boolean(). is_boolean_arg(Arg) -> LowerArg = lowercase(Arg), is_arg_true(LowerArg) orelse is_arg_false(LowerArg). -spec is_integer_arg(string()) -> boolean(). is_integer_arg("-" ++ Tail) -> is_non_neg_integer_arg(Tail); is_integer_arg(Arg) -> is_non_neg_integer_arg(Arg). -spec is_non_neg_integer_arg(string()) -> boolean(). is_non_neg_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 -> is_non_neg_integer_arg(Tail); is_non_neg_integer_arg([_Head | _Tail]) -> false; is_non_neg_integer_arg([]) -> true. -spec is_non_neg_float_arg(string()) -> boolean(). is_non_neg_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. -> is_non_neg_float_arg(Tail); is_non_neg_float_arg([_Head | _Tail]) -> false; is_non_neg_float_arg([]) -> true. %% @doc Show a message on standard_error indicating the command line options and %% arguments that are supported by the program. -spec usage([option_spec()], string()) -> ok. usage(OptSpecList, ProgramName) -> usage(OptSpecList, ProgramName, standard_error). %% @doc Show a message on standard_error or standard_io indicating the command line options and %% arguments that are supported by the program. -spec usage([option_spec()], string(), output_stream() | string()) -> ok. usage(OptSpecList, ProgramName, OutputStream) when is_atom(OutputStream) -> io:format(OutputStream, "~ts~n~n~ts~n", [unicode:characters_to_list(usage_cmd_line(ProgramName, OptSpecList)), unicode:characters_to_list(usage_options(OptSpecList))]); %% @doc Show a message on standard_error 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. usage(OptSpecList, ProgramName, CmdLineTail) -> usage(OptSpecList, ProgramName, CmdLineTail, standard_error). %% @doc Show a message on standard_error or standard_io 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()], ProgramName :: string(), CmdLineTail :: string(), output_stream() | [{string(), string()}]) -> ok. usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) -> io:format(OutputStream, "~ts~n~n~ts~n", [unicode:characters_to_list(usage_cmd_line(ProgramName, OptSpecList, CmdLineTail)), unicode:characters_to_list(usage_options(OptSpecList))]); %% @doc Show a message on standard_error 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. usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) -> usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error). %% @doc Show a message on standard_error or standard_io 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()], ProgramName :: string(), CmdLineTail :: string(), [{OptionName :: string(), Help :: string()}], output_stream()) -> ok. usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, OutputStream) -> io:format(OutputStream, "~ts~n~n~ts~n", [unicode:characters_to_list(usage_cmd_line(ProgramName, OptSpecList, CmdLineTail)), unicode:characters_to_list(usage_options(OptSpecList, OptionsTail))]). %% @doc Show a message on standard_error or standard_io indicating the %% command line options and arguments that are supported by the %% program. The Description allows for structured command line usage %% that works in addition to the standard options, and appears between %% the usage_cmd_line and usage_options sections. 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()], ProgramName :: string(), CmdLineTail :: string(), Description :: string(), [{OptionName :: string(), Help :: string()}], output_stream()) -> ok. usage(OptSpecList, ProgramName, CmdLineTail, Description, OptionsTail, OutputStream) -> io:format(OutputStream, "~ts~n~n~ts~n~n~ts~n", [unicode:characters_to_list(usage_cmd_line(ProgramName, OptSpecList, CmdLineTail)), Description, unicode:characters_to_list(usage_options(OptSpecList, OptionsTail))]). -spec usage_cmd_line(ProgramName :: string(), [option_spec()]) -> iolist(). usage_cmd_line(ProgramName, OptSpecList) -> usage_cmd_line(ProgramName, OptSpecList, ""). -spec usage_cmd_line(ProgramName :: string(), [option_spec()], CmdLineTail :: string()) -> iolist(). usage_cmd_line(ProgramName, OptSpecList, CmdLineTail) -> Prefix = "Usage: " ++ ProgramName, PrefixLength = length(Prefix), LineLength = line_length(), %% Only align the command line options after the program name when there is %% enough room to do so (i.e. at least 25 characters). If not, show the %% command line options below the program name with a 2-character indentation. if (LineLength - PrefixLength) > ?MIN_USAGE_COMMAND_LINE_OPTION_LENGTH -> Indentation = lists:duplicate(PrefixLength, $\s), [FirstOptLine | OptLines] = usage_cmd_line_options(LineLength - PrefixLength, OptSpecList, CmdLineTail), IndentedOptLines = [[Indentation | OptLine] || OptLine <- OptLines], [Prefix, FirstOptLine | IndentedOptLines]; true -> IndentedOptLines = [[" " | OptLine] || OptLine <- usage_cmd_line_options(LineLength, OptSpecList, CmdLineTail)], [Prefix, $\n, IndentedOptLines] end. %% @doc Return a list of the lines corresponding to the usage command line %% already wrapped according to the maximum MaxLineLength. -spec usage_cmd_line_options(MaxLineLength :: non_neg_integer(), [option_spec()], CmdLineTail :: string()) -> iolist(). usage_cmd_line_options(MaxLineLength, OptSpecList, CmdLineTail) -> usage_cmd_line_options(MaxLineLength, OptSpecList ++ lexemes(CmdLineTail, " "), [], 0, []). usage_cmd_line_options(MaxLineLength, [OptSpec | Tail], LineAcc, LineAccLength, Acc) -> Option = [$\s | lists:flatten(usage_cmd_line_option(OptSpec))], OptionLength = length(Option), %% We accumulate the options in LineAcc until its length is over the %% maximum allowed line length. When that happens, we append the line in %% LineAcc to the list with all the lines in the command line (Acc). NewLineAccLength = LineAccLength + OptionLength, if NewLineAccLength < MaxLineLength -> usage_cmd_line_options(MaxLineLength, Tail, [Option | LineAcc], NewLineAccLength, Acc); true -> usage_cmd_line_options(MaxLineLength, Tail, [Option], OptionLength + 1, [lists:reverse([$\n | LineAcc]) | Acc]) end; usage_cmd_line_options(MaxLineLength, [], [_ | _] = LineAcc, _LineAccLength, Acc) -> %% If there was a non-empty line in LineAcc when there are no more options %% to process, we add it to the list of lines to return. usage_cmd_line_options(MaxLineLength, [], [], 0, [lists:reverse(LineAcc) | Acc]); usage_cmd_line_options(_MaxLineLength, [], [], _LineAccLength, Acc) -> lists:reverse(Acc). -spec usage_cmd_line_option(option_spec()) -> string(). usage_cmd_line_option({_Name, Short, _Long, undefined, _Help}) when Short =/= undefined -> %% For options with short form and no argument. [$[, $-, Short, $]]; usage_cmd_line_option({_Name, _Short, Long, undefined, _Help}) when Long =/= undefined -> %% For options with only long form and no argument. [$[, $-, $-, Long, $]]; usage_cmd_line_option({_Name, _Short, _Long, undefined, _Help}) -> []; usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_atom(ArgSpec) -> %% For options with no default argument. if %% For options with short form and argument. Short =/= undefined -> [$[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]]; %% For options with only long form and argument. Long =/= undefined -> [$[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]]; %% For options with neither short nor long form and argument. true -> [$[, $<, atom_to_list(Name), $>, $]] end; usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_tuple(ArgSpec) -> %% For options with default argument. if %% For options with short form and default argument. Short =/= undefined -> [$[, $-, Short, $\s, $[, $<, atom_to_list(Name), $>, $], $]]; %% For options with only long form and default argument. Long =/= undefined -> [$[, $-, $-, Long, $\s, $[, $<, atom_to_list(Name), $>, $], $]]; %% For options with neither short nor long form and default argument. true -> [$[, $<, atom_to_list(Name), $>, $]] end; usage_cmd_line_option(Option) when is_list(Option) -> %% For custom options that are added to the command line. Option. %% @doc Return a list of help messages to print for each of the options and arguments. -spec usage_options([option_spec()]) -> [string()]. usage_options(OptSpecList) -> usage_options(OptSpecList, []). %% @doc Return a list of usage lines to print for each of the options and arguments. -spec usage_options([option_spec()], [{OptionName :: string(), Help :: string()}]) -> [string()]. usage_options(OptSpecList, CustomHelp) -> %% Add the usage lines corresponding to the option specifications. {MaxOptionLength0, UsageLines0} = add_option_spec_help_lines(OptSpecList, 0, []), %% Add the custom usage lines. {MaxOptionLength, UsageLines} = add_custom_help_lines(CustomHelp, MaxOptionLength0, UsageLines0), MaxLineLength = line_length(), lists:reverse([format_usage_line(MaxOptionLength + 1, MaxLineLength, UsageLine) || UsageLine <- UsageLines]). -spec add_option_spec_help_lines([option_spec()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) -> {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}. add_option_spec_help_lines([OptSpec | Tail], PrevMaxOptionLength, Acc) -> OptionText = usage_option_text(OptSpec), HelpText = usage_help_text(OptSpec), {MaxOptionLength, ColsWithLength} = get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength), add_option_spec_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]); add_option_spec_help_lines([], MaxOptionLength, Acc) -> {MaxOptionLength, Acc}. -spec add_custom_help_lines([usage_line()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) -> {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}. add_custom_help_lines([CustomCols | Tail], PrevMaxOptionLength, Acc) -> {MaxOptionLength, ColsWithLength} = get_max_option_length(CustomCols, PrevMaxOptionLength), add_custom_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]); add_custom_help_lines([], MaxOptionLength, Acc) -> {MaxOptionLength, Acc}. -spec usage_option_text(option_spec()) -> string(). usage_option_text({Name, undefined, undefined, _ArgSpec, _Help}) -> %% Neither short nor long form (non-option argument). "<" ++ atom_to_list(Name) ++ ">"; usage_option_text({_Name, Short, undefined, _ArgSpec, _Help}) -> %% Only short form. [$-, Short]; usage_option_text({_Name, undefined, Long, _ArgSpec, _Help}) -> %% Only long form. [$-, $- | Long]; usage_option_text({_Name, Short, Long, _ArgSpec, _Help}) -> %% Both short and long form. [$-, Short, $,, $\s, $-, $- | Long]. -spec usage_help_text(option_spec()) -> string(). usage_help_text({_Name, _Short, _Long, {ArgType, ArgValue}, [_ | _] = Help}) -> Help ++ " [default: " ++ default_arg_value_to_string(ArgType, ArgValue) ++ "]"; usage_help_text({_Name, _Short, _Long, _ArgSpec, Help}) -> Help. %% @doc Calculate the maximum width of the column that shows the option's short %% and long form. -spec get_max_option_length(usage_line(), PrevMaxOptionLength :: non_neg_integer()) -> {MaxOptionLength :: non_neg_integer(), usage_line_with_length()}. get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength) -> OptionLength = length(OptionText), {erlang:max(OptionLength, PrevMaxOptionLength), {OptionLength, OptionText, HelpText}}. %% @doc Format the usage line that is shown for the options' usage. Each usage %% line has 2 columns. The first column shows the options in their short %% and long form. The second column shows the wrapped (if necessary) help %% text lines associated with each option. e.g.: %% %% -h, --host Database server host name or IP address; this is the %% hostname of the server where the database is running %% [default: localhost] %% -p, --port Database server port [default: 1000] %% -spec format_usage_line(MaxOptionLength :: non_neg_integer(), MaxLineLength :: non_neg_integer(), usage_line_with_length()) -> iolist(). format_usage_line(MaxOptionLength, MaxLineLength, {OptionLength, OptionText, [_ | _] = HelpText}) when MaxOptionLength < (MaxLineLength div 2) -> %% If the width of the column where the options are shown is smaller than %% half the width of a console line then we show the help text line aligned %% next to its corresponding option, with a separation of at least 2 %% characters. [Head | Tail] = wrap_text_line(MaxLineLength - MaxOptionLength - 3, HelpText), FirstLineIndentation = lists:duplicate(MaxOptionLength - OptionLength + 1, $\s), Indentation = [$\n | lists:duplicate(MaxOptionLength + 3, $\s)], [" ", OptionText, FirstLineIndentation, Head, [[Indentation, Line] || Line <- Tail], $\n]; format_usage_line(_MaxOptionLength, MaxLineLength, {_OptionLength, OptionText, [_ | _] = HelpText}) -> %% If the width of the first column is bigger than the width of a console %% line, we show the help text on the next line with an indentation of 6 %% characters. HelpLines = wrap_text_line(MaxLineLength - 6, HelpText), [" ", OptionText, [["\n ", Line] || Line <- HelpLines], $\n]; format_usage_line(_MaxOptionLength, _MaxLineLength, {_OptionLength, OptionText, _HelpText}) -> [" ", OptionText, $\n]. %% @doc Wrap a text line converting it into several text lines so that the %% length of each one of them is never over Length characters. -spec wrap_text_line(Length :: non_neg_integer(), Text :: string()) -> [string()]. wrap_text_line(Length, Text) -> wrap_text_line(Length, Text, [], 0, []). wrap_text_line(Length, [Char | Tail], Acc, Count, CurrentLineAcc) when Count < Length -> wrap_text_line(Length, Tail, Acc, Count + 1, [Char | CurrentLineAcc]); wrap_text_line(Length, [_ | _] = Help, Acc, Count, CurrentLineAcc) -> %% Look for the first whitespace character in the current (reversed) line %% buffer to get a wrapped line. If there is no whitespace just cut the %% line at the position corresponding to the maximum length. {NextLineAcc, WrappedLine} = case cspan(CurrentLineAcc, " \t") of WhitespacePos when WhitespacePos < Count -> lists:split(WhitespacePos, CurrentLineAcc); _ -> {[], CurrentLineAcc} end, wrap_text_line(Length, Help, [lists:reverse(WrappedLine) | Acc], length(NextLineAcc), NextLineAcc); wrap_text_line(_Length, [], Acc, _Count, [_ | _] = CurrentLineAcc) -> %% If there was a non-empty line when we reached the buffer, add it to the accumulator lists:reverse([lists:reverse(CurrentLineAcc) | Acc]); wrap_text_line(_Length, [], Acc, _Count, _CurrentLineAcc) -> lists:reverse(Acc). default_arg_value_to_string(_, Value) when is_atom(Value) -> atom_to_list(Value); default_arg_value_to_string(binary, Value) when is_binary(Value) -> binary_to_list(Value); default_arg_value_to_string(utf8_binary, Value) when is_binary(Value) -> unicode:characters_to_list(Value); default_arg_value_to_string(_, Value) when is_integer(Value) -> integer_to_list(Value); default_arg_value_to_string(_, Value) when is_float(Value) -> lists:flatten(io_lib:format("~w", [Value])); default_arg_value_to_string(_, Value) -> Value. %% @doc Tokenize a command line string with support for single and double %% quoted arguments (needed for arguments that have embedded whitespace). %% The function also supports the expansion of environment variables in %% both the Unix (${VAR}; $VAR) and Windows (%VAR%) formats. It does NOT %% support wildcard expansion of paths. -spec tokenize(CmdLine :: string()) -> [nonempty_string()]. tokenize(CmdLine) -> tokenize(CmdLine, [], []). -spec tokenize(CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()]. tokenize([Sep | Tail], Acc, ArgAcc) when ?IS_WHITESPACE(Sep) -> NewAcc = case ArgAcc of [_ | _] -> %% Found separator: add to the list of arguments. [lists:reverse(ArgAcc) | Acc]; [] -> %% Found separator with no accumulated argument; discard it. Acc end, tokenize(Tail, NewAcc, []); tokenize([QuotationMark | Tail], Acc, ArgAcc) when QuotationMark =:= $"; QuotationMark =:= $' -> %% Quoted argument (might contain spaces, tabs, etc.) tokenize_quoted_arg(QuotationMark, Tail, Acc, ArgAcc); tokenize([Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% -> %% Unix and Windows environment variable expansion: ${VAR}; $VAR; %VAR% {NewCmdLine, Var} = expand_env_var(CmdLine), tokenize(NewCmdLine, Acc, lists:reverse(Var, ArgAcc)); tokenize([$\\, Char | Tail], Acc, ArgAcc) -> %% Escaped char. tokenize(Tail, Acc, [Char | ArgAcc]); tokenize([Char | Tail], Acc, ArgAcc) -> tokenize(Tail, Acc, [Char | ArgAcc]); tokenize([], Acc, []) -> lists:reverse(Acc); tokenize([], Acc, ArgAcc) -> lists:reverse([lists:reverse(ArgAcc) | Acc]). -spec tokenize_quoted_arg(QuotationMark :: char(), CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()]. tokenize_quoted_arg(QuotationMark, [QuotationMark | Tail], Acc, ArgAcc) -> %% End of quoted argument tokenize(Tail, Acc, ArgAcc); tokenize_quoted_arg(QuotationMark, [$\\, Char | Tail], Acc, ArgAcc) -> %% Escaped char. tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]); tokenize_quoted_arg($" = QuotationMark, [Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% -> %% Unix and Windows environment variable expansion (only for double-quoted arguments): ${VAR}; $VAR; %VAR% {NewCmdLine, Var} = expand_env_var(CmdLine), tokenize_quoted_arg(QuotationMark, NewCmdLine, Acc, lists:reverse(Var, ArgAcc)); tokenize_quoted_arg(QuotationMark, [Char | Tail], Acc, ArgAcc) -> tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]); tokenize_quoted_arg(_QuotationMark, CmdLine, Acc, ArgAcc) -> tokenize(CmdLine, Acc, ArgAcc). -spec expand_env_var(CmdLine :: nonempty_string()) -> {string(), string()}. expand_env_var(CmdLine) -> case CmdLine of "${" ++ Tail -> expand_env_var("${", $}, Tail, []); "$" ++ Tail -> expand_env_var("$", Tail, []); "%" ++ Tail -> expand_env_var("%", $%, Tail, []) end. -spec expand_env_var(Prefix :: string(), EndMark :: char(), CmdLine :: string(), Acc :: string()) -> {string(), string()}. expand_env_var(Prefix, EndMark, [Char | Tail], Acc) when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) -> expand_env_var(Prefix, EndMark, Tail, [Char | Acc]); expand_env_var(Prefix, EndMark, [EndMark | Tail], Acc) -> {Tail, get_env_var(Prefix, [EndMark], Acc)}; expand_env_var(Prefix, _EndMark, CmdLine, Acc) -> {CmdLine, Prefix ++ lists:reverse(Acc)}. -spec expand_env_var(Prefix :: string(), CmdLine :: string(), Acc :: string()) -> {string(), string()}. expand_env_var(Prefix, [Char | Tail], Acc) when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) -> expand_env_var(Prefix, Tail, [Char | Acc]); expand_env_var(Prefix, CmdLine, Acc) -> {CmdLine, get_env_var(Prefix, "", Acc)}. -spec get_env_var(Prefix :: string(), Suffix :: string(), Acc :: string()) -> string(). get_env_var(Prefix, Suffix, [_ | _] = Acc) -> Name = lists:reverse(Acc), %% Only expand valid/existing variables. case os:getenv(Name) of false -> Prefix ++ Name ++ Suffix; Value -> Value end; get_env_var(Prefix, Suffix, []) -> Prefix ++ Suffix. -spec line_length() -> 0..?LINE_LENGTH. line_length() -> case io:columns() of {ok, Columns} when Columns < ?LINE_LENGTH -> Columns - 1; _ -> ?LINE_LENGTH end. -spec to_string(term()) -> string(). to_string(List) when is_list(List) -> case io_lib:printable_list(List) of true -> List; false -> io_lib:format("~p", [List]) end; to_string(Atom) when is_atom(Atom) -> atom_to_list(Atom); to_string(Value) -> io_lib:format("~p", [Value]). %% OTP-20/21 conversion to unicode string module -ifdef(unicode_str). lowercase(Str) -> string:lowercase(Str). lexemes(Str, Separators) -> string:lexemes(Str, Separators). cspan(Str, Chars) -> length(element(1,string:take(Str, Chars, true))). -else. lowercase(Str) -> string:to_lower(Str). lexemes(Str, Separators) -> string:tokens(Str, Separators). cspan(Str, Chars) -> string:cspan(Str, Chars). -endif. 07070100000012000041ED000003E800000064000000025F4A649F00000000000000000000000000000000000000000000001200000000getopt-1.0.2/test07070100000013000081A4000003E800000064000000015F4A649F00005E75000000000000000000000000000000000000002200000000getopt-1.0.2/test/getopt_test.erl%%%------------------------------------------------------------------- %%% @author Juan Jose Comellas <juanjo@comellas.org> %%% @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 %%% 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(getopt_test). -author('juanjo@comellas.org'). -include_lib("eunit/include/eunit.hrl"). -import(getopt, [parse/2, check/2, parse_and_check/2, format_error/2, tokenize/1]). -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 %%%------------------------------------------------------------------- %%% Main test for the getopt/1 function. parse_main_test_() -> 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"}, ShortBool = {short_bool, $f, undefined, boolean, "Option with only short form and boolean argument"}, ShortInt = {short_int, $g, undefined, integer, "Option with only short form and integer argument"}, ShortFloat = {short_float, $h, undefined, float, "Option with only short form and float 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"}, LongBool = {long_bool, undefined, "long-bool", boolean, "Option with only long form and boolean argument"}, LongInt = {long_int, undefined, "long-int", integer, "Option with only long form and integer argument"}, LongFloat = {long_float, undefined, "long-float", float, "Option with only long form and float argument"}, ShortLong = {short_long, $i, "short-long", undefined, "Option with short form, long form and no argument"}, ShortLongArg = {short_long_arg, $j, "short-long-arg", string, "Option with short form, long form and argument"}, ShortLongDefArg = {short_long_def_arg, $k, "short-long-def-arg", {string, "default-short-long"}, "Option with short form, long form and default argument"}, ShortLongBool = {short_long_bool, $l, "short-long-bool", boolean, "Option with short form, long form and boolean argument"}, ShortLongInt = {short_long_int, $m, "short-long-int", integer, "Option with short form, long form and integer argument"}, ShortLongFloat = {short_long_float, $n, "short-long-float", float, "Option with short form, long form and float argument"}, NonOptArg = {non_opt_arg, undefined, undefined, undefined, "Non-option argument"}, NonOptBool = {non_opt_bool, undefined, undefined, boolean, "Non-option boolean argument"}, NonOptInt = {non_opt_int, undefined, undefined, integer, "Non-option integer argument"}, NonOptFloat = {non_opt_float, undefined, undefined, float, "Non-option float argument"}, CombinedOptSpecs = [ Short, ShortArg, ShortBool, ShortInt, Short2, Short3, Long, LongArg, LongBool, LongInt, ShortLong, ShortLongArg, ShortLongBool, ShortLongInt, NonOptArg, NonOptInt, ShortDefArg, LongDefArg, ShortLongDefArg ], CombinedArgs = [ [$-, ?SHORT(Short)], [$-, ?SHORT(ShortArg)], "value1", [$-, ?SHORT(ShortBool)], [$-, ?SHORT(ShortInt)], "100", [$-, ?SHORT(Short2), ?SHORT(Short3)], "--long", "--long-arg", "value2", "--long-bool", "true", "--long-int", "101", [$-, ?SHORT(ShortLong)], "--short-long-arg", "value3", "--short-long-bool", "false", "--short-long-int", "103", "value4", "104", "dummy1", "dummy2" ], CombinedOpts = [ ?NAME(Short), {?NAME(ShortArg), "value1"}, {?NAME(ShortBool), true}, {?NAME(ShortInt), 100}, ?NAME(Short2), ?NAME(Short3), ?NAME(Long), {?NAME(LongArg), "value2"}, {?NAME(LongBool), true}, {?NAME(LongInt), 101}, ?NAME(ShortLong), {?NAME(ShortLongArg), "value3"}, {?NAME(ShortLongBool), false}, {?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"], [ {"No options and no arguments", ?_assertMatch({ok, {[], []}}, parse([], []))}, {"Options and no arguments", ?_assertMatch({ok, {[], []}}, parse([Short], []))}, {"No options and single argument", ?_assertMatch({ok, {[], ["arg1"]}}, parse([], ["arg1"]))}, {"No options and arguments", ?_assertMatch({ok, {[], ["arg1", "arg2"]}}, parse([], ["arg1", "arg2"]))}, {"Unused options and arguments", ?_assertMatch({ok, {[], ["arg1", "arg2"]}}, parse([Short], ["arg1", "arg2"]))}, %% Option terminator {"Option terminator before arguments", ?_assertEqual({ok, {[], [[$-, ?SHORT(Short)], "arg1", "arg2"]}}, parse([Short], ["--", [$-, ?SHORT(Short)], "arg1", "arg2"]))}, {"Option terminator between arguments", ?_assertEqual({ok, {[], ["arg1", [$-, ?SHORT(Short)], "arg2"]}}, parse([Short], ["arg1", "--", [$-, ?SHORT(Short)], "arg2"]))}, {"Option terminator after options", ?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], [[$-, ?SHORT(Short)], "--", "arg1", "arg2"]))}, {"Option terminator at the end", ?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], [[$-, ?SHORT(Short)], "arg1", "arg2", "--"]))}, %% Options with only the short form {?HELP(Short), ?_assertMatch({ok, {[short], []}}, parse([Short], [[$-, ?SHORT(Short)]]))}, {?HELP(ShortArg), ?_assertMatch({ok, {[{short_arg, "value"}], []}}, parse([ShortArg], [[$-, ?SHORT(ShortArg)], "value"]))}, {?HELP(ShortDefArg), ?_assertMatch({ok, {[{short_def_arg, "default-short"}], []}}, parse([ShortDefArg], []))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, true}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool)]]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, true}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool), $t]]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, true}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool), $1]]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, true}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool)], "true"]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, false}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool), $f]]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, false}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool), $0]]))}, {?HELP(ShortBool), ?_assertMatch({ok, {[{short_bool, false}], []}}, parse([ShortBool], [[$-, ?SHORT(ShortBool)], "false"]))}, {?HELP(ShortInt), ?_assertMatch({ok, {[{short_int, 100}], []}}, parse([ShortInt], [[$-, ?SHORT(ShortInt), $1, $0, $0]]))}, {?HELP(ShortInt), ?_assertMatch({ok, {[{short_int, 100}], []}}, parse([ShortInt], [[$-, ?SHORT(ShortInt)], "100"]))}, {?HELP(ShortInt), ?_assertMatch({ok, {[{short_int, 3}], []}}, parse([ShortInt], [[$-, ?SHORT(ShortInt), ?SHORT(ShortInt), ?SHORT(ShortInt)]]))}, {?HELP(ShortFloat), ?_assertMatch({ok, {[{short_float, 1.0}], []}}, parse([ShortFloat], [[$-, ?SHORT(ShortFloat), $1, $., $0]]))}, {?HELP(ShortFloat), ?_assertMatch({ok, {[{short_float, 1.0}], []}}, parse([ShortFloat], [[$-, ?SHORT(ShortFloat)], "1.0"]))}, {"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(Short)], "arg1", "arg2"]))}, {"Short form option and arguments (unsorted)", ?_assertMatch({ok, {[short], ["arg1", "arg2"]}}, parse([Short], ["arg1", [$-, ?SHORT(Short)], "arg2"]))}, %% Options with only the long form {?HELP(Long), ?_assertMatch({ok, {[long], []}}, parse([Long], ["--long"]))}, {?HELP(LongArg), ?_assertMatch({ok, {[{long_arg, "value"}], []}}, parse([LongArg], ["--long-arg", "value"]))}, {?HELP(LongArg), ?_assertMatch({ok, {[{long_arg, "value"}], []}}, parse([LongArg], ["--long-arg=value"]))}, {?HELP(LongArg), ?_assertMatch({ok, {[{long_arg, "value=1"}], []}}, parse([LongArg], ["--long-arg=value=1"]))}, {?HELP(LongDefArg), ?_assertMatch({ok, {[{long_def_arg, "default-long"}], []}}, parse([LongDefArg], []))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, true}], []}}, parse([LongBool], ["--long-bool"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, true}], []}}, parse([LongBool], ["--long-bool=t"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, true}], []}}, parse([LongBool], ["--long-bool=1"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, true}], []}}, parse([LongBool], ["--long-bool", "true"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, false}], []}}, parse([LongBool], ["--long-bool=f"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, false}], []}}, parse([LongBool], ["--long-bool=0"]))}, {?HELP(LongBool), ?_assertMatch({ok, {[{long_bool, false}], []}}, parse([LongBool], ["--long-bool", "false"]))}, {?HELP(LongInt), ?_assertMatch({ok, {[{long_int, 100}], []}}, parse([LongInt], ["--long-int", "100"]))}, {?HELP(LongInt), ?_assertMatch({ok, {[{long_int, 100}], []}}, parse([LongInt], ["--long-int=100"]))}, {?HELP(LongFloat), ?_assertMatch({ok, {[{long_float, 1.0}], []}}, parse([LongFloat], ["--long-float", "1.0"]))}, {?HELP(LongFloat), ?_assertMatch({ok, {[{long_float, 1.0}], []}}, parse([LongFloat], ["--long-float=1.0"]))}, {"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 {?HELP(ShortLong), ?_assertMatch({ok, {[short_long], []}}, parse([ShortLong], [[$-, ?SHORT(ShortLong)]]))}, {?HELP(ShortLong), ?_assertMatch({ok, {[short_long], []}}, parse([ShortLong], ["--short-long"]))}, {?HELP(ShortLongArg), ?_assertMatch({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(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(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, true}], []}}, parse([ShortLongBool], [[$-, ?SHORT(ShortLongBool)]]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, true}], []}}, parse([ShortLongBool], [[$-, ?SHORT(ShortLongBool), $1]]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, true}], []}}, parse([ShortLongBool], [[$-, ?SHORT(ShortLongBool)], "yes"]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, true}], []}}, parse([ShortLongBool], ["--short-long-bool", "on"]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, true}], []}}, parse([ShortLongBool], ["--short-long-bool=enabled"]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, false}], []}}, parse([ShortLongBool], [[$-, ?SHORT(ShortLongBool), $0]]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, false}], []}}, parse([ShortLongBool], [[$-, ?SHORT(ShortLongBool)], "no"]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, false}], []}}, parse([ShortLongBool], ["--short-long-bool", "off"]))}, {?HELP(ShortLongBool), ?_assertMatch({ok, {[{short_long_bool, false}], []}}, parse([ShortLongBool], ["--short-long-bool=disabled"]))}, {?HELP(ShortLongInt), ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], [[$-, ?SHORT(ShortLongInt)], "1234"]))}, {?HELP(ShortLongInt), ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], ["--short-long-int", "1234"]))}, {?HELP(ShortLongInt), ?_assertMatch({ok, {[{short_long_int, 1234}], []}}, parse([ShortLongInt], ["--short-long-int=1234"]))}, {?HELP(ShortLongFloat), ?_assertMatch({ok, {[{short_long_float, 1.0}], []}}, parse([ShortLongFloat], [[$-, ?SHORT(ShortLongFloat)], "1.0"]))}, {?HELP(ShortLongFloat), ?_assertMatch({ok, {[{short_long_float, 1.0}], []}}, parse([ShortLongFloat], ["--short-long-float", "1.0"]))}, {?HELP(ShortLongFloat), ?_assertMatch({ok, {[{short_long_float, 1.0}], []}}, parse([ShortLongFloat], ["--short-long-float=1.0"]))}, %% Non-option arguments {?HELP(NonOptArg), ?_assertMatch({ok, {[{non_opt_arg, "value"}], []}}, parse([NonOptArg], ["value"]))}, {?HELP(NonOptBool), ?_assertMatch({ok, {[{non_opt_bool, false}], []}}, parse([NonOptBool], ["n"]))}, {?HELP(NonOptInt), ?_assertMatch({ok, {[{non_opt_int, 1234}], []}}, parse([NonOptInt], ["1234"]))}, {?HELP(NonOptFloat), ?_assertMatch({ok, {[{non_opt_float, 1.0}], []}}, parse([NonOptFloat], ["1.0"]))}, {"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 non-integer argument", ?_assertEqual({ok, {[{short_int, 1}], ["value"]}}, parse([ShortInt], [[$-, ?SHORT(ShortInt)], "value"]))} ]. %% Real world test for getopt/1. parse_multiple_repetitions_test_() -> OptSpecList = [ {define, $D, "define", string, "Define a variable"}, {debug, $d, "debug", integer, "Debug level"}, {offset, $o, "offset", float, "Offset"}, {verbose, $v, "verbose", boolean, "Enable verbose output"} ], [ {"Multiple repetitions of the same option", ?_assertEqual({ok, {[{define, "FOO"}, {define, "VAR1=VAL1"}, {define, "BAR"}, {verbose, true}, {verbose, true}, {debug, 2}, {offset, -61.0}, {debug, 1}, {debug, 4}], ["dummy1", "dummy2"]}}, parse(OptSpecList, "-DFOO -DVAR1=VAL1 -DBAR -vv -dd --offset=-61.0 --debug -dddd dummy1 dummy2"))} ]. %% Arguments with spaces. parse_args_with_spaces_test_() -> OptSpecList = [ {define, $D, "define", string, "Define a variable"}, {user, $u, "user", string, "User name"} ], [ {"Arguments with spaces", ?_assertEqual({ok, {[{define, "FOO BAR"}, {define, "VAR 1=VAL 1"}, {user, "my user name"}], [" dummy1 dummy2 "]}}, parse(OptSpecList, "-D'FOO BAR' -D\"VAR 1=VAL 1\" --user \"my user name\" ' dummy1 dummy2 "))} ]. %% Arguments with emulated shell variable expansion. parse_variable_expansion_test_() -> Path = os:getenv("PATH"), false = os:getenv("DUMMY_VAR_THAT_MUST_NOT_EXIST"), OptSpecList = [ {path, $p, "path", string, "File path"} ], [ {"Shell variable expansion (simple Unix/bash format)", ?_assertEqual({ok, {[{path, Path}], ["$DUMMY_VAR_THAT_MUST_NOT_EXIST"]}}, parse(OptSpecList, "--path $PATH $DUMMY_VAR_THAT_MUST_NOT_EXIST"))}, {"Shell variable expansion (full Unix/bash format)", ?_assertEqual({ok, {[{path, Path}], ["${DUMMY_VAR_THAT_MUST_NOT_EXIST}"]}}, parse(OptSpecList, " --path ${PATH} ${DUMMY_VAR_THAT_MUST_NOT_EXIST} "))}, {"Incomplete variable expansion (full Unix/bash format)", ?_assertEqual({ok, {[{path, "${PATH"}], ["${DUMMY_VAR_THAT_MUST_NOT_EXIST}"]}}, parse(OptSpecList, " --path ${PATH ${DUMMY_VAR_THAT_MUST_NOT_EXIST} "))}, {"Shell variable expansion (Windows format)", ?_assertEqual({ok, {[{path, Path}], ["%DUMMY_VAR_THAT_MUST_NOT_EXIST%"]}}, parse(OptSpecList, " --path %PATH% %DUMMY_VAR_THAT_MUST_NOT_EXIST% "))}, {"Incomplete variable expansion (Windows format)", ?_assertEqual({ok, {[{path, "%PATH"}], ["%DUMMY_VAR_THAT_MUST_NOT_EXIST%"]}}, parse(OptSpecList, " --path %PATH %DUMMY_VAR_THAT_MUST_NOT_EXIST% "))} ]. tokenize_test_() -> %% Path = os:getenv("PATH"), [ {"Tokenize", ?_assertEqual(["ABC","abc","1234","5678","DEFGHI","\"JKL \"", "$PATH"], tokenize(" ABC abc '1234' \"5678\" 'DEF'\"GHI\" '\"JKL \"' \\$PATH"))} ]. check_test_() -> OptSpecList = [ { arg, $a, "arg", string, "Required arg"}, {short, $s, undefined, string, "short option"}, { long, undefined, "long", string, "long option"}, {other, undefined, undefined, string, "task"} ], {ok, {Opts, _}} = parse(OptSpecList, ""), [ {"Check required options", ?_assertEqual({error, {missing_required_option, arg}}, check(OptSpecList, Opts))}, {"Parse arguments and check required options", ?_assertEqual({error, {missing_required_option, arg}}, parse_and_check(OptSpecList, ""))}, {"Parse arguments and check required option args", ?_assertEqual({error, {missing_option_arg, arg}}, parse_and_check(OptSpecList, "-a"))}]. format_error_test_() -> OptSpecList = [ { arg, $a, "arg", string, "Required arg"}, { short, $s, undefined, string, "short option"}, { long, undefined, "long", string, "long option"}, { other, undefined, undefined, string, "task"} ], [ {"Format missing option error test 1", ?_assertEqual("missing required option: -a (arg)", format_error(OptSpecList, {error, {missing_required_option, arg}}))}, {"Format missing option error test 2", ?_assertEqual("missing required option: -a (arg)", format_error(OptSpecList, {missing_required_option, arg}))}, {"Format missing option error test 3", ?_assertEqual("missing required option: -s", format_error(OptSpecList, {missing_required_option, short}))}, {"Format missing option error test 4", ?_assertEqual("missing required option: --long", format_error(OptSpecList, {missing_required_option, long}))}, {"Format missing option error test 5", ?_assertEqual("missing required option: <other>", format_error(OptSpecList, {missing_required_option, other}))}, {"Format invalid option error test 1", ?_assertEqual("invalid option: --verbose", format_error(OptSpecList, {error, {invalid_option, "--verbose"}}))}, {"Format invalid option argument error test 1", ?_assertEqual("invalid option argument: arg_value", format_error(OptSpecList, {error, {invalid_option_arg, "arg_value"}}))}, {"Format invalid option argument error test 2", ?_assertEqual("option 'verbose' has invalid argument: 100", format_error(OptSpecList, {error, {invalid_option_arg, {verbose, "100"}}}))}, {"Format missing option argument error test 1", ?_assertEqual("missing option argument: -a (arg) <arg>", format_error(OptSpecList, {error, {missing_option_arg, arg}}))}, {"Format missing option argument error test 2", ?_assertEqual("missing option argument: -a (arg) <arg>", format_error(OptSpecList, {missing_option_arg, arg}))}, {"Format missing option argument error test 3", ?_assertEqual("missing option argument: -s <short>", format_error(OptSpecList, {missing_option_arg, short}))}, {"Format missing option argument error test 4", ?_assertEqual("missing option argument: --long <long>", format_error(OptSpecList, {missing_option_arg, long}))}, {"Format missing option argument error test 5", ?_assertError(_, format_error(OptSpecList, {missing_option_arg, unknown}))} ]. utf8_binary_test_() -> OptSpecList = [{utf8, undefined, "utf8", utf8_binary, "UTF-8 arg"}], Unicode = [228, 220, 223, 1455], Utf8 = unicode:characters_to_binary(Unicode), io:setopts(standard_error, [{encoding, utf8}]), OptSpecsWithDefault = [{utf8, undefined, "utf8", {utf8_binary, Utf8}, "UTF-8 arg"}], [{"Empty utf8_binary argument", ?_assertEqual({ok, {[{utf8, <<>>}], []}}, parse(OptSpecList, ["--utf8", ""]))}, {"Non empty utf8_binary argument", ?_assertEqual({ok, {[{utf8, Utf8}], []}}, parse(OptSpecList, ["--utf8", Unicode]))}, {"Default utf8_binary argument", ?_assertEqual({ok, {[{utf8, Utf8}], []}}, parse(OptSpecsWithDefault, []))}, {"Default utf8_binary argument usage", ?_assert(is_list(string:find(getopt:usage_options(OptSpecsWithDefault), Unicode)))}]. 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!200 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor