Skip to content

First name and aliases cleanup #1990

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

Merged
merged 7 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ System.CommandLine
public interface IConsole : System.CommandLine.IO.IStandardError, System.CommandLine.IO.IStandardIn, System.CommandLine.IO.IStandardOut
public abstract class IdentifierSymbol : Symbol
public System.Collections.Generic.IReadOnlyCollection<System.String> Aliases { get; }
public System.String Name { get; set; }
public System.Void AddAlias(System.String alias)
public System.Boolean HasAlias(System.String alias)
public class LocalizationResources
Expand Down Expand Up @@ -196,7 +195,6 @@ System.CommandLine
public System.Boolean IsRequired { get; set; }
public System.Type ValueType { get; }
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
public System.Boolean HasAliasIgnoringPrefix(System.String alias)
public ParseResult Parse(System.String commandLine)
public ParseResult Parse(System.String[] args)
public class Option<T> : Option, IValueDescriptor<T>, System.CommandLine.Binding.IValueDescriptor
Expand Down
36 changes: 35 additions & 1 deletion src/System.CommandLine.DragonFruit/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public static CommandLineBuilder ConfigureHelpFromXmlComments(
{
var kebabCasedParameterName = parameterDescription.Key.ToKebabCase();

var option = builder.Command.Options.FirstOrDefault(o => o.HasAliasIgnoringPrefix(kebabCasedParameterName));
var option = builder.Command.Options.FirstOrDefault(o => HasAliasIgnoringPrefix(o, kebabCasedParameterName));

if (option != null)
{
Expand Down Expand Up @@ -300,5 +300,39 @@ private static string GetDefaultXmlDocsFileLocation(Assembly assembly)

return string.Empty;
}

/// <summary>
/// Indicates whether a given alias exists on the option, regardless of its prefix.
/// </summary>
/// <param name="alias">The alias, which can include a prefix.</param>
/// <returns><see langword="true"/> if the alias exists; otherwise, <see langword="false"/>.</returns>
private static bool HasAliasIgnoringPrefix(Option option, string alias)
{
ReadOnlySpan<char> rawAlias = alias.AsSpan(GetPrefixLength(alias));

foreach (string existingAlias in option.Aliases)
{
if (MemoryExtensions.Equals(existingAlias.AsSpan(GetPrefixLength(existingAlias)), rawAlias, StringComparison.CurrentCulture))
{
return true;
}
}

return false;

static int GetPrefixLength(string alias)
{
if (alias[0] == '-')
{
return alias.Length > 1 && alias[1] == '-' ? 2 : 1;
}
else if (alias[0] == '/')
{
return 1;
}

return 0;
}
}
}
}
17 changes: 0 additions & 17 deletions src/System.CommandLine.Tests/OptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public void A_prefixed_alias_can_be_added_to_an_option()

option.AddAlias("-a");

option.HasAliasIgnoringPrefix("a").Should().BeTrue();
option.HasAlias("-a").Should().BeTrue();
}

Expand All @@ -84,14 +83,6 @@ public void HasAlias_accepts_prefixed_short_value()
option.HasAlias("-o").Should().BeTrue();
}

[Fact]
public void HasAliasIgnorePrefix_accepts_unprefixed_short_value()
{
var option = new Option<string>(new[] { "-o", "--option" });

option.HasAliasIgnoringPrefix("o").Should().BeTrue();
}

[Fact]
public void HasAlias_accepts_prefixed_long_value()
{
Expand All @@ -100,14 +91,6 @@ public void HasAlias_accepts_prefixed_long_value()
option.HasAlias("--option").Should().BeTrue();
}

[Fact]
public void HasAliasIgnorePrefix_accepts_unprefixed_long_value()
{
var option = new Option<string>(new[] { "-o", "--option" });

option.HasAliasIgnoringPrefix("option").Should().BeTrue();
}

[Fact]
public void It_is_not_necessary_to_specify_a_prefix_when_adding_an_option()
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.CommandLine/Binding/ArgumentConversionResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static string FormatErrorMessage(
if (argument.FirstParent?.Symbol is IdentifierSymbol identifierSymbol &&
argument.FirstParent.Next is null)
{
var alias = identifierSymbol.Aliases.First();
var alias = identifierSymbol.GetLongestAlias(removePrefix: false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't really getting the longest alias but the first one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was inconsistent. As you can see other places in the source code were using longest option alias to get its default alias. Here we were using the first one but it was a bug (at least in my opinion).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this just use the name rather than an alias?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option.Name should not contain the prefix, while here we care about prefix.


switch (identifierSymbol)
{
Expand Down
28 changes: 20 additions & 8 deletions src/System.CommandLine/IdentifierSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.CommandLine.Parsing;
using System.Diagnostics;

namespace System.CommandLine
Expand All @@ -11,8 +12,7 @@ namespace System.CommandLine
/// </summary>
public abstract class IdentifierSymbol : Symbol
{
private protected readonly HashSet<string> _aliases = new(StringComparer.Ordinal);
private string? _specifiedName;
private readonly HashSet<string> _aliases = new(StringComparer.Ordinal);

/// <summary>
/// Initializes a new instance of the <see cref="IdentifierSymbol"/> class.
Expand All @@ -30,7 +30,7 @@ protected IdentifierSymbol(string? description = null)
/// <param name="description">The description of the symbol, which is displayed in command line help.</param>
protected IdentifierSymbol(string name, string? description = null)
{
Name = name;
Name = name ?? throw new ArgumentNullException(nameof(name));
Description = description;
}

Expand All @@ -42,19 +42,18 @@ protected IdentifierSymbol(string name, string? description = null)
/// <inheritdoc/>
public override string Name
{
get => _specifiedName ??= DefaultName;
set
{
if (_specifiedName is null || !string.Equals(_specifiedName, value, StringComparison.Ordinal))
if (_name is null || !string.Equals(_name, value, StringComparison.Ordinal))
{
AddAlias(value);

if (_specifiedName is { })
if (_name != null)
{
RemoveAlias(_specifiedName);
RemoveAlias(_name);
}

_specifiedName = value;
_name = value;
}
}
}
Expand Down Expand Up @@ -82,6 +81,19 @@ public void AddAlias(string alias)
/// <returns><see langword="true" /> if the alias has already been defined; otherwise <see langword="false" />.</returns>
public bool HasAlias(string alias) => _aliases.Contains(alias);

internal string GetLongestAlias(bool removePrefix)
{
string max = "";
foreach (string alias in _aliases)
{
if (alias.Length > max.Length)
{
max = alias;
}
}
return removePrefix ? max.RemovePrefix() : max;
}

[DebuggerStepThrough]
private void ThrowIfAliasIsInvalid(string alias)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.CommandLine/LocalizationResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public virtual string RequiredCommandWasNotProvided() =>
/// Interpolates values into a localized string similar to Option '{0}' is required.
/// </summary>
public virtual string RequiredOptionWasNotProvided(Option option) =>
GetResourceString(Properties.Resources.RequiredOptionWasNotProvided, option.Aliases.OrderByDescending(x => x.Length).First());
GetResourceString(Properties.Resources.RequiredOptionWasNotProvided, option.GetLongestAlias(removePrefix: false));

/// <summary>
/// Interpolates values into a localized string similar to Argument &apos;{0}&apos; not recognized. Must be one of:{1}.
Expand Down
55 changes: 2 additions & 53 deletions src/System.CommandLine/Option.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace System.CommandLine
/// <seealso cref="IdentifierSymbol" />
public abstract class Option : IdentifierSymbol, IValueDescriptor
{
private string? _name;
private List<Action<OptionResult>>? _validators;

private protected Option(string name, string? description) : base(description)
Expand All @@ -25,8 +24,6 @@ private protected Option(string name, string? description) : base(description)
throw new ArgumentNullException(nameof(name));
}

_name = name.RemovePrefix();

AddAlias(name);
}

Expand Down Expand Up @@ -82,45 +79,10 @@ public ArgumentArity Arity

internal bool DisallowBinding { get; init; }

/// <inheritdoc />
public override string Name
{
set
{
if (!HasAlias(value))
{
_name = null;
RemoveAlias(DefaultName);
}

base.Name = value;
}
}

internal List<Action<OptionResult>> Validators => _validators ??= new();

internal bool HasValidators => _validators is not null && _validators.Count > 0;

/// <summary>
/// Indicates whether a given alias exists on the option, regardless of its prefix.
/// </summary>
/// <param name="alias">The alias, which can include a prefix.</param>
/// <returns><see langword="true"/> if the alias exists; otherwise, <see langword="false"/>.</returns>
public bool HasAliasIgnoringPrefix(string alias)
{
ReadOnlySpan<char> rawAlias = alias.AsSpan(alias.GetPrefixLength());

foreach (string existingAlias in _aliases)
{
if (MemoryExtensions.Equals(existingAlias.AsSpan(existingAlias.GetPrefixLength()), rawAlias, StringComparison.CurrentCulture))
{
return true;
}
}

return false;
}

/// <summary>
/// Gets a value that indicates whether multiple argument tokens are allowed for each option identifier token.
/// </summary>
Expand All @@ -137,7 +99,7 @@ public bool HasAliasIgnoringPrefix(string alias)
public bool AllowMultipleArgumentsPerToken { get; set; }

internal virtual bool IsGreedy
=> Argument is not null && Argument.Arity.MinimumNumberOfValues > 0 && Argument.ValueType != typeof(bool);
=> Argument.Arity.MinimumNumberOfValues > 0 && Argument.ValueType != typeof(bool);

/// <summary>
/// Indicates whether the option is required when its parent command is invoked.
Expand All @@ -156,20 +118,7 @@ internal virtual bool IsGreedy

object? IValueDescriptor.GetDefaultValue() => Argument.GetDefaultValue();

private protected override string DefaultName => _name ??= GetLongestAlias();

private string GetLongestAlias()
{
string max = "";
foreach (string alias in _aliases)
{
if (alias.Length > max.Length)
{
max = alias;
}
}
return max.RemovePrefix();
}
private protected override string DefaultName => GetLongestAlias(true);

/// <inheritdoc />
public override IEnumerable<CompletionItem> GetCompletions(CompletionContext context)
Expand Down
2 changes: 1 addition & 1 deletion src/System.CommandLine/Parsing/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal static string RemovePrefix(this string alias)
: alias;
}

internal static int GetPrefixLength(this string alias)
private static int GetPrefixLength(this string alias)
{
if (alias[0] == '-')
{
Expand Down
7 changes: 1 addition & 6 deletions src/System.CommandLine/Parsing/SymbolResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;

namespace System.CommandLine.Parsing
{
Expand Down Expand Up @@ -31,11 +30,7 @@ internal static Token Token(this SymbolResult symbolResult)

Token CreateImplicitToken(Option option)
{
var optionName = option.Name;

var defaultAlias = option.Aliases.First(alias => alias.RemovePrefix() == optionName);

return new Token(defaultAlias, TokenType.Option, option, Parsing.Token.ImplicitPosition);
return new Token(option.GetLongestAlias(removePrefix: false), TokenType.Option, option, Parsing.Token.ImplicitPosition);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/System.CommandLine/Symbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace System.CommandLine
/// </summary>
public abstract class Symbol
{
private string? _name;
private protected string? _name;
private ParentNode? _firstParent;

private protected Symbol()
Expand Down