/blog/

2025 0531 Hammerspoon docs content adapter

Hammerspoon is an automation tool for macOS. You can publish spoons, which are modules written in Lua that run for Hammerspoon. Traditionally, spoon documentation is written in doc comments and then JSON is generated by Hammerspoon’s own tooling (and HTML then generated from that).

Hugo is a static site generator (which I use for this website and lots of others). It recently added support for content adapters, which let you build pages out of structured data in JSON (as well as other formats like CSV/TOML/etc).

I built a content adapter that reads Hammerspoon’s generated documentation JSON and builds a Hugo page for each Lua module in the spoon.

{{ range .Site.Data.docs }}

  {{ $sanitizedName := .name | urlize }}
  {{ $module := . }}
  {{ $content := .doc }}

  {{/* Hammerspoon section order and descriptions */}}
  {{ $sectionOrder := slice "Deprecated" "Command" "Constant" "Variable" "Function" "Constructor" "Field" "Method" }}
  {{ $sectionDetails := dict
    "Deprecated" "API features which will be removed in an upcoming release"
    "Command" "External shell commands"
    "Constant" "Useful values which cannot be changed"
    "Variable" "Configurable values"
    "Function" "API calls offered directly by the extension"
    "Constructor" "API calls which return an object, typically one that offers API methods"
    "Field" "Variables which can only be accessed from an object returned by a constructor"
    "Method" "API calls which can only be made on an object returned by a constructor"
  }}

  {{/* Check for submodules */}}
  {{ if .submodules }}
    {{ $content = printf "%s\n\n## Submodules\n" $content }}
    {{ range .submodules }}
      {{ $fullModuleName := printf "%s.%s" $module.name . }}
      {{ $moduleLink := printf "../%s" ($fullModuleName | urlize) }}
      {{ $content = printf "%s* [%s](%s)\n" $content $fullModuleName $moduleLink }}
    {{ end }}
  {{ end }}

  {{/* API Overview */}}
  {{ $content = printf "%s\n\n## API Overview\n" $content }}
  {{ $activeSections := slice }}

  {{ range $sectionOrder }}
    {{ $sectionName := . }}
    {{ $items := index $module $sectionName }}
    {{ if $items }}
      {{ $activeSections = $activeSections | append $sectionName }}
      {{ $sectionDesc := index $sectionDetails $sectionName }}
      {{ $content = printf "%s* %ss - %s\n" $content $sectionName $sectionDesc }}
      {{ range $items }}
        {{ $content = printf "%s  * [%s](#%s)\n" $content .name (.name | urlize) }}
      {{ end }}
    {{ end }}
  {{ end }}

  {{/* API Documentation */}}
  {{ $content = printf "%s\n\n## API Documentation\n" $content }}

  {{ range $activeSections }}
    {{ $sectionName := . }}
    {{ $items := index $module $sectionName }}
    {{ $content = printf "%s\n### %ss\n" $content $sectionName }}

    {{ range $items }}
      {{ $content = printf "%s\n#### %s {#%s}\n\n" $content .name (.name | urlize) }}
      {{ $content = printf "%s<table>\n" $content }}
      {{ if .def }}
        {{ $content = printf "%s<tr><td><strong>Signature</strong></td><td><code>%s</code></td></tr>\n" $content .def }}
      {{ else if .signature }}
        {{ $content = printf "%s<tr><td><strong>Signature</strong></td><td><code>%s</code></td></tr>\n" $content .signature }}
      {{ end }}
      {{ $content = printf "%s<tr><td><strong>Type</strong></td><td>%s</td></tr>\n" $content .type }}
      {{ $content = printf "%s<tr><td><strong>Description</strong></td><td>%s</td></tr>\n" $content .doc }}
      {{ $content = printf "%s</table>\n\n" $content }}
    {{ end }}
  {{ end }}

  {{ $contentDict := dict "mediaType" "text/markdown" "value" $content }}
  {{ $page := dict
    "content" $contentDict
    "kind" "page"
    "path" $sanitizedName
    "title" .name
  }}
  {{ $.AddPage $page }}

{{ end }}

To use this:

  1. Make sure Hammerspoon is running and that the hs command line tool is installed.

  2. Generate docs.json with something like:

    hs -A -c "hs.doc.builder.genJSON(\"$(pwd)\")" |
        grep -v "^--" |
        jq -S . \
        > data/docs.json
    

    The grep removes log output from the doc generator, and the jq sorts keys so that output is deterministic.

  3. Save the above code as _content.gotmpl in a section of your choice, e.g. content/your-spoon/_content.gotmpl

And that’s it.

I am using it in the documentation site for GridCraft, an action menu based on Starcraft 2 Grid Hotkeys I built in Hammerspoon. The full release isn’t ready yet — but the API docs are looking pretty good!

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).