-
Notifications
You must be signed in to change notification settings - Fork 140
/
Copy pathinit.lua
258 lines (223 loc) · 6.91 KB
/
init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
--- === EmmyLua ===
---
--- Thie plugin generates EmmyLua annotations for Hammerspoon and any installed Spoons
--- under ~/.hammerspoon/Spoons/EmmyLua.spoon/annotations.
--- Annotations will only be generated if they don't exist yet or are out of date.
---
--- Note: Load this Spoon before any pathwatchers are defined to avoid unintended behaviour (for example multiple reloads when the annotions are created).
---
--- In order to get auto completion in your editor, you need to have one of the following LSP servers properly configured:
--- * [lua-language-server](https://github.com/sumneko/lua-language-server) (recommended)
--- * [EmmyLua-LanguageServer](https://github.com/EmmyLua/EmmyLua-LanguageServer)
---
--- To start using this annotations library, add the annotations folder to your workspace.
--- for lua-languag-server:
---
--- ```json
--- {
--- "Lua.workspace.library": ["/Users/YOUR_USERNAME/.hammerspoon/Spoons/EmmyLua.spoon/annotations"]
--- }
--- ```
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/EmmyLua.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/EmmyLua.spoon.zip)
local M = {}
M.name = "EmmyLua"
M.version = "1.0"
M.author = "http://github.com/folke"
M.license = "MIT - https://opensource.org/licenses/MIT"
M.logger = hs.logger.new("EmmyLua")
local options = {
annotations = hs.spoons.resourcePath("annotations"),
timestampsFilename = hs.spoons.resourcePath("annotations").."/timestamps.json",
timestamps = {},
timestampsChanged = false,
types = {
bool = "boolean",
boolean = "boolean",
["false"] = "boolean",
["true"] = "boolean",
string = "string",
number = "number",
float = "number",
integer = "number",
app = "hs.application",
hsminwebtable = "hs.httpserver.hsminweb",
notificationobject = "hs.notify",
point = "hs.geometry",
rect = "hs.geometry",
["hs.geometry rect"] = "hs.geometry",
size = "hs.geometry",
},
}
M.spoonPath = hs.spoons.scriptPath()
function M.comment(str, commentStr)
commentStr = commentStr or "--"
return commentStr .. " " .. str:gsub("[\n]", "\n" .. commentStr .. " "):gsub("%s+\n", "\n") .. "\n"
end
function M.parseType(module, str)
if not str then
return
end
str = str:lower()
if options.types[str] then
return options.types[str]
end
local type = str:match("^(hs%.%S*)%s*object")
if type then
return type
end
type = str:match("^list of (hs%.%S*)%s*object")
if type then
return type .. "[]"
end
if module.name:find(str, 1, true) or str == "self" then
return module.name
end
end
function M.trim(str)
str = str:gsub("^%s*", "")
str = str:gsub("%s*$", "")
return str
end
function M.parseArgs(str)
local name, args = str:match("^(.*)%((.*)%)$")
if name then
args = args:gsub("%s*|%s*", "_or_")
args = args:gsub("%s+or%s+", "_or_")
args = args:gsub("[%[%]{}%(%)]", "")
if args:find("...") then
args = args:gsub(",?%s*%.%.%.", "")
args = M.trim(args)
if #args > 0 then
args = args .. ", "
end
args = args .. "..."
end
args = hs.fnutils.split(args, "%s*,%s*")
for a, arg in ipairs(args) do
if arg == "false" then
args[a] = "_false"
elseif arg == "function" then
args[a] = "fn"
elseif arg == "end" then
args[a] = "_end"
end
end
return name, args
end
return str
end
function M.parseDef(module, el)
el.def = el.def or ""
el.def = module.prefix .. el.def
local parts = hs.fnutils.split(el.def, "%s*%-+>%s*")
local name, args = M.parseArgs(parts[1])
local ret = { name = name, args = args, type = M.parseType(module, parts[2]) }
if name:match("%[.*%]$") then
if not ret.type then
ret.type = "table"
end
ret.name = ret.name:sub(1, ret.name:find("%[") - 1)
end
return ret
end
function M.processModule(module)
io.write("--# selene: allow(unused_variable)\n")
io.write("---@diagnostic disable: unused-local\n\n")
if module.name == "hs" then
io.write("--- global variable containing loaded spoons\n")
io.write("spoon = {}\n\n")
end
io.write(M.comment(module.doc))
io.write("---@class " .. module.name .. "\n")
io.write("local M = {}\n")
io.write(module.name .. " = M\n\n")
for _, item in ipairs(module.items) do
local def = M.parseDef(module, item)
-- io.write("-- " .. item.def)
io.write(M.comment(item.doc))
local name = def.name
if def.name:find(module.name, 1, true) == 1 then
name = "M" .. def.name:sub(#module.name + 1)
end
if def.args then
if def.type then
io.write("---@return " .. def.type .. "\n")
end
io.write("function " .. name .. "(" .. table.concat(def.args, ", ") .. ") end\n")
else
if def.type then
io.write("---@type " .. def.type .. "\n")
end
if def.type and (def.type:find("table") or def.type:find("%[%]")) then
io.write(name .. " = {}\n")
else
io.write(name .. " = nil\n")
end
end
io.write("\n")
end
end
function M.create(jsonDocs, prefix)
local mtime = hs.fs.attributes(jsonDocs, "modification")
prefix = prefix or ""
local data = hs.json.read(jsonDocs)
for _, module in ipairs(data) do
if module.type ~= "Module" then
error("Expected a module, but found type=" .. module.type)
end
module.prefix = prefix
module.name = prefix .. module.name
local fname = options.annotations .. "/" .. module.name .. ".lua"
local fmtime = hs.fs.attributes(fname, "modification")
if fmtime == nil or mtime > fmtime then
M.logger.i("creating " .. fname)
local fd = io.open(fname, "w+")
io.output(fd)
M.processModule(module)
io.close(fd)
else
M.logger.i("skipping " .. fname)
end
end
end
function M.createWhenChanged(jsonDocs, prefix)
local mtime = hs.fs.attributes(jsonDocs, "modification")
local timestamp = options.timestamps[jsonDocs]
if(timestamp == nil or mtime ~= timestamp) then
M.logger.i("reading "..jsonDocs)
M.create(jsonDocs, prefix)
options.timestamps[jsonDocs] = mtime
options.timestampsChanged = true
else
M.logger.i("skipping "..jsonDocs)
end
end
function M.readTimestamps()
timestamps = hs.json.read(options.timestampsFilename)
if timestamps then
options.timestamps = timestamps
end
M.logger.d(hs.inspect(options.timestamps))
end
function M.writeTimestamps()
M.logger.d(hs.inspect(options.timestamps))
if options.timestampsChanged then
hs.json.write(options.timestamps, options.timestampsFilename, true, true)
end
end
function M:init()
hs.fs.mkdir(options.annotations)
M.readTimestamps()
-- Load hammerspoon docs
M.createWhenChanged(hs.docstrings_json_file)
-- Load Spoons
for _, spoon in ipairs(hs.spoons.list()) do
local doc = hs.configdir .. "/Spoons/" .. spoon.name .. ".spoon/docs.json"
if hs.fs.attributes(doc, "modification") then
M.createWhenChanged(doc, "spoon.")
end
end
M.writeTimestamps()
end
return M