Text Table
1 Tables
table->string
simple-table->string
print-table
print-simple-table
border-style/  c
border-style1/  c
border-style2/  c
border-style-frame/  c
string-length=/  c
2 Utilities
2.1 Lists
pattern-list-of
pattern-list->list
transpose
group-by-lengths
2.2 Strings
string-repeat
~r*
8.6

Text Table

Laurent Orseau

A simple package to display utf-8 textual tables.

To install:

raco pkg install text-table

See the example in the main submodule of the "main.rkt" file. You can observe the results by running:

racket -l text-table

Examples:
; Minimalistic example:
> (print-table
   '((a b c d e f gggg h)
     (123 456 77 54 1  5646547987 41 1)
     (111 22 3333 44 5 6 7 8888)))

┌───┬───┬────┬──┬─┬──────────┬────┬────┐

│a  │b  │c   │d │e│f         │gggg│h   

├───┼───┼────┼──┼─┼──────────┼────┼────┤

│123│456│77  │54│1│5646547987│41  │1   

├───┼───┼────┼──┼─┼──────────┼────┼────┤

│111│22 │3333│44│5│6         │7   │8888│

└───┴───┴────┴──┴─┴──────────┴────┴────┘

; With more bells and whistles
> (print-table
   '((a b c d e f gggg h)
     (123 456 77 54 1  5646547987 41 1)
     (111 22 3333 44 5 6 7 8888))
   #:border-style 'double
   #:framed? #f
   #:row-sep? #t
   #:align '(left center right))

a  ║ b ║   c║ d║e║         f║gggg║   h

═══╬═══╬════╬══╬═╬══════════╬════╬════

123║456║  77║54║1║5646547987║  41║   1

═══╬═══╬════╬══╬═╬══════════╬════╬════

111║22 ║3333║44║5║         6║   7║8888

; Custom border style using border-style-frame/c
> (print-table '((abc abc abc)
                 (abcdef ab abcdef)
                 (a abcdef abc))
               #:border-style
               '("╭─┬╮"
                 "│.││"
                 "├─┼┤"
                 "╰─┴╯")
               #:align '(center)
               #:framed? #t
               #:row-sep? #t)

╭──────┬──────┬──────╮

│.abc..│.abc..│.abc..│

├──────┼──────┼──────┤

│abcdef│..ab..│abcdef│

├──────┼──────┼──────┤

│..a...│abcdef│.abc..│

╰──────┴──────┴──────╯

; Custom border style using border-style2/c
> (print-table '((abc abc abc)
                (abcdef ab abcdef)
                (a abcdef abc))
              #:border-style
              '(("<table>" "" "" "")
                ("<tr><td> " " " " </td><td> "  " </td></tr>")
                ("" "" "" "")
                ("</table>" "" "" ""))
              #:framed? #t
              #:row-sep? #f)

<table>

<tr><td> abc    </td><td> abc    </td><td> abc    </td></tr>

<tr><td> abcdef </td><td> ab     </td><td> abcdef </td></tr>

<tr><td> a      </td><td> abcdef </td><td> abc    </td></tr>

</table>

; LaTeX style
> (print-table '((abc abc abc)
                (abcdef ab abcdef)
                (a abcdef abc))
              #:border-style 'latex)

\begin{tabular}{|l|l|l|}

\hline

abc    & abc    & abc    \\

\hline

abcdef & ab     & abcdef \\

\hline

a      & abcdef & abc    \\

\hline

\end{tabular}

