Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

homebrew-kiln

Homebrew tap for kiln, a template compiler for GFI-like syntax.

Install

brew install teehemkay/kiln/kiln

Build static website prototypes from small, reusable HTML pieces — no build config, no gulpfile.js, no framework. Kiln is a command-line template compiler for GFI-like (gulp-file-include) syntax: you write plain HTML sprinkled with @@ directives — includes, variables, conditionals, loops — and kiln resolves them into finished HTML you can open in a browser or hand to a client.

Install

Kiln ships through two channels:

  • Homebrew (macOS / Linux) installs a self-contained binary — no Java, no Node:
    brew install teehemkay/kiln/kiln
  • npm (macOS / Linux / Windows) installs @webmaster/kiln, a JVM-uberjar wrapper that needs JDK 17+ on your PATH:
    npm i -g @webmaster/kiln
    It lives on the European Parliament GitLab npm registry (project ep/dg-comm/webmaster/sef/kiln), not public npm, so point the @webmaster scope at it once first — the project page walks through the one-time scope and token setup.

Then confirm it's on your PATH:

kiln --version

Your first prototype in five minutes

We'll build a tiny two-page site — a home page and a programme page — that share a <head> and a nav bar, with the programme built by repeating one session card over a list of sessions. Then we'll preview it live. Create this layout:

site/
└── src/templates/
    ├── pages/                  ← complete pages (these get built)
    │   ├── index.html
    │   └── program.html
    └── partials/               ← fragments (included, never built on their own)
        ├── head.html
        ├── nav.html
        └── session-card.html

The rule that makes this work: pages live under pages/, fragments under partials/. Kiln builds the .html files under pages/ — partials are pulled in by @@include / @@loop and never rendered on their own. (src/templates/pages and src/templates/partials are the defaults, so commands need no layout flags.)

The pages

src/templates/pages/index.html — owns the <head>, fills it from a partial, and hands each include some data:

<!DOCTYPE html>
<html lang="en">
<head>
  @@include('head.html', {title: 'Global Entrepreneurship Week'})
</head>
<body>
  @@include('nav.html', {active: 'home'})

  <main>
    <h1>One week. Thousands of events. Everywhere.</h1>
    <p>Welcome to GEW.</p>
  </main>
</body>
</html>

src/templates/pages/program.html — reuses the same partials, then repeats a card for every session via @@loop:

<!DOCTYPE html>
<html lang="en">
<head>
  @@include('head.html', {title: 'Programme'})
</head>
<body>
  @@include('nav.html', {active: 'program'})

  <main>
    <h1>Full programme</h1>
    <ul class="sessions">
      @@loop('session-card.html', [
        {name: 'Founder AMA',    from: '18:00', to: '18:45', online: true},
        {name: 'Pitch Night',    from: '19:30', to: '21:00', online: false},
        {name: 'Investor Panel', from: '17:00', to: '18:00', online: true}
      ])
    </ul>
  </main>
</body>
</html>

The partials

Each @@include / @@loop names its target by the path under partials/head.html, not partials/head.html.

src/templates/partials/head.html — holds the head's contents (the page already provides the <head> element), and uses the title it was handed:

<meta charset="utf-8">
<title>@@title — GEW</title>
<link rel="stylesheet" href="/assets/site.css">

src/templates/partials/nav.html — drops the active value into an attribute so CSS can highlight the current page:

<nav data-active="@@active">
  <a href="/index.html">Home</a>
  <a href="/program.html">Programme</a>
</nav>

src/templates/partials/session-card.html — rendered once per object in the loop. Each object's keys (name, from, to, online) become that iteration's variables, and @@when / @@unless branch on them:

<li class="session">
  <h3>@@name</h3>
  <p class="time">@@from – @@to</p>
  @@when(online)
    <span class="tag">Online</span>
  @@end-when
  @@unless(online)
    <span class="tag tag-venue">In person</span>
  @@end-unless
</li>

See it live

From inside the project (cd site):

kiln dev -o build

Kiln renders the pages into build/, opens them in your browser, and re-renders on every save with live-reload — edit a partial, watch every page that uses it update. This is the loop you'll spend most of your time in. (dev annotates the output with data-kiln-* provenance attributes — data-kiln-source, data-kiln-chain, data-kiln-projection — so you can trace each element back to its template; add --no-annotate to turn that off.)

