Skip to content

Instantly share code, notes, and snippets.

@wesleywerner
Created January 3, 2018 08:08
Show Gist options
  • Save wesleywerner/869313cb1e47b9f48e70c432e9eea1dc to your computer and use it in GitHub Desktop.
Save wesleywerner/869313cb1e47b9f48e70c432e9eea1dc to your computer and use it in GitHub Desktop.
Generates Geany function tags from LÖVE sources.
--[[
geany tag generator.lua
Generates Geany function tags from LÖVE sources.
You require lua file system to run this:
luarocks install luafilesystem
Copyright 2018 wesley werner <wesley.werner@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
]]--
local TAG_FILENAME = "love.lua.tags"
function trim1(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function string.ends(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
--- Cleans a line from unwanted tokens and keywords
function clean(line)
-- removes "TOKEN::" identifiers like "love::" and "image::" in:
-- love::image::ImageData *newScreenshot(love::image::Image *image, bool copyAlpha = true)
line = line:gsub("(%w+)::", "")
-- remove a list of parameter keywords
line = line:gsub("const ", "")
line = line:gsub("vector", "")
line = line:gsub("StrongRef", "")
-- trim and return
return trim1(line)
end
--- Returns if the given line is a function definition
function isDefinition(line)
local hasterminator = string.ends(line, ";")
local hasbrackets = line:match("%(")
return hasbrackets and hasterminator
end
--- Returns if the line indicates public functions start
function publicSectionStart(line)
return line == "public:"
end
--- Returns if the line indicates public functions end
function publicSectionEnd(line)
return line == "private:"
end
--- Returns if the line begins a comment
function commentStart(line)
return string.starts(line, "/*")
end
--- Returns if the line end a comment
function commentEnd(line)
return string.ends(line, "*/")
end
--- parse a line into it's components
function parseline(line)
local parts = { type="", name="", parameters={} }
local parametertype = nil
local id = 1
for value in line:gmatch("[_%a][_%w]*") do
--print(id..": "..value)
if id == 1 then
-- return value
parts["type"] = value
elseif id == 2 then
-- name
parts["name"] = value
else
-- odd numbers are types, even the name
if id % 2 == 0 then
-- only add the parameter if there is a stored type
if parametertype then
table.insert(parts.parameters, { name=value, type=parametertype } )
-- reset parameter type
parametertype = nil
end
else
-- store the parameter type
parametertype = value
end
end
id = id + 1
end
-- ignore definitions with no name
if parts.name == "" then
return nil
else
return parts
end
end
--- translates function parts into a pipe string.
function topipe(parts)
-- geany tags format
-- basename|string|(string path [, string suffix])|
--
-- The first field is the symbol name (usually a function name).
-- The second field is the type of the return value.
-- The third field is the argument list for this symbol.
-- The fourth field is the description for this symbol
-- but currently unused and should be left empty.
-- Except for the first field (symbol name), all other field
-- can be left empty but the pipe separator must appear for them.
local p = { }
for _, n in ipairs(parts.parameters) do
table.insert(p, string.format("%s %s", n.type, n.name))
end
return string.format("%s|%s|(%s)|", parts.name, parts.type, table.concat(p, ", "))
end
function parsefile(filename)
local taglines = { }
local insidePublicSection = false
local insideComment = false
local f = io.lines(filename)
for line in f do
line = clean(line)
-- flag when inside comments
if not insideComment then
if commentStart(line) then
insideComment = true
end
end
-- flag when inside and outside public functions sections
if not insidePublicSection then
if publicSectionStart(line) then
insidePublicSection = true
end
else
if publicSectionEnd(line) then
insidePublicSection = false
end
end
if not insideComment and insidePublicSection and isDefinition(line) then
local parts = parseline(line)
if parts then
--print(string.format("found %s (%s) with %d parameters", parts.name, parts.type, #parts.parameters))
table.insert(taglines, topipe(parts))
--print("\n"..line)
--print(string.format("%s %s", parts.type, parts.name))
--for n, v in ipairs(parts.parameters) do
--print(string.format("param %d) %s %s", n, v.type, v.name))
--end
end
end
if insideComment then
if commentEnd(line) then
insideComment = false
end
end
end
return taglines
end
--- Creates a new tags file
function createTags()
file = io.open(TAG_FILENAME, "w")
file:write("# format=pipe\n")
file:close()
end
--- Appends tags to file
function writeTags(lines)
file = io.open(TAG_FILENAME, "a+")
for _, line in ipairs(lines) do
file:write(line.."\n")
end
file:flush()
file:close()
end
--- Process source files recursively
function processSource(path)
local lfs = require("lfs")
for file in lfs.dir(path) do
if file ~= "." and file ~= ".." then
local fullpath = path..'/'..file
local attr = lfs.attributes(fullpath)
assert(type(attr) == "table")
if attr.mode == "directory" then
-- recurse subdirectories
processSource(fullpath)
else
if string.ends(file, ".h") then
print(string.format("Processing: %s", fullpath))
local filetags = parsefile(fullpath)
print(string.format("\tFound %d definitions", #filetags))
writeTags(filetags)
end
end
end
end
end
if not arg[1] then
print(string.format("Usage: lua %s [LOVE SOURCE PATH]", arg[0]))
else
createTags()
processSource(arg[1])
print(string.format("\nWritten %s", TAG_FILENAME))
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment