Skip to content

Commit

Permalink
Improve documentation for std.argparse.
Browse files Browse the repository at this point in the history
  • Loading branch information
FeepingCreature committed Sep 24, 2023
1 parent ee711aa commit 16dd6a8
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 116 deletions.
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fi

FLAGS="$JFLAG -I$($LLVM_CONFIG --includedir) -L-L$($LLVM_CONFIG --libdir)"

TAG=v0.3.0
TAG=v0.3.1
NEAT=.cache/bootstrap/"$TAG"/neat-"$TAG"-gcc/neat

if [ ! -f "$NEAT" ]
Expand Down
296 changes: 181 additions & 115 deletions src/std/argparse.nt
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,9 @@ module std.argparse;
macro import std.macro.assert;
macro import std.macro.listcomprehension;

alias ArgKind = (
:noArg |
:intArg |
:stringArg |
:multiArgs
);

struct ArgInfo
{
string shortname;

string longname;

ArgKind kind;

string longOrShort() => longname if !longname.empty else shortname;

(string | :hidden) description;
}

/**
* Parse command-line arguments and return matched arguments or errors.
*/
abstract class ArgParser
{
// TODO public_mut?
Expand All @@ -34,13 +17,21 @@ abstract class ArgParser

int delegate(string, string[], ArgResult)[] specialRules;

/**
* Add a command-line argument.
* Typical usage: `add("h", "help", :noArg, "Print this help page.");
*/
void add(string shortname, string longname, ArgKind kind, (string | :hidden) description) {
auto argInfo = ArgInfo(shortname, longname, kind, description);
longArgs[longname] = argInfo;
shortArgs[shortname] = argInfo;
args ~= argInfo;
}

/**
* Add a command-line argument without a short form.
* Typical usage: `add("help", :noArg, "Print this help page.");
*/
void add(string name, ArgKind kind, (string | :hidden) description) {
if (name.length == 1) {
auto argInfo = ArgInfo(shortname=name, "", kind, description);
Expand All @@ -53,17 +44,173 @@ abstract class ArgParser
}
}

/**
* Add a special rule. The callback will be invoked with the current
* argument, the remaining arguments, and the result object to fill out.
* It is expected to return the number of arguments consumed, including
* the current argument.
*/
void add(int delegate(string, string[], ArgResult) dg) {
specialRules ~= dg;
}

/**
* Parse a list of command-line arguments. If a string is returned,
* the command-line arguments did not match the defined arguments.
*/
abstract (ArgResult | fail string) parse(mut string[] args);
}

