--[[-------------------------------------------------------------------------- -- 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 A hack lua2dox converter Version 0.1 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. 0. 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. 1. Run "lua2dox_lua -g" to create a default Doxyfile. Then alter it to let it recognise lua. Add the two following lines: FILE_PATTERNS = *.lua FILTER_PATTERNS = *.lua=lua2dox_filter Either add them to the end or find the appropriate entry in Doxyfile. 2. When Doxyfile is edited run as "lua2dox_lua" When reading source with classes multiple passes are needed. Each pass generates a list of member functions (as a file) that were found on this pass. This list is read in on the next pass. If the class+methods haven't changed this time then you only need to run it once, else run twice. 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. 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_lua". Somewhere in your path (e.g. "~/bin" or "/usr/local/bin") put two links to "lua2dox_lua". Names to use are "lua2dox_lua" and "lua2dox_filter". Call it as "lua2dox_lua" and the filter that gets called by doxygen is "lua2dox_filter". ]] -- we won't use our library code, so this becomes more portable local TConfig_config={ ['LUA2DOX_COMMENTARY_FILE_IN']='./killme_lua2dox.methods' ,['LUA2DOX_COMMENTARY_FILE_OUT']='./killme_lua2dox.methods.out' } --! \brief Gets a config val local function TConfig_get(Key,Default) local val = TConfig_config[Key] if not val then val = Default end return val end --! \brief sets a config val local function TConfig_set(Key,Val) TConfig_config[Key] = Val end --! \brief write to stdout --! --! writes Str (if not nil) local function TIO_write(Str) if (Str) then io.write(Str) end end --! \brief write to stdout --! --! writelns Str (if not nil) and then an eoln. local function TIO_writeln(Str) if (Str) then io.write(Str) end io.write("\n") end --! \brief show error to stdout --! --! writelns Str (if not nil) and then an eoln. local function TIO_showError(Err,Str) local err = Err if not err then err = 1 end TIO_write('Error (' .. err .. '):') TIO_writeln(Str) return err end --! \brief run system command local function TOS_system(Cmd) local errno,str local rtn = os.execute(Cmd) if not (rtn==0) then errno = rtn str = 'an error occured' end return errno,str end --! \brief does file exist? local function TOS_fileExists(Filename) local fh = io.open(Filename,'r') if fh~=nil then fh:close() return true end return false end --! \brief get the current time local function TClock_GetTimeNow() if os.gettimeofday then return os.gettimeofday() else return os.time() end end --! \brief get a timestamp --! --! not strictly necessary here but lets us put a timestamp on the end of the output stream. --! Note that doxygen won't read this, and being off the end of the true file length (num lines), --! it will have no effect. --! However it lets us check the output file tail when debugging. --! local function TClock_getTimeStamp() local now = TClock_GetTimeNow() local fraction_secs = now - math.floor(now) return os.date('%c %Z',now) .. ':' .. fraction_secs end --! \brief trims a string from both ends local function TString_trim(Str) return Str:match("^%s*(.-)%s*$") end --! \brief split a string --! --! \param Str --! \param Pattern --! \returns table of string fragments local function TString_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 --! \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,'%-%-') if pos_comment then Line = string.sub(Line,1,pos_comment-1) end return Line end local TClassList_methods = {} --! \brief get methods local function TClassList_method_get(Klass) return TClassList_methods[Klass] end --! \brief add a method to list of known methods local function TClassList_method_add(Klass,Method) local classRec = TClassList_methods[Klass] if not classRec then TClassList_methods[Klass] = {} classRec = TClassList_methods[Klass] end table.insert(classRec,Method) end local TCommentary_fh --! \brief write to output file local function TCommentary_writeln(Str) if TCommentary_fh then TCommentary_fh:write(Str .. '\n') end end local TCommentary_fileID --! \brief open the commentary save file local function TCommentary_open(Filename,InputLuaFilename) TCommentary_fileID = '"' .. InputLuaFilename .. '" : ' .. TClock_getTimeStamp() TCommentary_fh = io.open(Filename,'a+') if TCommentary_fh then TCommentary_writeln('// opened:' .. TCommentary_fileID) end end --! \brief close the methods save file local function TCommentary_close() if TCommentary_fh then TCommentary_writeln('// closed: ' .. TCommentary_fileID) TCommentary_fh:close() TCommentary_fh = nil end end --! \brief read stuff from save file local function TCommentary_readFileContents(Filename) if TOS_fileExists(Filename) then local klass,method,dot local cmd,colon local k,v,equals for line in io.lines(Filename) do if string.sub(line,1,2)=='//' then -- it's a comment else colon = string.find(line,':') if colon then cmd = string.sub(line,1,colon) line = string.sub(line,colon+1) else cmd = nil end if (cmd == 'method:') then dot = string.find(line,'%.') klass = string.sub(line,1,dot-1) method = string.sub(line,dot+1) TClassList_method_add(klass,method) elseif(cmd == 'set:') then equals = string.find(line,'=') if equals then k = string.sub(line,1,equals-1) v = string.sub(line,equals+1) TConfig_set(k,v) else TConfig_set(line,true) end else -- ignore TIO_write('/* bad command:"' .. line .. '" */') end end end else TIO_write('/* file "' .. Filename .. '" don\'t exist */') end end --! \brief method to save file local function TCommentary_addMethod(Klass,Method) if TCommentary_fh then if Klass and Method then if string.find(Klass,'%.') or string.find(Method,'%.') then -- iffy, so we discard it else TCommentary_writeln('method:' .. Klass .. '.' .. Method) end end end end --! \brief output line to stream --! --! Wraps IO_writeln() local function TIO_out2stream(Line) TIO_writeln(Line) end --! \brief suppress line local function TIO_out2stream_commented(Line,Prefix,Suffix) local line = Line if (not Prefix) then Prefix='' end line = Prefix .. ':' .. line if (Suffix) then line = line .. Suffix end TIO_out2stream('// D' .. line) end TCommandline_argv = {} --! \brief setup/parse the commandline local function TCommandline_setup() local argv1 =arg[1] if not argv1 then argv1 = 'base' end TCommandline_argv_appname = argv1 local i=2 local argvi=1 while (argvi) do argvi = arg[i] if argvi then TCommandline_argv[i-1] = argvi i = i + 1 end end end --! setup the commandline now TCommandline_setup() --! \brief get commandline args local function TCommandline_getargv() return TCommandline_argv end --! \brief get appname local function TApp_get_appname() return TCommandline_argv_appname end --! \brief hack converter from lua to a pseudoC-ish language for doxygen --! --! This is a hack to make lua readable to doxygen. --! --! It works well enough to document functions/methods and classes, but not assignments. --! Our pseudo-C gets confused if we allow assginments to be shown. --! Because these are less interesting than class/functions/methods I have decided to --! live with this limitation. --! local function TApp_lua2dox(FileContents) local err local lines = FileContents local maxi=#lines local i = 1 local line,head while not err and (i<=maxi) do line = TString_trim(lines[i]) if #line==0 then TIO_out2stream() elseif string.sub(line,1,2)=='--' then -- it's a comment of some kind if string.sub(line,1,3)=='--!' then -- it's a magic comment TIO_out2stream('//!' .. string.sub(line,4)) elseif string.sub(line,1,4)=='--[[' then -- it's a multiline comment -- read lines to end of comment local hitend local comment = '' line = string.sub(line,5) --.. sep while not err and (i0) then line = line .. ' :public ' .. parent end -- need methods list local methods = TClassList_method_get(klass) local methods_str if methods then methods_str = 'public: ' for k,v in pairs(methods) do methods_str = methods_str .. v .. ';' end else methods_str = '/* no methods reported */' end line = line .. '{' .. methods_str .. '}' TIO_out2stream(line .. ';',true) else -- we don't know what this line means, so we can probably just comment it out TIO_out2stream_commented(line) end i = i + 1 end return err end --! \brief run the filter --! --! \param AppTimestamp application + timestamp for this run --! \param Filename the filename or if nil stdin --! \param CommentaryFiles names of commentary files --! \return err or nil --! local function TApp_run_filter(AppTimestamp,Filename,CommentaryFiles) local err local filecontents if Filename then -- syphon lines to our table 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 TCommentary_readFileContents(CommentaryFiles.infile) TCommentary_open(CommentaryFiles.outfile,Filename) err = TApp_lua2dox(filecontents) TCommentary_close() TIO_writeln('// done (' .. AppTimestamp .. ')') else err = TIO_showError(1,'couldn\'t find any file contents') end return err end --! \brief run doxygen for one --! --! \param AppTimestamp application + timestamp for this run --! \param Argv commandline for this run --! \param CommentaryFiles names of commentary files --! \return err or nil --! local function TApp_run_doxygen(AppTimestamp,Argv,CommentaryFiles) local err TIO_writeln('running: ' .. AppTimestamp) local argv1 = Argv[1] if argv1=='--help' then local appname = TApp_get_appname() TIO_writeln('Syntax:') TIO_writeln(' ' .. appname .. ' [[-g] [-s]] []|--help') TIO_writeln(' --help show this text') TIO_writeln(' -g: generate new Doxyfile') TIO_writeln(' -s: generate new Doxyfile without comments') TIO_writeln(' : name of Doxyfile') TIO_writeln() TIO_writeln(' For help on doxygen run its help system directly') --! \todo more help here else local cl = 'doxygen' for i,argv_i in ipairs(Argv) do if i>=1 then -- don't want to use this app's name cl = cl .. ' ' .. argv_i end end TIO_writeln('about to run "' .. cl .. '"') err = TOS_system(cl) if not err then -- cycle commentary files local newComments=CommentaryFiles.outfile local nextRunsComments=CommentaryFiles.infile if TOS_fileExists(newComments) then TIO_writeln('found outfile "' .. newComments .. '"') os.remove(nextRunsComments) TIO_writeln('mv "' .. newComments .. '"-> ' .. nextRunsComments .. '"') os.rename(newComments,nextRunsComments) end end end return err end -- main local timestamp = TClock_getTimeStamp() local appname = TApp_get_appname() local version = '0.1 20120704' local appTimestamp = appname .. '(v' .. version .. ') :' .. timestamp local commentary_files = { infile=TConfig_get('LUA2DOX_COMMENTARY_FILE_IN') ,outfile=TConfig_get('LUA2DOX_COMMENTARY_FILE_OUT') } if appname == 'lua2dox_filter' then err = TApp_run_filter(appTimestamp,TCommandline_getargv()[1],commentary_files) TIO_writeln('// do filter') else err = TApp_run_doxygen(appTimestamp,TCommandline_getargv(),commentary_files) end --eof