TextTableBuilder 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package TextTableBuilder --version 1.0.0                
NuGet\Install-Package TextTableBuilder -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TextTableBuilder" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TextTableBuilder --version 1.0.0                
#r "nuget: TextTableBuilder, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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:

  1. AddRow(Row row)<br>Either pass a ValueRow or ObjectRow
  2. AddRow(params object[] values)<br>Pass all values (e.g. .AddRow("foo", 123, "bar))
  3. 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 TypeHandlers 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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 181 3/7/2024
1.3.0 104 3/7/2024
1.2.2 124 3/7/2024
1.2.1 109 3/7/2024
1.2.0 2,035 5/3/2022
1.1.3 410 4/20/2022
1.1.2 416 4/14/2022
1.1.1 414 4/12/2022
1.1.0 419 4/12/2022
1.0.5 403 4/11/2022
1.0.4 418 4/11/2022
1.0.3 412 4/11/2022
1.0.2 414 4/11/2022
1.0.1 418 4/11/2022
1.0.0 421 4/11/2022