View on GitHub

Fsprettytable

Represent tabular data in visually appealing ASCII tables using F#

Download this project as a .zip file Download this project as a tar.gz file

FsPrettyTable is a simple F# library designed to make it quick and easy to represent tabular data in visually appealing ASCII tables, like this:

+----------+-----------------+-------------+---------------+
| Language | Developer       | Appeared in | Influenced by |
+----------+-----------------+-------------+---------------+
| IPL      | RAND Corp.      |    1956     |               |
| LISP     | John McCarthy   |    1958     |           IPL |
| ISWIM    | Peter J. Landin |    1966     |          LISP |
| ML       | Robin Milner    |    1973     |         ISWIM |
| Caml     | Gérard Huet     |    1985     |            ML |
| OCaml    | INRIA           |    1996     |          Caml |
| F#       | M$ / Don Syme   |    2005     |         OCaml |
+----------+-----------------+-------------+---------------+

It is more or less a rip off of the PrettyTable Python library made by Luke Maurits. I hope he doesn't mind.

PrettyTable lets you control many aspects of the table, like the width of the column padding, the alignment of text within columns, which characters are used to draw the table border, whether you even want a border, and much more. You can control which subsets of the columns and rows are printed, and you can sort the rows by the value of a particular column.

Installation

Install FsPrettyTable using nuget:

PM> Install-Package FsPrettyTable

Usage

Let's begin by defining some data which we would like to display as a table:

open PrettyTable
let headers = ["Language";"Developer";"Appeared in";"Influenced by"]
let rows = [["IPL";"RAND Corp.";"1956";""]
            ["LISP";"John McCarthy";"1958";"IPL"]
            ["ISWIM";"Peter J. Landin";"1966";"LISP"]
            ["ML";"Robin Milner";"1973";"ISWIM"]
            ["Caml";"Gérard Huet";"1985";"ML"]
            ["OCaml";"INRIA";"1996";"Caml"]
            ["F#";"M$ / Don Syme";"2005";"OCaml"]]

The PrettyTable module provides a bunch of functions which can be chained together to format, manimulate and finally print (or just return as a string). Let's just print it using the default style first:

rows |> prettyTable |> printTable

// Output:
+-------+-----------------+------+-------+
|  IPL  |   RAND Corp.    | 1956 |       |
| LISP  |  John McCarthy  | 1958 |  IPL  |
| ISWIM | Peter J. Landin | 1966 | LISP  |
|  ML   |  Robin Milner   | 1973 | ISWIM |
| Caml  |   Gérard Huet   | 1985 |  ML   |
| OCaml |      INRIA      | 1996 | Caml  |
|  F#   |  M$ / Don Syme  | 2005 | OCaml |
+-------+-----------------+------+-------+

Ok, nice. But where did the headers go? Well, you I need to add then obviously:

prettyTable rows
|> withHeaders headers
|> printTable

// Output:
+----------+-----------------+-------------+---------------+
| Language |    Developer    | Appeared in | Influenced by |
+----------+-----------------+-------------+---------------+
|   IPL    |   RAND Corp.    |    1956     |               |
|   LISP   |  John McCarthy  |    1958     |      IPL      |
|  ISWIM   | Peter J. Landin |    1966     |     LISP      |
|    ML    |  Robin Milner   |    1973     |     ISWIM     |
|   Caml   |   Gérard Huet   |    1985     |      ML       |
|  OCaml   |      INRIA      |    1996     |     Caml      |
|    F#    |  M$ / Don Syme  |    2005     |     OCaml     |
+----------+-----------------+-------------+---------------+

If you just want the table string representation instead of printing it, use sprintTable.

Changing the ASCII properties

Horizontal and vertical rules

You can turn both horizontal and vertical rules on or off individually. Let's try it:

prettyTable rows
|> withHeaders headers
|> verticalRules FsPrettyTable.Types.NoRules
|> printTable

// Output:
------------------------------------------------------------
  Language      Developer      Appeared in   Influenced by  
------------------------------------------------------------
    IPL        RAND Corp.         1956                      
    LISP      John McCarthy       1958            IPL       
   ISWIM     Peter J. Landin      1966           LISP       
     ML       Robin Milner        1973           ISWIM      
    Caml       Gérard Huet        1985            ML        
   OCaml          INRIA           1996           Caml       
     F#       M$ / Don Syme       2005           OCaml      
------------------------------------------------------------

That's pretty nice! And the other one:

prettyTable rows
|> withHeaders headers
|> horizontalRules FsPrettyTable.Types.NoRules
|> printTable

// Output:
| Language |    Developer    | Appeared in | Influenced by |
|   IPL    |   RAND Corp.    |    1956     |               |
|   LISP   |  John McCarthy  |    1958     |      IPL      |
|  ISWIM   | Peter J. Landin |    1966     |     LISP      |
|    ML    |  Robin Milner   |    1973     |     ISWIM     |
|   Caml   |   Gérard Huet   |    1985     |      ML       |
|  OCaml   |      INRIA      |    1996     |     Caml      |
|    F#    |  M$ / Don Syme  |    2005     |     OCaml     |

TODO: Implement difference between FsPrettyTable.Types.Frame and FsPrettyTable.Types.All.

What you might actually be looking for is turning all bordering off, and that can be done using hasBorder like so:

prettyTable rows
|> withHeaders headers
|> hasBorder false
|> printTable

// Output:
 Language     Developer     Appeared in  Influenced by 
   IPL       RAND Corp.        1956                    
   LISP     John McCarthy      1958           IPL      
  ISWIM    Peter J. Landin     1966          LISP      
    ML      Robin Milner       1973          ISWIM     
   Caml      Gérard Huet       1985           ML       
  OCaml         INRIA          1996          Caml      
    F#      M$ / Don Syme      2005          OCaml     

Horizontal alignment

You may align the content of table cells either Left, Right, og Center (default).

prettyTable rows
|> withHeaders headers
|> horizontalAlignment FsPrettyTable.Types.Left
|> printTable

// Output:
+----------+-----------------+-------------+---------------+
| Language | Developer       | Appeared in | Influenced by |
+----------+-----------------+-------------+---------------+
| IPL      | RAND Corp.      | 1956        |               |
| LISP     | John McCarthy   | 1958        | IPL           |
| ISWIM    | Peter J. Landin | 1966        | LISP          |
| ML       | Robin Milner    | 1973        | ISWIM         |
| Caml     | Gérard Huet     | 1985        | ML            |
| OCaml    | INRIA           | 1996        | Caml          |
| F#       | M$ / Don Syme   | 2005        | OCaml         |
+----------+-----------------+-------------+---------------+

And then you have the option to override alignment for each column, based on the heading value:

open FsPrettyTable.Types

prettyTable rows
|> withHeaders headers
|> horizontalAlignment Left
|> horizontalAlignmentForColumn "Appeared in" Center
|> horizontalAlignmentForColumn "Influenced by" Right
|> printTable

// Output:
+----------+-----------------+-------------+---------------+
| Language | Developer       | Appeared in | Influenced by |
+----------+-----------------+-------------+---------------+
| IPL      | RAND Corp.      |    1956     |               |
| LISP     | John McCarthy   |    1958     |           IPL |
| ISWIM    | Peter J. Landin |    1966     |          LISP |
| ML       | Robin Milner    |    1973     |         ISWIM |
| Caml     | Gérard Huet     |    1985     |            ML |
| OCaml    | INRIA           |    1996     |          Caml |
| F#       | M$ / Don Syme   |    2005     |         OCaml |
+----------+-----------------+-------------+---------------+

Vertical alignment

TODO: Implement vertical alignment

Padding

You may specify the amount of padding using the paddingWidth function. Default padding is 1, which adds a single space on either side of the value in a cell. You may also specify padding for the left and right side separately:

prettyTable rows
|> withHeaders headers
|> hasBorder false
|> horizontalAlignment Left
|> horizontalAlignmentForColumn "Appeared in" Center
|> leftPaddingWidth 0
|> rightPaddingWidth 4
|> printTable

// Output:
Language    Developer          Appeared in    Influenced by    
IPL         RAND Corp.            1956                         
LISP        John McCarthy         1958        IPL              
ISWIM       Peter J. Landin       1966        LISP             
ML          Robin Milner          1973        ISWIM            
Caml        Gérard Huet           1985        ML               
OCaml       INRIA                 1996        Caml             
F#          M$ / Don Syme         2005        OCaml                       

Header style

The casing style of the headers may be changed using the headerStyle function. Possible values are:

Value Description
KeepAsIs Simply leave it as it is. This is default.
LowerCase Turn all letters to lower case.
UpperCase Turn all letters to upper case.
TitleCase An attempt at producing a linguistically correct title case (for English titles). Uses it's own implementation, which is slightly more correct that the default .NET implementation.
Capitalise Simple use upper case for the first letter, lower case for the rest.
prettyTable rows
|> withHeaders headers
|> headerStyle FsPrettyTable.Types.UpperCase
|> printTable

// Output:
+----------+-----------------+-------------+---------------+
| LANGUAGE |    DEVELOPER    | APPEARED IN | INFLUENCED BY |
+----------+-----------------+-------------+---------------+
|   IPL    |   RAND Corp.    |    1956     |               |
|   LISP   |  John McCarthy  |    1958     |      IPL      |
...

Swapping out the separator characters

You can replace the characters used in the border:

