Render Clojure data and EDN files as readable GitHub-Flavored Markdown.
data-md is a small Clojure library for turning ordinary values into Markdown reports, tables, issue comments, README snippets, and GitHub Actions summaries. It emits Markdown directly, has no runtime Markdown parser, and keeps the core API intentionally small.
Use the Clojars coordinate:
{:deps {net.clojars.deadmeme5441/data-md {:mvn/version "0.2.0"}}}
For local development or local Maven installation:
clojure -T:build install
The library runtime depends only on Clojure.
render, render-table, render-file, write-file!, and CLI.render and render-table from the pure .cljc renderer namespaces.render, render-table, render-file, write-file!, and CLI.File I/O is JVM/Babashka-only. ClojureScript callers should parse data in their host environment and call render or render-table.
(require '[data-md.core :as md])
(md/render {:project "foo"
:status :green
:deps [{:lib 'a :version "1.0"}
{:lib 'b :version "2.0"}]})
Output:
## Project
foo
## Status
`:green`
## Deps
| Lib | Version |
| --- | --- |
| `a` | 1.0 |
| `b` | 2.0 |
(md/render data)
(md/render data opts)
(md/render-forms [data-1 data-2])
(md/render-forms [data-1 data-2] opts)
(md/render-table rows)
(md/render-table rows opts)
(md/read-edn-forms "input.edn")
(md/read-edn-forms reader opts)
(md/render-file "deps.edn")
(md/render-file "deps.edn" opts)
(md/write-file! "deps.edn" "docs/deps.md")
(md/write-file! "deps.edn" "docs/deps.md" opts)
Render functions return strings. read-edn-forms returns a vector of EDN values.
write-file! writes Markdown and returns the output path.
(md/render-file "deps.edn" {:title "deps.edn"})
(md/write-file! "deps.edn" "docs/deps.md" {:title "deps.edn"})
Files may contain one or more top-level EDN forms. A single form renders exactly
like render. Multiple forms render under numbered sections:
{:project "foo"}
{:status :green}
renders as:
## Form 1
### Project
foo
## Form 2
### Status
`:green`
Unknown EDN tagged literals are preserved by default:
#my/tag {:a 1}
renders as:
`#my/tag {:a 1}`
(md/render-table [{:name "Ada" :role :admin}
{:name "Rich" :language "Clojure"}])
Output:
| Name | Role | Language |
| --- | --- | --- |
| Ada | `:admin` | |
| Rich | | Clojure |
Vector rows are supported when columns are supplied:
(md/render-table [["Ada" "Clojure"]
["Rich" "Clojure"]]
{:columns [:name :lang]})
data-md handles:
Large and lazy collections are bounded by :max-collection-size, so infinite sequences are truncated instead of fully realized.
Common options:
| Option | Default | Meaning |
|---|---|---|
:title | nil | Optional # document title |
:heading-level | 2 | Heading level for top-level map sections |
:final-newline? | true | Append one final newline |
:line-ending | "\n" | Output line ending |
:max-depth | 4 | Depth where nested data falls back to fenced code |
:max-collection-size | 100 | Maximum rendered items before truncation |
:map-style | :auto | :auto, :sections, :list, or :code-block |
:seq-style | :auto | :auto, :table, :list, or :code-block |
:set-style | :list | :list or :code-block |
:record-style | :map-with-type | :map-with-type, :map, or :code-block |
:columns | nil | Explicit table columns |
:missing-cell | "" | Table cell text for absent values |
:newline-in-table-cell | "<br>" | Replacement for newlines inside table cells |
:table-align | nil | :left, :center, :right, or a column alignment map |
| scalar style opts | mixed | :plain or :code for strings, keywords, symbols, numbers, booleans, nil, chars, instants, UUIDs |
:code-language | "clojure" | Fenced code block info string |
:key-label-fn | nil | Custom map-key label function |
:value-renderer | nil | Custom value renderer hook |
:cell-renderer | nil | Custom table cell renderer hook |
:sort-key-fn | nil | Custom deterministic sort key for unordered maps and sets |
:include-metadata? | false | Include metadata before rendered values |
:readers | {} | EDN tagged-literal readers for file/CLI input |
:default-reader | :tagged-literal | Unknown EDN tag handling; use nil to reject unknown tags |
Unknown options are rejected with ex-info so typos fail early.
Custom value rendering:
(md/render {:coverage 0.9234}
{:value-renderer
(fn [v _ctx]
(when (and (number? v) (<= 0 v 1))
(format "%.2f%%" (* 100 v))))})
Output:
## Coverage
92.34%
Run with Clojure:
clojure -M -m data-md.cli input.edn
clojure -M -m data-md.cli input.edn output.md
clojure -M -m data-md.cli --title "deps.edn" input.edn output.md
clojure -M -m data-md.cli --table rows.edn
clojure -M -m data-md.cli --columns name,lang --table rows.edn
clojure -M -m data-md.cli --align right --table rows.edn
clojure -M -m data-md.cli --align-col lang=center --table rows.edn
clojure -M -m data-md.cli --max-depth 3 --max-items 50 input.edn
clojure -M -m data-md.cli --map-style list --nil-style plain input.edn
clojure -M -m data-md.cli --line-ending crlf --no-final-newline input.edn
clojure -M -m data-md.cli --version
Use - for stdin or stdout:
cat input.edn | clojure -M -m data-md.cli - -
CLI failures use exit code 2 for invalid CLI usage and 1 for read, render,
or write failures.
The runtime namespaces are Babashka-compatible:
bb -cp src -m data-md.cli test-resources/simple.edn
bb -cp src -e "(require '[data-md.core :as md]) (println (md/render {:a 1}))"
Repo tasks:
bb test
bb cljs-test
bb render test-resources/simple.edn
data-md optimizes for readable, deterministic Markdown, not perfect round-tripping. It uses sections, lists, tables, code spans, and fenced code blocks where they make the value easier to inspect.
Tables intentionally keep cells inline because GFM tables do not support arbitrary block Markdown inside cells.
data-md is not:
Run tests:
clojure -M:test
clojure -M:cljs-test
bb test
bb consumer-smoke
Build a jar:
clojure -T:build jar
Install locally:
clojure -T:build install
Deploy to Clojars:
clojure -T:build deploy
Deployment reads CLOJARS_USERNAME and CLOJARS_PASSWORD from the environment. The release workflow publishes when a v* tag is pushed.
MIT. See LICENSE.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |