1 --[[--------------------------------------------------------------------------
 
    2  --   Copyright (C) 2012 by Simon Dales   --
 
    3  --   simon@purrsoft.co.uk   --
 
    5  --   This program is free software; you can redistribute it and/or modify  --
 
    6  --   it under the terms of the GNU General Public License as published by  --
 
    7  --   the Free Software Foundation; either version 2 of the License, or     --
 
    8  --   (at your option) any later version.                                   --
 
   10  --   This program is distributed in the hope that it will be useful,       --
 
   11  --   but WITHOUT ANY WARRANTY; without even the implied warranty of        --
 
   12  --   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         --
 
   13  --   GNU General Public License 
for more details.                          --
 
   15  --   You should have received a copy of the GNU General Public License     --
 
   16  --   along with 
this program; 
if not, 
write to the                         --
 
   17  --   Free Software Foundation, Inc.,                                       --
 
   18  --   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             --
 
   19 ----------------------------------------------------------------------------]]
 
   22   \brief a hack lua2dox converter
 
   31   A hack lua2dox converter
 
   34   This lets us make Doxygen output some documentation to let
 
   37   It is partially cribbed from the functionality of lua2dox
 
   39   Found on CPAN when looking 
for something 
else; kinda handy.
 
   41   Improved from lua2dox to make the doxygen output more friendly.
 
   42   Also it runs faster in lua rather than Perl.
 
   44   Because 
this Perl based system is called 
"lua2dox"., I have decided to add 
".lua" to the name
 
   45   to keep the two separate.
 
   51   <li>  Ensure doxygen is installed on your system and that you are familiar with its use.
 
   52     Best is to 
try to make and document some simple C/C++/PHP to see what it produces.
 
   53     You can experiment with the enclosed example code.
 
   55   <li> Run 
"doxygen -g" to create a 
default Doxyfile.
 
   57     Then alter it to let it recognise lua. Add the two following lines:
 
   62   FILTER_PATTERNS = *.lua=lua2dox_filter
 
   66     Either add them to the end or find the appropriate entry in Doxyfile.
 
   68     There are other lines that you might like to alter, but see futher documentation 
for details.
 
   70   <li> When Doxyfile is edited run 
"doxygen" 
   72     The core 
function reads the input file (filename or stdin) and outputs some pseudo C-ish language.
 
   73     It only has to be good enough 
for doxygen to see it as legal.
 
   74     Therefore our lua interpreter is fairly limited, but 
"good enough".
 
   76     One limitation is that each line is treated separately (except 
for long comments).
 
   77     The implication is that 
class and function declarations must be on the same line.
 
   78     Some functions can have their parameter lists extended over multiple lines to make it look neat.
 
   79     Managing this where there are also some comments is a bit more coding than I want to do at this stage,
 
   80     so it will probably not document accurately if we do do this.
 
   82     However I have put in a hack that will insert the "missing" close paren.
 
   83     The effect is that you will get the function documented, but not with the parameter list you might expect.
 
   89   Here for linux or unix-like, for any other OS you need to refer to other documentation.
 
   91   This file is "lua2dox.lua". It gets called by "lua2dox_filter"(bash).
 
   92   Somewhere in your path (e.g. "~/bin" or "/usr/local/bin") put a link to "lua2dox_filter".
 
   97   Read the external documentation that should be part of this package.
 
   98   For example look for the "README" and some .PDFs.
 
  102 -- we won't use our library code, so this becomes more portable
 
  104 -- require 'elijah_fix_require'
 
  105 -- require 'elijah_class'
 
  107 --! \brief ``declare'' as 
class 
  111 --! TWibble = 
class()
 
  112 --! 
function TWibble.init(
this,Str)
 
  114 --!   -- more stuff here
 
  118 function class(BaseClass, ClassInitialiser)
 
  119   local newClass = {}    -- a 
new class newClass
 
  120   if not ClassInitialiser and type(BaseClass) == 
'function' then
 
  121     ClassInitialiser = BaseClass
 
  123   elseif type(BaseClass) == 
'table' then
 
  124     -- our 
new class is a shallow copy of the base 
class!
 
  125     for i,v in pairs(BaseClass) do
 
  128     newClass._base = BaseClass
 
  130   -- the 
class will be the metatable for all its newInstanceects,
 
  131   -- and they will look up their methods in it.
 
  132   newClass.__index = newClass
 
  134   -- expose a constructor which can be called by <classname>(<args>)
 
  135   local classMetatable = {}
 
  136   classMetatable.__call = 
 
  137   function(class_tbl, ...)
 
  138     local newInstance = {}
 
  139     setmetatable(newInstance,newClass)
 
  141     --  
