]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mudela-book.py
release: 1.1.62
[lilypond.git] / scripts / mudela-book.py
1 #!@PYTHON@
2 #
3 # The bugs you find are made by Tom Cato Amundsen <tomcato@xoommail.com>
4 # Send patches/questions/bugreports to a mailinglist:
5 #  gnu-music-discuss@gnu.org
6 #  bug-gnu-music@gnu.org
7 #  help-gnu-music@gnu.org
8 #
9 #  TODO:
10 # * center option (??)
11 # * make mudela-book understand usepackage{geometry}
12 # * check that linewidth set in \paper is not wider than actual linewidth?
13 # * the following fails because mudelabook doesn't care that the
14 #   last } after \end{mudela} finishes the marginpar:
15 #     \marginpar{
16 #     \begin{mudela}
17 #        c d e f g
18 #     \end{mudela}}
19 # * force-verbatim is maybe not that useful since latex fails with footnotes,
20 #   marginpars and others
21 # log:
22 # 0.3:
23 #   rewrite in Python.
24 # 0.4:
25 #   much rewritten by new author. I think the work has been split more
26 #   logical between different classes.
27 # 0.4.1:
28 #   - cleanup
29 #   - speedup, do all mudela parsing at the same time to avoid multiple
30 #     guile startup that takes some seconds on my computer
31 # 0.4.2:
32 #   - fixed default latex fontsize, it should be 10pt not 11pt
33 #   - verbatim option no longer visible
34 #   - mudela-book produces .dvi output
35 #   - change to use castingalgorithm = \Gourlay instead of \Wordwrap. It gives
36 #     better result on small linewidths.
37 #   - mudela-book-doc.doc rewritten
38 # 0.5:
39 #   - junked floating and fragment options, added eps option
40 #   - mudela-book will scan the mudela code to find out if it has to add
41 #     paper-definition and \score{\notes{...}}
42 #   - possible to define commands that look like this: \mudela{ c d e }
43 #   - don't produce .dvi output, it was a silly idea...
44 # 0.5.1:
45 #   - removed init/mudela-book-defs.py, \mudela is defined inside mudela-book
46 #   - fragment and nonfragment options will override autodetection of type of
47 #     in mudela-block (voice contents or complete code)
48 # 0.5.2:
49 #   - fixed verbatim option behaviour: don't show \begin{mudela} \end{mudela}
50 #     and show both mudela code and music
51 #   - veryverbatim option, same as verbatim but show \begin{mudela}
52 #     and \end{mudela}. (saves keystrokes in mudela-book-doc.doc)
53 #   - intertext="bla bla bla" option
54 #   - mudela-book now understand latex \begin{verbatim}
55 # 0.5.3:
56 #   - bf: \mudela{ \times 2/3{...} }
57 #        * \t in \times is not tab character and
58 #        * dont treat the first '}' as command ending
59 # 0.5.4: (Mats B)
60 #   - .fly and .sly files in \mudelafile{} are treated as standalone Lilypond.
61 #   - Fragments, .fly and .sly files are in \relative mode.
62 # 0.5.5: (Mats B)
63 #   - bf: Default fragments have linewidth=-1.0
64 #   - Added 'singleline' and 'multiline' options.
65 # 0.5.6:
66 #   - \mudelafile{} set linewidth correct, -1 for .sly and texlinewidth for .fly
67 #   - changes to Mudela_output
68 #   - changed RE to search for pre/postMudelaExample to make it possible to
69 #     comment out a definition.
70 #   - use sys.stderr and sys.stdout instead of print
71 # 1.1.62
72 #   - junked separate versioning
73 #   - pass -I options to lily.
74 #   - portability stuff (os.sep).
75
76 import os
77 import string
78 import re
79 import getopt
80 import sys
81 import __main__
82
83 outdir = 'out'
84 initfile = ''
85 program_version = '@TOPLEVEL_VERSION@'
86 include_path = ['.']
87
88 out_files = []
89
90 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen',
91                 20:'twenty', 26:'twentysix'}
92 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
93
94 # perhaps we can do without this?
95
96 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
97 begin_verbatim_re = re.compile ('^ *\\\\begin{verbatim}')
98 end_verbatim_re = re.compile ('^ *\\\\end{verbatim}')
99 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
100 extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt')
101 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
102 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
103 section_re = re.compile ('\\\\section')
104 chapter_re = re.compile ('\\\\chapter')
105 input_re = re.compile ('^\\\\input{([^}]*)')
106 include_re = re.compile ('^\\\\include{([^}]*)')
107 begin_document_re = re.compile ('^ *\\\\begin{document}')
108 documentclass_re = re.compile('\\\\documentclass')
109 twocolumn_re = re.compile('\\\\twocolumn')
110 onecolumn_re = re.compile('\\\\onecolumn')
111 mudela_file_re = re.compile('\\\\mudelafile{([^}]+)}')
112 file_ext_re = re.compile('.+\\.([^.}]+$)')
113 preMudelaExample_re = re.compile('^\s*\\\\def\\\\preMudelaExample')
114 postMudelaExample_re = re.compile('^\s*\\\\def\\\\postMudelaExample')
115 boundingBox_re = re.compile('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)')
116 intertext_re = re.compile("intertext=\"([^\"]*)\"")
117
118 def file_exist_b(name):
119     try: 
120         f = open(name)
121     except IOError:
122         return 0
123     f.close ()
124     return 1
125
126 def ps_dimention(fname):
127     fd = open(fname)
128     lines = fd.readlines()
129     for line in lines:
130         s = boundingBox_re.search(line)
131         if s:
132             break
133     return (int(s.groups()[2])-int(s.groups()[0]), 
134             int(s.groups()[3])-int(s.groups()[1]))
135
136
137 def find_file (name):
138     for a in include_path:
139         try:
140             nm = os.path.join (a, name)
141             f = open (nm)
142             return nm
143         except IOError:
144             pass
145     return ''
146
147
148 class CompileStatus:
149     pass
150 class SomethingIsSeriouslyBroken:
151     pass
152
153 def file_mtime (name):
154     return os.stat (name)[8] #mod time
155
156 def need_recompile_b(infile, outfile):
157     indate = file_mtime (infile)
158     try:
159         outdate = file_mtime (outfile)
160         return indate > outdate
161     except os.error:
162         return 1
163
164 #
165 # executes os.system(command) if infile is newer than
166 # outfile or outfile don't exist
167 #
168 def compile (command, workingdir, infile, outfile):
169     "Test if infile is newer than outfile. If so, cd to workingdir"
170     "and execute command"
171     print "COMPILE!!"
172     indate = file_mtime (workingdir+infile)
173     try:
174         outdate = file_mtime (workingdir+outfile)
175         recompile = indate > outdate
176
177     except os.error:
178         recompile = 1
179
180     if recompile:
181         sys.stderr.write ('invoking `%s\'\n' % command)
182         if workingdir == '':
183             status = os.system (command)
184         else:
185             status = os.system ('cd %s; %s' %(workingdir, command))
186         if status:
187             raise CompileStatus
188
189 class Properties:
190     #
191     # init
192     #
193     def __init__(self):
194         self.__linewidth = {
195             1: {'a4':{10: 345, 11: 360, 12: 390},
196                 'a5':{10: 276, 11: 276, 12: 276},
197                 'b5':{10: 345, 11: 356, 12: 356},
198                 'letter':{10: 345, 11: 360, 12: 390},
199                 'legal': {10: 345, 11: 360, 12: 390},
200                 'executive':{10: 345, 11: 360, 12: 379}},
201             2: {'a4':{10: 167, 11: 175, 12: 190},
202                 'a5':{10: 133, 11: 133, 12: 133},
203                 'b5':{10: 167, 11: 173, 12: 173},
204                 'letter':{10: 167, 11: 175, 12: 190},
205                 'legal':{10: 167, 11: 175, 12: 190},
206                 'executive':{10: 167, 11: 175, 12: 184}}}
207         # >0 --> force all mudela to this pt size
208         self.force_mudela_fontsize = 0
209         self.force_verbatim_b = 0
210         self.__data = {
211             'mudela-fontsize' : {'init': 16},
212             'papersize' : {'init': 'a4'},
213             'num-column' : {'init': 1},
214             'tex-fontsize' : {'init': 10}
215             }
216     def clear_for_new_file(self):
217         for var in self.__data.keys():
218             self.__data[var] = {'init': self.__data[var]['init']}
219     def clear_for_new_block(self):
220         for var in self.__data.keys():
221             if self.__data[var].has_key('block'):
222                 del self.__data[var]['block']
223     def __get_variable(self, var):
224         if self.__data[var].has_key('block'):
225             return self.__data[var]['block']
226         elif self.__data[var].has_key('file'):
227             return self.__data[var]['file']
228         else:
229             return self.__data[var]['init']
230     def setPapersize(self, size, requester):
231         self.__data['papersize'][requester] = size
232     def getPapersize(self):
233         return self.__get_variable('papersize')
234     def setMudelaFontsize(self, size, requester):
235         self.__data['mudela-fontsize'][requester] = size
236     def getMudelaFontsize(self):
237         if self.force_mudela_fontsize:
238             return self.force_mudela_fontsize
239         return self.__get_variable('mudela-fontsize')
240     def setTexFontsize(self, size, requester):
241         self.__data['tex-fontsize'][requester] = size
242     def getTexFontsize(self):
243         return self.__get_variable('tex-fontsize')
244     def setNumColumn(self, num, requester):
245         self.__data['num-column'][requester] = num
246     def getNumColumn(self):
247         return self.__get_variable('num-column')
248     def getLineWidth(self):
249         return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
250
251
252 class Mudela_output:
253     """ Using only self.code_type to deside both value of linewith and
254     if we have to put code into \score{...} was silly. Now we use:
255     self.code_type:  show us what need to be added.
256                         None : init value
257                         'NOTES' : add \context Voice{ ... }
258                         'CONTEXT' : add \score{ ... }
259                         'COMPLETE' : jupp
260     self.single_line_b:   0 : linewidth=-1  1: linewith=textwidth
261     """
262     def __init__ (self, basename):
263         self.basename = basename
264         self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
265         self.file = open (self.temp_filename, 'w')
266         self.__lines = []
267         # 'tex' or 'eps'
268         self.graphic_type = 'tex'
269         self.code_type = None
270         self.single_line_b = 1
271         self.optlist = []
272     def write (self, line):
273         # match only if there is nothing but whitespace before \begin.
274         # we should not have to do this RE for every line
275         if re.search('^\s*\\\\begin{mudela}', line):
276             r  = begin_mudela_opts_re.search(line)
277             if r:
278                 o = r.group()[1:-1]
279                 self.optlist =  re.compile('[\s,]*').split(o)
280             else:
281                 self.optlist = []
282         else: # ugh this is NOT bulletproof...
283             if not self.code_type:
284                 if re.search('^\s*%', line) or re.search('^\s*$', line):
285                     pass
286                 elif re.search('^\s*\\\\context', line):
287                     self.code_type = 'CONTEXT'
288                     self.single_line_b = 0
289                 elif re.search('^\s*\\\\score', line) or \
290                      re.search('^\s*\\\\paper', line) or \
291                      re.search('^\s*\\\\header', line) or \
292                      re.search('^\s*\\\\version', line) or \
293                      re.search('^\s*\\\\include', line) or \
294                      re.search('^\s*[A-Za-z]*\s*=', line):
295                     self.code_type = 'COMPLETE'
296                     self.single_line_b = 0
297                 else:
298                     self.code_type = 'NOTES'
299                     self.single_line_b = 1
300             self.__lines.append(line)
301     def write_red_tape(self):
302         if 'eps' in self.optlist:
303             self.graphic_type = 'eps'
304             #self.single_line_b = 1
305         if 'fragment' in self.optlist:
306             self.code_type = 'NOTES'
307         if 'nonfragment' in self.optlist:
308             self.code_type = 'COMPLETE'
309         if 'multiline' in self.optlist:
310             self.single_line_b = 0
311         for pt in fontsize_pt2i.keys():
312             if pt in self.optlist:
313                 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
314         self.file.write ('\\include \"paper%d.ly\"\n' \
315                          % Props.getMudelaFontsize())
316                          
317         s = fontsize_i2a[Props.getMudelaFontsize()]
318         if 'singleline' in self.optlist:
319             self.single_line_b = 1
320         if self.single_line_b:
321             linewidth_str = 'linewidth = -1.\cm;'
322         else:
323             linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth()
324         self.file.write("\\paper {"
325                         + "\\paper_%s " % s
326                         + linewidth_str
327                         + "castingalgorithm = \Gourlay; \n}")
328                         #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
329         if self.code_type == 'CONTEXT':
330             self.file.write('\\score{\n\\notes\\relative c{')
331         if self.code_type == 'NOTES' :
332             self.file.write('\\score{\n\\notes\\relative c{\\context Voice{')
333     def close (self):
334         self.write_red_tape()
335         for l in self.__lines:
336             self.file.write(l)
337         if self.code_type == 'CONTEXT':
338             self.file.write('}}')
339         elif self.code_type == 'NOTES':
340             self.file.write('}}}')
341         self.file.close()
342
343         inf = outdir + self.basename + '.ly'
344         outf = outdir + self.basename + '.tex'
345         if not os.path.isfile(inf):
346             status = 1
347         else:
348             status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
349         if status:
350             os.rename (self.temp_filename, inf)
351
352         recompile_b =  need_recompile_b(inf, outf)
353         if recompile_b:
354             out_files.append((self.graphic_type, inf))
355         return recompile_b
356
357     def insert_me_string(self):
358         "ugh the name of this function is wrong"
359         if self.graphic_type == 'tex':
360             return ['tex', self.basename]
361         elif self.graphic_type == 'eps':
362             return ['eps', self.basename]
363         else:
364             raise SomethingIsSeriouslyBroken
365
366 class Tex_output:
367     def __init__ (self, name):
368         self.output_fn = '%s/%s' % (outdir, name)
369         self.__lines = []
370     def open_verbatim (self, line, level):
371         self.__lines.append('\\begin{verbatim}\n')
372         if level == 2:
373             s = re.sub('veryverbatim[\s,]*', '', line)
374             s = re.sub('intertext=\"([^\"]*)\"[\s,]*', '', s)
375             s = re.sub(',\s*\]', ']', s)
376             s = re.sub('\[\]', '', s)
377             self.__lines.append(s);
378     def close_verbatim (self):
379         self.__lines.append('\\end{verbatim}\n')
380     def write (self, s):
381         self.__lines.append(s)
382     def create_graphics(self):
383         s = ''
384         g_vec = []
385         for line in self.__lines:
386             if type(line)==type([]):
387                 g_vec.append(line)
388         for g in g_vec:
389             if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
390                     s = s + ' ' + g[1]+'.ly'
391
392         lilyoptions = ''
393         for inc in __main__.include_path :
394                 p = inc[:]
395                 if p[0] <> os.sep and outdir:           # UGH-> win32 ?
396                         p = '..' + os.sep + p 
397                 lilyoptions = lilyoptions + " -I \"%s\"" % p
398                 
399         if s != '':
400             cmd = 'cd %s; lilypond %s %s' %(outdir, lilyoptions, s)
401             sys.stderr.write ('invoking command %s'  % cmd)
402             e = os.system(cmd)
403             if e:
404                 sys.stderr.write("error: lilypond exited with value %i\n" % e)
405                 sys.exit(e)
406         for g in g_vec:
407             if g[0] == 'eps':
408                 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
409                 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
410                         g[1]+'.dvi', g[1]+'.eps')
411     def write_outfile(self):
412         file = open(self.output_fn+'.latex', 'w')
413         file.write('% Created by mudela-book\n')
414         last_line = None
415         for line in self.__lines:
416             if type(line)==type([]):
417                 if last_line == '\n':
418                     file.write(r'\vspace{0.5cm}')
419                 if line[0] == 'tex':                    
420                     file.write('\\preMudelaExample \\input %s \\postMudelaExample\n'\
421                                % (line[1]+'.tex'))
422                 if line[0] == 'eps':
423                     ps_dim = ps_dimention(outdir+line[1]+'.eps')
424                     file.write('\\noindent\\parbox{%ipt}{\includegraphics{%s}}\n' \
425                                % (ps_dim[0], line[1]+'.eps'))
426             else:
427                 file.write(line)
428             if type(last_line)==type([]):
429                 if line=='\n':
430                     file.write(r'\vspace{0.5cm}')
431             last_line = line
432         file.close()
433
434 # given parameter s="\mudela[some options]{CODE} some text and commands"
435 # it returns a tuple:
436 #    (CODE, integer)
437 # where the last number is the index of the ending '}'
438 def extract_command(s):
439     start_found_b = 0
440     count = 0
441     start = 0
442     for idx in range(len(s)):
443         if s[idx] == '{':
444             if not start_found_b:
445                 start = idx
446                 start_found_b = 1
447             count = count + 1
448         if s[idx] == '}':
449             count = count - 1
450         if (start_found_b == 1) and (count == 0):
451             break
452     return s[start+1:idx], idx
453
454 class Tex_input:
455     def __init__ (self, filename):
456         for fn in [filename, filename+'.tex', filename+'.doc']:
457             try:
458                 self.infile = open (fn)
459                 self.filename = fn
460                 return
461             except:
462                 continue
463         raise IOError
464
465     def get_lines (self):
466         lines = self.infile.readlines ()
467         (retlines, retdeps) = ([],[self.filename])
468         for line in lines:
469             r_inp = input_re.search (line)
470             r_inc = include_re.search (line)
471
472             # Filename rules for \input :
473             # input: no .tex ext
474             # 1. will search for file with exact that name (tex-input.my will be found)
475             # 2. will search for file with .tex ext, (tex-input.my
476             #    will find tex-input.my.tex)
477             # input: with .tex ext
478             # 1. will find exact match
479             
480             # Filename rules for \include :
481             # 1. will only find files with name given to \include + .tex ext
482             if r_inp:
483                 try:
484                     t = Tex_input (r_inp.groups()[0])
485                     ls = t.get_lines ()
486                     retlines = retlines + ls[0]
487                     retdeps = retdeps + ls[1]
488                 except:
489                     sys.stderr.write("warning: can't find %s, let's hope latex will\n" % r_inp.groups()[0])
490                     retlines.append (line)
491             elif r_inc:
492                 try:
493                     t = Tex_input (r_inc.groups()[0]+'.tex')
494                     ls =t.get_lines ()
495                     ls[0].insert(0, '\\newpage\n')
496                     ls[0].append('\\newpage\n')
497                     retlines = retlines + ls[0]
498                     retdeps = retdeps + ls[1]
499                 except:
500                     sys.stderr.write("warning: can't find %s, let's hope latex will" % r_inc.groups()[0])
501                     retlines.append (line)
502             else:
503                 # This code should be rewritten, it looks terrible
504                 r_mud = defined_mudela_cmd_re.search(line)
505                 if r_mud:
506                     # TODO document this
507                     ss = "\\\\verb(?P<xx>[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \
508                          % re.escape(r_mud.group()[1:])
509                     # just append the line if the command is inside \verb|..|
510                     if re.search(ss, line):
511                         retlines.append(line)
512                         continue
513                     while 1:
514                         opts = r_mud.groups()[2]
515                         cmd_start_idx = r_mud.span()[0]
516                         if cmd_start_idx > 0:
517                             retlines.append(line[:cmd_start_idx])
518                             
519                         cmd_data, rest_idx = extract_command(line[cmd_start_idx:])
520                         rest_idx = rest_idx + cmd_start_idx + 1
521                         if opts == None:
522                             opts = ''
523                         else:
524                             opts = ', '+opts
525                         
526                         v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n')
527                         for l in v[1:-1]:
528                             l = string.replace(l, '\\fontoptions', opts)
529                             l = string.replace(l, '\\maininput', cmd_data)
530                             retlines.append(l)
531                         r_mud = defined_mudela_cmd_re.search(line[rest_idx:])
532                         if not r_mud:
533                             rs = line[rest_idx:]
534                             while rs[0] == " ":
535                                 rs = rs[1:]
536                             if rs != "\n":
537                                 retlines.append(line[rest_idx:])
538                             break;
539                         line = line[rest_idx:]
540                 else:
541                     retlines.append (line)
542         return (retlines, retdeps)
543
544
545 class Main_tex_input(Tex_input):
546     def __init__ (self, name, outname):
547
548         Tex_input.__init__ (self, name) # ugh
549         self.outname = outname
550         self.chapter = 0
551         self.section = 0
552         self.fine_count =0
553         self.mudtex = Tex_output (self.outname)
554         self.mudela = None
555         self.deps = []
556         self.verbatim = 0
557         # set to 'mudela' when we are processing mudela code,
558         # both verbatim and graphic-to-be
559         self.mode = 'latex'
560     def set_sections (self, l):
561         if section_re.search (l):
562             self.section = self.section + 1
563         if chapter_re.search (l):
564             self.section = 0
565             self.chapter = self.chapter + 1
566
567     def gen_basename (self):
568         return '%s-%d.%d.%d' % (self.outname, self.chapter,
569                                 self.section, self.fine_count)
570     def extract_papersize_from_documentclass(self, line):
571         pre = extract_papersize_re.search(line)
572         if not pre:
573             return None
574         return pre.groups()[0]
575     def extract_fontsize_from_documentclass(self, line):
576         r = extract_fontsize_re.search(line)
577         if r:
578             return int(r.groups()[0])
579     def do_it(self):
580         preMudelaDef = postMudelaDef = 0
581         (lines, self.deps) = self.get_lines ()
582         #HACK
583         latex_verbatim = 0
584         for line in lines:
585             if documentclass_re.search (line):
586                 p = self.extract_papersize_from_documentclass (line)
587                 if p:
588                     Props.setPapersize(p, 'file')
589                 f = self.extract_fontsize_from_documentclass (line)
590                 if f:
591                     Props.setTexFontsize (f, 'file')
592             elif twocolumn_re.search (line):
593                 Props.setNumColumn (2, 'file')
594             elif onecolumn_re.search (line):
595                 Props.setNumColumn (1, 'file')
596             elif preMudelaExample_re.search (line):
597                 preMudelaDef = 1
598             elif postMudelaExample_re.search (line):
599                 postMudelaDef = 1
600             elif begin_verbatim_re.search (line):
601                 latex_verbatim = 1
602             elif end_verbatim_re.search (line):
603                 latex_verbatim = 0
604             elif begin_document_re.search (line):
605                 if not preMudelaDef:
606                     self.mudtex.write ('\\def\\preMudelaExample{}\n')
607                 if not postMudelaDef:
608                     self.mudtex.write ('\\def\\postMudelaExample{}\n')
609
610             elif mudela_file_re.search(line):
611                 r = mudela_file_re.search(line)
612
613                 self.mudela = Mudela_output(self.gen_basename())
614                 fn = r.group (1)
615                 full_path = find_file (fn)
616                 if not full_path:
617                     sys.stderr.write("error: can't find file '%s'\n" % fn)
618                     sys.exit (1)
619
620                 f = open (full_path, 'r')
621                 lines =f.readlines ()
622                 self.mudela.write ('%% This is a copy of file %s\n' % full_path)
623                 for x in lines:
624                     self.mudela.write (x)
625                 r = file_ext_re.search(fn)
626                 if r:
627                     if r.group(1) == 'fly':
628                         self.mudela.optlist.append('multiline')
629                 stat =self.mudela.close ()
630                 if stat:
631                         sys.stdout.write("(File %s needs recompiling)\n" % full_path)
632                 self.mudtex.write (self.mudela.insert_me_string())
633                 self.deps.append (full_path)
634                 del self.mudela
635                 self.mudela = None
636                 self.fine_count = self.fine_count + 1
637                 continue
638             elif begin_mudela_re.search (line) and not latex_verbatim:
639                 Props.clear_for_new_block()
640                 if __debug__:
641                     if self.mode == 'mudela':
642                         raise AssertionError
643                 self.mode = 'mudela'
644                 r  = begin_mudela_opts_re.search (line)
645                 if r:
646                     o = r.group()[1:][:-1]
647                     optlist =  re.compile('[ ,]*').split(o)
648                     m = intertext_re.search(r.group())
649                     if m:
650                         self.intertext = m.groups()[0]
651                     else:
652                         self.intertext = None
653                 else:
654                     optlist = []
655                 if ('veryverbatim' in optlist):
656                     self.verbatim = 2
657                 elif ('verbatim' in optlist) or (Props.force_verbatim_b):
658                     self.verbatim = 1
659                 else:
660                     self.verbatim = 0
661                 if self.verbatim:
662                     self.mudtex.open_verbatim (line, self.verbatim)
663                 self.mudela = Mudela_output (self.gen_basename ())
664                 self.mudela.write (line)
665                 continue
666             elif end_mudela_re.search (line) and not latex_verbatim:
667                 if __debug__:
668                     if self.mode != 'mudela':
669                         raise AssertionError
670
671                 if self.verbatim:
672                     if self.verbatim == 2:
673                         self.mudtex.write (line)
674                     self.mudtex.close_verbatim ()
675                 self.mudela.close ()
676                 if self.verbatim and self.intertext:
677                     self.mudtex.write(self.intertext)
678                 self.mudtex.write (self.mudela.insert_me_string())
679                 del self.mudela
680                 self.mudela = None
681                 self.fine_count = self.fine_count + 1
682                 self.mode = 'latex'
683                 continue
684
685
686             if self.mode == 'mudela':
687                 self.mudela.write (line)
688                 if self.verbatim:
689                     self.mudtex.write (line)
690             else:
691                 self.mudtex.write (line)
692                 self.set_sections(line)
693         self.mudtex.create_graphics()
694         self.mudtex.write_outfile()
695         del self.mudtex
696                 
697
698 def help():
699     sys.stdout.write("""Usage: mudela-book [options] FILE\n
700 Generate hybrid LaTeX input from Latex + mudela
701 Options:\n
702   -h, --help                     print this help
703   -d, --outdir=DIR               directory to put generated files
704   -o, --outname=FILE             prefix for filenames
705   --default-mudela-fontsize=??pt default fontsize for music
706   --force-mudela-fontsize=??pt   force fontsize for all inline mudela
707   --force-verbatim               make all mudela verbatim\n
708   --dependencies                 write dependencies
709   --include                      include path
710   --init                         mudela-book initfile
711   """
712                      )
713     sys.exit (0)
714
715
716 def write_deps (fn, out,  deps):
717         out_fn = os.path.join (outdir, fn)
718         
719         sys.stdout.write('writing `%s\'\n' % out_fn)
720         
721         f = open (out_fn, 'w')
722         target = re.sub (os.sep + os.sep, os.sep, os.path.join (outdir, out + '.latex'))
723         f.write ('%s: %s\n'% (target,
724                               reduce (lambda x,y: x + ' '+ y, deps)))
725         f.close ()
726
727 def identify():
728     sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
729
730 def main():
731     global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
732     outname = ''
733     try:
734         (options, files) = getopt.getopt(
735             sys.argv[1:], 'hd:o:I:', ['outdir=', 'outname=',
736                                     'default-mudela-fontsize=',
737                                     'force-mudela-fontsize=',
738                                     'help', 'dependencies', 'include=',
739                                     'force-verbatim', 'init='])
740     except getopt.error, msg:
741         sys.stderr.write("error: %s" % msg)
742         sys.exit(1)
743         
744     do_deps = 0
745     for opt in options:    
746         o = opt[0]
747         a = opt[1]
748         if o == '--include' or o == '-I':
749             include_path.append (a)
750         elif o == '--outname' or o == '-o':
751             if len(files) > 1:
752                 #HACK
753                 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
754                 sys.exit(1)
755             outname = a
756         elif o == '--outdir' or o == '-d':
757             outdir = a
758         elif o == '--help' or o == '-h':
759             help ()
760         elif o == '--dependencies':
761             do_deps = 1
762         elif o == '--default-mudela-fontsize':
763             if not fontsize_pt2i.has_key(a):
764                 sys.stderr.write("Error: invalid fontsize: %s" % a)
765                 sys.stderr.write("  accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt")
766                 sys.exit()
767             Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
768         elif o == '--force-mudela-fontsize':
769             if not fontsize_pt2i.has_key(a):
770                 sys.stderr.write("Error: invalid fontsize: %s" % a)
771                 sys.stderr.write("  accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt")
772                 sys.exit()
773             Props.force_mudela_fontsize = fontsize_pt2i[a]
774         elif o == '--force-verbatim':
775             Props.force_verbatim_b = 1
776         elif o == '--init':
777             initfile =  a
778     if outdir[-1:] != os.sep:
779         outdir = outdir + os.sep
780
781     # r""" ... """ means: leave escape seqs alone.
782     defined_mudela_cmd = {'mudela': r"""
783 \begin{mudela}[eps, singleline \fontoptions]
784   \context Staff <
785     \context Voice{
786       \maininput
787     }
788   >
789 \end{mudela}
790 """}
791     if initfile != '':
792         f = open(initfile)
793         s = f.read()
794         f.close()
795         d = eval(s)
796         for i in d.keys():
797             defined_mudela_cmd[i] = d[i]
798         del d
799
800     c = string.join (defined_mudela_cmd.keys(), '|')
801
802     defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c)
803
804     if not os.path.isdir(outdir):
805         os.system('mkdir %s' % outdir)
806
807     for input_filename in files:
808         Props.clear_for_new_file()
809         if outname:
810             my_outname = outname
811         else:
812             my_outname = os.path.basename(os.path.splitext(input_filename)[0])
813         my_depname = my_outname + '.dep'        
814         inp = Main_tex_input (input_filename, my_outname)
815         inp.do_it ()
816         if do_deps:
817             write_deps (my_depname, my_outname, inp.deps)
818
819 identify()
820 Props = Properties()
821 main()