Implement a printable table.
This commit implements a simple printable table that can be used to display tabular data. The columns of the table can specify a maximum width which will cause the column text to wrap around to the next line.
This commit is contained in:
parent
2ff85cdd9a
commit
9ef495327a
16 changed files with 714 additions and 0 deletions
|
@ -619,4 +619,7 @@ setx PATH "%PATH%;{0}"
|
|||
<data name="ToolPackageConflictPackageId" xml:space="preserve">
|
||||
<value>Tool '{0}' (version '{1}') is already installed.</value>
|
||||
</data>
|
||||
<data name="ColumnMaxWidthMustBeGreaterThanZero" xml:space="preserve">
|
||||
<value>Column maximum width must be greater than zero.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
226
src/dotnet/PrintableTable.cs
Normal file
226
src/dotnet/PrintableTable.cs
Normal file
|
@ -0,0 +1,226 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.DotNet.Tools;
|
||||
|
||||
namespace Microsoft.DotNet.Cli
|
||||
{
|
||||
// Represents a table (with rows of type T) that can be printed to a terminal.
|
||||
internal class PrintableTable<T>
|
||||
{
|
||||
private const string ColumnDelimiter = " ";
|
||||
private List<Column> _columns = new List<Column>();
|
||||
|
||||
private class Column
|
||||
{
|
||||
public string Header { get; set; }
|
||||
public Func<T, string> GetContent { get; set; }
|
||||
public int MaxWidth { get; set; }
|
||||
public override string ToString() { return Header; }
|
||||
}
|
||||
|
||||
public void AddColumn(string header, Func<T, string> getContent, int maxWidth = int.MaxValue)
|
||||
{
|
||||
if (getContent == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(getContent));
|
||||
}
|
||||
|
||||
if (maxWidth <= 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
CommonLocalizableStrings.ColumnMaxWidthMustBeGreaterThanZero,
|
||||
nameof(maxWidth));
|
||||
}
|
||||
|
||||
_columns.Add(
|
||||
new Column() {
|
||||
Header = header,
|
||||
GetContent = getContent,
|
||||
MaxWidth = maxWidth
|
||||
});
|
||||
}
|
||||
|
||||
public void PrintRows(IEnumerable<T> rows, Action<string> writeLine)
|
||||
{
|
||||
if (rows == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rows));
|
||||
}
|
||||
|
||||
if (writeLine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writeLine));
|
||||
}
|
||||
|
||||
var widths = CalculateColumnWidths(rows);
|
||||
var totalWidth = CalculateTotalWidth(widths);
|
||||
if (totalWidth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var line in EnumerateHeaderLines(widths))
|
||||
{
|
||||
writeLine(line);
|
||||
}
|
||||
|
||||
writeLine(new string('-', totalWidth));
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
foreach (var line in EnumerateRowLines(row, widths))
|
||||
{
|
||||
writeLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int CalculateWidth(IEnumerable<T> rows)
|
||||
{
|
||||
if (rows == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rows));
|
||||
}
|
||||
|
||||
return CalculateTotalWidth(CalculateColumnWidths(rows));
|
||||
}
|
||||
|
||||
private IEnumerable<string> EnumerateHeaderLines(int[] widths)
|
||||
{
|
||||
if (_columns.Count != widths.Length)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return EnumerateLines(
|
||||
widths,
|
||||
_columns.Select(c => new StringInfo(c.Header ?? "")).ToArray());
|
||||
}
|
||||
|
||||
private IEnumerable<string> EnumerateRowLines(T row, int[] widths)
|
||||
{
|
||||
if (_columns.Count != widths.Length)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return EnumerateLines(
|
||||
widths,
|
||||
_columns.Select(c => new StringInfo(c.GetContent(row) ?? "")).ToArray());
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateLines(int[] widths, StringInfo[] contents)
|
||||
{
|
||||
if (widths.Length != contents.Length)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (contents.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
for (int line = 0; true; ++line)
|
||||
{
|
||||
builder.Clear();
|
||||
|
||||
bool emptyLine = true;
|
||||
bool appendDelimiter = false;
|
||||
for (int i = 0; i < contents.Length; ++i)
|
||||
{
|
||||
// Skip zero-width columns entirely
|
||||
if (widths[i] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appendDelimiter)
|
||||
{
|
||||
builder.Append(ColumnDelimiter);
|
||||
}
|
||||
|
||||
var startIndex = line * widths[i];
|
||||
var length = contents[i].LengthInTextElements;
|
||||
if (startIndex < length)
|
||||
{
|
||||
var endIndex = (line + 1) * widths[i];
|
||||
length = endIndex >= length ? length - startIndex : widths[i];
|
||||
builder.Append(contents[i].SubstringByTextElements(startIndex, length));
|
||||
builder.Append(' ', widths[i] - length);
|
||||
emptyLine = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No more content for this column; append whitespace to fill remaining space
|
||||
builder.Append(' ', widths[i]);
|
||||
}
|
||||
|
||||
appendDelimiter = true;
|
||||
}
|
||||
|
||||
if (emptyLine)
|
||||
{
|
||||
// Yield an "empty" line on the first line only
|
||||
if (line == 0)
|
||||
{
|
||||
yield return builder.ToString();
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private int[] CalculateColumnWidths(IEnumerable<T> rows)
|
||||
{
|
||||
return _columns
|
||||
.Select(c => {
|
||||
var width = new StringInfo(c.Header ?? "").LengthInTextElements;
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
width = Math.Max(
|
||||
width,
|
||||
new StringInfo(c.GetContent(row) ?? "").LengthInTextElements);
|
||||
}
|
||||
|
||||
return Math.Min(width, c.MaxWidth);
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static int CalculateTotalWidth(int[] widths)
|
||||
{
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
|
||||
foreach (var width in widths)
|
||||
{
|
||||
if (width == 0)
|
||||
{
|
||||
// Skip zero-width columns
|
||||
continue;
|
||||
}
|
||||
|
||||
sum += width;
|
||||
++count;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sum + (ColumnDelimiter.Length * (count - 1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
|
@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
|
|||
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
|
||||
<source>Column maximum width must be greater than zero.</source>
|
||||
<target state="new">Column maximum width must be greater than zero.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
420
test/dotnet.Tests/PrintableTableTests.cs
Normal file
420
test/dotnet.Tests/PrintableTableTests.cs
Normal file
|
@ -0,0 +1,420 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Microsoft.DotNet.Cli;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.DotNet.Tests
|
||||
{
|
||||
public class PrintableTableTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GivenNoColumnsItPrintsNoLines()
|
||||
{
|
||||
var table = new PrintableTable<string[]>();
|
||||
|
||||
var lines = new List<string>();
|
||||
table.PrintRows(new string[][] {}, l => lines.Add(l));
|
||||
|
||||
lines.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenAnEmptyRowsCollectionItPrintsColumnHeaders()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"First Column",
|
||||
"2nd Column",
|
||||
"Another Column"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"First Column 2nd Column Another Column",
|
||||
"------------------------------------------------"
|
||||
},
|
||||
ExpectedTableWidth = 48
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenASingleRowItPrintsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"first",
|
||||
"second",
|
||||
"third"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"1st 2nd 3rd ",
|
||||
"----------------------------",
|
||||
"first second third"
|
||||
},
|
||||
ExpectedTableWidth = 28
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenMultipleRowsItPrintsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"First",
|
||||
"Second",
|
||||
"Third",
|
||||
"Fourth",
|
||||
"Fifth"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd",
|
||||
"4th",
|
||||
"5th"
|
||||
},
|
||||
new [] {
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e"
|
||||
},
|
||||
new [] {
|
||||
"much longer string 1",
|
||||
"much longer string 2",
|
||||
"much longer string 3",
|
||||
"much longer string 4",
|
||||
"much longer string 5",
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"First Second Third Fourth Fifth ",
|
||||
"----------------------------------------------------------------------------------------------------------------------------",
|
||||
"1st 2nd 3rd 4th 5th ",
|
||||
"a b c d e ",
|
||||
"much longer string 1 much longer string 2 much longer string 3 much longer string 4 much longer string 5"
|
||||
},
|
||||
ExpectedTableWidth = 124
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenARowWithEmptyStringsItPrintsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"First",
|
||||
"Second",
|
||||
"Third",
|
||||
"Fourth",
|
||||
"Fifth"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd",
|
||||
"4th",
|
||||
"5th"
|
||||
},
|
||||
new [] {
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
},
|
||||
new [] {
|
||||
"much longer string 1",
|
||||
"much longer string 2",
|
||||
"much longer string 3",
|
||||
"much longer string 4",
|
||||
"much longer string 5",
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"First Second Third Fourth Fifth ",
|
||||
"----------------------------------------------------------------------------------------------------------------------------",
|
||||
"1st 2nd 3rd 4th 5th ",
|
||||
" ",
|
||||
"much longer string 1 much longer string 2 much longer string 3 much longer string 4 much longer string 5"
|
||||
},
|
||||
ExpectedTableWidth = 124
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenColumnsWithMaximumWidthsItPrintsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"First",
|
||||
"Second",
|
||||
"Third",
|
||||
},
|
||||
ColumnWidths = new[] {
|
||||
3,
|
||||
int.MaxValue,
|
||||
4
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"123",
|
||||
"1234567890",
|
||||
"1234"
|
||||
},
|
||||
new [] {
|
||||
"1",
|
||||
"1",
|
||||
"1",
|
||||
},
|
||||
new [] {
|
||||
"12345",
|
||||
"a much longer string",
|
||||
"1234567890"
|
||||
},
|
||||
new [] {
|
||||
"123456",
|
||||
"hello world",
|
||||
"12345678"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"Fir Second Thir",
|
||||
"st d ",
|
||||
"---------------------------------------",
|
||||
"123 1234567890 1234",
|
||||
"1 1 1 ",
|
||||
"123 a much longer string 1234",
|
||||
"45 5678",
|
||||
" 90 ",
|
||||
"123 hello world 1234",
|
||||
"456 5678"
|
||||
},
|
||||
ExpectedTableWidth = 39
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenARowContainingUnicodeCharactersItPrintsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"Poem"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new [] {
|
||||
"\u3044\u308D\u306F\u306B\u307B\u3078\u3068\u3061\u308A\u306C\u308B\u3092"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"Poem ",
|
||||
"------------",
|
||||
"\u3044\u308D\u306F\u306B\u307B\u3078\u3068\u3061\u308A\u306C\u308B\u3092"
|
||||
},
|
||||
ExpectedTableWidth = 12
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenARowContainingUnicodeCharactersItWrapsCorrectly()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"Poem"
|
||||
},
|
||||
ColumnWidths = new [] {
|
||||
5
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new [] {
|
||||
"\u3044\u308D\u306F\u306B\u307B\u3078\u3068\u3061\u308A\u306C\u308B\u3092"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"Poem ",
|
||||
"-----",
|
||||
"\u3044\u308D\u306F\u306B\u307B",
|
||||
"\u3078\u3068\u3061\u308A\u306C",
|
||||
"\u308B\u3092 "
|
||||
},
|
||||
ExpectedTableWidth = 5
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenARowContainingUnicodeCombiningCharactersItPrintsCorrectly()
|
||||
{
|
||||
// The unicode string is "test" with "enclosing circle backslash" around each character
|
||||
// Given 0x20E0 is a combining character, the string should be four graphemes in length,
|
||||
// despite having eight codepoints. Thus there should be 10 spaces following the characters.
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"Unicode String"
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new [] {
|
||||
"\u0074\u20E0\u0065\u20E0\u0073\u20E0\u0074\u20E0"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"Unicode String",
|
||||
"--------------",
|
||||
"\u0074\u20E0\u0065\u20E0\u0073\u20E0\u0074\u20E0 "
|
||||
},
|
||||
ExpectedTableWidth = 14
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenARowContainingUnicodeCombiningCharactersItWrapsCorrectly()
|
||||
{
|
||||
// See comment for GivenARowContainingUnicodeCombiningCharactersItPrintsCorrectly regarding string content
|
||||
// This should wrap after the second grapheme rather than the second code point (constituting the first grapheme)
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"01"
|
||||
},
|
||||
ColumnWidths = new[] {
|
||||
2
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new [] {
|
||||
"\u0074\u20E0\u0065\u20E0\u0073\u20E0\u0074\u20E0"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"01",
|
||||
"--",
|
||||
"\u0074\u20E0\u0065\u20E0",
|
||||
"\u0073\u20E0\u0074\u20E0"
|
||||
},
|
||||
ExpectedTableWidth = 2
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenAnEmptyColumnHeaderItPrintsTheColumnHeaderAsEmpty()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"First",
|
||||
"",
|
||||
"Third",
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"First Third",
|
||||
"-------------------------",
|
||||
"1st 2nd 3rd "
|
||||
},
|
||||
ExpectedTableWidth = 25
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenAllEmptyColumnHeadersItPrintsTheEntireHeaderAsEmpty()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
null,
|
||||
"",
|
||||
null,
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd"
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
" ",
|
||||
"---------------------",
|
||||
"1st 2nd 3rd"
|
||||
},
|
||||
ExpectedTableWidth = 21
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenZeroWidthColumnsItSkipsTheColumns()
|
||||
{
|
||||
RunTest(new TestData() {
|
||||
Columns = new[] {
|
||||
"",
|
||||
"First",
|
||||
null,
|
||||
"Second",
|
||||
""
|
||||
},
|
||||
Rows = new string[][] {
|
||||
new[] {
|
||||
"",
|
||||
"1st",
|
||||
null,
|
||||
"2nd",
|
||||
""
|
||||
}
|
||||
},
|
||||
ExpectedLines = new[] {
|
||||
"First Second",
|
||||
"-----------------",
|
||||
"1st 2nd "
|
||||
},
|
||||
ExpectedTableWidth = 17
|
||||
});
|
||||
}
|
||||
|
||||
public class TestData
|
||||
{
|
||||
public IEnumerable<string> Columns { get; set; }
|
||||
public int[] ColumnWidths { get; set; }
|
||||
public IEnumerable<string[]> Rows { get; set; }
|
||||
public IEnumerable<string> ExpectedLines { get; set; }
|
||||
public int ExpectedTableWidth { get; set; }
|
||||
}
|
||||
|
||||
private void RunTest(TestData data)
|
||||
{
|
||||
var table = new PrintableTable<string[]>();
|
||||
|
||||
int index = 0;
|
||||
foreach (var column in data.Columns)
|
||||
{
|
||||
var i = index;
|
||||
table.AddColumn(
|
||||
column,
|
||||
r => r[i],
|
||||
data.ColumnWidths?[i] ?? int.MaxValue);
|
||||
++index;
|
||||
}
|
||||
|
||||
var lines = new List<string>();
|
||||
table.PrintRows(data.Rows, l => lines.Add(l));
|
||||
|
||||
lines.Should().Equal(data.ExpectedLines);
|
||||
table.CalculateWidth(data.Rows).Should().Be(data.ExpectedTableWidth);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue