1 /*
2  * hunt-console eases the creation of beautiful and testable command line interfaces.
3  *
4  * Copyright (C) 2018-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.console.input.InputDefinition;
13 
14 import hunt.console.error.InvalidArgumentException;
15 import hunt.console.error.LogicException;
16 
17 import hunt.collection.Map;
18 import hunt.collection.LinkedHashMap;
19 import hunt.collection.HashMap;
20 import hunt.console.input.InputArgument;
21 import hunt.console.input.InputOption;
22 import hunt.collection.Collection;
23 import hunt.collection.List;
24 import hunt.collection.ArrayList;
25 import hunt.util.StringBuilder;
26 import hunt.Integer;
27 import std.string;
28 
29 class InputDefinition
30 {
31     private Map!(string, InputArgument) arguments;
32 
33     private int requiredCount;
34 
35     private bool hasAnArrayArgument = false;
36 
37     private bool hasOptional;
38 
39     private Map!(string, InputOption) options;
40 
41     private Map!(string, string) shortcuts;
42 
43     public this()
44     {
45         arguments = new LinkedHashMap!(string, InputArgument)();
46          options = new HashMap!(string, InputOption)();
47          shortcuts = new HashMap!(string, string)();
48         resetArguments();
49     }
50 
51     private void resetArguments()
52     {
53         arguments = new LinkedHashMap!(string, InputArgument)();
54         requiredCount = 0;
55         hasAnArrayArgument = false;
56         hasOptional = false;
57     }
58 
59     public void setArguments(Collection!(InputArgument) arguments)
60     {
61         setArguments(new ArrayList!(InputArgument)(arguments));
62     }
63 
64     public void setArguments(List!(InputArgument) arguments)
65     {
66         resetArguments();
67         addArguments(arguments);
68     }
69 
70     public void addArguments(Collection!(InputArgument) arguments)
71     {
72         addArguments(new ArrayList!(InputArgument)(arguments));
73     }
74 
75     public void addArguments(List!(InputArgument) arguments)
76     {
77         foreach (InputArgument argument ; arguments) {
78             addArgument(argument);
79         }
80     }
81 
82     public void addArgument(InputArgument argument)
83     {
84         if (arguments.containsKey(argument.getName())) {
85             throw new LogicException(format("An argument with name '%s' already exists.", argument.getName()));
86         }
87 
88         if (hasAnArrayArgument) {
89             throw new LogicException("Cannot add an argument after an array argument.");
90         }
91 
92         if (argument.isRequired() && hasOptional) {
93             throw new LogicException("Cannot add a required argument after an optional one.");
94         }
95 
96         if (argument.isArray()) {
97             hasAnArrayArgument = true;
98         }
99 
100         if (argument.isRequired()) {
101             ++requiredCount;
102         } else {
103             hasOptional = true;
104         }
105 
106         arguments.put(argument.getName(), argument);
107     }
108 
109     public InputArgument getArgument(string name)
110     {
111         if (!hasArgument(name)) {
112             throw new InvalidArgumentException(format("The '%s' argument does not exist.", name));
113         }
114 
115         return arguments.get(name);
116     }
117 
118     public InputArgument getArgument(int pos)
119     {
120         return cast(InputArgument) (arguments.values()[pos]);
121     }
122 
123     public bool hasArgument(string name)
124     {
125         return arguments.containsKey(name);
126     }
127 
128     public bool hasArgument(int pos)
129     {
130         return arguments.size() > pos;
131     }
132 
133     public Collection!InputArgument getArguments()
134     {
135         return new ArrayList!InputArgument(arguments.values());
136     }
137 
138     public int getArgumentCount()
139     {
140         return hasAnArrayArgument ? Integer.MAX_VALUE : arguments.size();
141     }
142 
143     public int getArgumentRequiredCount()
144     {
145         return requiredCount;
146     }
147 
148     public Map!(string, string) getArgumentDefaults()
149     {
150         HashMap!(string, string) defaultValues = new LinkedHashMap!(string, string)();
151         foreach (InputArgument argument ; arguments.values()) {
152             defaultValues.put(argument.getName(), argument.getDefaultValue());
153         }
154 
155         return defaultValues;
156     }
157 
158     public void setOptions(Collection!(InputOption) options)
159     {
160         setOptions(new ArrayList!(InputOption)(options));
161     }
162 
163     public void setOptions(List!(InputOption) options)
164     {
165         this.options = new HashMap!(string, InputOption)();
166         this.shortcuts = new HashMap!(string, string)();
167         addOptions(options);
168     }
169 
170     public void addOptions(Collection!(InputOption) options)
171     {
172         addOptions(new ArrayList!(InputOption)(options));
173     }
174 
175     public void addOptions(List!(InputOption) options)
176     {
177         foreach (InputOption option ; options) {
178             addOption(option);
179         }
180     }
181 
182     public void addOption(InputOption option)
183     {
184         if (options.containsKey(option.getName()) && !(option == options.get(option.getName()))) {
185             throw new LogicException(format("An option named '%s' already exists.", option.getName()));
186         }
187 
188         if (option.getShortcut() !is null) {
189             foreach (string shortcut ; option.getShortcut().split("\\|")) {
190                 if (shortcuts.containsKey(shortcut) && !(option == options.get(shortcuts.get(shortcut)))) {
191                     throw new LogicException(format("An option with shortcut '%s' already exists.", shortcut));
192                 }
193             }
194         }
195 
196         options.put(option.getName(), option);
197 
198         if (option.getShortcut() !is null) {
199             foreach (string shortcut ; option.getShortcut().split("|")) {
200                 shortcuts.put(shortcut, option.getName());
201             }
202         }
203     }
204 
205     public InputOption getOption(string name)
206     {
207         if (!hasOption(name)) {
208             throw new InvalidArgumentException(format("The '--%s' option does not exist.", name));
209         }
210 
211         return options.get(name);
212     }
213 
214     public bool hasOption(string name)
215     {
216         return options.containsKey(name);
217     }
218 
219     public Collection!(InputOption) getOptions()
220     {
221         return new ArrayList!(InputOption)(options.values());
222     }
223 
224     public bool hasShortcut(string name)
225     {
226         return shortcuts.containsKey(name);
227     }
228 
229     public InputOption getOptionForShortcut(string shortcut)
230     {
231         return getOption(shortcutToName(shortcut));
232     }
233 
234     public Map!(string, string) getOptionDefaults()
235     {
236         HashMap!(string, string) defaultValues = new HashMap!(string, string)();
237         foreach (InputOption option ; options.values()) {
238             defaultValues.put(option.getName(), option.getDefaultValue());
239         }
240 
241         return defaultValues;
242     }
243 
244     private string shortcutToName(string shortcut)
245     {
246         if (!shortcuts.containsKey(shortcut)) {
247             throw new InvalidArgumentException(format("The '-%s' option does not exist.", shortcut));
248         }
249 
250         return shortcuts.get(shortcut);
251     }
252 
253     public string getSynopsis()
254     {
255         StringBuilder synopsis = new StringBuilder();
256 
257         string shortcut;
258         foreach (InputOption option ; options.values()) {
259             shortcut = option.getShortcut() is null ? "" : (format("-%s|", option.getShortcut()));
260             synopsis.append(format("[" ~ (option.isValueRequired() ? "%s--%s='...'" : (option.isValueOptional() ? "%s--%s[='...']" : "%s--%s")) ~ "] ", shortcut, option.getName()));
261         }
262 
263         foreach (InputArgument argument ; arguments.values()) {
264             synopsis.append(format(argument.isRequired() ? "%s " : "[%s] ", argument.getName() ~ (argument.isArray() ? "1" : "")));
265             if (argument.isArray()) {
266                 synopsis.append(format("... [%sN]", argument.getName()));
267             }
268         }
269 
270         return synopsis.toString().strip();
271     }
272 
273     public string asText()
274     {
275         // todo implement
276 
277         return null;
278     }
279 
280     public string asXml()
281     {
282         // todo implement
283 
284         return null;
285     }
286 }