main.lua 8.5 KB

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