program.html's loop compiles to plain HTML — one <li> per session, no @@ left behind (kiln keeps the source whitespace, so the dropped @@unless branches leave blank lines):

<ul class="sessions">
      <li class="session">
  <h3>Founder AMA</h3>
  <p class="time">18:00 – 18:45</p>

    <span class="tag">Online</span>

</li>
<li class="session">
  <h3>Pitch Night</h3>
  <p class="time">19:30 – 21:00</p>

    <span class="tag tag-venue">In person</span>

</li>
<li class="session">
  <h3>Investor Panel</h3>
  <p class="time">17:00 – 18:00</p>

    <span class="tag">Online</span>

</li>

    </ul>

That's the whole idea: write a piece once, reuse it everywhere, feed it data.


The directives

Everything above is built from six @@ directives. All of them start with @@, are case-sensitive, and take no space before (.

Include a partial — @@include('path')

Splices another file in place. The path is the partial's name under partials/ — independent of which file does the including — in single or double quotes. No ./, no ../, no leading /.

@@include('nav.html')

Pass data into an include — @@include('path', {…})

The object becomes the partial's variables. This is how one partial serves many pages:

@@include('head.html', {title: 'Programme', year: 2026})

Inside head.html, @@title resolves to Programme and @@year to 2026.

Substitute a variable — @@name

A variable is @@ + a lowercase name (letters, digits, interior hyphens — @@page-title is one name). It's filled from the data the partial was given, or from the current loop item.

<title>@@title</title>
<body class="@@theme">

A variable with no value left to fill is reported as a warning when you build — so missing data never silently vanishes (add --strict to turn that warning into a failed build).

Brace form — @@{name} disambiguates where a name runs into literal text. Because hyphens are part of a name, @@slug-thumb reads as one variable; braces mark where the name ends:

<img src="/img/@@{slug}-thumb.jpg">
<!-- slug = "founder-ama"  →  /img/founder-ama-thumb.jpg -->

Show something conditionally — @@when / @@unless

Wrap content between the directive and its @@end-…. @@when keeps the block when the value is truthy; @@unless keeps it when falsy. (Falsy = false, null, 0, ""; everything else is truthy.) Condition names are letters/digits only — no hyphens here.

@@when(online)
  <span class="tag">Online</span>
@@end-when

@@unless(soldout)
  <a href="/register">Register</a>
@@end-unless

They nest:

@@when(featured)
  <article class="hero">
    @@unless(online)
      <p>In-person only</p>
    @@end-unless
  </article>
@@end-when

Repeat a template — @@loop('path', [ … ])

Renders a partial once per object in an array (at least one object). Each object's keys become that iteration's variables. Keys are unquoted and strings single-quoted — the JavaScript-object style gulp-file-include uses; double-quoted JSON ({"name": "Founder AMA"}) works too.

<ul>
  @@loop('session-card.html', [
    {name: 'Founder AMA', from: '18:00', to: '18:45', online: true},
    {name: 'Pitch Night', from: '19:30', to: '21:00', online: false}
  ])
</ul>

Share a layout — @@slot / @@fill

Look again at the two pages above: they share a whole shell — the same <head> include, the same nav, the same <main> wrapper. Only three things differ between them: the title, the active nav item, and the main content. Factor the shell into one partial and let each page supply just those three.

A layout partial holds the shared chrome and marks each varying region with @@slot(name):

src/templates/partials/layout.html

<!DOCTYPE html>
<html lang="en">
<head>
  @@include('head.html', {title: '@@title'})
</head>
<body>
  @@include('nav.html', {active: '@@active'})
  <main>
    @@slot(main)
  </main>
</body>
</html>

Each page becomes a block include of the layout: it passes the scalar values as data and fills the slot with @@fill(name) … @@end-fill, closing the block with @@end-include.

src/templates/pages/index.html

@@include('layout.html', {title: 'Global Entrepreneurship Week', active: 'home'})
  @@fill(main)
    <h1>One week. Thousands of events. Everywhere.</h1>
    <p>Welcome to GEW.</p>
  @@end-fill
@@end-include

src/templates/pages/program.html

@@include('layout.html', {title: 'Programme', active: 'program'})
  @@fill(main)
    <h1>Full programme</h1>
    <ul class="sessions">
      @@loop('session-card.html', [
        {name: 'Founder AMA',    from: '18:00', to: '18:45', online: true},
        {name: 'Pitch Night',    from: '19:30', to: '21:00', online: false},
        {name: 'Investor Panel', from: '17:00', to: '18:00', online: true}
      ])
    </ul>
  @@end-fill
@@end-include

The chrome now lives in one place. index.html renders to the same page as before:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
<title>Global Entrepreneurship Week — GEW</title>
<link rel="stylesheet" href="/assets/site.css">

</head>
<body>
  <nav data-active="home">
  <a href="/index.html">Home</a>
  <a href="/program.html">Programme</a>
</nav>

  <main>
    
    <h1>One week. Thousands of events. Everywhere.</h1>
    <p>Welcome to GEW.</p>
  
  </main>
</body>
</html>

Three rules make this work:

  • A slot name is a bare identifier@@slot(main), not @@slot('main'). Lowercase letters and digits, no quotes, no hyphens. Each @@fill(main) matches the slot of the same name.
  • Data handed to the include becomes the layout's variables. {title: 'Programme'} makes @@title resolve to Programme inside layout.html; writing {title: '@@title'} forwards that value on into head.html, so one value threads down the whole chain of includes.
  • A block include's body holds only fills. Every contribution sits inside a @@fill … @@end-fill; text left loose between the fills is an error.

A @@slot placed inside a @@fill re-exposes the hole, forwarding projection further down the chain — so layouts can nest inside layouts.

Two kiln extras

HTML won't let you put @@ in a tag name or in a bare attribute, so kiln adds two escape hatches.

A tag name from a variable — <kiln-tag kiln-tag="@@var">. Write a kiln-tag element carrying a kiln-tag attribute; kiln renames the element to that attribute's value, drops the attribute, and keeps everything else:

<kiln-tag kiln-tag="@@region" class="callout">
  <p>Chosen at render time.</p>
</kiln-tag>

With region = section, that becomes:

<section class="callout">
  <p>Chosen at render time.</p>
</section>

A conditional attribute — kiln-attr-<name>="@@var". kiln emits the attribute <name> from the resolved value, which lets you drive attributes HTML would otherwise force you to hard-code:

<input type="checkbox" kiln-attr-checked="@@selected">
<a href="/now" kiln-attr-aria-current="@@here">Today</a>

The value decides what appears:

  • "true" → a bare boolean attribute: selected = true gives <input type="checkbox" checked>.
  • empty or unresolved → the attribute is dropped entirely.
  • anything elsename="value": here = page gives <a href="/now" aria-current="page">Today</a>.

Everyday commands, by example

You met kiln dev above. The rest, run from the project root, as you'll actually use them:

# Build the site once into ./build (the pages, not the partials)
kiln render -o build

# About to share a draft? Confirm every include resolves and no partial
# is missing — writes nothing, just checks.
kiln preflight

# Serve a folder you already built, with live-reload
kiln serve build

The full command set

  • render — compile templates to finished HTML (-o <dir>).
  • dev — render, watch, and serve with live-reload (-o <dir>).
  • serve — serve an already-built directory with live-reload.
  • preflight — validate templates without writing anything: every include resolves, no partial missing.
  • check — report where a template's re-serialized HTML differs from its source (roundtrip fidelity).
  • parse — write each template's parsed syntax tree as EDN (-o <dir>).
  • normalize — migrate a legacy-layout project to the default pages/ + partials/ layout (writes only with -o).
  • version — print kiln's version (also kiln --version).

The template commands — render, preflight, check, parse, dev — take --root <dir> (project root, default the current directory), plus --pages / --includes to point at a non-default layout. render, preflight, and dev add --strict (turn warnings into a failed build); render and dev add --annotate. serve and dev take --port and --no-browser. Run kiln help <command> (or just kiln) for every flag and examples.


License

MIT (MIT). The full license text ships in the package's LICENSE file.

About

Homebrew tap for the kiln template compiler

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages