]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mudela-book.py
patch::: 1.1.3.tca1
[lilypond.git] / scripts / mudela-book.py
1 #!@PYTHON@
2 # All non-english comments are NOT in swedish, they are norwegian!
3
4 #  TODO:  center option (??)
5 # * One empty, not two line after a mudela{floating} should be enough
6 #   to get a new paragraph.
7 # * clean up handling of filename of inputfile
8 # * the   verbatim  option should not be visible in the created latex file
9 # * what the h.. does castingalgorithm do/mean???
10 # * the following fails because mudelabook doesn't care that the
11 #   last } after \end{mudela} finishes the marginpar:
12 #     \marginpar{
13 #     \begin{mudela}[fragment]
14 #        c d e f g
15 #     \end{mudela}}
16 # log:
17
18 # 0.3:
19 #   rewrite in Python.
20 # 0.4:
21 #   much rewritten by new author. I think the work has been split more
22 #   logical between different classes.
23 #   
24
25 import os
26 import string
27 import re
28 import getopt
29 import sys
30 import regsub
31
32 outdir = 'out'
33 program_version = '0.4'
34 default_paper_size_global = 'a4'
35 default_mudela_fontsize = '16pt'
36 force_mudela_fontsize_b = 0
37
38 EXPERIMENTAL = 0
39 out_files = []
40
41 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen', 20:'twenty', 26:'twentysix'}
42 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
43
44 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
45 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
46 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
47 section_re = re.compile ('\\\\section')
48 chapter_re = re.compile ('\\\\chapter')
49 input_re = re.compile ('^\\\\input{([^}]*)')
50 include_re = re.compile ('^\\\\include{([^}]*)')
51 begin_document_re = re.compile ('^ *\\\\begin{document}')
52 documentclass_re = re.compile('\\\\documentclass')
53 twocolumn_re = re.compile('\\\\twocolumn')
54 onecolumn_re = re.compile('\\\\onecolumn')
55 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
56 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
57 boundingBox_re = re.compile('%%BoundingBox: ([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
58
59 def file_exist_b(name):
60     try: 
61         f = open(name)
62     except IOError:
63         return 0
64     f.close ()
65     return 1
66
67 def ps_dimention(fname):
68     fd = open(fname)
69     lines = fd.readlines()
70     for line in lines:
71         s = boundingBox_re.search(line)
72         if s:
73             break
74     return (int(s.groups()[2])-int(s.groups()[0]), 
75             int(s.groups()[3])-int(s.groups()[1]))
76
77
78 class CompileStatus:
79     pass
80
81 def file_mtime (name):
82     return os.stat (name)[8] #mod time
83
84 def need_recompile_b(infile, outfile):
85     indate = file_mtime (infile)
86     try:
87         outdate = file_mtime (outfile)
88         return indate > outdate
89     except os.error:
90         return 1
91
92      
93 #
94 # executes os.system(command) if infile is newer than
95 # outfile or outfile don't exist
96 #
97 def compile (command, workingdir, infile, outfile):
98     indate = file_mtime (workingdir+infile)
99     try:
100         outdate = file_mtime (workingdir+outfile)
101         recompile = indate > outdate
102
103     except os.error:
104         recompile = 1
105
106     if recompile:
107         sys.stderr.write ('invoking `%s\'\n' % command)
108         if workingdir == '':
109             status = os.system (command)
110         else:
111             status = os.system ('cd %s; %s' %(workingdir, command))
112         if status:
113             raise CompileStatus
114
115
116 class PaperDef:
117     __onecolumn_linewidth = {
118         'a4':{'10pt': 345, '11pt': 360, '12pt':390},
119         'a5':{'10pt': 276, '11pt': 276, '12pt':276},
120         'b5':{'10pt': 345, '11pt': 356, '12pt':356},
121         'letter':{'10pt': 345, '11pt': 360, '12pt':390},
122         'legal':{'10pt': 345, '11pt': 360, '12pt':390},
123         'executive':{'10pt': 345, '11pt': 360, '12pt':379}
124         }
125     __twocolumn_linewidth = {
126          'a4':{'10pt': 167, '11pt': 175, '12pt':190},
127         'a5':{'10pt': 133, '11pt': 133, '12pt':133},
128         'b5':{'10pt': 167, '11pt': 173, '12pt':173},
129         'letter':{'10pt': 167, '11pt': 175, '12pt':190},
130         'legal':{'10pt': 167, '11pt': 175, '12pt':190},
131         'executive':{'10pt': 167, '11pt': 175, '12pt':184}
132         }
133     __numcolumn = 1
134     __fontsize = '11pt'
135     #
136     # init
137     #
138     def __init__(self):
139         self.__papersize = default_paper_size_global
140     def set_papersize (self, p):
141         if not self.__onecolumn_linewidth.has_key(p):
142             print "warning:unsupported papersize", p, \
143                   "will use", default_paper_size_global
144             self.__papersize = default_paper_size_global
145         else:
146             self.__papersize = p
147     def set_latex_fontsize(self, pt):
148         self.__fontsize = pt
149     def get_linewidth (self):
150         if self.__numcolumn == 1:
151             return self.__onecolumn_linewidth[self.__papersize][self.__fontsize]
152         else:
153             return self.__twocolumn_linewidth[self.__papersize][self.__fontsize]
154     def onecolumn (self):
155         self.__numcolumn = 1
156     def twocolumn (self):
157         self.__numcolumn = 2
158
159
160 class Mudela_output:
161     def __init__ (self, basename):
162         self.basename = basename
163         # it's an integer!
164         self.feta_pt_size = fontsize_pt2i[default_mudela_fontsize]
165         self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
166         self.file = open (self.temp_filename, 'w')
167         # 'tex' or 'eps'
168         self.graphic_type = 'tex'
169         self.fragment = 0
170     def write (self, line):
171         # match only if there is nothing but whitespace before \begin
172         if re.search('^\s*\\\\begin{mudela}', line):
173             self.scan_begin_statement(line)
174             self.write_red_tape()
175         else:
176             self.file.write (line)
177     def scan_begin_statement(self, line):
178         global force_mudela_fontsize_b;
179         r  = begin_mudela_opts_re.search(line)
180         if r:
181             o = r.group()[1:][:-1]
182             optlist =  re.compile('[ ,]*').split(o)
183         else:
184             optlist = []
185         if 'floating' in optlist:
186             self.graphic_type = 'eps'
187         else:
188             self.graphic_type = 'tex'
189         if 'fragment' in optlist:
190             self.fragment = 1
191         else:
192             self.fragment = 0
193         for pt in fontsize_pt2i.keys():
194             if pt in optlist:
195                 if force_mudela_fontsize_b:
196                     self.feta_pt_size = fontsize_pt2i[default_mudela_fontsize]
197                 else:
198                     self.feta_pt_size = fontsize_pt2i[pt]
199     def write_red_tape(self):
200         self.file.write ('\\include \"paper%d.ly\"\n' % self.feta_pt_size)
201         s = fontsize_i2a[self.feta_pt_size]
202         if self.fragment:
203             self.file.write("default_paper = \\paper {"
204                             + "\\paper_%s\n linewidth = -1.\\pt;" % s
205                             + "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
206             self.file.write("\\score{\n\\notes{") #HACK
207         else:
208             self.file.write ("default_paper = \\paper {"
209                              + "\\paper_%s\n linewidth = %i.\\pt;" % \
210                              (s, Paper.get_linewidth()) \
211                              + "castingalgorithm = \Wordwrap; indent = 2.\cm;\n}")
212     def close (self):
213         if self.fragment:
214             self.file.write ('}\\paper { \\default_paper; } }\n')
215         self.file.close ()
216
217         inf = self.basename + '.ly'
218         outf = self.basename + '.tex'
219         if not file_exist_b (inf):
220             status = 1
221         else:
222             status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
223         if status:
224             os.rename (self.temp_filename, inf)
225         if EXPERIMENTAL:
226             if need_recompile_b(inf, outf):
227                 print "Need recompile"
228                 out_files.append((self.graphic_type, inf))
229         else:
230             compile ('lilypond  -o %s %s;'%  (self.basename, inf), '', inf, outf)
231             if self.graphic_type == 'eps':
232                 bname = self.basename[string.rfind(self.basename, '/')+1:]
233                 tex_name = bname+'.tex'
234                 dvi_name = bname+'.dvi'
235                 eps_name = bname+'.eps'
236                 compile ('tex %s' % tex_name, outdir, tex_name, dvi_name)
237                 compile ('dvips -E -o %s %s' % (eps_name, dvi_name), outdir, dvi_name, eps_name)
238     def insert_me_string(self):
239         "Returns a string that can be used directly in latex."
240         if self.graphic_type == 'tex':
241             return '\\preMudelaExample\\input %s\n\postMudelaExample\n' % self.basename
242         elif self.graphic_type == 'eps':
243             ps_dim = ps_dimention('%s.eps' % self.basename)
244             return '\\parbox{%ipt}{\includegraphics{%s.eps}}' % (ps_dim[0], self.basename)
245         else:
246             print "Unsupported graphic type '%s'" % self.graphic_type
247             sys.exit(1)
248
249 class Tex_output:
250     def __init__ (self, name):
251         self.output_fn = '%s/%s' % (outdir, name)
252         self.file = open (self.output_fn , 'w')
253     def open_mudela (self, basename):
254         self.mudela_basename = basename
255     def open_verbatim (self):
256         self.file.write ('\\begin{verbatim}\n')
257     def close_verbatim (self):
258         self.file.write ('\\end{verbatim}\n')
259     def write (self, s):
260         self.file.write (s)
261
262 class Tex_input:
263     def __init__ (self,name):
264         # HACK
265         print "name=", name
266         try:
267             self.infile = open (name)
268         except IOError:
269             if (name[-4:] != '.tex') and (name[-4:] != '.doc'):
270                 try:
271                     name = name + '.doc'
272                     self.infile = open (name)
273                 except IOError:
274                     name = name + '.tex'
275                     self.infile = open(name)
276         self.filename = name
277             
278     def get_lines (self):
279         lines = self.infile.readlines ()
280         (retlines, retdeps) = ([],[self.filename])
281         for line in lines:
282             r = input_re.search (line)
283             ri = include_re.search (line)
284             if r:
285                 try:
286                     t = Tex_input (r.groups()[0])
287                     ls =t.get_lines ()
288                 except IOError:
289                     # HACK should not warn about files like lilyponddefs, only
290                     # files we think is a part of the document and include
291                     # mudela that need parsing
292                     print "warning: can't find file " % r.grops()[0]
293                     ls = [[line], []]
294                 retlines = retlines + ls[0]
295                 retdeps = retdeps + ls[1]
296             elif ri:
297                 try:
298                     t = Tex_input (ri.groups()[0])
299                     ls =t.get_lines ()
300                     ls[0].insert(0, '\\newpage')
301                     ls[0].append('\\newpage')
302                 except IOError:
303                     print "warning: can't find include file:",  ri.groups()[0]
304                     ls = [[line], []];
305                 retlines = retlines + ls[0]
306                 retdeps = retdeps + ls[1]
307             else:
308                 retlines.append (line)
309         return (retlines, retdeps)
310
311
312 class Main_tex_input(Tex_input):
313     def __init__ (self, name, outname):
314                 
315         Tex_input.__init__ (self, name) # ugh
316
317         self.outname = outname
318         self.chapter = 0
319         self.section = 0
320         self.fine_count =0
321         self.mudtex = Tex_output (self.outname)
322         self.mudela = None
323         self.deps = []
324         self.verbatim = 0
325         # set to 'mudela' when we are processing mudela code,
326         # both verbatim and graphic-to-be
327         self.mode = 'latex'
328     def set_sections (self, l):
329         if section_re.search (l):
330             self.section = self.section + 1
331         if chapter_re.search (l):
332             self.section = 0
333             self.chapter = self.chapter + 1
334
335     def gen_basename (self):
336         return '%s/%s-%d.%d.%d' % (outdir, self.outname,self.chapter,self.section,self.fine_count)
337
338     def extract_papersize_from_documentclass(self, line):
339         pre = re.search('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}', line)
340         if not pre:
341             return default_paper_size_global
342         return pre.groups()[0]
343     def extract_fontsize_from_documentclass(self, line):
344         if re.search('\\\\documentclass\[[^\]]*\]\{[^\}]*\}', line):
345             r = re.search('[ ,\[]*([0-9]*pt)', line)
346             if r:
347                 return r.groups()[0]
348         return '10pt'
349     def do_it(self):
350         preMudelaDef = postMudelaDef = 0
351         (lines, self.deps) = self.get_lines ()
352         for line in lines:
353             if documentclass_re.search (line):
354                 Paper.set_papersize (self.extract_papersize_from_documentclass (line) )
355                 Paper.set_latex_fontsize (self.extract_fontsize_from_documentclass (line) )
356             elif twocolumn_re.search (line):
357                 Paper.twocolumn ()
358             elif onecolumn_re.search (line):
359                 Paper.onecolumn ()
360             elif preMudelaExample_re.search (line):
361                 preMudelaDef = 1
362             elif postMudelaExample_re.search (line):
363                 postMudelaDef = 1
364             elif begin_document_re.search (line):
365                 if not preMudelaDef:
366                     self.mudtex.write ('\\def\\preMudelaExample{}\n')
367                 if not postMudelaDef:
368                     self.mudtex.write ('\\def\\postMudelaExample{}\n')
369             elif begin_mudela_re.search (line):
370                 if __debug__:
371                     if self.mode == 'mudela':
372                         raise AssertionError
373                 self.mode = 'mudela'
374                 r  = begin_mudela_opts_re.search (line)
375                 if r:
376                     o = r.group()[1:][:-1]
377                     optlist =  re.compile('[ ,]*').split(o)
378                 else:
379                     optlist = []
380                 if 'verbatim' in optlist:
381                     self.verbatim = 1
382                     self.mudtex.open_verbatim ()
383                 else:
384                     self.verbatim = 0
385                     self.mudela = Mudela_output (self.gen_basename ())
386
387             elif end_mudela_re.search (line):
388                 if __debug__:
389                     if self.mode != 'mudela':
390                         raise AssertionError
391                 if self.mudela:
392                     self.mudela.close ()
393                     self.mudtex.write (self.mudela.insert_me_string())
394                     del self.mudela
395                     self.mudela = None
396                     self.fine_count = self.fine_count + 1
397                 else:                    
398                     self.mudtex.write (line)
399                     self.mudtex.close_verbatim ()
400                 self.mode = 'latex'
401                 continue
402
403             if self.mode == 'mudela' and not self.verbatim:
404                 self.mudela.write (line)
405             else:
406                 self.mudtex.write (line)
407                 self.set_sections(line)
408         del self.mudtex
409                 
410
411 def help():
412     sys.stdout.write("Usage: mudela-book [options] FILE\n"
413                  + "Generate hybrid LaTeX input from Latex + mudela"
414                  + "Options:\n"
415                  + "  -h, --help             print this help\n"
416                  + "  -d, --outdir=DIR       directory to put generated files\n" 
417                  + "  -o, --outname=FILE     prefix for filenames\n"
418                  + "  --mudela-fontsize=??pt default fontsize when no parameter for \\begin{mudela}\n"
419                  + "  --force-mudela-fontsize=??pt force fontsize for all inline mudela\n"
420                      )
421     sys.exit (0)
422
423
424 def write_deps (fn, out,  deps):
425         out_fn = outdir + '/' + fn
426         print '\`writing \`%s\'\n\'' % out_fn
427         
428         f = open (out_fn, 'w')
429         f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
430                               reduce (lambda x,y: x + ' '+ y, deps)))
431         f.close ()
432
433 def identify():
434     sys.stderr.write('*** Lokal versjon av mudela-book ***\n')
435     sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
436
437 def main():
438     global default_mudela_fontsize, force_mudela_fontsize_b, outdir
439     outname = ''
440     try:
441         (options, files) = getopt.getopt(
442             sys.argv[1:], 'hd:o:', ['outdir=', 'outname=', 'mudela-fontsize=',
443                                     'force-mudela-fontsize=', 'help', 'dependencies'])
444     except getopt.error, msg:
445         print "error:", msg
446         sys.exit(1)
447         
448     do_deps = 0
449     for opt in options:
450         o = opt[0]
451         a = opt[1]
452         if o == '--outname' or o == '-o':
453             outname = a
454         if o == '--outdir' or o == '-d':
455             outdir = a
456         if o == '--help' or o == '-h':
457             help ()
458         if o == '--dependencies':
459             do_deps = 1
460         if o == '--mudela-fontsize':
461             default_mudela_fontsize = a
462         if o == '--force-mudela-fontsize':
463             default_mudela_fontsize = a
464             force_mudela_fontsize_b = 1
465
466     if outdir[-1:] != '/':
467         outdir = outdir + '/'
468
469     if not file_exist_b(outdir):
470         os.system('mkdir %s' % outdir)
471
472     if not fontsize_pt2i.has_key(default_mudela_fontsize):
473         print "warning: fontsize %s is not supported using 16pt" % default_mudela_fontsize
474         default_mudela_fontsize = '16pt'
475     
476     for f in files:
477         my_outname = outname
478         if not my_outname:
479             my_outname = regsub.sub ('\\(.*\\)\\.doc', '\\1', f)
480
481         my_depname = my_outname + '.dep'
482         
483         inp = Main_tex_input (f, my_outname)
484         inp.do_it ()
485
486     if do_deps:
487                 write_deps (my_depname, my_outname, inp.deps)
488
489
490
491 identify()
492 Paper = PaperDef()
493 main()
494 if EXPERIMENTAL:
495     print "outfile:", out_files
496     for i in out_files:
497         print "skal gjøre", i