310 lines
12 KiB
Lua
310 lines
12 KiB
Lua
|
|
--- === hs.doc ===
|
|
---
|
|
--- Create documentation objects for interactive help within Hammerspoon
|
|
---
|
|
--- The documentation object created is a table with tostring metamethods allowing access to a specific functions documentation by appending the path to the method or function to the object created.
|
|
---
|
|
--- From the Hammerspoon console:
|
|
---
|
|
--- doc = require("hs.doc")
|
|
--- doc.hs.application
|
|
---
|
|
--- Results in:
|
|
---
|
|
--- Manipulate running applications
|
|
---
|
|
--- [submodules]
|
|
--- hs.application.watcher
|
|
---
|
|
--- [subitems]
|
|
--- hs.application:activate([allWindows]) -> bool
|
|
--- hs.application:allWindows() -> window[]
|
|
--- ...
|
|
--- hs.application:visibleWindows() -> win[]
|
|
---
|
|
--- By default, the internal core documentation and portions of the Lua 5.3 manual, located at http://www.lua.org/manual/5.3/manual.html, are already registered for inclusion within this documentation object, but you can register additional documentation from 3rd party modules with `hs.registerJSONFile(...)`.
|
|
|
|
local USERDATA_TAG = "hs.doc"
|
|
local module = require("hs.libdoc")
|
|
local moduleMT = getmetatable(module)
|
|
|
|
-- autoloaded by __index -- see end of file
|
|
local submodules = {
|
|
markdown = USERDATA_TAG .. ".markdown",
|
|
hsdocs = USERDATA_TAG .. ".hsdocs",
|
|
builder = USERDATA_TAG .. ".builder",
|
|
}
|
|
|
|
local fnutils = require("hs.fnutils")
|
|
local watchable = require("hs.watchable")
|
|
local fs = require "hs.fs"
|
|
|
|
-- local log = require("hs.logger").new(USERDATA_TAG, require"hs.settings".get(USERDATA_TAG .. ".logLevel") or "warning")
|
|
|
|
-- private variables and methods -----------------------------------------
|
|
|
|
local changeCount = watchable.new("hs.doc")
|
|
changeCount.changeCount = 0
|
|
|
|
local triggerChangeCount = function()
|
|
changeCount.changeCount = changeCount.changeCount + 1
|
|
end
|
|
|
|
-- so we can trigger this from the C side
|
|
moduleMT._registerTriggerFunction(triggerChangeCount)
|
|
|
|
-- forward declarations for hsdocs
|
|
local _jsonForSpoons = nil
|
|
local _jsonForModules = nil
|
|
|
|
module._changeCountWatcher = watchable.watch("hs.doc", "changeCount", function(w, p, k, o, n) -- luacheck: ignore
|
|
_jsonForModules = nil
|
|
_jsonForSpoons = nil
|
|
end)
|
|
|
|
-- forward declaration of things we're going to wrap
|
|
local _help = module.help
|
|
local _registeredFilesFunction = module.registeredFiles
|
|
|
|
local helperMT
|
|
helperMT = {
|
|
__index = function(self, key)
|
|
local parent = rawget(self, "_parent") or ""
|
|
if parent ~= "" then parent = parent .. "." end
|
|
parent = parent .. self._key
|
|
local children = moduleMT._children(parent)
|
|
if fnutils.contains(children, key) then
|
|
return setmetatable({ _key = key, _parent = parent }, helperMT)
|
|
end
|
|
end,
|
|
__tostring = function(self)
|
|
local entry = rawget(self, "_parent")
|
|
if entry then entry = entry .. "." else entry = "" end
|
|
entry = entry .. self._key
|
|
return _help(entry)
|
|
end,
|
|
__pairs = function(self)
|
|
local parent = rawget(self, "_parent") or ""
|
|
if parent ~= "" then parent = parent .. "." end
|
|
parent = parent .. self._key
|
|
local children = {}
|
|
for i, v in ipairs(moduleMT._children(parent)) do children[v] = i end
|
|
return function(_, k)
|
|
local v
|
|
k, v = next(children, k)
|
|
return k, v
|
|
end, self, nil
|
|
end,
|
|
__len = function(self)
|
|
local parent = rawget(self, "_parent") or ""
|
|
if parent ~= "" then parent = parent .. "." end
|
|
parent = parent .. self._key
|
|
return #moduleMT._children(parent)
|
|
end,
|
|
}
|
|
|
|
-- Public interface ------------------------------------------------------
|
|
|
|
--- hs.doc.registeredFiles() -> table
|
|
--- Function
|
|
--- Returns the list of registered JSON files.
|
|
---
|
|
--- Parameters:
|
|
--- * None
|
|
---
|
|
--- Returns:
|
|
--- * a table containing the list of registered JSON files
|
|
---
|
|
--- Notes:
|
|
--- * The table returned by this function has a metatable including a __tostring method which allows you to see the list of registered files by simply typing `hs.doc.registeredFiles()` in the Hammerspoon Console.
|
|
---
|
|
--- * By default, the internal core documentation and portions of the Lua 5.3 manual, located at http://www.lua.org/manual/5.3/manual.html, are already registered for inclusion within this documentation object.
|
|
---
|
|
--- * You can unregister these defaults if you wish to start with a clean slate with the following commands:
|
|
--- * `hs.doc.unregisterJSONFile(hs.docstrings_json_file)` -- to unregister the Hammerspoon API docs
|
|
--- * `hs.doc.unregisterJSONFile((hs.docstrings_json_file:gsub("/docs.json$","/lua.json")))` -- to unregister the Lua 5.3 Documentation.
|
|
module.registeredFiles = function(...)
|
|
return setmetatable(_registeredFilesFunction(...), {
|
|
__tostring = function(self)
|
|
local result = ""
|
|
for _,v in pairs(self) do
|
|
result = result..v.."\n"
|
|
end
|
|
return result
|
|
end,
|
|
})
|
|
end
|
|
|
|
--- hs.doc.help(identifier)
|
|
--- Function
|
|
--- Prints the documentation for some part of Hammerspoon's API and Lua 5.3. This function has also been aliased as `hs.help` and `help` as a shorthand for use within the Hammerspoon console.
|
|
---
|
|
--- Parameters:
|
|
--- * identifier - A string containing the signature of some part of Hammerspoon's API (e.g. `"hs.reload"`)
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
---
|
|
--- Notes:
|
|
--- * This function is mainly for runtime API help while using Hammerspoon's Console
|
|
---
|
|
--- * Documentation files registered with [hs.doc.registerJSONFile](#registerJSONFile) or [hs.doc.preloadSpoonDocs](#preloadSpoonDocs) that have not yet been actually loaded will be loaded when this command is invoked in any of the forms described below.
|
|
---
|
|
--- * You can also access the results of this function by the following methods from the console:
|
|
--- * help("prefix.path") -- quotes are required, e.g. `help("hs.reload")`
|
|
--- * help.prefix.path -- no quotes are required, e.g. `help.hs.reload`
|
|
--- * `prefix` can be one of the following:
|
|
--- * `hs` - provides documentation for Hammerspoon's builtin commands and modules
|
|
--- * `spoon` - provides documentation for the Spoons installed on your system
|
|
--- * `lua` - provides documentation for the version of lua Hammerspoon is using, currently 5.3
|
|
--- * `lua._man` - provides the table of contents for the Lua 5.3 manual. You can pull up a specific section of the lua manual by including the chapter (and subsection) like this: `lua._man._3_4_8`.
|
|
--- * `lua._C` - provides documentation specifically about the Lua C API for use when developing modules which require external libraries.
|
|
--- * `path` is one or more components, separated by a period specifying the module, submodule, function, or moethod you wish to view documentation for.
|
|
module.help = function(...)
|
|
local answer = _help(...)
|
|
return setmetatable({}, {
|
|
__tostring = function(self) return answer end, -- luacheck: ignore
|
|
})
|
|
end
|
|
|
|
--- hs.doc.locateJSONFile(module) -> path | false, message
|
|
--- Function
|
|
--- Locates the JSON file corresponding to the specified third-party module or Spoon by searching package.path and package.cpath.
|
|
---
|
|
--- Parameters:
|
|
--- * module - the name of the module to locate a JSON file for
|
|
---
|
|
--- Returns:
|
|
--- * the path to the JSON file, or `false, error` if unable to locate a corresponding JSON file.
|
|
---
|
|
--- Notes:
|
|
--- * The JSON should be named 'docs.json' and located in the same directory as the `lua` or `so` file which is used when the module is loaded via `require`.
|
|
---
|
|
--- * The documentation for core modules is stored in the JSON file specified by the `hs.docstrings_json_file` variable; this function is intended for use in locating the documentation file for third party modules and Spoons.
|
|
module.locateJSONFile = function(moduleName)
|
|
local asLua = package.searchpath(moduleName, package.path)
|
|
local asC = package.searchpath(moduleName, package.cpath)
|
|
|
|
if asLua then
|
|
local pathPart = asLua:match("^(.*/).+%.lua$")
|
|
if pathPart then
|
|
if fs.attributes(pathPart.."docs.json") then
|
|
return pathPart.."docs.json"
|
|
else
|
|
return false, "No JSON file for "..moduleName.." found"
|
|
end
|
|
else
|
|
return false, "Unable to parse package.path for "..moduleName
|
|
end
|
|
elseif asC then
|
|
local pathPart = asC:match("^(.*/).+%.so$")
|
|
if pathPart then
|
|
if fs.attributes(pathPart.."docs.json") then
|
|
return pathPart.."docs.json"
|
|
else
|
|
return false, "No JSON file for "..moduleName.." found"
|
|
end
|
|
else
|
|
return false, "Unable to parse package.cpath for "..moduleName
|
|
end
|
|
else
|
|
return false, "Unable to locate module path for "..moduleName
|
|
end
|
|
end
|
|
|
|
--- hs.doc.preloadSpoonDocs()
|
|
--- Function
|
|
--- Locates all installed Spoon documentation files and and marks them for loading the next time the [hs.doc.help](#help) function is invoked.
|
|
---
|
|
--- Parameters:
|
|
--- * None
|
|
---
|
|
--- Returns:
|
|
--- * None
|
|
module.preloadSpoonDocs = function()
|
|
local spoonPaths, installedSpoons = {}, {}
|
|
for path in package.path:gmatch("([^;]+Spoons/%?%.spoon/init%.lua)") do
|
|
table.insert(spoonPaths, path)
|
|
end
|
|
for _, v in ipairs(spoonPaths) do
|
|
local dirPath = v:match("^(.+)/%?%.spoon/init%.lua$")
|
|
if dirPath and (fs.attributes(dirPath) or {}).mode == "directory" then
|
|
for file in fs.dir(dirPath) do
|
|
local name = file:match("^(.+)%.spoon$")
|
|
local spoonInit = name and package.searchpath(name, table.concat(spoonPaths, ";"))
|
|
if name and spoonInit then
|
|
local path = spoonInit:match("^(.+)/init%.lua$")
|
|
local docPath = path .. "/docs.json"
|
|
local hasDocs = fs.attributes(docPath) and true or false
|
|
if hasDocs then
|
|
module.registerJSONFile(docPath, true)
|
|
table.insert(installedSpoons, docPath)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Return Module Object --------------------------------------------------
|
|
|
|
module.registerJSONFile(hs.docstrings_json_file)
|
|
module.registerJSONFile((hs.docstrings_json_file:gsub("/docs.json$","/lua.json")))
|
|
|
|
-- we hide some debugging stuff in the metatable but we want to modify it here, and its considered bad style
|
|
-- to do so while it's attached to something, so...
|
|
|
|
local _mt = getmetatable(module) or {} -- in our case, it's not empty, but I cut and paste a lot
|
|
setmetatable(module, nil)
|
|
_mt.__call = function(_, ...) return module.help(...) end
|
|
_mt.__tostring = function() return _help() end
|
|
_mt.__index = function(self, key)
|
|
if submodules[key] then
|
|
self[key] = require(submodules[key])
|
|
return self[key]
|
|
end
|
|
|
|
_mt._loadRegisteredFiles() -- we have to assume they're accessing this for help or hsdocs, so load files
|
|
|
|
-- massage the result for hsdocs, which we should really rewrite at some point
|
|
if key == "_jsonForSpoons" or key == "_jsonForModules" then
|
|
if not _jsonForSpoons then
|
|
_jsonForSpoons = {}
|
|
for _, path in ipairs(module.registeredFiles()) do
|
|
local file = _mt._registeredFilesObject()[path]
|
|
if file.spoon then
|
|
for _, v in ipairs(file.json) do
|
|
if not (v.name:match("^lua$") or v.name:match("^lua[%.:]")) then
|
|
table.insert(_jsonForSpoons, v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(_jsonForSpoons, function(a,b) return a.name:lower() < b.name:lower() end)
|
|
end
|
|
if not _jsonForModules then
|
|
_jsonForModules = {}
|
|
for _, path in ipairs(module.registeredFiles()) do
|
|
local file = _mt._registeredFilesObject()[path]
|
|
if not file.spoon then
|
|
for _, v in ipairs(file.json) do
|
|
if not (v.name:match("^lua$") or v.name:match("^lua[%.:]")) then
|
|
table.insert(_jsonForModules, v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(_jsonForModules, function(a,b) return a.name:lower() < b.name:lower() end)
|
|
end
|
|
return (key == "_jsonForModules") and _jsonForModules or _jsonForSpoons
|
|
end
|
|
local children = _mt._children()
|
|
if fnutils.contains(children, key) then
|
|
return setmetatable({ _key = key }, helperMT)
|
|
end
|
|
return rawget(self, key)
|
|
end
|
|
|
|
return setmetatable(module, _mt)
|