cfilipov / TextTable

Swift package for easily rendering text tables. Inspired by the Python tabulate library.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Upcoming Changes

cfilipov opened this issue · comments

The API of this lib has seen several major changes. But it has been converging on what I think resembles a good API.

New Text Table Creation

As of v1.0.0-alpha.4 this is how you create a text table:

let table = TextTable<Person> {
    [Column("Name" <- $0.name),
     Column("Age" <- $0.age),
     Column("Birthday" <- $0.birhtday)]
}

This has one major disadvantage:

The closure that maps the type to a column value is intertwined with the rest of the column definition. You can't get the column definition without calling the closure, and you can't call the closure unless you pass in a value. This is problematic when you need column information up front, before the first row is even processed. The callback ends up calling the closure using the first row multiple times just to get the column definition.

I propose the next version will have the following API instead:

let table: TextTable<Person> = [
    { $0.name } <- Column("Name"),
    { $0.country } <- Column("Country"),
    { $0.visited } <- Column("Last Visit")
]

Since the example above didn't customize the columns in any way, the following is also valid:

let table: TextTable<Person> = [
    { $0.name } <- "Name",
    { $0.country } <- "Country",
    { $0.visited } <- "Last Visit"
]

The goal is that this library will have good-enough defaults that for most cases one wouldn't be compelled to customize each column.

The <- operator is just syntactic sugar that allows us to keep the property mapping visually close to the column name so it reads better. The following is equivalent to the previous two examples:

let table: TextTable<Person> = [
    Column("Name", valueMapping: { $0.name }),
    Column("Country", valueMapping: { $0.country }),
    Column("Last Visit", valueMapping: { $0.visited })
]

Note the differences to the current API:

  1. TextTable<T> now conforms to ExpressibleByArrayLiteral. Instead of passing a closure on the init method of TextTable, you are now passing an array literal of columns.
  2. The value mapping for the column is defined as an individual closure for each column instead of a single closure for all columns.

Customizing the columns works similar to the current version:

let table: TextTable<Person> = [
    { $0.name } <- Column("Name", align: .left),
    { $0.country } <- Column("Country", align: .center, width: 12, truncate: .head),
    { $0.visited } <- Column("Last Visit", align: .right, formatter: df)
]

String Constructor Instead of String Method

Another change to consider: Extending String with an init for TextTable instead of using a string(for:) method.

let s = String(table: table)

Instead of

let s = table.string(for: data)

Stream Print

TextTable tries to be memory efficient. It will not copy the contents of your collection, instead it will call back to your collection whenever it needs a value.

Currently the print method on table just creates a string using the aforementioned API and just passes it to Swift.print. The next version will instead stream the contents of the table to Swift.print roughly line-by-line as the table is generated.

Conformance to Sequence Type

TextTable will actually conform to swift's Collection (or maybe just provide a Sequence). This will let one lazily pull the string parts of the rendered table.

This proposal has been tentatively abandoned. This proposal is superseded by #2 which is predicated on the approval of improved key paths in swift SE-0161. If for some reason SE-0161 is rejected, then this proposal will be revisited.