TextTableBuilder 1.3.1
dotnet add package TextTableBuilder --version 1.3.1
NuGet\Install-Package TextTableBuilder -Version 1.3.1
<PackageReference Include="TextTableBuilder" Version="1.3.1" />
paket add TextTableBuilder --version 1.3.1
#r "nuget: TextTableBuilder, 1.3.1"
// Install TextTableBuilder as a Cake Addin #addin nuget:?package=TextTableBuilder&version=1.3.1 // Install TextTableBuilder as a Cake Tool #tool nuget:?package=TextTableBuilder&version=1.3.1
TextTableBuilder
A simple, opinionated, modern table builder. Supports configuring how different datatypes will be formatted. Available as Nuget package
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
There are more examples below.
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()
.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 / fixed width for the column as well as a TypeHandler
(see Type Handling).
Column widths
A column, by default, simply stretches to be wide enough to contain all values in that column. You can, however, specify a minimum width (MinWidth
) or a width (Width
). The MinWidth
ensures the column is always at least the number of specified characters wide, but may be wider when the column contains longer values. The Width
ensures a column is always exactly the specified width. Longer values will be truncated. Note that truncating depends on the alignment of the values. Right-aligned values will be truncated from the left, left-aligned values will be truncated from the right and center-aligned values will be truncated from both sides.
To specifiy a width, use either the AddColumn()
overload that allows you to pass an optional minWidth
or width
argument, or the AddColumn(Column)
overload and specify the width
or minWidth
with the Column
's constructor arguments.
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()
.AddColumns(new[] { "No.", "Name", "Position", "^Salary^" })
.AddRow(1, "Bill Gates", "Founder Microsoft", 10000m)
// etc ...
...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 instead of the TypeHandlers
property we can also use the AddTypeHandler()
method:
tablebuilder.AddTypeHandler<decimal>((value, formatProvider) => string.Format("$ {0:N2}", value));
And for those about to point out this can be written even shorter:
tablebuilder.AddTypeHandler<decimal>((v, _) => $"$ {v:N2}");
A TypeHandler
can also be passed to a Column
's constructor, in which case that TypeHandler
is used for all values in that column.
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),
// etc ...
};
Default object handling
By default the TextTableBuilder outputs properties of objects in alfabetical order; for our example that just happens to work out:
var table = new Table()
.AddColumns(new[] { "Name", "Position", "^Salary^" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
Console.WriteLine(tablebuilder.Build(table));
The order of the outputted properties can be changed by using a ColumnOrder attribute. Properties (or fields) that don't have this attribute will be ordered by name.
You'll probably want (a lot) more control; in which case you should look into Custom object handling.
Custom object handling
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()
.AddColumns(new[] { "Name", "Position", "^Salary^" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
// Specify object handler to use for persons
tablebuilder.ObjectHandlers.AddHandler<Person>(new PersonHandler());
// Or, alternatively:
tablebuilder.AddObjectHandler<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.AddObjectHandler<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)),
// etc ...
};
var table = new 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()
.AddColumns(new[] { "Position", "Name", "^Salary^", "Birthdate", "Alma mater", "Spouse" })
.AddRows(persons);
var tablebuilder = new TableBuilder();
tablebuilder.AddTypeHandler<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.
Table Renderers
The TextTableBuilder uses an ITableRenderer
to do the actual 'rendering' of the table. The TableRenderer is provided with RenderColums
, which provide column information, and an IEnumerable<string[]>
which represents the rows and values. The values have been formatted at this point; the table renderer takes care of aligning, padding etc.
By default, the TextTableBuilder uses the DefaultTableRenderer
which produced the above examples. A few other, very simple, renderers are provided. These are the MinimalTableRenderer
and MSDOSTableRenderer
, SimpleLineTableRenderer
, SingleLineTableRenderer
, DoubleLineTableRenderer
, HatchedTableRenderer
, DotsTableRenderer
and RounderCornersTableRenderer
.
To use a specific ITableRenderer
you pass one to the Build()
method:
Console.WriteLine(tablebuilder.Build(table, new MSDOSTableRenderer()));
Going back to our very first example, the following styles are currently provided. More may be added in the future (as well as ANSI color support etc.) but it's also trivial to build your own; just implement ITableRenderer
:
DefaultTableRenderer:
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
MinimalTableRenderer:
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
MSDOSTableRenderer:
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
SimpleLineTableRenderer:
+----+-----------------+-------------------+----------------+
| 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 |
+----+-----------------+-------------------+----------------+
SingleLineTableRenderer:
┌────┬─────────────────┬───────────────────┬────────────────┐
│ 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 │
└────┴─────────────────┴───────────────────┴────────────────┘
DoubleLineTableRenderer:
╔════╦═════════════════╦═══════════════════╦════════════════╗
║ 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 ║
╚════╩═════════════════╩═══════════════════╩════════════════╝
RoundedCornersTableRenderer:
╭────┬─────────────────┬───────────────────┬────────────────╮
│ 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 │
╰────┴─────────────────┴───────────────────┴────────────────╯
HatchedTableRenderer:
/----+-----------------+-------------------+----------------\
| 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 |
\----+-----------------+-------------------+----------------/
DotsTableRenderer:
.............................................................
: 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 :
.............................................................
Examples
With all the examples above demonstrating a specific option each, you may not have noticed how easy this package can make your life (that's what it's meant to do). So here's an example that shows typical usage. Assuming you have a Person
class/record but you can't (or don't want to) 'pollute' it with ColumnOrder
-attributes:
var table = new Table()
.AddColumns(new[] { "Name", "Position", "^Salary^" })
.AddRows(DBContext.Persons.Where(p => p.Salary > 100));
var tablebuilder = new TableBuilder()
.AddObjectHandler<Person>( // Specify object handler to use for persons
(person, columnCount) => new object[] { person.Name, person.Position, person.Salary }
);
Console.WriteLine(tablebuilder.Build(table));
Icon made by Freepik from www.flaticon.com is licensed by CC 3.0.
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 |