From ea2c68a67141a1f3176fd990c6e34432caf9226b Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sat, 8 Jun 2019 16:38:40 +0100 Subject: [PATCH 1/8] Make it easier to configure HelpText --- src/CommandLine/Parser.cs | 13 ++- src/CommandLine/ParserSettings.cs | 27 +++++- src/CommandLine/Text/HelpText.cs | 52 +++++++---- src/CommandLine/Text/HelpTextConfiguration.cs | 87 +++++++++++++++++++ .../Unit/HelpTextConfigurationTests.cs | 56 ++++++++++++ .../Unit/Text/HelpTextTests.cs | 11 ++- 6 files changed, 218 insertions(+), 28 deletions(-) create mode 100644 src/CommandLine/Text/HelpTextConfiguration.cs create mode 100644 tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 8f4bd049..4795eb7c 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -19,7 +19,7 @@ public class Parser : IDisposable private bool disposed; private readonly ParserSettings settings; private static readonly Lazy DefaultParser = new Lazy( - () => new Parser(new ParserSettings { HelpWriter = Console.Error })); + () => new Parser(new ParserSettings() )); /// /// Initializes a new instance of the class. @@ -194,16 +194,15 @@ private static ParserResult MakeParserResult(ParserResult parserResult, { return DisplayHelp( parserResult, - settings.HelpWriter, - settings.MaximumDisplayWidth); + settings.HelpTextConfiguration); } - private static ParserResult DisplayHelp(ParserResult parserResult, TextWriter helpWriter, int maxDisplayWidth) + private static ParserResult DisplayHelp(ParserResult parserResult, HelpTextConfiguration helpTextConfig) { parserResult.WithNotParsed( errors => - Maybe.Merge(errors.ToMaybe(), helpWriter.ToMaybe()) - .Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, maxDisplayWidth))) + Maybe.Merge(errors.ToMaybe(), helpTextConfig.HelpWriter.ToMaybe()) + .Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult,helpTextConfig))) ); return parserResult; @@ -229,4 +228,4 @@ private void Dispose(bool disposing) } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 7b182a22..27575140 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -5,6 +5,8 @@ using System.IO; using CommandLine.Infrastructure; +using CommandLine.Text; + namespace CommandLine { @@ -18,7 +20,7 @@ public class ParserSettings : IDisposable private bool disposed; private bool caseSensitive; private bool caseInsensitiveEnumValues; - private TextWriter helpWriter; + private HelpTextConfiguration helpTextConfiguration = HelpTextConfiguration.Default; private bool ignoreUnknownArguments; private bool autoHelp; private bool autoVersion; @@ -69,6 +71,7 @@ public bool CaseSensitive set { PopsicleSetter.Set(Consumed, ref caseSensitive, value); } } + /// /// Gets or sets a value indicating whether perform case sensitive comparisons of values. /// Note that case insensitivity only applies to values, not the parameters. @@ -102,13 +105,31 @@ public CultureInfo ParsingCulture /// /// /// It is the caller's responsibility to dispose or close the . + /// REVIEW - this is preserve solely to provide backwards compatibility with code + /// where it was directly exposed. Ideally it would be deprecated. /// public TextWriter HelpWriter { - get { return helpWriter; } - set { PopsicleSetter.Set(Consumed, ref helpWriter, value); } + get { return HelpTextConfiguration.HelpWriter; } + set { HelpTextConfiguration = HelpTextConfiguration.WithHelpWriter(value); } + } + + + /// + /// Allows the HelpText to be configured + /// + /// + /// It is intended that any future HelpText configuraion should be encapusulated in this object. + /// REVIEW - It's actually not obvious to me that this should be supplied here rather than passed in during + /// WithNotParsed but since that's the pattern set by the original TextWriter I've left it here. + /// + public HelpTextConfiguration HelpTextConfiguration + { + get { return helpTextConfiguration; } + set { PopsicleSetter.Set(Consumed, ref helpTextConfiguration, value); } } + /// /// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it /// encounter an unknown arguments diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index cd11a475..43da1c98 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -20,7 +20,6 @@ namespace CommandLine.Text public class HelpText { private const int BuilderCapacity = 128; - private const int DefaultMaximumLength = 80; // default console width private readonly StringBuilder preOptionsHelp; private readonly StringBuilder postOptionsHelp; private readonly SentenceBuilder sentenceBuilder; @@ -109,12 +108,12 @@ public HelpText(SentenceBuilder sentenceBuilder, string heading, string copyrigh maximumDisplayWidth = Console.WindowWidth; if (maximumDisplayWidth < 1) { - maximumDisplayWidth = DefaultMaximumLength; + maximumDisplayWidth = HelpTextConfiguration.DefaultMaximumLength; } } catch (IOException) { - maximumDisplayWidth = DefaultMaximumLength; + maximumDisplayWidth = HelpTextConfiguration.DefaultMaximumLength; } this.sentenceBuilder = sentenceBuilder; this.heading = heading; @@ -227,14 +226,17 @@ public SentenceBuilder SentenceBuilder /// A delegate used to customize the text block of reporting parsing errors text block. /// A delegate used to customize model used to render text block of usage examples. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. - /// The maximum width of the display. - /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. + /// The configuration for the help-text + /// The parameter is not only a metter of formatting, it controls whether to handle verbs or options. + //REVIEW - I believe this is only public for test purposes and is not a client API hence I have not supplied + //a backwards compatibility version public static HelpText AutoBuild( ParserResult parserResult, Func onError, Func onExample, - bool verbsIndex = false, - int maxDisplayWidth = DefaultMaximumLength) + HelpTextConfiguration config, + bool verbsIndex = false + ) { var auto = new HelpText { @@ -242,7 +244,7 @@ public static HelpText AutoBuild( Copyright = CopyrightInfo.Empty, AdditionalNewLineAfterOption = true, AddDashesToOption = !verbsIndex, - MaximumDisplayWidth = maxDisplayWidth + MaximumDisplayWidth = config.DisplayWidth }; try @@ -264,7 +266,8 @@ public static HelpText AutoBuild( if (errors.OnlyMeaningfulOnes().Any()) auto = onError(auto); } - + //this seems to be the earliest meaningful point at which we can let the client configure the helptext options + config.Configurer(auto); ReflectionHelper.GetAttribute() .Do(license => license.AddToHelpText(auto, true)); @@ -307,7 +310,26 @@ public static HelpText AutoBuild( /// /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . - public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength) + //REVIEW - the use of this method with a supplied maxDisplayWidth should be deprecated in favour of passing + //in a HelpTextConfiguration object + public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = HelpTextConfiguration.DefaultMaximumLength) + { + return AutoBuild(parserResult, HelpTextConfiguration.Default.WithWidth(maxDisplayWidth)); + } + + + /// + /// Creates a new instance of the class, + /// automatically handling verbs or options scenario. + /// + /// The containing the instance that collected command line arguments parsed with class. + /// The configuration for the help text. + /// + /// An instance of class. + /// + /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property + /// of . + public static HelpText AutoBuild(ParserResult parserResult, HelpTextConfiguration config) { if (parserResult.Tag != ParserResultType.NotParsed) throw new ArgumentException("Excepting NotParsed type.", "parserResult"); @@ -315,16 +337,16 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay var errors = ((NotParsed)parserResult).Errors; if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = config.DisplayWidth }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) - return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); + return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e,config); var err = errors.OfType().Single(); var pr = new NotParsed(TypeInfo.Create(err.Type), Enumerable.Empty()); return err.Matched - ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e, maxDisplayWidth: maxDisplayWidth) - : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, true, maxDisplayWidth); + ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e,config) + : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e,config,true); } /// @@ -998,4 +1020,4 @@ private static string FormatDefaultValue(T value) : string.Empty; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Text/HelpTextConfiguration.cs b/src/CommandLine/Text/HelpTextConfiguration.cs new file mode 100644 index 00000000..64069c68 --- /dev/null +++ b/src/CommandLine/Text/HelpTextConfiguration.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; + + +namespace CommandLine.Text +{ + /// + /// Contains the configuration used when building HelpText + /// + /// + /// It is intended that this class should contain all settings/logic associated with configuring the + /// display of helptext. + /// + public class HelpTextConfiguration + { + /// + /// The default console width + /// + public const int DefaultMaximumLength = 80; + + /// + /// The width of the display. Text will wrap if this is exceeded. + /// + public readonly int DisplayWidth; + + /// + /// Method used to set display options within the HelpText + /// + /// + /// In the current implementation, there is only one HelpText and it grows as verbs and options + /// are scanned. Hence any flags this method sets will apply to all text. E.g. setting the + /// 'display enums' flag will set it for all options. + public readonly Action Configurer; + + /// + /// The output for the HelpText + /// + /// + /// This was moved from ParserSettings because logically is part of the help-text generation phase. + /// + public readonly TextWriter HelpWriter; + + /// + /// Constructor - private to avoid too much reliance on the particular argument list + /// + private HelpTextConfiguration(Action configurer, int displayWidth,TextWriter writer) + { + Configurer = configurer; + DisplayWidth = displayWidth; + HelpWriter = writer; + } + + /// + /// Default Configuration which will give acceptable results + /// + public static HelpTextConfiguration Default { get; } = new HelpTextConfiguration(_ => { },DefaultMaximumLength,Console.Error); + + /// + /// Sets the TextWriter + /// + /// + /// The client is expected to dispose of the writer + /// + public HelpTextConfiguration WithHelpWriter(TextWriter writer) + { + return new HelpTextConfiguration(Configurer,DisplayWidth,writer); + } + /// + /// Sets a different width of the help-text output + /// + public HelpTextConfiguration WithWidth(int maxDisplayWidth) + { + return new HelpTextConfiguration(Configurer,maxDisplayWidth,HelpWriter); + } + /// + /// Allows the client to pass in an action which will be called to configure the HelpText class + /// + /// + /// The Parser constructs the HelpText object then calls this method at the earliest opportunity + /// which allows various display flags and options to be set. + /// + public HelpTextConfiguration WithConfigurer(Action func) + { + return new HelpTextConfiguration(func,DisplayWidth,HelpWriter); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs new file mode 100644 index 00000000..b0709eaa --- /dev/null +++ b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; + +namespace CommandLine.Tests.Unit +{ + public class HelpTextConfigurationTests + { + public enum AnEnum + { + Option1, + Option2 + } + // Options + [Verb("run", HelpText = "a verb")] + internal class Options + { + [Option] + public AnEnum AnOption { get; set; } + } + + + // Test method (xUnit) which fails + [Fact] + public void DetailTextIsShownCorrectly() + { + var help = new StringWriter(); + var sut = new Parser(config => + { + config.HelpWriter = help; + config.MaximumDisplayWidth = 80; + + config.HelpTextConfiguration = HelpTextConfiguration.Default + .WithHelpWriter(help) + .WithConfigurer(h => h.AddEnumValuesToHelpText = true); + }); + + //There seems to a bug that prevents "help VERB" outputing anything if + //the parser is only supplied one option class so this test provides + //Add_verb as a workaround + sut.ParseArguments( + new[] {"help", "run",}); + var result = help.ToString(); + + // Verify outcome + var lines = result.ToNotEmptyLines().TrimStringArray(); + lines.Any(line=>line.Contains("Option1")).Should().BeTrue(); + lines.Any(line=>line.Contains("Option2")).Should().BeTrue(); + + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 7c4d7590..1921b4ef 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -588,7 +588,10 @@ public void AutoBuild_when_no_assembly_attributes() { onErrorCalled = true; return ht; - }, ex => ex); + }, + ex => ex, + HelpTextConfiguration.Default + ); onErrorCalled.Should().BeTrue(); actualResult.Copyright.Should().Be(expectedCopyright); @@ -613,7 +616,8 @@ public void AutoBuild_with_assembly_title_and_version_attributes_only() { onErrorCalled = true; return ht; - }, ex => ex); + }, ex => ex, + HelpTextConfiguration.Default); onErrorCalled.Should().BeTrue(); actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion)); @@ -637,7 +641,8 @@ public void AutoBuild_with_assembly_company_attribute_only() { onErrorCalled = true; return ht; - }, ex => ex); + }, ex => ex, + HelpTextConfiguration.Default); onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany)); From baa35dd3f9d6224c84e7b7673a5b196798a80d42 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sat, 8 Jun 2019 16:59:13 +0100 Subject: [PATCH 2/8] Missed the use of displaywidth in ParserSettings --- src/CommandLine/ParserSettings.cs | 19 +++++++++---------- src/CommandLine/Text/HelpText.cs | 2 +- src/CommandLine/Text/HelpTextConfiguration.cs | 2 +- .../Unit/HelpTextConfigurationTests.cs | 1 + 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 27575140..2b00812e 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -15,8 +15,6 @@ namespace CommandLine /// public class ParserSettings : IDisposable { - private const int DefaultMaximumLength = 80; // default console width - private bool disposed; private bool caseSensitive; private bool caseInsensitiveEnumValues; @@ -26,8 +24,7 @@ public class ParserSettings : IDisposable private bool autoVersion; private CultureInfo parsingCulture; private bool enableDashDash; - private int maximumDisplayWidth; - + /// /// Initializes a new instance of the class. /// @@ -40,15 +37,13 @@ public ParserSettings() parsingCulture = CultureInfo.InvariantCulture; try { - maximumDisplayWidth = Console.WindowWidth; - if (maximumDisplayWidth < 1) + if (Console.WindowWidth >= 1) { - maximumDisplayWidth = DefaultMaximumLength; + HelpTextConfiguration=HelpTextConfiguration.WithDisplayWidth(Console.WindowWidth); } } catch (IOException) { - maximumDisplayWidth = DefaultMaximumLength; } } @@ -178,10 +173,14 @@ public bool EnableDashDash /// /// Gets or sets the maximum width of the display. This determines word wrap when displaying the text. /// + /// + /// REVIEW - this is provided only for backwards compatibility. Ideally it would be deprecated and the display width + /// accessed solely through HelpTextConfiguration + /// public int MaximumDisplayWidth { - get { return maximumDisplayWidth; } - set { maximumDisplayWidth = value; } + get { return HelpTextConfiguration.DisplayWidth; } + set { HelpTextConfiguration = HelpTextConfiguration.WithDisplayWidth(value); } } internal StringComparer NameComparer diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 43da1c98..c0781e32 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -314,7 +314,7 @@ public static HelpText AutoBuild( //in a HelpTextConfiguration object public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = HelpTextConfiguration.DefaultMaximumLength) { - return AutoBuild(parserResult, HelpTextConfiguration.Default.WithWidth(maxDisplayWidth)); + return AutoBuild(parserResult, HelpTextConfiguration.Default.WithDisplayWidth(maxDisplayWidth)); } diff --git a/src/CommandLine/Text/HelpTextConfiguration.cs b/src/CommandLine/Text/HelpTextConfiguration.cs index 64069c68..82e1cb20 100644 --- a/src/CommandLine/Text/HelpTextConfiguration.cs +++ b/src/CommandLine/Text/HelpTextConfiguration.cs @@ -68,7 +68,7 @@ public HelpTextConfiguration WithHelpWriter(TextWriter writer) /// /// Sets a different width of the help-text output /// - public HelpTextConfiguration WithWidth(int maxDisplayWidth) + public HelpTextConfiguration WithDisplayWidth(int maxDisplayWidth) { return new HelpTextConfiguration(Configurer,maxDisplayWidth,HelpWriter); } diff --git a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs index b0709eaa..f72843e0 100644 --- a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs +++ b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs @@ -36,6 +36,7 @@ public void DetailTextIsShownCorrectly() config.HelpTextConfiguration = HelpTextConfiguration.Default .WithHelpWriter(help) + .WithWidth(50) .WithConfigurer(h => h.AddEnumValuesToHelpText = true); }); From 540ba8aa44bf05496b098d1e97adf6c3cf96dc1b Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sat, 8 Jun 2019 17:07:33 +0100 Subject: [PATCH 3/8] trivial change to kick build server --- tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs index f72843e0..a0570dce 100644 --- a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs +++ b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs @@ -36,10 +36,11 @@ public void DetailTextIsShownCorrectly() config.HelpTextConfiguration = HelpTextConfiguration.Default .WithHelpWriter(help) - .WithWidth(50) + .WithDisplayWidth(50) .WithConfigurer(h => h.AddEnumValuesToHelpText = true); }); + //There seems to a bug that prevents "help VERB" outputing anything if //the parser is only supplied one option class so this test provides //Add_verb as a workaround From 946df062201c176ca2b020c477bcd056e98d8bff Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sun, 9 Jun 2019 12:13:25 +0100 Subject: [PATCH 4/8] Add fluent api for easier configuration --- src/CommandLine/Parser.cs | 46 +++++++++++++++++++ src/CommandLine/WidthPolicy.cs | 17 +++++++ .../Unit/HelpTextConfigurationTests.cs | 29 ++++++++++-- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/CommandLine/WidthPolicy.cs diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 4795eb7c..637a7534 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -227,5 +227,51 @@ private void Dispose(bool disposing) disposed = true; } } + /// + /// Allows the client to use a stream other than Console.Error for ouput. NOTE - client should dispose the stream + /// + /// The output stream to use + /// The parser + /// REVIEW - to be honest the "Consumed" flag seems to be adding very little value in ParserSettings. I understand the + /// desire to stop clients messing around with settings during parsing but it's hard to abuse the API in a way that would + /// really happen. My suggestion would be to remove it. + public Parser SetTextWriter(TextWriter writer) + { + settings.Consumed = false; + settings.HelpTextConfiguration = settings.HelpTextConfiguration.WithHelpWriter(writer); + settings.Consumed = true; + return this; + } + + /// + /// Allows the client to select the display width + /// + /// The desired width + /// The policy to use when setting the width + /// The parser + public Parser SetDisplayWidth(int width,WidthPolicy policy) + { + settings.Consumed = false; + //Note that the parser constructor has probably already worked out the console width + if (policy == WidthPolicy.FitToScreen) + width = Math.Min(settings.HelpTextConfiguration.DisplayWidth, width); + settings.HelpTextConfiguration = settings.HelpTextConfiguration.WithDisplayWidth(width); + settings.Consumed = true; + return this; + } + + /// + /// Allows the client to configure flags in the helpText class + /// + /// An action which can set flags in the HelpText class + /// The parser + public Parser SetHelpTextConfiguration(Action configurer) + { + settings.Consumed = false; + settings.HelpTextConfiguration = settings.HelpTextConfiguration.WithConfigurer(configurer); + settings.Consumed = true; + return this; + } + } } diff --git a/src/CommandLine/WidthPolicy.cs b/src/CommandLine/WidthPolicy.cs new file mode 100644 index 00000000..a7fd3bb0 --- /dev/null +++ b/src/CommandLine/WidthPolicy.cs @@ -0,0 +1,17 @@ +namespace CommandLine +{ + /// + /// Policy that controls how to configure the width of help text + /// + public enum WidthPolicy + { + /// + /// Use the supplied width regardless of whether it will fit on screen (recommended if you are writing to custom stream) + /// + ForceUse, + /// + /// Use the screen width if smaller than the supplied width + /// + FitToScreen + } +} \ No newline at end of file diff --git a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs index a0570dce..7104b91d 100644 --- a/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs +++ b/tests/CommandLine.Tests/Unit/HelpTextConfigurationTests.cs @@ -26,7 +26,7 @@ internal class Options // Test method (xUnit) which fails [Fact] - public void DetailTextIsShownCorrectly() + public void ConfigurationIsAccepted() { var help = new StringWriter(); var sut = new Parser(config => @@ -41,8 +41,31 @@ public void DetailTextIsShownCorrectly() }); - //There seems to a bug that prevents "help VERB" outputing anything if - //the parser is only supplied one option class so this test provides + //There seems to a bug that prevents "help VERB" outputting anything if + //the parser is only supplied one verb so this test provides + //Add_verb as a workaround + sut.ParseArguments( + new[] {"help", "run",}); + var result = help.ToString(); + + // Verify outcome + var lines = result.ToNotEmptyLines().TrimStringArray(); + lines.Any(line=>line.Contains("Option1")).Should().BeTrue(); + lines.Any(line=>line.Contains("Option2")).Should().BeTrue(); + + } + + [Fact] + public void ConfigurationIsAcceptedUsingFluentAPI() + { + var help = new StringWriter(); + var sut =Parser.Default + .SetDisplayWidth(80,WidthPolicy.FitToScreen) + .SetTextWriter(help) + .SetHelpTextConfiguration(h => h.AddEnumValuesToHelpText = true); + + //There seems to a bug that prevents "help VERB" outputting anything if + //the parser is only supplied one verb so this test provides //Add_verb as a workaround sut.ParseArguments( new[] {"help", "run",}); From c5ba0d7dcca299cb4b86ac2f8f26cf020ced1eb0 Mon Sep 17 00:00:00 2001 From: x789 <14997061+x789@users.noreply.github.com> Date: Mon, 17 Jun 2019 14:34:08 +0200 Subject: [PATCH 5/8] propagate parser-settings to HelpText This fixes issues #414 and #455 (value of AutoHelp and AutoVersion are ignored by HelpText) --- src/CommandLine/Parser.cs | 11 +- src/CommandLine/Text/HelpText.cs | 54 ++++++++- tests/CommandLine.Tests/Unit/ParserTests.cs | 112 ++++++++++++++++++ .../Unit/Text/HelpTextTests.cs | 55 +++++++-- 4 files changed, 211 insertions(+), 21 deletions(-) diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 8f4bd049..8c0b7732 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -194,16 +194,15 @@ private static ParserResult MakeParserResult(ParserResult parserResult, { return DisplayHelp( parserResult, - settings.HelpWriter, - settings.MaximumDisplayWidth); + settings); } - private static ParserResult DisplayHelp(ParserResult parserResult, TextWriter helpWriter, int maxDisplayWidth) + private static ParserResult DisplayHelp(ParserResult parserResult, ParserSettings settings) { parserResult.WithNotParsed( errors => - Maybe.Merge(errors.ToMaybe(), helpWriter.ToMaybe()) - .Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, maxDisplayWidth))) + Maybe.Merge(errors.ToMaybe(), settings.HelpWriter.ToMaybe()) + .Do((_, writer) => writer.Write(HelpText.AutoBuild(parserResult, settings))) ); return parserResult; @@ -229,4 +228,4 @@ private void Dispose(bool disposing) } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index cd11a475..ccf229b7 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -235,6 +235,29 @@ public static HelpText AutoBuild( Func onExample, bool verbsIndex = false, int maxDisplayWidth = DefaultMaximumLength) + { + var settings = new ParserSettings() { MaximumDisplayWidth = maxDisplayWidth }; + return HelpText.AutoBuild(parserResult, onError, onExample, settings, verbsIndex); + } + + /// + /// Creates a new instance of the class using common defaults. + /// + /// + /// An instance of class. + /// + /// The containing the instance that collected command line arguments parsed with class. + /// A delegate used to customize the text block of reporting parsing errors text block. + /// A delegate used to customize model used to render text block of usage examples. + /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. + /// The parser settings + /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. + public static HelpText AutoBuild( + ParserResult parserResult, + Func onError, + Func onExample, + ParserSettings settings, + bool verbsIndex = false) { var auto = new HelpText { @@ -242,7 +265,9 @@ public static HelpText AutoBuild( Copyright = CopyrightInfo.Empty, AdditionalNewLineAfterOption = true, AddDashesToOption = !verbsIndex, - MaximumDisplayWidth = maxDisplayWidth + MaximumDisplayWidth = settings.MaximumDisplayWidth, + AutoHelp = settings.AutoHelp, + AutoVersion = settings.AutoVersion }; try @@ -308,6 +333,23 @@ public static HelpText AutoBuild( /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength) + { + var settings = new ParserSettings() { MaximumDisplayWidth = maxDisplayWidth }; + return HelpText.AutoBuild(parserResult, settings); + } + + /// + /// Creates a new instance of the class, + /// automatically handling verbs or options scenario. + /// + /// The containing the instance that collected command line arguments parsed with class. + /// The parser settings. + /// + /// An instance of class. + /// + /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property + /// of . + public static HelpText AutoBuild(ParserResult parserResult, ParserSettings settings) { if (parserResult.Tag != ParserResultType.NotParsed) throw new ArgumentException("Excepting NotParsed type.", "parserResult"); @@ -315,16 +357,16 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay var errors = ((NotParsed)parserResult).Errors; if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = settings.MaximumDisplayWidth, AutoHelp = settings.AutoHelp, AutoVersion = settings.AutoVersion }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) - return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); + return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, settings); var err = errors.OfType().Single(); var pr = new NotParsed(TypeInfo.Create(err.Type), Enumerable.Empty()); return err.Matched - ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e, maxDisplayWidth: maxDisplayWidth) - : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, true, maxDisplayWidth); + ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e, settings) + : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, settings, true); } /// @@ -998,4 +1040,4 @@ private static string FormatDefaultValue(T value) : string.Empty; } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index c183c47d..3e9e8fa1 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -860,5 +860,117 @@ public void Parse_options_with_shuffled_index_values() Assert.Equal("two", args.Arg2); }); } + + [Fact] + // Tests a fix for issue #455 + public void Help_screen_does_not_show_help_verb_if_AutoHelp_is_disabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoHelp = false; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.DoesNotContain("help", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display more information on a specific command", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #455 + public void Help_screen_does_not_show_help_option_if_AutoHelp_is_disabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoHelp = false; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.DoesNotContain("--help", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display this help screen.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #455 + public void Help_screen_shows_help_verb_if_AutoHelp_is_enabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoHelp = true; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.Contains("help", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display more information on a specific command", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #455 + public void Help_screen_shows_help_option_if_AutoHelp_is_enabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoHelp = true; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.Contains("help", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display this help screen.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #414 + public void Help_screen_does_not_show_version_verb_if_AutoVersion_is_disabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoVersion = false; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.DoesNotContain("version", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display version information.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #414 + public void Help_screen_does_not_show_version_option_if_AutoVersion_is_disabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoVersion = false; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.DoesNotContain("--version", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display version information.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #414 + public void Help_screen_shows_version_verb_if_AutoVersion_is_enabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoVersion = true; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.Contains("version", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display version information.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + // Tests a fix for issue #414 + public void Help_screen_shows_version_option_if_AutoVersion_is_ensabled() + { + var output = new StringWriter(); + var sut = new Parser(config => { config.AutoVersion = true; config.HelpWriter = output; }); + + sut.ParseArguments(new string[] { }); + + var helpText = output.ToString(); + Assert.Contains("--version", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display version information.", helpText, StringComparison.InvariantCulture); + } } } diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 7c4d7590..d83ebcc6 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -318,9 +318,10 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() new BadFormatTokenError("badtoken"), new SequenceOutOfRangeError(new NameInfo("i", "")) }); + var settings = new ParserSettings(); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, settings); // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); @@ -353,9 +354,10 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo { new HelpVerbRequestedError("commit", typeof(Commit_Verb), true) }); + var settings = new ParserSettings(); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, settings); // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); @@ -386,9 +388,10 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo { new HelpVerbRequestedError("commit", typeof(Commit_Verb), true) }); + var settings = new ParserSettings() { MaximumDisplayWidth = 100 }; // Exercize system - var helpText = HelpText.AutoBuild(fakeResult, maxDisplayWidth: 100); + var helpText = HelpText.AutoBuild(fakeResult, settings); // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); @@ -418,9 +421,10 @@ public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_for TypeInfo.Create(typeof(NullInstance), verbTypes), new Error[] { new HelpVerbRequestedError(null, null, false) }); + var settings = new ParserSettings(); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, settings); // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); @@ -504,9 +508,10 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte { new BadFormatTokenError("badtoken") }); + var settings = new ParserSettings(); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, settings); // Verify outcome var text = helpText.ToString(); @@ -556,10 +561,11 @@ public void Default_set_to_sequence_should_be_properly_printed() new NotParsed( typeof(Options_With_Default_Set_To_Sequence).ToTypeInfo(), new Error[] { new BadFormatTokenError("badtoken") }); + var settings = new ParserSettings(); // Exercize system handlers.ChangeCulture(); - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, settings); handlers.ResetCulture(); // Verify outcome @@ -577,6 +583,7 @@ public void Default_set_to_sequence_should_be_properly_printed() [Fact] public void AutoBuild_when_no_assembly_attributes() { + var settings = new ParserSettings(); string expectedCopyright = "Copyright (C) 1 author"; ReflectionHelper.SetAttributeOverride(new Attribute[0]); @@ -588,7 +595,7 @@ public void AutoBuild_when_no_assembly_attributes() { onErrorCalled = true; return ht; - }, ex => ex); + }, ex => ex, settings); onErrorCalled.Should().BeTrue(); actualResult.Copyright.Should().Be(expectedCopyright); @@ -597,6 +604,7 @@ public void AutoBuild_when_no_assembly_attributes() [Fact] public void AutoBuild_with_assembly_title_and_version_attributes_only() { + var settings = new ParserSettings(); string expectedTitle = "Title"; string expectedVersion = "1.2.3.4"; @@ -613,7 +621,7 @@ public void AutoBuild_with_assembly_title_and_version_attributes_only() { onErrorCalled = true; return ht; - }, ex => ex); + }, ex => ex, settings); onErrorCalled.Should().BeTrue(); actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion)); @@ -623,6 +631,7 @@ public void AutoBuild_with_assembly_title_and_version_attributes_only() [Fact] public void AutoBuild_with_assembly_company_attribute_only() { + var settings = new ParserSettings(); string expectedCompany = "Company"; ReflectionHelper.SetAttributeOverride(new Attribute[] @@ -637,7 +646,7 @@ public void AutoBuild_with_assembly_company_attribute_only() { onErrorCalled = true; return ht; - }, ex => ex); + }, ex => ex, settings); onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany)); @@ -653,5 +662,33 @@ public void Add_line_with_two_empty_spaces_at_the_end() Assert.Equal("T" + Environment.NewLine + "e" + Environment.NewLine + "s" + Environment.NewLine + "t", b.ToString()); } + + [Fact] + public void AutoBuild_without_settings_contains_help_and_version() + { + var parserResult = new NotParsed(TypeInfo.Create(typeof(NullInstance)), new[] { new BadFormatConversionError(new NameInfo("f", "foo")) }); + + var helpText = HelpText.AutoBuild(parserResult); + + var text = helpText.ToString(); + Assert.Contains("--help", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display version information.", helpText, StringComparison.InvariantCulture); + Assert.Contains("--version", helpText, StringComparison.InvariantCulture); + Assert.Contains("Display this help screen.", helpText, StringComparison.InvariantCulture); + } + + [Fact] + public void AutoBuild_with_settings_does_not_contain_help_and_version() + { + var parserResult = new NotParsed(TypeInfo.Create(typeof(NullInstance)), new[] { new BadFormatConversionError(new NameInfo("f", "foo")) }); + var settings = new ParserSettings() { AutoHelp = false, AutoVersion = false }; + var helpText = HelpText.AutoBuild(parserResult, settings); + + var text = helpText.ToString(); + Assert.DoesNotContain("--help", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display version information.", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("--version", helpText, StringComparison.InvariantCulture); + Assert.DoesNotContain("Display this help screen.", helpText, StringComparison.InvariantCulture); + } } } From 0f0133bfdfccd5d9baabc244a74e291947e9fbcb Mon Sep 17 00:00:00 2001 From: neil macmullen Date: Mon, 24 Jun 2019 23:16:18 +0100 Subject: [PATCH 6/8] Add extra test to prove only a single ERROR line is outpu --- tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 6f4b1721..13fe149d 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using CommandLine.Core; using System.Linq; using System.Reflection; @@ -700,7 +701,18 @@ public void AutoBuild_can_disable_autoversion() Assert.Contains("Display this help screen.", helpText, StringComparison.InvariantCulture); Assert.DoesNotContain("Display version information.", helpText, StringComparison.InvariantCulture); Assert.DoesNotContain("--version", helpText, StringComparison.InvariantCulture); - + } + + [Fact] + public void Only_contains_one_error() + { + var helpWriter = new StringWriter(); + var sut = new Parser(config => { config.AutoHelp = false; config.HelpWriter = helpWriter; }); + + sut.ParseArguments(new string[] {"--help"}); + var helpText = helpWriter.ToString(); + helpText.Contains("--help").Should().BeFalse(); + helpText.Replace("ERROR(S)", "").Length.Should().Be(helpText.Length - 8); } } } From b60c3eff2dc431a2d8ccc07879a833ae0d45dbbb Mon Sep 17 00:00:00 2001 From: neil macmullen Date: Mon, 24 Jun 2019 23:21:08 +0100 Subject: [PATCH 7/8] Add one more test --- tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 13fe149d..109e7141 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -714,5 +714,18 @@ public void Only_contains_one_error() helpText.Contains("--help").Should().BeFalse(); helpText.Replace("ERROR(S)", "").Length.Should().Be(helpText.Length - 8); } + + [Fact] + public void Only_contains_one_error_using_new_configuration_mechanism() + { + var helpWriter = new StringWriter(); + var sut = Parser.Default.SetAutoHelp(false) + .SetTextWriter(helpWriter); + + sut.ParseArguments(new string[] {"--help"}); + var helpText = helpWriter.ToString(); + helpText.Contains("--help").Should().BeFalse(); + helpText.Replace("ERROR(S)", "").Length.Should().Be(helpText.Length - 8); + } } } From 2d10f923bdb94a71bc0839c89edee610eda6817d Mon Sep 17 00:00:00 2001 From: neil macmullen Date: Mon, 24 Jun 2019 23:29:10 +0100 Subject: [PATCH 8/8] Add a couple more obsolete warnings --- src/CommandLine/Parser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 339deae6..0d570cf0 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -24,6 +24,7 @@ public class Parser : IDisposable /// /// Initializes a new instance of the class. /// + [Obsolete("Calling the constructor directly is deprecated - prefer Parser.Default")] public Parser() { settings = new ParserSettings { Consumed = true }; @@ -35,6 +36,7 @@ public Parser() /// /// The delegate used to configure /// aspects and behaviors of the parser. + [Obsolete("Calling the constructor directly is deprecated - prefer Parser.Default.Set....")] public Parser(Action configuration) { if (configuration == null) throw new ArgumentNullException("configuration");