Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit 9ef4953

Browse files
author
Peter Huene
committed
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.
1 parent 2ff85cd commit 9ef4953

16 files changed

+714
-0
lines changed

src/dotnet/CommonLocalizableStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,4 +619,7 @@ setx PATH "%PATH%;{0}"
619619
<data name="ToolPackageConflictPackageId" xml:space="preserve">
620620
<value>Tool '{0}' (version '{1}') is already installed.</value>
621621
</data>
622+
<data name="ColumnMaxWidthMustBeGreaterThanZero" xml:space="preserve">
623+
<value>Column maximum width must be greater than zero.</value>
624+
</data>
622625
</root>

src/dotnet/PrintableTable.cs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Globalization;
7+
using System.Linq;
8+
using System.Text;
9+
using Microsoft.DotNet.Tools;
10+
11+
namespace Microsoft.DotNet.Cli
12+
{
13+
// Represents a table (with rows of type T) that can be printed to a terminal.
14+
internal class PrintableTable<T>
15+
{
16+
private const string ColumnDelimiter = " ";
17+
private List<Column> _columns = new List<Column>();
18+
19+
private class Column
20+
{
21+
public string Header { get; set; }
22+
public Func<T, string> GetContent { get; set; }
23+
public int MaxWidth { get; set; }
24+
public override string ToString() { return Header; }
25+
}
26+
27+
public void AddColumn(string header, Func<T, string> getContent, int maxWidth = int.MaxValue)
28+
{
29+
if (getContent == null)
30+
{
31+
throw new ArgumentNullException(nameof(getContent));
32+
}
33+
34+
if (maxWidth <= 0)
35+
{
36+
throw new ArgumentException(
37+
CommonLocalizableStrings.ColumnMaxWidthMustBeGreaterThanZero,
38+
nameof(maxWidth));
39+
}
40+
41+
_columns.Add(
42+
new Column() {
43+
Header = header,
44+
GetContent = getContent,
45+
MaxWidth = maxWidth
46+
});
47+
}
48+
49+
public void PrintRows(IEnumerable<T> rows, Action<string> writeLine)
50+
{
51+
if (rows == null)
52+
{
53+
throw new ArgumentNullException(nameof(rows));
54+
}
55+
56+
if (writeLine == null)
57+
{
58+
throw new ArgumentNullException(nameof(writeLine));
59+
}
60+
61+
var widths = CalculateColumnWidths(rows);
62+
var totalWidth = CalculateTotalWidth(widths);
63+
if (totalWidth == 0)
64+
{
65+
return;
66+
}
67+
68+
foreach (var line in EnumerateHeaderLines(widths))
69+
{
70+
writeLine(line);
71+
}
72+
73+
writeLine(new string('-', totalWidth));
74+
75+
foreach (var row in rows)
76+
{
77+
foreach (var line in EnumerateRowLines(row, widths))
78+
{
79+
writeLine(line);
80+
}
81+
}
82+
}
83+
84+
public int CalculateWidth(IEnumerable<T> rows)
85+
{
86+
if (rows == null)
87+
{
88+
throw new ArgumentNullException(nameof(rows));
89+
}
90+
91+
return CalculateTotalWidth(CalculateColumnWidths(rows));
92+
}
93+
94+
private IEnumerable<string> EnumerateHeaderLines(int[] widths)
95+
{
96+
if (_columns.Count != widths.Length)
97+
{
98+
throw new InvalidOperationException();
99+
}
100+
101+
return EnumerateLines(
102+
widths,
103+
_columns.Select(c => new StringInfo(c.Header ?? "")).ToArray());
104+
}
105+
106+
private IEnumerable<string> EnumerateRowLines(T row, int[] widths)
107+
{
108+
if (_columns.Count != widths.Length)
109+
{
110+
throw new InvalidOperationException();
111+
}
112+
113+
return EnumerateLines(
114+
widths,
115+
_columns.Select(c => new StringInfo(c.GetContent(row) ?? "")).ToArray());
116+
}
117+
118+
private static IEnumerable<string> EnumerateLines(int[] widths, StringInfo[] contents)
119+
{
120+
if (widths.Length != contents.Length)
121+
{
122+
throw new InvalidOperationException();
123+
}
124+
125+
if (contents.Length == 0)
126+
{
127+
yield break;
128+
}
129+
130+
var builder = new StringBuilder();
131+
for (int line = 0; true; ++line)
132+
{
133+
builder.Clear();
134+
135+
bool emptyLine = true;
136+
bool appendDelimiter = false;
137+
for (int i = 0; i < contents.Length; ++i)
138+
{
139+
// Skip zero-width columns entirely
140+
if (widths[i] == 0)
141+
{
142+
continue;
143+
}
144+
145+
if (appendDelimiter)
146+
{
147+
builder.Append(ColumnDelimiter);
148+
}
149+
150+
var startIndex = line * widths[i];
151+
var length = contents[i].LengthInTextElements;
152+
if (startIndex < length)
153+
{
154+
var endIndex = (line + 1) * widths[i];
155+
length = endIndex >= length ? length - startIndex : widths[i];
156+
builder.Append(contents[i].SubstringByTextElements(startIndex, length));
157+
builder.Append(' ', widths[i] - length);
158+
emptyLine = false;
159+
}
160+
else
161+
{
162+
// No more content for this column; append whitespace to fill remaining space
163+
builder.Append(' ', widths[i]);
164+
}
165+
166+
appendDelimiter = true;
167+
}
168+
169+
if (emptyLine)
170+
{
171+
// Yield an "empty" line on the first line only
172+
if (line == 0)
173+
{
174+
yield return builder.ToString();
175+
}
176+
yield break;
177+
}
178+
179+
yield return builder.ToString();
180+
}
181+
}
182+
183+
private int[] CalculateColumnWidths(IEnumerable<T> rows)
184+
{
185+
return _columns
186+
.Select(c => {
187+
var width = new StringInfo(c.Header ?? "").LengthInTextElements;
188+
189+
foreach (var row in rows)
190+
{
191+
width = Math.Max(
192+
width,
193+
new StringInfo(c.GetContent(row) ?? "").LengthInTextElements);
194+
}
195+
196+
return Math.Min(width, c.MaxWidth);
197+
})
198+
.ToArray();
199+
}
200+
201+
private static int CalculateTotalWidth(int[] widths)
202+
{
203+
int sum = 0;
204+
int count = 0;
205+
206+
foreach (var width in widths)
207+
{
208+
if (width == 0)
209+
{
210+
// Skip zero-width columns
211+
continue;
212+
}
213+
214+
sum += width;
215+
++count;
216+
}
217+
218+
if (count == 0)
219+
{
220+
return 0;
221+
}
222+
223+
return sum + (ColumnDelimiter.Length * (count - 1));
224+
}
225+
}
226+
}

src/dotnet/xlf/CommonLocalizableStrings.cs.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.de.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.es.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.fr.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.it.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.ja.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.ko.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

src/dotnet/xlf/CommonLocalizableStrings.pl.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@ setx PATH "%PATH%;{0}"
848848
<target state="new">Failed to uninstall tool package '{0}': {1}</target>
849849
<note />
850850
</trans-unit>
851+
<trans-unit id="ColumnMaxWidthMustBeGreaterThanZero">
852+
<source>Column maximum width must be greater than zero.</source>
853+
<target state="new">Column maximum width must be greater than zero.</target>
854+
<note />
855+
</trans-unit>
851856
</body>
852857
</file>
853858
</xliff>

0 commit comments

Comments
 (0)