I am not the original author of cl-ansi-term, but I revived it lately. In particular, I added useful stuff to print data in tables:
- print list of lists (where the first one is the list of headers)
- print horizontal or vertical tables
- the header keys are either the first row, either the first column
- print hash-tables, plists, alists
- filter keys to display (include, exclude)
- limit the number of columns
- they can be styled:
- with or without borders
- choose the columns’ width
- choose the borders’ elements (“-|+”)
- choose the headers’ and the cells’ style (color, bold…).
For example:
(progn
(defparameter d (serapeum:dict :a 1.1 :b 2.2 :c 3.3))
(banner "A single hash-table")
(table d)
(banner "A single hash-table, in columns")
(vtable d)
(banner "A single hash-table, ignoring column :B")
(table d :exclude :b)
(banner "A single hash-table, vertically ignoring column :B")
(vtable d :exclude :b)
(banner "A list of hash-tables")
(table (list d d d))
(banner "A list of hash-tables, ignoring column :B")
(table (list d d d) :keys '(:a :c))
(banner "A list of hash-tables, in columns")
(vtable (list d d d))
(banner "same, ignoring the column :b")
(vtable (list d d d) :exclude :b))
prints:
--------------------------------------------------------------------------------
A single hash-table
--------------------------------------------------------------------------------
+---------+---------+---------+
|A |B |C |
+---------+---------+---------+
|1.1 |2.2 |3.3 |
+---------+---------+---------+
--------------------------------------------------------------------------------
A single hash-table, in columns
--------------------------------------------------------------------------------
+---------+---------+
|A |1.1 |
+---------+---------+
|B |2.2 |
+---------+---------+
|C |3.3 |
+---------+---------+
--------------------------------------------------------------------------------
A single hash-table, ignoring column :B
--------------------------------------------------------------------------------
+---------+---------+
|A |C |
+---------+---------+
|1.1 |3.3 |
+---------+---------+
--------------------------------------------------------------------------------
A single hash-table, vertically ignoring column :B
--------------------------------------------------------------------------------
+---------+---------+
|A |1.1 |
+---------+---------+
|C |3.3 |
+---------+---------+
--------------------------------------------------------------------------------
A list of hash-tables
--------------------------------------------------------------------------------
+---------+---------+---------+
|A |B |C |
+---------+---------+---------+
|1.1 |2.2 |3.3 |
+---------+---------+---------+
|1.1 |2.2 |3.3 |
+---------+---------+---------+
|1.1 |2.2 |3.3 |
+---------+---------+---------+
--------------------------------------------------------------------------------
A list of hash-tables, ignoring column :B
--------------------------------------------------------------------------------
+---------+---------+
|A |C |
+---------+---------+
|1.1 |3.3 |
+---------+---------+
|1.1 |3.3 |
+---------+---------+
|1.1 |3.3 |
+---------+---------+
--------------------------------------------------------------------------------
A list of hash-tables, in columns
--------------------------------------------------------------------------------
+---------+---------+---------+---------+
|A |1.1 |1.1 |1.1 |
+---------+---------+---------+---------+
|B |2.2 |2.2 |2.2 |
+---------+---------+---------+---------+
|C |3.3 |3.3 |3.3 |
+---------+---------+---------+---------+
--------------------------------------------------------------------------------
same, ignoring the column :b
--------------------------------------------------------------------------------
+---------+---------+---------+---------+
|A |1.1 |1.1 |1.1 |
+---------+---------+---------+---------+
|C |3.3 |3.3 |3.3 |
+---------+---------+---------+---------+
or again
TERM> (table (list d d d) :exclude :b :border-style nil)
A C
1.1 3.3
1.1 3.3
1.1 3.3
Real example
Remember, the scripts I use in production. I’m usually fine with big data output in the REPL, until:
- until I want a cleaner output in the production script, so I can see quicker what’s going on.
- when I want to filter and study the data a bit more.
In this case I extract data from my DB and I get a list of plists:
((:|isbn| "3760281971082" :|quantity| -1 :|price| 12.8d0 :|vat| NIL
:|distributor| NIL :|discount| NIL :|type_name| NIL :|type_vat| NIL
:|price_bought| NIL :|price_sold| 12.8d0 :|quantity_sold| 1 :|sold_date|
"2024-04-03 09:27:12")
(:|isbn| "9791094298169" :|quantity| 4 :|price| 15.0d0 :|vat| NIL
:|distributor| NIL :|discount| NIL :|type_name| "book" :|type_vat| NIL
:|price_bought| NIL :|price_sold| 15.0d0 :|quantity_sold| 1 :|sold_date|
"2024-04-03 10:06:58")
…)
With the table
and vtable
functions, I can explore data in a clearer fashion.
(uiop:add-package-local-nickname :sera :serapeum)
(term:table (sera:take 15 *sells*)
:keys '(:|isbn| :|quantity| :|price|)
:plist t
:column-width '(15 10 10))
+--------------+---------+---------+
|isbn |quantity |price |
+--------------+---------+---------+
|3760281971082 |-1 |12.8d0 |
+--------------+---------+---------+
|9791094298169 |4 |15.0d0 |
+--------------+---------+---------+
|3700275724249 |-126 |2.8d0 |
+--------------+---------+---------+
|9782372600842 |1 |10.0d0 |
+--------------+---------+---------+
|9782372600736 |0 |10.0d0 |
+--------------+---------+---------+
|9782221256770 |1 |19.0d0 |
+--------------+---------+---------+
|3700275734392 |171 |3.95d0 |
+--------------+---------+---------+
|3662846007789 |2 |16.95d0 |
+--------------+---------+---------+
|9782368292907 |1 |8.95d0 |
+--------------+---------+---------+
|9782095022679 |1 |12.95d0 |
+--------------+---------+---------+
|3662846007871 |5 |5.9d0 |
+--------------+---------+---------+
|9782092588949 |2 |5.95d0 |
+--------------+---------+---------+
|3700275724249 |-126 |2.8d0 |
+--------------+---------+---------+
|3700275734392 |171 |3.95d0 |
+--------------+---------+---------+
|3770017095135 |0 |29.99d0 |
+--------------+---------+---------+
Yes, this calls for more features: align the numbers, automatically adapt the cells’ width (DONE), style cells individually (DONE), etc.
(I’m sure we could have an explorer window, watching for changes, displaying data in a real table with interactive features… I can feel we’re close… CLOG frame and malleable systems someone?)
Use case and other primitives: title, banner, vspace, o-list
The use case is cleaner output for scripts.
Other libraries exist with other goals:
- https://github.com/AccelerationNet/data-table “A Common Lisp data structure representing tabular data (think CSVs and database results)”
- https://github.com/40ants/teddy “A data framework for Common Lisp”
- https://github.com/telephil/cl-ascii-table/ “Common Lisp API to present tabular data in ASCII-art tables”
Here are some of other cl-ansi-term’s utilities:
ordered and un-ordered lists:
(term:o-list '((:one one-a (:one-b :one-b-1 :one-b-2)) :two))
1. ONE
1. ONE-A
2. ONE-B
1. ONE-B-1
2. ONE-B-2
2. TWO
Horizontal lines
(term:hr :filler "=")
================================================================================
printing stuff, align on screen:
(term:cat-print '(:abc :def :ghi) :align :center)
;; =>
ABCDEFGHI
vspace
for vertical space (default: 3 newlines)
banner
:
(banner "My title" :space 1)
--------------------------------------------------------------------------------
My title
--------------------------------------------------------------------------------
Stylesheets and colorized text
The library allows to use styles.
Start by defining your stylesheet.
(term:update-style-sheet
'((:header :cyan :underline)
(:mark :red :reverse)
(:term :yellow :bold)))
:header
, :mark
and :term
are now your own vocabulary. Anytime you
use functions that accept a style, reference them.
Example:
(term:table (list '(:name :age) '(:me 7)) :header-style :header)
To see colors in a “dumb” terminal like in Emacs Slime, install the package slime-repl-ansi-color
, “require” it and enable it ith M-x slime-repl-ansi-color-mode
.
You can also disable styles in non-interactive terminals with term::*enable-effects-on-dumb-terminals*
.
Happy lisping.