TextTableBuilder 1.3.1

dotnet add package TextTableBuilder --version 1.3.1
NuGet\Install-Package TextTableBuilder -Version 1.3.1
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.3.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TextTableBuilder --version 1.3.1
#r "nuget: TextTableBuilder, 1.3.1"
#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.3.1

// Install TextTableBuilder as a Cake Tool
#tool nuget:?package=TextTableBuilder&version=1.3.1

logo TextTableBuilder

Build Status Nuget version

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:

  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 / 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 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.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 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 120 3/7/2024
1.3.0 58 3/7/2024
1.2.2 91 3/7/2024
1.2.1 79 3/7/2024
1.2.0 1,994 5/3/2022
1.1.3 381 4/20/2022
1.1.2 389 4/14/2022
1.1.1 387 4/12/2022
1.1.0 392 4/12/2022
1.0.5 379 4/11/2022
1.0.4 392 4/11/2022
1.0.3 384 4/11/2022
1.0.2 384 4/11/2022
1.0.1 390 4/11/2022
1.0.0 392 4/11/2022