Mercury
1 The Big Idea
1.1 The build process
1.2 Source/  output path mapping
source/  output-pattern?
source-path-pattern?
output-path-pattern?
file-extension?
non-rkt-file-extension?
2 Reference
2.1 Site Definitions
site
define-mercury-site
2.2 Using Sites
page
site-client%
new
make-site!
get-pages
get-non-source-rkt
get-source-folders
get-pages-in-src-folder
get-root
get-site-tag
get-output-folder
get-static-assets
9.0

Mercury🔗

 (require mercury) package: mercury

This is a preview document; it describes software that is currently unfinished and has not been published anywhere. As of July 2025 the software implements almost everything described here.

Mercury is a framework for static website publishing that comes with a GUI — Mercury Terminal — for the most common tasks. It is build on top of my other web publishing packages:

  • punct for authoring in Racket-extensible Commonmark

  • splitflap for generating fully-validated RSS feeds

  • html-printer for human-friendly HTML output

Mercury Terminal provides a quick way to create/edit posts, build your sites, preview your sites on localhost, and publish to your web server.

  • You can double-click on a source file to edit it, or click New page to start a new file with the date and draft? metas pre-filled. You can choose which text editor you prefer in Mercury Terminal’s preferences dialog.

  • When you start the site preview, Mercury starts a local web server on an available port, serving the "publish" subfolder of your package diretory, and opens a tab in your browser to localhost:8001/. It watches the files in your package directory and rebuilds your site if any of them change (The build process).

  • If you drag-and-drop an image onto Mercury Terminal, it will copy the image into "static/images/", optionally optimize it (using a script or procedure you provide), and provide markup you can copy/paste into your source files.

    1 The Big Idea

      1.1 The build process

      1.2 Source/output path mapping

    2 Reference

      2.1 Site Definitions

      2.2 Using Sites

1 The Big Idea🔗

In Mercury, you create each website as a local Racket package. The package folder houses your site’s source files: which are documents written in #lang punct (or at least provide a Punct document binding named doc), and which have a particular file extension (".page.rkt" by default). Each source file corresponds to a single ".html" output file.

The site module for your site’s package must provide a site struct using define-mercury-site. This struct provides information about the site, as well as procedures used for building the site.

You can lay out your project’s folder structure however you want. By default, the "static" folder is used for static assets, and the "publish" folder is used for the built website.

If a source file has a date meta value of the form "YYYY-MM-DD" (tag-entity-date?) then Mercury converts it to a moment for use as the post date which is supplied to the render procedure and the site feed.

For each source file, Mercury computes a slug, which is a URL-friendly identifier string. If a source file has a slug meta value, then Mercury uses that for the slug; otherwise it uses the source file’s filename (without any file extensions).

The define-mercury-site macro is the only way to create such a struct:

#lang racket/base
 
(require mercury/define-site
         "render.rkt")
 