prettyTable rows
|> withHeaders headers
|> verticalChar '/'
|> horizontalChar ' '
|> junctionChar 'o'
|> printTable

// Output:
o          o                 o             o               o
/ Language /    Developer    / Appeared in / Influenced by /
o          o                 o             o               o
/   IPL    /   RAND Corp.    /    1956     /               /
/   LISP   /  John McCarthy  /    1958     /      IPL      /
/  ISWIM   / Peter J. Landin /    1966     /     LISP      /
/    ML    /  Robin Milner   /    1973     /     ISWIM     /
/   Caml   /   Gérard Huet   /    1985     /      ML       /
/  OCaml   /      INRIA      /    1996     /     Caml      /
/    F#    /  M$ / Don Syme  /    2005     /     OCaml     /
o          o                 o             o               o

Predefined styles

FsPrettyTable comes with some predefined style sets. They are:

Style Description
DefaultStyle By default, FsPrettyTable produces ASCII tables that look like the ones used in SQL database shells. Use this to undo any style changes you may have made.
PlainColumns A borderless style that works well with command line programs for columnar data.
MsWordFriendly A format which works nicely with Microsoft Word's "Convert to table" feature
PlainRows Basically just no vertical rules. I think this looks nice :)
Markdown Not supported yet..

This is how you set a style:

prettyTable rows
|> withHeaders headers
|> setStyle MsWordFriendly
|> printTable

Filtering columns

You may filter column output based on the column headers. This requires that you are actually using headers.

prettyTable rows
|> withHeaders headers
|> onlyColumns ["Language"; "Appeared in"]
|> printTable

// Output:
+----------+-------------+
| Language | Appeared in |
+----------+-------------+
|   IPL    |    1956     |
|   LISP   |    1958     |
...

You may also specify filtering by column index. This is what you'll have to use if you have a table with no headers:

myTable |> onlyColumnsByIndex [0; 2]

or by a predicate function which will be passed both the column index and the header value as arguments:

myTable |> onlyColumnsByChoice
               (fun i h -> i = 0 || h = "Appeared in")

Specifying a column filtering overwrites any previous filtering. To remove the filter, use function allColumns.

Sorting

You may sort the table on a single column, again by specifying the header value. Use sortBy for ascending order or sortByDescending for descending order. Use sortByNone to clear any sorting you have previously added.

prettyTable rows
|> withHeaders headers
|> sortByDescending "Appeared in"
|> printTable

// Output:
+----------+-----------------+-------------+---------------+
| Language |    Developer    | Appeared in | Influenced by |
+----------+-----------------+-------------+---------------+
|    F#    |  M$ / Don Syme  |    2005     |     OCaml     |
|  OCaml   |      INRIA      |    1996     |     Caml      |
|   Caml   |   Gérard Huet   |    1985     |      ML       |
|    ML    |  Robin Milner   |    1973     |     ISWIM     |
|  ISWIM   | Peter J. Landin |    1966     |     LISP      |
|   LISP   |  John McCarthy  |    1958     |      IPL      |
|   IPL    |   RAND Corp.    |    1956     |               |
+----------+-----------------+-------------+---------------+

Remember that all values are strings, and will be sorted as such.

TODO: Specify column using index

TODO: Specify column using a compare function (takes complete row)

API

Pre version 1.0 the API may change.

TODO: Add info about validation

Module PrettyTable

val prettyTable : string list list -> Table
val withHeaders : string list -> Table -> Table

val sprintTable : Table -> string
val printTable : Table -> unit

val hasHeader : bool -> Table -> Table
val hasBorder : bool -> Table -> Table
val verticalRules : Rules -> Table -> Table
val horizontalRules : Rules -> Table -> Table

val headerStyle : HeaderStyle -> Table -> Table

val horizontalAlignment : HorizontalAlignment -> Table -> Table
val horizontalAlignmentForColumn : string -> HorizontalAlignment -> Table -> Table

val paddingWidth : int -> Table -> Table
val leftPaddingWidth : int -> Table -> Table
val rightPaddingWidth : int -> Table -> Table

val verticalChar : char -> Table -> Table
val horizontalChar : char -> Table -> Table
val junctionChar : char -> Table -> Table

val onlyColumns : string list -> Table -> Table
val onlyColumnsByIndex : int list -> Table -> Table
val onlyColumnsByChoice : (int -> string -> bool) -> Table -> Table
val allColumns : Table -> Table

val sortBy : string -> Table -> Table
val sortByDescending : string -> Table -> Table
val sortByNone Table -> Table

type Style = DefaultStyle | PlainColumns | PlainRows | MsWordFriendly
val setStyle : Style -> Table -> Table

License

The MIT License (MIT)

Copyright (c) 2015 Torbjørn Marø

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.