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