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.12

Text Table🔗ℹ

Laurent Orseau

A simple package to display utf-8 textual tables.

License: APACHE2+MIT

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. Note that strings are not converted.

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

procedure

(print-table table    
  [#:->string to-string    
  #:border-style border-style    
  #:framed? framed?    
  #:row-sep? row-sep?    
  #:col-sep? col-sep?    
  #:align align    
  #:row-align row-align])  void?
  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
Shorthand form for (displayln (table->string args ...)). Takes the same arguments as table->string.

procedure

(print-simple-table table    
  [#:->string to-string    
  #:border-style border-style    
  #:framed? framed?    
  #:row-sep? row-sep?    
  #:col-sep? col-sep?    
  #:align align    
  #:row-align row-align])  void?
  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
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    
  [#:truncate-ok? truncate-ok?]    
  result-length)  list?
  pat : (pattern-list-of any/c)
  truncate-ok? : any/c = #f
  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)

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

Minimum length of list l exceeds n-elt '(a b c d ... ... ...

e f) 2

> (pattern-list->list '(a b c d ... ... ... e f) 2 #:truncate-ok? #t)

'(a e)

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 
  #:groups groups 
  #:group-sep group-sep 
  #:decimal-sep decimal-sep]) 
  (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? = " "
  groups : (non-empty-listof exact-positive-integer?) = '(3)
  group-sep : string? = ""
  decimal-sep : 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│

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