2
0

main.lua 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. local serpent = require 'serpent'
  2. -- Helpers
  3. local function copy(t)
  4. if type(t) ~= 'table' then return t end
  5. local result = {}
  6. for k, v in pairs(t) do
  7. result[k] = copy(v)
  8. end
  9. return result
  10. end
  11. local function unindent(code)
  12. local indent = code:match('^(% +)')
  13. if indent then
  14. return code:gsub('\n' .. indent, '\n'):gsub('^' .. indent, ''):gsub('%s*$', '')
  15. else
  16. return code
  17. end
  18. end
  19. local function unwrap(str)
  20. if not str then return str end
  21. str = unindent(str)
  22. return str:gsub('([^\n])\n(%S)', function(a, b)
  23. if b == '-' or b == '>' then
  24. return a .. '\n' .. b
  25. else
  26. return a .. ' ' .. b
  27. end
  28. end)
  29. :gsub('^%s+', '')
  30. :gsub('%s+$', '')
  31. end
  32. local function pluralify(t, key)
  33. t[key .. 's'] = t[key .. 's'] or (t[key] and { t[key] } or nil)
  34. t[key] = nil
  35. return t[key .. 's']
  36. end
  37. local lookup = {}
  38. local function track(obj)
  39. lookup[obj.key] = obj
  40. end
  41. local function warnIf(cond, s, ...)
  42. if cond then print(string.format(s, ...)) end
  43. end
  44. -- Processors
  45. local function processExample(example)
  46. if type(example) == 'string' then
  47. return {
  48. code = unindent(example)
  49. }
  50. else
  51. example.description = unwrap(example.description)
  52. example.code = unindent(example.code)
  53. end
  54. return example
  55. end
  56. local function processEnum(path, parent)
  57. local enum = require(path)
  58. enum.name = path:match('[^/]+$')
  59. enum.key = enum.name
  60. enum.module = parent.key
  61. enum.description = unwrap(enum.description)
  62. enum.notes = unwrap(enum.notes)
  63. for _, value in ipairs(enum.values) do
  64. value.description = unwrap(value.description)
  65. end
  66. track(enum)
  67. return enum
  68. end
  69. local function processFunction(path, parent)
  70. local fn = require(path)
  71. fn.name = path:match('[^/]+$')
  72. fn.key = parent.name:match('^[A-Z]') and (parent.key .. ':' .. fn.name) or (path:gsub('/', '.'):gsub('callbacks%.', ''))
  73. fn.description = unwrap(fn.description)
  74. fn.module = parent.module or parent.key
  75. fn.notes = unwrap(fn.notes)
  76. fn.examples = pluralify(fn, 'example')
  77. for k, example in ipairs(fn.examples or {}) do
  78. fn.examples[k] = processExample(example)
  79. end
  80. if not fn.variants then
  81. fn.variants = {
  82. {
  83. arguments = fn.arguments,
  84. returns = fn.returns
  85. }
  86. }
  87. else
  88. assert(fn.arguments, string.format('Function %q with variants does not have arguments list', fn.key))
  89. for name, arg in pairs(fn.arguments) do
  90. arg.name = name
  91. end
  92. assert(fn.returns, string.format('Function %q with variants does not have returns list', fn.key))
  93. for name, ret in pairs(fn.returns) do
  94. ret.name = name
  95. end
  96. for _, variant in ipairs(fn.variants) do
  97. for i, name in ipairs(variant.arguments) do
  98. assert(fn.arguments[name], string.format('Function %q variant argument %q does not exist', fn.key, name))
  99. variant.arguments[i] = copy(fn.arguments[name])
  100. end
  101. for i, name in ipairs(variant.returns) do
  102. assert(fn.returns[name], string.format('Function %q variant return %q does not exist', fn.key, name))
  103. variant.returns[i] = copy(fn.returns[name])
  104. end
  105. end
  106. end
  107. for _, variant in ipairs(fn.variants) do
  108. local function processTable(t)
  109. if not t then return end
  110. for _, field in ipairs(t) do
  111. field.description = unwrap(field.description)
  112. processTable(field.table)
  113. end
  114. t.description = unwrap(t.description)
  115. end
  116. variant.description = unwrap(variant.description)
  117. assert(variant.arguments, string.format('Variant for %q is missing arguments', fn.key))
  118. for _, arg in ipairs(variant.arguments) do
  119. arg.description = unwrap(arg.description)
  120. processTable(arg.table)
  121. end
  122. assert(variant.returns, string.format('Variant for %q is missing returns', fn.key))
  123. for _, ret in ipairs(variant.returns) do
  124. ret.description = unwrap(ret.description)
  125. processTable(ret.table)
  126. end
  127. end
  128. fn.arguments = nil
  129. fn.returns = nil
  130. track(fn)
  131. return fn
  132. end
  133. local function processObject(path, parent)
  134. local object = require(path .. '.init')
  135. object.key = path:match('[^/]+$')
  136. object.name = object.key
  137. object.description = unwrap(object.description)
  138. object.summary = object.summary or object.description
  139. object.module = parent.key
  140. object.methods = {}
  141. object.constructors = pluralify(object, 'constructor')
  142. object.notes = unwrap(object.notes)
  143. object.examples = pluralify(object, 'example')
  144. if object.sections then
  145. for _, section in ipairs(object.sections) do
  146. section.description = unwrap(section.description)
  147. end
  148. end
  149. for k, example in ipairs(object.examples or {}) do
  150. object.examples[k] = processExample(example)
  151. end
  152. for _, file in ipairs(lovr.filesystem.getDirectoryItems(path)) do
  153. if file ~= 'init.lua' then
  154. table.insert(object.methods, processFunction(path .. '/' .. file:gsub('%..+$', ''), object))
  155. end
  156. end
  157. table.sort(object.methods, function(a, b) return a.key < b.key end)
  158. track(object)
  159. return object
  160. end
  161. local function processModule(path)
  162. local module = require(path .. '.init') -- So we avoid requiring the module itself
  163. module.key = module.external and path:match('[^/]+$') or path:gsub('/', '.')
  164. module.name = module.external and module.key or module.key:match('[^%.]+$')
  165. module.description = unwrap(module.description)
  166. module.functions = {}
  167. module.objects = {}
  168. module.enums = {}
  169. module.notes = unwrap(module.notes)
  170. if module.sections then
  171. for _, section in ipairs(module.sections) do
  172. section.description = unwrap(section.description)
  173. end
  174. end
  175. module.examples = pluralify(module, 'example')
  176. for k, example in ipairs(module.examples or {}) do
  177. module.examples[k] = processExample(example)
  178. end
  179. for _, file in ipairs(lovr.filesystem.getDirectoryItems(path)) do
  180. local childPath = path .. '/' .. file
  181. local childModule = childPath:gsub('%..+$', '')
  182. local isFile = lovr.filesystem.isFile(childPath)
  183. local capitalized = file:match('^[A-Z]')
  184. if file ~= 'init.lua' and not capitalized and isFile then
  185. table.insert(module.functions, processFunction(childModule, module))
  186. elseif capitalized and not isFile then
  187. table.insert(module.objects, processObject(childModule, module))
  188. elseif capitalized and isFile then
  189. table.insert(module.enums, processEnum(childModule, module))
  190. end
  191. end
  192. table.sort(module.functions, function(a, b) return a.key < b.key end)
  193. table.sort(module.objects, function(a, b) return a.key < b.key end)
  194. table.sort(module.enums, function(a, b) return a.key < b.key end)
  195. track(module)
  196. return module
  197. end
  198. -- Validation
  199. local function validateRelated(item)
  200. for _, key in ipairs(item.related or {}) do
  201. warnIf(not lookup[key], 'Related item for %s not found: %s', item.key, key)
  202. end
  203. end
  204. local function validateEnum(enum)
  205. validateRelated(enum)
  206. end
  207. local function validateObject(object)
  208. for _, constructor in ipairs(object.constructors or {}) do
  209. warnIf(not lookup[constructor], 'Constructor for %s not found: %s', object.key, constructor)
  210. end
  211. validateRelated(object)
  212. end
  213. local function validateFunction(fn)
  214. if fn.tag then
  215. local found = false
  216. for _, section in ipairs(lookup[fn.module].sections or {}) do
  217. if section.tag == fn.tag then found = true break end
  218. end
  219. warnIf(not found, 'Unknown tag %s for %s', fn.tag, fn.key)
  220. end
  221. for _, variant in ipairs(fn.variants) do
  222. for _, arg in ipairs(variant.arguments) do
  223. warnIf(not arg or not arg.name, 'Invalid argument for variant of %s', fn.key)
  224. end
  225. for _, ret in ipairs(variant.returns) do
  226. warnIf(not ret or not ret.name, 'Invalid return for variant of %s', fn.key)
  227. end
  228. end
  229. validateRelated(fn)
  230. end
  231. local function validateModule(module)
  232. for _, object in ipairs(module.objects) do
  233. validateObject(object)
  234. end
  235. for _, fn in ipairs(module.functions) do
  236. validateFunction(fn)
  237. end
  238. for _, fn in ipairs(module.enums) do
  239. validateEnum(fn)
  240. end
  241. end
  242. function lovr.load()
  243. local api = {
  244. modules = {},
  245. callbacks = {}
  246. }
  247. -- Modules
  248. table.insert(api.modules, processModule('lovr'))
  249. for _, file in ipairs(lovr.filesystem.getDirectoryItems('lovr')) do
  250. local path = 'lovr/' .. file
  251. if file ~= 'callbacks' and file:match('^[a-z]') and lovr.filesystem.isDirectory(path) then
  252. table.insert(api.modules, processModule(path))
  253. end
  254. end
  255. -- Callbacks
  256. local callbacks = 'lovr/callbacks'
  257. for _, file in ipairs(lovr.filesystem.getDirectoryItems(callbacks)) do
  258. table.insert(api.callbacks, processFunction(callbacks .. '/' .. file:gsub('%.lua', ''), api.modules[1]))
  259. end
  260. -- Validate
  261. for _, module in ipairs(api.modules) do
  262. validateModule(module)
  263. end
  264. -- Sort
  265. table.sort(api.modules, function(a, b) return a.key < b.key end)
  266. table.sort(api.callbacks, function(a, b) return a.key < b.key end)
  267. -- Serialize
  268. local file = io.open(lovr.filesystem.getSource() .. '/init.lua', 'w')
  269. assert(file, 'Could not open init.lua for writing')
  270. local keyPriority = {
  271. name = 1,
  272. tag = 2,
  273. summary = 3,
  274. type = 4,
  275. description = 5,
  276. key = 6,
  277. module = 7,
  278. arguments = 8,
  279. returns = 9
  280. }
  281. local function sort(keys, t)
  282. table.sort(keys, function(a, b) return (keyPriority[a] or 1000) < (keyPriority[b] or 1000) end)
  283. end
  284. local contents = 'return ' .. serpent.block(api, { comment = false, sortkeys = sort })
  285. file:write(contents)
  286. file:close()
  287. -- Bye
  288. lovr.event.quit()
  289. end