Skip to content

A minimal markup DSL and AST for JSON - Transforms into HTML, SVG or XML-like output via CLI or JS library

License

Notifications You must be signed in to change notification settings

metaory/markup.json

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
logo

A minimal markup DSL and AST for JSON

Transforms into HTML, SVG or XML-like output
via CLI or JS library
w/ First Class Attribute Strings

markup.json is a compact, declarative JSON-based DSL for representing markup structures. It defines an AST format and provides a CLI and library to transform it into XML-like output (HTML, SVG, RSS, etc).


preview-json preview-html

🎯 Where markup.json Shines

Functional XML Generation

markup.json is the perfect functional tool for generating any XML-like output. Use it for HTML, SVG, RSS feeds, or any structured markup:

# Generate SVG from data
curl api.example.com/chart-data | jq 'transform_to_svg' | markup > chart.svg

# Generate RSS feed
curl api.example.com/posts | jq 'transform_to_rss' | markup > feed.xml

# Generate HTML portfolio
curl api.github.com/users/metaory/repos | jq 'transform_to_portfolio' | markup > portfolio.html

Unix Pipeline Integration

Perfect synergy with jq for functional data transformation:

# One-liner: API → Transform → Markup → Output
curl api.example.com/data | jq '.[] | select(.active) | transform_to_card' | markup > cards.html

Configuration-Driven UI Generation

Generate HTML from JSON configs without template engines:

[
  "dashboard",
  ["h1", "User Dashboard"],
  ["div", {"class": "stats"},
    ["span", "Users: 1,234"],
    ["span", "Revenue: $56,789"]
  ]
]

API Response to HTML Transformation

Convert API responses directly to HTML without intermediate steps:

curl api.example.com/users | jq 'transform_to_markup' | markup > users.html

Static Site Generation from Data

Perfect for generating documentation, portfolios, or reports from structured data:

  • Documentation sites from JSON schemas
  • Portfolio pages from project metadata
  • Report generation from analytics data

CLI Tools with Rich Output

Create beautiful CLI outputs that can be piped to HTML:

git log --format=json | jq 'transform_to_changelog' | markup > changelog.html

Micro-Frontend Composition

Compose UI components as JSON and render them:

[
  "app",
  ["header", {"component": "nav"}],
  ["main", {"component": "dashboard"}],
  ["footer", {"component": "stats"}]
]

🌟 Unique Advantages

  • Zero Dependencies - Pure JSON, no framework needed
  • Language Agnostic - Works with any language that can generate JSON
  • Version Control Friendly - JSON diffs are clean and readable
  • Pipeline Integration - Perfect for Unix-style data processing
  • Schema Validation - Can validate structure with JSON Schema
  • Template Composition - Combine multiple JSON templates
  • Dynamic Attributes - First-class support for complex attributes
  • Functional Style - Pure functions, no side effects
  • XML Agnostic - Generate any XML-like format (HTML, SVG, RSS, etc.)

🚀 Quick Start

Installation

Library:

npm install markup.json
# or
pnpm add markup.json

CLI:

npm i -g markup.json
# or
pnpm add -g markup.json
# or with npx
npx markup.json

Basic Usage

Library:

import markup from 'markup.json'

const html = markup([
  "Hello",
  ["h1", "World"],
  ["p", "This is markup.json!"]
])

CLI:

echo '["Hello", ["h1", "World"], ["p", "This is markup.json!"]]' | markup

🤔 The Headless HTML Generation Problem

Imagine you're in a CI environment with some JSON data and need to generate HTML. Here's how you'd solve it without markup.json:

Traditional Approach (Complex)

# Option 1: Template Engine (Node.js)
npm install handlebars
node -e "
const Handlebars = require('handlebars');
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('data.json'));
const template = fs.readFileSync('template.hbs', 'utf8');
const compiled = Handlebars.compile(template);
fs.writeFileSync('output.html', compiled(data));
"

# Option 2: Python Template Engine
pip install jinja2
python -c "
import json
from jinja2 import Template
with open('data.json') as f: data = json.load(f)
with open('template.html') as f: template = Template(f.read())
with open('output.html', 'w') as f: f.write(template.render(**data))
"

# Option 3: Sed/Awk (Fragile)
cat data.json | jq -r '.items[] | "\(.name): \(.description)"' | \
awk '{print "<div><h3>" $1 "</h3><p>" $2 "</p></div>"}' > output.html

With markup.json (Simple)

# One-liner: Data → Transform → HTML
cat data.json | jq 'transform_to_markup' | markup > output.html

Why markup.json wins:

  • ✅ No dependencies - No npm/pip installs needed
  • ✅ Pure functional - Single pipeline, no side effects
  • ✅ Version control friendly - JSON templates are readable and diffable
  • ✅ Language agnostic - Works with any tool that outputs JSON
  • ✅ CI/CD ready - Perfect for automated environments

📖 Complete Example

[!Tip] Here are some real usage examples

cat .github/preview.json
[
  "headless",
  "",
  ["h1", "marκup.json"],
  ["hr"],
  [
    "h4",
    "DOM tree",
    "representation in",
    ["i", "compact"],
    "JSON"
  ],
  [
    "a",
    {
      "class": "primary",
      "data-planet-id": "92432",
      "href": [
        "github.com/search?",
        {
          "q": "markup",
          "type": "repositories"
        }
      ],
      "style": {
        "color": "indigo",
        "background": "fuchsia"
      }
    },
    "🔥 First Class Attribute Strings"
  ],
  "Spec",
  "CLI",
  "Library"
]
import { readFile } from 'node:fs/promises'
import markup from 'markup.json'

const opt = { encoding: 'utf8' }
const tpl = await readFile('./tpl.json', opt)

const html = markup(JSON.parse(tpl))
headless

<h1>
  marκup.json
</h1>
<hr />
<h4>
  DOM tree
  representation in
  <i>
    compact
  </i>
  JSON
</h4>
<a
  class="primary"
  data-planet-id="92432"
  href="github.com/search?q=markup&type=repositories&"
  style="color:indigo; background:fuchsia;"
>
  🔥 First Class Attribute Strings
</a>
Spec
CLI
Library

📋 CLI Usage Examples

CLI Synopsis

markup [-]|FILE [FILE]

Reads input from standard input or FILE

Writes to stdout or FILE

CLI Usage

  # read input and output path from args
markup [FILE] [FILE]
markup tpl.json index.html
    # or with npx
npx markup.json tpl.json index.html
  # read input path from args
  # write output to standard output
markup [FILE]
markup tpl.json
markup tpl.json > index.html
    # or with npx
npx markup.json tpl.json > index.html
  # read input from standard input
  # write output to standard output
cat FILE | markup
cat tpl.json | markup
cat tpl.json | markup > index.html
    # or with npx
npx markup.json tpl.json > index.html
  # read from file descriptor
  # write output to standard output
markup < FILE
markup < tpl.json
markup < tpl.json > index.html
    # or with npx
npx markup.json < tpl.json > index.html

🔧 Advanced Features & Types

Types

type Tag = string

type primitive = string | number | boolean

type AttributeString = [string, { [k: string]: primitive }]

type Attribute =
  | { [k: string]: primitive | AttributeString | object }
  | primitive

type Node = [Tag, Attribute?, ...primitive[]] | string

type Markup = Node[]

Primitive Attribute values

Attributes with Primitive values are rendered as is;

[
  "top level",
  "can be without tag",
  [
    "button",
    {
      "class": "primary btn",
      "name": "xorg"
    },
    "hi",
    ["b", "main"],
    "btn",
    "here"
  ]
]
top level
can be without tag
<button
  class="primary btn"
  name="xorg"
>
  hi
  <b> main </b>
  btn here
</button>

[!Tip] Attributes can come at any position after tag

[
  "attributes open",
  "position",
  [
    "button",
    "hell",
    {
      "class": "primary btn",
      "name": "xorg"
    },
    "OoO",
    ["b", "main"],
    "btn",
    "here"
  ]
]
attributes open
position
<button
  class="primary btn"
  name="xorg"
>
  hell
  OoO
  <b>
    main
  </b>
  btn
  here
</button>

[!Tip] Repeated attributes will merge

[
  "repeated",
  "attributes",
  [
    "button",
    {
      "class": "primary btn",
      "name": "xorg"
    },
    "hi",
    { "class": "shadowed", "id": "usr" },
    ["b", "main"],
    "btn",
    "here"
  ],
  "EO"
]
repeated
attributes
<button
  class="shadowed"
  name="xorg"
  id="usr"
>
  hi
  <b>
    main
  </b>
  btn
  here
</button>
EO

Attribute with Object values

Attributes with Object values are folded delimit key and value pairs with ; delimit keys and values with :

[
  "OBJ Values",
  ["h4", "Attribute with Object values"],
  [
    "span",
    {
      "class": "secondary",
      "style": {
        "color": "indigo",
        "background": "fuchsia"
      },
      "anything": {
        "name": "etc",
        "planet": "8e81"
      }
    },
    "Object values",
    ["b", "x11"],
    "xorg"
  ]
]
OBJ Values
<h4>
  Attribute with Object values
</h4>
<span
  class="secondary"
  style="color:indigo; background:fuchsia;"
  anything="name:etc; planet:8e81;"
>
  Object values
  <b>
    x11
  </b>
  xorg
</span>

Attribute with Array values

Attributes with Object values are folded delimit key and value pairs with & delimit keys and values with =

[
  "begin",
  ["h4", "Attribute with Array values"],
  [
    "a",
    {
      "class": "secondary",
      "href": [
        "github.com/search?",
        {
          "q": "markup",
          "type": "repositories",
          "l": "Lua"
        }
      ]
    },
    "go",
    ["b", "find"],
    "repos"
  ]
]
begin
<h4>
  Attribute with Array values
</h4>
<a
  class="secondary"
  href="github.com/search?q=markup&type=repositories&l=Lua&"
>
  go
  <b>
    find
  </b>
  repos
</a>
[
  "attributes with",
  "array values",
  [
    "img",
    {
      "width": "80%",
      "alt": "stats",
      "src": [
        "github-readme-stats.vercel.app/api?",
        {
          "username": "metaory",
          "ring_color": "5522CC",
          "text_color": "44BBFF",
          "border_radius": 30,
          "hide_title": true,
          "hide_rank": false,
          "show_icons": true
        }
      ]
    }
  ]
]
attributes with
array values
<img
  width="80%"
  alt="stats"
  src="github-readme-stats.vercel.app/api?username=metaory&ring_color=5522CC&text_color=44BBFF&border_radius=30&hide_title=true&hide_rank=false&show_icons=true&"
 />

[!Tip] Values are normalized with Unicode NFC Form Canonical Composition

"\u0041\u006d\u00e9\u006c\u0069\u0065" would be "Amélie"

ref: Unicode_equivalence


[!Note] The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.

ref: 2.3.2 Boolean attributes -- html.spec.whatwg.org

[
  "Boolean attributes",
  ["hr"],
  [
    "label",
    [
      "input",
      {
        "type": "checkbox",
        "name": "cheese",
        "disabled": false,
        "checked": true
      }
    ],
    "Cheese"
  ]
]
Boolean attributes
<hr />
<label>
  <input
    type="checkbox"
    name="cheese"
    checked
   />
  Cheese
</label>

License

MIT

About

A minimal markup DSL and AST for JSON - Transforms into HTML, SVG or XML-like output via CLI or JS library

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks