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