Skip to content

Commit

Permalink
Refactor (#96)
Browse files Browse the repository at this point in the history
* Move onError tp api/cli.d

* Improve ANSI styling handling and making AnsiStylingArgument boolean-like

* Make Parser private

* Remove hooks

* Remove subcommands

* Remove argumentparser

* Update readme

* Add unit tests
  • Loading branch information
andrey-zherikov authored Jul 25, 2023
1 parent d789306 commit 813e05e
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 367 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ void printText(bool enableStyle)
auto myStyle = enableStyle ? bold.italic.cyan.onRed : noStyle;
// "Hello" is always printed in green;
// "world!" is printed in bold, italic, cyan and on red when `enableStyle` is true, "as is" otherwise
// "world!" is printed in bold, italic, cyan and on red when `enableStyle` is true, or "as is" otherwise
writeln(green("Hello "), myStyle("world!"));
}
```
Expand Down Expand Up @@ -1110,7 +1110,7 @@ struct Arguments
mixin CLI!Arguments.main!((args)
{
// 'autodetect' is converted to either 'on' or 'off'
if(args.color == Config.StylingMode.on)
if(args.color)
writeln("Colors are enabled");
else
writeln("Colors are disabled");
Expand All @@ -1131,7 +1131,7 @@ This parameter has the following members that can be tuned:

### Heuristics for enabling styling

Below is the exact sequence of steps argparse uses to determine whether or not to emit ANSI escape codes
Below is the exact sequence of steps `argparse` uses to determine whether or not to emit ANSI escape codes
(see detectSupport() function [here](https://github.com/andrey-zherikov/argparse/blob/master/source/argparse/ansi.d) for details):

1. If environment variable `NO_COLOR != ""` then styling is **disabled**. See [here](https://no-color.org/) for details.
Expand Down Expand Up @@ -1526,7 +1526,7 @@ assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); })(["-a","!4"]) == 0);

## Parser customization

`argparser` provides decent amount of settings to customize the parser. All customizations can be done by creating
`argparse` provides decent amount of settings to customize the parser. All customizations can be done by creating
`Config` object with required settings (see below).

### Assign character
Expand Down
5 changes: 2 additions & 3 deletions examples/getting_started/advanced/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ mixin CLI!(config(), Advanced).main!((args, unparsed)
writeln("Unparsed args: ", unparsed);

// use actual styling mode to print output
auto style = Advanced.color == Config.StylingMode.on ? red.onWhite : noStyle;
writeln(style("Styling mode: "), Advanced.color);
assert(Advanced.color == Config.StylingMode.on || Advanced.color == Config.StylingMode.off);
auto style = Advanced.color ? red.onWhite : noStyle;
writeln(style("Styling mode: "), Advanced.color ? "on" : "off");

return 0;
});
85 changes: 39 additions & 46 deletions source/argparse/api/ansi.d
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module argparse.api.ansi;
import argparse.config;
import argparse.param;
import argparse.api.argument: NamedArgument, Description, NumberOfValues, AllowedValues, Parse, Action, ActionNoValue;
import argparse.internal.hooks: Hooks;
import argparse.internal.parsehelpers: PassThrough;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -18,79 +17,73 @@ import argparse.internal.parsehelpers: PassThrough;
.Action!(AnsiStylingArgument.action)
.ActionNoValue!(AnsiStylingArgument.action)
)
@(Hooks.onParsingDone!(AnsiStylingArgument.finalize))
private struct AnsiStylingArgument
{
Config.StylingMode stylingMode = Config.StylingMode.autodetect;
package(argparse) static bool isEnabled;

alias stylingMode this;

string toString() const
public bool opCast(T : bool)() const
{
import std.conv: to;
return stylingMode.to!string;
return isEnabled;
}

void set(const Config* config, Config.StylingMode mode)
{
config.setStylingMode(stylingMode = mode);
}
static void action(ref AnsiStylingArgument receiver, RawParam param)
private static void action(ref AnsiStylingArgument receiver, RawParam param)
{
switch(param.value[0])
{
case "always": receiver.set(param.config, Config.StylingMode.on); return;
case "auto": receiver.set(param.config, Config.StylingMode.autodetect); return;
case "never": receiver.set(param.config, Config.StylingMode.off); return;
case "auto": isEnabled = param.config.stylingMode == Config.StylingMode.on; return;
case "always": isEnabled = true; return;
case "never": isEnabled = false; return;
default:
}
}
static void action(ref AnsiStylingArgument receiver, Param!void param)
{
receiver.set(param.config, Config.StylingMode.on);
}
static void finalize(ref AnsiStylingArgument receiver, const Config* config)
private static void action(ref AnsiStylingArgument receiver, Param!void param)
{
receiver.set(config, config.stylingMode);
isEnabled = true;
}
}

auto ansiStylingArgument()
unittest
{
return AnsiStylingArgument.init;
AnsiStylingArgument arg;
AnsiStylingArgument.action(arg, Param!void.init);
assert(arg);

AnsiStylingArgument.action(arg, RawParam(null, "", [""]));
}

unittest
{
import std.conv: to;
AnsiStylingArgument arg;

assert(ansiStylingArgument == AnsiStylingArgument.init);
assert(ansiStylingArgument.toString() == Config.StylingMode.autodetect.to!string);
AnsiStylingArgument.action(arg, RawParam(null, "", ["always"]));
assert(arg);

Config config;
config.setStylingModeHandlers ~= (Config.StylingMode mode) { config.stylingMode = mode; };
AnsiStylingArgument.action(arg, RawParam(null, "", ["never"]));
assert(!arg);
}

unittest
{
Config config;
AnsiStylingArgument arg;
AnsiStylingArgument.action(arg, Param!void(&config));

assert(config.stylingMode == Config.StylingMode.on);
assert(arg.toString() == Config.StylingMode.on.to!string);
config.stylingMode = Config.StylingMode.on;
AnsiStylingArgument.action(arg, RawParam(&config, "", ["auto"]));
assert(arg);

config.stylingMode = Config.StylingMode.off;
AnsiStylingArgument.action(arg, RawParam(&config, "", ["auto"]));
assert(!arg);
}

unittest
{
auto test(string value)
{
Config config;
config.setStylingModeHandlers ~= (Config.StylingMode mode) { config.stylingMode = mode; };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

AnsiStylingArgument arg;
AnsiStylingArgument.action(arg, RawParam(&config, "", [value]));
return config.stylingMode;
}
auto ansiStylingArgument()
{
return AnsiStylingArgument.init;
}

assert(test("always") == Config.StylingMode.on);
assert(test("auto") == Config.StylingMode.autodetect);
assert(test("never") == Config.StylingMode.off);
assert(test("") == Config.StylingMode.autodetect);
unittest
{
assert(ansiStylingArgument == AnsiStylingArgument.init);
}
52 changes: 49 additions & 3 deletions source/argparse/api/cli.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,41 @@ import argparse.internal.parser: callParser;
import argparse.internal.completer: completeArgs, Complete;


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Private helper for error output

private void defaultErrorPrinter(string message)
{
import std.stdio: stderr, writeln;

stderr.writeln("Error: ", message);
}

private void onError(Config config, alias printer = defaultErrorPrinter)(string message) nothrow
{
static if(config.errorHandlerFunc)
config.errorHandlerFunc(message);
else
try
{
printer(message);
}
catch(Exception e)
{
throw new Error(e.msg);
}
}

unittest
{
import std.exception;

alias printer = (s) { throw new Exception("My Message."); };

assert(collectExceptionMsg!Error(onError!(Config.init, printer)("text")) == "My Message.");
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Public API for CLI wrapper
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -36,7 +71,12 @@ template CLI(Config config, COMMAND)
{
static Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)
{
return callParser!(config, false)(receiver, args, unrecognizedArgs);
auto res = callParser!(config, false)(receiver, args, unrecognizedArgs);

if(!res && res.errorMsg.length > 0)
onError!config(res.errorMsg);

return res;
}

static Result parseKnownArgs(ref COMMAND receiver, ref string[] args)
Expand All @@ -56,7 +96,7 @@ template CLI(Config config, COMMAND)
if(res && args.length > 0)
{
res = Result.Error("Unrecognized arguments: ", args);
config.onError(res.errorMsg);
onError!config(res.errorMsg);
}

return res;
Expand Down Expand Up @@ -115,12 +155,18 @@ template CLI(Config config, COMMAND)

auto res = callParser!(config, false)(receiver, args, unrecognizedArgs);
if(!res)
{
// This never happens
if(res.errorMsg.length > 0)
onError!config(res.errorMsg);

return 1;
}

if(res && unrecognizedArgs.length > 0)
{
import std.conv: to;
config.onError("Unrecognized arguments: "~unrecognizedArgs.to!string);
onError!config("Unrecognized arguments: "~unrecognizedArgs.to!string);
return 1;
}

Expand Down
45 changes: 12 additions & 33 deletions source/argparse/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@ struct Config
enum StylingMode { autodetect, on, off }
StylingMode stylingMode = StylingMode.autodetect;

package void delegate(StylingMode)[] setStylingModeHandlers;
package void setStylingMode(StylingMode mode) const
{
foreach(dg; setStylingModeHandlers)
dg(mode);
}

/**
Help style.
*/
Expand All @@ -94,38 +87,24 @@ struct Config
{
return errorHandlerFunc = func;
}
}

unittest
{
auto f = function(string s) nothrow {};

package void onError(string message) const nothrow
{
import std.stdio: stderr, writeln;

try
{
if(errorHandlerFunc)
errorHandlerFunc(message);
else
stderr.writeln("Error: ", message);
}
catch(Exception e)
{
throw new Error(e.msg);
}
}
Config c;
assert(!c.errorHandlerFunc);
assert((c.errorHandler = f));
assert(c.errorHandlerFunc);
}

unittest
{
enum text = "--just testing error func--";

bool called = false;
auto f = delegate(string s) nothrow {};

Config c;
c.errorHandler = (string s)
{
assert(s == text);
called = true;
};
c.onError(text);
assert(called);
assert(!c.errorHandlerFunc);
assert((c.errorHandler = f) == f);
assert(c.errorHandlerFunc == f);
}
Loading

0 comments on commit 813e05e

Please sign in to comment.