Skip to content

Generate tables from objects. Similar to console.table() but more suitable for CLIs and less debug-like.

License

Notifications You must be signed in to change notification settings

Ergberg/table-string

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

table-string

A package to generate tables on TTYs.

colorful table

tableString() is a function originally inspired by console.table() but with the following main differences:

  • First, it doesn't output anything to the console directly, but returns a string that can be used in different ways.
  • More importantly, its output looks less technical. It aims to simplify the creation of meaningful tables for CLIs.

Purpose

We will generate a table for this array:

const data = [
  { price: 1.99, fruit: chalk.green("Apples") },
  { price: 3.99, fruit: chalk.red("Strawberries") },
  { price: 0.99, fruit: chalk.bgBlue.yellow("Bananas") },
  { price: 12.99, fruit: chalk.blue.bgWhite("Bilberries") },
],

What you get without tableString

On node.js console.table(data) produces a table that looks very debug-like:

console.table output

What you might be looking for

The same structure with tableString(data) looks much cleaner: tableString output

What else tableString can do

By adding two parameters we can control the columns and table options: tableString output with options

tableString(data,
  [{ price: "Price in $" }, { fruit: "Fruit" }],
  {
    tableChalk: chalk.bgHex("#ddddbb").black(" "),
    headerChalk: chalk.bgHex("#0088cc").black(" "),
    headerFrameChalk: chalk.bgHex("#004466").white(" "),
  }
);

Geared for CLI output, not debugging

As I was looking into this, I noticed that the output of console.table() looks pretty technical. It's more appropriate for developers debugging their code than for users of a CLI expecting informative tables.

Less technical

I've intentionally omitted some functions from console.table() because they give the output a more technical, debugger-like look:

  • there is no coloring of values based on their JavaScript type
  • there are no quotes around strings or 'm' after bigints
  • null values and values of type "function" are not rendered
  • for arrays, the index column is included only if explicitly specified
  • the index column has no header by default

More functionality

The table-string package supports more options compared to console.table:

  • It provides full control over table headings and alignment.
  • It is compatible with ANSI color sequences. For example, you can use the chalk package to colorize strings without affecting the layout. Even better, padding recognizes background colors and extends them. You can even define a chalk for the table's border.

Non-goals:

There are other table packages with different targets. The table-string package does not share them all. Here are explicit non-goals

  • No support for emojis. Currently, emojis do not seem to be well supported in monospace fonts. Emojis tend to break table spacing.
  • No dynamic effects on TTYs based on cursor repositioning. The result of the tableString function is a simple string that can be printed to a text file.

Usage

tableString

tableString generates a multiline string that renders as a table when printed to a tty. The data for the table is typically provided as an array of objects. The properties of those objects define the columns of the table, the array entries are the rows. The look of the table can be controlled by additional option parameters:

tableString(data, columnOptions, tableOptions) takes three parameters:

  1. The data to be displayed
  2. Column Options describing the order, headings and alignment of the columns
  3. Table Options that specify global characteristics of the table, such as alignment of headings or a chalk for the border.

The options are typically simple values or key-value pairs. But sometimes it is also helpful to use JavaScript functions to compute option.

Flatten

The flatten function can turn multiline string values into multiple rows and nested objects into additional columns.

Flatten multiline strings

Newlines in your data will break the output of tableString. Multiple lines of text in a table cell are not supported. But it is possible to substitute multiline strings with multiple rows. The flatten function will do that for you:

flatten([{a:"one line", b: "two\nlines"}]) == [{a:"one line", b:"two"}, {a:"", b:"lines"}]

Printing tableString(flatten([{a:"one line", b: "two\nlines"}])) will look like this:

flatten

With flatten, it is also possible to use tableString results as values in tableString tables 😏

flatten

The flatten function provides no special treatment for ANSI color escapes. There are no mechanisms to continue open color settings across tables rows. It is recommended to close/reset all color codes before newlines.

Flatten nested objects

