digitalmars.D.learn - Constructing a variadic template parameter with source in two files
- Jon Degenhardt (102/102) Dec 21 2016 I'd like to find a way to define programming constructs in one
- =?UTF-8?Q?Ali_=c3=87ehreli?= (23/29) Dec 21 2016 I'm not sure this is any better than your mixin solution but getopt can
- Jon Degenhardt (6/22) Dec 22 2016 Yes, that might work, thanks. I'll need to work on the code
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
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
On Thursday, 22 December 2016 at 07:33:42 UTC, Ali Çehreli wrote:On 12/21/2016 07:59 PM, Jon Degenhardt wrote: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. --Jonconstruct the 'opts' parameter from definitions stored in two or more files. The reason for doingthis is tocreate a customization mechanism where-by there are a numberof defaultcapabilities built-in to the main code base, but someone cancustomizetheir copy of the code, putting definitions in a separatefile, and haveit added in at compile time, including modifying command linearguments. 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. [...]
Dec 22 2016