; Aligning numbers (incorrectly then well)
> (print-table
   #:row-sep? '(#t #f)
   #:col-sep? '(#t #f)
   #:align '(left right ... center)
   #:->string
   (list
    ~a ; Name
    ~a (~r*) (~r* #:precision '(= 2)) (~r* #:notation 'exponential) ; Speed
    ~a) ; Unit
    ; The table:
    (map (λ (l) (pattern-list->list l 6))
        `((Name Speed ... Unit)
          (Alice 10 ... "km/h")
          (Bob ,(sqrt 2) ... "m/s")
          (Charlie +inf.0 +nan.0 ... ... n/a)
          (light ,(* 299792458 (expt 10 3)) ... "mm/s"))))

┌───────┬─────────────────────────────────────────────────────────────────┐

│Name               Speed        Speed           Speed        Speed Unit│

├───────┼─────────────────────────────────────────────────────────────────┤

│Alice                  10           10           10.00        1e+01 km/h│

│Bob    │1.4142135623730951     1.414214            1.41 1.414214e+00 m/s │

│Charlie│            +inf.0       +nan.0          +inf.0       +nan.0 n/a │

│light        299792458000 299792458000 299792458000.00 2.997925e+11 mm/s│

└───────┴─────────────────────────────────────────────────────────────────┘

; Empty style and doubly repeating alignments
> (print-simple-table
    #:border-style 'empty
    #:align '(right left ... ...)
   (list (make-list 10 '*)
         (make-list 10 '**)
         (make-list 10 '***)
         (make-list 10 '****)
         (make-list 10 '*****)
         (make-list 10 "|")))

    **        **        **        **        **    

   ****      ****      ****      ****      ****   

  ******    ******    ******    ******    ******  

 ********  ********  ********  ********  ********

**************************************************

    ||        ||        ||        ||        ||    

; Multiple separators
> (print-table (for/list ((i 6)) (for/list ((j 10)) (* (+ i 1) (+ j 1))))
               #:row-sep? '(#t #f ... ...)
               #:col-sep? '(#t #f ... ...))

┌─┬─────┬─────┬─────┬─────┬──┐

│1│2  3 │4  5 │6  7 │8  9 │10│

├─┼─────┼─────┼─────┼─────┼──┤

│2│4  6 │8  10│12 14│16 18│20│

│3│6  9 │12 15│18 21│24 27│30│

├─┼─────┼─────┼─────┼─────┼──┤

│4│8  12│16 20│24 28│32 36│40│

│5│10 15│20 25│30 35│40 45│50│

├─┼─────┼─────┼─────┼─────┼──┤

│6│12 18│24 30│36 42│48 54│60│

└─┴─────┴─────┴─────┴─────┴──┘

1 Tables

procedure

(table->string table    
  [#:->string to-string    
  #:border-style border-style    
  #:framed? framed?    
  #:row-sep? row-sep?    
  #:col-sep? col-sep?    
  #:align align    
  #:row-align row-align])  string?
  table : (listof list?)
  to-string : (pattern-list-of (procedure-arity-includes/c 1))
   = ~a
  border-style : border-style/c = 'single
  framed? : boolean? = #t
  row-sep? : (pattern-list-of boolean?) = #t
  col-sep? : (pattern-list-of boolean?) = #t
  align : (pattern-list-of (or/c 'left 'center 'right)) = 'left
  row-align : (pattern-list-of (or/c 'top 'center 'bottom))
   = 'top
Accepts a table specified as a list of lists, and returns a string representing the table. The lists must all be of the same lengths.

The to-string procedure is used to convert cell values to strings, or a pattern-list of such procedures.

The border-style specifies the style of lines to be used in drawing the table.

When framed? is #true, a frame is drawn around the outside of the table.

The row-sep? and col-sep? arguments specify whether separators are added between rows or columns.

The align specification indicates how the contents of the cells are to be aligned within their cells. A single-symbol specification applies to all cells, or a list of symbols of the same length as the rows can be applied in order to specify the alignment of each column independently. When align is a list, it is trimmed to the length of the columns if it is too long, or the last element of the list is used for the remaining columns if it is too short.

The row-align specification indicates how the contents of the cells are aligned in a row, when cells are strings with multiple lines.

The to-string, align and row-align, row-sep? and col-sep? arguments accept pattern lists.

procedure

(simple-table->string table    
  [#:->string to-string    
  #:border-style border-style    
  #:framed? framed?    
  #:row-sep? row-sep?    
  #:col-sep? col-sep?    
  #:align align    
  #:row-align row-align])  string?
  table : (listof list?)
  to-string : (pattern-list-of (procedure-arity-includes/c 1))
   = ~a
  border-style : border-style/c = 'single
  framed? : boolean? = #f
  row-sep? : (pattern-list-of boolean?) = #f
  col-sep? : (pattern-list-of boolean?) = #f
  align : (pattern-list-of (or/c 'left 'center 'right)) = 'left
  row-align : (pattern-list-of (or/c 'top 'center 'bottom))
   = 'top
Like table->string, but with different default arguments to output a minimalistic table.

Example:
> (displayln
   (simple-table->string
    #:align '(left right)
    '((a b c d e f gggg h)
      (123 456 77 54 1  5646547987 41 1)
      (111 22 3333 44 5 6 7 8888))))

a     b    c  d e          f gggg    h

123 456   77 54 1 5646547987   41    1

111  22 3333 44 5          6    7 8888

syntax

(print-table args ...)

Shorthand form for (displayln (table->string args ...)). Takes the same arguments as table->string.

syntax

(print-simple-table args ...)

Shorthand form for (displayln (simple-table->string args ...)). Takes the same arguments as simple-table->string.

value

border-style/c : contract?

 = 
(or/c
 'empty 'latex 'space 'space-single 'single 'rounded 'double 'heavy
 border-style1/c
 border-style2/c
 border-style-frame/c)
Border style contract. The list element is for custom border styles. See the example at the top of this document.

value

border-style1/c : contract?

 = 
(list/c char? ; row sep
        (list/c string? string? string?) ; text line
        (list/c string? string? string?) ; top line
        (list/c string? string? string?) ; middle line
        (list/c string? string? string?) ; bottom line)
The old border style. Obsolete but kept for backward compatibility. See border-style2/c instead. Note that, compared to border-style2/c, the first an second lists are in reverse order, the row separator is the same for all lines, and the space filler is always " ".

value

border-style2/c : contract?

 = 
(list/c (list/c string? string? string? string?) ; top line
        (list/c string? string? string? string?) ; text line
        (list/c string? string? string? string?) ; middle line
        (list/c string? string? string? string?) ; bottom line)
Each string specifies one of the elements of the frame of the table. The strings can be of arbitrary length
> (print-table
   '((_ _ ___ _)
     (_ _ _ _)
     (_ "_\n__" _ _))
   #:border-style
   '(("╭" "^" "┬" "╮")
     ("{" "." "│" "}")
     ("├" "─" "+" "┤")
     ("╰" "v" "┴" "╯")))

╭^^┬^^┬^^^^┬^╮

{_.│_.│____│_}

├──+──+────+─┤

{_.│_.│_...│_}

├──+──+────+─┤

{__│_.│_...│_}

{..│__│....│.}

╰vv┴vv┴vvvv┴v╯

The element "." is a space filler. Note that each element can be a multi-character string rather than a single char. See also border-style-frame/c.

value

border-style-frame/c : contract?

 = 
(list/c (string-length=/c 5) ; top line
        (string-length=/c 5) ; text line
        (string-length=/c 5) ; middle line
        (string-length=/c 5) ; bottom line)
A simplification of border-style2/c where each element of the frame is a single character, so they can all be specified in a single string per line.
> (print-table '((abc abc abc)
       (abcdef ab abcdef)
       (a abcdef abc))
     #:border-style
     '("╭─┬╮"
       "│.││"
       "├─┼┤"
       "╰─┴╯")
     #:align '(center))

╭──────┬──────┬──────╮

│.abc..│.abc..│.abc..│

├──────┼──────┼──────┤

│abcdef│..ab..│abcdef│

├──────┼──────┼──────┤

│..a...│abcdef│.abc..│

╰──────┴──────┴──────╯

Note that the "." is the space filler.

procedure

((string-length=/c n) x)  boolean?

  n : integer?
  x : any/c
Returns #true if x is a string of length n, #false otherwise.

2 Utilities

Utilities used to build text tables.

2.1 Lists

procedure

((pattern-list-of pred?) x)  boolean?

  pred? : (procedure-arity-includes/c 1)
  x : any/c
Returns #true if either x is not a list and (pred? x) is #true, or x is a list (head ... dots ... tail ...) satisfying (andmap pred? (head ... tail ...)) and (dots ...) is a list of '... not longer than (head ...).

procedure

(pattern-list->list pat result-length)  list?

  pat : (pattern-list-of any/c)
  result-length : exact-nonnegative-integer?

Examples:
> (pattern-list->list 'a 3)

'(a a a)

> (pattern-list->list '(a) 3)

'(a a a)

> (pattern-list->list '(a b) 5)

'(a b b b b)

> (pattern-list->list '(a b ...) 5)

'(a b b b b)

> (pattern-list->list '(a b c ... ...) 10)

'(a b c b c b c b c b)

> (pattern-list->list '(a b c d ... ... ... e f) 10)

'(a b c d b c d b e f)

procedure

(transpose l)  (listof list?)

  l : (listof list?)
Returns a new list where the columns and rows of l are swapped.

Example:
> (transpose '((a b c) (1 2 3)))

'((a 1) (b 2) (c 3))

procedure

(group-by-lengths l lengths)  (listof list?)

  l : list?
  lengths : (listof exact-nonnegative-integer?)
Returns a list with the same elements as l but grouped in sublists of lengths given by lengths.

Example:
> (group-by-lengths '(a b c d e f g)
                    '(1 0 2 3 0 1))

'((a) () (b c) (d e f) () (g))

2.2 Strings

procedure

(string-repeat str len)  string?

  str : string?
  len : exact-nonnegative-integer?
Returns a string of length len by repeating str.

Examples:
> (string-repeat "abc" 5)

"abcab"

> (string-repeat "abc" 2)

"ab"

procedure

(~r* [#:sign sign 
  #:base base 
  #:precision precision 
  #:notation notation 
  #:format-exponent format-exponent 
  #:min-width min-width 
  #:pad-string pad-string]) 
  (any/c . -> . string?)
  sign : 
(or/c #f '+ '++ 'parens
      (let ([ind (or/c string? (list/c string? string?))])
        (list/c ind ind ind)))
   = #f
  base : (or/c (integer-in 2 36) (list/c 'up (integer-in 2 36)))
   = 10
  precision : 
(or/c exact-nonnegative-integer?
      (list/c '= exact-nonnegative-integer?))
 = 6
  notation : 
(or/c 'positional 'exponential
      (-> rational? (or/c 'positional 'exponential)))
   = 'positional
  format-exponent : (or/c #f string? (-> exact-integer? string?))
   = #f
  min-width : exact-positive-integer? = 1
  pad-string : non-empty-string? = " "
Like ~r but curried, and also accepts non-rationals, which are printed with ~a instead.

Example:
> (print-table
   #:->string (list ~a ; 1
                    (~r*) ; 2
                    (~r* #:notation 'exponential) ; 3
                    (~r* #:precision '(= 2)) ; 4 (good)
                    (~r* #:notation 'exponential #:precision '(= 2)) ; 5 (good)
                    (~r* #:min-width 10 #:pad-string ".")) ; 6
   #:align '(right ...)
   #:row-sep? '(#f #t #f ...)
   (cons
    '("1" "2" "3" "4 (good)" "5 (good)" "6")
    (transpose
    (make-list 6 `(header 1111.11 22.222 3333000.0 4440000000000.0 ,(sqrt 2))))))

┌──────────────────┬─────────────┬────────────┬────────────────┬────────┬─────────────┐

                1│            2│           3│        4 (good)│5 (good)│            6│

            header│       header│      header│          header│  header│   header....│

├──────────────────┼─────────────┼────────────┼────────────────┼────────┼─────────────┤

          1111.11│      1111.11│ 1.11111e+03│         1111.11│1.11e+03│   ...1111.11│

            22.222│       22.222│  2.2222e+01│           22.22│2.22e+01│   ....22.222│

        3333000.0│      3333000│   3.333e+06│      3333000.00│3.33e+06│   ...3333000│

  4440000000000.0│4440000000000│    4.44e+12│4440000000000.00│4.44e+12│4440000000000│

│1.4142135623730951│     1.414214│1.414214e+00│            1.41│1.41e+00│   ..1.414214│

└──────────────────┴─────────────┴────────────┴────────────────┴────────┴─────────────┘