You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
7.6 KiB
Lua
374 lines
7.6 KiB
Lua
--[=[
|
|
@c ClassName [x base_1 x base_2 ... x base_n]
|
|
@t tag
|
|
@mt methodTag (applies to all class methods)
|
|
@p parameterName type
|
|
@op optionalParameterName type
|
|
@d description+
|
|
]=]
|
|
|
|
--[=[
|
|
@m methodName
|
|
@t tag
|
|
@p parameterName type
|
|
@op optionalParameterName type
|
|
@r return
|
|
@d description+
|
|
]=]
|
|
|
|
--[=[
|
|
@p propertyName type description+
|
|
]=]
|
|
|
|
local fs = require('fs')
|
|
local pathjoin = require('pathjoin')
|
|
|
|
local insert, sort, concat = table.insert, table.sort, table.concat
|
|
local format = string.format
|
|
local pathJoin = pathjoin.pathJoin
|
|
|
|
local function scan(dir)
|
|
for fileName, fileType in fs.scandirSync(dir) do
|
|
local path = pathJoin(dir, fileName)
|
|
if fileType == 'file' then
|
|
coroutine.yield(path)
|
|
else
|
|
scan(path)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function match(s, pattern) -- only useful for one capture
|
|
return assert(s:match(pattern), s)
|
|
end
|
|
|
|
local function gmatch(s, pattern, hash) -- only useful for one capture
|
|
local tbl = {}
|
|
if hash then
|
|
for k in s:gmatch(pattern) do
|
|
tbl[k] = true
|
|
end
|
|
else
|
|
for v in s:gmatch(pattern) do
|
|
insert(tbl, v)
|
|
end
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
local function matchType(s)
|
|
return s:match('^@(%S+)')
|
|
end
|
|
|
|
local function matchComments(s)
|
|
return s:gmatch('--%[=%[%s*(.-)%s*%]=%]')
|
|
end
|
|
|
|
local function matchClassName(s)
|
|
return match(s, '@c (%S+)')
|
|
end
|
|
|
|
local function matchMethodName(s)
|
|
return match(s, '@m (%S+)')
|
|
end
|
|
|
|
local function matchDescription(s)
|
|
return match(s, '@d (.+)'):gsub('%s+', ' ')
|
|
end
|
|
|
|
local function matchParents(s)
|
|
return gmatch(s, 'x (%S+)')
|
|
end
|
|
|
|
local function matchReturns(s)
|
|
return gmatch(s, '@r (%S+)')
|
|
end
|
|
|
|
local function matchTags(s)
|
|
return gmatch(s, '@t (%S+)', true)
|
|
end
|
|
|
|
local function matchMethodTags(s)
|
|
return gmatch(s, '@mt (%S+)', true)
|
|
end
|
|
|
|
local function matchProperty(s)
|
|
local a, b, c = s:match('@p (%S+) (%S+) (.+)')
|
|
return {
|
|
name = assert(a, s),
|
|
type = assert(b, s),
|
|
desc = assert(c, s):gsub('%s+', ' '),
|
|
}
|
|
end
|
|
|
|
local function matchParameters(s)
|
|
local ret = {}
|
|
for optional, paramName, paramType in s:gmatch('@(o?)p (%S+) (%S+)') do
|
|
insert(ret, {paramName, paramType, optional == 'o'})
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local function matchMethod(s)
|
|
return {
|
|
name = matchMethodName(s),
|
|
desc = matchDescription(s),
|
|
parameters = matchParameters(s),
|
|
returns = matchReturns(s),
|
|
tags = matchTags(s),
|
|
}
|
|
end
|
|
|
|
----
|
|
|
|
local docs = {}
|
|
|
|
local function newClass()
|
|
|
|
local class = {
|
|
methods = {},
|
|
statics = {},
|
|
properties = {},
|
|
}
|
|
|
|
local function init(s)
|
|
class.name = matchClassName(s)
|
|
class.parents = matchParents(s)
|
|
class.desc = matchDescription(s)
|
|
class.parameters = matchParameters(s)
|
|
class.tags = matchTags(s)
|
|
class.methodTags = matchMethodTags(s)
|
|
assert(not docs[class.name], 'duplicate class: ' .. class.name)
|
|
docs[class.name] = class
|
|
end
|
|
|
|
return class, init
|
|
|
|
end
|
|
|
|
for f in coroutine.wrap(scan), './libs' do
|
|
|
|
local d = assert(fs.readFileSync(f))
|
|
|
|
local class, initClass = newClass()
|
|
for s in matchComments(d) do
|
|
local t = matchType(s)
|
|
if t == 'c' then
|
|
initClass(s)
|
|
elseif t == 'm' then
|
|
local method = matchMethod(s)
|
|
for k, v in pairs(class.methodTags) do
|
|
method.tags[k] = v
|
|
end
|
|
method.class = class
|
|
insert(method.tags.static and class.statics or class.methods, method)
|
|
elseif t == 'p' then
|
|
insert(class.properties, matchProperty(s))
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
----
|
|
|
|
local output = 'docs'
|
|
|
|
local function link(str)
|
|
if type(str) == 'table' then
|
|
local ret = {}
|
|
for i, v in ipairs(str) do
|
|
ret[i] = link(v)
|
|
end
|
|
return concat(ret, ', ')
|
|
else
|
|
local ret = {}
|
|
for t in str:gmatch('[^/]+') do
|
|
insert(ret, docs[t] and format('[[%s]]', t) or t)
|
|
end
|
|
return concat(ret, '/')
|
|
end
|
|
end
|
|
|
|
local function sorter(a, b)
|
|
return a.name < b.name
|
|
end
|
|
|
|
local function writeHeading(f, heading)
|
|
f:write('## ', heading, '\n\n')
|
|
end
|
|
|
|
local function writeProperties(f, properties)
|
|
sort(properties, sorter)
|
|
f:write('| Name | Type | Description |\n')
|
|
f:write('|-|-|-|\n')
|
|
for _, v in ipairs(properties) do
|
|
f:write('| ', v.name, ' | ', link(v.type), ' | ', v.desc, ' |\n')
|
|
end
|
|
f:write('\n')
|
|
end
|
|
|
|
local function writeParameters(f, parameters)
|
|
f:write('(')
|
|
local optional
|
|
if #parameters > 0 then
|
|
for i, param in ipairs(parameters) do
|
|
f:write(param[1])
|
|
if i < #parameters then
|
|
f:write(', ')
|
|
end
|
|
if param[3] then
|
|
optional = true
|
|
end
|
|
end
|
|
f:write(')\n\n')
|
|
if optional then
|
|
f:write('| Parameter | Type | Optional |\n')
|
|
f:write('|-|-|:-:|\n')
|
|
for _, param in ipairs(parameters) do
|
|
local o = param[3] and '✔' or ''
|
|
f:write('| ', param[1], ' | ', link(param[2]), ' | ', o, ' |\n')
|
|
end
|
|
f:write('\n')
|
|
else
|
|
f:write('| Parameter | Type |\n')
|
|
f:write('|-|-|\n')
|
|
for _, param in ipairs(parameters) do
|
|
f:write('| ', param[1], ' | ', link(param[2]), ' |\n')
|
|
end
|
|
f:write('\n')
|
|
end
|
|
else
|
|
f:write(')\n\n')
|
|
end
|
|
end
|
|
|
|
local methodTags = {}
|
|
|
|
methodTags['http'] = 'This method always makes an HTTP request.'
|
|
methodTags['http?'] = 'This method may make an HTTP request.'
|
|
methodTags['ws'] = 'This method always makes a WebSocket request.'
|
|
methodTags['mem'] = 'This method only operates on data in memory.'
|
|
|
|
local function checkTags(tbl, check)
|
|
for i, v in ipairs(check) do
|
|
if tbl[v] then
|
|
for j, w in ipairs(check) do
|
|
if i ~= j then
|
|
if tbl[w] then
|
|
return error(string.format('mutually exclusive tags encountered: %s and %s', v, w), 1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function writeMethods(f, methods)
|
|
|
|
sort(methods, sorter)
|
|
for _, method in ipairs(methods) do
|
|
|
|
f:write('### ', method.name)
|
|
writeParameters(f, method.parameters)
|
|
f:write(method.desc, '\n\n')
|
|
|
|
local tags = method.tags
|
|
checkTags(tags, {'http', 'http?', 'mem'})
|
|
checkTags(tags, {'ws', 'mem'})
|
|
|
|
for k in pairs(tags) do
|
|
if k ~= 'static' then
|
|
assert(methodTags[k], k)
|
|
f:write('*', methodTags[k], '*\n\n')
|
|
end
|
|
end
|
|
|
|
f:write('**Returns:** ', link(method.returns), '\n\n----\n\n')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not fs.existsSync(output) then
|
|
fs.mkdirSync(output)
|
|
end
|
|
|
|
local function collectParents(parents, k, ret, seen)
|
|
ret = ret or {}
|
|
seen = seen or {}
|
|
for _, parent in ipairs(parents) do
|
|
parent = docs[parent]
|
|
if parent then
|
|
for _, v in ipairs(parent[k]) do
|
|
if not seen[v] then
|
|
seen[v] = true
|
|
insert(ret, v)
|
|
end
|
|
end
|
|
end
|
|
collectParents(parent.parents, k, ret, seen)
|
|
end
|
|
return ret
|
|
end
|
|
|
|
for _, class in pairs(docs) do
|
|
|
|
local f = io.open(pathJoin(output, class.name .. '.md'), 'w')
|
|
|
|
local parents = class.parents
|
|
local parentLinks = link(parents)
|
|
|
|
if next(parents) then
|
|
f:write('#### *extends ', parentLinks, '*\n\n')
|
|
end
|
|
|
|
f:write(class.desc, '\n\n')
|
|
|
|
checkTags(class.tags, {'ui', 'abc'})
|
|
if class.tags.ui then
|
|
writeHeading(f, 'Constructor')
|
|
f:write('### ', class.name)
|
|
writeParameters(f, class.parameters)
|
|
elseif class.tags.abc then
|
|
f:write('*This is an abstract base class. Direct instances should never exist.*\n\n')
|
|
else
|
|
f:write('*Instances of this class should not be constructed by users.*\n\n')
|
|
end
|
|
|
|
local properties = collectParents(parents, 'properties')
|
|
if next(properties) then
|
|
writeHeading(f, 'Properties Inherited From ' .. parentLinks)
|
|
writeProperties(f, properties)
|
|
end
|
|
|
|
if next(class.properties) then
|
|
writeHeading(f, 'Properties')
|
|
writeProperties(f, class.properties)
|
|
end
|
|
|
|
local statics = collectParents(parents, 'statics')
|
|
if next(statics) then
|
|
writeHeading(f, 'Static Methods Inherited From ' .. parentLinks)
|
|
writeMethods(f, statics)
|
|
end
|
|
|
|
local methods = collectParents(parents, 'methods')
|
|
if next(methods) then
|
|
writeHeading(f, 'Methods Inherited From ' .. parentLinks)
|
|
writeMethods(f, methods)
|
|
end
|
|
|
|
if next(class.statics) then
|
|
writeHeading(f, 'Static Methods')
|
|
writeMethods(f, class.statics)
|
|
end
|
|
|
|
if next(class.methods) then
|
|
writeHeading(f, 'Methods')
|
|
writeMethods(f, class.methods)
|
|
end
|
|
|
|
f:close()
|
|
|
|
end
|