unittest
{
auto parser = new UnixArgParser;
parser.add("h", "help", :noArg, "Print this help page");

with (parser.parse(null)
.case(string: assert(false)))
{
assert(remaining.empty);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("h", "help", :noArg, "Print this help page");

with (parser.parse(["foo"])
.case(string: assert(false)))
{
assert(remaining == ["foo"]);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("f", "foo", :intArg, "Foo arg");

parser.parse(["-f"])
.case(ArgResult: assert(false))
.assertEqual("Flag '-f' missing parameter");

parser.parse(["--foo"])
.case(ArgResult: assert(false))
.assertEqual("Flag '--foo' missing parameter");

parser.parse(["-f", "x"])
.case(ArgResult: assert(false))
.assertEqual("Flag '-f' expected integer parameter");

with (parser.parse(["-f", "5"])
.case(string: assert(false)))
{
assert(has("foo"));
assert(remaining.empty);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("f", "foo", :noArg, :hidden);
parser.add("b", "bar", :noArg, :hidden);

with (parser.parse(["-fb"]).case(string s: assert(false))) {
assert(get("foo") == "");
assert(get("bar") == "");
assert(remaining.empty);
}
}

unittest
{
auto parser = new GccArgParser;
parser.add("f", "foo", :noArg, :hidden);
parser.add("b", "bar", :noArg, :hidden);

parser.parse(["-fb"])
.case(ArgResult: assert(false))
.assertEqual("Flag '-f' expected no parameter");
}

unittest
{
auto parser = new UnixArgParser;
with (parser.parse(["foo", "--", "bar"])
.case(string s: assert(false)))
{
assert(remaining == ["foo"]);
assert(extra == ["bar"]);
}
}

unittest
{
auto parser = new GccArgParser;
parser.add("L", :multiArgs, :hidden);
with (parser.parse(["-L-lpthreads"])
.case(string s: assert(false)))
{
assert(getMultiple("L") == ["-lpthreads"]);
}
}

unittest
{
auto parser = new UnixArgParser;
int blaAction(string arg, string[] args, ArgResult result) {
if (arg == "bla") {
result.foundArgs["bla"] = "";
// arg consumed
return 1;
}
// no match
return 0;
}
parser.add(new &blaAction);
with (parser.parse(["bla"])
.case(string: assert(false)))
{
assert(get("bla") == "");
assert(remaining.empty);
}
}

alias assertEqual = (a, b) => assert(a == b);

/**
* The kind of a command-line argument. This determines how many further
* arguments it consumes.
*/
alias ArgKind = (
:noArg |
:intArg |
:stringArg |
:multiArgs
);

/**
* Information about a defined command-line argument.
*/
struct ArgInfo
{
string shortname;

string longname;

ArgKind kind;

string longOrShort() => longname if !longname.empty else shortname;

(string | :hidden) description;
}

/**
* UNIX style:
* --foo bar
* -fx is -f, -x
*
* * `--foo bar`
* * `-fx` is `-f, -x`
*/
class UnixArgParser : ArgParser
{
Expand Down Expand Up @@ -150,8 +297,9 @@ class UnixArgParser : ArgParser

/**
* GCC style:
* --foo bar, --foo=bar, -foo=bar
* -fx is --foo=x
*
* * `--foo bar`, `--foo=bar`, `-foo=bar`
* * `-fx` is `--foo=x`
*/
class GccArgParser : ArgParser
{
Expand Down Expand Up @@ -283,6 +431,9 @@ class GccArgParser : ArgParser
}
}

/**
* The result of successfully parsing command-line arguments.
*/
class ArgResult
{
private ArgParser parser;
Expand All @@ -292,16 +443,18 @@ class ArgResult

public string[][string] multiArgs;

// non-flag arguments in the commandline
/// Non-flag arguments in the commandline
public string[] remaining;

// arguments after the --
/// Arguments after the --
public string[] extra;

this(this.parser) {}

/// Returns whether an argument was matched.
bool has(string arg) => this.foundArgs.has(arg) || this.multiArgs.has(arg);

/// Returns the parameter of a matched argument that took a parameter.
string get(string arg) {
if (!this.foundArgs.has(arg)) {
print("wrong result key '$arg'");
Expand All @@ -310,6 +463,7 @@ class ArgResult
return this.foundArgs[arg];
}

/// Returns all parameters of an argument that could match multiple times.
string[] getMultiple(string arg) {
if (!this.multiArgs.has(arg)) {
print("wrong result key '$arg'");
Expand All @@ -318,94 +472,6 @@ class ArgResult
return this.multiArgs[arg];
}

/// Helper to manually set an argument to a value.
void set(string arg, string value) { this.foundArgs[arg] = value; }
}

unittest
{
auto parser = new UnixArgParser;
parser.add("h", "help", :noArg, "Print this help page");
with (parser.parse(null).case(string: assert(false))) {
assert(remaining.empty);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("h", "help", :noArg, "Print this help page");
with (parser.parse(["foo"]).case(string: assert(false))) {
assert(remaining == ["foo"]);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("f", "foo", :intArg, "Foo arg");
assert(parser.parse(["-f"]).case(ArgResult: assert(false)) == "Flag '-f' missing parameter");
assert(parser.parse(["--foo"]).case(ArgResult: assert(false)) == "Flag '--foo' missing parameter");
assert(parser.parse(["-f", "x"]).case(ArgResult: assert(false)) == "Flag '-f' expected integer parameter");
with (parser.parse(["-f", "5"]).case(string: assert(false))) {
assert(has("foo"));
assert(remaining.empty);
}
}

unittest
{
auto parser = new UnixArgParser;
parser.add("f", "foo", :noArg, :hidden);
parser.add("b", "bar", :noArg, :hidden);
with (parser.parse(["-fb"]).case(string s: assert(false))) {
assert(get("foo") == "");
assert(get("bar") == "");
assert(remaining.empty);
}
}

unittest
{
auto parser = new GccArgParser;
parser.add("f", "foo", :noArg, :hidden);
parser.add("b", "bar", :noArg, :hidden);
assert(parser.parse(["-fb"]).case(ArgResult: assert(false)) == "Flag '-f' expected no parameter");
}

unittest
{
auto parser = new UnixArgParser;
with (parser.parse(["foo", "--", "bar"]).case(string s: assert(false))) {
assert(remaining == ["foo"]);
assert(extra == ["bar"]);
}
}

unittest
{
auto parser = new GccArgParser;
parser.add("L", :multiArgs, :hidden);
with (parser.parse(["-L-lpthreads"]).case(string s: assert(false))) {
assert(getMultiple("L") == ["-lpthreads"]);
}
}

unittest
{
auto parser = new UnixArgParser;
int blaAction(string arg, string[] args, ArgResult result) {
if (arg == "bla") {
result.foundArgs["bla"] = "";
// arg consumed
return 1;
}
// no match
return 0;
}
parser.add(new &blaAction);
with (parser.parse(["bla"]).case(string: assert(false))) {
assert(get("bla") == "");
assert(remaining.empty);
}
}

0 comments on commit 16dd6a8

Please sign in to comment.