|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT license. |
| 3 | +using System.Collections; |
| 4 | + |
| 5 | +namespace SQLCheck |
| 6 | +{ |
| 7 | + |
| 8 | + // |
| 9 | + // Written by the Microsoft CSS SQL Networking Team |
| 10 | + // |
| 11 | + // Calling order: |
| 12 | + // |
| 13 | + // AddRule - n times |
| 14 | + // string result = Parse(args) - once, result contains error message or "" if okay |
| 15 | + // ArrayList List = GetArgs(argname) - once per arg, returns an ArrayList of 0..n CommandlineArgs |
| 16 | + // |
| 17 | + // Arguments can have the following prioperties: |
| 18 | + // |
| 19 | + // Case sensitive or case-insensitive |
| 20 | + // Required or optional |
| 21 | + // Appear once or many times (or 0 times if optional) |
| 22 | + // Have a value or not |
| 23 | + // If the arg name is "" then the value must not be proceeded by a flag that requires a value |
| 24 | + // |
| 25 | + // EXAMPLE USAGE: |
| 26 | + // |
| 27 | + // appname.exe filename [-out filename] [-g value] [-G] -h value [-flags value [-flags value [...]]] |
| 28 | + // |
| 29 | + // public ArgRule(argName, hasValue, allowDuplicates = false, caseInsensitive = true, required = true) |
| 30 | + // cp.AddRule(new ArgRule("", true, false, true, true)); // file name is required |
| 31 | + // cp.AddRule(new ArgRule("flags", true, true, true, false)); // flags is optional but may appear more than once |
| 32 | + // cp.AddRule(new ArgRule("out", true, false, true, false)); // out is optional but may only appear once |
| 33 | + // cp.AddRule(new ArgRule("g", true, false, false, false)); // g is optional and case-sensitive |
| 34 | + // cp.AddRule(new ArgRule("G", false, false, false, false)); // G is optional and takes no value and is case sensitive |
| 35 | + // cp.AddRule(new ArgRule("h", true, false, true, true)); // h is required and takes an argument |
| 36 | + // |
| 37 | + // argements can appear in any order, as long as name/value pairs are adjacent |
| 38 | + // arguments other than -g and -G are case-insensitive |
| 39 | + // -flags may appear 0..n times |
| 40 | + // -G can come before filename |
| 41 | + // name value pairs can be: -x=y, -x:y, or -x y |
| 42 | + // |
| 43 | + |
| 44 | + class CommandLineParser |
| 45 | + { |
| 46 | + public ArrayList Args = new ArrayList(); |
| 47 | + public ArrayList Rules = new ArrayList(); |
| 48 | + |
| 49 | + public string Parse(string[] args) |
| 50 | + { |
| 51 | + string lastToken = ""; |
| 52 | + string ruleViolation = ""; |
| 53 | + |
| 54 | + foreach (string arg in args) |
| 55 | + { |
| 56 | + string argName = ""; |
| 57 | + if (arg.StartsWith("-") || arg.StartsWith("/")) |
| 58 | + { |
| 59 | + string[] parts = null; |
| 60 | + argName = arg.Substring(1); // strip leading - or / |
| 61 | + |
| 62 | + // |
| 63 | + // process lastToken, if it has a value |
| 64 | + // |
| 65 | + if (lastToken.Length > 0) |
| 66 | + { |
| 67 | + if (lastToken.StartsWith("-") || lastToken.StartsWith("/")) |
| 68 | + { |
| 69 | + ruleViolation = AddArg(lastToken.Substring(1), ""); // switch with no arguments |
| 70 | + } |
| 71 | + else |
| 72 | + { |
| 73 | + ruleViolation = AddArg("", lastToken); // switch-less argument |
| 74 | + } |
| 75 | + if (ruleViolation != "") return ruleViolation; |
| 76 | + lastToken = ""; |
| 77 | + } |
| 78 | + |
| 79 | + // |
| 80 | + // does the switch have a value in the same arg ??? |
| 81 | + // |
| 82 | + if (argName.Contains(":") || argName.Contains("=")) |
| 83 | + { |
| 84 | + parts = argName.Split(new char[] {':', '='}, 2); |
| 85 | + ruleViolation = AddArg(parts[0], parts[1]); // switch with argument separated by : or = |
| 86 | + if (ruleViolation != "") return ruleViolation; |
| 87 | + } |
| 88 | + else |
| 89 | + { |
| 90 | + lastToken = arg; // switch - argument may be in next arg - wait for next iteration |
| 91 | + } |
| 92 | + } |
| 93 | + else // switchless argument - process lastToken |
| 94 | + { |
| 95 | + if (lastToken.Length > 0) |
| 96 | + { |
| 97 | + if (lastToken.StartsWith("-") || lastToken.StartsWith("/")) |
| 98 | + { |
| 99 | + // |
| 100 | + // Should the switch take an argument? |
| 101 | + // |
| 102 | + lastToken = lastToken.Substring(1); // trim - or / |
| 103 | + ArgRule r = GetArgRule(lastToken); |
| 104 | + if (r.fHasValue) |
| 105 | + { |
| 106 | + ruleViolation = AddArg(lastToken, arg); // switch with value in next argument |
| 107 | + if (ruleViolation != "") return ruleViolation; |
| 108 | + lastToken = ""; |
| 109 | + } |
| 110 | + else |
| 111 | + { |
| 112 | + ruleViolation = AddArg(lastToken, ""); // switch takes no arguments, promote arg to lastToken |
| 113 | + if (ruleViolation != "") return ruleViolation; |
| 114 | + lastToken = arg; |
| 115 | + } |
| 116 | + } |
| 117 | + else |
| 118 | + { |
| 119 | + ruleViolation = AddArg("", lastToken); // switch-less argument |
| 120 | + if (ruleViolation != "") return ruleViolation; |
| 121 | + lastToken = ""; |
| 122 | + } |
| 123 | + } |
| 124 | + else |
| 125 | + { |
| 126 | + lastToken = arg; |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // |
| 132 | + // Process lastToken if one exists |
| 133 | + // |
| 134 | + if (lastToken.Length > 0) |
| 135 | + { |
| 136 | + if (lastToken.StartsWith("-") || lastToken.StartsWith("/")) |
| 137 | + { |
| 138 | + ruleViolation = AddArg(lastToken.Substring(1), ""); // switch takes no arguments, promote arg to lastToken |
| 139 | + if (ruleViolation != "") return ruleViolation; |
| 140 | + } |
| 141 | + else |
| 142 | + { |
| 143 | + ruleViolation = AddArg("", lastToken); // switch-less argument |
| 144 | + if (ruleViolation != "") return ruleViolation; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + if (ruleViolation != "") return ruleViolation; |
| 149 | + |
| 150 | + // |
| 151 | + // Evaluate missing required switches |
| 152 | + // |
| 153 | + |
| 154 | + foreach (ArgRule r in Rules) |
| 155 | + { |
| 156 | + if (r.fMustAppear && (GetArgs(r.name).Count == 0)) |
| 157 | + { |
| 158 | + if (r.name == "") |
| 159 | + return @"Required value is missing."; |
| 160 | + else |
| 161 | + return @"Required switch " + r.name + " is missing."; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + return ""; |
| 166 | + } |
| 167 | + |
| 168 | + private string AddArg(string name, string value) |
| 169 | + { |
| 170 | + // check rules |
| 171 | + ArgRule r = GetArgRule(name); |
| 172 | + if (r == null && name == "") return @"Invalid argument: " + value; |
| 173 | + if (r == null) return @"Invalid switch: " + name; |
| 174 | + |
| 175 | + if (r.fInsensitive) name = name.ToLower(); |
| 176 | + |
| 177 | + if (r.fDuplicates == false) if (GetArgs(name).Count > 0) |
| 178 | + { |
| 179 | + if (name == "") |
| 180 | + return @"A switch-less value can only appear once."; |
| 181 | + else |
| 182 | + return @"Switch " + name + " can only appear once."; |
| 183 | + } |
| 184 | + if (r.fHasValue == false) if (value != "") return @"Switch " + name + " does not take an argument."; |
| 185 | + if (r.fHasValue == true) if (value == "") return @"Switch " + name + " requires an argument."; |
| 186 | + // |
| 187 | + // if fall through, we add the argument |
| 188 | + // |
| 189 | + if (value == "") value = "true"; |
| 190 | + CommandLineArgs a = new CommandLineArgs(); |
| 191 | + a.name = name; |
| 192 | + a.value = value; |
| 193 | + Args.Add(a); |
| 194 | + return ""; |
| 195 | + } |
| 196 | + |
| 197 | + public ArrayList GetArgs(string name) |
| 198 | + { |
| 199 | + ArrayList outList = new ArrayList(); |
| 200 | + foreach (CommandLineArgs a in Args) |
| 201 | + { |
| 202 | + if (a.name == name) |
| 203 | + outList.Add(a); |
| 204 | + } |
| 205 | + return outList; |
| 206 | + } |
| 207 | + |
| 208 | + private ArgRule GetArgRule(string name) |
| 209 | + { |
| 210 | + foreach (ArgRule r in Rules) |
| 211 | + { |
| 212 | + if (r.name == name) |
| 213 | + return r; |
| 214 | + if (r.fInsensitive && (r.name.ToLower() == name.ToLower())) |
| 215 | + return r; |
| 216 | + } |
| 217 | + return null; |
| 218 | + } |
| 219 | + |
| 220 | + public void AddRule(ArgRule r) |
| 221 | + { |
| 222 | + Rules.Add(r); |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + public class CommandLineArgs |
| 227 | + { |
| 228 | + public string name = ""; |
| 229 | + public string value = ""; |
| 230 | + } |
| 231 | + |
| 232 | + public class ArgRule |
| 233 | + { |
| 234 | + public string name = ""; |
| 235 | + public bool fHasValue = false; |
| 236 | + public bool fDuplicates = false; |
| 237 | + public bool fInsensitive = true; |
| 238 | + public bool fMustAppear = true; |
| 239 | + |
| 240 | + public ArgRule(string argName, bool hasValue, bool allowDuplicates = false, bool caseInsensitive = true, bool required = true) |
| 241 | + { |
| 242 | + name = caseInsensitive ? argName.ToLower() : argName; |
| 243 | + fHasValue = hasValue; |
| 244 | + fDuplicates = allowDuplicates; |
| 245 | + fInsensitive = caseInsensitive; |
| 246 | + fMustAppear = required; |
| 247 | + } |
| 248 | + } |
| 249 | +} |
0 commit comments