]> rtime.felk.cvut.cz Git - opencv.git/blob - opencv/doc/latex2sphinx/latex.py
New latex2tool - builds all 3 targets
[opencv.git] / opencv / doc / latex2sphinx / latex.py
1 import sys
2 from latexparser import latexparser, TexCmd
3 import distutils.dep_util
4 import os
5 import cPickle as pickle
6 import pyparsing as pp
7 import StringIO
8 from qfile import QOpen
9
10 import pythonapi
11
12 python_api = pythonapi.reader("../../interfaces/python/api")
13
14
15 class SphinxWriter:
16     def __init__(self, filename, language):
17         assert language in ['py', 'c', 'cpp']
18         self.language = language
19
20         self.f_index = QOpen(os.path.join(self.language, filename), 'wt')
21         self.f = self.f_index
22         self.f_chapter = None
23         self.f_section = None
24         self.indent = 0
25         self.state = None
26         self.envstack = []
27         self.tags = {}
28         self.errors = open('errors', 'wt')
29         self.unhandled_commands = set()
30         self.freshline = True
31         self.function_props = {}
32         self.covered = set()        # covered functions, used for error report
33         self.description = ""
34
35     def write(self, s):
36         self.freshline = len(s) > 0 and (s[-1] == '\n')
37         self.f.write(s.replace('\n', '\n' + self.indent * "    "))
38
39     def appendspace(self):
40         """ append a space to the output - if we're not at the start of a line """
41         if not self.freshline:
42             self.write(' ')
43
44     def doplain(self, s):
45         if (len(s) > 1) and (s[0] == '$' and s[-1] == '$') and self.state != 'math':
46             s = ":math:`%s`" % s[1:-1].strip()
47         elif self.state != 'math':
48             s.replace('\\_', '_')
49         if self.state == 'fpreamble':
50             self.description += s
51         else:
52             self.write(s)
53
54     def docmd(self, c):
55         if self.state == 'math':
56             if c.cmd != ']':
57                 self.default_cmd(c)
58             else:
59                 self.indent -= 1
60                 self.state = None
61                 self.write('\n\n')
62         else:
63             if c.cmd == '[':
64                 meth = self.cmd_gomath
65             else:
66                 cname = "cmd_" + c.cmd
67                 meth = getattr(self, cname, self.unrecognized_cmd)
68             meth(c)
69
70     def cmd_gomath(self, c):
71         self.state = 'math'
72         print >>self, "\n\n.. math::"
73         self.indent += 1
74         print >>self
75
76     def cmd_chapter(self, c):
77         filename = str(c.params[0]).lower().replace(' ', '_').replace('/','_')
78         self.f_index.write("    %s\n" % filename)
79         self.f_chapter = QOpen(os.path.join(self.language, filename + '.rst'), 'wt')
80         self.f_section = None
81         self.f = self.f_chapter
82         self.indent = 0
83         title = str(c.params[0])
84         print >>self, '*' * len(title)
85         print >>self, title
86         print >>self, '*' * len(title)
87         print >>self
88         self.chapter_intoc = False
89
90     def cmd_section(self, c):
91         filename = str(c.params[0]).lower().replace(' ', '_').replace('/','_')
92         if not self.chapter_intoc:
93             self.chapter_intoc = True
94             print >>self.f_chapter
95             print >>self.f_chapter, '.. toctree::'
96             print >>self.f_chapter, '    :maxdepth: 2'
97             print >>self.f_chapter
98         self.f_chapter.write("    %s\n" % filename)
99         self.f_section = QOpen(os.path.join(self.language, filename + '.rst'), 'wt')
100         self.f = self.f_section
101         self.indent = 0
102         title = str(c.params[0])
103         print >>self, title
104         print >>self, '=' * len(title)
105         print >>self
106         print >>self, '.. highlight:: %s' % {'c': 'c', 'cpp': 'cpp', 'py': 'python'}[self.language]
107         print >>self
108
109     def cmd_subsection(self, c):
110         print >>self
111         print >>self, str(c.params[0])
112         print >>self, '-' * len(str(c.params[0]))
113         print >>self
114         self.function_props = {}
115
116     def cmd_includegraphics(self, c):
117         filename = os.path.join('..', '..', str(c.params[0]))
118         print >>self, "\n\n.. image:: %s\n\n" % filename
119
120     def cmd_cvCppCross(self, c):
121         self.write(":ref:`%s`" % str(c.params[0]))
122
123     def cmd_cvCPyCross(self, c):
124         self.write(":ref:`%s`" % str(c.params[0]))
125
126     def cmd_cross(self, c):
127         self.write(":ref:`%s`" % str(c.params[0]))
128
129     def cmd_cvCross(self, c):
130         self.write(":ref:`%s`" % str(c.params[0]))
131
132     def cmd_cvclass(self, c):
133         self.indent = 0
134         self.state = None
135         nm = self.render(list(c.params[0].str))
136         print >>self, "\n.. index:: %s\n" % nm
137         print >>self, ".. _%s:\n" % nm
138         print >>self, nm
139         print >>self, '-' * len(nm)
140         print >>self
141         if self.language == 'py':
142             print >>self, ".. class:: " + nm + "\n"
143         else:
144             print >>self, ".. ctype:: " + nm + "\n"
145         print >>self
146         self.addtag(nm, c)
147
148     def addtag(self, nm, c):
149         if nm == "":
150             self.report_error(c, "empty name")
151         self.tags[nm] = "%s\t%s\t%d" % (nm, os.path.join(os.getcwd(), c.filename), c.lineno)
152
153     def cmd_cvfunc(self, c):
154         self.cmd_cvCPyFunc(c)
155
156     def cmd_cvCPyFunc(self, c):
157         self.indent = 0
158         nm = self.render(c.params[0].str)
159         print >>self, "\n.. index:: %s\n" % nm
160         print >>self, ".. _%s:\n" % nm
161         print >>self, nm
162         print >>self, '-' * len(nm)
163         print >>self
164         self.state = 'fpreamble'
165         if self.description != "":
166             self.report_error(c, "overflow - preceding cvfunc (starting %s) not terminated?" % repr(self.description[:30]))
167         self.description = ""
168         self.addtag(nm, c)
169
170         self.function_props = {'name' : nm}
171         self.covered.add(nm)
172
173     def cmd_cvCppFunc(self, c):
174         self.indent = 0
175         nm = self.render(c.params[0].str)
176         print >>self, "\n.. index:: %s\n" % nm
177         print >>self, ".. _%s:\n" % nm
178         print >>self, nm
179         print >>self, '-' * len(nm)
180         print >>self
181         self.state = 'fpreamble'
182         if self.description != "":
183             self.report_error(c, "overflow - preceding cvfunc (starting %s) not terminated?" % repr(self.description[:30]))
184         self.description = ""
185         self.addtag(nm, c)
186
187         self.function_props = {'name' : nm}
188         self.covered.add(nm)
189
190     def cmd_cvdefC(self, c):
191         if self.language != 'c':
192             return
193         s = str(c.params[0]).replace('\\_', '_')
194         s = s.replace('\\par', '')
195         s = s.replace(';', '')
196         self.indent = 0
197         print >>self, ".. cfunction:: " + s + "\n"
198         # print >>self, "=", repr(c.params[0].str)
199         print >>self, '    ' + self.description
200         self.description = ""
201         print >>self
202         self.state = None
203         self.function_props['defpy'] = s
204
205     def cmd_cvdefCpp(self, c):
206         if self.language != 'cpp':
207             return
208         s = str(c.params[0]).replace('\\_', '_')
209         s = s.replace('\\par', '')
210         s = s.replace(';', '')
211         self.indent = 0
212         for proto in s.split('\\newline'):
213             if proto.strip() != "":
214                 print >>self, "\n\n.. cfunction:: " + proto.strip() + "\n"
215         # print >>self, "=", repr(c.params[0].str)
216         if self.description != "":
217             print >>self, '    ' + self.description
218         else:
219             self.report_error(c, 'empty description')
220         self.description = ""
221         print >>self
222         self.state = None
223         self.function_props['defpy'] = s
224
225     def cmd_cvdefPy(self, c):
226         if self.language != 'py':
227             return
228         s = str(c.params[0]).replace('\\_', '_')
229         self.indent = 0
230         print >>self, ".. function:: " + s + "\n"
231         # print >>self, "=", repr(c.params[0].str)
232         print >>self, '    ' + self.description
233         print >>self
234         self.description = ""
235         self.state = None
236         self.function_props['defpy'] = s
237
238         pp.ParserElement.setDefaultWhitespaceChars(" \n\t")
239         def returnList(x):
240             def listify(s, loc, toks):
241                 return [toks]
242             x.setParseAction(listify)
243             return x
244         def CommaList(word):
245             return returnList(pp.Optional(word + pp.ZeroOrMore(pp.Suppress(',') + word)))
246         def sl(s):
247             return pp.Suppress(pp.Literal(s))
248
249         ident = pp.Word(pp.alphanums + "_.+-")
250         ident_or_tuple = ident | (sl('(') + CommaList(ident) + sl(')'))
251         initializer = ident_or_tuple
252         arg = returnList(ident + pp.Optional(sl('=') + initializer))
253
254         decl = ident + sl('(') + CommaList(arg) + sl(')') + sl("->") + ident_or_tuple + pp.StringEnd()
255
256         try:
257             l = decl.parseString(s)
258             if str(l[0]) != self.function_props['name']:
259                 self.report_error(c, 'Decl "%s" does not match function name "%s"' % (str(l[0]), self.function_props['name']))
260             self.function_props['signature'] = l
261             if l[0] in python_api:
262                 (ins, outs) = python_api[l[0]]
263                 ins = [a for a in ins if not 'O' in a.flags]
264                 if outs != None:
265                     outs = outs.split(',')
266                 if len(ins) != len(l[1]):
267                     self.report_error(c, "function %s documented arity %d, code arity %d" % (l[0], len(l[1]), len(ins)))
268                 if outs == None:
269                     if l[2] != 'None':
270                         self.report_error(c, "function %s documented None, but code has %s" % (l[0], l[2]))
271                 else:
272                     if isinstance(l[2], str):
273                         doc_outs = [l[2]]
274                     else:
275                         doc_outs = l[2]
276                     if len(outs) != len(doc_outs):
277                         self.report_error(c, "function %s output documented tuple %d, code %d" % (l[0], len(outs), len(doc_outs)))
278             else:
279                 # self.report_error(c, "function %s documented but not found in code" % l[0])
280                 pass
281         except pp.ParseException, pe:
282             self.report_error(c, str(pe))
283             print s
284             print pe
285
286     def report_error(self, c, msg):
287         print >>self.errors, "%s:%d: [%s] Error %s" % (c.filename, c.lineno, self.language, msg)
288
289     def cmd_begin(self, c):
290         if len(c.params) == 0:
291             self.report_error(c, "Malformed begin")
292             return
293         self.write('\n')
294         s = str(c.params[0])
295         self.envstack.append((s, (c.filename, c.lineno)))
296         if s == 'description':
297             if self.language == 'py' and 'name' in self.function_props and not 'defpy' in self.function_props:
298                 self.report_error(c, "No cvdefPy for function %s" % self.function_props['name'])
299             self.indent += 1
300         elif s == 'lstlisting':
301             print >>self, "\n::\n"
302             self.indent += 1
303         elif s in ['itemize', 'enumerate']:
304             self.indent += 1
305         elif s == 'tabular':
306             self.f = StringIO.StringIO()
307         else:
308             self.default_cmd(c)
309
310     def cmd_item(self, c):
311         if len(self.ee()) == 0:
312             self.report_error(c, "item without environment")
313             return
314         self.indent -= 1
315         markup = {'itemize' : '*', 'enumerate' : '#.', 'description' : '*'}[self.ee()[-1]]
316         if len(c.args) > 0:
317             markup += " " + self.render([c.args[0].str])
318         if len(c.params) > 0:
319             markup += " " + self.render(c.params[0].str)
320         self.write("\n\n" + markup)
321         self.indent += 1
322
323     def cmd_end(self, c):
324         if len(c.params) != 1:
325             self.report_error(c, "Malformed end")
326             return
327         if len(self.envstack) == 0:
328             self.report_error(c, "end with no env")
329             return
330         self.write('\n')
331         s = str(c.params[0])
332         if self.envstack == []:
333             print "Cannot pop at", (c.filename, c.lineno)
334         if self.envstack[-1][0] != s:
335             self.report_error(c, "end{%s} does not match current stack %s" % (s, repr(self.envstack)))
336         self.envstack.pop()
337         if s == 'description':
338             self.indent -= 1
339             if self.indent == 0:
340                 self.function_props['done'] = True
341         elif s in ['itemize', 'enumerate']:
342             self.indent -= 1
343         elif s == 'tabular':
344             tabletxt = self.f.getvalue()
345             self.f = self.f_section
346             self.f.write(self.handle_table(tabletxt))
347         elif s == 'lstlisting':
348             print >>self
349             self.indent -= 1
350             print >>self
351             print >>self, ".."      # otherwise a following :param: gets treated as more listing
352         elif s == 'document':
353             pass
354         else:
355             self.default_cmd(c)
356         
357     def cmd_label(self, c):
358         pass
359
360     def cmd_lstinputlisting(self, c):
361         s = str(c.params[0])
362         print >>self.f, ".. include:: %s" % os.path.join('..', s)
363         print >>self.f, "    :literal:"
364         print >>self.f
365
366     # Conditionals
367     def cmd_cvC(self, c):
368         self.do_conditional(['c'], c)
369     def cmd_cvCpp(self, c):
370         self.do_conditional(['cpp'], c)
371     def cmd_cvPy(self, c):
372         self.do_conditional(['py'], c)
373     def cmd_cvCPy(self, c):
374         self.do_conditional(['c', 'py'], c)
375     def do_conditional(self, langs, c):
376         if self.language in langs:
377             self.doL(c.params[0].str, False)
378
379     def render(self, L):
380         """ return L rendered as a string """
381         save = self.f
382         self.f = StringIO.StringIO()
383         for x in L:
384             if isinstance(x, TexCmd):
385                 self.docmd(x)
386             else:
387                 self.doplain(x)
388         r = self.f.getvalue()
389         self.f = save
390         return r
391
392     def cmd_cvarg(self, c):
393         if len(c.params) != 2:
394             self.report_error(c, "Malformed cvarg")
395             return
396         is_func_arg = self.ee() == ['description'] and not 'done' in self.function_props
397         if is_func_arg:
398             nm = self.render(c.params[0].str)
399             print >>self, '\n:param %s: ' % nm,
400             type = None         # Try to figure out the argument type
401             # For now, multiple args get a pass
402             if (self.language == 'py') and ('signature' in self.function_props) and (not ',' in nm):
403                 sig = self.function_props['signature']
404                 argnames = [a[0] for a in sig[1]]
405                 if isinstance(sig[2], str):
406                     resnames = [sig[2]]
407                 else:
408                     resnames = list(sig[2])
409                 if not nm in argnames + resnames:
410                     self.report_error(c, "Argument %s is not mentioned in signature (%s) (%s)" % (nm, ", ".join(argnames), ", ".join(resnames)))
411
412                 api = python_api.get(self.function_props['name'], None)
413                 if api:
414                     (ins, outs) = api
415                     adict = dict([(a.nm, a) for a in ins])
416                     arg = adict.get(nm, None)
417                     if arg:
418                         type = arg.ty
419                     else:
420                         self.report_error(c, 'cannot find arg %s in code' % nm)
421         elif self.ee() == ['description']:
422             print >>self, '\n* **%s** ' % self.render(c.params[0].str),
423         elif self.ee() == ['description', 'description']:
424             print >>self, '\n* **%s** ' % self.render(c.params[0].str),
425         else:
426             print 'strange env', self.envstack
427         self.indent += 1
428         self.doL(c.params[1].str, False)
429         self.indent -= 1
430         print >>self
431         if is_func_arg and type:
432             type = type.replace('*', '')
433             translate = {
434                 "ints" : "sequence of int",
435                 "floats" : "sequence of int",
436                 "IplImages" : "sequence of :class:`IplImage`",
437                 "double" : "float",
438                 "int" : "int",
439                 "float" : "float",
440                 "char" : "str",
441                 "cvarrseq" : ":class:`CvArr` or :class:`CvSeq`",
442                 "CvPoint2D32fs" : "sequence of (float, float)",
443                 "pts_npts_contours" : "list of lists of (x,y) pairs",
444             }
445             print >>self, "\n:type %s: %s" % (nm, translate.get(type, ':class:`%s`' % type))
446
447     def cmd_genc(self, c): pass 
448     def cmd_genpy(self, c): pass 
449     def cmd_author(self, c): pass 
450     def cmd_cite(self, c): pass
451     def cmd_date(self, c): pass
452     def cmd_def(self, c): pass
453     def cmd_documentclass(self, c): pass
454     def cmd_maketitle(self, c): pass
455     def cmd_newcommand(self, c): pass
456     def cmd_newline(self, c): pass
457     def cmd_setcounter(self, c): pass
458     def cmd_tableofcontents(self, c): pass
459     def cmd_targetlang(self, c): pass
460     def cmd_usepackage(self, c): pass
461     def cmd_title(self, c): pass
462     def cmd_par(self, c): pass
463     def cmd_hline(self, c):
464         print >>self, "\\hline"
465
466     def cmd_href(self, c):
467         if len(c.params) == 2:
468             self.write("`%s <%s>`_" % (str(c.params[1]), str(c.params[0])))
469         else:
470             self.report_error(c, "href should have two params")
471
472     def cmd_url(self, c):
473         self.write(str(c.params[0]))
474
475     def cmd_emph(self, c):
476         self.write("*" + self.render(c.params[0].str) + "*")
477
478     def cmd_textit(self, c):
479         self.write("*" + self.render(c.params[0].str) + "*")
480
481     def cmd_textbf(self, c):
482         self.write("**" + self.render(c.params[0].str) + "**")
483
484     def cmd_texttt(self, c):
485         self.write("``" + self.render(c.params[0].str) + "``")
486
487     def default_cmd(self, c):
488         if self.f == self.f_section:
489             self.write(repr(c))
490
491     def unrecognized_cmd(self, c):
492         if self.f == self.f_section:
493             self.write(repr(c))
494         self.unhandled_commands.add(c.cmd)
495
496     def doL(self, L, newlines = True):
497         for x in L:
498             if isinstance(x, TexCmd):
499                 self.docmd(x)
500             else:
501                 if 'lstlisting' in self.ee() or not newlines:
502                     self.doplain(x)
503                 else:
504                     self.doplain(x.lstrip())
505             if self.state in ['math'] or not newlines:
506                 self.appendspace()
507             else:
508                 self.write('\n')
509
510     def handle_table(self, s):
511         oneline = s.replace('\n', ' ').strip()
512         rows = [r.strip() for r in oneline.split('\\hline')]
513         tab = []
514         for r in rows:
515             if r != "":
516                 cols = [c.strip() for c in r.split('&')]
517                 tab.append(cols)
518         widths = [max([len(r[i]) for r in tab]) for i in range(len(tab[0]))]
519
520         st = ""         # Sphinx table
521
522         if 0:
523             sep = "+" + "+".join(["-" * w for w in widths]) + "+"
524             st += sep + '\n'
525             for r in tab:
526                 st += "|" + "|".join([c.center(w) for (c, w) in zip(r, widths)]) + "|" + '\n'
527                 st += sep + '\n'
528
529         st = '.. table::\n\n'
530         sep = "  ".join(["=" * w for w in widths])
531         st += '    ' + sep + '\n'
532         for y,r in enumerate(tab):
533             st += '    ' + "  ".join([c.ljust(w) for (c, w) in zip(r, widths)]) + '\n'
534             if y == 0:
535                 st += '    ' + sep + '\n'
536         st += '    ' + sep + '\n'
537         return st
538
539     def ee(self):
540         """ Return tags of the envstack.  envstack[0] is 'document', so skip it """
541         return [n for (n,_) in self.envstack[1:]]
542
543     def get_tags(self):
544         return self.tags
545
546     def close(self):
547
548         if self.envstack != []:
549             print >>self.errors, "Error envstack not empty at end of doc: " + repr(self.envstack)
550         print >>self.errors, "unrecognized commands:"
551         print >>self.errors, "\n    ".join(sorted(self.unhandled_commands))
552         print >>self.errors
553         print >>self.errors, "The following functions are undocumented"
554         for f in sorted(set(python_api) - self.covered):
555             print >>self.errors, '    ', f
556
557         print >>self.f_index, """
558
559 Indices and tables
560 ==================
561
562 * :ref:`genindex`
563 * :ref:`search`
564 """
565
566 if 1:
567     sources = ['../' + f for f in os.listdir('..') if f.endswith('.tex')]
568     if distutils.dep_util.newer_group(["latexparser.py"] + sources, "pickled"):
569         fulldoc = latexparser(sys.argv[1], 0)
570         pickle.dump(fulldoc, open("pickled", 'wb'))
571         raw = open('fulldoc', 'w')
572         for x in fulldoc:
573             print >>raw, repr(x)
574         raw.close()
575     else:
576         fulldoc = pickle.load(open("pickled", "rb"))
577
578     # Filter on target language
579     def preprocess_conditionals(fd, conditionals):
580         r = []
581         ifstack = []
582         for x in fd:
583             if isinstance(x, TexCmd):
584                 ll = x.cmd.rstrip()
585                 loc = (x.filename, x.lineno)
586                 if ll.startswith("if"):
587                     # print " " * len(ifstack), '{', loc
588                     ifstack.append((conditionals.get(ll[2:], False), loc))
589                 elif ll.startswith("else"):
590                     ifstack[-1] = (not ifstack[-1][0], ifstack[-1][1])
591                 elif ll.startswith("fi"):
592                     ifstack.pop()
593                     # print " " * len(ifstack), '}', loc
594                 elif not False in [p for (p,_) in ifstack]:
595                     r.append(x)
596             else:
597                 if not False in [p for (p,_) in ifstack]:
598                     r.append(x)
599         if ifstack != []:
600             print "unterminated if", ifstack
601             sys.exit(0)
602         return r
603
604     tags = {}
605     for language in ['c', 'cpp', 'py']:
606         doc = preprocess_conditionals(fulldoc, {
607                                               'C' : language=='c',
608                                               'Python' : language=='py',
609                                               'Py' : language=='py',
610                                               'CPy' : (language=='py' or language == 'c'),
611                                               'Cpp' : language=='cpp',
612                                               'plastex' : True})
613
614         raw = open('raw.%s' % language, 'w')
615         for x in doc:
616             print >>raw, repr(x)
617         raw.close()
618         sr = SphinxWriter('index.rst', language)
619         print >>sr, """
620 OpenCV |version| %s Reference
621 =================================
622
623 The OpenCV Wiki is here: http://opencv.willowgarage.com/
624
625 Contents:
626
627 .. toctree::
628     :maxdepth: 2
629
630 """ % {'c': 'C', 'cpp': 'C++', 'py': 'Python'}[language]
631         sr.doL(doc)
632         sr.close()
633         tags.update(sr.get_tags())
634     open('TAGS', 'w').write("\n".join(sorted(tags.values())) + "\n")
635