1 /** 2 * Модуль обработки командной строки 3 * 4 * Copyright: (c) 2015-2017, Milofon Project. 5 * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file. 6 * Authors: Maksim Galanin 7 */ 8 module dango.system.commandline; 9 10 private 11 { 12 import std.array: split; 13 import std.getopt; 14 import std.process: environment; 15 import std.traits: isArray, isNarrowString; 16 import std.conv : to; 17 18 import proped.properties: Properties, PropNode; 19 } 20 21 22 private template ValueTuple(T...) { alias ValueTuple = T; } 23 24 private alias getoptConfig = ValueTuple!(std.getopt.config.passThrough, std.getopt.config.bundling); 25 26 private alias getoptRequireConfig = ValueTuple!(std.getopt.config.passThrough, std.getopt.config.bundling, std.getopt.config.required); 27 28 29 /** 30 * Процессор для обработки командной строки 31 */ 32 class CommandLineProcessor 33 { 34 private 35 { 36 string[] args; 37 Option[] options; 38 Properties config; 39 40 bool helpWanted; 41 bool errorWanted; 42 } 43 44 45 this(string[] args) 46 { 47 PropNode[string] map; 48 this.config = Properties(PropNode(map)); 49 this.args = args; 50 } 51 52 53 private Option splitAndGet(string names) pure nothrow 54 { 55 auto sp = split(names, "|"); 56 Option ret; 57 if (sp.length > 1) 58 { 59 ret.optShort = "-" ~ (sp[0].length < sp[1].length ? 60 sp[0] : sp[1]); 61 62 ret.optLong = "--" ~ (sp[0].length > sp[1].length ? 63 sp[0] : sp[1]); 64 } 65 else 66 { 67 ret.optLong = "--" ~ sp[0]; 68 } 69 70 return ret; 71 } 72 73 74 private void addProperties(T)(string key, T value) 75 { 76 static if (isNarrowString!T) 77 config.set(key, value); 78 else static if (isArray!T) 79 { 80 PropNode[] arr; 81 foreach(item; value) 82 arr ~= PropNode(item); 83 config.set(key, arr); 84 } 85 else 86 config.set(key, value.to!string); 87 } 88 89 /** 90 * Чтение аргумента из командной строки при помощи стандартных стредств phobos 91 * 92 * Params: 93 * 94 * names = Считываемые с командной строки аргументы 95 * value = Ссылка на переменную в которую будет установлено заначение 96 * helpText = Текст справки 97 * required = Флаг обязательности аргумента 98 * propertyPath = Путь, по которому будет установлено значение в конфиге 99 * 100 * Example: 101 * -------------------- 102 * readOption("config|c", &configFiles, "Конфиг файлы", false, "trand.config"); 103 * -------------------- 104 */ 105 void readOption(T)(string names, T* value, string helpText, bool required = false, string propertyPath = null) 106 { 107 Option opt = splitAndGet(names); 108 opt.help = helpText; 109 opt.required = required; 110 options ~= opt; 111 112 try 113 { 114 GetoptResult gr; 115 if (required) 116 gr = getopt(args, getoptRequireConfig, names, helpText, value); 117 else 118 gr = getopt(args, getoptConfig, names, helpText, value); 119 120 if (!helpWanted) 121 helpWanted = gr.helpWanted; 122 123 string propName = (propertyPath is null) ? "args." ~ opt.optLong[2..$] : propertyPath; 124 addProperties!T(propName, (*value)); 125 } 126 catch (Exception e) 127 errorWanted = true; 128 } 129 130 /** 131 * Проверка на успешность разбора командной строки 132 */ 133 bool checkOptions() 134 { 135 return !(helpWanted || errorWanted); 136 } 137 138 /** 139 * Вывод справки в поток стандартного вывода 140 * 141 * Params: 142 * 143 * text = Заголовок сообщения 144 */ 145 void printer(string text) 146 { 147 defaultGetoptPrinter(text, options); 148 } 149 150 /** 151 * Возвращает свойства полученные при разборе 152 */ 153 Properties getOptionProperties() 154 { 155 return config; 156 } 157 158 /** 159 * Возвращает свойства полученные из переменных окружения приложения 160 */ 161 Properties getEnvironmentProperties() 162 { 163 PropNode[string] map; 164 foreach(string key, string val; environment.toAA) 165 map[key] = PropNode(val); 166 167 return Properties(PropNode(["env": PropNode(map)])); 168 } 169 }