Moonlab needed a way to turn a directory of AsciiDoc files into a website.

There are already more static site generators than anyone needs. I wrote another one anyway.

It's called anchor. It's written in Common Lisp, it builds this site, and it has a small job: read AsciiDoc documents and write a directory of static files.

The short reason is that I wanted less than most static site generators give me. I did not want a database, a theme system, a template language, an asset pipeline, or a deployment system. I wanted the source tree to look like the site, and I wanted the content to contain the facts about itself.

Anchor does that. Most of the interesting parts follow from those two choices.

A document, not a string

Anchor is built on cl-adoc, an AsciiDoc parser and HTML renderer that I also write in Common Lisp.

cl-adoc separates parsing from rendering. It reads an AsciiDoc file and builds a document tree. Rendering that tree as HTML is a later step.

That distinction matters to anchor.

Anchor does not read a title from one file, a date from front matter, an excerpt from a marker, and a page body from rendered HTML. It asks the parsed document for those things.

The title is the document title. The date is a document attribute. Headings are nodes in the tree. Links and images are references. An excerpt is part of the document, not a string cut from the final HTML.

Everything comes from the document.

That choice makes several common static-site features unnecessary. Anchor has no front matter because it does not need a second place to repeat facts that already exist in the document. It does not use regular expressions to inspect rendered HTML because it still has the document that produced the HTML.

It also makes checking a site simple. Before anchor writes anything, it knows which pages exist and which files they reference. anchor check can walk that graph and report missing targets, unused files, duplicate output paths, page sizes, and resolved references.

It checks what the author wrote, not a guess about what the rendered text means.

The filesystem is the model

The rest of the site comes from the directory tree.

A directory that contains an index.adoc is a page. Its path is its URL.

content/
  index.adoc
  about/
    index.adoc
  posts/
  index.adoc
  2026/
    anchor/
      index.adoc

content/index.adoc becomes /. The about page becomes /about/. This article becomes /posts/2026/anchor/.

There is no slug field and no permalink setting. Moving the source moves the page.

A directory can also be both a page and a section. posts/index.adoc is the page at /posts/, and the directories below it are posts. That relationship already exists in the filesystem, so anchor does not ask me to describe it again.

Files can live beside the page that uses them. Shared files can live higher in the tree. Configuration follows the tree and inherits from parent directories.

I like this because I can look at the source directory and know what site it describes. There is very little hidden state.

Why Lisp

The practical answer is that Lisp fits the way anchor works.

Layouts are Common Lisp functions. Configuration is Common Lisp. HTML layouts are ordinary Lisp data. There is no separate template language because I already have a language.

When I run anchor serve, the site graph stays in the Lisp image. I can inspect a page at the REPL, redefine a layout function, rebuild one page, and keep going. I did not add a special hot-reload system to get that behavior. It comes from working in a Lisp image.

There is also a less technical answer.

I sometimes tell people that I tend to think in Lisp.

That does not mean I think every program should be written in Common Lisp. I have spent much of my career writing production software in C, C++, Go, and other languages chosen for the work, the client, or the system around it. Those choices come with constraints that a personal project does not have.

When I am working through an idea for myself, Lisp is often where my hands go first.

Anchor is my tool. It did not need to justify its implementation language to anyone but me.

What anchor does not do

Anchor has no plugin system.

It has no theme format, asset pipeline, image processor, shortcode system, or deployment subsystem.

These are not unfinished features. Anchor does not need to know about them.

A theme can be a CSS file and a layout function. An emitter can write another file, such as an Atom feed, a sitemap, or robots.txt. If I need to copy the finished site to a server, rsync, Git, and CI systems already know how to copy files.

I do not want anchor to become the place where every task related to a website must live.

The program should know enough to build the site and check its own work. Other programs can keep their jobs.

Finished

I wrote a specification for anchor before I finished the implementation. It defines what version 1 must do, what I might consider later, and what the program will not do.

Version 1 now does everything marked for version 1.

So anchor is finished.

I do not mean abandoned. I will fix bugs. I will keep it working with released versions of cl-adoc. If the program fails to do something the specification says it does, that is a defect.

But a finished program does not need a roadmap for the sake of having one.

Software often grows because growth itself is treated as proof that a project is alive. A feature gets added, then a configuration option for the feature, then an extension point for people who need the feature to work another way. Each addition creates more to document, test, understand, and keep working.

Sometimes that is the right trade. For anchor, it is not.

I wanted a tool that does a known amount of work and then stops. Reaching that stopping point is part of the design, not a failure of ambition.

Moonlab is the first site built with anchor.

This page started as an AsciiDoc file. cl-adoc parsed it. Anchor used that document to build the page, check its references, place it at this URL, and write an ordinary HTML file.

That seems as good a place as any to start.