www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - args.d | a command line argument and config file parser

reply Robert burner Schadek <rburners gmail.com> writes:
args.d is a command line argument and config file parser.

The basic idea of args.d is that that command line options and 
config file options are basically the same or should be.
The configuration options are build from UDA annotated structs 
that can be nested.
The package can be used with dub "argsd": "~>0.2.0" 
http://code.dlang.org/packages/argsd

Less talking more example:

```D
import args;

/** args.d arguments are structures as shown below.
Each argument that should be searched for needs to have $(D 
 Arg())
attached to it.
$(D  Arg()) takes three kinds of parameter.
1. A $(D string) which is used as the help message for that 
argument.
2. A $(D char) which is used as the character for the short 
argument
selector.
3. A $(D Optional) value to make the argument as optional or not 
(default
Optional.yes).
The order of the three parameter is not relevant.
Arguments can be nested, see the nested $(D NestedArgument) $(D 
struct) in
$(D MyAppArguments).
Arguments can be of all primitive types, arrays of primitive 
types and $(D
enum)s.

All arguments take the shape "name value". Equal sign syntax is 
not
supported.
Array values can be given as separate values of as comma 
separated values.

The name of the argument will be derived from the name of the 
member in
the struct. The names are case sensitive.
Arguments in nested structs have the name of the struct prefixed 
(compare
"--nested.someFloatValue).

Short names must be unique. If they are not unique an Exception 
will be
thrown. Short names are used by prefixing the character with a 
single "-".
The short name "-h" is reserved for requestion the help page.

Long names are unique by definition. Long names are prefixed with 
"--".
The long name "--help" is reserved for requestion the help page.

If $(D parseArgsWithConfigFile) is used two more long names are 
reserved,
"--config", and "--genConfig". Both take a $(D string) as 
argument.
"--config filename" will try to parse the file with name $(I 
filename) and
assign the values in that file to the argument struct passed.

"--genConfig filename" can be used to create a config file with
the default values of the argument struct. The name of the config 
file is
again $(I filename).
*/


/** A enum used inside of NestedArguments */
enum NestedEnumArgument {
	one,
	two,
	many
}

/** A struct nested in MyAppArguments */
static struct NestedArguments {
	 Arg("Important Help Message") float someFloatValue;

	// D allows to assign default values to the arguments
	 Arg('z') NestedEnumArgument enumArg = NestedEnumArgument.two;
	 Arg() bool someBool;
}

/** The options to the created program. */
static struct MyAppArguments {
	 Arg(Optional.no) string inputFilename;
	 Arg('b') int[] testValues;

	/** All options inside of $(D nested) need to be prefixed with
	  "nested.".
	*/
	 Arg() NestedArguments nested;
}

import std.algorithm.comparison : equal;
import std.format : format;
import std.math : approxEqual;

/** It is good practice to have the arguments write-protected by 
default.
The following three declarations show a possible implementation.
In order to look up a argument the developer would use the $(D 
config())
function, returning him a write-protected version of the 
arguments.
In order to populate the arguments the writable version returned 
from
$(D configWriteable) is passed to $(D parseArgsWithConfigFile).
This, and the option definitions is usually placed in a separate 
file and
the visibility of $(D MyAppArguments arguments) is set to $(D 
private).
*/
MyAppArguments arguments;

ref MyAppArguments configWriteable() {
	return arguments;
}

ref const(MyAppArguments) config() {
	return arguments;
}

/** This $(D string[]) serves as an example of what would be 
passed to the
$(D main) function from the command line.
*/
string[] args = ["./executablename",
	"--nested.someBool",
	"--nested.someFloatValue", "12.34",
	"--testValues", "10",
	"-b", "11,12",
	"--nested.enumArg", "many",
	"--inputFilename", "nice.d"];

/** Populates the argument struct returned from configWriteable 
with the
values passed in $(D args).

$(D true) is returned if the help page is requested with either 
"-h" or
"--help".
$(D parseArgsWithConfigFile), and $(D parseArgs) will remove all 
used
strings from $(D args).
After the unused strings and the application name are left in $(D 
args).

Replacing $(D parseArgsWithConfigFile) with $(D parseArgs) will 
disable
the config file parsing option.
*/
bool helpWanted = parseArgsWithConfigFile(configWriteable(), 
args);

if(helpWanted) {
	/** If the help page is wanted by the user the $(D printArgsHelp)
	function can be used to print help page.
	*/
	printArgsHelp(config(), "A text explaining the program");
}

/** Here it is tested if the parsing of $(D args) was successful. 
*/
assert(equal(config().testValues, [10,11,12]));
assert(config().nested.enumArg == NestedEnumArgument.many);
assert(approxEqual(config().nested.someFloatValue, 12.34));
assert(config().nested.someBool);
assert(config().inputFilename == "nice.d");
```
Aug 01
next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 1 August 2017 at 15:44:34 UTC, Robert burner
 import args;
I suggest adding a module declaration with some kind of top level namespace as soon as possible. This is liable to conflict with some other module with the same name from a user's project.
Aug 01
prev sibling parent reply "H. S. Teoh via Digitalmars-d-announce" writes:
On Tue, Aug 01, 2017 at 03:44:34PM +0000, Robert burner Schadek via
Digitalmars-d-announce wrote:
 args.d is a command line argument and config file parser.
 
 The basic idea of args.d is that that command line options and config
 file options are basically the same or should be.
 The configuration options are build from UDA annotated structs that
 can be nested.
[...] Cool! I had the same idea recently and implemented something similar for one of my projects. Great minds think alike. :-D I think UDA-driven configuration parsing is ultimately the right direction to go. And by that I mean more than just command-line parsing, but the parsing of configuration parameters in general, including command-line options, configuration files, etc.. Usually a lot of boilerplate is involved in constructing a parser, enumerating options, then mapping that to configuration variables, adding helpful descriptions, etc.. All of which are a maintenance burden on the programmer, because any of these components can become out-of-sync with each other: the parsing of parameters, the mapping of these parameters to internal variables, and the help text. With UDAs, it's possible to unify all three in one declaration, thereby ensuring things will never go out-of-sync, and also greatly reduces the amount of boilerplate the programmer has to type. I didn't look too closely at args.d yet, but in my implementation, I have UDAs for specifying alternate names for common configuration parameters (e.g., "outfile=..." instead of "outputFilename=..."). This allows more user-friendly option names, and also decouples it from internal variable naming schemes. There's also UDAs for optionally flattening a nested struct, so that internally I can have separate structs for configuring each module, but the main program combines all of them into a single flattened struct, so that to the user all the options are top-level (can specify "outfile=..." instead of "backend.outputFilename=..."). The user shouldn't need to know how the program is organized internally, after all, yet I can still properly encapsulate configuration parameters relevant to only that module, thereby avoiding spaghetti dependencies of modules on a single global configuration struct. T -- The easy way is the wrong way, and the hard way is the stupid way. Pick one.
Aug 01
parent reply Robert burner Schadek <rburners gmail.com> writes:
On Tuesday, 1 August 2017 at 17:46:57 UTC, H. S. Teoh wrote:
 I think UDA-driven configuration parsing is ultimately the 
 right direction to go.  And by that I mean more than just 
 command-line parsing, but the parsing of configuration 
 parameters in general, including command-line options, 
 configuration files, etc..  Usually a lot of boilerplate is 
 involved in constructing a parser, enumerating options, then 
 mapping that to configuration variables, adding helpful 
 descriptions, etc.. All of which are a maintenance burden on 
 the programmer, because any of these components can become 
 out-of-sync with each other: the parsing of parameters, the 
 mapping of these parameters to internal variables, and the help 
 text.
args.d uses the same parser for command line options and for the config file. The parser gets adapted between the two with some compile time parameters.
 With UDAs, it's possible to unify all three in one declaration, 
 thereby ensuring things will never go out-of-sync, and also 
 greatly reduces the amount of boilerplate the programmer has to 
 type.

 I didn't look too closely at args.d yet, but in my 
 implementation, I have UDAs for specifying alternate names for 
 common configuration parameters (e.g., "outfile=..." instead of 
 "outputFilename=..."). This allows more user-friendly option 
 names, and also decouples it from internal variable naming 
 schemes.
I found that good variable names make good command line option names. And you remove one indirection, which I always like. Plus, you can always use short options, which are checked for uniqueness at compile time by args.d.
 There's also UDAs for optionally flattening a nested struct, so 
 that internally I can have separate structs for configuring 
 each module, but the main program combines all of them into a 
 single flattened struct, so that to the user all the options 
 are top-level (can specify "outfile=..." instead of 
 "backend.outputFilename=..."). The user shouldn't need to know 
 how the program is organized internally, after all, yet I can 
 still properly encapsulate configuration parameters relevant to 
 only that module, thereby avoiding spaghetti dependencies of 
 modules on a single global configuration struct.
I thought about that as well, but didn't do it because if found: --mysql.ipAddress Type: string default: localhost Help: --redis.ipAddress Type: string default: localhost Help: looks and works just awesome. At least better than --mysqlipAddress and --redisipAddress.
Aug 02
parent "H. S. Teoh via Digitalmars-d-announce" writes:
On Wed, Aug 02, 2017 at 07:29:42AM +0000, Robert burner Schadek via
Digitalmars-d-announce wrote:
 On Tuesday, 1 August 2017 at 17:46:57 UTC, H. S. Teoh wrote:
[...]
 There's also UDAs for optionally flattening a nested struct, so that
 internally I can have separate structs for configuring each module,
 but the main program combines all of them into a single flattened
 struct, so that to the user all the options are top-level (can
 specify "outfile=..." instead of "backend.outputFilename=..."). The
 user shouldn't need to know how the program is organized internally,
 after all, yet I can still properly encapsulate configuration
 parameters relevant to only that module, thereby avoiding spaghetti
 dependencies of modules on a single global configuration struct.
I thought about that as well, but didn't do it because if found: --mysql.ipAddress Type: string default: localhost Help: --redis.ipAddress Type: string default: localhost Help: looks and works just awesome. At least better than --mysqlipAddress and --redisipAddress.
Well, yeah, flattening is optional in my code, so I'd only use that when the constituent structs have unique field names. Otherwise I'd just leave them nested, which also works. T -- Shin: (n.) A device for finding furniture in the dark.
Aug 02