diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs
index cd11a475..575c6fbc 100644
--- a/src/CommandLine/Text/HelpText.cs
+++ b/src/CommandLine/Text/HelpText.cs
@@ -21,6 +21,18 @@ public class HelpText
{
private const int BuilderCapacity = 128;
private const int DefaultMaximumLength = 80; // default console width
+ ///
+ /// The number of spaces between an option and its associated help text
+ ///
+ private const int OptionToHelpTextSeparatorWidth = 4;
+ ///
+ /// The width of the option prefix (either "--" or " "
+ ///
+ private const int OptionPrefixWidth = 2;
+ ///
+ /// The total amount of extra space that needs to accounted for when indenting Option help text
+ ///
+ private const int TotalOptionPadding = OptionToHelpTextSeparatorWidth + OptionPrefixWidth;
private readonly StringBuilder preOptionsHelp;
private readonly StringBuilder postOptionsHelp;
private readonly SentenceBuilder sentenceBuilder;
@@ -608,7 +620,7 @@ public static IEnumerable RenderUsageTextAsLines(ParserResult pars
var styles = example.GetFormatStylesOrDefault();
foreach (var s in styles)
{
- var commandLine = new StringBuilder(2.Spaces())
+ var commandLine = new StringBuilder(OptionPrefixWidth.Spaces())
.Append(appAlias)
.Append(' ')
.Append(Parser.Default.FormatCommandLine(example.Sample,
@@ -665,37 +677,7 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen
value = value.TrimEnd();
builder.AppendWhen(builder.Length > 0, Environment.NewLine);
- do
- {
- var wordBuffer = 0;
- var words = value.Split(' ');
- for (var i = 0; i < words.Length; i++)
- {
- if (words[i].Length < (maximumLength - wordBuffer))
- {
- builder.Append(words[i]);
- wordBuffer += words[i].Length;
- if ((maximumLength - wordBuffer) > 1 && i != words.Length - 1)
- {
- builder.Append(" ");
- wordBuffer++;
- }
- }
- else if (words[i].Length >= maximumLength && wordBuffer == 0)
- {
- builder.Append(words[i].Substring(0, maximumLength));
- wordBuffer = maximumLength;
- break;
- }
- else
- break;
- }
- value = value.Substring(Math.Min(wordBuffer, value.Length));
- builder.AppendWhen(value.Length > 0, Environment.NewLine);
- }
- while (value.Length > maximumLength);
-
- builder.Append(value);
+ builder.Append(TextWrapper.WrapAndIndentText(value, 0, maximumLength));
}
private IEnumerable GetSpecificationsFromType(Type type)
@@ -757,7 +739,7 @@ private HelpText AddOptionsImpl(
optionsHelp = new StringBuilder(BuilderCapacity);
- var remainingSpace = maximumLength - (maxLength + 6);
+ var remainingSpace = maximumLength - (maxLength + TotalOptionPadding);
specifications.ForEach(
option =>
@@ -809,7 +791,7 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe
optionsHelp
.Append(name.Length < maxLength ? name.ToString().PadRight(maxLength) : name.ToString())
- .Append(" ");
+ .Append(OptionToHelpTextSeparatorWidth.Spaces());
var optionHelpText = specification.HelpText;
@@ -821,44 +803,13 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe
if (specification.Required)
optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText;
-
- if (!string.IsNullOrEmpty(optionHelpText))
- {
- do
- {
- var wordBuffer = 0;
- var words = optionHelpText.Split(' ');
- for (var i = 0; i < words.Length; i++)
- {
- if (words[i].Length < (widthOfHelpText - wordBuffer))
- {
- optionsHelp.Append(words[i]);
- wordBuffer += words[i].Length;
- if ((widthOfHelpText - wordBuffer) > 1 && i != words.Length - 1)
- {
- optionsHelp.Append(" ");
- wordBuffer++;
- }
- }
- else if (words[i].Length >= widthOfHelpText && wordBuffer == 0)
- {
- optionsHelp.Append(words[i].Substring(0, widthOfHelpText));
- wordBuffer = widthOfHelpText;
- break;
- }
- else
- break;
- }
-
- optionHelpText = optionHelpText.Substring(Math.Min(wordBuffer, optionHelpText.Length)).Trim();
- optionsHelp.AppendWhen(optionHelpText.Length > 0, Environment.NewLine,
- new string(' ', maxLength + 6));
- }
- while (optionHelpText.Length > widthOfHelpText);
- }
-
+
+ //note that we need to indent trim the start of the string because it's going to be
+ //appended to an existing line that is as long as the indent-level
+ var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart();
+
optionsHelp
- .Append(optionHelpText)
+ .Append(indented)
.Append(Environment.NewLine)
.AppendWhen(additionalNewLineAfterOption, Environment.NewLine);
@@ -944,13 +895,13 @@ private int GetMaxOptionLength(OptionSpecification spec)
{
specLength += spec.LongName.Length;
if (AddDashesToOption)
- specLength += 2;
+ specLength += OptionPrefixWidth;
specLength += metaLength;
}
if (hasShort && hasLong)
- specLength += 2; // ", "
+ specLength += OptionPrefixWidth;
return specLength;
}
@@ -997,5 +948,10 @@ private static string FormatDefaultValue(T value)
? builder.ToString(0, builder.Length - 1)
: string.Empty;
}
+
+
+
}
-}
\ No newline at end of file
+}
+
+
diff --git a/src/CommandLine/Text/TextWrapper.cs b/src/CommandLine/Text/TextWrapper.cs
new file mode 100644
index 00000000..19a93f15
--- /dev/null
+++ b/src/CommandLine/Text/TextWrapper.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using CommandLine.Infrastructure;
+
+namespace CommandLine.Text
+{
+ ///
+ /// A utility class to word-wrap and indent blocks of text
+ ///
+ public class TextWrapper
+ {
+ private string[] lines;
+ public TextWrapper(string input)
+ {
+ //start by splitting at newlines and then reinserting the newline as a separate word
+ //Note that on the input side, we can't assume the line-break style at run time so we have to
+ //be able to handle both. We can't use Environment.NewLine because that changes at
+ //_runtime_ and may not match the line-break style that was compiled in
+ lines = input
+ .Replace("\r","")
+ .Split(new[] {'\n'}, StringSplitOptions.None);
+ }
+
+ ///
+ /// Splits a string into a words and performs wrapping while also preserving line-breaks and sub-indentation
+ ///
+ /// The number of characters we can use for text
+ ///
+ /// This method attempts to wrap text without breaking words
+ /// For example, if columnWidth is 10 , the input
+ /// "a string for wrapping 01234567890123"
+ /// would return
+ /// "a string
+ /// "for
+ /// "wrapping
+ /// "0123456789
+ /// "0123"
+ ///
+ /// this
+ public TextWrapper WordWrap(int columnWidth)
+ {
+ //ensure we always use at least 1 column even if the client has told us there's no space available
+ columnWidth = Math.Max(1, columnWidth);
+ lines= lines
+ .SelectMany(line => WordWrapLine(line, columnWidth))
+ .ToArray();
+ return this;
+ }
+
+ ///
+ /// Indent all lines in the TextWrapper by the desired number of spaces
+ ///
+ /// The number of spaces to indent by
+ /// this
+ public TextWrapper Indent(int numberOfSpaces)
+ {
+ lines = lines
+ .Select(line => numberOfSpaces.Spaces() + line)
+ .ToArray();
+ return this;
+ }
+
+ ///
+ /// Returns the current state of the TextWrapper as a string
+ ///
+ ///
+ public string ToText()
+ {
+ //return the whole thing as a single string
+ return string.Join(Environment.NewLine,lines);
+ }
+
+ ///
+ /// Convenience method to wraps and indent a string in a single operation
+ ///
+ /// The string to operate on
+ /// The number of spaces to indent by
+ /// The width of the column used for wrapping
+ ///
+ /// The string is wrapped _then_ indented so the columnWidth is the width of the
+ /// usable text block, and does NOT include the indentLevel.
+ ///
+ /// the processed string
+ public static string WrapAndIndentText(string input, int indentLevel,int columnWidth)
+ {
+ return new TextWrapper(input)
+ .WordWrap(columnWidth)
+ .Indent(indentLevel)
+ .ToText();
+ }
+
+
+ private string [] WordWrapLine(string line,int columnWidth)
+ {
+ //create a list of individual lines generated from the supplied line
+
+ //When handling sub-indentation we must always reserve at least one column for text!
+ var unindentedLine = line.TrimStart();
+ var currentIndentLevel = Math.Min(line.Length - unindentedLine.Length,columnWidth-1) ;
+ columnWidth -= currentIndentLevel;
+
+ return unindentedLine.Split(' ')
+ .Aggregate(
+ new List(),
+ (lineList, word) => AddWordToLastLineOrCreateNewLineIfNecessary(lineList, word, columnWidth)
+ )
+ .Select(builder => currentIndentLevel.Spaces()+builder.ToString().TrimEnd())
+ .ToArray();
+ }
+
+ ///
+ /// When presented with a word, either append to the last line in the list or start a new line
+ ///
+ /// A list of StringBuilders containing results so far
+ /// The individual word to append
+ /// The usable text space
+ ///
+ /// The 'word' can actually be an empty string. It's important to keep these -
+ /// empty strings allow us to preserve indentation and extra spaces within a line.
+ ///
+ /// The same list as is passed in
+ private static List AddWordToLastLineOrCreateNewLineIfNecessary(List lines, string word,int columnWidth)
+ {
+ //The current indentation level is based on the previous line but we need to be careful
+ var previousLine = lines.LastOrDefault()?.ToString() ??string.Empty;
+
+ var wouldWrap = !lines.Any() || (word.Length>0 && previousLine.Length + word.Length > columnWidth);
+
+ if (!wouldWrap)
+ {
+ //The usual case is we just append the 'word' and a space to the current line
+ //Note that trailing spaces will get removed later when we turn the line list
+ //into a single string
+ lines.Last().Append(word + ' ');
+ }
+ else
+ {
+ //The 'while' here is to take account of the possibility of someone providing a word
+ //which just can't fit in the current column. In that case we just split it at the
+ //column end.
+ //That's a rare case though - most of the time we'll succeed in a single pass without
+ //having to split
+ //Note that we always do at least one pass even if the 'word' is empty in order to
+ //honour sub-indentation and extra spaces within strings
+ do
+ {
+ var availableCharacters = Math.Min(columnWidth, word.Length);
+ var segmentToAdd = LeftString(word,availableCharacters) + ' ';
+ lines.Add(new StringBuilder(segmentToAdd));
+ word = RightString(word,availableCharacters);
+ } while (word.Length > 0);
+ }
+ return lines;
+ }
+
+
+ ///
+ /// Return the right part of a string in a way that compensates for Substring's deficiencies
+ ///
+ private static string RightString(string str,int n)
+ {
+ return (n >= str.Length || str.Length==0)
+ ? string.Empty
+ : str.Substring(n);
+ }
+ ///
+ /// Return the left part of a string in a way that compensates for Substring's deficiencies
+ ///
+ private static string LeftString(string str,int n)
+ {
+
+ return (n >= str.Length || str.Length==0)
+ ? str
+ : str.Substring(0,n);
+ }
+ }
+}
diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs
new file mode 100644
index 00000000..afa77f3a
--- /dev/null
+++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs
@@ -0,0 +1,13 @@
+namespace CommandLine.Tests.Fakes
+{
+ public class HelpTextWithLineBreaksAndSubIndentation_Options
+ {
+
+ [Option(HelpText = @"This is a help text description where we want:
+ * The left pad after a linebreak to be honoured and the indentation to be preserved across to the next line
+ * The ability to return to no indent.
+Like this.")]
+ public string StringValue { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs
new file mode 100644
index 00000000..c93e73aa
--- /dev/null
+++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs
@@ -0,0 +1,23 @@
+namespace CommandLine.Tests.Fakes
+{
+ public class HelpTextWithLineBreaks_Options
+ {
+ [Option(HelpText =
+ @"This is a help text description.
+It has multiple lines.
+We also want to ensure that indentation is correct.")]
+ public string StringValue { get; set; }
+
+
+ [Option(HelpText = @"This is a help text description where we want
+ The left pad after a linebreak to be honoured so that
+ we can sub-indent within a description.")]
+ public string StringValu2 { get; set; }
+
+
+ [Option(HelpText = @"This is a help text description where we want
+ The left pad after a linebreak to be honoured and the indentation to be preserved across to the next line in a way that looks pleasing")]
+ public string StringValu3 { get; set; }
+
+ }
+}
diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs
new file mode 100644
index 00000000..9950dbc7
--- /dev/null
+++ b/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs
@@ -0,0 +1,9 @@
+namespace CommandLine.Tests.Fakes
+{
+ public class HelpTextWithMixedLineBreaks_Options
+ {
+ [Option(HelpText =
+ "This is a help text description\n It has multiple lines.\r\n Third line")]
+ public string StringValue { get; set; }
+ }
+}
diff --git a/tests/CommandLine.Tests/StringExtensions.cs b/tests/CommandLine.Tests/StringExtensions.cs
index e3830b0c..1ea18538 100644
--- a/tests/CommandLine.Tests/StringExtensions.cs
+++ b/tests/CommandLine.Tests/StringExtensions.cs
@@ -13,6 +13,11 @@ public static string[] ToNotEmptyLines(this string value)
return value.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
}
+ public static string[] ToLines(this string value)
+ {
+ return value.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
+ }
+
public static string[] TrimStringArray(this IEnumerable array)
{
return array.Select(item => item.Trim()).ToArray();
diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs
new file mode 100644
index 00000000..1db7497e
--- /dev/null
+++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Linq;
+using CommandLine.Text;
+using FluentAssertions;
+using Xunit;
+
+namespace CommandLine.Tests.Unit.Core
+{
+ public class TextWrapperTests
+ {
+ private string NormalizeLineBreaks(string str)
+ {
+ return str.Replace("\r", "");
+ }
+
+ private void EnsureEquivalent(string a, string b)
+ {
+ //workaround build system line-end inconsistencies
+ NormalizeLineBreaks(a).Should().Be(NormalizeLineBreaks(b));
+ }
+
+
+ [Fact]
+ public void ExtraSpacesAreTreatedAsNonBreaking()
+ {
+ var input =
+ "here is some text with some extra spacing";
+ var expected = @"here is some text
+with some extra
+spacing";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+
+ [Fact]
+ public void IndentWorksCorrectly()
+ {
+ var input =
+ @"line1
+line2";
+ var expected = @" line1
+ line2";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.Indent(2).ToText(), expected);
+ }
+
+ [Fact]
+ public void LongWordsAreBroken()
+ {
+ var input =
+ "here is some text that contains a veryLongWordThatWontFitOnASingleLine";
+ var expected = @"here is some text
+that contains a
+veryLongWordThatWont
+FitOnASingleLine";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+ [Fact]
+ public void NegativeColumnWidthStillProducesOutput()
+ {
+ var input = @"test";
+ var expected = string.Join(Environment.NewLine, input.Select(c => c.ToString()));
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(-1).ToText(), expected);
+ }
+
+ [Fact]
+ public void SimpleWrappingIsAsExpected()
+ {
+ var input =
+ @"here is some text that needs wrapping";
+ var expected = @"here is
+some text
+that needs
+wrapping";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(10).ToText(), expected);
+ }
+
+ [Fact]
+ public void SingleColumnStillProducesOutputForSubIndentation()
+ {
+ var input = @"test
+ ind";
+
+ var expected = @"t
+e
+s
+t
+i
+n
+d";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(-1).ToText(), expected);
+ }
+
+ [Fact]
+ public void SpacesWithinStringAreRespected()
+ {
+ var input =
+ "here is some text with some extra spacing";
+ var expected = @"here is some
+text with some extra
+spacing";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+ [Fact]
+ public void SubIndentationCorrectlyWrapsWhenColumnWidthRequiresIt()
+ {
+ var input = @"test
+ indented";
+ var expected = @"test
+ in
+ de
+ nt
+ ed";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(6).ToText(), expected);
+ }
+
+ [Fact]
+ public void SubIndentationIsPreservedWhenBreakingWords()
+ {
+ var input =
+ "here is some text that contains \n a veryLongWordThatWontFitOnASingleLine";
+ var expected = @"here is some text
+that contains
+ a
+ veryLongWordThatWo
+ ntFitOnASingleLine";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+ [Fact]
+ public void WrappingAvoidsBreakingWords()
+ {
+ var input =
+ @"here hippopotamus is some text that needs wrapping";
+ var expected = @"here
+hippopotamus is
+some text that
+needs wrapping";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(15).ToText(), expected);
+ }
+
+
+ [Fact]
+ public void WrappingExtraSpacesObeySubIndent()
+ {
+ var input =
+ "here is some\n text with some extra spacing";
+ var expected = @"here is some
+ text
+ with some extra
+ spacing";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+ [Fact]
+ public void WrappingObeysLineBreaksOfAllStyles()
+ {
+ var input =
+ "here is some text\nthat needs\r\nwrapping";
+ var expected = @"here is some text
+that needs
+wrapping";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+
+
+ [Fact]
+ public void WrappingPreservesSubIndentation()
+ {
+ var input =
+ "here is some text\n that needs wrapping where we want the wrapped part to preserve indentation\nand this part to not be indented";
+ var expected = @"here is some text
+ that needs
+ wrapping where we
+ want the wrapped
+ part to preserve
+ indentation
+and this part to not
+be indented";
+ var wrapper = new TextWrapper(input);
+ EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected);
+ }
+ }
+}
diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs
index 9d6e56fb..23cf0f32 100644
--- a/tests/CommandLine.Tests/Unit/ParserTests.cs
+++ b/tests/CommandLine.Tests/Unit/ParserTests.cs
@@ -803,5 +803,25 @@ public void Parse_options_with_shuffled_index_values()
Assert.Equal("two", args.Arg2);
});
}
+
+
+ [Fact]
+ public void Blank_lines_are_inserted_between_verbs()
+ {
+ // Fixture setup
+ var help = new StringWriter();
+ var sut = new Parser(config => config.HelpWriter = help);
+
+ // Exercize system
+ sut.ParseArguments(new string[] { });
+ var result = help.ToString();
+
+ // Verify outcome
+ var lines = result.ToLines().TrimStringArray();
+ lines[6].Should().BeEquivalentTo("add Add file contents to the index.");
+ lines[8].Should().BeEquivalentTo("help Display more information on a specific command.");
+ lines[10].Should().BeEquivalentTo("version Display version information.");
+ // Teardown
+ }
}
}
diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs
index bd2148a2..1dd8d45f 100644
--- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs
+++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs
@@ -154,14 +154,12 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c
var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
lines[2].Should().BeEquivalentTo(" v, verbose This is the description"); //"The first line should have the arguments and the start of the Help Text.");
//string formattingMessage = "Beyond the second line should be formatted as though it's in a column.";
- lines[3].Should().BeEquivalentTo(" of the verbosity to ");
- lines[4].Should().BeEquivalentTo(" test out the wrapping ");
- lines[5].Should().BeEquivalentTo(" capabilities of the ");
- lines[6].Should().BeEquivalentTo(" Help Text.");
+ lines[3].Should().BeEquivalentTo(" of the verbosity to test");
+ lines[4].Should().BeEquivalentTo(" out the wrapping");
+ lines[5].Should().BeEquivalentTo(" capabilities of the Help");
+ lines[6].Should().BeEquivalentTo(" Text.");
// Teardown
}
-
-
[Fact]
public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_column_given_width_of_100()
@@ -176,7 +174,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c
// Verify outcome
var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
- lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the wrapping capabilities of "); //"The first line should have the arguments and the start of the Help Text.");
+ lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the wrapping capabilities of"); //"The first line should have the arguments and the start of the Help Text.");
//string formattingMessage = "Beyond the second line should be formatted as though it's in a column.";
lines[3].Should().BeEquivalentTo(" the Help Text.");
// Teardown
@@ -196,7 +194,7 @@ public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text
// Verify outcome
var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
- lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the "); //"The first line should have the arguments and the start of the Help Text.");
+ lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the"); //"The first line should have the arguments and the start of the Help Text.");
//string formattingMessage = "Beyond the second line should be formatted as though it's in a column.";
lines[3].Should().BeEquivalentTo(" wrapping capabilities of the Help Text.");
// Teardown
@@ -216,10 +214,10 @@ public void Long_help_text_without_spaces()
// Verify outcome
var lines = sut.ToString().ToNotEmptyLines();
- lines[1].Should().BeEquivalentTo(" v, verbose Before ");
+ lines[1].Should().BeEquivalentTo(" v, verbose Before");
lines[2].Should().BeEquivalentTo(" 012345678901234567890123");
lines[3].Should().BeEquivalentTo(" After");
- lines[4].Should().BeEquivalentTo(" input-file Before ");
+ lines[4].Should().BeEquivalentTo(" input-file Before");
lines[5].Should().BeEquivalentTo(" 012345678901234567890123");
lines[6].Should().BeEquivalentTo(" 456789 After");
// Teardown
@@ -238,12 +236,12 @@ public void Long_pre_and_post_lines_without_spaces()
// Verify outcome
var lines = sut.ToString().ToNotEmptyLines();
- lines[1].Should().BeEquivalentTo("Before ");
+ lines[1].Should().BeEquivalentTo("Before");
lines[2].Should().BeEquivalentTo("0123456789012345678901234567890123456789");
lines[3].Should().BeEquivalentTo("012 After");
- lines[lines.Length - 3].Should().BeEquivalentTo("Before ");
+ lines[lines.Length - 3].Should().BeEquivalentTo("Before");
lines[lines.Length - 2].Should().BeEquivalentTo("0123456789012345678901234567890123456789");
- lines[lines.Length - 1].Should().BeEquivalentTo(" After");
+ lines[lines.Length - 1].Should().BeEquivalentTo("After");
// Teardown
}
@@ -296,7 +294,6 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text(
// Verify outcome
var lines = errorsText.ToNotEmptyLines();
-
lines[0].Should().BeEquivalentTo(" ERR badtoken");
lines[1].Should().BeEquivalentTo(" ERR x, switch");
lines[2].Should().BeEquivalentTo(" ERR unknown");
@@ -324,7 +321,6 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text()
// Verify outcome
var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray();
-
lines[0].Should().Be(HeadingInfo.Default.ToString());
lines[1].Should().Be(CopyrightInfo.Default.ToString());
lines[2].Should().BeEquivalentTo("ERROR(S):");
@@ -381,7 +377,6 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo
// Verify outcome
var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray();
-
lines[0].Should().Be(HeadingInfo.Default.ToString());
lines[1].Should().Be(CopyrightInfo.Default.ToString());
lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which changes to commit.");
@@ -517,7 +512,6 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte
// Teardown
}
-#if !PLATFORM_DOTNET
[Fact]
public void Default_set_to_sequence_should_be_properly_printed()
{
@@ -543,7 +537,6 @@ public void Default_set_to_sequence_should_be_properly_printed()
// Teardown
}
-#endif
[Fact]
public void AutoBuild_when_no_assembly_attributes()
@@ -590,7 +583,6 @@ public void AutoBuild_with_assembly_title_and_version_attributes_only()
actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion));
}
-
[Fact]
public void AutoBuild_with_assembly_company_attribute_only()
{
@@ -624,6 +616,128 @@ 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 HelpTextHonoursLineBreaks()
+ {
+ // Fixture setup
+ // Exercize system
+ var sut = new HelpText {AddDashesToOption = true}
+ .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)),
+ Enumerable.Empty()));
+
+ // Verify outcome
+
+ var lines = sut.ToString().ToNotEmptyLines();
+ lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description.");
+ lines[1].Should().BeEquivalentTo(" It has multiple lines.");
+ lines[2].Should().BeEquivalentTo(" We also want to ensure that indentation is correct.");
+
+ // Teardown
+ }
+
+ [Fact]
+ public void HelpTextHonoursIndentationAfterLineBreaks()
+ {
+ // Fixture setup
+ // Exercize system
+ var sut = new HelpText {AddDashesToOption = true}
+ .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)),
+ Enumerable.Empty()));
+
+ // Verify outcome
+
+ var lines = sut.ToString().ToNotEmptyLines();
+ lines[3].Should().BeEquivalentTo(" --stringvalu2 This is a help text description where we want");
+ lines[4].Should().BeEquivalentTo(" the left pad after a linebreak to be honoured so that");
+ lines[5].Should().BeEquivalentTo(" we can sub-indent within a description.");
+
+ // Teardown
+ }
+
+ [Fact]
+ public void HelpTextPreservesIndentationAcrossWordWrap()
+ {
+ // Fixture setup
+ // Exercise system
+ var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 60}
+ .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)),
+ Enumerable.Empty()));
+
+ // Verify outcome
+
+ var lines = sut.ToString().ToNotEmptyLines();
+ lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description where we");
+ lines[1].Should().BeEquivalentTo(" want:");
+ lines[2].Should().BeEquivalentTo(" * The left pad after a linebreak to");
+ lines[3].Should().BeEquivalentTo(" be honoured and the indentation to be");
+ lines[4].Should().BeEquivalentTo(" preserved across to the next line");
+ lines[5].Should().BeEquivalentTo(" * The ability to return to no indent.");
+ lines[6].Should().BeEquivalentTo(" Like this.");
+
+ // Teardown
+ }
+
+ [Fact]
+ public void HelpTextIsConsitentRegardlessOfCompileTimeLineStyle()
+ {
+ // Fixture setup
+ // Exercize system
+ var sut = new HelpText {AddDashesToOption = true}
+ .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithMixedLineBreaks_Options)),
+ Enumerable.Empty()));
+
+ // Verify outcome
+
+ var lines = sut.ToString().ToNotEmptyLines();
+ lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description");
+ lines[1].Should().BeEquivalentTo(" It has multiple lines.");
+ lines[2].Should().BeEquivalentTo(" Third line");
+
+ // Teardown
+ }
+
+ [Fact]
+ public void HelpTextPreservesIndentationAcrossWordWrapWithSmallMaximumDisplayWidth()
+ {
+ // Fixture setup
+ // Exercise system
+ var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 10}
+ .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)),
+ Enumerable.Empty()));
+
+ // Verify outcome
+
+ Assert.True(sut.ToString().Length>0);
+
+ // Teardown
+ }
+
+ [Fact]
+ public void Options_should_be_separated_by_spaces()
+ {
+ // Fixture setup
+ var handlers = new CultureInfo("en-US").MakeCultureHandlers();
+ var fakeResult =
+ new NotParsed(
+ typeof(Options_With_Default_Set_To_Sequence).ToTypeInfo(),
+ Enumerable.Empty()
+ );
+
+ // Exercize system
+ handlers.ChangeCulture();
+ var helpText = HelpText.AutoBuild(fakeResult);
+ handlers.ResetCulture();
+
+ // Verify outcome
+ var text = helpText.ToString();
+ var lines = text.ToLines().TrimStringArray();
+ Console.WriteLine(text);
+ lines[3].Should().Be("-z, --strseq (Default: a b c)");
+ lines[5].Should().Be("-y, --intseq (Default: 1 2 3)");
+ lines[7].Should().Be("-q, --dblseq (Default: 1.1 2.2 3.3)");
+
+ // Teardown
+ }
}
}