-
Notifications
You must be signed in to change notification settings - Fork 191
/
Copy pathdocgen.lua
377 lines (330 loc) · 9.06 KB
/
docgen.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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
local doctypes = require('docs')
local doc = doctypes[1]
local types = doctypes[2]
--- @param str string
--- @return string
local function dedent(str)
local prefix = 99
str = str:gsub('^\n', ''):gsub('%s+$', '')
for line in str:gmatch('[^\n]+') do
local s, e = line:find('^%s*')
local amount = (e - s) + 1
if amount < prefix then
prefix = amount
end
end
local result = {} --- @type string[]
for line in str:gmatch('([^\n]*)\n?') do
result[#result + 1] = line:sub(prefix + 1)
end
local ret = table.concat(result, '\n')
ret = ret:gsub('\n+$', '')
return ret
end
--- @param lvl integer
--- @param str string
--- @return string
local function heading(lvl, str)
return string.rep('#', lvl) .. ' ' .. str:gsub(' %- ', ' — ')
end
--- @param ty Doc.Type
--- @return boolean
local function isoptional(ty)
if type(ty) == 'string' then
return ty == 'nil'
end
if ty.kind == 'union' then
for _, uty in ipairs(ty[1]) do
if isoptional(uty) then
return true
end
end
end
return false
end
--- @param method Doc.Method
--- @return string
local function sig(method)
local args = {} --- @type string[]
for _, param in ipairs(method.params or {}) do
local nm = param.name
if isoptional(param.type) then
nm = '[' .. nm .. ']'
end
args[#args + 1] = nm
end
return ('`uv.%s(%s)`'):format(method.name, table.concat(args, ', '))
end
local function pad(lvl)
return string.rep(' ', lvl * 2)
end
local normty
--- @param ty Doc.Type.Fun
--- @param lvl? integer
--- @param desc? string
--- @return string
local function normtyfun(ty, lvl, desc)
local r = {} --- @type string[]
r[#r + 1] = '`callable`'
if desc then
r[#r] = r[#r] .. ' ' .. desc
end
for _, arg in ipairs(ty.args) do
local arg_nm, arg_ty, arg_desc = arg[1], arg[2], arg[3]
if arg_nm == 'err' then
arg_ty = 'nil|string'
end
r[#r + 1] = ('%s- `%s`: %s'):format(pad(lvl), arg_nm, normty(arg_ty, lvl + 1))
if arg_desc then
r[#r] = r[#r] .. ' ' .. arg_desc
end
end
return table.concat(r, '\n')
end
--- @param ty Doc.Type.Table
--- @param lvl? integer
--- @param desc? string
--- @return string
local function normtytbl(ty, lvl, desc)
local r = {} --- @type string[]
r[#r + 1] = '`table`'
if desc then
r[#r] = r[#r] .. ' ' .. desc
end
for _, field in ipairs(ty.fields) do
local name, aty, default, adesc = field[1], field[2], field[3], field[4]
r[#r + 1] = ('%s- `%s`: %s'):format(pad(lvl), name, normty(aty, lvl + 1, adesc))
if default then
r[#r] = ('%s (default: `%s`)'):format(r[#r], default)
end
end
return table.concat(r, '\n')
end
--- @param ty string
--- @param _lvl? integer
--- @param desc? string
--- @return string
local function normtystr(ty, _lvl, desc)
do -- TODO(lewis6991): remove
if ty == 'uv_handle_t' or ty == 'uv_req_t' or ty == 'uv_stream_t' then
return '`userdata` for sub-type of `' .. ty .. '`'
end
ty = ty:gsub('uv_[a-z_]+', '%0 userdata')
ty = ty:gsub('%|', '` or `')
end
local desc_str = desc and ' ' .. desc or ''
return '`' .. ty .. '`' .. desc_str
end
--- @param ty Doc.Type.Dict
--- @param lvl? integer
--- @param desc? string
--- @return string
local function normtydict(ty, lvl, desc)
local r = {} --- @type string[]
r[#r + 1] = '`table`'
if desc then
r[#r] = r[#r] .. ' ' .. desc
end
-- TODO(lewis6991): remove
if ty.key == 'integer' then
ty.key = '1, 2, 3, ..., n'
end
r[#r + 1] = ('%s- `[%s]`: %s'):format(pad(lvl), ty.key, normty(ty.value, lvl + 1))
return table.concat(r, '\n')
end
--- @param ty Doc.Type.Union
--- @param lvl? integer
--- @param desc? string
--- @return string
local function normtyunion(ty, lvl, desc)
local tys = ty[1]
local r = {} --- @type string[]
local main_ty --- @type integer?
for i, uty in ipairs(tys) do
if normty(uty):match('\n') then
main_ty = i
end
r[#r + 1] = normty(uty, lvl, i == #tys and desc or nil)
end
if main_ty and #tys > 1 then
local others = {} --- @type string[]
for i, oty in ipairs(r) do
if i ~= main_ty then
others[#others + 1] = oty
end
end
local other_ty_str = table.concat(others, ' or ')
-- local other_ty_str = table.concat(others, '|')
return (r[main_ty]:gsub('\n', ' or ' .. other_ty_str .. '\n', 1))
end
return table.concat(r, ' or ')
-- return table.concat(r, '|')
end
local types_noinline = {
threadargs = true,
buffer = true,
}
--- @param ty Doc.Type
--- @param lvl? integer
--- @param desc? string
--- @return string
function normty(ty, lvl, desc)
-- resolve type
if types[ty] and not types_noinline[ty] and not types[ty].extends then
ty = types[ty]
end
lvl = lvl or 0
local f --- @type fun(ty: Doc.Type, lvl: integer, desc: string): string
if type(ty) == 'string' then
f = normtystr
elseif ty.kind == 'function' then
f = normtyfun
elseif ty.kind == 'table' then
f = normtytbl
elseif ty.kind == 'dict' then
f = normtydict
elseif ty.kind == 'union' then
f = normtyunion
end
return f(ty, lvl, desc)
end
--- @param out file*
--- @param param Doc.Method.Param
local function write_param(out, param)
out:write(('- `%s`:'):format(param.name))
local ty = param.type
if ty then
out:write(' ', normty(ty, 1, param.desc))
elseif param.desc then
out:write(' ', param.desc)
end
if param.default then
out:write((' (default: `%s`)'):format(param.default))
end
out:write('\n')
end
--- @param ty Doc.Type
local function remove_nil(ty)
if type(ty) == 'table' and ty.kind == 'union' then
for i, uty in ipairs(ty[1]) do
if uty == 'nil' then
table.remove(ty[1], i)
break
else
remove_nil(uty)
end
end
end
end
--- @param out file*
--- @param x string|Doc.Method.Return[]
--- @param variant? string
local function write_return(out, x, variant)
local variant_str = variant and (' (%s version)'):format(variant) or ''
if type(x) == 'string' then
out:write(('**Returns%s:** %s\n'):format(variant_str, normty(x)))
elseif type(x) == 'table' then
if x[2] and x[2][2] == 'err' and x[3] and x[3][2] == 'err_name' then
local sty = x[1][1]
remove_nil(sty)
local rty = normty(sty, nil, 'or `fail`')
out:write(('**Returns%s:** %s\n\n'):format(variant_str, rty))
return
else
local tys = {} --- @type string[]
for _, ret in ipairs(x) do
tys[#tys + 1] = normty(ret[1])
end
out:write(('**Returns%s:** %s\n'):format(variant_str, table.concat(tys, ', ')))
end
else
out:write('**Returns:** Nothing.\n')
end
out:write('\n')
end
--- @param out file*
--- @param method Doc.Method
--- @param lvl integer
local function write_method(out, method, lvl)
out:write(heading(lvl, sig(method)), '\n\n')
if method.method_form then
out:write(('> method form `%s`\n\n'):format(method.method_form))
end
if method.deprecated then
out:write('**Deprecated:** ', dedent(method.deprecated), '\n\n')
return
end
if method.params then
out:write('**Parameters:**\n')
for _, param in ipairs(method.params) do
write_param(out, param)
end
out:write('\n')
end
if method.desc then
out:write(dedent(method.desc), '\n\n')
end
if method.returns_doc then
out:write('**Returns:**')
local r = dedent(method.returns_doc)
out:write(r:sub(1, 1) == '-' and '\n' or ' ')
out:write(r, '\n\n')
elseif method.returns_sync and method.returns_async then
write_return(out, method.returns_sync, 'sync')
write_return(out, method.returns_async, 'async')
else
write_return(out, method.returns)
end
if method.example then
out:write(dedent(method.example), '\n\n')
end
if method.see then
out:write(('See [%s][].\n\n'):format(method.see))
end
for _, note in ipairs(method.notes or {}) do
local notes = dedent(note)
out:write('**Note**:')
out:write(notes:sub(1, 3) == '1. ' and '\n' or ' ')
out:write(notes, '\n\n')
end
for _, warn in ipairs(method.warnings or {}) do
out:write(('**Warning**: %s\n\n'):format(dedent(warn)))
end
if method.since then
out:write(('**Note**: New in libuv version %s.\n\n'):format(method.since))
end
end
--- @param out file*
--- @param section Doc
--- @param lvl integer
local function write_section(out, section, lvl)
local title = section.title
if title then
out:write(heading(lvl, title))
out:write('\n\n')
end
local id = section.id
if id then
local tag = assert(title):match('^`[a-z_]+`') or title
out:write(('[%s]: #%s\n\n'):format(tag, id))
end
if section.desc then
out:write(dedent(section.desc), '\n\n')
end
if section.constants then
for _, constant in ipairs(section.constants) do
out:write(('- `%s`: "%s"\n'):format(constant[1], constant[2]))
end
out:write('\n')
end
for _, method in ipairs(section.methods or {}) do
write_method(out, method, lvl + 1)
end
for _, subsection in ipairs(section.sections or {}) do
write_section(out, subsection, lvl + 1)
end
end
local out = assert(io.open('docs.md', 'w'))
for _, section in ipairs(doc) do
write_section(out, section, 1)
end