Liking cljdoc? Tell your friends :D

clj-string-layout

CI Status Release Clojars cljdoc License

clj-string-layout turns rows of strings into aligned text — Markdown tables, box-drawing terminal output, CSV, HTML, psql, Org mode, and anything you can build out of the underlying layout DSL.

One spec, many shapes

Describe the data once:

(require '[clj-string-layout.table :as table])

(def items
  [{:item "apple" :qty 12 :price 1.50}
   {:item "pear"  :qty  4 :price 2.00}
   {:item "kiwi"  :qty  8 :price 0.75}])

(def cols
  [:item
   {:from :qty   :as "Qty"   :align :right}
   {:from :price :as "Price" :align :right
    :formatter   #(format "$%.2f" %)}])

Render it any way you need.

Box drawing — terminals, docs, REPL output

(table/table-str {:format :box :columns cols :rows items})
┌───────┬─────┬───────┐
│ item  │ Qty │ Price │
├───────┼─────┼───────┤
│ apple │  12 │ $1.50 │
├───────┼─────┼───────┤
│ pear  │   4 │ $2.00 │
├───────┼─────┼───────┤
│ kiwi  │   8 │ $0.75 │
└───────┴─────┴───────┘

Markdown — READMEs, issue templates, generated docs

(table/table-str {:format :markdown :columns cols :rows items})
| item  | Qty | Price |
|:----- | ---:| -----:|
| apple |  12 | $1.50 |
| pear  |   4 | $2.00 |
| kiwi  |   8 | $0.75 |

HTML — emails, reports, server output

(table/table-str {:format :html :columns cols :rows items})
<table>
  <tr><th>item</th><th>Qty</th><th>Price</th></tr>
  <tr><td>apple</td><td>12</td><td>$1.50</td></tr>
  <tr><td>pear</td><td>4</td><td>$2.00</td></tr>
  <tr><td>kiwi</td><td>8</td><td>$0.75</td></tr>
</table>

CSV — handoff to spreadsheets, downstream tools

(table/table-str {:format :csv :columns cols :rows items})
item,Qty,Price
apple,12,$1.50
pear,4,$2.00
kiwi,8,$0.75

The same cols and items also render as :double-box, :ascii-grid, :psql, :org, :rst, :tsv, :pipe, :plain, and the three alignment-specific :markdown-* variants. The examples gallery shows every named format side by side with the same data.

Install

;; deps.edn
{:deps {io.github.mbjarland/clj-string-layout {:mvn/version "2.1.1"}}}

No third-party Clojure runtime dependencies — a Babashka script can require it directly:

#!/usr/bin/env bb
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {io.github.mbjarland/clj-string-layout
                        {:mvn/version "2.1.1"}}})

(require '[clj-string-layout.table :as table])
(println (table/table-str {:format :box
                           :headers ["Name" "Qty"]
                           :rows [["apple" 12] ["pear" 4]]}))

Tested on Java 11, 17, and 21.

Two layers

The example above is the high-level table API. Underneath it sits a small layout DSL — the engine every named format is built on, and the thing you reach for when you want text to line up in ways the table API doesn't quite cover (custom borders, ANSI dashboards, log lines with aligned timestamps, key/value pairs, anything).

┌─ HIGH ────────────────────────────────────────────────────────────────┐
│ clj-string-layout.table                                               │
│ (table/table {:format :box :columns [...] :rows [...]})               │
│ → Most callers start (and stop) here.                                 │
├───────────────────────────────────────────────────────────────────────┤
│ clj-string-layout.core/layout                                         │
│ (layout rows {:layout {:cols ["[L] [R]"]}})                           │
│ → For anything the table API doesn't reach.                           │
│   Pre-canned configs in clj-string-layout.presets.                    │
└─ LOW ─────────────────────────────────────────────────────────────────┘

Deeper docs: table API · examples gallery · layout language · recipes · preset catalog · CLI · errors.

Table API reference

Four moving parts.

Rows

A vector of maps:

:rows [{:item "apple" :qty 12} {:item "pear" :qty 4}]

…or a vector of positional vectors:

:rows [["apple" 12] ["pear" 4]]

Columns

Two shapes:

:qty                                                  ;; defaults
{:from :qty :as "Qty" :align :right                   ;; explicit
 :formatter #(format "%s units" %)
 :width 10 :overflow :ellipsis}

Map keys:

KeyMeans
:fromRow map key. Omit for vector rows — the column's source is its position.
:asHeader label. Defaults to the keyword name of :from.
:align:left, :center, :right, or :verbatim.
:formatterOne-arg function applied to the cell value before rendering.
:widthMaximum cell width.
:overflow:none, :clip, :ellipsis, :wrap, :error.

Format

A keyword. One of:

:plain · :markdown · :markdown-left · :markdown-center · :markdown-right · :box · :double-box · :ascii-grid · :csv · :tsv · :pipe · :psql · :org · :rst · :html.

Extras

KeyAdds
:titleCentered caption above text formats; <caption> for HTML.
:footersTrailing rows that share column treatment.
:cell-fnPer-cell decoration callback — pair with :display-width for ANSI styling.
:width + :fill?Expand the rendered table toward a target width.
:raw?Return vectors of pieces instead of joined strings.

See the table API guide for the full surface.

Layout DSL

When the named formats don't reach, drop down to the layout DSL. The column layout is a string made of column markers ([L] [C] [R] [V]), fill regions (f), and optional repeat groups ({…}):

(require '[clj-string-layout.core :refer [layout]])

(layout [["left" "right"]]
        {:width     30
         :fill-char \.
         :layout    {:cols ["[L]f[R]"]}})
;; => ["left.....................right"]

The string above has two column markers ([L] and [R]) and one fill region (f) between them — extra width is absorbed by the fill so the right-hand value sits flush against the target width. The DSL also handles virtual row layouts (top/middle/bottom rules), repeat groups for variable column counts, raw-piece output for cell decoration, and streaming via layout-seq. See the layout language reference for the grammar and the recipe book for paste-able examples.

Command line

clojure -M:cli -- --input csv --format markdown --headers data.csv
bb bb-format --input tsv --format box < data.tsv

bb bb-format runs natively under Babashka in ~50 ms instead of the JVM path's ~700 ms. See the CLI guide for the full option list and the programmatic cli/render entry point.

Development

Local checks, code expectations, and the release process live in CONTRIBUTING.md. Documentation is rebuilt on every release at cljdoc.org.

License

Copyright © 2017-2026 Matias Bjarland · Eclipse Public License 1.0

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close