Skip to content

Improve AutoHelp and AutoVersion handling #686

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions src/CommandLine/Core/ArgumentsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace CommandLine.Core
static class ArgumentsExtensions
{
public static IEnumerable<Error> Preprocess(
this IEnumerable<string> arguments,
this IEnumerable<Token> arguments,
IEnumerable<
Func<IEnumerable<string>, IEnumerable<Error>>
Func<IEnumerable<Token>, IEnumerable<Error>>
> preprocessorLookup)
{
return preprocessorLookup.TryHead().MapValueOrDefault(
Expand Down
44 changes: 36 additions & 8 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public static ParserResult<T> Build<T>(
ignoreValueCase,
parsingCulture,
autoHelp,
false,
autoVersion,
false,
false,
nonFatalErrors);
}

Expand All @@ -45,9 +47,12 @@ public static ParserResult<T> Build<T>(
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoHelpShortName,
bool autoVersion,
bool autoVersionShortName,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors) {
IEnumerable<ErrorType> nonFatalErrors)
{
var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T));

var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create(
Expand All @@ -56,7 +61,9 @@ public static ParserResult<T> Build<T>(

var specs = from pt in specProps select pt.Specification;

var optionSpecs = specs
var autoSpecs = AddAutoSpecs(specs, nameComparer, autoHelp, autoHelpShortName, autoVersion, autoVersionShortName);

var optionSpecs = autoSpecs
.ThrowingValidate(SpecificationGuards.Lookup)
.OfType<OptionSpecification>()
.Memoize();
Expand All @@ -71,12 +78,13 @@ public static ParserResult<T> Build<T>(
errs => new NotParsed<T>(makeDefault().GetType().ToTypeInfo(), errs);

var argumentsList = arguments.Memoize();
Func<ParserResult<T>> buildUp = () =>
{
var tokenizerResult = tokenizer(argumentsList, optionSpecs);

var tokens = tokenizerResult.SucceededWith().Memoize();
var tokenizerResult = tokenizer(argumentsList, optionSpecs);

var tokens = tokenizerResult.SucceededWith().Memoize();

Func<ParserResult<T>> buildUp = () =>
{
var partitions = TokenPartitioner.Partition(
tokens,
name => TypeLookup.FindTypeDescriptorAndSibling(name, optionSpecs, nameComparer));
Expand Down Expand Up @@ -136,8 +144,8 @@ public static ParserResult<T> Build<T>(
};

var preprocessorErrors = (
argumentsList.Any()
? arguments.Preprocess(PreprocessorGuards.Lookup(nameComparer, autoHelp, autoVersion))
tokens.Any()
? tokens.Preprocess(PreprocessorGuards.Lookup(nameComparer, autoHelp, autoHelpShortName, autoVersion, autoVersionShortName))
: Enumerable.Empty<Error>()
).Memoize();

Expand All @@ -150,6 +158,26 @@ public static ParserResult<T> Build<T>(
return result;
}

private static IEnumerable<Specification> AddAutoSpecs(IEnumerable<Specification> specs, StringComparer nameComparer, bool autoHelp, bool autoHelpShortName, bool autoVersion, bool autoVersionShortName)
{
var optionSpecs = specs.OfType<OptionSpecification>().Memoize();
bool useHelpShortName = autoHelpShortName && !(optionSpecs.Any(spec => nameComparer.Equals(spec.ShortName, "h")));
bool useVersionShortName = autoVersionShortName && !(optionSpecs.Any(spec => nameComparer.Equals(spec.ShortName, "V"))); // Uppercase V
bool addAutoHelp = autoHelp && !(optionSpecs.Any(spec => nameComparer.Equals(spec.LongName, "help")));
bool addAutoVersion = autoVersion && !(optionSpecs.Any(spec => nameComparer.Equals(spec.LongName, "version")));

var autoSpecs = new List<OptionSpecification>(2);
if (addAutoHelp)
{
autoSpecs.Add(OptionSpecification.NewSwitch(useHelpShortName ? "h" : String.Empty, "help", false, "Display this help screen.", String.Empty));
}
if (addAutoVersion)
{
autoSpecs.Add(OptionSpecification.NewSwitch(useVersionShortName ? "V" : String.Empty, "version", false, "Display version information.", String.Empty));
}
return specs.Concat(autoSpecs);
}

private static T BuildMutable<T>(Maybe<Func<T>> factory, IEnumerable<SpecificationProperty> specPropsWithValue, List<Error> setPropertyErrors )
{
var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance<T>());
Expand Down
24 changes: 19 additions & 5 deletions src/CommandLine/Core/InstanceChooser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ public static ParserResult<object> Choose(
ignoreValueCase,
parsingCulture,
autoHelp,
false,
autoVersion,
false,
false,
nonFatalErrors);
}

Expand All @@ -44,7 +46,9 @@ public static ParserResult<object> Choose(
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoHelpShortName,
bool autoVersion,
bool autoVersionShortName,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
Expand All @@ -65,19 +69,19 @@ bool preprocCompare(string command) =>
nameComparer.Equals(command, firstArg) ||
nameComparer.Equals(string.Concat("--", command), firstArg);

return (autoHelp && preprocCompare("help"))
return (autoHelp && preprocCompare("help")) || (autoHelp && autoHelpShortName && nameComparer.Equals("-h", firstArg))
? MakeNotParsed(types,
MakeHelpVerbRequestedError(verbs,
arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer))
: (autoVersion && preprocCompare("version"))
: (autoVersion && preprocCompare("version")) || (autoVersion && autoVersionShortName && nameComparer.Equals("-V", firstArg))
? MakeNotParsed(types, new VersionRequestedError())
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors);
: MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoHelpShortName, autoVersion, autoVersionShortName, allowMultiInstance, nonFatalErrors);
}

return arguments.Any()
? choose()
: (defaultVerbCount == 1
? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors)
? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoHelpShortName, autoVersion, autoVersionShortName, allowMultiInstance, nonFatalErrors)
: MakeNotParsed(types, new NoVerbSelectedError()));
}

