main.lua 8.9 KB

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