1 module postrock; 2 3 import beard.io; 4 import beard.vector : pushBack, pushFront; 5 6 import std.array : split, join; 7 import std.bigint : BigInt; 8 import std.process : getenv; 9 10 /// Generic command line error. 11 class CmdLineError : Throwable { 12 this(string error) { super(error); } 13 } 14 15 /// Thrown when a user supplies a malformed command-line argument such as ---arg 16 class BadCommandLineArgument : CmdLineError { 17 this(string error) { super(error); } 18 } 19 20 /// When a user supplies a flag that is not known. 21 class UnknownCommandLineArgument : CmdLineError { 22 this(string error) { super(error); } 23 } 24 25 /// This is thrown when an argument requires a value but the user supplied none. 26 class BadCommandLineArgumentValue : CmdLineError { 27 this(string error) { super(error); } 28 } 29 30 int maxLeftColumnWidth = 40; 31 32 /// Command-line argument parser object. 33 class Parser { 34 struct State { 35 this(string[] *_args) { args = _args; } 36 37 bool empty() { return argIdx >= args.length; } 38 ref string front() { return (*args)[argIdx]; } 39 40 char firstChar() { return front()[argOffset]; } 41 char charAt(int idx) { return front()[argOffset + idx]; } 42 43 string substring() { return front()[argOffset..$]; } 44 45 void advanceOffset(int incr) { 46 argOffset += incr; 47 if (argOffset >= front().length) 48 popArgument(); 49 } 50 51 // advance pointer, keeping argument in args 52 void saveArgument() { 53 if (nextSaveIdx < argIdx) 54 (*args)[nextSaveIdx] = (*args)[argIdx]; 55 nextSaveIdx += 1; 56 57 popArgument(); 58 } 59 60 void popArgument() { 61 argIdx += 1; 62 argOffset = 0; 63 } 64 65 string[] *args; 66 // which argument currently looking at 67 int argIdx = 1; 68 // offset used to keep track of where parser is within current option 69 int argOffset = 0; 70 // idx where last saved argument was 71 int nextSaveIdx = 1; 72 } 73 74 interface AbstractValue { 75 void parse(ref State state); 76 } 77 78 class Value(T) : AbstractValue { 79 this(T *ptr) { valPtr_ = ptr; } 80 81 private static void parseHelper(U)(ref U val, ref State state) { 82 static if (is(U : bool)) { 83 val = true; 84 } 85 else static if (is(U : string)) { 86 if (state.empty) 87 throw new BadCommandLineArgumentValue(state.front); 88 val = state.substring; 89 state.popArgument; 90 } 91 else static if (is(U V : V[])) { 92 val.length += 1; 93 parseHelper(val[val.length - 1], state); 94 } 95 else { 96 } 97 } 98 99 void parse(ref State state) { 100 parseHelper(*valPtr_, state); 101 } 102 103 T* valPtr_; 104 } 105 106 class ShowHelp : AbstractValue { 107 this(Parser parser) { parser_ = parser; } 108 private Parser parser_; 109 110 void parse(ref State state) { parser_.showHelp; } 111 } 112 113 private: 114 struct Help { 115 this(string _help) { help = _help; } 116 117 ulong leftColWidth() { 118 auto ret = (args.length - 1) * 2; 119 foreach (arg ; args) { 120 ret += 2; 121 if (arg.length > 1) 122 ret += arg.length; 123 } 124 return ret; 125 } 126 127 string help; 128 string[] args; 129 } 130 131 void addDefaultHelpOption() { 132 if ("h" in optionMap_ && "help" in optionMap_) 133 return; 134 135 auto helpShower = new ShowHelp(this); 136 137 auto help = Help("show help"); 138 if (! ("h" in optionMap_)) { 139 pushBack(help.args, "h"); 140 optionMap_["h"] = helpShower; 141 } 142 143 if (! ("help" in optionMap_)) { 144 pushBack(help.args, "help"); 145 optionMap_["help"] = helpShower; 146 } 147 148 pushFront(helps_, help); 149 } 150 151 public: 152 ref Parser opCall(T)(string args, T *storage, string helpString) { 153 auto vals = split(args, ","); 154 auto optValue = new Value!T(storage); 155 auto help = Help(helpString); 156 157 foreach (val ; vals) { 158 pushBack(help.args, val); 159 optionMap_[val] = optValue; 160 } 161 162 pushBack(helps_, help); 163 164 return this; 165 } 166 167 ref Parser banner(string banner) { 168 banner_ = banner; 169 return this; 170 } 171 172 /// @brief Parse the command line. 173 /// @detailed This will also add -h/--help options to show the help if 174 /// such options have not already been added. 175 void parse(string[] *args) { 176 auto state = State(args); 177 178 addDefaultHelpOption; 179 180 while (! state.empty) { 181 if ('-' != state.firstChar) { 182 state.saveArgument; 183 continue; 184 } 185 186 auto front = state.front; 187 if (1 == front.length) 188 throw new BadCommandLineArgument(front); 189 190 if ('-' == state.charAt(1)) { 191 if (2 == front.length) { 192 state.popArgument; 193 while (! state.empty) 194 state.saveArgument; 195 break; 196 } 197 198 string search = front[2..$]; 199 state.popArgument; // advance past long option 200 auto value = optionMap_.get(search, null); 201 if (! value) 202 throw new UnknownCommandLineArgument(front); 203 204 value.parse(state); 205 } 206 else { 207 // parse short option.. maybe more than one 208 state.advanceOffset(1); 209 do { 210 string search = "" ~ state.firstChar; 211 212 auto value = optionMap_.get(search, null); 213 if (! value) 214 throw new UnknownCommandLineArgument(front); 215 216 state.advanceOffset(1); // advance past option 217 value.parse(state); 218 } while (state.argOffset); 219 } 220 } 221 222 args.length = state.nextSaveIdx; 223 } 224 225 // show help (-h/--help) 226 void showHelp() { 227 shownHelp_ = true; 228 if (banner_.length) 229 println(banner_); 230 231 // get maximum column width 232 ulong leftColWidth = 0; 233 foreach (help ; helps_) { 234 auto width = help.leftColWidth; 235 if (width > leftColWidth) 236 leftColWidth = width; 237 } 238 239 leftColWidth += 4; // 2 spaces either side 240 if (leftColWidth > maxLeftColumnWidth) 241 leftColWidth = maxLeftColumnWidth; 242 243 foreach (help ; helps_) { 244 string leftCol = ""; 245 foreach (arg ; help.args) { 246 if (leftCol.length) leftCol ~= ", "; 247 if (arg.length > 1) 248 leftCol ~= "--" ~ arg; 249 else 250 leftCol ~= "-" ~ arg; 251 } 252 253 print(" " ~ leftCol); 254 auto nSpaces = leftColWidth - leftCol.length - 4; 255 while (nSpaces) { 256 print(' '); 257 nSpaces -= 1; 258 } 259 print(" "); 260 println(help.help); 261 } 262 } 263 264 /// Has the help been displayed yet? 265 bool shownHelp() { return shownHelp_; } 266 267 private: 268 bool shownHelp_ = false; 269 AbstractValue[string] optionMap_; 270 Help[] helps_; 271 string banner_; 272 } 273 // vim:ts=4 sw=4