TextTableBuilder 1.0.0
See the version list below for details.
dotnet add package TextTableBuilder --version 1.0.0
NuGet\Install-Package TextTableBuilder -Version 1.0.0
<PackageReference Include="TextTableBuilder" Version="1.0.0" />
paket add TextTableBuilder --version 1.0.0
#r "nuget: TextTableBuilder, 1.0.0"
// Install TextTableBuilder as a Cake Addin #addin nuget:?package=TextTableBuilder&version=1.0.0 // Install TextTableBuilder as a Cake Tool #tool nuget:?package=TextTableBuilder&version=1.0.0
<img src="logo.png" width="64" height="64" alt="logo" align="left"> TextTableBuilder
A simple, opnionated, modern table builder. Supports configuring how different datatypes will be formatted.
Quickstart
// Create table
var table = new Table();
table.AddColumn("No")
.AddColumn("Name")
.AddColumn("Position")
.AddColumn("Salary", Align.Right, Align.Right) // Align column header and values to the right
.AddRow("1", "Bill Gates", "Founder Microsoft", 10000)
.AddRow("2", "Steve Jobs", "Founder Apple", 1200000)
.AddRow("3", "Larry Page", "Founder Google", 1100000)
.AddRow("4", "Mark Zuckerberg", "Founder Facebook", 1300000);
// Use TableBuilder to render table
var tablebuilder = new TableBuilder();
Console.WriteLine(tablebuilder.Build(table));
No | Name | Position | Salary
-- | --------------- | ----------------- | ---------
1 | Bill Gates | Founder Microsoft | 10,000
2 | Steve Jobs | Founder Apple | 1,200,000
3 | Larry Page | Founder Google | 1,100,000
4 | Mark Zuckerberg | Founder Facebook | 1,300,000
Convenience methods
Columns
An easier, quicker way to add columns is to invoke AddColumns()
. By passing an array of column names all columns can be specified in one call:
var table = new Table();
table.AddColumns(new[] { "No.", "Name", "Position", "^Salary^" })
.AddRow("1", "Bill Gates", "Founder Microsoft", 10000)
// etc...
For aligning columns, see Aligning columns and values.
Rows
Rows can be added in three ways:
AddRow(Row row)
<br>Either pass aValueRow
orObjectRow
AddRow(params object[] values)
<br>Pass all values (e.g..AddRow("foo", 123, "bar)
)AddRow<T>(value)
<br>Pass an object (e.g.AddRow<Customer>(paul)
) (see Type handling)
Method 2 adds a ValueRow
to the table whereas method 3 adds an ObjectRow
to the table. Method 1 is provided only for completeness' sake.
For aligning row values, see Aligning columns and values.
Aligning columns and values
Columnames can be prefixed and suffixed with:
^
Align right~
Align center
When a columnname is specified as "^Salary"
, the column name will be right aligned, the values will default to left. When the name is specified as "Salary^"
the column values will be right aligned, the column name itself will default to left aligned. And, finally, when the name is specified as "^Salary^"
then both the column name and values will be right aligned.
If you want more control over a column you'll need to use the AddColumn()
method which allows you to specify a minimum width for the column as well as a TypeHandler
(see Type Handling).
Internationalization (i18n)
TextTableBuilder supports i18n by supporting an IFormatProvider
which can be specified by passing it to the Build()
method. The above example is based on an en_US
locale. If we pass another locale, we get:
Console.WriteLine(tablebuilder.Build(table, new CultureInfo("nl_NL")));
No | Name | Position | Salary
-- | --------------- | ----------------- | ---------
1 | Bill Gates | Founder Microsoft | 10.000
2 | Steve Jobs | Founder Apple | 1.200.000
3 | Larry Page | Founder Google | 1.100.000
4 | Mark Zuckerberg | Founder Facebook | 1.300.000
By default, unless specified otherwise, the TaxtTableBuilder uses the current UI locale (CultureInfo.CurrentUICulture
).
Type handling
By default TextTableBuilder comes with type handlers for all primitives (e.g. int
, decimal
, ...) and some other common types like DateTime
and TimeSpan
. However, you can customize how a type is formatted by specifying a TypeHandler
that implements ITypeHandler
.
TextTableBuilder will first try to use the TypeHandler
for the column being formatted; when no TypeHandler
is specified for a column then the type of the value is used to determine which TypeHandler
to use.
An example of a typehandler is:
public class CurrencyTypeHandler : ITypeHandler
{
public string Handle(object value, IFormatProvider formatProvider)
=> string.Format("$ {0:N2}", value);
}
So when we then specify our values as decimals (by adding the m
-suffix)...
var table = new Table();
table.AddColumns(new[] { "No.", "Name", "Position", "^Salary^" })
.AddRow("1", "Bill Gates", "Founder Microsoft", 10000m)
.AddRow("2", "Steve Jobs", "Founder Apple", 1200000m)
.AddRow("3", "Larry Page", "Founder Google", 1100000m)
.AddRow("4", "Mark Zuckerberg", "Founder Facebook", 1300000m);
...and we register our new CurrencyTypeHandler
...
var tablebuilder = new TableBuilder();
tablebuilder.TypeHandlers.AddHandler<decimal>(new CurrencyTypeHandler());
Console.WriteLine(tablebuilder.Build(table, new CultureInfo("en_US")));
...we get:
No. | Name | Position | Salary
--- | --------------- | ----------------- | --------------
1 | Bill Gates | Founder Microsoft | $ 10.000,00
2 | Steve Jobs | Founder Apple | $ 1.200.000,00
3 | Larry Page | Founder Google | $ 1.100.000,00
4 | Mark Zuckerberg | Founder Facebook | $ 1.300.000,00
An alternative method of creating a TypeHandler
is to inherit from DelegatingTypeHandler<T>
which allows you to simply use a delegate function:
public class CurrencyTypeHandler : DelegatingTypeHandler<decimal>
{
public CurrencyTypeHandler()
: base((value, formatProvider) => string.Format("$ {0:N2}", value)) { }
}
Or, even shorter:
tablebuilder.TypeHandlers.AddHandler<decimal>(new DelegatingTypeHandler<decimal>((value, fp) => string.Format("$ {0:N2}", value)));
And still shorter:
tablebuilder.TypeHandlers.AddHandler<decimal>((value, formatProvider) => string.Format("$ {0:N2}", value));
And for those about to point out this can be written even shorter:
tablebuilder.TypeHandlers.AddHandler<decimal>((v, _) => $"$ {v:N2}");
Null value handling
A special case is the NullValueHandler
; by default a null
value is formatted as an empty string. However, you may want to show null
values as "<NULL>
" for example. To accomplish this we simply use the built-in NullValueHandler
:
tablebuilder.TypeHandlers.NullValueHandler = new NullHandler("<NULL>");
It is possible to implement your own NullValueHandler
by implementing INullValueHandler
.
Object handling
For the following examples we're going to assume a collection of persons:
public record Person(string Name, string Position, decimal Salary);
var persons = new[]
{
new Person("Bill Gates", "Founder Microsoft", 10000m),
new Person("Steve Jobs", "Founder Apple", 1200000m),
new Person("Larry Page", "Founder Google", 1100000m),
new Person("Mark Zuckerberg", "Founder Facebook", 1300000m),
};
First, we implement an IObjectHandler
:
public class PersonHandler : IObjectHandler
{
public object[] Handle(object value, int columnCount)
{
var person = (Person)value;
// Return properties as value array
return new object[] { person.Name, person.Position, person.Salary };
}
}
After that, building a table for this data is simple:
var table = new Table();
table.AddColumns(new[] { "Name", "Position", "^Salary^" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
// Specify object handler to use for persons
tablebuilder.ObjectHandlers.AddHandler<Person>(new PersonHandler());
Console.WriteLine(tablebuilder.Build(table));
Which outputs:
Name | Position | Salary
--------------- | ----------------- | ------------
Bill Gates | Founder Microsoft | 10.000,00
Steve Jobs | Founder Apple | 1.200.000,00
Larry Page | Founder Google | 1.100.000,00
Mark Zuckerberg | Founder Facebook | 1.300.000,00
TextTableBuilder will still use the TypeHandler
s to handle the types of the values as always.
A shorter method is to inherit from the DelegateObjectHandler<T>
:
public class PersonHandler : DelegatingObjectHandler<Person>
{
public PersonHandler()
: base((person, columnCount) => new object[] { person.Name, person.Position, person.Salary }) { }
}
Even shorter:
tablebuilder.ObjectHandlers.AddHandler<Person>(new DelegatingObjectHandler<decimal>((person, fp) => new object[] { person.Name, person.Position, person.Salary }));
Still shorter:
tablebuilder.ObjectHandlers.AddHandler<Person>((person, columnCount) => new object[] { person.Name, person.Position, person.Salary });
When no handler for a specific object can be found then the DefaultObjectHandler
is used which simply takes all readable properties and returns those in alfabetical order unless...
ColumnOrder attribute
When adding rows by adding objects directly (e.g. .AddRow(myperson)
where myperson
is a Person
objecy) the order of the properties can be specified for the DefaultObjectHandler
. If you implement your own IObjectHandler
then you need to either return the values in te correct order or look for the ColumnOrder
attribute and use it's Order
property to determine the order of the properties.
public record Person(
[property: ColumnOrder(2)] string Name,
[property: ColumnOrder(1)] string Position,
[property: ColumnOrder(3)] decimal Salary,
[property: ColumnOrder(4)] DateTime DateOfBirth
);
Or, a bit more old-fashioned:
public class Person
{
[ColumnOrder(2)]
public string Name { get; set; }
[ColumnOrder(1)]
public string Position { get; set; }
[ColumnOrder(3)]
public decimal Salary { get; set; }
[ColumnOrder(4)]
public DateTime DateOfBirth { get; set; }
}
If we now print the table:
var persons = new[]
{
new Person("Bill Gates", "Founder Microsoft", 10000m, new DateTime(1955, 10, 28)),
new Person("Steve Jobs", "Founder Apple", 1200000m, new DateTime(1955, 2, 24)),
new Person("Larry Page", "Founder Google", 1100000m, new DateTime(1973, 3, 26)),
new Person("Mark Zuckerberg", "Founder Facebook", 1300000m, new DateTime(1984, 3, 14)),
};
var table = new Table();
table.AddColumns(new[] { "Position", "Name", "^Salary^" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
Console.WriteLine(tablebuilder.Build(table));
The result is:
Position | Name | Salary
----------------- | --------------- | ------------
Founder Microsoft | Bill Gates | 10,000.00
Founder Apple | Steve Jobs | 1,200,000.00
Founder Google | Larry Page | 1,100,000.00
Founder Facebook | Mark Zuckerberg | 1,300,000.00
Note the DateOfBirth column is missing; this is because the DefaultObjectHandler
, by default, only takes the number of properties equal to the number of columns.
However, if we print the table like this:
var table = new Table();
table.AddColumns(new[] { "Position", "Name", "^Salary^", "Birthdate", "Alma mater", "Spouse" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
tablebuilder.TypeHandlers.AddHandler<DateTime>(new DelegatingTypeHandler<DateTime>((date, formatprovider) => $"{date:yyyy-MM-dd}"));
Console.WriteLine(tablebuilder.Build(table));
The result is:
Position | Name | Salary | Birthdate | Alma mater | Spouse
----------------- | --------------- | ------------ | ---------- | ---------- | ------
Founder Microsoft | Bill Gates | 10,000.00 | 1955-10-28 | |
Founder Apple | Steve Jobs | 1,200,000.00 | 1955-02-24 | |
Founder Google | Larry Page | 1,100,000.00 | 1973-03-26 | |
Founder Facebook | Mark Zuckerberg | 1,300,000.00 | 1984-03-14 | |
The DefaultObjectHandler
, by default, pads all rows with missing values with null
values.
Styles
The TextTableBuilder has support for (very simple) styles. These can be specified as an optional argument to the Build()
method. Currently only a very few styles are supported.
To specify a style, invoke the Build()
method with a style
argument:
Console.WriteLine(tablebuilder.Build(table, TableStyle.MSDOS));
Going back to our very first example, the following styles are currently implemented. More may be added in the future (as well as ANSI color support etc.):
TableStyle.Default
No | Name | Position | Salary
-- | --------------- | ----------------- | ---------
1 | Bill Gates | Founder Microsoft | 10,000
2 | Steve Jobs | Founder Apple | 1,200,000
3 | Larry Page | Founder Google | 1,100,000
4 | Mark Zuckerberg | Founder Facebook | 1,300,000
TableStyle.Minimal
No Name Position Salary
1 Bill Gates Founder Microsoft 10,000
2 Steve Jobs Founder Apple 1,200,000
3 Larry Page Founder Google 1,100,000
4 Mark Zuckerberg Founder Facebook 1,300,000
TableStyle.MSDOS
No║Name ║Position ║ Salary
══║═══════════════║═════════════════║═════════
1 ║Bill Gates ║Founder Microsoft║ 10,000
2 ║Steve Jobs ║Founder Apple ║1,200,000
3 ║Larry Page ║Founder Google ║1,100,000
4 ║Mark Zuckerberg║Founder Facebook ║1,300,000
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.3.1 | 179 | 3/7/2024 |
1.3.0 | 102 | 3/7/2024 |
1.2.2 | 122 | 3/7/2024 |
1.2.1 | 105 | 3/7/2024 |
1.2.0 | 2,033 | 5/3/2022 |
1.1.3 | 408 | 4/20/2022 |
1.1.2 | 414 | 4/14/2022 |
1.1.1 | 412 | 4/12/2022 |
1.1.0 | 417 | 4/12/2022 |
1.0.5 | 401 | 4/11/2022 |
1.0.4 | 416 | 4/11/2022 |
1.0.3 | 410 | 4/11/2022 |
1.0.2 | 412 | 4/11/2022 |
1.0.1 | 416 | 4/11/2022 |
1.0.0 | 419 | 4/11/2022 |