123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- --[[--------------------------------------------------------------------------
- -- Copyright (C) 2012 by Simon Dales --
- -- simon@purrsoft.co.uk --
- -- --
- -- This program is free software; you can redistribute it and/or modify --
- -- it under the terms of the GNU General Public License as published by --
- -- the Free Software Foundation; either version 2 of the License, or --
- -- (at your option) any later version. --
- -- --
- -- This program is distributed in the hope that it will be useful, --
- -- but WITHOUT ANY WARRANTY; without even the implied warranty of --
- -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --
- -- GNU General Public License for more details. --
- -- --
- -- You should have received a copy of the GNU General Public License --
- -- along with this program; if not, write to the --
- -- Free Software Foundation, Inc., --
- -- 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. --
- ----------------------------------------------------------------------------]]
- --[[!
- \file
- \brief a hack lua2dox converter
- ]]
- --[[!
- \mainpage
- Introduction
- ------------
- A hack lua2dox converter
- Version 0.2
- This lets us make Doxygen output some documentation to let
- us develop this code.
- It is partially cribbed from the functionality of lua2dox
- (http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm).
- Found on CPAN when looking for something else; kinda handy.
- Improved from lua2dox to make the doxygen output more friendly.
- Also it runs faster in lua rather than Perl.
- Because this Perl based system is called "lua2dox"., I have decided to add ".lua" to the name
- to keep the two separate.
- Running
- -------
- <ol>
- <li> Ensure doxygen is installed on your system and that you are familiar with its use.
- Best is to try to make and document some simple C/C++/PHP to see what it produces.
- You can experiment with the enclosed example code.
- <li> Run "doxygen -g" to create a default Doxyfile.
- Then alter it to let it recognise lua. Add the two following lines:
- \code{.bash}
- FILE_PATTERNS = *.lua
- FILTER_PATTERNS = *.lua=lua2dox_filter
- \endcode
- Either add them to the end or find the appropriate entry in Doxyfile.
- There are other lines that you might like to alter, but see futher documentation for details.
- <li> When Doxyfile is edited run "doxygen"
- The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language.
- It only has to be good enough for doxygen to see it as legal.
- Therefore our lua interpreter is fairly limited, but "good enough".
- One limitation is that each line is treated separately (except for long comments).
- The implication is that class and function declarations must be on the same line.
- Some functions can have their parameter lists extended over multiple lines to make it look neat.
- Managing this where there are also some comments is a bit more coding than I want to do at this stage,
- so it will probably not document accurately if we do do this.
- However I have put in a hack that will insert the "missing" close paren.
- The effect is that you will get the function documented, but not with the parameter list you might expect.
- </ol>
- Installation
- ------------
- Here for linux or unix-like, for any other OS you need to refer to other documentation.
- This file is "lua2dox.lua". It gets called by "lua2dox_filter"(bash).
- Somewhere in your path (e.g. "~/bin" or "/usr/local/bin") put a link to "lua2dox_filter".
- Documentation
- -------------
- Read the external documentation that should be part of this package.
- For example look for the "README" and some .PDFs.
- ]]
- -- we won't use our library code, so this becomes more portable
- -- require 'elijah_fix_require'
- -- require 'elijah_class'
- --
- --! \brief ``declare'' as class
- --!
- --! use as:
- --! \code{.lua}
- --! TWibble = class()
- --! function TWibble.init(this,Str)
- --! this.str = Str
- --! -- more stuff here
- --! end
- --! \endcode
- --!
- function class(BaseClass, ClassInitialiser)
- local newClass = {} -- a new class newClass
- if not ClassInitialiser and type(BaseClass) == 'function' then
- ClassInitialiser = BaseClass
- BaseClass = nil
- elseif type(BaseClass) == 'table' then
- -- our new class is a shallow copy of the base class!
- for i,v in pairs(BaseClass) do
- newClass[i] = v
- end
- newClass._base = BaseClass
- end
- -- the class will be the metatable for all its newInstanceects,
- -- and they will look up their methods in it.
- newClass.__index = newClass
- -- expose a constructor which can be called by <classname>(<args>)
- local classMetatable = {}
- classMetatable.__call =
- function(class_tbl, ...)
- local newInstance = {}
- setmetatable(newInstance,newClass)
- --if init then
- -- init(newInstance,...)
- if class_tbl.init then
- class_tbl.init(newInstance,...)
- else
- -- make sure that any stuff from the base class is initialized!
- if BaseClass and BaseClass.init then
- BaseClass.init(newInstance, ...)
- end
- end
- return newInstance
- end
- newClass.init = ClassInitialiser
- newClass.is_a =
- function(this, klass)
- local thisMetatable = getmetatable(this)
- while thisMetatable do
- if thisMetatable == klass then
- return true
- end
- thisMetatable = thisMetatable._base
- end
- return false
- end
- setmetatable(newClass, classMetatable)
- return newClass
- end
- -- require 'elijah_clock'
- --! \class TCore_Clock
- --! \brief a clock
- TCore_Clock = class()
- --! \brief get the current time
- function TCore_Clock.GetTimeNow()
- if os.gettimeofday then
- return os.gettimeofday()
- else
- return os.time()
- end
- end
- --! \brief constructor
- function TCore_Clock.init(this,T0)
- if T0 then
- this.t0 = T0
- else
- this.t0 = TCore_Clock.GetTimeNow()
- end
- end
- --! \brief get time string
- function TCore_Clock.getTimeStamp(this,T0)
- local t0
- if T0 then
- t0 = T0
- else
- t0 = this.t0
- end
- return os.date('%c %Z',t0)
- end
- --require 'elijah_io'
- --! \class TCore_IO
- --! \brief io to console
- --!
- --! pseudo class (no methods, just to keep documentation tidy)
- TCore_IO = class()
- --
- --! \brief write to stdout
- function TCore_IO_write(Str)
- if (Str) then
- io.write(Str)
- end
- end
- --! \brief write to stdout
- function TCore_IO_writeln(Str)
- if (Str) then
- io.write(Str)
- end
- io.write("\n")
- end
- --require 'elijah_string'
- --! \brief trims a string
- function string_trim(Str)
- return Str:match("^%s*(.-)%s*$")
- end
- --! \brief split a string
- --!
- --! \param Str
- --! \param Pattern
- --! \returns table of string fragments
- function string_split(Str, Pattern)
- local splitStr = {}
- local fpat = "(.-)" .. Pattern
- local last_end = 1
- local str, e, cap = string.find(Str,fpat, 1)
- while str do
- if str ~= 1 or cap ~= "" then
- table.insert(splitStr,cap)
- end
- last_end = e+1
- str, e, cap = string.find(Str,fpat, last_end)
- end
- if last_end <= #Str then
- cap = string.sub(Str,last_end)
- table.insert(splitStr, cap)
- end
- return splitStr
- end
- --require 'elijah_commandline'
- --! \class TCore_Commandline
- --! \brief reads/parses commandline
- TCore_Commandline = class()
- --! \brief constructor
- function TCore_Commandline.init(this)
- this.argv = arg
- this.parsed = {}
- this.params = {}
- end
- --! \brief get value
- function TCore_Commandline.getRaw(this,Key,Default)
- local val = this.argv[Key]
- if not val then
- val = Default
- end
- return val
- end
- --require 'elijah_debug'
- -------------------------------
- --! \brief file buffer
- --!
- --! an input file buffer
- TStream_Read = class()
- --! \brief get contents of file
- --!
- --! \param Filename name of file to read (or nil == stdin)
- function TStream_Read.getContents(this,Filename)
- -- get lines from file
- local filecontents
- if Filename then
- -- syphon lines to our table
- --TCore_Debug_show_var('Filename',Filename)
- filecontents={}
- for line in io.lines(Filename) do
- table.insert(filecontents,line)
- end
- else
- -- get stuff from stdin as a long string (with crlfs etc)
- filecontents=io.read('*a')
- -- make it a table of lines
- filecontents = TString_split(filecontents,'[\n]') -- note this only works for unix files.
- Filename = 'stdin'
- end
- if filecontents then
- this.filecontents = filecontents
- this.contentsLen = #filecontents
- this.currentLineNo = 1
- end
- return filecontents
- end
- --! \brief get lineno
- function TStream_Read.getLineNo(this)
- return this.currentLineNo
- end
- --! \brief get a line
- function TStream_Read.getLine(this)
- local line
- if this.currentLine then
- line = this.currentLine
- this.currentLine = nil
- else
- -- get line
- if this.currentLineNo<=this.contentsLen then
- line = this.filecontents[this.currentLineNo]
- this.currentLineNo = this.currentLineNo + 1
- else
- line = ''
- end
- end
- return line
- end
- --! \brief save line fragment
- function TStream_Read.ungetLine(this,LineFrag)
- this.currentLine = LineFrag
- end
- --! \brief is it eof?
- function TStream_Read.eof(this)
- if this.currentLine or this.currentLineNo<=this.contentsLen then
- return false
- end
- return true
- end
- --! \brief output stream
- TStream_Write = class()
- --! \brief constructor
- function TStream_Write.init(this)
- this.tailLine = {}
- end
- --! \brief write immediately
- function TStream_Write.write(this,Str)
- TCore_IO_write(Str)
- end
- --! \brief write immediately
- function TStream_Write.writeln(this,Str)
- TCore_IO_writeln(Str)
- end
- --! \brief write immediately
- function TStream_Write.writelnComment(this,Str)
- TCore_IO_write('// ZZ: ')
- TCore_IO_writeln(Str)
- end
- --! \brief write to tail
- function TStream_Write.writelnTail(this,Line)
- if not Line then
- Line = ''
- end
- table.insert(this.tailLine,Line)
- end
- --! \brief outout tail lines
- function TStream_Write.write_tailLines(this)
- for k,line in ipairs(this.tailLine) do
- TCore_IO_writeln(line)
- end
- TCore_IO_write('// Lua2DoX new eof')
- end
- --! \brief input filter
- TLua2DoX_filter = class()
- --! \brief allow us to do errormessages
- function TLua2DoX_filter.warning(this,Line,LineNo,Legend)
- this.outStream:writelnTail(
- '//! \todo warning! ' .. Legend .. ' (@' .. LineNo .. ')"' .. Line .. '"'
- )
- end
- --! \brief trim comment off end of string
- --!
- --! If the string has a comment on the end, this trims it off.
- --!
- local function TString_removeCommentFromLine(Line)
- local pos_comment = string.find(Line,'%-%-')
- local tailComment
- if pos_comment then
- Line = string.sub(Line,1,pos_comment-1)
- tailComment = string.sub(Line,pos_comment)
- end
- return Line,tailComment
- end
- --! \brief get directive from magic
- local function getMagicDirective(Line)
- local macro,tail
- local macroStr = '[\\@]'
- local pos_macro = string.find(Line,macroStr)
- if pos_macro then
- --! ....\\ macro...stuff
- --! ....\@ macro...stuff
- local line = string.sub(Line,pos_macro+1)
- local space = string.find(line,'%s+')
- if space then
- macro = string.sub(line,1,space-1)
- tail = string_trim(string.sub(line,space+1))
- else
- macro = line
- tail = ''
- end
- end
- return macro,tail
- end
- --! \brief check comment for fn
- local function checkComment4fn(Fn_magic,MagicLines)
- local fn_magic = Fn_magic
- -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
- local magicLines = string_split(MagicLines,'\n')
- local macro,tail
- for k,line in ipairs(magicLines) do
- macro,tail = getMagicDirective(line)
- if macro == 'fn' then
- fn_magic = tail
- -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
- else
- --TCore_IO_writeln('// not found fn "' .. line .. '"')
- end
- end
- return fn_magic
- end
- --! \brief run the filter
- function TLua2DoX_filter.readfile(this,AppStamp,Filename)
- local err
- local inStream = TStream_Read()
- local outStream = TStream_Write()
- this.outStream = outStream -- save to this obj
- if (inStream:getContents(Filename)) then
- -- output the file
- local line
- local fn_magic -- function name/def from magic comment
- outStream:writelnTail('// #######################')
- outStream:writelnTail('// app run:' .. AppStamp)
- outStream:writelnTail('// #######################')
- outStream:writelnTail()
- local state = ''
- while not (err or inStream:eof()) do
- line = string_trim(inStream:getLine())
- -- TCore_Debug_show_var('inStream',inStream)
- -- TCore_Debug_show_var('line',line )
- if string.sub(line,1,2)=='--' then -- it's a comment
- if string.sub(line,3,3)=='@' then -- it's a magic comment
- state = 'in_magic_comment'
- local magic = string.sub(line,4)
- outStream:writeln('/// @' .. magic)
- fn_magic = checkComment4fn(fn_magic,magic)
- elseif string.sub(line,3,3)=='-' then -- it's a nonmagic doc comment
- local comment = string.sub(line,4)
- outStream:writeln('/// '.. comment)
- elseif string.sub(line,3,4)=='[[' then -- it's a long comment
- line = string.sub(line,5) -- nibble head
- local comment = ''
- local closeSquare,hitend,thisComment
- while (not err) and (not hitend) and (not inStream:eof()) do
- closeSquare = string.find(line,']]')
- if not closeSquare then -- need to look on another line
- thisComment = line .. '\n'
- line = inStream:getLine()
- else
- thisComment = string.sub(line,1,closeSquare-1)
- hitend = true
- -- unget the tail of the line
- -- in most cases it's empty. This may make us less efficient but
- -- easier to program
- inStream:ungetLine(string_trim(string.sub(line,closeSquare+2)))
- end
- comment = comment .. thisComment
- end
- if string.sub(comment,1,1)=='@' then -- it's a long magic comment
- outStream:write('/*' .. comment .. '*/ ')
- fn_magic = checkComment4fn(fn_magic,comment)
- else -- discard
- outStream:write('/* zz:' .. comment .. '*/ ')
- fn_magic = nil
- end
- -- TODO(justinmk): Uncomment this if we want "--" lines to continue the
- -- preceding magic ("---", "--@", …) lines.
- -- elseif state == 'in_magic_comment' then -- next line of magic comment
- -- outStream:writeln('/// '.. line:sub(3))
- else -- discard
- outStream:writeln('// zz:"' .. line .. '"')
- fn_magic = nil
- end
- elseif string.find(line,'^function') or string.find(line,'^local%s+function') then
- state = 'in_function' -- it's a function
- local pos_fn = string.find(line,'function')
- -- function
- -- ....v...
- if pos_fn then
- -- we've got a function
- local fn_type
- if string.find(line,'^local%s+') then
- fn_type = ''--'static ' -- static functions seem to be excluded
- else
- fn_type = ''
- end
- local fn = TString_removeCommentFromLine(string_trim(string.sub(line,pos_fn+8)))
- if fn_magic then
- fn = fn_magic
- fn_magic = nil
- end
- if string.sub(fn,1,1)=='(' then
- -- it's an anonymous function
- outStream:writelnComment(line)
- else
- -- fn has a name, so is interesting
- -- want to fix for iffy declarations
- local open_paren = string.find(fn,'[%({]')
- local fn0 = fn
- if open_paren then
- fn0 = string.sub(fn,1,open_paren-1)
- -- we might have a missing close paren
- if not string.find(fn,'%)') then
- fn = fn .. ' ___MissingCloseParenHere___)'
- end
- end
- local dot = string.find(fn0,'[%.:]')
- if dot then -- it's a method
- local klass = string.sub(fn,1,dot-1)
- local method = string.sub(fn,dot+1)
- --TCore_IO_writeln('function ' .. klass .. '::' .. method .. ftail .. '{}')
- --TCore_IO_writeln(klass .. '::' .. method .. ftail .. '{}')
- outStream:writeln(
- '/*! \\memberof ' .. klass .. ' */ '
- .. method .. '{}'
- )
- else
- -- add vanilla function
- outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
- end
- end
- else
- this:warning(inStream:getLineNo(),'something weird here')
- end
- fn_magic = nil -- mustn't indavertently use it again
- elseif string.find(line,'=%s*class%(') then
- state = 'in_class' -- it's a class declaration
- local tailComment
- line,tailComment = TString_removeCommentFromLine(line)
- local equals = string.find(line,'=')
- local klass = string_trim(string.sub(line,1,equals-1))
- local tail = string_trim(string.sub(line,equals+1))
- -- class(wibble wibble)
- -- ....v.
- local parent = string.sub(tail,7,-2)
- if #parent>0 then
- parent = ' :public ' .. parent
- end
- outStream:writeln('class ' .. klass .. parent .. '{};')
- else
- state = '' -- unknown
- if #line>0 then -- we don't know what this line means, so just comment it out
- outStream:writeln('// zz: ' .. line)
- else
- outStream:writeln() -- keep this line blank
- end
- end
- end
- -- output the tail
- outStream:write_tailLines()
- else
- outStream:writeln('!empty file')
- end
- end
- --! \brief this application
- TApp = class()
- --! \brief constructor
- function TApp.init(this)
- local t0 = TCore_Clock()
- this.timestamp = t0:getTimeStamp()
- this.name = 'Lua2DoX'
- this.version = '0.2 20130128'
- this.copyright = 'Copyright (c) Simon Dales 2012-13'
- end
- function TApp.getRunStamp(this)
- return this.name .. ' (' .. this.version .. ') '
- .. this.timestamp
- end
- function TApp.getVersion(this)
- return this.name .. ' (' .. this.version .. ') '
- end
- function TApp.getCopyright(this)
- return this.copyright
- end
- local This_app = TApp()
- --main
- local cl = TCore_Commandline()
- local argv1 = cl:getRaw(2)
- if argv1 == '--help' then
- TCore_IO_writeln(This_app:getVersion())
- TCore_IO_writeln(This_app:getCopyright())
- TCore_IO_writeln([[
- run as:
- lua2dox_filter <param>
- --------------
- Param:
- <filename> : interprets filename
- --version : show version/copyright info
- --help : this help text]])
- elseif argv1 == '--version' then
- TCore_IO_writeln(This_app:getVersion())
- TCore_IO_writeln(This_app:getCopyright())
- else
- -- it's a filter
- local appStamp = This_app:getRunStamp()
- local filename = argv1
- local filter = TLua2DoX_filter()
- filter:readfile(appStamp,filename)
- end
- --eof
|