The flatten function has an optional second parameter. It is called objectDepth. Its default value is 0. Setting objectDepth to 1 will replace all objects that tableString would show as [object Object] with columns holding the values of their properties. Of course those property values might again show as [object Object] Setting objectDepth to higher values will also replace those with their properties. To replace objects at any depth set objectDepth to Infinity. The names of the added columns are formed by chaining the names of the nested objects. It is an error if such a column name already exists. If an object's toString() returns a string different from [object Object] this result is used to show the object and the object is not turned into columns.

Example:

flatten([{ a: { b: "c" } }]) === [{ a: { b: "c" } }] // objectDepth not specified
flatten([{ a: { b: "c" } }], 1) === [{ "a.b": "c" }] // nested object's property is replaced by a new column
flatten([{ a: { b: { c: "d" } } }], 1) === [{ "a.b": { c: "d" } }] // only outer object is replaced with objectDepth === 1
flatten([{ a: { b: { c: "d" } } }], 2) === [{ "a.b.c": "d" }] // also inner object is replaced with objectDepth === 2
flatten([{ a: { b: "c" }, "a.b": "exists" }], 1) // Error: Flattening object: property "a.b" already exists

frame.characters

By default, the border of the table and the lines are 'drawn' with graphical single line characters: 'β”Œ', '─', '┐' ...
You can change this by assigning to frame.characters. Predefined values are:

frame.characters = standard.characters // 'β”Œ', '─', '┐' ...
frame.characters = double.characters  // 'β•”', '═', 'β•—' ...
frame.characters = ascii.characters   // '.', '-', '.' ...
frame.characters = stars.characters   // '*', '*', '*' ...
frame.characters = space.characters   // ' ', ' ', ' ' ...

To define your characters, assign your own object to frame.characters

frame.characters = {
  topRow: "β”Œβ”€β”¬β”",
  normal: "β”‚ β”‚β”‚",
  h_line: "β”œβ”€β”Όβ”€",
  bottom: "β””β”€β”΄β”˜",
};

Configuration

Data

In general, tableString is called with an array1. The elements of the array are used to populate the rows of the table. Values that are not strings, numbers, or objects are ignored. Strings can contain ANSI color escapes. If they occur at the beginning or the end of the string, they will be automatically extended if the string needs to be padded to fill the column width.

If the array contains primitive values, they are displayed in a column called "Values". This is true for strings, booleans, numbers and bigints. The other primitives, i.e. symbol, null and undefined, are ignored. Non primitive values are objects. These objects have properties. Each property defines a column of the table and the property value of an row's object is the value in the column for that row. Columns values should be primitive values. More complex values are likely show as "[object Object]". If they define a toString() function, the result of the toString() function will be used to show the object. String values with newlines break the table layout. To use multiline strings as values, first flatten the table. The flatten function can also be used to replace objects that show as "[object Object]" with columns of their attributes.

There are to special property called "ts:horizontalLine" and "ts:chalk".

If the "ts:horizontalLine" property is defined in the object for a row, e.g. { .... "ts:horizontalLine": true}, an horizontal line is drawn after that row.

If the "ts:chalk" property is defined in the object for a row, e.g. { .... "ts:chalk": "\x1B[37m\x1B[40m\x1B[49m\x1B[39m"}, this chalk is used for the row with precedence over table chalks and column chalks.

Column Options

The main purpose of column options is to tell the layout which columns to show and in what order. They are also used to specify alignments and headings of columns.

An entry of the columnOptions array defines up to 5 values for a column:

  • name
  • heading
  • minWidth, maxWidth, width
  • padding
  • align
  • alignHeading
  • chalk Of these values, only the name is mandatory.

If no column options are specified, the table shows all columns available in the underlying data. If column options are defined, only those columns are shown in the table.

Example: [ {name: "firstName" }, {name: "lastName" } ]2 will show these two columns in that order. The values are taken from the properties of the same name (from the objects that form the rows of the table).

name

A string. The name selects a single data column of the table. It is the name of this column. Possible values are the property names (keys) of the objects specified as table data. These include '0', '1', ... if the values in the data object are arrays. In addition, two special column names may exist:

  • "Values" is the name of the column of primitive values. If the data object for the table is an array, this column contains all strings and numbers that are direct elements of this array.
  • "" is the name of the index column. Unlike console.table(), this column is not included by default. To add an index column, you must explicitly specify the values for the index column using the index table option.

