A few notes on documentation for Hammerspoon Spoons that I took as I was writing GridCraft and the Hammerspoon docs.json content adapter for it.
First, read the Spoon docs on writing Spoon docs. This shows an example of the basic format of the documentation comments.
Documentation generation
There are two processes you should understand:
-
Using
genJSON
:hs -c "hs.doc.builder.genJSON(\"$(pwd)\")" | grep -v "^--" > docs.json
This will generate
docs.json
only.It looks through all Lua (and ObjC) files in the passed directory recursively, so make sure that doesn’t include anything you don’t want docs for.
It does some basic checking, but doesn’t show which file has an error.
It requires the
hs
command-line tool installed but nothing else. -
Using
build_docs.py
:hammerspoondir=/path/to/hammerspoon/checkout docsout=dist/docs src="$pwd" python "$hammerspoondir/scripts/docs/bin/build_docs.py" \ --standalone \ --templates "$hammerspoondir/scripts/docs/templates" \ --output_dir "$docsout" \ --json \ --html \ --markdown \ --standalone \ "$src"
This requires the
hs
cli tool installed, as well as a local checkout of the Hammerspoon source code and various Python dependencies installed.It does more checking and shows more detailed errors including which lines contain problems for the docstring parser.
It builds HTML documentation, but unfortunately for my purposes it isn’t really suitable for hosting anywhere except on the official https://hammerspoon.org site.
Docstring format
Some notes on the docstring format:
- Docstrings must start with 3 dashes and a space
- Bullets must start with an additional space, so three dashes + two spaces
- Sections like
Parameters
/Notes
/etc must end with a colon - Section content must not contain any blank lines
Examples
The most helpful things I found for writing Hammerspoon docs were examples and parsing code in the Hammerspoon repo itself.
hs.window.layout:setScreenConfiguration()
hs.window.layout:setScreenConfiguration(screens) shows a method with parameters, returns, notes, and examples sections.
Documentation comment for the method
--- hs.window.layout:setScreenConfiguration(screens) -> hs.window.layout object
--- Method
--- Determines the screen configuration that permits applying this windowlayout
---
--- Parameters:
--- * screens - a map, where each *key* must be a valid "hint" for `hs.screen.find()`, and the corresponding
--- value can be:
--- * `true` - the screen must be currently present (attached and enabled)
--- * `false` - the screen must be currently absent
--- * an `hs.geometry` point (or constructor argument) - the screen must be present and in this specific
--- position in the current arrangement (as per `hs.screen:position()`)
---
--- Returns:
--- * the `hs.window.layout` object
---
--- Notes:
--- * If `screens` is `nil`, any previous screen configuration is removed, and this windowlayout will be always allowed
--- * For "active" windowlayouts, call this method *before* calling `hs.window.layout:start()`
--- * By using `hs.geometry` size objects as hints you can define separate layouts for the same physical screen at different resolutions
--- * With this method you can define different windowlayouts for different screen configurations (as per System Preferences->Displays->Arrangement).
--- * For example, suppose you define two "graphics design work" windowlayouts, one for "desk with dual monitors" and one for "laptop only mode":
--- * "passive mode" use: you call `:apply()` on *both* on your chosen hotkey (via `hs.hotkey:bind()`), but only the appropriate layout for the current arrangement will be applied
--- * "active mode" use: you just call `:start()` on both windowlayouts; as you switch between workplaces (by attaching or detaching external screens) the correct layout "kicks in" automatically - this is in effect a convenience wrapper that calls `:pause()` on the no longer relevant layout, and `:resume()` on the appropriate one, at every screen configuration change
---
--- Examples:
--- ```lua
--- local laptop_layout,desk_layout=... -- define your layouts
--- -- just the laptop screen:
--- laptop_layout:setScreenConfiguration{['Color LCD']='0,0',dell=false,['3840x2160']=false}:start()
--- -- attached to a 4k primary + a Dell on the right:
--- desk_layout:setScreenConfiguration{['3840x2160']='0,0',['dell']='1,0',['Color LCD']='-1,0'}:start()
--- -- as above, but in clamshell mode (laptop lid closed):
--- clamshell_layout:setScreenConfiguration{['3840x2160']='0,0',['dell']='1,0',['Color LCD']=false}:start()
--- ```
logger.lua
examples
logger.lua shows that the doc comments can be found anywhere, not necessarily right next to their code.
An excerpt of some doc strings
--- hs.logger.setLogLevel(loglevel)
--- Method
--- Sets the log level of the logger instance
---
--- Parameters:
--- * loglevel - can be 'nothing', 'error', 'warning', 'info', 'debug', or 'verbose'; or a corresponding number between 0 and 5
---
--- Returns:
--- * None
--- hs.logger.getLogLevel() -> number
--- Method
--- Gets the log level of the logger instance
---
--- Parameters:
--- * None
---
--- Returns:
--- * The log level of this logger as a number between 0 and 5
Explanation of types of doc strings in module.lp
module.lp is a Lua template file of some kind, which explains what the different types of doc strings are.
A list of the different types of doc strings
local sectionorder = { "Deprecated", "Command", "Constant", "Variable", "Function", "Constructor", "Field", "Method" }
local sectiondetails = {
["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",
}