// todo - This class was originally very dumb. It parsed the command line arguments without knowledge of the definition. // However, several special syntaxes that folks were expecting would only be possible if the parser had pretty deep // knowledge of the program structure. So now this class takes in the definition and inspects it to handle these // special cases. I should finish the job and handle positional elements this way too. This would remove the need // for the 'ImplicitParameters' collection in the ParseResult. On that other hand that would be a breaking change just // for the sake of cleanup. I need to think it over. // // Another potential item would be to refactor the parse method here. It's a mess, but it's a working, heavily tested mess // so cleaning it up will mean accepting some risk. internal static ParseResult Parse(CommandLineArgumentsDefinition Definition, string[] commandLineArgs) { var args = commandLineArgs; ParseResult result = new ParseResult(); int argumentPosition = 0; for (int i = 0; i < args.Length; i++) { var token = args[i]; // this block handles action parameters that must always be the first token if (i == 0 && Definition.Actions.Count > 0 && Definition.FindMatchingAction(token) != null) { result.ImplicitParameters.Add(0, token); argumentPosition++; } else if (token.StartsWith("/")) { var param = ParseSlashExplicitOption(token); if (result.ExplicitParameters.ContainsKey(param.Key)) throw new DuplicateArgException("Argument specified more than once: " + param.Key); result.ExplicitParameters.Add(param.Key, param.Value); argumentPosition = -1; } else if (token.StartsWith("-")) { string key = token.Substring(1); if (key.Length == 0) throw new ArgException("Missing argument value after '-'"); string value; // Handles long form syntax --argName=argValue. if (key.StartsWith("-") && key.Contains("=")) { var index = key.IndexOf("="); value = key.Substring(index + 1); key = key.Substring(0, index); } else { if (i == args.Length - 1) { value = ""; } else if (IsBool(key, Definition, result)) { var next = args[i + 1].ToLower(); if (next == "true" || next == "false" || next == "0" || next == "1") { i++; value = next; } else { value = "true"; } } else { i++; value = args[i]; } } if (result.ExplicitParameters.ContainsKey(key)) { throw new DuplicateArgException("Argument specified more than once: " + key); } result.ExplicitParameters.Add(key, value); if(IsArrayOrList(key, Definition, result)) { while((i+1) < args.Length) { var nextToken = args[i+1]; if(nextToken.StartsWith("/") || nextToken.StartsWith("-")) { break; } else { result.AddAdditionalParameter(key, nextToken); i++; } } } argumentPosition = -1; } else { if (argumentPosition < 0) throw new UnexpectedArgException("Unexpected argument: " + token); var possibleActionContext = result.ImplicitParameters.ContainsKey(0) ? result.ImplicitParameters[0] : null; var potentialListArgument = Definition.FindArgumentByPosition(argumentPosition, possibleActionContext); if (potentialListArgument != null) { bool isArrayOrList = potentialListArgument.ArgumentType.IsArray || potentialListArgument.ArgumentType.GetInterfaces().Contains(typeof(IList)); if (isArrayOrList) { // this block does special handling to allow for space separated collections for positioned parameters result.ExplicitParameters.Add(potentialListArgument.DefaultAlias, token); argumentPosition = -1; // no more positional arguments are allowed after this while ((i + 1) < args.Length) { var nextToken = args[i + 1]; if (nextToken.StartsWith("/") || nextToken.StartsWith("-")) { break; } else { result.AddAdditionalParameter(potentialListArgument.DefaultAlias, nextToken); i++; } } } else { // not an array or list parameter so add to the implicit parameter collection result.ImplicitParameters.Add(argumentPosition, token); argumentPosition++; } } else { // not an array or list parameter so add to the implicit parameter collection result.ImplicitParameters.Add(argumentPosition, token); argumentPosition++; } } } return result; }
// todo - This class was originally very dumb. It parsed the command line arguments without knowledge of the definition. // However, several special syntaxes that folks were expecting would only be possible if the parser had pretty deep // knowledge of the program structure. So now this class takes in the definition and inspects it to handle these // special cases. I should finish the job and handle positional elements this way too. This would remove the need // for the 'ImplicitParameters' collection in the ParseResult. On that other hand that would be a breaking change just // for the sake of cleanup. I need to think it over. // // Another potential item would be to refactor the parse method here. It's a mess, but it's a working, heavily tested mess // so cleaning it up will mean accepting some risk. internal static ParseResult Parse(CommandLineArgumentsDefinition Definition, string[] commandLineArgs) { var args = commandLineArgs; ParseResult result = new ParseResult(); int argumentPosition = 0; for (int i = 0; i < args.Length; i++) { var token = args[i]; // this block handles action parameters that must always be the first token if (i == 0 && Definition.Actions.Count > 0 && Definition.FindMatchingAction(token) != null) { result.ImplicitParameters.Add(0, token); argumentPosition++; } else if (token.StartsWith("/")) { var param = ParseSlashExplicitOption(token); if (result.ExplicitParameters.ContainsKey(param.Key)) { throw new DuplicateArgException("Argument specified more than once: " + param.Key); } result.ExplicitParameters.Add(param.Key, param.Value); argumentPosition = -1; } else if (token.StartsWith("-")) { string key = token.Substring(1); if (key.Length == 0) { throw new ArgException("Missing argument value after '-'"); } string value; // Handles long form syntax --argName=argValue. if (key.StartsWith("-") && key.Contains("=")) { var index = key.IndexOf("="); value = key.Substring(index + 1); key = key.Substring(0, index); } else { if (i == args.Length - 1) { value = ""; } else if (IsBool(key, Definition, result)) { var next = args[i + 1].ToLower(); if (next == "true" || next == "false" || next == "0" || next == "1") { i++; value = next; } else { value = "true"; } } else { i++; value = args[i]; } } if (result.ExplicitParameters.ContainsKey(key)) { throw new DuplicateArgException("Argument specified more than once: " + key); } result.ExplicitParameters.Add(key, value); if (IsArrayOrList(key, Definition, result)) { while ((i + 1) < args.Length) { var nextToken = args[i + 1]; if (nextToken.StartsWith("/") || nextToken.StartsWith("-")) { break; } else { result.AddAdditionalParameter(key, nextToken); i++; } } } argumentPosition = -1; } else { if (argumentPosition < 0) { throw new UnexpectedArgException("Unexpected argument: " + token); } var possibleActionContext = result.ImplicitParameters.ContainsKey(0) ? result.ImplicitParameters[0] : null; var potentialListArgument = Definition.FindArgumentByPosition(argumentPosition, possibleActionContext); if (potentialListArgument != null) { bool isArrayOrList = potentialListArgument.ArgumentType.IsArray || potentialListArgument.ArgumentType.GetInterfaces().Contains(typeof(IList)); if (isArrayOrList) { // this block does special handling to allow for space separated collections for positioned parameters result.ExplicitParameters.Add(potentialListArgument.DefaultAlias, token); argumentPosition = -1; // no more positional arguments are allowed after this while ((i + 1) < args.Length) { var nextToken = args[i + 1]; if (nextToken.StartsWith("/") || nextToken.StartsWith("-")) { break; } else { result.AddAdditionalParameter(potentialListArgument.DefaultAlias, nextToken); i++; } } } else { // not an array or list parameter so add to the implicit parameter collection result.ImplicitParameters.Add(argumentPosition, token); argumentPosition++; } } else { // not an array or list parameter so add to the implicit parameter collection result.ImplicitParameters.Add(argumentPosition, token); argumentPosition++; } } } return(result); }