cfilipov / texttable Goto Github PK
View Code? Open in Web Editor NEWSwift package for easily rendering text tables. Inspired by the Python tabulate library.
License: MIT License
Swift package for easily rendering text tables. Inspired by the Python tabulate library.
License: MIT License
I've been keeping an eye on this repo for ages, because it's a nice fit for some console debug output I need for histograms. Today was the day I decided to put it in action, then I realised there's no pod.
I'm reading up on Swift's packager manager now, but it's so far not as straight forward (from my perspective) as Cocoapods, so it's a hindrance. Being able to just add another line to my Podfile would be a plus.
That aside, loving the design of this lib!
This proposal supersedes #1, which was tentatively abandoned. This proposal is a breaking API change predicated on the approval of improved key paths in swift SE-0161.
I propose a new API for constructing a TextTable
instance. In particular, the way column definitions are mapped to values. Column/value mapping will be defined via a dictionary of key paths to columns.
The current API uses an initializer that takes a closure which is expected to return an array of column definitions given an instance of some row type Person
.
let table = TextTable<Person> {
[Column("Name" <- $0.name),
Column("Age" <- $0.age),
Column("Birthday" <- $0.birthday)]
}
This has a few disadvantages:
Any
.With the new API, TextTable
will conform to ExpressibleByDictionaryLiteral
. A TextTable<T>
will be created by using a dictionary literal of [PartialKeyPath<T>: Column]
.
Questions
- Should
Column
beColumn<T>
?- Could/should we use
KeyPath<T, V>
instead?
let table: TextTable<Person> = [
.name: "Name",
.country: "Country",
.visited: "Last Visit"
]
The above snippet of code is just short hand for the following (Column
will conform to ExpressibleByStringLiteral
, so Column
does not have to be explicitly constructed):
let table: TextTable<Person> = [
.name: Column("Name"),
.country: Column("Country"),
.visited: Column("Last Visit")
]
The above snippet is also short hand for the following code. This example illustrates the default values that are used when you leave out the optional arguments. Typically you would only provide arguments where you want to customize the specific column.
let table: TextTable<Person> = [
.name: Column("Name", width: .auto, align: .auto, truncate: .tail, formatter: nil),
.country: Column("Country", width: .auto, align: .auto, truncate: .tail, formatter: nil),
.visited: Column("Last Visit", width: .auto, align: .auto, truncate: .tail, formatter: nil)
]
You would never type this out, but for illustration purposes here is an even more explicit version. Once again, this is equivalent to all the previous examples:
let table: TextTable<Person> = [
#keyPath(Person, .name): Column("Name", width: .auto, align: .auto, truncate: .tail, formatter: nil),
#keyPath(Person, .country): Column("Country", width: .auto, align: .auto, truncate: .tail, formatter: nil),
#keyPath(Person, .visited): Column("Last Visit", width: .auto, align: .auto, truncate: .tail, formatter: nil)
]
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)
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.
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.
The API of this lib has seen several major changes. But it has been converging on what I think resembles a good API.
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:
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.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)
]
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)
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.
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.
Hi,
I would like to use https://github.com/onevcat/Rainbow to colorize cells in a TextTable.
let table = TextTable<Attributes> {
[
Column(title: "Country", value: $0.country!.blue),
Column(title: "County", value: $0.county!.red),
]
}
table.print(result, style: Style.psql)
This result in a table where the length of the cell data in https://github.com/cfilipov/TextTable/blob/master/Sources/TextTable/TextTable.swift#L237 should only count the visible characters. The escape sequences used for colors should not be counted here:
+----------------------------+--------------------------------+
| Country | County |
+----------------------------+--------------------------------+
| Baden-Württemberg | SK Karlsruhe |
| Baden-Württemberg | SK Ulm |
(The colors are missing...)
Is there any way this can already be achieved or implemented?
Greetings
Joachim
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.