/til/

2025 0607 Writing docs for Hammerspoon spoons

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:

  1. 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.

  2. 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",
}

Other files with docstring examples

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!).