Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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.