private string Usage(Type type) { VerbAttribute verbAttr = type.GetCustomAttributes <VerbAttribute>().FirstOrDefault(); var options = OptionsClassProcessor.GetOptions(type); var values = OptionsClassProcessor.GetValues(type); var restOfInput = OptionsClassProcessor.GetRestOfInputProperty(type); StringBuilder result = new StringBuilder(); if (verbAttr != null) { result.AppendLine(verbAttr.HelpText.SplitToLines(LineWidth + 20, 0) + Environment.NewLine); } foreach (var option in options.Values) { result.AppendFormat("{0,-20} {1}" + Environment.NewLine, (option.IsShortOption ? "-" : "--") + option.Option, option.Description.SplitToLines(LineWidth, PrepadSpaces)); } foreach (var value in values.Values.OrderBy(v => v.Index)) { result.AppendFormat("#{0,-19} {1}" + Environment.NewLine, value.Index, value.Description.SplitToLines(LineWidth, PrepadSpaces)); } if (restOfInput != null) { result.AppendFormat("{0,-20} {1}" + Environment.NewLine, "rest of input", restOfInput.Item2.Description.SplitToLines(LineWidth, PrepadSpaces)); } return(result.ToString()); }
private static ParseResult <object> SetRestOfInputPropertyIfAvailable(Type type, object instance, Tokenizer tokenizer) { var restOfInputProp = OptionsClassProcessor.GetRestOfInputProperty(type); if (restOfInputProp != null) { if (restOfInputProp.Item2.Required && tokenizer.AtEnd) { return(ParseResult <object> .WithError("A value for the property '" + restOfInputProp.Item1.Name + "' is missing")); } restOfInputProp.Item1.SetValue(instance, tokenizer.RestOfInput); } return(null); // No error }
public ParseResult <object> Parse(IEnumerable <Type> verbTypes, string input) { Dictionary <string, Type> verbs = OptionsClassProcessor.GetVerbs(verbTypes); var tokenizer = new Tokenizer(input); var token = tokenizer.NextToken; if (token.Kind != TokenKind.Value) { DisplayHelp(verbs, null); return(ParseResult <object> .WithError("Could not find an expected verb")); } if (token.Value == "help") { DisplayHelp(verbs, tokenizer); // The user intentionally asked for help, and this is not an error. The caller has to // be aware of the option that they are getting a null value in that case. return(ParseResult <object> .WithValue(null)); } Type type; if (!verbs.TryGetValue(token.Value, out type)) { DisplayHelp(verbs, null); return(ParseResult <object> .WithError("Could not find a match for verb '" + token.Value + "'")); } var result = Parse(type, tokenizer.RestOfInput); if (!result.Success) { _helpWriter.Write(Usage(type)); } return(result); }
private ParseResult <object> Parse(Type type, string input) { // TODO Refactor this whole thing to use exception handling and store common objects in a class object instance = Activator.CreateInstance(type); Dictionary <string, OptionAttribute> options = OptionsClassProcessor.GetOptions(type); Dictionary <int, ValueAttribute> values = OptionsClassProcessor.GetValues(type); int unsatisfiedCount = options.Count + values.Count; int nextValueToFill = 0; OptionAttribute optionExpectingValue = null; Tokenizer tokenizer = new Tokenizer(input); while (!tokenizer.AtEnd && unsatisfiedCount > 0) { Token token = tokenizer.NextToken; switch (token.Kind) { case TokenKind.LongOption: case TokenKind.ShortOption: if (optionExpectingValue != null) { return(ParseResult <object> .WithError("No value provided for option '" + optionExpectingValue.Option + "'")); } OptionAttribute optionAttr; if (!options.TryGetValue(token.Value, out optionAttr)) { return(ParseResult <object> .WithError("Unexpected option '" + token.Value + "'")); } if (optionAttr.Satisfied) { return(ParseResult <object> .WithError("Multiple values for option '" + token.Value + "' are not allowed")); } var alreadySatisfiedExclusiveOption = FirstAlreadySatisfiedMutuallyExclusiveOption(options, optionAttr); if (alreadySatisfiedExclusiveOption != null) { return(ParseResult <object> .WithError("The option '" + token.Value + "' is mutually exclusive with the option '" + alreadySatisfiedExclusiveOption.Option + "' that was already satisfied")); } if (optionAttr.TargetProperty.PropertyType == typeof(bool)) { optionAttr.TargetProperty.SetValue(instance, true); optionAttr.Satisfied = true; --unsatisfiedCount; } else { optionExpectingValue = optionAttr; } break; case TokenKind.Value: if (optionExpectingValue != null) { string error = SetPropertyValue(optionExpectingValue, instance, token.Value); if (error != null) { return(ParseResult <object> .WithError("Error setting option '" + optionExpectingValue.Option + "': " + error)); } optionExpectingValue.Satisfied = true; optionExpectingValue = null; --unsatisfiedCount; } else { if (nextValueToFill >= values.Count) { // There are no more values to fill, just options return(ParseResult <object> .WithError("Unexpected plain value '" + token.Value + "'")); } ValueAttribute valueAttr = values[nextValueToFill]; string error = SetPropertyValue(valueAttr, instance, token.Value); if (error != null) { return(ParseResult <object> .WithError("Error setting value #" + nextValueToFill + ": " + error)); } valueAttr.Satisfied = true; --unsatisfiedCount; ++nextValueToFill; } break; case TokenKind.Error: return(ParseResult <object> .WithError("Error parsing options: " + token.Value)); } } SetDefaultValues(instance, options, values); var errorResult = FindUnsatisfiedOptionsAndValues(options, values); if (errorResult != null) { return(errorResult); } errorResult = SetRestOfInputPropertyIfAvailable(type, instance, tokenizer); if (errorResult != null) { return(errorResult); } return(ParseResult <object> .WithValue(instance)); }