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:
-
Make sure Hammerspoon is running and that the
hs
command line tool is installed. -
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 thejq
sorts keys so that output is deterministic. -
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!