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 version(unittest) import aurorafw.unit.assertion; 52 53 @safe pure 54 class OptionHandlerException : Exception 55 { 56 mixin basicExceptionCtors; 57 } 58 59 static assert(is(typeof(new OptionHandlerException("message")))); 60 static assert(is(typeof(new OptionHandlerException("message", Exception.init)))); 61 62 @safe pure 63 struct OptionHandler { 64 struct Option { 65 string[] opts; 66 string help; 67 } 68 69 struct Argument { 70 string name; 71 ArgumentSize size; 72 string value = null; 73 } 74 75 enum ArgumentSize { 76 Long, 77 Short 78 } 79 80 @safe pure 81 public this(string[] args) 82 { 83 foreach(string arg ; args[1 .. $]) 84 { 85 if(arg.startsWith("--")) 86 { 87 string splitted = arg.split("--")[1]; 88 if(splitted.length == 0) 89 break; 90 91 ptrdiff_t hasValue = splitted.indexOf('='); 92 if(hasValue != -1) 93 { 94 string newSplitted = splitted[0 .. hasValue]; 95 if(newSplitted.isAlpha) 96 this.args ~= Argument(newSplitted, ArgumentSize.Long, splitted[hasValue + 1 .. $]); 97 } 98 else if(splitted.isAlpha) 99 this.args ~= Argument(splitted, ArgumentSize.Long); 100 } 101 else if (arg.startsWith("-")) 102 { 103 string splitted = arg.split("-")[1]; 104 ptrdiff_t hasValue = splitted.indexOf('='); 105 if(hasValue != -1) 106 { 107 string newSplitted = splitted[0 .. hasValue]; 108 if(newSplitted.isAlpha) 109 this.args ~= Argument(newSplitted, ArgumentSize.Short, splitted[hasValue + 1 .. $]); 110 } 111 else if(splitted.isAlpha && splitted.length != 0) 112 this.args ~= Argument(splitted, ArgumentSize.Short); 113 } 114 } 115 } 116 117 @safe pure 118 public T[] read(T)(string opts, string help, bool required = false) 119 { 120 read(opts, help); 121 122 T[] ret; 123 bool value = false; 124 foreach(arg; args) if(opts.canFind(arg.name)) 125 { 126 value = true; 127 string retstr = arg.value; 128 if(retstr !is null) 129 { 130 import std.conv : to; 131 ret ~= to!T(retstr); 132 } 133 } 134 135 if(required && (!value || (value && ret.empty))) 136 throw new OptionHandlerException("Required option %s with %s type".format(opts, fullyQualifiedName!T)); 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 @safe pure nothrow 176 public bool helpWanted() 177 { 178 foreach(arg; args) 179 { 180 if(arg.name == "help") 181 return true; 182 } 183 return false; 184 } 185 186 @safe pure 187 public string printableHelp(string programName) 188 { 189 string ret; 190 191 ret ~= "Usage:\n\t%s -- <options>\n\nOptions:\n".format(programName); 192 193 foreach(opt; opts) 194 { 195 ret ~= opt.opts[0]; 196 if(opt.opts.length == 2) 197 { 198 ret ~= " " ~ opt.opts[1]; 199 } 200 ret ~= "\t" ~ opt.help ~ "\n"; 201 } 202 return ret; 203 } 204 205 @safe pure 206 @property public Argument[] arguments() 207 { 208 return this.args.dup; 209 } 210 211 @property @safe pure 212 public Option[] options() 213 { 214 return this.opts.dup; 215 } 216 217 private Argument[] args; 218 private Option[] opts; 219 } 220 221 @safe pure 222 @("Option Handler: help") 223 unittest 224 { 225 bool dummy; 226 227 { 228 OptionHandler optHandler = OptionHandler(["prog", "--help"]); 229 optHandler.read("dummy", "some", dummy); 230 optHandler.read("duuu|d", "none", dummy); 231 232 auto pHelp = "Usage:\n\tprogram -- <options>\n\nOptions:\n"; 233 pHelp ~= "dummy\tsome\n"; 234 pHelp ~= "d duuu\tnone\n"; 235 236 assertEquals(optHandler.printableHelp("program"), pHelp); 237 assertTrue(optHandler.helpWanted); 238 239 optHandler = OptionHandler(["prog"]); 240 assertFalse(optHandler.helpWanted); 241 } 242 } 243 244 @safe pure 245 @("Option Handler: check arguments and options") 246 unittest 247 { 248 OptionHandler opts = OptionHandler(["prog", "--foo", "-f", "--bar=foobar", "--", "tunaisgood"]); 249 250 OptionHandler.Argument[] args = [ 251 OptionHandler.Argument("foo", OptionHandler.ArgumentSize.Long), 252 OptionHandler.Argument("f", OptionHandler.ArgumentSize.Short), 253 OptionHandler.Argument("bar", OptionHandler.ArgumentSize.Long, "foobar") 254 ]; 255 256 assertEquals(args, opts.arguments); 257 assertEquals([], opts.options); 258 } 259 260 261 @safe pure 262 @("Option Handler: check read") 263 unittest 264 { 265 bool isFoo, isFoobar; 266 267 { 268 OptionHandler optHandler = OptionHandler(["prog", "--foo", "-f"]); 269 270 optHandler.read("foo", "information", isFoo, true); 271 optHandler.read("foobar|f", "quick", isFoobar); 272 273 auto opts = [ 274 OptionHandler.Option(["foo"], "information"), 275 OptionHandler.Option(["f", "foobar"], "quick") 276 ]; 277 278 assertEquals(opts, optHandler.options); 279 280 assertThrown(optHandler.read!int("tuna", "tunaisgood", true)); 281 } 282 283 assertTrue(isFoo); 284 assertTrue(isFoobar); 285 } 286 287 288 @safe pure 289 @("Option Handler: check values") 290 unittest 291 { 292 int[] foo; 293 294 { 295 OptionHandler optHandler = OptionHandler(["prog", "--foo=4", "-f=7"]); 296 297 foo = optHandler.read!int("foo|f", "information", true); 298 } 299 300 assertFalse(foo.empty); 301 assertEquals(4, foo[0]); 302 assertEquals(7, foo[1]); 303 } 304 305 306 @safe pure 307 @("Option Handler: required exception") 308 unittest 309 { 310 bool isFoo, isBar; 311 312 { 313 OptionHandler optHandler = OptionHandler(["prog"]); 314 optHandler.read("bar", "information", isBar); 315 316 assertThrown!OptionHandlerException(optHandler.read("foo", "more information", isFoo, true)); 317 } 318 } 319 320 @safe pure 321 @("Option Handler: twice") 322 unittest 323 { 324 bool dummy; 325 326 { 327 OptionHandler optHandler = OptionHandler(["prog"]); 328 optHandler.read("dummy", "some", dummy); 329 330 assertThrown!OptionHandlerException(optHandler.read("dummy", "same", dummy)); 331 } 332 }