Skip to content

Instantly share code, notes, and snippets.

@LPGhatguy
Created December 31, 2013 05:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LPGhatguy/8192994 to your computer and use it in GitHub Desktop.
Save LPGhatguy/8192994 to your computer and use it in GitHub Desktop.
LDOC Prototype. Produces HTML-formatted documentation from Lua comments. See http://hastebin.com/nexedayeqo.lua for example input code. This is taken from my personal stash of utility scripts and isn't guaranteed to be decent. util.lua is the library's utility set (one function?) parser.lua parses the comments and forms a document tree. output.l…
return {
["html"] = {
value_process = function(source)
if (type(source) == "string") then
return source:gsub("\n", "\n<br />")
else
return source
end
end,
extension = ".html",
index = [=[
<!doctype html>
<html>
<head>
<title>Index</title>
</head>
<body>
<ul>
{{!children indexnode}}
</ul>
</body>
</html>
]=],
indexnode = [=[
<li><a href="{{path}}">{{id}} - {{title}}</a></li>
]=],
root = [=[
<!doctype html>
<html>
<head>
<title>{{id}}</title>
<style type="text/css">
body {
font-family: monospace;
padding: 0;
margin: 0;
background-color: black;
color: white;
}
.child {
border-top-style: groove;
border-bottom-style: groove;
border-width: 2px;
border-color: gray;
padding: 4px;
margin: 0;
}
.def {
color: #B81F11;
font-weight: bold;
}
.returns {
color: #FFE318;
font-weight: bold;
}
.title {
color: gray;
}
.desc {
font-style: italic;
}
.id {
font-style: italic;
}
.status {
font-weight: bold;
}
.s-needs-testing:after {
content: "Needs Testing";
color: #FFE318;
}
.s-incomplete:after {
content: "Incomplete";
color: #B81F11;
}
.s-production:after {
content: "Production";
color: #089708;
}
</style>
</head>
<body>
<div id="root">
<div id="properties">
<h1 class="title">{{title}}</h1>
<h2>ID: <span class="id">{{id}}</span></h2>
<br />
{{#version <h3>version $$</h3>|}}
{{#status <h3>Status: <span class="status s-$$"> </span></h3>|}}
<br />
<p class="desc">{{desc}}</p>
<h3>TODO</h3>
<ul class="todos">
{{#todo <li>$$</li>|no todos}}
</ul>
</div>
<div id="children">
{{!children child}}
</div>
</div>
</body>
</html>
]=],
child = [=[
<div class="child {{type}}" id="{{id}}">
<h2 class="title">{{title}}</h2>
<p><span class="returns">{{returns}}</span> <span class="def">{{root.id}}.{{id}}{{def|()}}</span></p>
<p class="desc">{{desc|No description provided.}}</p>
</div>
]=]
}
}
local arg = {...}
if (#arg == 0) then
print("Invoked improperly")
return
end
local lfs = require("lfs")
local util = require("util")
local parser = require("parser")
local output = require("output")
local format = require("format")
--assume path to be the last argument
local path = arg[#arg]
local modules = {}
local file_patterns = {"%.lua$", "%.ldoc$"}
local output_directory = path .. "/ldoc"
local index = parser:node("index")
index.children = modules
local unknowns = 0
local total = 0
local ignores = 0
local faileds = 0
local function parse_path(dir, modules)
local modules = modules or {}
for item in lfs.dir(dir) do
if (item ~= "." and item ~= "..") then
local path = dir .. "/" .. item
local mode = lfs.attributes(path, "mode")
if (mode == "file") then
local matches = false
for key, value in pairs(file_patterns) do
if (item:match(value)) then
matches = true
break
end
end
if (matches) then
local handle = io.open(path, "r")
if (handle) then
local body = handle:read("*all")
handle:close()
local parsed = parser:parse(body)
if (parsed) then
table.insert(modules, parsed)
else
ignores = ignores + 1
end
else
faileds = faileds + 1
print("I/O error reading " .. path)
end
end
elseif (mode == "directory") then
parse_path(path, modules)
end
end
end
return modules
end
lfs.mkdir(output_directory)
local parsed = parse_path(path, modules)
for key, mod in pairs(parsed) do
local id = mod.properties["id"]
if (not id) then
unknowns = unknowns + 1
id = "unknown-" .. unknowns
end
local extpath = ""
for ext, per in id:gmatch("([^%.]+)(%.?)") do
extpath = extpath .. "/" .. ext
if (per:len() == 1) then
lfs.mkdir(output_directory .. extpath)
end
end
local total_path = output_directory .. extpath .. (format[output.style].extension or ".txt")
local docbody = output:process(mod)
local handle = io.open(total_path, "w")
if (handle) then
handle:write(docbody)
handle:close()
total = total + 1
else
faileds = faileds + 1
print("Couldn't open file for write:", total_path)
end
end
local ibody = output:process(index, nil, "index")
local ihandle = io.open(output_directory .. "/index" .. (format[output.style].extension or ".txt"), "w")
if (ihandle) then
ihandle:write(ibody)
ihandle:close()
else
print("Couldn't open index for write!")
end
print(([[
LDOC documentation generated successfully!
%d files with no ID
%d files ignored
%d files failed
%d files succeeded]]):format(unknowns, ignores, faileds, total))
local format = require("format")
local util = require("util")
local output
local MODE_ARRAY = "#"
local MODE_RECURSE = "!"
local PATTERNED_SUB = "$$"
output = {
style = "html",
process = function(self, tree, root, doc)
doc = doc or "root"
root = root or tree
local style = format[self.style]
local output = style[doc] or ("no document '" .. doc .. "'")
for whole, inner in output:gmatch("({{(.-)}})") do
local safewhole = util.escape_string(whole)
local mode, middle, default = inner:match("^(%W?)([^|]+)|?(.-)$")
local prop, extra = middle:match("^(%S+)%s?(.-)$")
local rprop = prop:match("root%.(.+)")
local source
if (rprop) then
prop = rprop
source = root.properties[prop] or root[prop]
else
source = tree.properties[prop] or tree[prop]
end
if (source and style.value_process) then
source = style.value_process(source) or source
end
if (mode == "") then --standard replace
local safeprop = util.escape_string(prop)
output = output:gsub(safewhole, tostring(source or default))
elseif (mode == MODE_ARRAY) then
local built = ""
if (source) then
local safesub = util.escape_string(PATTERNED_SUB)
if (type(source) == "table") then
if (#source == 0) then
built = default
else
for index, value in next, source do
built = built .. extra:gsub(safesub, tostring(value))
end
end
else
built = built .. extra:gsub(safesub, tostring(source or default))
end
else
built = default
end
output = output:gsub(safewhole, built)
elseif (mode == MODE_RECURSE) then
local built = ""
if (source and type(source) == "table") then
for index, value in next, source do
built = built .. self:process(value, root, extra)
end
else
built = default
end
output = output:gsub(safewhole, built)
else
print("unsupported mode", mode)
end
end
return output
end
}
return output
local util = require("util")
local parser
local DOC_DISABLE = "~NODOC"
local TOKEN_PROPERTY = "#"
local TOKEN_OBJECT = "@"
local MODE_ARRAY = "#"
local ID_PROPERTY = "id"
parser = {
node = function(self, sort)
return {
type = sort,
path = "nil",
properties = {},
children = {}
}
end,
parse = function(self, source)
local root = self:node("module")
local object = root
if (source:match(DOC_DISABLE)) then
return false
end
for line in source:gmatch("[^\r\n]+") do
line = line:match("%s*%-*%[*(.-)$")
local token = line:sub(1, 1)
if (token == TOKEN_PROPERTY) then
local safeprop = util.escape_string(TOKEN_PROPERTY)
local mode, key, value = line:match(safeprop .. "(.-)([%w_]*)%s(.*)")
if (mode == MODE_ARRAY) then
if (object.properties[key]) then
table.insert(object.properties[key], value)
else
object.properties[key] = {value}
end
else
if (object.properties[key]) then
object.properties[key] = object.properties[key] .. "\n" .. value
else
object.properties[key] = value
end
end
elseif (token == TOKEN_OBJECT) then
local sort, id = line:match(".-([%w%-_]+)%s?(.-)$")
if (sort) then
object = self:node(sort)
if (id:len() > 0) then
object.properties[ID_PROPERTY] = id
end
table.insert(root.children, object)
else
object = root
end
end
end
local id = root.properties[ID_PROPERTY]
if (id) then
root.path = id:gsub("%.", "/") .. ".html"
end
return root
end
}
return parser
local util
util = {
escape_string = function(source)
--This is a work in progress function
source = source:gsub("([%-%?%*%.%$%(%)])", "%%%0")
return source
end
}
return util
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment