Skip to content

Commit

Permalink
feature: add default value API for option/argument (and show it in th…
Browse files Browse the repository at this point in the history
…e help text) (#389)
  • Loading branch information
scott-xu committed Nov 7, 2020
1 parent 4b13f2e commit ea5922d
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 66 deletions.
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

0 comments on commit ea5922d

Please sign in to comment.