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 }