Created
January 3, 2018 08:08
-
-
Save wesleywerner/869313cb1e47b9f48e70c432e9eea1dc to your computer and use it in GitHub Desktop.
Generates Geany function tags from LÖVE sources.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 <[email protected]> | |
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