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 }