Thread overview
Constructing a variadic template parameter with source in two files
Dec 22, 2016
Jon Degenhardt
Dec 22, 2016
Ali Çehreli
Dec 22, 2016
Jon Degenhardt
December 22, 2016
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());
}

December 21, 2016
On 12/21/2016 07:59 PM, Jon Degenhardt wrote:

> 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'm not sure this is any better than your mixin solution but getopt can be called multiple times on the same arguments. So, for example common code can parse them for its arguments and special code can parse them for its arguments. Useful bits:

* std.getopt.config.passThrough allows unrecognized arguments

* Although main's args can be passed to any function, program arguments are also available through Runtime.args()

The following program calls getopt twice.

import core.runtime : Runtime;
import std.getopt;

void foo() {
    int special;
    // Making a copy as Runtime.args() seems to return rvalue and getopt takes by-ref
    auto progArgs = Runtime.args();
    getopt(progArgs, std.getopt.config.passThrough, "special", &special);
}

void main(string[] args) {
    int length;
    getopt(args, std.getopt.config.passThrough, "length", &length);
}

Ali

December 22, 2016
On Thursday, 22 December 2016 at 07:33:42 UTC, Ali Çehreli wrote:
> On 12/21/2016 07:59 PM, Jon Degenhardt wrote:
>
> > 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'm not sure this is any better than your mixin solution but getopt can be called multiple times on the same arguments. So, for example common code can parse them for its arguments and special code can parse them for its arguments. [...]
>
Yes, that might work, thanks. I'll need to work on the code structure a bit (there are a couple other nuances to account for), but might be able to make it work. The mixin approach feels a bit brittle.

--Jon