main.lua 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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. for name, arg in pairs(fn.arguments) do
  89. arg.name = name
  90. end
  91. for name, ret in pairs(fn.returns) do
  92. ret.name = name
  93. end
  94. for _, variant in ipairs(fn.variants) do
  95. for i, name in ipairs(variant.arguments) do
  96. variant.arguments[i] = copy(fn.arguments[name])
  97. end
  98. for i, name in ipairs(variant.returns) do
  99. variant.returns[i] = copy(fn.returns[name])
  100. end
  101. end
  102. end
  103. for _, variant in ipairs(fn.variants) do
  104. local function processTable(t)
  105. if not t then return end
  106. for _, field in ipairs(t) do
  107. field.description = unwrap(field.description)
  108. processTable(field.table)
  109. end
  110. t.description = unwrap(t.description)
  111. end
  112. variant.description = unwrap(variant.description)
  113. for _, arg in ipairs(variant.arguments) do
  114. arg.description = unwrap(arg.description)
  115. processTable(arg.table)
  116. end
  117. for _, ret in ipairs(variant.returns) do
  118. ret.description = unwrap(ret.description)
  119. processTable(ret.table)
  120. end
  121. end
  122. fn.arguments = nil
  123. fn.returns = nil
  124. track(fn)
  125. return fn
  126. end
  127. local function processObject(path, parent)
  128. local object = require(path)
  129. object.key = path:match('[^/]+$')
  130. object.name = object.key
  131. object.description = unwrap(object.description)
  132. object.summary = object.summary or object.description
  133. object.module = parent.key
  134. object.methods = {}
  135. object.constructors = pluralify(object, 'constructor')
  136. object.notes = unwrap(object.notes)
  137. object.examples = pluralify(object, 'example')
  138. if object.sections then
  139. for _, section in ipairs(object.sections) do
  140. section.description = unwrap(section.description)
  141. end
  142. end
  143. for k, example in ipairs(object.examples or {}) do
  144. object.examples[k] = processExample(example)
  145. end
  146. for _, file in ipairs(lovr.filesystem.getDirectoryItems(path)) do
  147. if file ~= 'init.lua' then
  148. table.insert(object.methods, processFunction(path .. '/' .. file:gsub('%..+$', ''), object))
  149. end
  150. end
  151. table.sort(object.methods, function(a, b) return a.key < b.key end)
  152. track(object)
  153. return object
  154. end
  155. local function processModule(path)
  156. local module = require(path .. '.init') -- So we avoid requiring the module itself
  157. module.key = module.external and path:match('[^/]+$') or path:gsub('/', '.')
  158. module.name = module.external and module.key or module.key:match('[^%.]+$')
  159. module.description = unwrap(module.description)
  160. module.functions = {}
  161. module.objects = {}
  162. module.enums = {}
  163. module.notes = unwrap(module.notes)
  164. if module.sections then
  165. for _, section in ipairs(module.sections) do
  166. section.description = unwrap(section.description)
  167. end
  168. end
  169. module.examples = pluralify(module, 'example')
  170. for k, example in ipairs(module.examples or {}) do
  171. module.examples[k] = processExample(example)
  172. end
  173. for _, file in ipairs(lovr.filesystem.getDirectoryItems(path)) do
  174. local childPath = path .. '/' .. file
  175. local childModule = childPath:gsub('%..+$', '')
  176. local isFile = lovr.filesystem.isFile(childPath)
  177. local capitalized = file:match('^[A-Z]')
  178. if file ~= 'init.lua' and not capitalized and isFile then
  179. table.insert(module.functions, processFunction(childModule, module))
  180. elseif capitalized and not isFile then
  181. table.insert(module.objects, processObject(childModule, module))
  182. elseif capitalized and isFile then
  183. table.insert(module.enums, processEnum(childModule, module))
  184. end
  185. end
  186. table.sort(module.functions, function(a, b) return a.key < b.key end)
  187. table.sort(module.objects, function(a, b) return a.key < b.key end)
  188. table.sort(module.enums, function(a, b) return a.key < b.key end)
  189. track(module)
  190. return module
  191. end
  192. -- Validation
  193. local function validateRelated(item)
  194. for _, key in ipairs(item.related or {}) do
  195. warnIf(not lookup[key], 'Related item for %s not found: %s', item.key, key)
  196. end
  197. end
  198. local function validateEnum(enum)
  199. validateRelated(enum)
  200. end
  201. local function validateObject(object)
  202. for _, constructor in ipairs(object.constructors or {}) do
  203. warnIf(not lookup[constructor], 'Constructor for %s not found: %s', object.key, constructor)
  204. end
  205. validateRelated(object)
  206. end
  207. local function validateFunction(fn)
  208. if fn.tag then
  209. local found = false
  210. for _, section in ipairs(lookup[fn.module].sections or {}) do
  211. if section.tag == fn.tag then found = true break end
  212. end
  213. warnIf(not found, 'Unknown tag %s for %s', fn.tag, fn.key)
  214. end
  215. for _, variant in ipairs(fn.variants) do
  216. for _, arg in ipairs(variant.arguments) do
  217. warnIf(not arg or not arg.name, 'Invalid argument for variant of %s', fn.key)
  218. end
  219. for _, ret in ipairs(variant.returns) do
  220. warnIf(not ret or not ret.name, 'Invalid return for variant of %s', fn.key)
  221. end
  222. end
  223. validateRelated(fn)
  224. end
  225. local function validateModule(module)
  226. for _, object in ipairs(module.objects) do
  227. validateObject(object)
  228. end
  229. for _, fn in ipairs(module.functions) do
  230. validateFunction(fn)
  231. end
  232. for _, fn in ipairs(module.enums) do
  233. validateEnum(fn)
  234. end
  235. end
  236. function lovr.load()
  237. local api = {
  238. modules = {},
  239. callbacks = {}
  240. }
  241. -- Modules
  242. table.insert(api.modules, processModule('lovr'))
  243. for _, file in ipairs(lovr.filesystem.getDirectoryItems('lovr')) do
  244. local path = 'lovr/' .. file
  245. if file ~= 'callbacks' and file:match('^[a-z]') and lovr.filesystem.isDirectory(path) then
  246. table.insert(api.modules, processModule(path))
  247. end
  248. end
  249. -- Callbacks
  250. local callbacks = 'lovr/callbacks'
  251. for _, file in ipairs(lovr.filesystem.getDirectoryItems(callbacks)) do
  252. table.insert(api.callbacks, processFunction(callbacks .. '/' .. file:gsub('%.lua', ''), api.modules[1]))
  253. end
  254. -- Validate
  255. for _, module in ipairs(api.modules) do
  256. validateModule(module)
  257. end
  258. -- Sort
  259. table.sort(api.modules, function(a, b) return a.key < b.key end)
  260. table.sort(api.callbacks, function(a, b) return a.key < b.key end)
  261. -- Serialize
  262. local file = io.open(lovr.filesystem.getSource() .. '/init.lua', 'w')
  263. assert(file, 'Could not open init.lua for writing')
  264. local keyPriority = {
  265. name = 1,
  266. tag = 2,
  267. summary = 3,
  268. type = 4,
  269. description = 5,
  270. key = 6,
  271. module = 7,
  272. arguments = 8,
  273. returns = 9
  274. }
  275. local function sort(keys, t)
  276. table.sort(keys, function(a, b) return (keyPriority[a] or 1000) < (keyPriority[b] or 1000) end)
  277. end
  278. local contents = 'return ' .. serpent.block(api, { comment = false, sortkeys = sort })
  279. file:write(contents)
  280. file:close()
  281. -- Bye
  282. lovr.event.quit()
  283. end