1 /* 2 __ 3 / _| 4 __ _ _ _ _ __ ___ _ __ __ _ | |_ ___ ___ ___ 5 / _` | | | | '__/ _ \| '__/ _` | | _/ _ \/ __/ __| 6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \ 7 \__,_|\__,_|_| \___/|_| \__,_| |_| \___/|___/___/ 8 9 Copyright (C) 2018-2019 Aurora Free Open Source Software. 10 Copyright (C) 2018-2019 Luís Ferreira <luis@aurorafoss.org> 11 12 This file is part of the Aurora Free Open Source Software. This 13 organization promote free and open source software that you can 14 redistribute and/or modify under the terms of the GNU Lesser General 15 Public License Version 3 as published by the Free Software Foundation or 16 (at your option) any later version approved by the Aurora Free Open Source 17 Software Organization. The license is available in the package root path 18 as 'LICENSE' file. Please review the following information to ensure the 19 GNU Lesser General Public License version 3 requirements will be met: 20 https://www.gnu.org/licenses/lgpl.html . 21 22 Alternatively, this file may be used under the terms of the GNU General 23 Public License version 3 or later as published by the Free Software 24 Foundation. Please review the following information to ensure the GNU 25 General Public License requirements will be met: 26 https://www.gnu.org/licenses/gpl-3.0.html. 27 28 NOTE: All products, services or anything associated to trademarks and 29 service marks used or referenced on this file are the property of their 30 respective companies/owners or its subsidiaries. Other names and brands 31 may be claimed as the property of others. 32 33 For more info about intellectual property visit: aurorafoss.org or 34 directly send an email to: contact (at) aurorafoss.org . 35 */ 36 37 module aurorafw.core.opt; 38 39 public import aurorafw.stdx.getopt : defaultRuntimeArgs; 40 import std.algorithm : startsWith, canFind, sort, any; 41 import aurorafw.stdx..string : isAlpha; 42 import std.array : split, array; 43 import std.range.primitives : empty; 44 import std.exception; 45 import std..string : indexOf; 46 import std.format : format; 47 import std.variant; 48 import core.runtime : Runtime; 49 import std.traits : fullyQualifiedName; 50 import std.typecons; 51 52 @safe pure 53 class OptionHandlerException : Exception 54 { 55 mixin basicExceptionCtors; 56 } 57 58 static assert(is(typeof(new OptionHandlerException("message")))); 59 static assert(is(typeof(new OptionHandlerException("message", Exception.init)))); 60 61 @safe pure 62 struct OptionHandler { 63 struct Option { 64 string[] opts; 65 string help; 66 } 67 68 struct Argument { 69 string name; 70 ArgumentSize size; 71 string value = null; 72 } 73 74 enum ArgumentSize { 75 Long, 76 Short 77 } 78 79 @safe pure 80 public this(string[] args) 81 { 82 foreach(string arg ; args[1 .. $]) 83 { 84 if(arg.startsWith("--")) 85 { 86 string splitted = arg.split("--")[1]; 87 if(splitted.length == 0) 88 break; 89 90 ptrdiff_t hasValue = splitted.indexOf('='); 91 if(hasValue != -1) 92 { 93 string newSplitted = splitted[0 .. hasValue]; 94 if(newSplitted.isAlpha) 95 this.args ~= Argument(newSplitted, ArgumentSize.Long, splitted[hasValue + 1 .. $]); 96 } 97 else if(splitted.isAlpha) 98 this.args ~= Argument(splitted, ArgumentSize.Long); 99 } 100 else if (arg.startsWith("-")) 101 { 102 string splitted = arg.split("-")[1]; 103 ptrdiff_t hasValue = splitted.indexOf('='); 104 if(hasValue != -1) 105 { 106 string newSplitted = splitted[0 .. hasValue]; 107 if(newSplitted.isAlpha) 108 this.args ~= Argument(newSplitted, ArgumentSize.Short, splitted[hasValue + 1 .. $]); 109 } 110 else if(splitted.isAlpha && splitted.length != 0) 111 this.args ~= Argument(splitted, ArgumentSize.Short); 112 } 113 } 114 } 115 116 @safe pure 117 public T[] read(T)(string opts, string help, bool required = false) 118 { 119 Option opte = read(opts, help); 120 T[] ret; 121 bool value = false; 122 foreach(arg; args) if(opts.canFind(arg.name)) 123 { 124 value = true; 125 string retstr = arg.value; 126 if(retstr !is null) 127 { 128 import std.conv : to; 129 ret ~= to!T(retstr); 130 } 131 } 132 if(required) 133 { 134 if(!value || (value && ret.empty)) 135 throw new OptionHandlerException("Required option %s with %s type".format(opts, fullyQualifiedName!T)); 136 } 137 138 return ret; 139 } 140 141 @safe pure 142 public Nullable!Argument read(string opts, string help, ref bool value, bool required = false) 143 { 144 Option opte = read(opts, help); 145 value = false; 146 foreach(opt; opte.opts) 147 { 148 foreach(arg; args) if(arg.name == opt) 149 { 150 value = true; 151 return arg.nullable; 152 } 153 } 154 if(value == false && required == true) 155 throw new OptionHandlerException("Required option " ~ opts); 156 157 return Nullable!Argument(); 158 } 159 160 @safe pure 161 private Option read(string opts, string help) 162 { 163 Option opte; 164 opte.opts = opts.split("|").sort!((a, b) => a.length < b.length).array; 165 opte.help = help; 166 167 if(this.opts.any!(o => o.opts == opte.opts)) 168 throw new OptionHandlerException("Trying to read the same option name twice"); 169 170 this.opts ~= opte; 171 172 return opte; 173 } 174 175 public bool helpWanted() 176 { 177 foreach(arg; args) 178 { 179 if(arg.name == "help") 180 return true; 181 } 182 return false; 183 } 184 185 public string printableHelp(string programName) 186 { 187 string ret; 188 189 ret ~= "Usage:\n\t%s -- <options>\n\nOptions:\n".format(programName); 190 191 foreach(opt; opts) 192 { 193 ret ~= opt.opts[0]; 194 if(opt.opts.length == 2) 195 { 196 ret ~= " " ~ opt.opts[1]; 197 } 198 ret ~= "\t" ~ opt.help ~ "\n"; 199 } 200 return ret; 201 } 202 203 @safe pure 204 @property public Argument[] arguments() 205 { 206 return this.args.dup; 207 } 208 209 @property 210 public Option[] options() 211 { 212 return this.opts.dup; 213 } 214 215 private Argument[] args; 216 private Option[] opts; 217 } 218 219 @safe pure 220 @("Option Handler: check arguments") 221 unittest 222 { 223 OptionHandler opts = OptionHandler(["prog", "--foo", "-f", "--bar=foobar"]); 224 225 OptionHandler.Argument[] args = [ 226 OptionHandler.Argument("foo", OptionHandler.ArgumentSize.Long), 227 OptionHandler.Argument("f", OptionHandler.ArgumentSize.Short), 228 OptionHandler.Argument("bar", OptionHandler.ArgumentSize.Long, "foobar") 229 ]; 230 231 assert(opts.arguments == args); 232 } 233 234 235 @safe pure 236 @("Option Handler: check read") 237 unittest 238 { 239 bool isFoo, isFoobar; 240 241 { 242 OptionHandler optHandler = OptionHandler(["prog", "--foo", "-f"]); 243 244 optHandler.read("foo", "information", isFoo, true); 245 optHandler.read("foobar|f", "quick", isFoobar); 246 } 247 248 assert(isFoo == true); 249 assert(isFoobar == true); 250 } 251 252 253 @safe pure 254 @("Option Handler: check values") 255 unittest 256 { 257 int[] foo; 258 259 { 260 OptionHandler optHandler = OptionHandler(["prog", "--foo=4", "-f=7"]); 261 262 foo = optHandler.read!int("foo|f", "information", true); 263 } 264 265 assert(!foo.empty); 266 assert(foo[0] == 4); 267 assert(foo[1] == 7); 268 } 269 270 271 @safe pure 272 @("Option Handler: required exception") 273 unittest 274 { 275 bool isFoo, isBar; 276 277 { 278 OptionHandler optHandler = OptionHandler(["prog"]); 279 280 optHandler.read("bar", "information", isBar); 281 282 try { 283 optHandler.read("foo", "more information", isFoo, true); 284 assert(0); 285 } catch(OptionHandlerException) {} 286 } 287 } 288 289 @safe pure 290 @("Option Handler: twice") 291 unittest 292 { 293 bool dummy; 294 OptionHandler optHandler = OptionHandler(["prog"]); 295 optHandler.read("dummy", "some", dummy); 296 297 try { 298 optHandler.read("dummy", "same", dummy); 299 assert(0); 300 } catch (OptionHandlerException) {} 301 }