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