www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Constructing a variadic template parameter with source in two files

reply Jon Degenhardt <noreply noreply.com> writes:
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());
}
Dec 21 2016
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 21 2016
parent Jon Degenhardt <noreply noreply.com> writes:
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
Dec 22 2016