1 module clid.help; 2 3 import std.traits : hasUDA, getUDAs; 4 import std.meta : Alias; 5 import std..string : leftJustify; 6 7 import clid.attributes; 8 import clid.util; 9 10 /** 11 * Print help information for a given configuration struct. 12 * Params: c = The configuration struct to print help information for. 13 */ 14 void printHelp(C)(C c) 15 { 16 import std.stdio : stderr; 17 18 validateStruct!C(); 19 stderr.write(getHelp(c)); 20 } 21 22 private string getHelp(C)(C c) // @suppress(dscanner.suspicious.unused_parameter) 23 { 24 import std.ascii : newline; 25 26 string str = usage() ~ newline ~ newline; 27 28 static foreach (member; __traits(allMembers, C)) 29 { 30 str ~= Describe!(__traits(getMember, c, member)) ~ newline; 31 } 32 str ~= "\t-h, --help".leftJustify(30) ~ "Show this help\n"; 33 return str; 34 } 35 36 private template GetFlagName(alias E) 37 { 38 static assert(getUDAs!(E, Parameter).length == 1, "Must have at most 1 @Parameter attribute."); 39 static if (getUDAs!(E, Parameter)[0].shortName == ' ') 40 { 41 alias GetFlagName = Alias!(" --" ~ getUDAs!(E, Parameter)[0].longName); 42 } 43 else 44 { 45 alias GetFlagName = Alias!("-" ~ getUDAs!(E, 46 Parameter)[0].shortName ~ ", --" ~ getUDAs!(E, Parameter)[0].longName); 47 } 48 } 49 50 private template GetFlagValue(alias E) 51 { 52 import std.uni : toUpper; 53 54 static if (hasUDA!(E, Description) && getUDAs!(E, Description)[0].optionType.length > 0) 55 { 56 alias GetFlagValue = Alias!(" " ~ getUDAs!(E, Description)[0].optionType); 57 } 58 else static if (is(typeof(E) : bool)) 59 { 60 alias GetFlagValue = Alias!(""); 61 } 62 else 63 { 64 alias GetFlagValue = Alias!(" " ~ typeof(E).stringof.toUpper); 65 } 66 } 67 68 private template GetFlag(alias E) 69 { 70 alias GetFlag = Alias!(GetFlagName!(E) ~ GetFlagValue!(E)); 71 } 72 73 private template GetDescriptionOnly(alias E) 74 { 75 static if (hasUDA!(E, Description)) 76 alias GetDescriptionOnly = Alias!(getUDAs!(E, Description)[0].description); 77 else 78 alias GetDescriptionOnly = Alias!(""); 79 } 80 81 private template GetDescription(alias E) 82 { 83 static if (hasUDA!(E, Required)) 84 alias GetDescription = Alias!("[REQUIRED] " ~ GetDescriptionOnly!(E)); 85 else 86 alias GetDescription = GetDescriptionOnly!(E); 87 } 88 89 private template Describe(alias E) 90 { 91 static if (!hasUDA!(E, Description) && !hasUDA!(E, Required)) 92 { 93 alias Describe = Alias!('\t' ~ GetFlag!(E)); 94 } 95 else 96 { 97 alias Describe = Alias!(leftJustify('\t' ~ GetFlag!(E), 30) ~ GetDescription!(E)); 98 } 99 } 100 101 private string usage() 102 { 103 import core.runtime : Runtime; 104 105 return "Usage: " ~ Runtime.args()[0] ~ " [OPTION...]"; 106 } 107 108 // ====================== 109 // Unit tests start here. 110 // ====================== 111 112 unittest 113 { 114 struct Config 115 { 116 @Parameter("foo", 'f') 117 @Description("Foobar") 118 int value; 119 120 @Parameter("str", 's') 121 @Description("Just a string") 122 string stringValue; 123 124 @Parameter("time", 't') 125 @Description("Interval time", "secs") 126 int time; 127 128 @Parameter("bar") 129 @Description("A random description") 130 @Required bool other; 131 } 132 133 immutable Config config; 134 static assert(GetFlag!(config.value) == "-f, --foo INT"); 135 static assert(GetDescription!(config.value) == "Foobar"); 136 137 printHelp(config); 138 } 139 140 unittest 141 { 142 struct Config 143 { 144 @Parameter("foo") 145 int value; 146 } 147 148 immutable Config config; 149 static assert(GetFlag!(config.value) == " --foo INT"); 150 //static assert(Describe!(config.value) == "\t --foo INT"); 151 }