/// <summary> /// /// </summary> /// <param name="options">Object decorated by Plossum's attributes.</param> public CommandLineParser(object options) { if (options == null) { throw new ArgumentNullException("options"); } Options = options; CommandLineManagerAttribute = PlossumAttributesHelper.GetCommandLineManagerAttribute(options.GetType()); OptionAttributes = PlossumAttributesHelper.GetCommandLineOptionAttributes(options.GetType()); GroupAttributesDic = PlossumAttributesHelper.GetCommandLineOptionGroupAttributesDic(options.GetType()); }
public static string AnalyzeAssignedOptions(object optionsAssigned) { if (optionsAssigned == null) { throw new ArgumentNullException("optionsAssigned"); } var typeOfParametersAndOptions = optionsAssigned.GetType(); var propertiesOfParametersAndOptions = new List <PropertyInfo>(typeOfParametersAndOptions.GetProperties()); var managerAttribute = PlossumAttributesHelper.GetCommandLineManagerAttribute(typeOfParametersAndOptions); var isCaseSensitive = managerAttribute.IsCaseSensitive; var allOptionAttributes = PlossumAttributesHelper.GetCommandLineOptionAttributes(typeOfParametersAndOptions); var allPropertyInfo = PlossumAttributesHelper.GetOptionProperties(typeOfParametersAndOptions); List <PropertyInfo> propertyInfoOfAssignedProperties = new List <PropertyInfo>(); List <CommandLineOptionAttribute> optionAttributesOfAssignedProperties = new List <CommandLineOptionAttribute>(); foreach (PropertyInfo propertyInfo in propertiesOfParametersAndOptions) { var propertyValue = propertyInfo.GetValue(optionsAssigned, null); if (propertyValue == null) { continue; } int parameterOrder = PropertyHelper.GetParameterOrder(propertyInfo); if (parameterOrder < int.MaxValue)//so this is a fixed parameter { continue; } else//option { var defaultValue = PropertyHelper.ReadDefaultValue(propertyInfo); var optionAttribute = PlossumAttributesHelper.GetCommandLineOptionAttribute(propertyInfo); if (!propertyValue.Equals(defaultValue)) { optionAttributesOfAssignedProperties.Add(optionAttribute); propertyInfoOfAssignedProperties.Add(propertyInfo); } } } #region about Grouped options var optionAttributesGrouped = optionAttributesOfAssignedProperties.GroupBy(d => d.GroupId).OrderBy(k => k.Key); var allGroupAttributesDic = PlossumAttributesHelper.GetCommandLineOptionGroupAttributesDic(typeOfParametersAndOptions); StringBuilder builder = new StringBuilder(); foreach (var item in allGroupAttributesDic) { var groupId = item.Key; var groupAttribute = item.Value; var optionAttributes = optionAttributesGrouped.FirstOrDefault(d => d.Key == groupId); switch (groupAttribute.Require) { case OptionGroupRequirement.None: continue; case OptionGroupRequirement.AtMostOne: if (optionAttributes == null) { continue; } if (!(optionAttributes.Count() <= 1)) { builder.AppendLine(String.Format("Group {0} requires at most one option defined, but actually these are {1} options defined", groupAttribute.Name, optionAttributes.Count())); } break; case OptionGroupRequirement.AtLeastOne: if ((optionAttributes == null) || !(optionAttributes.Count() >= 1)) { builder.AppendLine(String.Format("Group {0} requires at least one option defined, but actually these is none defined", groupAttribute.Name)); } break; case OptionGroupRequirement.ExactlyOne: if (optionAttributes == null) { builder.AppendLine(String.Format("Group {0} requires exactly one option defined, but actually these is none defined", groupAttribute.Name)); } else if (!(optionAttributes.Count() == 1)) { builder.AppendLine(String.Format("Group {0} requires exactly one option defined, but actually these are {1} options defined.", groupAttribute.Name, optionAttributes.Count())); } break; case OptionGroupRequirement.All: if (optionAttributes == null) { builder.AppendLine(String.Format("Group {0} requires all options defined, but actually these is none defined", groupAttribute.Name)); } else { var numberOfOptionsOfGroup = allOptionAttributes.Count(d => d.GroupId == groupId); var delta = numberOfOptionsOfGroup - optionAttributes.Count(); if (delta != 0) { builder.AppendLine(String.Format("Group {0} requires all options defined, but actually there are {1} options(s) missing.", groupAttribute.Name, delta)); } } break; default: break; } } #endregion #region about Prohibits for (int i = 0; i < propertyInfoOfAssignedProperties.Count; i++) { var optionAttribute = optionAttributesOfAssignedProperties[i]; if (String.IsNullOrEmpty(optionAttribute.Prohibits)) { continue; } var propertyInfo = propertyInfoOfAssignedProperties[i]; string[] optionNamesProhibited = optionAttribute.Prohibits.Split(mSeparators, StringSplitOptions.RemoveEmptyEntries); if (optionNamesProhibited.Length == 0) { continue; } for (int k = 0; k < propertyInfoOfAssignedProperties.Count; k++) { var nextPropertyInfo = propertyInfoOfAssignedProperties[k]; bool matched = false; if (optionNamesProhibited.Contains(nextPropertyInfo.Name, isCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase)) { matched = true; } else { var nextOptionAttribute = optionAttributesOfAssignedProperties[k]; if (optionNamesProhibited.Contains(nextOptionAttribute.Name, isCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase)) { matched = true; } else { if ((nextOptionAttribute.AliasesArray != null) && (nextOptionAttribute.AliasesArray.Length > 0)) { matched = optionNamesProhibited.Any(d => nextOptionAttribute.AliasesArray.Contains(d, isCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase)); } } } if (matched) { builder.AppendLine(String.Format("Option {0} defined is prohibited since option {1} is already defined.", nextPropertyInfo.Name, propertyInfo.Name)); } } } #endregion for (int i = 0; i < propertyInfoOfAssignedProperties.Count; i++) { var optionAttribute = optionAttributesOfAssignedProperties[i]; var propertyInfo = propertyInfoOfAssignedProperties[i]; if (propertyInfo.PropertyType.IsArray) { var array = propertyInfo.GetValue(optionsAssigned, null); var list = array as Array; var arrayLength = list.Length; if ((optionAttribute.MaxOccurs > 1) && (arrayLength > optionAttribute.MaxOccurs)) { builder.AppendLine(String.Format("Option {0} with MaxOccurs={1} has {2} presented.", propertyInfo.Name, optionAttribute.MaxOccurs, arrayLength)); } if ((optionAttribute.MinOccurs > 0) && (arrayLength < optionAttribute.MinOccurs)) { builder.AppendLine(String.Format("Option {0} with MinOccurs={1} has {2} presented.", propertyInfo.Name, optionAttribute.MinOccurs, arrayLength)); } if ((optionAttribute.MaxOccurs > 0) && (optionAttribute.MinOccurs > optionAttribute.MaxOccurs)) { builder.AppendLine(String.Format("In option {0} MinOccurs={1} is greater than MaxOccurs={2} .", propertyInfo.Name, optionAttribute.MinOccurs, optionAttribute.MaxOccurs)); } } } for (int i = 0; i < allPropertyInfo.Length; i++) { var optionAttribute = allOptionAttributes[i]; var propertyInfo = allPropertyInfo[i]; if (propertyInfo.PropertyType.IsArray) { var array = propertyInfo.GetValue(optionsAssigned, null); var list = array as Array; // var arrayLength = list.Length; if ((list == null) && (optionAttribute.MinOccurs > 0)) { builder.AppendLine(String.Format("Option {0} with MinOccurs={1} has no value.", propertyInfo.Name, optionAttribute.MinOccurs)); } } else { if (optionAttribute.MaxOccurs > 1) { builder.AppendLine(String.Format("Option {0} must not have MaxOccurs greater than 1.", propertyInfo.Name)); } } } #region about MaxValue and MinValue for (int i = 0; i < propertyInfoOfAssignedProperties.Count; i++) { var optionAttribute = optionAttributesOfAssignedProperties[i]; var propertyInfo = propertyInfoOfAssignedProperties[i]; var maxValue = optionAttribute.MaxValue; var minValue = optionAttribute.MinValue; if ((maxValue == null) && (minValue == null)) { continue; } if (propertyInfo.PropertyType.IsArray) { var array = propertyInfo.GetValue(optionsAssigned, null); var list = array as Array; if (list == null) { continue; } var firstMember = list.GetValue(0); var memberType = firstMember.GetType(); if (!IsNumericType(memberType)) { builder.AppendLine(String.Format("Options {0} is not of numeric type, and must not have either MaxValue or MinValue defined in CommandLineOptionAttribute.", propertyInfo.Name)); continue; } if (maxValue != null) { if (memberType.Equals(decimalType) && !decimalType.Equals(maxValue)) { maxValue = Convert.ToDecimal(maxValue); } foreach (var m in list) { IComparable comparable = m as IComparable; if (comparable.CompareTo(maxValue) > 0) { builder.AppendLine(String.Format("In option {0}, this member {1} is greater than maxValue {2}.", propertyInfo.Name, m, maxValue)); } } } if (minValue != null) { if (memberType.Equals(decimalType) && !decimalType.Equals(minValue)) { minValue = Convert.ToDecimal(minValue); } foreach (var m in list) { IComparable comparable = m as IComparable; if (comparable.CompareTo(minValue) < 0) { builder.AppendLine(String.Format("In option {0}, this member {1} is less than than minValue {2}.", propertyInfo.Name, m, minValue)); } } } } else { if (!IsNumericType(propertyInfo.PropertyType)) { builder.AppendLine(String.Format("Options {0} is not of numeric type, and must not have either MaxValue or MinValue defined in CommandLineOptionAttribute.", propertyInfo.Name)); continue; } if (maxValue != null) { var m = propertyInfo.GetValue(optionsAssigned, null); IComparable comparable = m as IComparable; if (propertyInfo.PropertyType.Equals(decimalType) && !decimalType.Equals(maxValue)) { maxValue = Convert.ToDecimal(maxValue); } if (comparable.CompareTo(maxValue) > 0) { builder.AppendLine(String.Format("In option {0}, this member {1} is greater than maxValue {2}.", propertyInfo.Name, m, maxValue)); } } if (minValue != null) { var m = propertyInfo.GetValue(optionsAssigned, null); IComparable comparable = m as IComparable; if (propertyInfo.PropertyType.Equals(decimalType) && !decimalType.Equals(minValue)) { minValue = Convert.ToDecimal(minValue); } if (comparable.CompareTo(minValue) < 0) { builder.AppendLine(String.Format("In option {0}, this member {1} is less than than minValue {2}.", propertyInfo.Name, m, minValue)); } } } } #endregion return(builder.ToString()); }
/// <summary> /// Parse option text and assign property values to options accordingly. /// </summary> /// <param name="optionsText">For options only. The caller should toss all fixed parameters out.</param> /// <param name="options"></param> /// <returns>Error message</returns> public static string ReadOptions(string optionsText, object options) { if (options == null) { throw new ArgumentNullException("options"); } if (String.IsNullOrWhiteSpace(optionsText)) { return(null); } var parserResult = ArgumentParserResult.Parse(optionsText); var fixedParameters = parserResult.FixedParameters; var optionsDic = parserResult.OptionsDictionary; if (optionsDic.Count == 0) { return("Cannot find any token when parsing optionsText."); } var optionsType = options.GetType(); var propertiesOfOptions = optionsType.GetProperties(); #region Handle fixed parameters List <PropertyInfo> propertiesOfFixedParameters = new List <PropertyInfo>(); foreach (var propertyItem in propertiesOfOptions) { var orderAttribute = PropertyHelper.ReadAttribute <ParameterOrderAttribute>(propertyItem); if (orderAttribute != null) { propertiesOfFixedParameters.Add(propertyItem); } } int upperBoundOfFixedParameters = fixedParameters.Count(); int lastAssignedIndex = 0; for (int i = 0; i < propertiesOfFixedParameters.Count; i++) { var propertyInfo = propertiesOfFixedParameters[i]; if ((i < upperBoundOfFixedParameters) && (!propertyInfo.PropertyType.IsArray)) // in case fixed parameters in command line is less then those properties in the object, so the rest won't be assigned. { propertyInfo.SetValue(options, fixedParameters[i], null); lastAssignedIndex = i; } else { break; } } if (lastAssignedIndex < propertiesOfFixedParameters.Count - 1) { var lastIndex = lastAssignedIndex + 1; var propertyInfo = propertiesOfFixedParameters[lastIndex]; if (propertyInfo.PropertyType.IsArray) { var restParameters = fixedParameters.Skip(lastIndex).ToArray(); propertyInfo.SetValue(options, restParameters, null); } } #endregion StringBuilder builder = new StringBuilder(); List <string> allParameterNames = new List <string>(); foreach (var propertyItem in propertiesOfOptions) { var optionAttribute = PlossumAttributesHelper.GetCommandLineOptionAttribute(propertyItem); if (optionAttribute != null) { string[] optionValues = null; string optionName; if (String.IsNullOrWhiteSpace(optionAttribute.Name)) { optionName = propertyItem.Name; optionAttribute.Name = optionName; } else { optionName = optionAttribute.Name; } allParameterNames.Add(optionName); bool parameterNameFound = false; Action tryGetValuesArrayFromDic = () => { List <string> list = new List <string>(); string[] array; if (optionsDic.TryGetValue(optionName, out array)) { list.AddRange(array); parameterNameFound = true; } if (optionAttribute.AliasesArray != null) { allParameterNames.AddRange(optionAttribute.AliasesArray); foreach (var a in optionAttribute.AliasesArray) { if (optionsDic.TryGetValue(a, out array)) { list.AddRange(array); parameterNameFound = true; } } } optionValues = list.ToArray(); }; tryGetValuesArrayFromDic(); if (parameterNameFound) { if (propertyItem.PropertyType == typeof(bool)) //bool parameter has no explicit value defined { propertyItem.SetValue(options, true, null); if (optionValues.Length > 0) { builder.AppendLine(String.Format("Boolean option {0} should not have explicit values.", optionName)); } continue; } if (optionValues.Length == 0) { builder.AppendLine(String.Format("Option {0} expects some values but no value is found with this option.", optionAttribute.Name)); continue; } if (!propertyItem.PropertyType.IsArray) { var valueText = optionValues[0]; if (optionValues.Length > 1) { builder.Append(String.Format(String.Format("Option {0} of type {1} has more than one value assigned: {2}. However, the first value {3} is used." , optionName, propertyItem.PropertyType.ToString(), String.Join(", ", optionValues), valueText))); } if (propertyItem.PropertyType == typeof(string)) { propertyItem.SetValue(options, valueText, null); continue; } Action addError = () => { builder.AppendLine(String.Format("Option {0} of type {1} has some invalid value {2}", optionName, propertyItem.PropertyType.ToString(), valueText)); }; if (propertyItem.PropertyType == typeof(int)) { int value; if (!int.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(byte)) { byte value; if (!byte.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(sbyte)) { sbyte value; if (!sbyte.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(char)) { char value; if (!char.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(decimal)) { decimal value; if (!decimal.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(double)) { double value; if (!double.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(float)) { float value; if (!float.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(uint)) { uint value; if (!uint.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(long)) { long value; if (!long.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(ulong)) { ulong value; if (!ulong.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(short)) { short value; if (!short.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType == typeof(ushort)) { ushort value; if (!ushort.TryParse(valueText, out value)) { addError(); } else { propertyItem.SetValue(options, value, null); } continue; } if (propertyItem.PropertyType.IsEnum) { try { var converter = PropertyHelper.CreateTypeConverter(propertyItem.PropertyType); object value = converter == null?Enum.Parse(propertyItem.PropertyType, valueText, true) //starndard conversion : converter.ConvertFromString(valueText); //custom propertyItem.SetValue(options, value, null); } catch (ArgumentException e) { builder.AppendLine(String.Format("Option {0} has some invalid values {1}. {2}", optionAttribute.Name, optionValues, e.Message)); } catch (OverflowException e) { builder.AppendLine(String.Format("Option {0} has some values {1} out of range. {2}", optionAttribute.Name, optionValues, e.Message)); } continue; } } else //Handle values array of parameter { if (propertyItem.PropertyType == typeof(string[])) { propertyItem.SetValue(options, optionValues, null); continue; } //bool array haven't been seen, not supported if (HandleValueArray <int>(int.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <byte>(byte.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <sbyte>(sbyte.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <decimal>(decimal.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <double>(double.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <float>(float.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <uint>(uint.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <long>(long.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <ulong>(ulong.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <short>(short.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } if (HandleValueArray <ushort>(ushort.Parse, propertyItem, options, optionValues, optionAttribute, builder)) { continue; } } } else { continue; } } } var unknown = optionsDic.Keys.Where(d => !allParameterNames.Any(p => p.Equals(d, StringComparison.CurrentCultureIgnoreCase))).ToArray(); if (unknown.Length > 0) { builder.AppendLine("Unknown options: " + String.Join(" ", unknown)); } return(builder.ToString()); }