Liking cljdoc? Tell your friends :D

data-md

Render Clojure data and EDN files as readable GitHub-Flavored Markdown.

Clojars Project

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.

Installation

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.

Compatibility

  • Clojure: render, render-table, render-file, write-file!, and CLI.
  • ClojureScript: render and render-table from the pure .cljc renderer namespaces.
  • Babashka: 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.

Quick Start

(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 |

Public API

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

Render EDN Files

(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}`

Render Tables

(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]})

What It Renders

data-md handles:

  • scalars: nil, booleans, strings, chars, keywords, symbols, numbers, UUIDs, instants, tagged literals
  • maps: top-level sections, nested key-value lists, nested subsections
  • sequences: tables for row-shaped data, bullet lists for mixed values
  • sets: deterministic sorted bullet lists
  • records: type line plus field rendering
  • deep or unknown values: fenced Clojure code or inline printable fallback

Large and lazy collections are bounded by :max-collection-size, so infinite sequences are truncated instead of fully realized.

Options

Common options:

OptionDefaultMeaning
:titlenilOptional # document title
:heading-level2Heading level for top-level map sections
:final-newline?trueAppend one final newline
:line-ending"\n"Output line ending
:max-depth4Depth where nested data falls back to fenced code
:max-collection-size100Maximum 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
:columnsnilExplicit table columns
:missing-cell""Table cell text for absent values
:newline-in-table-cell"<br>"Replacement for newlines inside table cells
:table-alignnil:left, :center, :right, or a column alignment map
scalar style optsmixed:plain or :code for strings, keywords, symbols, numbers, booleans, nil, chars, instants, UUIDs
:code-language"clojure"Fenced code block info string
:key-label-fnnilCustom map-key label function
:value-renderernilCustom value renderer hook
:cell-renderernilCustom table cell renderer hook
:sort-key-fnnilCustom deterministic sort key for unordered maps and sets
:include-metadata?falseInclude metadata before rendered values
:readers{}EDN tagged-literal readers for file/CLI input
:default-reader:tagged-literalUnknown 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%

CLI

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.

Babashka

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

Design Philosophy

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.

Non-Goals

data-md is not:

  • a Markdown parser
  • an HTML renderer
  • a notebook system
  • a schema documentation generator
  • a static site generator
  • a replacement for Clerk, Kindly, Codox, or Markdown parser libraries

Development

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.

License

MIT. See LICENSE.

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