Upcoming events update
[mp-talk.git] / wiki2beamer-0.9.2
1 #!/usr/bin/env python
2
3 # wiki2beamer
4 #
5 # (c) 2007-2008 Michael Rentzsch (http://www.repc.de)
6 # (c) 2009-2010 Michael Rentzsch (http://www.repc.de)
7 #               Kai Dietrich (mail@cleeus.de)
8 #
9 # Create latex beamer sources for multiple frames from a wiki-like code.
10 #
11 #
12 #     This file is part of wiki2beamer.
13 # wiki2beamer is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 2 of the License, or
16 # (at your option) any later version.
17 #
18 # wiki2beamer is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with wiki2beamer.  If not, see <http://www.gnu.org/licenses/>.
25 #
26 # Additional commits by:
27 #     Valentin Haenel <valentin.haenel@gmx.de>
28 #     Julius Plenz <julius@plenz.com>
29
30
31 import sys
32 import re
33 import random
34 import string
35 import optparse
36
37 VERSIONTAG = "0.9.2"
38 __version__= VERSIONTAG
39 __author__= "Michael Rentzsch, Kai Dietrich and others"
40
41 #python 2.4 compatability
42 if sys.version_info >= (2, 5):
43     import hashlib
44 else:
45     import md5
46 #python 2.4 compatability
47 def md5hex(string):
48     if sys.version_info >= (2, 5):
49         return hashlib.md5(string).hexdigest()
50     else:
51         dg = md5.md5()
52         dg.update(string)
53         return dg.hexdigest()
54
55 def mydebug(message):
56     """ print debug message to stderr """
57     print >>sys.stderr, message
58
59 def syntax_error(message, code):
60     print >>sys.stderr, 'syntax error: %s' % message
61     print >>sys.stderr, '\tcode:\n%s' % code
62     sys.exit(-3)
63
64 class IncludeLoopException(Exception):
65     pass
66
67 lstbasicstyle=\
68 r"""{basic}{
69     captionpos=t,%
70     basicstyle=\footnotesize\ttfamily,%
71     numberstyle=\tiny,%
72     numbers=left,%
73     stepnumber=1,%
74     frame=single,%
75     showspaces=false,%
76     showstringspaces=false,%
77     showtabs=false,%
78     %
79     keywordstyle=\color{blue},%
80     identifierstyle=,%
81     commentstyle=\color{gray},%
82     stringstyle=\color{magenta}%
83 }"""
84
85 autotemplate = [\
86     ('documentclass', '{beamer}'),\
87     ('usepackage', '{listings}'),\
88     ('usepackage', '{wasysym}'),\
89     ('usepackage', '{graphicx}'),\
90     ('date', '{\\today}'),\
91     ('lstdefinestyle', lstbasicstyle),\
92     ('titleframe', 'True')\
93 ]
94
95 nowikistartre = re.compile(r'^<\[\s*nowiki\s*\]')
96 nowikiendre = re.compile(r'^\[\s*nowiki\s*\]>')
97 codestartre = re.compile(r'^<\[\s*code\s*\]')
98 codeendre = re.compile(r'^\[\s*code\s*\]>')
99
100 # lazy initialisation cache for file content
101 _file_cache = dict()
102
103 def add_lines_to_cache(filename, lines):
104     if not filename in _file_cache:
105         _file_cache[filename] = lines
106     return
107
108
109 def get_lines_from_cache(filename):
110     if filename in _file_cache:
111         return _file_cache[filename]
112     else:
113         lines = read_file_to_lines(filename)
114         _file_cache[filename] = lines
115         return lines
116     return
117
118 def clear_file_cache():
119     _file_cache = {}
120     return
121
122
123 class w2bstate:
124     def __init__(self):
125         self.frame_opened = False
126         self.enum_item_level = ''
127         self.frame_header = ''
128         self.frame_footer = ''
129         self.next_frame_footer = ''
130         self.next_frame_header = ''
131         self.current_line = 0
132         self.autotemplate_opened = False
133         self.defverbs = {}
134         self.code_pos = 0
135         return
136
137     def switch_to_next_frame(self):
138         self.frame_header = self.next_frame_header
139         self.frame_footer = self.next_frame_footer
140         return
141
142 def escape_resub(string):
143     p = re.compile(r"\\")
144     return p.sub(r"\\\\", string)
145
146
147 def transform_itemenums(string, state):
148     """handle itemizations/enumerations"""
149     preamble = ""   # for enumeration/itemize environment commands
150
151     # handle itemizing/enumerations
152     p = re.compile("^([\*\#]+).*$")
153     m = p.match(string)
154     if (m == None):
155         my_enum_item_level = ""
156     else:
157         my_enum_item_level = m.group(1)
158
159     # trivial: old level = new level
160     if (my_enum_item_level == state.enum_item_level):
161         pass
162     else:
163         # find common part
164         common = -1 
165         while (len(state.enum_item_level) > common + 1) and \
166                 (len(my_enum_item_level) > common + 1) and \
167                 (state.enum_item_level[common+1] == my_enum_item_level[common+1]):
168             common = common + 1
169
170         # close enum_item_level environments from back to front
171         for i in range(len(state.enum_item_level)-1, common, -1):
172             if (state.enum_item_level[i] == "*"):
173                 preamble = preamble + "\\end{itemize}\n"
174             elif (state.enum_item_level[i] == "#"):
175                 preamble = preamble + "\\end{enumerate}\n"
176         
177         # open my_enum_item_level environments from front to back
178         for i in range(common+1, len(my_enum_item_level)):
179             if (my_enum_item_level[i] == "*"):
180                 preamble = preamble + "\\begin{itemize}\n"
181             elif (my_enum_item_level[i] == "#"):
182                 preamble = preamble + "\\begin{enumerate}\n"
183     state.enum_item_level = my_enum_item_level
184     
185     # now, substitute item markers
186     p = re.compile("^([\*\#]+)(.*)$")
187     _string = p.sub(r"  \\item\2", string)
188     string = preamble + _string
189  
190     return string
191
192 def transform_define_foothead(string, state):
193     """ header and footer definitions"""
194     p = re.compile("^@FRAMEHEADER=(.*)$", re.VERBOSE)
195     m = p.match(string)
196     if (m != None):
197         #print m.group(1)
198         state.next_frame_header = m.group(1)
199         string = ""
200     p = re.compile("^@FRAMEFOOTER=(.*)$", re.VERBOSE)
201     m = p.match(string)
202     if (m != None):
203         #print m.group(1)
204         state.next_frame_footer = m.group(1)
205         string = ""
206     return string
207
208 def transform_detect_manual_frameclose(string, state):
209     """ detect manual closing of frames """
210     p = re.compile(r"\[\s*frame\s*\]>")
211     if state.frame_opened:
212         if p.match(string) != None:
213             state.frame_opened = False
214     return string
215
216 def get_frame_closing(state):
217     return " %s \n\\end{frame}\n" % state.frame_footer
218
219 def transform_h4_to_frame(string, state):
220     """headings (3) to frames"""
221     frame_opening = r"\\begin{frame}\2\n \\frametitle{\1}\n %s \n" % escape_resub(state.next_frame_header)
222     frame_closing = escape_resub(get_frame_closing(state))
223     
224     p = re.compile("^!?====\s*(.*?)\s*====(.*)", re.VERBOSE)
225     if not state.frame_opened:
226         _string = p.sub(frame_opening, string)
227     else:
228         _string = p.sub(frame_closing + frame_opening, string)
229
230     if (string != _string):
231         state.frame_opened = True
232         state.switch_to_next_frame()
233
234     return _string
235
236 def transform_h3_to_subsec(string, state):
237     """ headings (2) to subsections """
238     frame_closing = escape_resub(get_frame_closing(state))
239     subsec_opening = r"\n\\subsection\2{\1}\n\n"
240
241     p = re.compile("^===\s*(.*?)\s*===(.*)", re.VERBOSE)
242     if state.frame_opened:
243         _string = p.sub(frame_closing + subsec_opening, string)
244     else:
245         _string = p.sub(subsec_opening, string)
246     if (string != _string):
247         state.frame_opened = False
248     
249     return _string
250
251 def transform_h2_to_sec(string, state):
252     """ headings (1) to sections """
253     frame_closing = escape_resub(get_frame_closing(state))
254     sec_opening = r"\n\\section\2{\1}\n\n"
255     p = re.compile("^==\s*(.*?)\s*==(.*)", re.VERBOSE)
256     if state.frame_opened:
257         _string = p.sub(frame_closing + sec_opening, string)
258     else:
259         _string = p.sub(sec_opening, string)
260     if (string != _string):
261         state.frame_opened = False
262
263     return _string
264
265 def transform_replace_headfoot(string, state):
266     string = string.replace("<---FRAMEHEADER--->", state.frame_header)
267     string = string.replace("<---FRAMEFOOTER--->", state.frame_footer)
268     return string
269
270 def transform_environments(string):
271     """
272     latex environments, the users takes full responsibility
273     for closing ALL opened environments
274     exampe:
275     <[block]{block title}
276     message
277     [block]>
278     """
279     # -> open
280     p = re.compile("^<\[([^{}]*?)\]", re.VERBOSE)
281     string = p.sub(r"\\begin{\1}", string)
282     # -> close
283     p = re.compile("^\[([^{}]*?)\]>", re.VERBOSE)
284     string = p.sub(r"\\end{\1}", string)
285
286     return string
287
288 def transform_columns(string):
289     """ columns """
290     p = re.compile("^\[\[\[(.*?)\]\]\]", re.VERBOSE)
291     string = p.sub(r"\\column{\1}", string)
292     return string
293
294 def transform_boldfont(string):
295     """ bold font """
296     p = re.compile("'''(.*?)'''", re.VERBOSE)
297     string = p.sub(r"\\textbf{\1}", string)
298     return string
299
300 def transform_italicfont(string):
301     """ italic font """
302     p = re.compile("''(.*?)''", re.VERBOSE)
303     string = p.sub(r"\\emph{\1}", string) 
304     return string
305
306 def _transform_mini_parser(character, replacement, string):
307     # implemented as a state-machine
308     output, typewriter = [], []
309     seen_at, seen_escape = False, False
310     for char in string:
311         if seen_escape:
312             if char == character:
313                 output.append(character)
314             else:
315                 output.append('\\' + char)
316             seen_escape = False
317         elif char == "\\":
318             seen_escape = True
319         elif char == character:
320             if seen_at:
321                 seen_at = False
322                 output, typewriter = typewriter, output
323                 output.append('\\'+replacement+'{')
324                 output += typewriter
325                 output.append('}')
326                 typewriter = []
327             else:
328                 seen_at = True
329                 output, typewriter = typewriter, output
330         else:
331             output.append(char)
332     if seen_at:
333         output, typewriter = typewriter, output
334         output.append(character)
335         output += typewriter
336     return "".join(output)
337
338 def transform_typewriterfont(string):
339     """ typewriter font """
340     return _transform_mini_parser('@', 'texttt', string)
341
342 def transform_alerts(string):
343     """ alerts """
344     return _transform_mini_parser('!', 'alert', string)
345
346 def transform_colors(string):
347     """ colors """
348     p = re.compile("_([^_\\\\]*?)_([^_]*?[^_\\\\])_", re.VERBOSE)
349     string = p.sub(r"\\textcolor{\1}{\2}", string) 
350     return string
351    
352 def transform_footnotes(string):
353     """ footnotes """
354     p = re.compile("\(\(\((.*?)\)\)\)", re.VERBOSE)
355     string = p.sub(r"\\footnote{\1}", string) 
356     return string
357
358 def transform_graphics(string):
359     """ figures/images """
360     p = re.compile("\<\<\<(.*?),(.*?)\>\>\>", re.VERBOSE)
361     string = p.sub(r"\\includegraphics[\2]{\1}", string) 
362     p = re.compile("\<\<\<(.*?)\>\>\>", re.VERBOSE)
363     string = p.sub(r"\\includegraphics{\1}", string) 
364     return string
365
366 def transform_substitutions(string):
367     """ substitutions """
368     p = re.compile("(\s)-->(\s)", re.VERBOSE)
369     string = p.sub(r"\1$\\rightarrow$\2", string) 
370     p = re.compile("(\s)<--(\s)", re.VERBOSE)
371     string = p.sub(r"\1$\\leftarrow$\2", string) 
372     p = re.compile("(\s)==>(\s)", re.VERBOSE)
373     string = p.sub(r"\1$\\Rightarrow$\2", string) 
374     p = re.compile("(\s)<==(\s)", re.VERBOSE)
375     string = p.sub(r"\1$\\Leftarrow$\2", string) 
376     p = re.compile("(\s):-\)(\s)", re.VERBOSE)
377     string = p.sub(r"\1\\smiley\2", string) 
378     p = re.compile("(\s):-\((\s)", re.VERBOSE)
379     string = p.sub(r"\1\\frownie\2", string)
380     return string
381
382 def transform_vspace(string):
383     """vspace"""
384     p = re.compile("^\s*--(.*)--\s*$")
385     string = p.sub(r"\n\\vspace{\1}\n", string)
386     return string
387
388 def transform_vspacestar(string):
389     """vspace*"""
390     p = re.compile("^\s*--\*(.*)--\s*$")
391     string = p.sub(r"\n\\vspace*{\1}\n", string)
392     return string
393
394 def transform_uncover(string):
395     """uncover"""
396     p = re.compile("\+<(.*)>\s*{(.*)") # +<1-2>{.... -> \uncover<1-2>{....
397     string = p.sub(r"\uncover<\1>{\2", string)
398     return string
399
400 def transform_only(string):
401     """only"""
402     p = re.compile("-<(.*)>\s*{(.*)") # -<1-2>{.... -> \only<1-2>{....
403     string = p.sub(r"\only<\1>{\2", string)
404     return string
405
406 def transform(string, state):
407     """ convert/transform one line in context of state"""
408
409     #string = transform_itemenums(string, state)
410     string = transform_define_foothead(string, state)
411     string = transform_detect_manual_frameclose(string, state)
412     string = transform_h4_to_frame(string, state)
413     string = transform_h3_to_subsec(string, state)
414     string = transform_h2_to_sec(string, state)
415     string = transform_replace_headfoot(string, state)
416
417     string = transform_environments(string)
418     string = transform_columns(string)
419     string = transform_boldfont(string)
420     string = transform_italicfont(string)
421     string = transform_typewriterfont(string)
422     string = transform_alerts(string)
423     string = transform_colors(string)
424     string = transform_footnotes(string)
425     string = transform_graphics(string)
426     string = transform_substitutions(string)
427     string = transform_vspacestar(string)
428     string = transform_vspace(string)
429     string = transform_uncover(string)
430     string = transform_only(string)
431
432     string = transform_itemenums(string, state)
433
434     return string
435
436 def expand_code_make_defverb(content, name):
437     return "\\defverbatim[colored]\\%s{\n%s\n}" % (name, content)
438
439 def expand_code_make_lstlisting(content, options):
440     return "\\begin{lstlisting}%s%s\\end{lstlisting}" % (options, content)
441
442 def expand_code_search_escape_sequences(code):
443     open = '1'
444     close = '2'
445     while code.find(open) != -1 or code.find(close) != -1:
446         open = open + chr(random.randint(48,57))
447         close = close + chr(random.randint(48,57))
448
449     return (open,close)
450
451 def expand_code_tokenize_anims(code):
452     #escape
453     (esc_open, esc_close) = expand_code_search_escape_sequences(code)
454     code = code.replace('\\[', esc_open)
455     code = code.replace('\\]', esc_close)
456
457     p = re.compile(r'\[\[(?:.|\s)*?\]\]|\[(?:.|\s)*?\]')
458     non_anim = p.split(code)
459     anim = p.findall(code)
460     
461     #unescape
462     anim = map(lambda s: s.replace(esc_open, '\\[').replace(esc_close, '\\]'), anim)
463     non_anim = map(lambda s: s.replace(esc_open, '[').replace(esc_close, ']'), non_anim)
464
465     return (anim, non_anim)
466
467 def expand_code_parse_overlayspec(overlayspec):
468     overlays = []
469
470     groups = overlayspec.split(',')
471     for group in groups:
472         group = group.strip()
473         if group.find('-')!=-1:
474             nums = group.split('-')
475             if len(nums)<2:
476                 syntax_error('overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
477             else:
478                 try:
479                     start = int(nums[0])
480                     stop = int(nums[1])
481                 except ValueError:
482                     syntax_error('not an int, overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
483
484                 overlays.extend(range(start,stop+1))
485         else:
486             try:
487                 num = int(group)
488             except ValueError:
489                 syntax_error('not an int, overlay specs must be of the form <(%d-%d)|(%d), ...>', overlayspec)
490             overlays.append(num)
491     
492     #make unique
493     overlays = list(set(overlays))
494     return overlays
495
496 def expand_code_parse_simpleanimspec(animspec):
497     #escape
498     (esc_open, esc_close) = expand_code_search_escape_sequences(animspec)
499     animspec = animspec.replace('\\[', esc_open)
500     animspec = animspec.replace('\\]', esc_close)
501
502     p = re.compile(r'^\[<([0-9,\-]+)>((?:.|\s)*)\]$')
503     m = p.match(animspec)
504     if m != None:
505         overlays = expand_code_parse_overlayspec(m.group(1))
506         code = m.group(2)
507     else:
508         syntax_error('specification does not match [<%d>%s]', animspec)
509
510     #unescape code
511     code = code.replace(esc_open, '[').replace(esc_close, ']')
512     
513     return [(overlay, code) for overlay in overlays]
514
515
516 def expand_code_parse_animspec(animspec):
517     if len(animspec)<4 or not animspec.startswith('[['):
518         return ('simple', expand_code_parse_simpleanimspec(animspec))
519     
520     #escape
521     (esc_open, esc_close) = expand_code_search_escape_sequences(animspec)
522     animspec = animspec.replace('\\[', esc_open)
523     animspec = animspec.replace('\\]', esc_close)
524     
525     p = re.compile(r'\[|\]\[|\]')
526     simple_specs = map(lambda s: '[%s]'%s, filter(lambda s: len(s.strip())>0, p.split(animspec)))
527
528     #unescape
529     simple_specs = map(lambda s: s.replace(esc_open, '\\[').replace(esc_close, '\\]'), simple_specs)
530     parsed_simple_specs = map(expand_code_parse_simpleanimspec, simple_specs)
531     #print parsed_simple_specs
532     unified_pss = []
533     for pss in parsed_simple_specs:
534         unified_pss.extend(pss)
535     #print unified_pss
536     return ('double', unified_pss)
537     
538
539 def expand_code_getmaxoverlay(parsed_anims):
540     max_overlay = 0
541     for anim in parsed_anims:
542         for spec in anim:
543             if spec[0] > max_overlay:
544                 max_overlay = spec[0]
545     return max_overlay
546
547 def expand_code_getminoverlay(parsed_anims):
548     min_overlay = sys.maxint
549     for anim in parsed_anims:
550         for spec in anim:
551             if spec[0] < min_overlay:
552                 min_overlay = spec[0]
553     if min_overlay == sys.maxint:
554         min_overlay = 0
555     return min_overlay
556
557
558 def expand_code_genanims(parsed_animspec, minoverlay, maxoverlay, type):
559     #get maximum length of code
560     maxlen=0
561     if type=='double':
562         for simple_animspec in parsed_animspec:
563             if maxlen < len(simple_animspec[1]):
564                 maxlen = len(simple_animspec[1])
565     
566     out = []
567     fill = ''.join([' ' for i in xrange(0, maxlen)])
568     for x in xrange(minoverlay,maxoverlay+1):
569         out.append(fill[:])
570
571     for simple_animspec in parsed_animspec:
572         out[simple_animspec[0]-minoverlay] = simple_animspec[1]
573
574     return out
575
576 def expand_code_getname(code):
577     asciihextable = string.maketrans('0123456789abcdef',\
578                                      'abcdefghijklmnop')
579     d = md5hex(code).translate(asciihextable)
580     return d
581
582 def expand_code_makeoverprint(names, minoverlay):
583     out = ['\\begin{overprint}\n']
584     for (index, name) in enumerate(names):
585         out.append('  \\onslide<%d>\\%s\n' % (index+minoverlay, name))
586     out.append('\\end{overprint}\n')
587
588     return ''.join(out)
589
590 def expand_code_get_unique_name(defverbs, code, lstparams):
591     """generate a collision free entry in the defverbs-map and names-list"""
592     name = expand_code_getname(code)
593     expanded_code = expand_code_make_defverb(expand_code_make_lstlisting(code, lstparams), name)
594     rehash = ''
595     while name in defverbs and defverbs[name] != expanded_code:
596         rehash += char(random.randint(65,90)) #append a character from A-Z to rehash value
597         name = expanded_code_getname(code + rehash)
598         expanded_code = expand_code_make_defverb(expand_code_make_lstlisting(code, lstparams), name)
599
600     return (name, expanded_code)
601
602    
603 def expand_code_segment(result, codebuffer, state):
604     #treat first line as params for lstlistings
605     lstparams = codebuffer[0]
606     codebuffer[0] = ''
607  
608     #join lines into one string
609     code = ''.join(codebuffer)
610     #print code
611
612     #tokenize code into anim and non_anim parts
613     (anim, non_anim) = expand_code_tokenize_anims(code)
614     #print anim
615     #print non_anim
616     if len(anim)>0:
617         #generate multiple versions of the anim parts
618         parsed_anims = map(expand_code_parse_animspec, anim)
619         #print parsed_anims
620         max_overlay = expand_code_getmaxoverlay(map(lambda x: x[1], parsed_anims))
621         #if there is unanimated code, use 0 as the starting overlay
622         if len(non_anim)>0:
623             min_overlay = 1
624         else:
625             min_overlay = expand_code_getminoverlay(map(lambda x: x[1], parsed_anims))
626         #print min_overlay
627         #print max_overlay
628         gen_anims = map(lambda x: expand_code_genanims(x[1], min_overlay, max_overlay, x[0]), parsed_anims)
629         #print gen_anims
630         anim_map = {}
631         for i in xrange(0,max_overlay-min_overlay+1):
632             anim_map[i+min_overlay] = map(lambda x: x[i], gen_anims)
633         #print anim_map
634     
635         names = []
636         for overlay in sorted(anim_map.keys()):
637             #combine non_anim and anim parts
638             anim_map[overlay].append('')
639             zipped = zip(non_anim, anim_map[overlay])
640             mapped = map(lambda x: x[0] + x[1], zipped)
641             code = ''.join(mapped)
642             
643             #generate a collision free entry in the defverbs-map and names-list
644             (name, expanded_code) = expand_code_get_unique_name(state.defverbs, code, lstparams)
645
646             #now we have a collision free entry, append it
647             names.append(name)
648             state.defverbs[name] = expanded_code
649         
650         #append overprint area to result
651         overprint = expand_code_makeoverprint(names, min_overlay)
652         result.append(overprint)
653     else:
654         #we have no animations and can just put the defverbatim in
655         #remove escapings
656         code = code.replace('\\[', '[').replace('\\]', ']')
657         (name, expanded_code) = expand_code_get_unique_name(state.defverbs, code, lstparams)  
658         state.defverbs[name] = expanded_code
659         result.append('\n\\%s\n' % name)
660
661     #print '----'
662     return
663
664 def expand_code_defverbs(result, state):
665         result[state.code_pos] = result[state.code_pos] + '\n'.join(state.defverbs.values()) + '\n'
666         state.defverbs={}
667
668 def get_autotemplate_closing():
669     return '\n\end{document}\n'
670
671 def parse_bool(string):
672     boolean = False
673
674     if string == 'True' or string == 'true' or string == '1':
675         boolean = True
676     elif string == 'False' or string == 'false' or string =='0':
677         boolean = False
678     else:
679         syntax_error('Boolean expected (True/true/1 or False/false/0)', string)
680
681     return boolean
682
683 def parse_autotemplate(autotemplatebuffer):
684     """
685     @param autotemplatebuffer (list)
686         a list of lines found in the autotemplate section
687     @return (list)
688         a list of tuples of the form (string, string) with \command.parameters pairs
689     """
690     autotemplate = []
691
692     for line in autotemplatebuffer:
693         if len(line.lstrip())==0: #ignore empty lines
694             continue
695         if len(line.lstrip())>0 and line.lstrip().startswith('%'): #ignore lines starting with % as comments
696             continue
697
698         tokens = line.split('=', 1)
699         if len(tokens)<2:
700             syntax_error('lines in the autotemplate section have to be of the form key=value', line)
701
702         autotemplate.append((tokens[0], tokens[1]))
703
704     return autotemplate
705
706 def parse_usepackage(usepackage):
707     """
708     @param usepackage (str)
709         the unparsed usepackage string in the form [options]{name}
710     @return (tuple)
711         (name(str), options(str))
712     """
713     
714     p = re.compile(r'^\s*(\[.*\])?\s*\{(.*)\}\s*$')
715     m = p.match(usepackage)
716     g = m.groups()
717     if len(g)<2 or len(g)>2:
718         syntax_error('usepackage specifications have to be of the form [%s]{%s}', usepackage)
719     elif g[1]==None and g[1].strip()!='':
720         syntax_error('usepackage specifications have to be of the form [%s]{%s}', usepackage)
721     else:
722         options = g[0]
723         name = g[1].strip()
724         return (name, options)
725
726
727 def unify_autotemplates(autotemplates):
728     usepackages = {} #packagename : options
729     documentclass = ''
730     titleframe = False
731
732     merged = []
733     for template in autotemplates:
734         for command in template:
735             if command[0] == 'usepackage':
736                 (name, options) = parse_usepackage(command[1])
737                 usepackages[name] = options
738             elif command[0] == 'titleframe':
739                 titleframe = command[1]
740             elif command[0] == 'documentclass':
741                 documentclass = command[1]
742             else:
743                 merged.append(command)
744     
745     autotemplate = []
746     autotemplate.append(('documentclass', documentclass))
747     for (name, options) in usepackages.items():
748         if options != None and options.strip() != '':
749             string = '%s{%s}' % (options, name)
750         else:
751             string = '{%s}' % name
752         autotemplate.append(('usepackage', string))
753     autotemplate.append(('titleframe', titleframe))
754
755     autotemplate.extend(merged)
756
757     return autotemplate
758
759 def expand_autotemplate_gen_opening(autotemplate):
760     """
761     @param autotemplate (list)
762         the specification of the autotemplate in the form of a list of tuples
763     @return (string)
764         the string the with generated latex code
765     """
766     titleframe = False
767     out = []
768     for item in autotemplate:
769         if item[0]!='titleframe':
770             out.append('\\%s%s' % item)
771         else:
772             titleframe = parse_bool(item[1])
773
774     out.append('\n\\begin{document}\n')
775     if titleframe:
776         out.append('\n\\frame{\\titlepage}\n')
777
778     return '\n'.join(out)
779
780
781 def expand_autotemplate_opening(result, templatebuffer, state):
782     my_autotemplate = parse_autotemplate(templatebuffer)
783     the_autotemplate = unify_autotemplates([autotemplate, my_autotemplate])
784
785     opening = expand_autotemplate_gen_opening(the_autotemplate)
786
787     result.append(opening)
788     result.append('')
789     state.code_pos = len(result)
790     state.autotemplate_opened = True
791     return
792
793 def get_autotemplatemode(line, autotemplatemode):
794     autotemplatestart = re.compile(r'^<\[\s*autotemplate\s*\]')
795     autotemplateend = re.compile(r'^\[\s*autotemplate\s*\]>')
796     if not autotemplatemode and autotemplatestart.match(line)!=None:
797         line = autotemplatestart.sub('', line)
798         return (line, True)
799     elif autotemplatemode and autotemplateend.match(line)!=None:
800         line = autotemplateend.sub('', line)
801         return (line, False)
802     else:
803         return (line, autotemplatemode)
804
805 def get_nowikimode(line, nowikimode):
806     
807     if not nowikimode and nowikistartre.match(line)!=None:
808         line = nowikistartre.sub('', line)
809         return (line, True)
810     elif nowikimode and nowikiendre.match(line)!=None:
811         line = nowikiendre.sub('', line)
812         return (line, False)
813     else:
814         return (line, nowikimode)
815
816 def get_codemode(line, codemode):
817     if not codemode and codestartre.match(line)!=None:
818         line = codestartre.sub('', line)
819         return (line, True)
820     elif codemode and codeendre.match(line)!=None:
821         line = codeendre.sub('', line)
822         return (line, False)
823     else:
824         return (line, codemode)
825
826 def joinLines(lines):
827     """ join lines ending with unescaped percent signs, unless inside codemode or nowiki mode """
828     nowikimode = False
829     codemode = False
830     r = []  # result array
831     s = ''  # new line
832     for _l in lines:
833         (_,nowikimode) = get_nowikimode(_l, nowikimode)
834         if not nowikimode:
835             (_,codemode) = get_codemode(_l, codemode)
836
837         if not codemode:
838             l = _l.rstrip()
839         else:
840             l = _l
841
842         if not (nowikimode or codemode) and (len(l) > 1) and (l[-1] == "%") and (l[-2] != "\\"):
843             s = s + l[:-1]
844         elif not (nowikimode or codemode) and (len(l) == 1) and (l[-1] == "%"):
845             s = s + l[:-1]
846         else:
847             s = s + l
848             r.append(s)
849             s = ''
850
851     return r
852
853 def read_file_to_lines(filename):
854     """ read file """
855     try:
856         f = open(filename, "r")
857         lines = joinLines(f.readlines())
858         f.close()
859     except:
860         print >>sys.stdout, "Cannot read file: " + filename
861         sys.exit(-2)
862
863     return lines
864
865
866 def scan_for_selected_frames(lines):
867     """scans for frames that should be rendered exclusively, returns true if such frames have been found"""
868     p = re.compile("^!====\s*(.*?)\s*====(.*)", re.VERBOSE)
869     for line in lines:
870         mo = p.match(line)
871         if mo != None:
872             return True
873     return False
874
875 def line_opens_unselected_frame(line):
876     p = re.compile("^====\s*(.*?)\s*====(.*)", re.VERBOSE)
877     if p.match(line) != None:
878         return True
879     return False
880
881 def line_opens_selected_frame(line):
882     p = re.compile("^!====\s*(.*?)\s*====(.*)", re.VERBOSE)
883     if p.match(line) != None:
884         return True
885     return False
886
887 def line_closes_frame(line):
888     p = re.compile("^\s*\[\s*frame\s*\]>", re.VERBOSE)
889     if p.match(line) != None:
890         return True
891     return False
892
893 def filter_selected_lines(lines):
894     selected_lines = []
895
896     selected_frame_opened = False
897     unselected_frame_opened = False
898     frame_closed = True
899     frame_manually_closed = False
900     for line in lines:
901         if line_opens_selected_frame(line):
902             selected_frame_opened = True
903             unselected_frame_opened = False
904             frame_closed = False
905
906         if line_opens_unselected_frame(line):
907             unselected_frame_opened = True
908             selected_frame_opened = False
909             frame_closed = False
910
911         if line_closes_frame(line):
912             unselected_frame_opened = False
913             selected_frame_opened = False
914             frame_closed = True
915             frame_manually_closed = True
916         
917         if selected_frame_opened or (frame_closed and not frame_manually_closed):
918             selected_lines.append(line)
919
920     return selected_lines
921
922 def convert2beamer(lines):
923     out = ""
924     selectedframemode = scan_for_selected_frames(lines)
925     if selectedframemode:
926         out = convert2beamer_selected(lines)
927     else:
928         out = convert2beamer_full(lines)
929
930     return out
931
932 def convert2beamer_selected(lines):
933     selected_lines = filter_selected_lines(lines)
934     out = convert2beamer_full(selected_lines)
935     return out
936
937 def include_file(line):
938     """ Extract filename to include.
939
940     @param line string
941         a line that might include an inclusion
942     @return string or None
943         if the line contains an inclusion, return the filename,
944         otherwise return None
945     """
946     p = re.compile("\>\>\>(.*?)\<\<\<", re.VERBOSE)
947     if p.match(line):
948         filename = p.sub(r"\1", line)
949         return filename
950     else:
951         return None
952
953 def include_file_recursive(base):
954     stack = []
955     output = []
956     def recurse(file_):
957         stack.append(file_)
958         nowikimode = False
959         codemode = False
960         for line in get_lines_from_cache(file_):
961             if nowikimode or codemode:
962                 if nowikiendre.match(line):
963                     nowikimode = False
964                 elif codestartre.match(line):
965                     codemode = False
966                 output.append(line)
967             elif nowikistartre.match(line):
968                 output.append(line)
969                 nowikimode = True
970             elif codestartre.match(line):
971                 output.append(line)
972                 codemode = True
973             else:
974                 include = include_file(line)
975                 if include is not None:
976                     if include in stack:
977                         raise IncludeLoopException('Loop detected while trying '
978                                 "to include: '%s'.\n" % include +
979                                 'Stack: '+ "->".join(stack))
980                     else:
981                         recurse(include)
982                 else:
983                     output.append(line)
984         stack.pop()
985     recurse(base)
986     return output
987
988 def convert2beamer_full(lines):
989     """ convert to LaTeX beamer"""
990     state = w2bstate()
991     result = [''] #start with one empty line as line 0
992     codebuffer = []
993     autotemplatebuffer = []
994
995     nowikimode = False
996     codemode = False
997     autotemplatemode = False
998
999     for line in lines:
1000         (line, nowikimode) = get_nowikimode(line, nowikimode)
1001         if nowikimode:
1002             result.append(line)
1003         else:
1004             (line, _codemode) = get_codemode(line, codemode)
1005             if _codemode and not codemode: #code mode was turned on
1006                 codebuffer = []
1007             elif not _codemode and codemode: #code mode was turned off
1008                 expand_code_segment(result, codebuffer, state)
1009             codemode = _codemode
1010
1011             if codemode:
1012                 codebuffer.append(line)
1013             else:
1014                 (line, _autotemplatemode) = get_autotemplatemode(line, autotemplatemode)
1015                 if _autotemplatemode and not autotemplatemode: #autotemplate mode was turned on
1016                     autotemplatebuffer = []
1017                 elif not _autotemplatemode and autotemplatemode: #autotemplate mode was turned off
1018                     expand_autotemplate_opening(result, autotemplatebuffer, state)
1019                 autotemplatemode = _autotemplatemode
1020                 
1021                 if autotemplatemode:
1022                     autotemplatebuffer.append(line)
1023                 else:
1024                     state.current_line = len(result)
1025                     result.append(transform(line, state))
1026
1027     result.append(transform("", state))   # close open environments
1028
1029     if state.frame_opened:
1030         result.append(get_frame_closing(state))
1031     if state.autotemplate_opened:
1032         result.append(get_autotemplate_closing())
1033     
1034     #insert defverbs somewhere at the beginning
1035     expand_code_defverbs(result, state)
1036
1037     return result
1038
1039 def print_result(lines):
1040     """ print result to stdout """
1041     for l in lines:
1042         print >>sys.stdout, l
1043     return
1044
1045 def main(argv):
1046     """ check parameters, start file processing """
1047     usage = "%prog wiki2beamer [input1.txt [input2.txt ...]] > output.tex"
1048     version = "%prog (http://wiki2beamer.sf.net), version: " + VERSIONTAG
1049
1050     parser = optparse.OptionParser(usage="\n  " + usage, version=version)
1051     opts, args = parser.parse_args()
1052
1053     input_files = []
1054     if not sys.stdin.isatty():
1055         _file_cache['stdin'] = joinLines(sys.stdin.readlines())
1056         input_files.append('stdin')
1057     elif len(args) == 0:
1058         parser.error("You supplied no files to convert!")
1059
1060     input_files += args
1061     lines = []
1062     for file_ in input_files:
1063         try:
1064             lines += include_file_recursive(file_)
1065         except Exception, e:
1066             syntax_error(e, -4)
1067
1068     lines = convert2beamer(lines)
1069     print_result(lines)
1070
1071
1072 if (__name__ == "__main__"):
1073     main(sys.argv)
This page took 0.052969 seconds and 4 git commands to generate.