Expand All @@ -90,7 +94,10 @@ private static ParserResult<object> MatchDefaultVerb(
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoHelpShortName,
bool autoVersion,
bool autoVersionShortName,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
return !(defaultVerb is null)
Expand All @@ -102,7 +109,10 @@ private static ParserResult<object> MatchDefaultVerb(
ignoreValueCase,
parsingCulture,
autoHelp,
autoHelpShortName,
autoVersion,
autoVersionShortName,
allowMultiInstance,
nonFatalErrors)
: MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First()));
}
Expand All @@ -116,7 +126,9 @@ private static ParserResult<object> MatchVerb(
bool ignoreValueCase,
CultureInfo parsingCulture,
bool autoHelp,
bool autoHelpShortName,
bool autoVersion,
bool autoVersionShortName,
bool allowMultiInstance,
IEnumerable<ErrorType> nonFatalErrors)
{
Expand All @@ -129,7 +141,7 @@ private static ParserResult<object> MatchVerb(

if (verbUsed == default)
{
return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors);
return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoHelpShortName, autoVersion, autoVersionShortName, allowMultiInstance, nonFatalErrors);
}
return InstanceBuilder.Build(
Maybe.Just<Func<object>>(
Expand All @@ -140,7 +152,9 @@ private static ParserResult<object> MatchVerb(
ignoreValueCase,
parsingCulture,
autoHelp,
autoHelpShortName,
autoVersion,
autoVersionShortName,
allowMultiInstance,
nonFatalErrors);
}
Expand Down
36 changes: 29 additions & 7 deletions src/CommandLine/Core/PreprocessorGuards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,53 @@ namespace CommandLine.Core
{
static class PreprocessorGuards
{
public static IEnumerable<Func<IEnumerable<string>, IEnumerable<Error>>>
Lookup(StringComparer nameComparer, bool autoHelp, bool autoVersion)
public static IEnumerable<Func<IEnumerable<Token>, IEnumerable<Error>>>
Lookup(StringComparer nameComparer, bool autoHelp, bool autoHelpShortName, bool autoVersion, bool autoVersionShortName)
{
var list = new List<Func<IEnumerable<string>, IEnumerable<Error>>>();
var list = new List<Func<IEnumerable<Token>, IEnumerable<Error>>>();
if (autoHelp)
list.Add(HelpCommand(nameComparer));
if (autoHelp && autoHelpShortName)
list.Add(ShortHelpCommand(nameComparer));
if (autoVersion)
list.Add(VersionCommand(nameComparer));
if (autoVersion && autoVersionShortName)
list.Add(ShortVersionCommand(nameComparer));
return list;
}

public static Func<IEnumerable<string>, IEnumerable<Error>> HelpCommand(StringComparer nameComparer)
public static Func<IEnumerable<Token>, IEnumerable<Error>> HelpCommand(StringComparer nameComparer)
{
return
arguments =>
nameComparer.Equals("--help", arguments.First())
arguments.OfType<Name>().Any(arg => nameComparer.Equals("help", arg.Text))
? new Error[] { new HelpRequestedError() }
: Enumerable.Empty<Error>();
}

public static Func<IEnumerable<string>, IEnumerable<Error>> VersionCommand(StringComparer nameComparer)
public static Func<IEnumerable<Token>, IEnumerable<Error>> ShortHelpCommand(StringComparer nameComparer)
{
return
arguments =>
nameComparer.Equals("--version", arguments.First())
arguments.OfType<Name>().Any(arg => nameComparer.Equals("h", arg.Text))
? new Error[] { new HelpRequestedError() }
: Enumerable.Empty<Error>();
}

public static Func<IEnumerable<Token>, IEnumerable<Error>> VersionCommand(StringComparer nameComparer)
{
return
arguments =>
arguments.OfType<Name>().Any(arg => nameComparer.Equals("version", arg.Text))
? new Error[] { new VersionRequestedError() }
: Enumerable.Empty<Error>();
}

public static Func<IEnumerable<Token>, IEnumerable<Error>> ShortVersionCommand(StringComparer nameComparer)
{
return
arguments =>
arguments.OfType<Name>().Any(arg => nameComparer.Equals("V", arg.Text)) // Uppercase V
? new Error[] { new VersionRequestedError() }
: Enumerable.Empty<Error>();
}
Expand Down
4 changes: 1 addition & 3 deletions src/CommandLine/ErrorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ public static ParserResult<T> ToParserResult<T>(this IEnumerable<Error> errors,
public static IEnumerable<Error> OnlyMeaningfulOnes(this IEnumerable<Error> errors)
{
return errors
.Where(e => !e.StopsProcessing)
.Where(e => !(e.Tag == ErrorType.UnknownOptionError
&& ((UnknownOptionError)e).Token.EqualsOrdinalIgnoreCase("help")));
.Where(e => !e.StopsProcessing);
}

}
Expand Down
6 changes: 6 additions & 0 deletions src/CommandLine/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ public ParserResult<T> ParseArguments<T>(IEnumerable<string> args)
settings.CaseInsensitiveEnumValues,
settings.ParsingCulture,
settings.AutoHelp,
settings.AutoHelpShortName,
settings.AutoVersion,
settings.AutoVersionShortName,
settings.AllowMultiInstance,
HandleUnknownArguments(settings.IgnoreUnknownArguments)),
settings);
Expand Down Expand Up @@ -131,7 +133,9 @@ public ParserResult<T> ParseArguments<T>(Func<T> factory, IEnumerable<string> ar
settings.CaseInsensitiveEnumValues,
settings.ParsingCulture,
settings.AutoHelp,
settings.AutoHelpShortName,
settings.AutoVersion,
settings.AutoVersionShortName,
settings.AllowMultiInstance,
HandleUnknownArguments(settings.IgnoreUnknownArguments)),
settings);
Expand Down Expand Up @@ -164,7 +168,9 @@ public ParserResult<object> ParseArguments(IEnumerable<string> args, params Type
settings.CaseInsensitiveEnumValues,
settings.ParsingCulture,
settings.AutoHelp,
settings.AutoHelpShortName,
settings.AutoVersion,
settings.AutoVersionShortName,
settings.AllowMultiInstance,
HandleUnknownArguments(settings.IgnoreUnknownArguments)),
settings);
Expand Down
22 changes: 22 additions & 0 deletions src/CommandLine/ParserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public class ParserSettings : IDisposable
private TextWriter helpWriter;
private bool ignoreUnknownArguments;
private bool autoHelp;
private bool autoHelpShortName;
private bool autoVersion;
private bool autoVersionShortName;
private CultureInfo parsingCulture;
private bool enableDashDash;
private int maximumDisplayWidth;
Expand All @@ -35,7 +37,9 @@ public ParserSettings()
caseSensitive = true;
caseInsensitiveEnumValues = false;
autoHelp = true;
autoHelpShortName = false;
autoVersion = true;
autoVersionShortName = false;
parsingCulture = CultureInfo.InvariantCulture;
maximumDisplayWidth = GetWindowWidth();
}
Expand Down Expand Up @@ -147,6 +151,15 @@ public bool AutoHelp
set { PopsicleSetter.Set(Consumed, ref autoHelp, value); }
}

/// <summary>
/// Gets or sets a value indicating whether implicit option or verb 'help' should have the shortname '-h'.
/// </summary>
public bool AutoHelpShortName
{
get { return autoHelpShortName; }
set { PopsicleSetter.Set(Consumed, ref autoHelpShortName, value); }
}

/// <summary>
/// Gets or sets a value indicating whether implicit option or verb 'version' should be supported.
/// </summary>
Expand All @@ -156,6 +169,15 @@ public bool AutoVersion
set { PopsicleSetter.Set(Consumed, ref autoVersion, value); }
}

/// <summary>
/// Gets or sets a value indicating whether implicit option or verb 'help' should have the shortname '-h'.
/// </summary>
public bool AutoVersionShortName
{
get { return autoVersionShortName; }
set { PopsicleSetter.Set(Consumed, ref autoVersionShortName, value); }
}

/// <summary>
/// Gets or sets a value indicating whether enable double dash '--' syntax,
/// that forces parsing of all subsequent tokens as values.
Expand Down
Loading