heading

A string. Sets the heading of the column. If not specified, the column name is used as the heading.

minWidth

A positive integer. The minimal width of a column is derived from the heading and the widest values. The width option can be used to explicitly set a minimum width for the column.

maxWidth

A positive integer. If set, truncates too long output in this column to n characters. It is an error to set maxWidth < minWidth.

width

A positive integer. Can be used as an abbreviation for setting minWidth and maxWidth to the same value.

padding

A positive integer. Adds spaces to the left and to the right of a value in a column. Default is 1 space.

align

Possible values are left, center, and right. Sets the alignment for the values of the column. The default is left for most values. If no alignment is specified for the column, number values will be right aligned by default. This also affects how the heading of the column is aligned. If you want a different alignment for the column heading, use the alignHeading column option. By default, column headings are all center aligned. This can be changed globally with the alignTableHeadings table option.

alignHeading

Possible values are left, center, and right. If the align column option is defined, this will also align the heading. The option alignHeading allows a different alignment of the heading. The default is center if not overridden by the table option alignTableHeadings.

chalk

A chalk for the table column, see the section about table chalks for the format. A value given for the column takes precedence over tableChalk-

alternateChalk

A chalk for every other row in the table column. A value given for the column takes precedence over alternateTableChalk.

Table Options

While column options refer to individual columns, there are a few options that affect the entire table:

  • alignTableHeadings
  • Table, frame, and header chalks
  • propertyCompareFunction
  • index

alignTableHeadings

Possible values are left, center, and right. This overrides the default "center" alignment of column headings. The align and alignHeading values for individual columns take precedence over this option.

Chalks

tableStringsupports various options to color tables and their borders. The values for the options are strings with opening and closing ANSI color escapes.

For example: { tableChalk: "\x1B[37m\x1B[40m\x1B[49m\x1B[39m" }

The options are:

Name Purpose
tableChalk Chalk for the content of the table
alternateTableChalk If defined, use this for every second row
frameChalk Chalk for the frame of the table
alternateFrameChalk If defined, use this for every second row
headerChalk Chalk for the header, defaults to tableChalk
headerFrameChalk Chalk for the tables frame in the header rows, defaults to frameChalk

propertyCompareFunction

A function. Normally, the data object for tableString() is an array. However, you can also pass an object. The properties of this object are then used to form the rows of the table. If the order of the rows is important to you, you can specify a comparison function to sort them. By default, the properties ar sorted alphabetically. If you do not want sorting, specify: propertyCompareFunction: null.

index

An array of values. To add an index column, define values for that column. This can look like this: index: ["A", "B", "C"] or index: [...data.keys()]. The column is named "" (the empty string) and its heading is also "". You can change heading and alignment with a column option for "": { name: "", heading: "(index)", align: "right" }.

Miscellaneous

Shortened Notation

Often, you do not need to specify all options for a column. For these cases, two abbreviations are supported:

  • The tuple { column: "name", heading: "Column Heading" } can be abbreviated to { name: "Column Heading" }\
  • The { name: "prop2" } object can be abbreviated to a single string "prop2".

All forms can be mixed: [ { prop1: "Column Name" }, "prop2", { name: "prop3", align: "center" } ]

Using Functions in Options

The examples for the index table option also include an example for a computed option value: [...data.keys()] computes an index from the data array. There are other examples where using functions for option values greatly simplifies configuration and improves readability.

For example, the table option tableChalk could also be set with the chalk package as follows: { tableChalk: chalk.red.bgBlue("x") }. Here the string itself is not important, but it should have a non-zero length. Otherwise, chalk optimizes the colors away.

As another example, if you just want the columns to show up in alphabetical order:
tableString(data = [{ z: 3, y: 4, x:2 }]), [...Object.keys(data[0])].sort()) renders as sorted columns

Footnotes

  1. You can also pass an object instead of an array, see the table option propertyCompareFunction. ↩

  2. Or shorter: [ "firstName", "lastName" ], see shortened notation ↩

About

Generate tables from objects. Similar to console.table() but more suitable for CLIs and less debug-like.

Resources

License

Stars

Watchers

Forks

Packages

No packages published