| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 | local f = io.popen('git branch --show-current')local dev = f and f:read('*a'):match('%w+') == 'dev'f:close()-- Helperslocal function copy(t)  if type(t) ~= 'table' then return t end  local result = {}  for k, v in pairs(t) do    result[k] = copy(v)  end  return resultendlocal function unindent(code)  local indent = code:match('^(% +)')  if indent then    return code:gsub('\n' .. indent, '\n'):gsub('^' .. indent, ''):gsub('%s*$', '')  else    return code  endendlocal function unwrap(str)  if not str then return str end  str = unindent(str)  return str:gsub('([^\n]+)\n(%S)', function(a, b)    if b == '-' or b == '>' or b == ':' or a:match(':::') then      return a .. '\n' .. b    else      return a .. ' ' .. b    end  end)    :gsub('^%s+', '')    :gsub('%s+$', '')endlocal function pluralify(t, key)  t[key .. 's'] = t[key .. 's'] or (t[key] and { t[key] } or nil)  t[key] = nil  return t[key .. 's']endlocal lookup = {}local function track(obj)  lookup[obj.key] = objendlocal function warn(s, ...)  print(string.format(s, ...))endlocal function warnIf(cond, ...)  if cond then warn(...) endendlocal function getVisibleDirectoryItems(path)  local input = lovr.filesystem.getDirectoryItems(path)  local output = {}  for _, item in pairs(input) do    if item:match('^%.') then      warn('Skipping hidden file "%s"', item)    else      table.insert(output, item)    end  end  return outputend-- Processorslocal function processExample(example, key)  if type(example) == 'string' then    return {      code = unindent(example)    }  else    assert(example.code, string.format('%s example is missing code', key))    example.description = unwrap(example.description)    example.code = unindent(example.code)  end  return exampleendlocal function processEnum(path, parent)  local enum = require(path)  enum.name = path:match('[^/]+$')  enum.key = enum.name  enum.module = parent.key  enum.description = unwrap(enum.description)  enum.notes = unwrap(enum.notes)  for _, value in ipairs(enum.values) do    value.description = unwrap(value.description)  end  track(enum)  return enumendlocal function processFunction(path, parent)  local fn = require(path)  fn.name = path:match('[^/]+$')  fn.key = parent.name:match('^[A-Z]') and (parent.key .. ':' .. fn.name) or (parent.key .. '.' .. fn.name)  fn.deprecated = type(fn.deprecated) == 'string' and unwrap(fn.deprecated) or fn.deprecated  fn.description = unwrap(fn.description)  fn.module = parent.module or parent.key  fn.notes = unwrap(fn.notes)  fn.examples = pluralify(fn, 'example')  for k, example in ipairs(fn.examples or {}) do    fn.examples[k] = processExample(example, fn.key)  end  assert(fn.variants and #fn.variants > 0, string.format('Function %q is missing variants', fn.key))  assert(fn.arguments, string.format('Function %q does not have arguments list', fn.key))  assert(fn.returns, string.format('Function %q does not have returns list', fn.key))  for name, arg in pairs(fn.arguments) do    arg.name = name  end  for name, ret in pairs(fn.returns) do    ret.name = name  end  for i, variant in ipairs(fn.variants) do    assert(variant.arguments, string.format('%q variant #%d is missing arguments', fn.key, i))    assert(variant.returns, string.format('%q variant #%d is missing returns', fn.key, i))    for j, name in ipairs(variant.arguments) do      warnIf(not fn.arguments[name], string.format('%s uses unknown argument %s', fn.key, name))      variant.arguments[j] = copy(fn.arguments[name])    end    for j, name in ipairs(variant.returns) do      warnIf(not fn.returns[name], string.format('%s uses unknown return %s', fn.key, name))      variant.returns[j] = copy(fn.returns[name])    end  end  for _, variant in ipairs(fn.variants) do    local function processTable(t)      if not t then return end      for _, field in ipairs(t) do        field.description = unwrap(field.description)        processTable(field.table)      end      t.description = unwrap(t.description)    end    variant.description = unwrap(variant.description)    for _, arg in ipairs(variant.arguments) do      arg.description = unwrap(arg.description)      processTable(arg.table)    end    for _, ret in ipairs(variant.returns) do      ret.description = unwrap(ret.description)      processTable(ret.table)    end  end  fn.arguments = nil  fn.returns = nil  track(fn)  return fnendlocal function processObject(path, parent)  local object = require(path .. '.init')  assert(type(object) == 'table', string.format('%s/init.lua did not return a table', path))  object.key = path:match('[^/]+$')  object.name = object.key  object.description = unwrap(object.description)  object.summary = object.summary or object.description  object.module = parent.key  object.constructors = pluralify(object, 'constructor')  object.notes = unwrap(object.notes)  object.examples = pluralify(object, 'example')  if object.sections then    for _, section in ipairs(object.sections) do      section.description = unwrap(section.description)    end  end  local methods = {}  for _, file in ipairs(getVisibleDirectoryItems(path)) do    if file ~= 'init.lua' then      local method = file:gsub('%..+$', '')      local key = ('%s:%s'):format(object.name, method)      methods[key] = processFunction(path .. '/' .. method, object)    end  end  if object.methods then    for i, key in ipairs(object.methods) do      object.methods[i] = methods[key]      warnIf(not methods[key], '%s links to unknown method %q', object.key, key)      methods[key] = nil    end    for method in pairs(methods) do      warn('%s is missing link to %q', object.key, method)    end  else    object.methods = {}    for key, method in pairs(methods) do      table.insert(object.methods, method)    end    table.sort(object.methods, function(a, b) return a.key < b.key end)  end  for k, example in ipairs(object.examples or {}) do    object.examples[k] = processExample(example, object.key)  end  track(object)  return objectendlocal function processModule(path)  local module = require(path .. '.init') -- So we avoid requiring the module itself  module.key = module.external and path:match('[^/]+$') or path:gsub('/', '.')  module.name = module.external and module.key or module.key:match('[^%.]+$')  module.description = unwrap(module.description)  module.functions = {}  module.objects = {}  module.enums = {}  module.notes = unwrap(module.notes)  if module.sections then    for _, section in ipairs(module.sections) do      section.description = unwrap(section.description)    end  end  module.examples = pluralify(module, 'example')  for k, example in ipairs(module.examples or {}) do    module.examples[k] = processExample(example, module.key)  end  for _, file in ipairs(getVisibleDirectoryItems(path)) do    local childPath = path .. '/' .. file    local childModule = childPath:gsub('%..+$', '')    local isFile = lovr.filesystem.isFile(childPath)    local capitalized = file:match('^[A-Z]')    if file ~= 'init.lua' and not capitalized and isFile then      table.insert(module.functions, processFunction(childModule, module))    elseif capitalized and not isFile then      table.insert(module.objects, processObject(childModule, module))    elseif capitalized and isFile then      table.insert(module.enums, processEnum(childModule, module))    end  end  table.sort(module.functions, function(a, b) return a.key < b.key end)  table.sort(module.objects, function(a, b) return a.key < b.key end)  table.sort(module.enums, function(a, b) return a.key < b.key end)  track(module)  return moduleend-- Validationlocal function validateRelated(item)  for _, key in ipairs(item.related or {}) do    warnIf(not lookup[key], '%s has unknown related item %s', item.key, key)    warnIf(key == item.key, '%s should not be related to itself', key)  endendlocal function validateEnum(enum)  warnIf(not enum.summary, 'Enum %s is missing summary', enum.name)  warnIf(not enum.description, 'Enum %s is missing description', enum.name)  for i, value in ipairs(enum.values) do    warnIf(not value.name, 'Enum %s value #%d is missing name', enum.name, i)    warnIf(not value.description, 'Enum %s value #%d is missing description', enum.name, i)  end  validateRelated(enum)endlocal function validateType(type, fields, key, kind)  if not type then    warn('Missing %s type in %s', kind, key)  elseif type == 'table' and fields then    for i, field in ipairs(fields) do      validateType(field.type, field.table, key, kind)    end  else    local valid = {      ['nil'] = true,      boolean = true,      number = true,      table = true,      string = true,      userdata = true,      lightuserdata = true,      ['function'] = true,      ['*'] = true    }    for word in type:gmatch('%w+') do      if word:match('^[A-Z]') then        warnIf(not lookup[word], 'Invalid %s type "%s" in %s', kind, word, key)      else        warnIf(not valid[word], 'Invalid %s type "%s" in %s', kind, word, key)      end    end  endendlocal function validateFunction(fn)  if fn.tag then    local found = false    for _, section in ipairs(lookup[fn.module].sections or {}) do      if section.tag == fn.tag then found = true break end    end    for _, object in ipairs(lookup[fn.module].objects) do      for _, section in ipairs(object.sections or {}) do        if section.tag == fn.tag then found = true break end      end    end    warnIf(not found, 'Unknown tag %s for %s', fn.tag, fn.key)  end  for _, variant in ipairs(fn.variants) do    for _, arg in ipairs(variant.arguments) do      warnIf(not arg or not arg.name, 'Invalid argument for variant of %s', fn.key)      validateType(arg.type, arg.table, fn.key, 'argument')    end    for _, ret in ipairs(variant.returns) do      warnIf(not ret or not ret.name, 'Invalid return for variant of %s', fn.key)      validateType(ret.type, ret.table, fn.key, 'return')    end  end  validateRelated(fn)endlocal function validateObject(object)  for _, constructor in ipairs(object.constructors or {}) do    warnIf(not lookup[constructor], 'Constructor for %s not found: %s', object.key, constructor)  end  for _, method in ipairs(object.methods or {}) do    validateFunction(method)    if object.sections and not method.deprecated then      local found = false      for _, section in ipairs(object.sections) do        if section.tag and section.tag == method.tag then          found = true          break        else          for _, link in ipairs(section.links or {}) do            if link == method.key then              found = true              break            end          end        end      end      warnIf(not found, '%s is missing from its parent\'s page', method.key)    end  end  local metatable = debug.getregistry()[object.name]  if dev and metatable then    local hasMethod = {}    for _, method in ipairs(object.methods or {}) do      if not metatable[method.name] and not method.deprecated then        warn('%s has docs for unknown method %s', object.name, method.name)      end      hasMethod[method.name] = true    end    if object.extends then      for i, method in ipairs(lookup[object.extends].methods) do        hasMethod[method.name] = true      end    end    local ignore = {      type = true,      release = true,      monkey = true    }    for name in pairs(metatable) do      if not name:match('^__') and not ignore[name] then        warnIf(not hasMethod[name], '%s is missing docs for %s', object.name, name)      end    end  end  validateRelated(object)endlocal function validateModule(module)  local t = lovr[module.name]  for _, object in ipairs(module.objects) do    validateObject(object)  end  for _, fn in ipairs(module.functions) do    validateFunction(fn)    if dev and not fn.deprecated then      warnIf(t and not t[fn.name], '%s has docs for unknown function %s', module.key, fn.name)    end    if module.sections and not fn.deprecated then      local found = false      for _, section in ipairs(module.sections) do        if section.tag and section.tag == fn.tag then          found = true          break        else          for _, link in ipairs(section.links or {}) do            if link == fn.key then              found = true              break            end          end        end      end      warnIf(not found, '%s is missing a parent link/tag', fn.key)    end  end  local ignore = {    setSource = true,    getBundlePath = true,    openConsole = true  }  for name in pairs(t or {}) do    local key = ('%s.%s'):format(module.key, name)    warnIf(not ignore[name] and not lookup[key], 'Missing docs for %s', key)  end  for _, fn in ipairs(module.enums) do    validateEnum(fn)  endendfunction lovr.load()  local api = {    modules = {},    callbacks = {}  }  -- So errhand exits  lovr.graphics = nil  -- Modules  table.insert(api.modules, processModule('lovr'))  for _, file in ipairs(lovr.filesystem.getDirectoryItems('lovr')) do    local path = 'lovr/' .. file    if file ~= 'callbacks' and file:match('^[a-z]') and lovr.filesystem.isDirectory(path) then      table.insert(api.modules, processModule(path))    end  end  -- Callbacks  local callbacks = 'lovr/callbacks'  for _, file in ipairs(getVisibleDirectoryItems(callbacks)) do    table.insert(api.callbacks, processFunction(callbacks .. '/' .. file:gsub('%.lua', ''), api.modules[1]))  end  -- Validate  for _, callback in ipairs(api.callbacks) do    validateFunction(callback)  end  for _, module in ipairs(api.modules) do    validateModule(module)  end  -- Sort  table.sort(api.modules, function(a, b) return a.key < b.key end)  table.sort(api.callbacks, function(a, b) return a.key < b.key end)  -- Generate  local ok, generator = pcall(require, 'generators.' .. (arg[1] or 'lua'))  if not ok then    print(('Could not load generator %q: %s'):format(name, generator))  else    generator(api)  end  -- Bye  lovr.event.quit()end
 |