I'd like to find a way to define programming constructs in one file and reference them in a getopt call defined in another file. getopt uses variadic template argument, so the argument list must be known at compile time. The std.getopt.getopt signature:

     GetoptResult getopt(T...)(ref string[] args, T opts)

So, what I'm trying to do is construct the 'opts' parameter from definitions stored in two or more files. The reason for doing this is to create a customization mechanism where-by there are a number of default capabilities built-in to the main code base, but someone can customize their copy of the code, putting definitions in a separate file, and have it added in at compile time, including modifying command line arguments.

I found a way to do this with a mixin template, shown below. However, it doesn't strike me as a particularly modular design. My question - Is there a better approach?

The solution I identified is below. The '--say-hello' option is built-in (defined in app.d), the '--say-hello-world' command is defined in custom_commands.d. Running:

    $ ./app --say-hello --say-hello-world

will print:

     Hello
     Hello World

Which is the goal. But, is there a better way? Help appreciated.

--Jon

=== command_base.d ===
/* API for defining "commands". */
interface Command
{
    string exec();
}

class BaseCommand : Command
{
    private string _result;
    this (string result) { _result = result; }
    final string exec() { return _result; }
}

=== custom_commands.d ===
/* Defines custom commands and a mixin for generating the getopt argument. * Note that 'commandArgHandler' is defined in app.d, not visible in this file.
 */
import command_base;

class HelloWorldCommand : BaseCommand
{
    this() { super("Hello World"); }
}

mixin template CustomCommandDeclarations()
{
    import std.meta;

auto pHelloWorldHandler = &commandArgHandler!HelloWorldCommand;

    alias CustomCommandOptions = AliasSeq!(
"say-hello-world", "Print 'hello world'.", pHelloWorldHandler,
        );
}

=== app.d ===
/* This puts it all together. It creates built-in commands and uses the mixin from * custom_commands.d to declare commands and construct the getopt argument.
 */
import std.stdio;
import command_base;

class HelloCommand : BaseCommand
{
    this() { super("Hello"); }
}

struct CmdOptions
{
    import std.meta;
    Command[] commands;

    void commandArgHandler(DerivedCommand : BaseCommand)()
    {
        commands ~= new DerivedCommand();
    }

    bool processArgs (ref string[] cmdArgs)
    {
        import std.getopt;
        import custom_commands;

        auto pHelloHandler = &commandArgHandler!HelloCommand;

        alias BuiltinCommandOptions = AliasSeq!(
            "say-hello",  "Print 'hello'.", pHelloHandler,
            );

        mixin CustomCommandDeclarations;
auto CommandOptions = AliasSeq!(BuiltinCommandOptions, CustomCommandOptions);
        auto r = getopt(cmdArgs, CommandOptions);
if (r.helpWanted) defaultGetoptPrinter("Options:", r.options); return !r.helpWanted; // Return true if execution should continue
    }
}

void main(string[] cmdArgs)
{
    CmdOptions cmdopt;

    if (cmdopt.processArgs(cmdArgs))
        foreach (cmd; cmdopt.commands)
            writeln(cmd.exec());
}

Reply via email to