(define-mercury-site #:title "My Site"
                     #:author-name "My Name"
                     #:author-email "me@example.com"
                     #:founded "2008-03"
                     #:url "https://example.com"
                     #:source-render-proc main-template
                     #:output-paths '("events/YYYY/*/" "blog/YYYY/mm/dd/*/")
                     #:source-ext ".md_rkt")
  • Any folder containing source files is included in the site build process.

  • Source files are mapped to output files/paths using the patterns supplied in the #:output-paths argument to define-mercury-site (see Source/output path mapping).

  • The "publish" subfolder is reserved for use as the output path for the site build. Its contents may be deleted or overwritten any time the site is built.

  • Files and folders in the "static" subfolder are copied statically into the built site (the "publish" folder).

1.1 The build process🔗

Mercury follows these steps when it builds your site:

  1. Mercury uses the name of your site’s module path to find its root folder on your filesystem, and, separately, to dynamically require its site-info value.

  2. Mercury locates every source file in the root folder and any subfolders, and uses the site-info to construct a mapping of source files to output file paths.

  3. To this source→output map, Mercury adds all files/folders in the "static" subfolder.

  4. Mercury assumes that any ".rkt" file that is not also a source file affects the entire site; Thus if any such file, or any other file in your site’s cache watchlist, has a modification timestamp later than any single existing output file in the "publish" subfolder, the "publish" subfolder is wiped for a clean build, and Mercury runs raco make -j n -l sitemodpath, where n is two less than the number of CPU cores on your computer.

  5. Any entry in the source→output map whose source is newer than its output file (or whose output file does not exist) is rebuilt, using a simple file copy if the source is in "static", or using the site’s source-render-proc otherwise.

  6. Mercury regenerates your site’s RSS/Atom feed. All web pages whose source files include a date meta value, and whose draft? meta value, if present, is not #f, are included in the feed.

1.2 Source/output path mapping🔗

 (require mercury/path-map) package: mercury

Every source file in your site produces an HTML file, and this file needs to be placed somewhere in your website’s public folder structure. Source/output path maps allow you to decide what that public folder structure will look like, and which output files will get placed in which folders.

Here’s an example of a source/output path mapping:

'("blog/*" . "blog/YYYY/MM/DD/*/")

Under this mapping, a source file saved as "blog/first-post.md.rkt", and whose date meta is set to 19 January 2025, will be output as "blog/2025/01/19/first-post/index.html".

The mapping consists of a pair of values: a source-path pattern and an output-path pattern.

A source-path pattern is a relative path ending with a single *. The pattern matches all source files contained in the path, including in any unnamed subfolders of the given path.

An output-path pattern is a relative path which must contain a * as at least one of its elements. The pattern describes the folder structure that will be created to house the output file. Any folder name in the pattern which is valid CIDR syntax will be replaced with a new name, generated by formatting the source file’s date information with that pattern using ~t. Any folder given as * will be replaced with the source file’s slug. If the output-path pattern ends in a slash, the output file will be named "index.html"; otherwise, the output file will be the name of the pattern’s final path element with the ".html" extension added.

Here are some more examples:

; Given a 'blog/first-post.md.rkt' source dated 2025-01-19:
'("blog/*" . "blog/YYYY/MM/DD/*/") ; → blog/2025/01/19/first-post/index.html
'("blog/*" . "quarterly/YYYY/MM/*") ; → quarterly/2025/01/first-post.html
'("blog/*" . "blog/*") ; → blog/first-post.html
'("blog/*" . "annual-events/*/YYYY") ; → annual-events/first-post/2025.html
'("blog/*" . "blog/YYYY/qqq/*/") ; → blog/2025/Q2/first-post/index.html

Shorthand path maps: You’ll notice there is a lot of repetition. If the folder holding your source folders is the same as the first folder in your output path, you can supply just the output path pattern instead, and the source path pattern will be inferred.

Thus, "blog/YYYY/*/" is equivalent to '("blog/*" . "blog/YYYY/*/"), and "blog/posts/*" is equivalent to '("blog/*" . "blog/posts/*").

procedure

(source/output-pattern? v)  boolean?

  v : any/c
Returns #t if v is either an output-path-pattern? (the shorthand form) or a pair consisting of a source-path-pattern? and an output-path-pattern?.

(source/output-pattern? "blog/YYYY/MM/*/") ; #t
(source/output-pattern? '("foo/*" . "pages/*")) ; #t

procedure

(source-path-pattern? v)  boolean?

  v : any/c
Returns #t if v is a source-path pattern — that is, a relative path which does not contain "." or ".." elements, and whose final element is "*" and not syntactically a directory.

(source-path-pattern? "blog/*") ; #t
(source-path-pattern? "blog/subfolder/*") ; #t
(source-path-pattern? "blog/*/") ; #f (must end with *)
(source-path-pattern? "blog/../../*") ; #f (. and .. not allowed)

procedure

(output-path-pattern? v)  boolean?

  v : any/c
Returns #t if v is an output-path pattern — that is, a relative path which does not contain "." or ".." elements, and which contains at least one element consisting only of "*".

(output-path-pattern? "blog/*") ; #t
(output-path-pattern? "blog/*/foo") ; #t
(output-path-pattern? "blog/**/foo") ; #f (must contain * as one of the elements)
(output-path-pattern? "blog/../../*") ; #f (. and .. not allowed)

procedure

(file-extension? v)  boolean?

  v : any/c
Returns #t if v is a string or byte-string that begins with ., does not contain any directory separators (\ or /), double periods or newlines, and is at least two characters long.

procedure

(non-rkt-file-extension? v)  boolean?

  v : any/c
Returns #t if v is a string or byte-string that is a valid file-extension? and is not equal to ".rkt" (or #".rkt").

2 Reference🔗

2.1 Site Definitions🔗

 (require mercury/site-info) package: mercury

struct

(struct site (root
    title
    authors
    founded
    url
    render-proc
    pathmap-proc
    options))
  root : absolute-path?
  title : non-empty-string?
  authors : (listof person?)
  founded : tag-entity-date?
  url : valid-url-string?
  render-proc : (-> relative-path? document? non-empty-string? (or/c xexpr? bytes?))
  pathmap-proc : (-> relative-path? non-empty-string? moment? relative-path?)
  options : (hash/c symbol? any/c)
Structure containing data related to a website. These can only be created using define-mercury-site.

  • The root field is the path to the site’s root folder; this is set by define-mercury-site to the folder containing the module where the define-mercury-site expression appears.

  • The title, authors, founded, and url fields are used to build the site’s feed.

  • The render-proc takes a path to a source file (which is relative to root), the file’s Punct document, and the file’s slug, and must return either an HTML X-expression or bytes?.

  • The pathmap-proc takes a relative path to a source file, a slug and a post date and returns a path to an output filename (relative to root). This procedure is automatically constructed by define-mercury-site from its #:output-paths argument.

syntax

(define-mercury-site
  [site-id]
  #:title title
  #:author-name author-name-or-names
  #:author-email author-email-or-emails
  #:founded founded
  #:url site-url
  #:output-paths path-maps
  #:source-render-proc render-proc
  [#:source-ext source-ext]
  [#:static-folder static-folder]
  [#:output-folder output-folder]
  [#:cache-watchlist cache-watchlist]
  [#:feed-id feed-id]
  [#:feed-type feed-type])
 
  title : non-empty-string?
  author-name-or-names : (or/c non-empty-string? (listof non-empty-string?))
  author-email-or-emails : (or/c email-address? (listof email-address?))
  founded : tag-entity-date?
  site-url : valid-url-string?
  path-maps : (listof source/output-pattern?)
  render-proc : (-> document? non-empty-string? xexpr?)
  source-ext : non-rkt-file-extension?
  static-folder : relative-path?
  output-folder : relative-path?
  cache-watchlist : (listof relative-path?)
  feed-id : tag-specific-string?
  feed-type : (or/c 'atom 'rss)
Defines a site. If site-id is given, binds the resulting struct to that identifier; otherwise, a temporary identifier is used. In either case, the resulting struct is provided as site-info (using rename-out).

When either author-name-or-names or author-email-or-emails is a list, they must both be lists of the same length. These arguments are combined into the equivalent number of person structs in the resulting site.

See the site struct definition for more info on this macro’s non-optional arguments.

The optional arguments are stored in the site-options field of the resulting struct, and are described below:

Optional Arg

 

Description

 

Default

#:source-ext

 

File extension used to indicate source files.

 

".page.rkt"

#:static-folder

 

Name of the subfolder used to hold static assets.

 

"static"

#:output-folder

 

Name of the subfolder used to hold the rendered output files for the site.

 

"publish"

#:cache-watchlist

 

List of additional files to watch that trigger a complete rebuild if changed.

 

()

#:feed-type

 

Sets the type of feed generated for the site (either 'atom or 'rss).

 

'atom

#:feed-id

 

If not #f, a string used for the “specific” portion of the tag URI minted for the site’s feed.

 

#f

2.2 Using Sites🔗

 (require mercury/site-client) package: mercury

This module exposes the structs and classes used by the Mercury Terminal app to interact with sites that provide info via define-mercury-site.

This section is not essential reading for using Mercury. It’s here for my own reference, and you will find it useful if you want to build your own tools for working with Mercury sites instead of using the Mercury Terminal app.

struct

(struct page (source target doc slug draft?)
    #:extra-constructor-name make-page)
  source : absolute-path?
  target : absolute-path?
  doc : document?
  slug : non-empty-string?
  draft? : any/c
Struct containing info about each source file in a site. When a site-client% object is created for a given module, it dynamically constructs page structs for each source file using the information in the module’s define-mercury-site expression.

class

site-client% : class?

  superclass: object%

This class facilitates the use of site-info bindings provided by other modules.
Currently some of its methods return vectors instead of lists, for easier use with racket/gui/easy in the Mercury Terminal app.

constructor

(new site-client% [modpath modpath])  (is-a?/c site-client%)

  modpath : module-path?
Creates a new site-client% instance. The module referenced must have a define-mercury-site expression.

method

(send a-site-client make-site! method)  void?

  method : (or/c 'incremental 'fresh)

This method will probably be changed to pass in the name of an updated file.

Re-renders files in the site’s output folder, ensuring the output folder contains a fresh copy of all output files and static files, and that any files in the output folder which are not in either of those sets are deleted.

If method is 'incremental, then only the output files and static files that don’t already exist, or which are older than their source files, are re-rendered. If method is any other value, then the output folder is deleted and recreated, all static files are re-copied into the output folder, and all pages are rendered into their output files.

The site’s Atom/RSS feed is always regenerated when this method is called.

method

(send a-site-client get-pages)  (vectorof page?)

Returns a vector of all pages for the site.

method

(send a-site-client get-non-source-rkt)

  (listof absolute-path?)
Returns a list of all ".rkt" files that are not also source files.

method

(send a-site-client get-source-folders)

  (vectorof absolue-path?)
Returns a vector of all folders in the site that contain source files.

method

(send a-site-client get-pages-in-src-folder folder)

  (vectorof page?)
  folder : absolute-path?
Returns a vector of pages contained in folder.

method

(send a-site-client get-root)  absolute-path?

Returns the root folder for the site.

method

(send a-site-client get-site-tag)  tag-uri?

Returns the tag URI used in the site’s Atom feed.

method

(send a-site-client get-output-folder)  absolute-path?

Returns the path to the site’s output folder (where generated files are placed).

method

(send a-site-client get-static-assets)

  (listof absolute-path?)
Returns a list of all files and and subfolders in the site’s “static” folder.