1 /++ Command-Line-Interface (CLI) utilities. 2 3 Expose CLI based on a root type T, where T is typically a struct, class or 4 module. By default it creates an instance of an aggregate and expose members 5 as sub-command. This handles D types, files, etc. 6 7 Reflects on `nxt.path`, `string`, and other built-in types using 8 `arg.to!Arg`. 9 10 Constructor of `T` is also reflected as flags before sub-command. 11 12 TODO: Use in MLParser: - `--requestedFormats=C,D` or `--fmts` because arg is 13 a Fmt[] - `scan dirPath` 14 15 Auto-gen of help also works if any arg in args is --help or -h. 16 +/ 17 module nxt.cli; 18 19 /++ Match method to use when matching CLI sub-commands with member functions. +/ 20 enum Match { 21 exact, 22 prefix, 23 acronym, 24 } 25 26 struct Flags { 27 @disable this(this); 28 Match match; 29 } 30 31 /++ Evaluate `cmd` as a CLI-like sub-command calling a member of `agg` of type `T`. 32 Typically called with the `args` passed to a `main` function. 33 34 Returns: `true` iff the command `cmd` result in a call to `T`-member named `cmd[1]`. 35 36 TODO: Use parameter `flags` 37 38 TODO: Auto-generate help description when --help is passed. 39 40 TODO: Support both 41 - EXE scan("/tmp/") 42 - EXE scan /tmp/ 43 where the former is inputted in bash as 44 EXE 'scan("/tmp/")' 45 +/ 46 bool evalMemberCommand(T)(ref T agg, in string exe, in const(string)[] cmd, in Flags flags = Flags.init) 47 if (is(T == struct) || is(T == class)) { 48 import nxt.path : FilePath, DirPath; 49 import nxt.algorithm : canFind; 50 import nxt.stdio; 51 if (cmd.length == 0) 52 return false; 53 const showHelp = cmd.canFind("--help") || cmd.canFind("-h"); 54 if (showHelp) { 55 debug writeln("Usage: ", exe ? exe : "", " [SUB-COMMAND]"); 56 debug writeln("Sub Commands:"); 57 } 58 foreach (const mn; __traits(allMembers, T)) { /+ member name +/ 59 // TODO: Use std.traits.isSomeFunction or it's inlined definition. 60 // is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return) /+ isSomeFunction +/ 61 static immutable qmn = T.stringof ~ '.' ~ mn; /+ qualified +/ 62 alias member = __traits(getMember, agg, mn); 63 static if (__traits(getVisibility, member) == "public") { // TODO: perhaps include other visibilies later on 64 static if (!is(member) /+ not a type +/ && !(mn.length >= 2 && mn[0 .. 2] == "__")) /+ non-generated members like `__ctor` +/ { 65 if (showHelp) { 66 debug writeln(" ", mn); 67 } 68 switch (cmd.length) { 69 case 0: // nullary 70 static if (__traits(compiles, { mixin(`agg.`~mn~`();`); })) { /+ nullary function +/ 71 mixin(`agg.`~mn~`();`); // call 72 return true; 73 } 74 break; 75 case 1: // unary 76 static if (__traits(compiles, { mixin(`agg.`~mn~`(FilePath.init);`); })) { 77 mixin(`agg.`~mn~`(FilePath(cmd[0]));`); // call 78 return true; 79 } 80 static if (__traits(compiles, { mixin(`agg.`~mn~`(DirPath.init);`); })) { 81 mixin(`agg.`~mn~`(DirPath(cmd[0]));`); // call 82 return true; 83 } 84 break; 85 default: 86 break; 87 } 88 } 89 } 90 } 91 return false; 92 } 93 94 /// 95 @safe pure unittest { 96 import nxt.path : DirPath; 97 98 struct S { 99 version (none) @disable this(this); 100 @safe pure nothrow @nogc: 101 void f1() scope { 102 f1Count += 1; 103 } 104 void f2(int inc = 1) scope { // TODO: support f2:32 105 f2Count += inc; 106 } 107 void scan(DirPath path) { 108 _path = path; 109 _scanDone = true; 110 } 111 private uint f1Count; 112 uint f2Count; 113 DirPath _path; 114 bool _scanDone; 115 } 116 S s; 117 118 assert(!s.evalMemberCommand(null, [])); 119 assert(s.evalMemberCommand(null, [""])); 120 assert(s.evalMemberCommand(null, ["_"])); 121 122 assert(s.f1Count == 0); 123 s.evalMemberCommand(null, ["f1"]); 124 // TODO: assert(s.f1Count == 1); 125 126 assert(s.f2Count == 0); 127 s.evalMemberCommand(null, ["f2"]); // TODO: call as "f2", "42" 128 // TODO: assert(s.f2Count == 1); 129 130 // TODO: assert(s._path == DirPath.init); 131 // TODO: assert(!s._scanDone); 132 // TODO: assert(s.evalMemberCommand(null, ["scan", "/tmp"])); 133 // TODO: assert(s._path == DirPath("/tmp")); 134 assert(s._scanDone); 135 } 136 137 version (unittest) { 138 import nxt.debugio; 139 }