#!/usr/bin/env lua
-- (c) 2011 John MacFarlane
-- Released under the MIT license.

--[===[
@startman
# NAME

lunadoc - simple markdown documentation for lua modules

# SYNOPSIS

luadoc [options] [file..]

# DESCRIPTION

This program reads a number of source files, scans them
for special comment blocks beginning with '---', and writes
an HTML file documenting each module to the doc directory.
There must be blank space between the opening dashes and
any following text.

If the comment block immediately precedes a declaration
of a function or variable, the name is extracted automatically
and placed at the top of the comment block in the output.
Example:

    --- This function frobs a brib.
    -- `brib` is the brib to frob.
    -- `smorg` turns on smorging if true.
    function M.frob_a_brib(brib)

Each file is assumed to be a module.  The 'module' keyword need
not be used. The `M.` prefix on declarations will be removed,
as it is assumed that this is used to put definitions in the
module's namespace.

Documentation is parsed as markdown, with definition lists and smart
typography extensions.

Module names in square brackets automatically create links to the module's
documentation. For example:

    -- See [lunamark.util].

# OPTIONS

`--dir,-d` PATH
:   Output directory (default 'doc'). Directory will
    be created if it does not exist.
`--css,-c` PATH
:   Path of CSS file to use. If not specified, a
    default CSS file will be used.
`--template,-T` PATH
:   Path of a cosmo template to be used for module
    documentation.  If not specified, a default template
    will be used.
`--version,-V`
:   Print version information.
`--help,-h`
:   This message

# AUTHORS

John MacFarlane.

@stopman
--]===]

local lunamark = require("lunamark")
local cosmo = require("cosmo")
local alt_getopt = require("alt_getopt")
local lfs = require("lfs")

local version = [[
lunadoc 0.1
Copyright (C) 2011 John MacFarlane
]]

local usage = [[
Usage: lunadoc [options] [source..] - create documentation

Options:
  --dir,-d PATH        Output directory (default 'doc')
  --css,-c PATH        CSS to use
  --template,-T PATH   Template to use
  --version,-V         Version information
  --help,-h            This message
]]

local long_opts = {
  dir = "d",
  css = "c",
  template = "T",
  version = "V",
  help = "h"
}

local short_opts = "d:c:t:Vh"
local optarg,optind = alt_getopt.get_opts(arg, short_opts, long_opts)

if optarg.h or optind > #arg then
  io.write(usage)
  os.exit(0)
end

if optarg.V then
  io.write(version)
  os.exit(0)
end

local index = {}
local index_refs = {}

local function get_modname(s)
  return s:gsub("%.lua$",""):gsub("/",".")
end

local function normalize(s)
  return s:gsub("[ \t\n]+"," "):lower()
end

for i=optind,#arg do
  local modname = get_modname(arg[i])
  table.insert(index, modname)
  index_refs[normalize(modname)] = { url = modname .. ".html" }
end

local writer = lunamark.writer.html.new()

local converter = lunamark.reader.markdown.new(writer,{smart=true, definition_lists=true, references = index_refs })


local function extract_comments(f)
  local commentlines = {}
  local chunks = {}
  local collect = false
  io.input(f)
  for l in io.lines() do
    local m = l:match("^%s*%-%-%-%s(.*)")
    if m then
      collect = true
      table.insert(commentlines,m)
    elseif collect then
      local n = l:match("^%s*%-%-%-?%s?(.*)")
      if n then
        table.insert(commentlines,n)
      else
        collect = false
        local declname, decargs = nil, nil
        declname, decargs = l:match("([^%s%=]+)%s*=%s*function%s*(%b())")
        if not declname then
          declname, decargs = l:match("function%s*([^%s%(]+)%s*(%b())")
        end
        if not declname then
          declname = l:match("([^%s=]+)%s*=")
        end
        if declname then
          -- strip off M. which puts declaration in module
          declname = string.gsub(declname,"^M%.","")
        end
        local decl = (declname or "") .. ((declname and decargs) or "")
        local rawcontents = table.concat(commentlines, "\n") .. "\n\n"
        local thischunk = { name = decl,
                            contents = converter(rawcontents),
                            id = decl and decl:match("^[^%( \t]*")
                          }
        table.insert(chunks, thischunk)
        commentlines = {}
      end
    end
  end
  return chunks
end

local default_template = [===[
<html>
<head>
<title>$modname</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="lunadoc.css" type="text/css" />
</head>
<body>
<div id="content">
<h1 class="module"><code>$modname</code></h1>
$chunks[=[
<div class="section" $if{ id }[[ id="$id"]]>
<h3 class="declaration"><code>$name</code></h3>
$contents
</div>
]=]
</div>
<div id="index">
$index[[
<p><a href="$it.html">$it</a></p>
]]
</div>
</body>
</html>
]===]

local default_css = [[
body { font-family: Georgia, serif; }
h3.declaration { margin-bottom: 0.5em; }
div.section { clear: both; border-top: 2px solid #eee; }
code,pre { font-family: Courier, monospace; font-size: 90%; }
div#index { position: absolute; left: 1em; top: 1em; width:14em; }
div#content { position: absolute; left: 15em; top: 0.5em; padding-left: 2em; max-width: 40em; padding-bottom: 1em; border-left: 1px solid #555; }
]]

local destdir = optarg.d or "doc"

local template = default_template
if optarg.t then
  local tfile = io.open(optarg.t, "r")
  template = tfile:read("*all")
  tfile:close()
end

local css = default_css
if optarg.c then
  local cfile = io.open(optarg.c, "r")
  css = cfile:read("*all")
  cfile:close()
end

for i=optind,#arg do
  local f = arg[i]
  local data = {}
  data["if"] = cosmo.cif  -- this activates the "if" keyword
  data.sepby = lunamark.util.sepby
  data.chunks = extract_comments(f)
  data.modname = get_modname(f)
  data.index = index
  local page = cosmo.fill(template, data)
  if not lfs.attributes(destdir) then
    lfs.mkdir(destdir)
  end
  local file = io.open(destdir .. "/" .. data.modname .. ".html", "w")
  file:write(page)
  file:close()
  -- index page is a copy of the first page
  -- symlink would be better, but that won't work on windows
  if i == optind then
    file = io.open(destdir .. "/" .. "index.html", "w")
    file:write(page)
    file:close()
  end
end

local file = io.open(destdir .. "/lunadoc.css", "w")
file:write(css)
file:close()