init(newInstance,...)
 
  142     if class_tbl.
init then
 
  143       class_tbl.
init(newInstance,...)
 
  145       -- make sure that any stuff from the base 
class is initialized!
 
  146       if BaseClass and BaseClass.
init then
 
  147         BaseClass.
init(newInstance, ...)
 
  152   newClass.
init = ClassInitialiser
 
  154   function(this, klass)
 
  155     local thisMetatable = getmetatable(this)
 
  156     while thisMetatable do 
 
  157       if thisMetatable == klass then
 
  160       thisMetatable = thisMetatable._base
 
  164   setmetatable(newClass, classMetatable)
 
  168 -- require 'elijah_clock'
 
  174 --! \brief get the current time
 
  176   if os.gettimeofday then
 
  177     return os.gettimeofday()
 
  183 --! \brief constructor
 
  192 --! \brief get time 
string 
  200   return os.date('%c %Z',t0)
 
  204 --require 'elijah_io'
 
  207 --! \brief io to console
 
  209 --! pseudo 
class (no methods, just to keep documentation tidy)
 
  212 --! \brief 
write to stdout
 
  219 --! \brief 
write to stdout
 
  228 --require 'elijah_string'
 
  230 --! \brief trims a 
string 
  232   return Str:match("^%s*(.-)%s*$")
 
  235 --! \brief split a 
string 
  239 --! \returns table of 
string fragments
 
  242    local fpat = 
"(.-)" .. Pattern
 
  244    local str, e, cap = 
string.find(Str,fpat, 1)
 
  246       if str ~= 1 or cap ~= 
"" then
 
  247          table.insert(splitStr,cap)
 
  250       str, e, cap = 
string.find(Str,fpat, last_end)
 
  252    if last_end <= #Str then
 
  253       cap = 
string.sub(Str,last_end)
 
  254       table.insert(splitStr, cap)
 
  260 --require 
'elijah_commandline' 
  263 --! \brief reads/parses commandline
 
  266 --! \brief constructor
 
  275   local val = this.argv[Key]
 
  283 --require 
'elijah_debug' 
  285 -------------------------------
 
  286 --! \brief file buffer
 
  288 --! an input file buffer
 
  291 --! \brief 
get contents of file
 
  293 --! \param Filename name of file to read (or nil == stdin)
 
  295   -- 
get lines from file
 
  298     -- syphon lines to our table
 
  299     --TCore_Debug_show_var(
'Filename',Filename)
 
  301     for line in io.lines(Filename) 
do 
  302       table.insert(filecontents,line)
 
  305     -- 
get stuff from stdin as a 
long string (with crlfs etc)
 
  306     filecontents=io.read(
'*a')
 
  307     --  make it a table of lines
 
  308     filecontents = TString_split(filecontents,
'[\n]') -- note 
this only works 
for unix files.
 
  313     this.filecontents = filecontents
 
  314     this.contentsLen = #filecontents
 
  315     this.currentLineNo = 1
 
  321 --! \brief 
get lineno
 
  323   return this.currentLineNo
 
  326 --! \brief 
get a line
 
  329   if this.currentLine then
 
  330     line = this.currentLine
 
  331     this.currentLine = nil
 
  334     if this.currentLineNo<=this.contentsLen then
 
  335       line = this.filecontents[this.currentLineNo]
 
  336       this.currentLineNo = this.currentLineNo + 1
 
  344 --! \brief save line fragment
 
  346   this.currentLine = LineFrag
 
  349 --! \brief is it 
eof?
 
  351   if this.currentLine or this.currentLineNo<=this.contentsLen then
 
  357 --! \brief output stream
 
  360 --! \brief constructor
 
  365 --! \brief 
write immediately
 
  370 --! \brief 
write immediately
 
  375 --! \brief 
write immediately
 
  381 --! \brief 
write to tail
 
  386   table.insert(this.tailLine,Line)
 
  389 --! \brief outout tail lines
 
  391   for k,line in ipairs(this.tailLine) do
 
  397 --! \brief input filter
 
  400 --! \brief allow us to do errormessages
 
  407 --! \brief trim comment off end of 
string 
  409 --! If the 
string has a comment on the end, this trims it off.
 
  412   local pos_comment = 
string.find(Line,'%-%-')
 
  415     Line = 
string.sub(Line,1,pos_comment-1)
 
  416     tailComment = 
string.sub(Line,pos_comment)
 
  418   return Line,tailComment
 
  421 --! \brief get directive from magic
 
  424   local macroStr = '[\\\@]'
 
  425   local pos_macro = 
string.find(Line,macroStr)
 
  427     --! ....\\ macro...stuff
 
  428     --! ....\@ macro...stuff
 
  429     local line = 
string.sub(Line,pos_macro+1)
 
  430     local space = 
string.find(line,'%s+')
 
  432       macro = 
string.sub(line,1,space-1)
 
  442 --! \brief check comment for fn
 
  444   local fn_magic = Fn_magic
 
  451   for k,line in ipairs(magicLines) do
 
  453     if macro == 'fn' then
 
  463 --! \brief run the filter
 
  469   this.outStream = outStream -- save to this obj
 
  474     local fn_magic -- function name/def from  magic comment
 
  481     while not (err or inStream:
eof()) do
 
  483 --      TCore_Debug_show_var('inStream',inStream)
 
  484 --      TCore_Debug_show_var('line',line )
 
  485       if 
string.sub(line,1,2)=='--' then -- its a comment
 
  486         if 
string.sub(line,3,3)=='!' then -- it's a magic comment
 
  487           local magic = 
string.sub(line,4)
 
  490         elseif 
string.sub(line,3,4)=='[[' then -- it's a 
long comment
 
  491           line = 
string.sub(line,5) -- nibble head
 
  493           local closeSquare,hitend,thisComment
 
  494           while (not err) and (not hitend) and (not inStream:
eof()) do
 
  495             closeSquare = 
string.find(line,']]')
 
  496             if not closeSquare then -- need to look on another line
 
  497               thisComment = line .. '\n'
 
  500               thisComment = 
string.sub(line,1,closeSquare-1)
 
  503               -- unget the tail of the line
 
  504               -- in most cases it's empty. This may make us less efficient but
 
  508             comment = comment .. thisComment
 
  510           if 
string.sub(comment,1,1)=='!' then -- it's a 
long magic comment
 
  521       elseif 
string.find(line,'^function') or 
string.find(line,'^local%s+function') then
 
  523         local pos_fn = 
string.find(line,'function')
 
  527           -- we've got a function
 
  529           if 
string.find(line,'^local%s+') then
 
  540           if 
string.sub(fn,1,1)=='(' then
 
  541             -- it's an anonymous function
 
  544             -- fn has a name, so is interesting
 
  546             -- want to fix for iffy declarations
 
  547             local open_paren = 
string.find(fn,'[%({]
') 
  550               fn0 = string.sub(fn,1,open_paren-1) 
  551               -- we might have a missing close paren 
  552               if not string.find(fn,'%)
') then 
  553                 fn = fn .. ' ___MissingCloseParenHere___)
' 
  557             local dot = string.find(fn0,'[%.:]
') 
  558             if dot then -- it's a method
 
  559               local klass = 
string.sub(fn,1,dot-1)
 
  560               local method = 
string.sub(fn,dot+1)
 
  564                 '/*! \\memberof ' .. klass .. 
' */ ' 
  568               -- add vanilla 
function 
  570               outStream:
writeln(fn_type .. 
'function ' .. fn .. 
'{}')
 
  576         fn_magic = nil -- mustn
't indavertently use it again 
  577       elseif string.find(line,'=%s*
class%(
') then 
  578         -- it's a 
class declaration
 
  581         local equals = 
string.find(line,
'=')
 
  582         local klass = 
string_trim(
string.sub(line,1,equals-1))
 
  583         local tail =  
string_trim(
string.sub(line,equals+1))
 
  584         -- 
class(wibble wibble)
 
  586         local parent = 
string.sub(tail,7,-2)
 
  588           parent = 
' :public ' .. parent
 
  590         outStream:
writeln(
'class ' .. klass .. parent .. 
'{};')
 
  592         -- we don
't know what this line means, so we can probably just comment it out 
  596           outStream:
writeln() -- keep 
this line blank
 
  604     outStream:
writeln(
'!empty file')
 
  608 --! \brief 
this application
 
  611 --! \brief constructor
 
  615   this.name = 'Lua2DoX'
 
  616   this.version = '0.2 20130128'
 
  617   this.copyright = 'Copyright (c) Simon Dales 2012-13'
 
  621   return this.name .. ' (' .. this.version .. ') ' 
 
  626   return this.name .. ' (' .. this.version .. ') ' 
 
  630   return this.copyright 
 
  633 local This_app = 
TApp()
 
  638 local argv1 = cl:
getRaw(2)
 
  639 if argv1 == '--help' then
 
  644 lua2dox_filter <param>
 
  647   <filename> : interprets filename
 
  648   --version  : show version/copyright info
 
  649   --help     : this help text]])
 
  650 elseif argv1 == '--version' then
 
  656   local filename = argv1