Last active
August 1, 2024 16:36
-
-
Save WetHat/a49e6f2140b401a190d45d31e052af8f to your computer and use it in GitHub Desktop.
Pretty Print Table Data in Common Lisp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Pretty Printing Table Data in Common Lisp\n", | |
"\n", | |
"Common Lisp has a powerfull but _byzantine_ string formatting language which can do all sorts\n", | |
"of magic. Here we are using the power of\n", | |
"[format](http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm)\n", | |
"to produce [ASCII art](https://en.wikipedia.org/wiki/ASCII_art)\n", | |
"tables from tabular data." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Objective\n", | |
"\n", | |
"Table output should:\n", | |
"* be general purpose (i.e. not specialized to numbers)\n", | |
"* be _pretty_ in its ASCII form\n", | |
"* support simple column level text alignment\n", | |
"* should be usable in Markdown text." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## About this Jupyter Notebook\n", | |
"\n", | |
"This _Gist_ was created using:\n", | |
"* the [Jupyter Lab](https://jupyter.org/) computational notebook.\n", | |
"* the [common-lisp-jupyter](https://yitzchak.github.io/common-lisp-jupyter/) kernel by Frederic Peschanski.\n", | |
"* [Steel Bank Common Lisp](http://www.sbcl.org/) (SBCL)." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Coding Style\n", | |
"\n", | |
"For the most part the [Google Common Lisp Style Guide](https://google.github.io/styleguide/lispguide.xml) is used." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Table Formatting\n", | |
"\n", | |
"The format of choice for table output format is [Markdown](https://www.markdownguide.org/getting-started)\n", | |
"_[pipe tables](https://www.markdownguide.org/extended-syntax)_ which:\n", | |
"* Provides basic cell alignment instructions (left, right, center)\n", | |
"* Is pretty in ASCII form.\n", | |
"\n", | |
"**Example:**\n", | |
"\n", | |
"The Markdown pipe table:\n", | |
"\n", | |
"~~~\n", | |
"| Coord | Value |\n", | |
"| :----: | ----: |\n", | |
"| X | 12.5 |\n", | |
"| Y | 3.1 |\n", | |
"| Z | 19.0 |\n", | |
"~~~\n", | |
"\n", | |
"Renders as (Note: At the time of writing GitHub flavored Markdown did not support column alignment):\n", | |
"\n", | |
"| Coord | Value |\n", | |
"| :----: | ----: |\n", | |
"| X | 12.5 |\n", | |
"| Y | 3.1 |\n", | |
"| Z | 19.0 |\n", | |
"\n", | |
"\n", | |
"We want to do the heavy lifting of formatting key-value data only once, therefore we first define a generic table data model which can be easily generated from a variety of data structures.\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Table Cell Formatting\n", | |
"\n", | |
"To properly format table cells we need to find the corresponding formatting instructions using:\n", | |
"* [Tilde Less-Than-Sign: Justification](http://www.lispworks.com/documentation/HyperSpec/Body/22_cfb.htm)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"\"|Test |\"" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"\"| Test |\"" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"\"| Test|\"" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(format nil \"|~vA|\" 10 \"Test\") ; Left align text in a table cell\n", | |
"(format nil \"|~v:@<~A~>|\" 10 \"Test\") ; Center text in a table\n", | |
"(format nil \"|~v@A|\" 10 \"Test\") ; Right align text in a table cell" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Table Data Model \n", | |
"\n", | |
"To represent table date we use a simple and intuitive list-of-lists model which looks like:\n", | |
"\n", | |
"~~~ Lisp\n", | |
" '(( <obj1.1> ... <obj1.N>) ; row 1\n", | |
" ...\n", | |
" ( <objN.1> ... <objN.N>)) ; row N\n", | |
"~~~" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Table Cell Alignment Format Specification\n", | |
"\n", | |
"With the preparation from the previous chapter the format function can now be implemented.\n", | |
"We start with defining a property list which maps alignment keywords to cell format strings." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"+CELL-FORMATS+" | |
] | |
}, | |
"execution_count": 3, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(defconstant +CELL-FORMATS+ '(:left \"~vA\"\n", | |
" :center \"~v:@<~A~>\"\n", | |
" :right \"~v@A\"))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## format-table [Function]\n", | |
"\n", | |
"Format table data as Markdown pipe table:\n", | |
"\n", | |
"~~~ Lisp\n", | |
"(format-table stream data [:column-label {string list}]\n", | |
" [:colun-align {keyword list}])\n", | |
"~~~\n", | |
"\n", | |
"#### Arguments\n", | |
"\n", | |
"stream {`output-stream`}\n", | |
"> The stream to write the table to. The symbol `T` can be used as\n", | |
"> an alias for `*standard-output*`\n", | |
"\n", | |
"data {`list of lists`}\n", | |
"> Table data of the form:\n", | |
">\n", | |
"> ~~~ Lisp\n", | |
"> '((<obj1.1> ... <obj1.N>) ; row 1\n", | |
"> ...\n", | |
"> (<objN.1> ... <objN.N>)) ; row N\n", | |
"> ~~~\n", | |
"\n", | |
"column-label {`string list`} default `'(\"COL1\" ... \"COLn\")`\n", | |
"> Optional list of column labels. If omitted the default labels\n", | |
"> `\"COL1\" ... \"COLn\"` are used. The\n", | |
"> length of the colum header list given in this parameter must be\n", | |
"> identical to the number of columns in the table.\n", | |
"\n", | |
"column-align {`keyword list`} default `'(:left ... :left)}`\n", | |
"> Optional list of column alignments. A column alignment is\n", | |
"> one of the keywords `:left`, `:center`, `:right`. The\n", | |
"> length of the keyword list given in this parameter must be\n", | |
"> identical to the number of columns in the table." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"FORMAT-TABLE" | |
] | |
}, | |
"execution_count": 4, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(defun format-table (stream data &key (column-label (loop for i from 1 to (length (car data))\n", | |
" collect (format nil \"COL~D\" i)))\n", | |
" (column-align (loop for i from 1 to (length (car data))\n", | |
" collect :left)))\n", | |
" (let* ((col-count (length column-label))\n", | |
" (strtable (cons column-label ; table header\n", | |
" (loop for row in data ; table body with all cells as strings\n", | |
" collect (loop for cell in row\n", | |
" collect (if (stringp cell)\n", | |
" cell\n", | |
" ;else\n", | |
" (format nil \"~A\" cell))))))\n", | |
" (col-widths (loop with widths = (make-array col-count :initial-element 0)\n", | |
" for row in strtable\n", | |
" do (loop for cell in row\n", | |
" for i from 0\n", | |
" do (setf (aref widths i)\n", | |
" (max (aref widths i) (length cell))))\n", | |
" finally (return widths))))\n", | |
" ;------------------------------------------------------------------------------------\n", | |
" ; splice in the header separator\n", | |
" (setq strtable\n", | |
" (nconc (list (car strtable) ; table header\n", | |
" (loop for align in column-align ; generate separator\n", | |
" for width across col-widths\n", | |
" collect (case align\n", | |
" (:left (format nil \":~v@{~A~:*~}\"\n", | |
" (1- width) \"-\"))\n", | |
" (:right (format nil \"~v@{~A~:*~}:\"\n", | |
" (1- width) \"-\"))\n", | |
" (:center (format nil \":~v@{~A~:*~}:\"\n", | |
" (- width 2) \"-\")))))\n", | |
" (cdr strtable))) ; table body\n", | |
" ;------------------------------------------------------------------------------------\n", | |
" ; Generate the formatted table\n", | |
" (let ((row-fmt (format nil \"| ~{~A~^ | ~} |~~%\" ; compile the row format\n", | |
" (loop for align in column-align\n", | |
" collect (getf +CELL-FORMATS+ align))))\n", | |
" (widths (loop for w across col-widths collect w)))\n", | |
" ; write each line to the given stream\n", | |
" (dolist (row strtable)\n", | |
" (apply #'format stream row-fmt (mapcan #'list widths row)))))\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"toc-hr-collapsed": false | |
}, | |
"source": [ | |
"# Examples\n", | |
"\n", | |
"The table data sets used in the examples below were shamelessly stolen from the\n", | |
"[Northwind Database](https://github.com/graphql-compose/graphql-compose-examples/tree/master/examples/northwind/data/csv)." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Customer Table" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"+CUSTOMER-DATA+" | |
] | |
}, | |
"execution_count": 5, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(defconstant +CUSTOMER-DATA+\n", | |
" '((:ALFKI\t\"Alfreds Futterkiste\" \"Maria Anders\")\n", | |
" (:ANATR\t\"Ana Trujillo Emparedados y helados\" \"Ana Trujillo\")\n", | |
" (:ANTON\t\"Antonio Moreno Taquería\" \"Antonio Moreno\")\n", | |
" (:AROUT\t\"Around the Horn\" \"Thomas Hardy\")\n", | |
" (:BERGS\t\"Berglunds snabbköp\" \"Christina Berglund\")\n", | |
" (:BLAUS\t\"Blauer See Delikatessen\" \"Hanna Moos\")\n", | |
" (:BLONP\t\"Blondesddsl père et fils\" \"Frédérique Citeaux\")\n", | |
" (:BOLID\t\"Bólido Comidas preparadas\" \"Martín Sommer\")\n", | |
" (:BONAP\t\"Bon app'\" \"Laurence Lebihan\")\n", | |
" (:BOTTM\t\"Bottom-Dollar Markets\" \"Elizabeth Lincoln\")\n", | |
" (:BSBEV\t\"B's Beverages\" \"Victoria Ashworth\")\n", | |
" (:CACTU\t\"Cactus Comidas para llevar\" \"Patricio Simpson\")\n", | |
" (:CENTC\t\"Centro comercial Moctezuma\" \"Francisco Chang\")\n", | |
" (:CHOPS\t\"Chop-suey Chinese\" \"Yang Wang\")\n", | |
" (:COMMI\t\"Comércio Mineiro\" \"Pedro Afonso\")\n", | |
" (:CONSH\t\"Consolidated Holdings\" \"Elizabeth Brown\")\n", | |
" (:DRACD\t\"Drachenblut Delikatessen\" \"Sven Ottlieb\")))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"NIL" | |
] | |
}, | |
"execution_count": 6, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"| ID | Customer | Contact |\n", | |
"| ----: | ---------------------------------: | :----------------: |\n", | |
"| ALFKI | Alfreds Futterkiste | Maria Anders |\n", | |
"| ANATR | Ana Trujillo Emparedados y helados | Ana Trujillo |\n", | |
"| ANTON | Antonio Moreno Taquería | Antonio Moreno |\n", | |
"| AROUT | Around the Horn | Thomas Hardy |\n", | |
"| BERGS | Berglunds snabbköp | Christina Berglund |\n", | |
"| BLAUS | Blauer See Delikatessen | Hanna Moos |\n", | |
"| BLONP | Blondesddsl père et fils | Frédérique Citeaux |\n", | |
"| BOLID | Bólido Comidas preparadas | Martín Sommer |\n", | |
"| BONAP | Bon app' | Laurence Lebihan |\n", | |
"| BOTTM | Bottom-Dollar Markets | Elizabeth Lincoln |\n", | |
"| BSBEV | B's Beverages | Victoria Ashworth |\n", | |
"| CACTU | Cactus Comidas para llevar | Patricio Simpson |\n", | |
"| CENTC | Centro comercial Moctezuma | Francisco Chang |\n", | |
"| CHOPS | Chop-suey Chinese | Yang Wang |\n", | |
"| COMMI | Comércio Mineiro | Pedro Afonso |\n", | |
"| CONSH | Consolidated Holdings | Elizabeth Brown |\n", | |
"| DRACD | Drachenblut Delikatessen | Sven Ottlieb |\n", | |
"\n", | |
"Evaluation took:\n", | |
" 0.000 seconds of real time\n", | |
" 0.000000 seconds of total run time (0.000000 user, 0.000000 system)\n", | |
" 100.00% CPU\n", | |
" 1,054,994 processor cycles\n", | |
" 65,456 bytes consed\n", | |
" \n" | |
] | |
} | |
], | |
"source": [ | |
"; DEMO: Create a formatted table from data with 3 columns\n", | |
"(time (format-table t +CUSTOMER-DATA+\n", | |
" :column-label '(\"ID\" \"Customer\" \"Contact\")\n", | |
" :column-align '(:right :right :center)))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"When rendered with a Markdown viewer the table looks like (Note: At the time of writing GitHub flavored Markdown did not support column alignment): \n", | |
"\n", | |
" ID | Customer | Contact \n", | |
"----: | ---------------------------------: | :----------------:\n", | |
"ALFKI | Alfreds Futterkiste | Maria Anders \n", | |
"ANATR | Ana Trujillo Emparedados y helados | Ana Trujillo \n", | |
"ANTON | Antonio Moreno Taquería | Antonio Moreno \n", | |
"AROUT | Around the Horn | Thomas Hardy \n", | |
"BERGS | Berglunds snabbköp | Christina Berglund\n", | |
"BLAUS | Blauer See Delikatessen | Hanna Moos \n", | |
"BLONP | Blondesddsl père et fils | Frédérique Citeaux\n", | |
"BOLID | Bólido Comidas preparadas | Martín Sommer \n", | |
"BONAP | Bon app' | Laurence Lebihan \n", | |
"BOTTM | Bottom-Dollar Markets | Elizabeth Lincoln \n", | |
"BSBEV | B's Beverages | Victoria Ashworth \n", | |
"CACTU | Cactus Comidas para llevar | Patricio Simpson \n", | |
"CENTC | Centro comercial Moctezuma | Francisco Chang \n", | |
"CHOPS | Chop-suey Chinese | Yang Wang \n", | |
"COMMI | Comércio Mineiro | Pedro Afonso \n", | |
"CONSH | Consolidated Holdings | Elizabeth Brown \n", | |
"DRACD | Drachenblut Delikatessen | Sven Ottlieb " | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Single Column Data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"+SINGLE-COLUMN-DATA+" | |
] | |
}, | |
"execution_count": 7, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(defconstant +SINGLE-COLUMN-DATA+\n", | |
" '((\"Alfreds Futterkiste\")\n", | |
" (\"Ana Trujillo Emparedados y helados\")\n", | |
" (\"Antonio Moreno Taquería\")\n", | |
" (\"Around the Horn\")\n", | |
" (\"Berglunds snabbköp\")\n", | |
" (\"Blauer See Delikatessen\")\n", | |
" (\"Blondesddsl père et fils\")\n", | |
" (\"Bólido Comidas preparadas\")\n", | |
" (\"Bon app'\")\n", | |
" (\"Bottom-Dollar Markets\")\n", | |
" (\"B's Beverages\")\n", | |
" (\"Cactus Comidas para llevar\")\n", | |
" (\"Centro comercial Msoctezuma\")\n", | |
" (\"Chop-suey Chinese\")\n", | |
" (\"Comércio Mineiro\")\n", | |
" (\"Consolidated Holdings\")\n", | |
" (\"Drachenblut Delikatessen\")))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"NIL" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"| Customer |\n", | |
"| :--------------------------------: |\n", | |
"| Alfreds Futterkiste |\n", | |
"| Ana Trujillo Emparedados y helados |\n", | |
"| Antonio Moreno Taquería |\n", | |
"| Around the Horn |\n", | |
"| Berglunds snabbköp |\n", | |
"| Blauer See Delikatessen |\n", | |
"| Blondesddsl père et fils |\n", | |
"| Bólido Comidas preparadas |\n", | |
"| Bon app' |\n", | |
"| Bottom-Dollar Markets |\n", | |
"| B's Beverages |\n", | |
"| Cactus Comidas para llevar |\n", | |
"| Centro comercial Msoctezuma |\n", | |
"| Chop-suey Chinese |\n", | |
"| Comércio Mineiro |\n", | |
"| Consolidated Holdings |\n", | |
"| Drachenblut Delikatessen |\n", | |
"\n", | |
"Evaluation took:\n", | |
" 0.000 seconds of real time\n", | |
" 0.000000 seconds of total run time (0.000000 user, 0.000000 system)\n", | |
" 100.00% CPU\n", | |
" 320,908 processor cycles\n", | |
" 0 bytes consed\n", | |
" \n" | |
] | |
} | |
], | |
"source": [ | |
"; DEMO: Create a formatted table from data with 1 column\n", | |
"(time (format-table t +SINGLE-COLUMN-DATA+\n", | |
" :column-label '(\"Customer\")\n", | |
" :column-align '(:center)))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Tables with Numbers" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"+NUMERIC-DATA+" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(defconstant +NUMERIC-DATA+\n", | |
" '((10248 11 14.00 12 0)\n", | |
" (10248 42 9.80 10 0)\n", | |
" (10248 72 34.80 5 0)\n", | |
" (10249 14 18.60 9 0)\n", | |
" (10249 51 42.40 40 0)\n", | |
" (10250 41 7.70 10 0)\n", | |
" (10250 51 42.40 35 0.15)\n", | |
" (10250 65 16.80 15 0.15)\n", | |
" (10251 22 16.80 6 0.05)\n", | |
" (10251 57 15.60 15 0.05)\n", | |
" (10251 65 16.80 20 0)))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"NIL" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"| Order# | Product# | Price | Quantity | Discount |\n", | |
"| :----: | :------: | ----: | -------: | :------- |\n", | |
"| 10248 | 11 | 14.0 | 12 | 0 |\n", | |
"| 10248 | 42 | 9.8 | 10 | 0 |\n", | |
"| 10248 | 72 | 34.8 | 5 | 0 |\n", | |
"| 10249 | 14 | 18.6 | 9 | 0 |\n", | |
"| 10249 | 51 | 42.4 | 40 | 0 |\n", | |
"| 10250 | 41 | 7.7 | 10 | 0 |\n", | |
"| 10250 | 51 | 42.4 | 35 | 0.15 |\n", | |
"| 10250 | 65 | 16.8 | 15 | 0.15 |\n", | |
"| 10251 | 22 | 16.8 | 6 | 0.05 |\n", | |
"| 10251 | 57 | 15.6 | 15 | 0.05 |\n", | |
"| 10251 | 65 | 16.8 | 20 | 0 |\n", | |
"\n", | |
"Evaluation took:\n", | |
" 0.000 seconds of real time\n", | |
" 0.000000 seconds of total run time (0.000000 user, 0.000000 system)\n", | |
" 100.00% CPU\n", | |
" 666,304 processor cycles\n", | |
" 65,376 bytes consed\n", | |
" \n" | |
] | |
} | |
], | |
"source": [ | |
"; DEMO: Create a formatted table from data with 3 columns\n", | |
"(time (format-table t +NUMERIC-DATA+\n", | |
" :column-label '(\"Order#\" \"Product#\" \"Price\" \"Quantity\" \"Discount\")\n", | |
" :column-align '(:center :center :right :right :left)))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Reference" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Formatting Directives\n", | |
"\n", | |
"These format commands were used in the implementation of the pretty print methods:\n", | |
"\n", | |
"* [Tilde Tilde](http://www.lispworks.com/documentation/HyperSpec/Body/22_cae.htm)\n", | |
"* [Tilde Left-Brace: Iteration](http://www.lispworks.com/documentation/HyperSpec/Body/22_cgd.htm)\n", | |
"* [Tilde Asterisk: Go-To](http://www.lispworks.com/documentation/HyperSpec/Body/22_cga.htm)\n", | |
"* [Tilde Less-Than-Sign: Justification](http://www.lispworks.com/documentation/HyperSpec/Body/22_cfb.htm)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## See also\n", | |
"* [18. A Few FORMAT Recipes](http://www.gigamonkeys.com/book/a-few-format-recipes.html) from [Practical Common Lisp](http://www.gigamonkeys.com/book/)\n", | |
"* [22.3 Formatted Output](http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm) from [Common Lisp Hyperspec](http://www.lispworks.com/documentation/HyperSpec/Front/index.htm)\n", | |
"* [Google Common Lisp Style Guide](https://google.github.io/styleguide/lispguide.xml)\n", | |
"* [pipe tables](https://www.markdownguide.org/extended-syntax) from [Markdown Guide](https://www.markdownguide.org)\n", | |
"* [What is Markdown](https://www.markdownguide.org/getting-started) from [Markdown Guide](https://www.markdownguide.org)\n", | |
"* [A simple data dump from SQL's Northwind database to CSV](https://github.com/graphql-compose/graphql-compose-examples/tree/master/examples/northwind/data/csv)" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Common Lisp", | |
"language": "common-lisp", | |
"name": "common-lisp" | |
}, | |
"language_info": { | |
"codemirror_mode": "text/x-common-lisp", | |
"file_extension": ".lisp", | |
"mimetype": "text/x-common-lisp", | |
"name": "common-lisp", | |
"pygments_lexer": "common-lisp", | |
"version": "2.0.0" | |
}, | |
"toc": { | |
"base_numbering": 1, | |
"nav_menu": {}, | |
"number_sections": true, | |
"sideBar": true, | |
"skip_h1_title": false, | |
"title_cell": "Table of Contents", | |
"title_sidebar": "Contents", | |
"toc_cell": true, | |
"toc_position": { | |
"height": "calc(100% - 180px)", | |
"left": "10px", | |
"top": "150px", | |
"width": "193.865px" | |
}, | |
"toc_section_display": true, | |
"toc_window_display": true | |
}, | |
"toc-showtags": false | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Very good catch lispm! Your input is very much appreciated!
This is great. I am about finished with table formatting functions for use with the pretty printer and thought about a markdown/restructured text version. Glad I found this. Perhaps these two could be combined into a project, say, "formatted-tables" ? My stuff is under the MS-PL. What's the license for this?
@Symbolics, the code has no usage restrictions. Use it as you see fit.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can get rid of APPLY: