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