Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default value for option/argument (and show it in the help text) #389

12 changes: 3 additions & 9 deletions src/CommandLineUtils/Abstractions/ValueParserProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/CommandLineUtils/CommandArgument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public CommandArgument()
/// </summary>
public string? Value => Values.FirstOrDefault();

/// <summary>
/// The default value of the argument.
/// </summary>
public string? DefaultValue { get; set; }

/// <summary>
/// A collection of validators that execute before invoking <see cref="CommandLineApplication.OnExecute(Func{int})"/>.
/// When validation fails, <see cref="CommandLineApplication.ValidationErrorHandler"/> is invoked.
Expand Down
30 changes: 30 additions & 0 deletions src/CommandLineUtils/CommandArgument{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class CommandArgument<T> : CommandArgument, IInternalCommandParamOfT
{
private readonly List<T> _parsedValues = new List<T>();
private readonly IValueParser<T> _valueParser;
private T _defaultValue;

/// <summary>
/// Initializes a new instance of <see cref="CommandArgument{T}" />
Expand All @@ -29,6 +30,7 @@ public CommandArgument(IValueParser<T> valueParser)
{
_valueParser = valueParser ?? throw new ArgumentNullException(nameof(valueParser));
UnderlyingType = typeof(T);
SetBaseDefaultValue(default);
}

/// <summary>
Expand All @@ -41,6 +43,19 @@ public CommandArgument(IValueParser<T> valueParser)
/// </summary>
public IReadOnlyList<T> ParsedValues => _parsedValues;

/// <summary>
/// The default value of the argument.
/// </summary>
public new T DefaultValue
{
get => _defaultValue;
set
{
_defaultValue = value;
SetBaseDefaultValue(value);
}
}

void IInternalCommandParamOfT.Parse(CultureInfo culture)
{
_parsedValues.Clear();
Expand All @@ -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<object> enumerable)
{
base.DefaultValue = string.Join(", ", enumerable.Select(x => x?.ToString()));
}
else
{
base.DefaultValue = value?.ToString();
}
}
}
}
}
5 changes: 5 additions & 0 deletions src/CommandLineUtils/CommandOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ internal CommandOption(CommandOptionType type)
/// </summary>
public List<string?> Values { get; } = new List<string?>();

/// <summary>
/// The default value of the option.
/// </summary>
public string? DefaultValue { get; set; }

/// <summary>
/// Defines the type of the option.
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions src/CommandLineUtils/CommandOption{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,6 +20,7 @@ public class CommandOption<T> : CommandOption, IInternalCommandParamOfT
{
private readonly List<T> _parsedValues = new List<T>();
private readonly IValueParser<T> _valueParser;
private T _defaultValue;

/// <summary>
/// Initializes a new instance of <see cref="CommandOption{T}" />
Expand All @@ -31,6 +33,7 @@ public CommandOption(IValueParser<T> valueParser, string template, CommandOption
{
_valueParser = valueParser ?? throw new ArgumentNullException(nameof(valueParser));
UnderlyingType = typeof(T);
SetBaseDefaultValue(default);
}

/// <summary>
Expand All @@ -43,6 +46,19 @@ public CommandOption(IValueParser<T> valueParser, string template, CommandOption
/// </summary>
public IReadOnlyList<T> ParsedValues => _parsedValues;

/// <summary>
/// The default value of the option.
/// </summary>
public new T DefaultValue
{
get => _defaultValue;
set
{
_defaultValue = value;
SetBaseDefaultValue(value);
}
}

void IInternalCommandParamOfT.Parse(CultureInfo culture)
{
_parsedValues.Clear();
Expand All @@ -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<object> enumerable)
{
base.DefaultValue = string.Join(", ", enumerable.Select(x => x?.ToString()));
}
else
{
base.DefaultValue = value?.ToString();
}
}
}
}
}
54 changes: 37 additions & 17 deletions src/CommandLineUtils/Conventions/ArgumentAttributeConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public virtual void Apply(ConventionContext context)
argPropOrder.Add(argumentAttr.Order, prop);
argOrder.Add(argumentAttr.Order, argument);

var getter = ReflectionHelper.GetPropertyGetter(prop);
var setter = ReflectionHelper.GetPropertySetter(prop);

if (argument.MultipleValues)
Expand All @@ -109,14 +110,23 @@ public virtual void Apply(ConventionContext context)

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<object> 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));
}
}
});
}
Expand All @@ -130,19 +140,29 @@ public virtual void Apply(ConventionContext context)

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));
}
}
});
}
Expand Down
33 changes: 29 additions & 4 deletions src/CommandLineUtils/Conventions/OptionAttributeConventionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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<object> 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:
Expand All @@ -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:
Expand Down
14 changes: 12 additions & 2 deletions src/CommandLineUtils/HelpText/DefaultHelpTextGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ public virtual void Generate(CommandLineApplication application, TextWriter outp
}
}

if (!string.IsNullOrEmpty(arg.DefaultValue))
{
description += $"\nDefault value is: {arg.DefaultValue}.";
}

var wrappedDescription = IndentWriter?.Write(description);
var message = string.Format(outputFormat, arg.Name, wrappedDescription);

Expand Down Expand Up @@ -266,6 +271,11 @@ public virtual void Generate(CommandLineApplication application, TextWriter outp
}
}

if (!string.IsNullOrEmpty(opt.DefaultValue))
{
description += $"\nDefault value is: {opt.DefaultValue}.";
}

var wrappedDescription = IndentWriter?.Write(description);
var message = string.Format(outputFormat, Format(opt), wrappedDescription);

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLineUtils/Internal/CommandLineProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down