Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 255 lines (215 sloc) 6.123 kB
#!/usr/bin/env lua
-- docs
-- oUF documentation generator
--
-- This is really just a quick and dirty way of generating documentation for
-- oUF[1]. The syntax is inspired by TomDoc[2], but a lot of the non-oUF and
-- non-Lua things aren't implemented.
--
-- Why implement my own documentation generator?
-- It was mainly done because oUF is kind-of special, but also because the
-- available alternatives aren't good enough or have issues I can't workaround.
--
-- Things that need fixing:
-- - No highlighting of Lua code.
-- - Doesn't validate that comments are documentation strings.
-- - Doesn't parse its own documentation header.
-- - Close to zero error handling.
--
-- Usage
--
-- docs [docs path] [file...]
--
-- Links
--
-- [1] https://github.com/haste/oUF
-- [2] http://tomdoc.org/
local out
local lines
local tisf = function(fmt, ...)
table.insert(out, fmt:format(...))
end
local trim = function(str)
return str:match('^()%s*$') and '' or str:match('^%s*(.*%S)')
end
local findNextEmpty = function(start, stop)
for i=start, stop or #lines do
if(lines[i] == '') then
return i
end
end
end
local findNextHeader = function(offest)
for i=offest, #lines do
local pre, header, post = unpack(lines, i, i + 2)
-- Single lines without punctuation are headers.
if(pre == '' and post == '' and not header:match'%.') then
return i + 1
end
end
end
local findNextArguent = function(start, stop, padding, pattern)
for i=start, stop do
local match, pad = lines[i]:match(pattern)
if(match and pad == padding) then
return i
end
end
end
local replaceMarkup = function(str)
return str
-- `in-line code` to <code>in-line code</code>
:gsub('`([^`]+)`', '<code>%1</code>')
-- [Link](http://example.com) to <a href="http://example.com">Link</a>
:gsub('%[([^%]]+)%]%(([^)]+)%)', '<a href="%2">%1</a>')
end
local handleArguments = function(start, stop, pattern)
tisf('<dl>')
repeat
-- Tear out the argument name and offset of where the description begins.
local def, padding, offset = lines[start]:match(pattern)
tisf('<dt>%s</dt>', def)
-- Insert the first line of the description.
tisf('<dd>')
tisf(lines[start]:sub(offset))
-- Find the next argument in the list or continue to the end of the
-- current section.
local nextarg = findNextArguent(start + 1, stop, padding, pattern)
nextarg = (nextarg or stop + 1) - 1
for i=start + 1, nextarg do
tisf(trim(lines[i]))
end
tisf('</dd>')
start = nextarg + 1
until start > stop
tisf('</dl>')
end
local handleExamples = function(start, stop)
-- An extra line gets added if we don't do this.
tisf('<pre><code>%s', lines[start]:sub(3))
for i=start + 1, stop do
tisf(lines[i]:sub(3))
end
tisf('</code></pre>')
end
local handleParagraph = function(start, stop)
tisf('<p>')
for i=start, stop do
tisf(trim(lines[i]))
end
tisf('</p>')
end
local handleSection = function(start, stop)
while(start) do
-- Find the next empty line or continue until the end of the section.
-- findNextEmpty() returns the position of the empty line, so we need to
-- subtract one from it.
local nextEmpty = findNextEmpty(start + 1, stop)
if(nextEmpty) then
nextEmpty = nextEmpty - 1
else
nextEmpty = stop
end
local line = lines[start]
if(not line) then
return
elseif(line:match('^%S+%s*%- ')) then
handleArguments(start, nextEmpty, '(%S+)%s*()%- ()')
elseif(line:sub(1, 2) == ' ') then
handleExamples(start, nextEmpty)
else
handleParagraph(start, nextEmpty)
end
start = findNextEmpty(nextEmpty, stop)
if(start) then start = start + 1 end
end
end
local generateDocs = function(str, level)
lines = {}
out = {}
for line in str:gmatch('([^\n]*)\n') do
table.insert(lines, line:gsub('\t*', ''):sub(2))
end
-- The first line is always the main header.
tisf('<h%d>%s</h%d>', level, lines[1], level)
-- Then comes the main description.
local offset = findNextHeader(1)
-- Continue until two lines before the header or to the end of the comment.
if(offset) then
offset = offset - 2
else
offset = #lines
end
local init = findNextEmpty(1) + 1
if(init > offset) then
init = 2
end
handleSection(init, offset)
while(true) do
offset = findNextHeader(offset)
if(not offset) then break end
-- Every section has a header.
tisf('<h%d>%s</h%d>', level + 1, lines[offset], level + 1)
-- Find out the size of the section.
local start = findNextEmpty(offset) + 1
if(not lines[start]) then
-- There's no section here, only a headline.
break
end
local stop
local nextHeader = findNextHeader(start)
if(nextHeader) then
stop = nextHeader - 2
else
local nextEmpty = findNextEmpty(start)
if(nextEmpty) then
stop = nextEmpty - 1
else
stop = #lines
end
end
handleSection(start, stop)
offset = stop + 1
end
return table.concat(out, '\n')
end
local handleFile = function(path)
local file = io.open(path, 'r')
local content = file:read'*a'
file:close()
local out = {}
local init = 1
repeat
local _, comStart, depth = content:find('%-%-%[(=*)%[ ', init)
if(comStart) then
local comEnd = content:find('%]' .. depth .. '%]', comStart)
local comment = content:sub(comStart, comEnd - 1)
-- Convert markup to html.
comment = replaceMarkup(comment)
-- The first comment uses h1 and h2, while the subsequent ones uses h3
-- and h4.
local level = init == 1 and 1 or 3
table.insert(out, generateDocs(comment, init == 1 and 1 or 3))
init = comEnd
end
until not comStart
return table.concat(out, '\n')
end
local destination = (...)
for i=2, select('#', ...) do
local file = select(i, ...)
local path, filename = file:match('(.+)/(.+)$')
if(path:sub(1,3) == '../') then
path = path:sub(4)
end
if(#path == 0) then path = nil end
filename = filename:gsub('lua', 'html')
local doc = handleFile(file)
if(doc) then
local dfPath = string.format('%s/%s', destination, path or '')
os.execute(string.format('mkdir -p %s', dfPath))
local docFile = io.open(string.format('%s/%s', dfPath, filename), 'w+')
docFile:write(doc)
docFile:close()
end
end
Jump to Line
Something went wrong with that request. Please try again.