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 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)); }