lua2dox 0.2 20130128

- Main Page
- Classes
- Files

- File List
- File Members

lua2dox.lua
Go to the documentation of this file.
1 --[[--------------------------------------------------------------------------
2 -- Copyright (C) 2012 by Simon Dales --
3 -- simon@purrsoft.co.uk --
4 -- --
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. --
9 -- --
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. --
14 -- --
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 ----------------------------------------------------------------------------]]
20 --[[!
21 \file
22 \brief a hack lua2dox converter
23 ]]
24
25 --[[!
26 \mainpage
27
28 Introduction
29 ------------
30
31 A hack lua2dox converter
32 Version 0.2
33
34 This lets us make Doxygen output some documentation to let
35 us develop this code.
36
37 It is partially cribbed from the functionality of lua2dox
38 (http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm).
39 Found on CPAN when looking for something else; kinda handy.
40
41 Improved from lua2dox to make the doxygen output more friendly.
42 Also it runs faster in lua rather than Perl.
43
44 Because this Perl based system is called "lua2dox"., I have decided to add ".lua" to the name
45 to keep the two separate.
46
47 Running
48 -------
49
50 <ol>
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.
54
55 <li> Run "doxygen -g" to create a default Doxyfile.
56
57 Then alter it to let it recognise lua. Add the two following lines:
58
59 \code{.bash}
60 FILE_PATTERNS = *.lua
61
62 FILTER_PATTERNS = *.lua=lua2dox_filter
63 \endcode
64
65
66 Either add them to the end or find the appropriate entry in Doxyfile.
67
68 There are other lines that you might like to alter, but see futher documentation for details.
69
70 <li> When Doxyfile is edited run "doxygen"
71
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".
75
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.
81
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.
84 </ol>
85
86 Installation
87 ------------
88
89 Here for linux or unix-like, for any other OS you need to refer to other documentation.
90
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".
93
94 Documentation
95 -------------
96
97 Read the external documentation that should be part of this package.
98 For example look for the "README" and some .PDFs.
99
100 ]]
101
102 -- we won't use our library code, so this becomes more portable
103
104 -- require 'elijah_fix_require'
105 -- require 'elijah_class'
106 --
107 --! \brief ``declare'' as class
108 --!
109 --! use as:
110 --! \code{.lua}
111 --! TWibble = class()
112 --! function TWibble.init(this,Str)
113 --! this.str = Str
114 --! -- more stuff here
115 --! end
116 --! \endcode
117 --!
118 function class(BaseClass, ClassInitialiser)
119 local newClass = {} -- a new class newClass
120 if not ClassInitialiser and type(BaseClass) == 'function' then
121 ClassInitialiser = BaseClass
122 BaseClass = nil
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
126 newClass[i] = v
127 end
128 newClass._base = BaseClass
129 end
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
133
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)
140 --if init then
141 -- init(newInstance,...)
142 if class_tbl.init then
143 class_tbl.init(newInstance,...)
144 else
145 -- make sure that any stuff from the base class is initialized!
146 if BaseClass and BaseClass.init then
147 BaseClass.init(newInstance, ...)
148 end
149 end
150 return newInstance
151 end
152 newClass.init = ClassInitialiser
153 newClass.is_a =
154 function(this, klass)
155 local thisMetatable = getmetatable(this)
156 while thisMetatable do
157 if thisMetatable == klass then
158 return true
159 end
160 thisMetatable = thisMetatable._base
161 end
162 return false
163 end
164 setmetatable(newClass, classMetatable)
165 return newClass
166 end
167
168 -- require 'elijah_clock'
169
170 --! \class TCore_Clock
171 --! \brief a clock
172 TCore_Clock = class()
173
174 --! \brief get the current time
175 function TCore_Clock.GetTimeNow()
176 if os.gettimeofday then
177 return os.gettimeofday()
178 else
179 return os.time()
180 end
181 end
182
183 --! \brief constructor
184 function TCore_Clock.init(this,T0)
185 if T0 then
186 this.t0 = T0
187 else
188 this.t0 = TCore_Clock.GetTimeNow()
189 end
190 end
191
192 --! \brief get time string
193 function TCore_Clock.getTimeStamp(this,T0)
194 local t0
195 if T0 then
196 t0 = T0
197 else
198 t0 = this.t0
199 end
200 return os.date('%c %Z',t0)
201 end
202
203
204 --require 'elijah_io'
205
206 --! \class TCore_IO
207 --! \brief io to console
208 --!
209 --! pseudo class (no methods, just to keep documentation tidy)
210 TCore_IO = class()
211 --
212 --! \brief write to stdout
213 function TCore_IO_write(Str)
214 if (Str) then
215 io.write(Str)
216 end
217 end
218
219 --! \brief write to stdout
220 function TCore_IO_writeln(Str)
221 if (Str) then
222 io.write(Str)
223 end
224 io.write("\n")
225 end
226
227
228 --require 'elijah_string'
229
230 --! \brief trims a string
231 function string_trim(Str)
232 return Str:match("^%s*(.-)%s*$")
233 end
234
235 --! \brief split a string
236 --!
237 --! \param Str
238 --! \param Pattern
239 --! \returns table of string fragments
240 function string_split(Str, Pattern)
241 local splitStr = {}
242 local fpat = "(.-)" .. Pattern
243 local last_end = 1
244 local str, e, cap = string.find(Str,fpat, 1)
245 while str do
246 if str ~= 1 or cap ~= "" then
247 table.insert(splitStr,cap)
248 end
249 last_end = e+1
250 str, e, cap = string.find(Str,fpat, last_end)
251 end
252 if last_end <= #Str then
253 cap = string.sub(Str,last_end)
254 table.insert(splitStr, cap)
255 end
256 return splitStr
257 end
258
259
260 --require 'elijah_commandline'
261
262 --! \class TCore_Commandline
263 --! \brief reads/parses commandline
264 TCore_Commandline = class()
265
266 --! \brief constructor
267 function TCore_Commandline.init(this)
268 this.argv = arg
269 this.parsed = {}
270 this.params = {}
271 end
272
273 --! \brief get value
274 function TCore_Commandline.getRaw(this,Key,Default)
275 local val = this.argv[Key]
276 if not val then
277 val = Default
278 end
279 return val
280 end
281
282
283 --require 'elijah_debug'
284
285 -------------------------------
286 --! \brief file buffer
287 --!
288 --! an input file buffer
289 TStream_Read = class()
290
291 --! \brief get contents of file
292 --!
293 --! \param Filename name of file to read (or nil == stdin)
294 function TStream_Read.getContents(this,Filename)
295 -- get lines from file
296 local filecontents
297 if Filename then
298 -- syphon lines to our table
299 --TCore_Debug_show_var('Filename',Filename)
300 filecontents={}
301 for line in io.lines(Filename) do
302 table.insert(filecontents,line)
303 end
304 else
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.
309 Filename = 'stdin'
310 end
311
312 if filecontents then
313 this.filecontents = filecontents
314 this.contentsLen = #filecontents
315 this.currentLineNo = 1
316 end
317
318 return filecontents
319 end
320
321 --! \brief get lineno
322 function TStream_Read.getLineNo(this)
323 return this.currentLineNo
324 end
325
326 --! \brief get a line
327 function TStream_Read.getLine(this)
328 local line
329 if this.currentLine then
330 line = this.currentLine
331 this.currentLine = nil
332 else
333 -- get line
334 if this.currentLineNo<=this.contentsLen then
335 line = this.filecontents[this.currentLineNo]
336 this.currentLineNo = this.currentLineNo + 1
337 else
338 line = ''
339 end
340 end
341 return line
342 end
343
344 --! \brief save line fragment
345 function TStream_Read.ungetLine(this,LineFrag)
346 this.currentLine = LineFrag
347 end
348
349 --! \brief is it eof?
350 function TStream_Read.eof(this)
351 if this.currentLine or this.currentLineNo<=this.contentsLen then
352 return false
353 end
354 return true
355 end
356
357 --! \brief output stream
358 TStream_Write = class()
359
360 --! \brief constructor
361 function TStream_Write.init(this)
362 this.tailLine = {}
363 end
364
365 --! \brief write immediately
366 function TStream_Write.write(this,Str)
367 TCore_IO_write(Str)
368 end
369
370 --! \brief write immediately
371 function TStream_Write.writeln(this,Str)
372 TCore_IO_writeln(Str)
373 end
374
375 --! \brief write immediately
376 function TStream_Write.writelnComment(this,Str)
377 TCore_IO_write('// ZZ: ')
378 TCore_IO_writeln(Str)
379 end
380
381 --! \brief write to tail
382 function TStream_Write.writelnTail(this,Line)
383 if not Line then
384 Line = ''
385 end
386 table.insert(this.tailLine,Line)
387 end
388
389 --! \brief outout tail lines
390 function TStream_Write.write_tailLines(this)
391 for k,line in ipairs(this.tailLine) do
392 TCore_IO_writeln(line)
393 end
394 TCore_IO_write('// Lua2DoX new eof')
395 end
396
397 --! \brief input filter
398 TLua2DoX_filter = class()
399
400 --! \brief allow us to do errormessages
401 function TLua2DoX_filter.warning(this,Line,LineNo,Legend)
402 this.outStream:writelnTail(
403 '
404 )
405 end
406
407 --! \brief trim comment off end of string
408 --!
409 --! If the string has a comment on the end, this trims it off.
410 --!
411 local function TString_removeCommentFromLine(Line)
412 local pos_comment = string.find(Line,'%-%-')
413 local tailComment
414 if pos_comment then
415 Line = string.sub(Line,1,pos_comment-1)
416 tailComment = string.sub(Line,pos_comment)
417 end
418 return Line,tailComment
419 end
420
421 --! \brief get directive from magic
422 local function getMagicDirective(Line)
423 local macro,tail
424 local macroStr = '[\\\@]'
425 local pos_macro = string.find(Line,macroStr)
426 if pos_macro then
427 --! ....\\ macro...stuff
428 --! ....\@ macro...stuff
429 local line = string.sub(Line,pos_macro+1)
430 local space = string.find(line,'%s+')
431 if space then
432 macro = string.sub(line,1,space-1)
433 tail = string_trim(string.sub(line,space+1))
434 else
435 macro = line
436 tail = ''
437 end
438 end
439 return macro,tail
440 end
441
442 --! \brief check comment for fn
443 local function checkComment4fn(Fn_magic,MagicLines)
444 local fn_magic = Fn_magic
445 -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
446
447 local magicLines = string_split(MagicLines,'\n')
448
449 local macro,tail
450
451 for k,line in ipairs(magicLines) do
452 macro,tail = getMagicDirective(line)
453 if macro == 'fn' then
454 fn_magic = tail
455 -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
456 else
457 --TCore_IO_writeln('// not found fn "' .. line .. '"')
458 end
459 end
460
461 return fn_magic
462 end
463 --! \brief run the filter
464 function TLua2DoX_filter.readfile(this,AppStamp,Filename)
465 local err
466
467 local inStream = TStream_Read()
468 local outStream = TStream_Write()
469 this.outStream = outStream -- save to this obj
470
471 if (inStream:getContents(Filename)) then
472 -- output the file
473 local line
474 local fn_magic -- function name/def from magic comment
475
476 outStream:writelnTail('// #######################')
477 outStream:writelnTail('// app run:' .. AppStamp)
478 outStream:writelnTail('// #######################')
479 outStream:writelnTail()
480
481 while not (err or inStream:eof()) do
482 line = string_trim(inStream:getLine())
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)
488 outStream:writeln('
489 fn_magic = checkComment4fn(fn_magic,magic)
490 elseif string.sub(line,3,4)=='[[' then -- it's a long comment
491 line = string.sub(line,5) -- nibble head
492 local comment = ''
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'
498 line = inStream:getLine()
499 else
500 thisComment = string.sub(line,1,closeSquare-1)
501 hitend = true
502
503 -- unget the tail of the line
504 -- in most cases it's empty. This may make us less efficient but
505 -- easier to program
506 inStream:ungetLine(string_trim(string.sub(line,closeSquare+2)))
507 end
508 comment = comment .. thisComment
509 end
510 if string.sub(comment,1,1)=='!' then -- it's a long magic comment
511 outStream:write('/*' .. comment .. '*/ ')
512 fn_magic = checkComment4fn(fn_magic,comment)
513 else -- discard
514 outStream:write('/* zz:' .. comment .. '*/ ')
515 fn_magic = nil
516 end
517 else
518 outStream:writeln('// zz:"' .. line .. '"')
519 fn_magic = nil
520 end
521 elseif string.find(line,'^function') or string.find(line,'^local%s+function') then
522 -- it's a function
523 local pos_fn = string.find(line,'function')
524 -- function
525 -- ....v...
526 if pos_fn then
527 -- we've got a function
528 local fn_type
529 if string.find(line,'^local%s+') then
530 fn_type = 'static '
531 else
532 fn_type = ''
533 end
534 local fn = TString_removeCommentFromLine(string_trim(string.sub(line,pos_fn+8)))
535 if fn_magic then
536 fn = fn_magic
537 fn_magic = nil
538 end
539
540 if string.sub(fn,1,1)=='(' then
541 -- it's an anonymous function
542 outStream:writelnComment(line)
543 else
544 -- fn has a name, so is interesting
545
546 -- want to fix for iffy declarations
547 local open_paren = string.find(fn,'[%({]')
548 local fn0 = fn
549 if open_paren then
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___)'
554 end
555 end
556
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)
561 --TCore_IO_writeln('function ' .. klass .. '::' .. method .. ftail .. '{}')
562 --TCore_IO_writeln(klass .. '::' .. method .. ftail .. '{}')
563 outStream:writeln(
564 '/*! \\memberof ' .. klass .. ' */ '
565 .. method .. '{}'
566 )
567 else
568 -- add vanilla function
569
570 outStream:writeln(fn_type .. 'function ' .. fn .. '{}')
571 end
572 end
573 else
574 this:warning(inStream:getLineNo(),'something weird here')
575 end
576 fn_magic = nil -- mustn't indavertently use it again
577 elseif string.find(line,'=%s*class%(') then
578 -- it's a class declaration
579 local tailComment
580 line,tailComment = TString_removeCommentFromLine(line)
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)
585 -- ....v.
586 local parent = string.sub(tail,7,-2)
587 if #parent>0 then
588 parent = ' :public ' .. parent
589 end
590 outStream:writeln('class ' .. klass .. parent .. '{};')
591 else
592 -- we don't know what this line means, so we can probably just comment it out
593 if #line>0 then
594 outStream:writeln('// zz: ' .. line)
595 else
596 outStream:writeln() -- keep this line blank
597 end
598 end
599 end
600
601 -- output the tail
602 outStream:write_tailLines()
603 else
604 outStream:writeln('!empty file')
605 end
606 end
607
608 --! \brief this application
609 TApp = class()
610
611 --! \brief constructor
612 function TApp.init(this)
613 local t0 = TCore_Clock()
614 this.timestamp = t0:getTimeStamp()
615 this.name = 'Lua2DoX'
616 this.version = '0.2 20130128'
617 this.copyright = 'Copyright (c) Simon Dales 2012-13'
618 end
619
620 function TApp.getRunStamp(this)
621 return this.name .. ' (' .. this.version .. ') '
622 .. this.timestamp
623 end
624
625 function TApp.getVersion(this)
626 return this.name .. ' (' .. this.version .. ') '
627 end
628
629 function TApp.getCopyright(this)
630 return this.copyright
631 end
632
633 local This_app = TApp()
634
635 --main
636 local cl = TCore_Commandline()
637
638 local argv1 = cl:getRaw(2)
639 if argv1 == '--help' then
640 TCore_IO_writeln(This_app:getVersion())
641 TCore_IO_writeln(This_app:getCopyright())
642 TCore_IO_writeln([[
643 run as:
644 lua2dox_filter <param>
645 --------------
646 Param:
647 <filename> : interprets filename
648 --version : show version/copyright info
649 --help : this help text]])
650 elseif argv1 == '--version' then
651 TCore_IO_writeln(This_app:getVersion())
652 TCore_IO_writeln(This_app:getCopyright())
653 else
654 -- it's a filter
655 local appStamp = This_app:getRunStamp()
656 local filename = argv1
657
658 local filter = TLua2DoX_filter()
659 filter:readfile(appStamp,filename)
660 end
661
662
663 --eof