diff --git a/src/CommandLineUtils/Abstractions/ValueParserProvider.cs b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs index 1f54528d..2090a924 100644 --- a/src/CommandLineUtils/Abstractions/ValueParserProvider.cs +++ b/src/CommandLineUtils/Abstractions/ValueParserProvider.cs @@ -101,7 +101,7 @@ public IValueParser GetParser(Type type) return parser; } - if (ReflectionHelper.IsNullableType(type, out var wrappedType) && wrappedType != null) + if (ReflectionHelper.IsNullableType(type, out var wrappedType)) { if (wrappedType.IsEnum) { @@ -114,15 +114,9 @@ public IValueParser GetParser(Type type) } } - if (!type.IsGenericType) + if (ReflectionHelper.IsSpecialValueTupleType(type, out var wrappedType2)) { - return null; - } - - var typeDef = type.GetGenericTypeDefinition(); - if (typeDef == typeof(ValueTuple<,>) && type.GenericTypeArguments[0] == typeof(bool)) - { - var innerParser = GetParser(type.GenericTypeArguments[1]); + var innerParser = GetParser(wrappedType2); if (innerParser == null) { return null; diff --git a/src/CommandLineUtils/CommandArgument.cs b/src/CommandLineUtils/CommandArgument.cs index e218de2f..7b2e117a 100644 --- a/src/CommandLineUtils/CommandArgument.cs +++ b/src/CommandLineUtils/CommandArgument.cs @@ -54,6 +54,11 @@ public CommandArgument() /// public string? Value => Values.FirstOrDefault(); + /// + /// The default value of the argument. + /// + public string? DefaultValue { get; set; } + /// /// A collection of validators that execute before invoking . /// When validation fails, is invoked. diff --git a/src/CommandLineUtils/CommandArgument{T}.cs b/src/CommandLineUtils/CommandArgument{T}.cs index f98e0885..bc99a216 100644 --- a/src/CommandLineUtils/CommandArgument{T}.cs +++ b/src/CommandLineUtils/CommandArgument{T}.cs @@ -20,6 +20,7 @@ public class CommandArgument : CommandArgument, IInternalCommandParamOfT { private readonly List _parsedValues = new List(); private readonly IValueParser _valueParser; + private T _defaultValue; /// /// Initializes a new instance of @@ -29,6 +30,7 @@ public CommandArgument(IValueParser valueParser) { _valueParser = valueParser ?? throw new ArgumentNullException(nameof(valueParser)); UnderlyingType = typeof(T); + SetBaseDefaultValue(default); } /// @@ -41,6 +43,19 @@ public CommandArgument(IValueParser valueParser) /// public IReadOnlyList ParsedValues => _parsedValues; + /// + /// The default value of the argument. + /// + public new T DefaultValue + { + get => _defaultValue; + set + { + _defaultValue = value; + SetBaseDefaultValue(value); + } + } + void IInternalCommandParamOfT.Parse(CultureInfo culture) { _parsedValues.Clear(); @@ -49,5 +64,20 @@ void IInternalCommandParamOfT.Parse(CultureInfo culture) _parsedValues.Add(_valueParser.Parse(Name, t, culture)); } } + + void SetBaseDefaultValue(T value) + { + if (!ReflectionHelper.IsSpecialValueTupleType(typeof(T), out _)) + { + if (MultipleValues && value is IEnumerable enumerable) + { + base.DefaultValue = string.Join(", ", enumerable.Select(x => x?.ToString())); + } + else + { + base.DefaultValue = value?.ToString(); + } + } + } } } diff --git a/src/CommandLineUtils/CommandOption.cs b/src/CommandLineUtils/CommandOption.cs index 1a0c2bb7..5cf3ae88 100644 --- a/src/CommandLineUtils/CommandOption.cs +++ b/src/CommandLineUtils/CommandOption.cs @@ -101,6 +101,11 @@ internal CommandOption(CommandOptionType type) /// public List Values { get; } = new List(); + /// + /// The default value of the option. + /// + public string? DefaultValue { get; set; } + /// /// Defines the type of the option. /// diff --git a/src/CommandLineUtils/CommandOption{T}.cs b/src/CommandLineUtils/CommandOption{T}.cs index c52b0635..f3dcbe7a 100644 --- a/src/CommandLineUtils/CommandOption{T}.cs +++ b/src/CommandLineUtils/CommandOption{T}.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using McMaster.Extensions.CommandLineUtils.Abstractions; namespace McMaster.Extensions.CommandLineUtils @@ -19,6 +20,7 @@ public class CommandOption : CommandOption, IInternalCommandParamOfT { private readonly List _parsedValues = new List(); private readonly IValueParser _valueParser; + private T _defaultValue; /// /// Initializes a new instance of @@ -31,6 +33,7 @@ public CommandOption(IValueParser valueParser, string template, CommandOption { _valueParser = valueParser ?? throw new ArgumentNullException(nameof(valueParser)); UnderlyingType = typeof(T); + SetBaseDefaultValue(default); } /// @@ -43,6 +46,19 @@ public CommandOption(IValueParser valueParser, string template, CommandOption /// public IReadOnlyList ParsedValues => _parsedValues; + /// + /// The default value of the option. + /// + public new T DefaultValue + { + get => _defaultValue; + set + { + _defaultValue = value; + SetBaseDefaultValue(value); + } + } + void IInternalCommandParamOfT.Parse(CultureInfo culture) { _parsedValues.Clear(); @@ -51,5 +67,20 @@ void IInternalCommandParamOfT.Parse(CultureInfo culture) _parsedValues.Add(_valueParser.Parse(LongName ?? ShortName ?? SymbolName, t, culture)); } } + + void SetBaseDefaultValue(T value) + { + if (!ReflectionHelper.IsSpecialValueTupleType(typeof(T), out _)) + { + if (OptionType == CommandOptionType.MultipleValue && value is IEnumerable enumerable) + { + base.DefaultValue = string.Join(", ", enumerable.Select(x => x?.ToString())); + } + else + { + base.DefaultValue = value?.ToString(); + } + } + } } } diff --git a/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs b/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs index 132e2455..85adc70d 100644 --- a/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs +++ b/src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs @@ -95,6 +95,7 @@ private void AddArgument(PropertyInfo prop, argPropOrder.Add(argumentAttr.Order, prop); argOrder.Add(argumentAttr.Order, argument); + var getter = ReflectionHelper.GetPropertyGetter(prop); var setter = ReflectionHelper.GetPropertySetter(prop); if (argument.MultipleValues) @@ -109,14 +110,23 @@ private void AddArgument(PropertyInfo prop, convention.Application.OnParsingComplete(r => { - if (argument.Values.Count == 0) - { - return; - } - if (r.SelectedCommand is IModelAccessor cmd) { - setter.Invoke(cmd.GetModel(), collectionParser.Parse(argument.Name, argument.Values)); + if (argument.Values.Count == 0) + { + if (!ReflectionHelper.IsSpecialValueTupleType(prop.PropertyType, out _)) + { + if (getter.Invoke(cmd.GetModel()) is IEnumerable value) + { + argument.Values.AddRange(value.Select(x => x?.ToString())); + argument.DefaultValue = string.Join(", ", value.Select(x => x?.ToString())); + } + } + } + else + { + setter.Invoke(cmd.GetModel(), collectionParser.Parse(argument.Name, argument.Values)); + } } }); } @@ -130,19 +140,29 @@ private void AddArgument(PropertyInfo prop, convention.Application.OnParsingComplete(r => { - if (argument.Values.Count == 0) - { - return; - } - if (r.SelectedCommand is IModelAccessor cmd) { - setter.Invoke( - cmd.GetModel(), - parser.Parse( - argument.Name, - argument.Value, - convention.Application.ValueParsers.ParseCulture)); + if (argument.Values.Count == 0) + { + if (!ReflectionHelper.IsSpecialValueTupleType(prop.PropertyType, out _)) + { + var value = getter.Invoke(cmd.GetModel()); + if (value != null) + { + argument.Values.Add(value.ToString()); + argument.DefaultValue = value.ToString(); + } + } + } + else + { + setter.Invoke( + cmd.GetModel(), + parser.Parse( + argument.Name, + argument.Value, + convention.Application.ValueParsers.ParseCulture)); + } } }); } diff --git a/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs b/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs index db72a6f0..115b9443 100644 --- a/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs +++ b/src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; @@ -57,6 +59,7 @@ private protected void AddOption(ConventionContext context, CommandOption option context.Application._longOptions.Add(option.LongName, prop); } + var getter = ReflectionHelper.GetPropertyGetter(prop); var setter = ReflectionHelper.GetPropertySetter(prop); switch (option.OptionType) @@ -71,9 +74,19 @@ private protected void AddOption(ConventionContext context, CommandOption option { if (!option.HasValue()) { - return; + if (!ReflectionHelper.IsSpecialValueTupleType(prop.PropertyType, out var type)) + { + if (getter.Invoke(modelAccessor.GetModel()) is IEnumerable value) + { + option.Values.AddRange(value.Select(x => x?.ToString())); + option.DefaultValue = string.Join(", ", value.Select(x => x?.ToString())); + } + } + } + else + { + setter.Invoke(modelAccessor.GetModel(), collectionParser.Parse(option.LongName, option.Values)); } - setter.Invoke(modelAccessor.GetModel(), collectionParser.Parse(option.LongName, option.Values)); }); break; case CommandOptionType.SingleOrNoValue: @@ -87,9 +100,21 @@ private protected void AddOption(ConventionContext context, CommandOption option { if (!option.HasValue()) { - return; + if (!ReflectionHelper.IsSpecialValueTupleType(prop.PropertyType, out var type)) + { + var value = getter.Invoke(modelAccessor.GetModel()); + + if (value != null) + { + option.Values.Add(value.ToString()); + option.DefaultValue = value.ToString(); + } + } + } + else + { + setter.Invoke(modelAccessor.GetModel(), parser.Parse(option.LongName, option.Value(), context.Application.ValueParsers.ParseCulture)); } - setter.Invoke(modelAccessor.GetModel(), parser.Parse(option.LongName, option.Value(), context.Application.ValueParsers.ParseCulture)); }); break; case CommandOptionType.NoValue: diff --git a/src/CommandLineUtils/HelpText/DefaultHelpTextGenerator.cs b/src/CommandLineUtils/HelpText/DefaultHelpTextGenerator.cs index aa8cc1e7..d25ec4fa 100644 --- a/src/CommandLineUtils/HelpText/DefaultHelpTextGenerator.cs +++ b/src/CommandLineUtils/HelpText/DefaultHelpTextGenerator.cs @@ -214,6 +214,11 @@ protected virtual void GenerateArguments( } } + if (!string.IsNullOrEmpty(arg.DefaultValue)) + { + description += $"\nDefault value is: {arg.DefaultValue}."; + } + var wrappedDescription = IndentWriter?.Write(description); var message = string.Format(outputFormat, arg.Name, wrappedDescription); @@ -266,6 +271,11 @@ protected virtual void GenerateOptions( } } + if (!string.IsNullOrEmpty(opt.DefaultValue)) + { + description += $"\nDefault value is: {opt.DefaultValue}."; + } + var wrappedDescription = IndentWriter?.Write(description); var message = string.Format(outputFormat, Format(opt), wrappedDescription); @@ -412,9 +422,9 @@ private string[] ExtractNamesFromEnum(Type type) return ExtractNamesFromEnum(wrappedType); } - if (ReflectionHelper.IsSpecialValueTupleType(type, out var fieldInfo)) + if (ReflectionHelper.IsSpecialValueTupleType(type, out var wrappedType2)) { - return ExtractNamesFromEnum(fieldInfo.FieldType); + return ExtractNamesFromEnum(wrappedType2); } if (type.IsEnum) diff --git a/src/CommandLineUtils/Internal/CommandLineProcessor.cs b/src/CommandLineUtils/Internal/CommandLineProcessor.cs index 7a19722f..ef4885de 100644 --- a/src/CommandLineUtils/Internal/CommandLineProcessor.cs +++ b/src/CommandLineUtils/Internal/CommandLineProcessor.cs @@ -216,7 +216,7 @@ private bool ProcessOption(OptionArgument arg) // If we find a help/version option, show information and stop parsing if (_currentCommand.OptionHelp == option) { - _currentCommand.ShowHelp(); + _currentCommand.OnParsingComplete(_ => _currentCommand.ShowHelp()); option.TryParse(null); return false; } diff --git a/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs b/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs index 0f98fcd6..ecf13692 100644 --- a/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs +++ b/src/CommandLineUtils/Internal/CommandOptionTypeMapper.cs @@ -55,28 +55,24 @@ public CommandOptionType GetOptionType(Type clrType, ValueParserProvider? valueP return CommandOptionType.SingleValue; } - if (clrType.IsGenericType) + if (ReflectionHelper.IsNullableType(clrType, out var wrappedType)) { - var typeDef = clrType.GetGenericTypeDefinition(); - if (typeDef == typeof(Nullable<>)) - { - return GetOptionType(clrType.GetGenericArguments().First(), valueParsers); - } + return GetOptionType(wrappedType, valueParsers); + } - if (typeDef == typeof(Tuple<,>) && clrType.GenericTypeArguments[0] == typeof(bool)) + if (ReflectionHelper.IsSpecialValueTupleType(clrType, out var wrappedType2)) + { + if (GetOptionType(wrappedType2, valueParsers) == CommandOptionType.SingleValue) { - if (GetOptionType(clrType.GenericTypeArguments[1], valueParsers) == CommandOptionType.SingleValue) - { - return CommandOptionType.SingleOrNoValue; - } + return CommandOptionType.SingleOrNoValue; } + } - if (typeDef == typeof(ValueTuple<,>) && clrType.GenericTypeArguments[0] == typeof(bool)) + if (ReflectionHelper.IsSpecialTupleType(clrType, out var wrappedType3)) + { + if (GetOptionType(wrappedType3, valueParsers) == CommandOptionType.SingleValue) { - if (GetOptionType(clrType.GenericTypeArguments[1], valueParsers) == CommandOptionType.SingleValue) - { - return CommandOptionType.SingleOrNoValue; - } + return CommandOptionType.SingleOrNoValue; } } diff --git a/src/CommandLineUtils/Internal/Delegates.cs b/src/CommandLineUtils/Internal/Delegates.cs index c9d9de13..e9471010 100644 --- a/src/CommandLineUtils/Internal/Delegates.cs +++ b/src/CommandLineUtils/Internal/Delegates.cs @@ -4,4 +4,6 @@ namespace McMaster.Extensions.CommandLineUtils { internal delegate void SetPropertyDelegate(object obj, object? value); + + internal delegate object GetPropertyDelegate(object obj); } diff --git a/src/CommandLineUtils/Internal/ReflectionHelper.cs b/src/CommandLineUtils/Internal/ReflectionHelper.cs index 35e55b05..e22c795d 100644 --- a/src/CommandLineUtils/Internal/ReflectionHelper.cs +++ b/src/CommandLineUtils/Internal/ReflectionHelper.cs @@ -35,6 +35,26 @@ public static SetPropertyDelegate GetPropertySetter(PropertyInfo prop) } } + public static GetPropertyDelegate GetPropertyGetter(PropertyInfo prop) + { + var getter = prop.GetGetMethod(nonPublic: true); + if (getter != null) + { + return obj => getter.Invoke(obj, new object[] { }); + } + else + { + var backingField = prop.DeclaringType.GetField($"<{prop.Name}>k__BackingField", DeclaredOnlyLookup); + if (backingField == null) + { + throw new InvalidOperationException( + $"Could not find a way to get {prop.DeclaringType.FullName}.{prop.Name}. Try adding a getter."); + } + + return obj => backingField.GetValue(obj); + } + } + public static MethodInfo[] GetPropertyOrMethod(Type type, string name) { var members = GetAllMembers(type).ToList(); @@ -105,12 +125,22 @@ public static bool IsNullableType(Type type, out Type? wrappedType) return result; } - public static bool IsSpecialValueTupleType(Type type, out FieldInfo fieldInfo) + public static bool IsSpecialValueTupleType(Type type, out Type? wrappedType) { var result = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTuple<,>) && type.GenericTypeArguments[0] == typeof(bool); - fieldInfo = result ? type.GetFields()[1] : null; + wrappedType = result ? type.GenericTypeArguments[1] : null; + + return result; + } + + public static bool IsSpecialTupleType(Type type, out Type? wrappedType) + { + var result = type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(Tuple<,>) && + type.GenericTypeArguments[0] == typeof(bool); + wrappedType = result ? type.GenericTypeArguments[1] : null; return result; } diff --git a/src/CommandLineUtils/PublicAPI.Shipped.txt b/src/CommandLineUtils/PublicAPI.Shipped.txt index 56994b24..ef6a793b 100644 --- a/src/CommandLineUtils/PublicAPI.Shipped.txt +++ b/src/CommandLineUtils/PublicAPI.Shipped.txt @@ -53,6 +53,8 @@ McMaster.Extensions.CommandLineUtils.ArgumentAttribute.ShowInHelpText.set -> voi McMaster.Extensions.CommandLineUtils.ArgumentEscaper McMaster.Extensions.CommandLineUtils.CommandArgument McMaster.Extensions.CommandLineUtils.CommandArgument.CommandArgument() -> void +McMaster.Extensions.CommandLineUtils.CommandArgument.DefaultValue.get -> string? +McMaster.Extensions.CommandLineUtils.CommandArgument.DefaultValue.set -> void McMaster.Extensions.CommandLineUtils.CommandArgument.Description.get -> string? McMaster.Extensions.CommandLineUtils.CommandArgument.Description.set -> void McMaster.Extensions.CommandLineUtils.CommandArgument.MultipleValues.get -> bool @@ -66,6 +68,8 @@ McMaster.Extensions.CommandLineUtils.CommandArgument.Value.get -> string? McMaster.Extensions.CommandLineUtils.CommandArgument.Values.get -> System.Collections.Generic.List! McMaster.Extensions.CommandLineUtils.CommandArgument McMaster.Extensions.CommandLineUtils.CommandArgument.CommandArgument(McMaster.Extensions.CommandLineUtils.Abstractions.IValueParser! valueParser) -> void +McMaster.Extensions.CommandLineUtils.CommandArgument.DefaultValue.get -> T +McMaster.Extensions.CommandLineUtils.CommandArgument.DefaultValue.set -> void McMaster.Extensions.CommandLineUtils.CommandArgument.ParsedValue.get -> T McMaster.Extensions.CommandLineUtils.CommandArgument.ParsedValues.get -> System.Collections.Generic.IReadOnlyList! McMaster.Extensions.CommandLineUtils.CommandAttribute @@ -194,6 +198,8 @@ McMaster.Extensions.CommandLineUtils.CommandLineApplication.ModelFactory McMaster.Extensions.CommandLineUtils.CommandLineApplicationExtensions McMaster.Extensions.CommandLineUtils.CommandOption McMaster.Extensions.CommandLineUtils.CommandOption.CommandOption(string! template, McMaster.Extensions.CommandLineUtils.CommandOptionType optionType) -> void +McMaster.Extensions.CommandLineUtils.CommandOption.DefaultValue.get -> string? +McMaster.Extensions.CommandLineUtils.CommandOption.DefaultValue.set -> void McMaster.Extensions.CommandLineUtils.CommandOption.Description.get -> string? McMaster.Extensions.CommandLineUtils.CommandOption.Description.set -> void McMaster.Extensions.CommandLineUtils.CommandOption.HasValue() -> bool @@ -216,6 +222,8 @@ McMaster.Extensions.CommandLineUtils.CommandOption.ValueName.set -> void McMaster.Extensions.CommandLineUtils.CommandOption.Values.get -> System.Collections.Generic.List! McMaster.Extensions.CommandLineUtils.CommandOption McMaster.Extensions.CommandLineUtils.CommandOption.CommandOption(McMaster.Extensions.CommandLineUtils.Abstractions.IValueParser! valueParser, string! template, McMaster.Extensions.CommandLineUtils.CommandOptionType optionType) -> void +McMaster.Extensions.CommandLineUtils.CommandOption.DefaultValue.get -> T +McMaster.Extensions.CommandLineUtils.CommandOption.DefaultValue.set -> void McMaster.Extensions.CommandLineUtils.CommandOption.ParsedValue.get -> T McMaster.Extensions.CommandLineUtils.CommandOption.ParsedValues.get -> System.Collections.Generic.IReadOnlyList! McMaster.Extensions.CommandLineUtils.CommandOptionType diff --git a/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs b/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs index 1a0c3df2..a641c8e1 100644 --- a/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs +++ b/test/CommandLineUtils.Tests/DefaultHelpTextGeneratorTests.cs @@ -32,6 +32,7 @@ private class EmptyShortName public void ItFormatsOptions() { var app = new CommandLineApplication(); + app.Conventions.UseDefaultHelpOption(); var option = app.Option("-a|--all ", "All", CommandOptionType.SingleValue); option.ShortName = "b"; var helpText = GetHelpText(app); @@ -80,25 +81,25 @@ public void DoesNotOrderCommandsByName() Assert.True(indexOfA > indexOfB); } - private string GetHelpText(CommandLineApplication app, DefaultHelpTextGenerator generator) + private string GetHelpText(CommandLineApplication app, DefaultHelpTextGenerator generator, string helpOption = null) { var sb = new StringBuilder(); - generator.Generate(app, new StringWriter(sb)); + app.Out = new StringWriter(sb); + app.HelpTextGenerator = generator; + app.Parse(helpOption ?? "-h"); var helpText = sb.ToString(); - _output.WriteLine(helpText); - return helpText; } - private string GetHelpText(CommandLineApplication app) + private string GetHelpText(CommandLineApplication app, string helpOption = null) { var generator = new DefaultHelpTextGenerator { MaxLineLength = 80 }; - return GetHelpText(app, generator); + return GetHelpText(app, generator, helpOption); } public enum SomeEnum { None, Normal, Extreme } @@ -110,6 +111,7 @@ public void ShowHelp() app.HelpOption(); app.Option("--strOpt ", "str option desc.", CommandOptionType.SingleValue); app.Option("--rStrOpt ", "restricted str option desc.", CommandOptionType.SingleValue, o => o.IsRequired().Accepts().Values("Foo", "Bar")); + app.Option("--dStrOpt ", "str option with default value desc.", CommandOptionType.SingleValue, o => o.DefaultValue = "Foo"); app.Option("--intOpt ", "int option desc.", CommandOptionType.SingleValue); app.Option("--enumOpt ", "enum option desc.", CommandOptionType.SingleValue); app.Option("--enumOpt2 ", "restricted enum option desc.", CommandOptionType.SingleValue, o => o.Accepts().Values("None", "Normal")); @@ -117,21 +119,26 @@ public void ShowHelp() app.Option("--enumOpt4 ", "nullable enum option desc.", CommandOptionType.SingleOrNoValue); app.Argument("SomeStringArgument", "string arg desc."); app.Argument("RestrictedStringArgument", "restricted string arg desc.", a => a.IsRequired().Accepts().Values("Foo", "Bar")); + app.Argument("DefaultValStringArgument", "string arg with default value desc.", a => a.DefaultValue = "Foo"); app.Argument("SomeEnumArgument", "enum arg desc."); app.Argument("RestrictedEnumArgument", "restricted enum arg desc.", a => a.Accepts().Values("None", "Normal")); app.Argument<(bool, SomeEnum)>("SomeNullableEnumArgument", "nullable enum arg desc."); var helpText = GetHelpText(app); - Assert.Equal(@"Usage: [options] + Assert.Equal(@"Usage: [options] Arguments: SomeStringArgument string arg desc. RestrictedStringArgument restricted string arg desc. Allowed values are: Foo, Bar. + DefaultValStringArgument string arg with default value desc. + Default value is: Foo. SomeEnumArgument enum arg desc. Allowed values are: None, Normal, Extreme. + Default value is: None. RestrictedEnumArgument restricted enum arg desc. Allowed values are: None, Normal. + Default value is: None. SomeNullableEnumArgument nullable enum arg desc. Allowed values are: None, Normal, Extreme. @@ -140,11 +147,16 @@ SomeNullableEnumArgument nullable enum arg desc. --strOpt str option desc. --rStrOpt restricted str option desc. Allowed values are: Foo, Bar. + --dStrOpt str option with default value desc. + Default value is: Foo. --intOpt int option desc. + Default value is: 0. --enumOpt enum option desc. Allowed values are: None, Normal, Extreme. + Default value is: None. --enumOpt2 restricted enum option desc. Allowed values are: None, Normal. + Default value is: None. --enumOpt3[:] nullable enum option desc. Allowed values are: None, Normal, Extreme. --enumOpt4[:] nullable enum option desc. @@ -163,28 +175,39 @@ public void ShowHelpFromAttributes() app.Conventions.UseDefaultConventions(); var helpText = GetHelpText(app); - Assert.Equal(@"Usage: test [options] + Assert.Equal(@"Usage: test [options] Arguments: SomeStringArgument string arg desc. RestrictedStringArgument restricted string arg desc. Allowed values are: Foo, Bar. + DefaultValStringArgument string arg with default value desc. + Default value is: Foo. SomeEnumArgument enum arg desc. Allowed values are: None, Normal, Extreme. + Default value is: None. RestrictedEnumArgument restricted enum arg desc. Allowed values are: None, Normal. + Default value is: None. SomeNullableEnumArgument nullable enum arg desc. Allowed values are: None, Normal, Extreme. Options: -strOpt|--str-opt str option desc. - -rStrOpt|--r-str-opt restricted str option desc. + -rStrOpt|--r-str-opt restricted str option desc. Allowed values are: Foo, Bar. + -dStrOpt|--d-str-opt str option with default value desc. + Default value is: Foo. + -dStrOpt2|--d-str-opt2 str array option with default value desc. + Default value is: Foo, Bar. -intOpt|--int-opt int option desc. + Default value is: 0. -enumOpt|--verbosity enum option desc. Allowed values are: None, Normal, Extreme. + Default value is: None. -enumOpt2|--verb2 restricted enum option desc. Allowed values are: None, Normal. + Default value is: None. -enumOpt3|--verb3[:] nullable enum option desc. Allowed values are: None, Normal, Extreme. -enumOpt4|--verb4[:] nullable enum option desc. @@ -198,14 +221,20 @@ SomeNullableEnumArgument nullable enum arg desc. public class MyApp { - [Option(ShortName = "strOpt", Description = "str option desc.")] + [Option(ShortName = "strOpt", ValueName = "STR_OPT", Description = "str option desc.")] public string strOpt { get; set; } - [Option(ShortName = "rStrOpt", Description = "restricted str option desc.")] + [Option(ShortName = "rStrOpt", ValueName = "STR_OPT", Description = "restricted str option desc.")] [Required] [AllowedValues("Foo", "Bar")] public string rStrOpt { get; set; } + [Option(ShortName = "dStrOpt", ValueName = "STR_OPT", Description = "str option with default value desc.")] + public string dStrOpt { get; set; } = "Foo"; + + [Option(ShortName = "dStrOpt2", ValueName = "STR_OPT", Description = "str array option with default value desc.")] + public string[] dStrOpt2 { get; set; } = new[] { "Foo", "Bar" }; + [Option(ShortName = "intOpt", Description = "int option desc.")] public int intOpt { get; set; } @@ -230,14 +259,17 @@ public class MyApp [AllowedValues("Foo", "Bar")] public string RestrictedStringArgument { get; set; } - [Argument(2, Description = "enum arg desc.")] + [Argument(2, Description = "string arg with default value desc.")] + public string DefaultValStringArgument { get; set; } = "Foo"; + + [Argument(3, Description = "enum arg desc.")] public SomeEnum SomeEnumArgument { get; set; } - [Argument(3, Description = "restricted enum arg desc.")] + [Argument(4, Description = "restricted enum arg desc.")] [AllowedValues("None", "Normal")] public SomeEnum RestrictedEnumArgument { get; set; } - [Argument(4, Description = "nullable enum arg desc.")] + [Argument(5, Description = "nullable enum arg desc.")] public (bool HasValue, SomeEnum Value) SomeNullableEnumArgument { get; set; } } @@ -253,7 +285,7 @@ public void ShowHelpWithSubcommands(string helpOption, string expectedHintText, if (helpOption != null) app.HelpOption(helpOption); app.Command("Subcommand", _ => { }); app.Conventions.UseDefaultConventions(); - var helpText = GetHelpText(app); + var helpText = GetHelpText(app, helpOption); Assert.Equal($@"Usage: test [command] [options]