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
9 # All non-english comments are NOT in swedish, they are norwegian!
11 # * center option (??)
12 # * make mudela-book understand usepackage{geometry}
13 # * check that linewidth set in \paper is not wider than actual linewidth?
14 # * the following fails because mudelabook doesn't care that the
15 # last } after \end{mudela} finishes the marginpar:
20 # * force-verbatim is maybe not that useful since latex fails with footnotes,
21 # marginpars and others
26 # much rewritten by new author. I think the work has been split more
27 # logical between different classes.
30 # - speedup, do all mudela parsing at the same time to avoid multiple
31 # guile startup that takes some seconds on my computer
33 # - fixed default latex fontsize, it should be 10pt not 11pt
34 # - verbatim option no longer visible
35 # - mudela-book produces .dvi output
36 # - change to use castingalgorithm = \Gourlay instead of \Wordwrap. It gives
37 # better result on small linewidths.
38 # - mudela-book-doc.doc rewritten
40 # - junked floating and fragment options, added eps option
41 # - mudela-book will scan the mudela code to find out if it has to add
42 # paper-definition and \score{\notes{...}}
43 # - possible to define commands that look like this: \mudela{ c d e }
44 # - don't produce .dvi output, it was a silly idea...
46 # - removed init/mudela-book-defs.py, \mudela is defined inside mudela-book
47 # - fragment and nonfragment options will override autodetection of type of
48 # in mudela-block (voice contents or complete code)
50 # - fixed verbatim option behaviour: don't show \begin{mudela} \end{mudela}
51 # and show both mudela code and music
52 # - veryverbatim option, same as verbatim but show \begin{mudela}
53 # and \end{mudela}. (saves keystrokes in mudela-book-doc.doc)
54 # - intertext="bla bla bla" option
55 # - mudela-book now understand latex \begin{verbatim}
65 program_version = '0.5.2'
69 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen',
70 20:'twenty', 26:'twentysix'}
71 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
73 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
74 begin_verbatim_re = re.compile ('^ *\\\\begin{verbatim}')
75 end_verbatim_re = re.compile ('^ *\\\\end{verbatim}')
76 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
77 extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt')
78 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
79 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
80 section_re = re.compile ('\\\\section')
81 chapter_re = re.compile ('\\\\chapter')
82 input_re = re.compile ('^\\\\input{([^}]*)')
83 include_re = re.compile ('^\\\\include{([^}]*)')
84 begin_document_re = re.compile ('^ *\\\\begin{document}')
85 documentclass_re = re.compile('\\\\documentclass')
86 twocolumn_re = re.compile('\\\\twocolumn')
87 onecolumn_re = re.compile('\\\\onecolumn')
88 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
89 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
90 boundingBox_re = re.compile('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)')
91 intertext_re = re.compile("intertext=\"([^\"]*)\"")
93 def file_exist_b(name):
101 def ps_dimention(fname):
103 lines = fd.readlines()
105 s = boundingBox_re.search(line)
108 return (int(s.groups()[2])-int(s.groups()[0]),
109 int(s.groups()[3])-int(s.groups()[1]))
114 class SomethingIsSeriouslyBroken:
117 def file_mtime (name):
118 return os.stat (name)[8] #mod time
120 def need_recompile_b(infile, outfile):
121 indate = file_mtime (infile)
123 outdate = file_mtime (outfile)
124 return indate > outdate
129 # executes os.system(command) if infile is newer than
130 # outfile or outfile don't exist
132 def compile (command, workingdir, infile, outfile):
133 "Test if infile is newer than outfile. If so, cd to workingdir"
134 "and execute command"
135 indate = file_mtime (workingdir+infile)
137 outdate = file_mtime (workingdir+outfile)
138 recompile = indate > outdate
144 sys.stderr.write ('invoking `%s\'\n' % command)
146 status = os.system (command)
148 status = os.system ('cd %s; %s' %(workingdir, command))
158 1: {'a4':{10: 345, 11: 360, 12: 390},
159 'a5':{10: 276, 11: 276, 12: 276},
160 'b5':{10: 345, 11: 356, 12: 356},
161 'letter':{10: 345, 11: 360, 12: 390},
162 'legal': {10: 345, 11: 360, 12: 390},
163 'executive':{10: 345, 11: 360, 12: 379}},
164 2: {'a4':{10: 167, 11: 175, 12: 190},
165 'a5':{10: 133, 11: 133, 12: 133},
166 'b5':{10: 167, 11: 173, 12: 173},
167 'letter':{10: 167, 11: 175, 12: 190},
168 'legal':{10: 167, 11: 175, 12: 190},
169 'executive':{10: 167, 11: 175, 12: 184}}}
170 # >0 --> force all mudela to this pt size
171 self.force_mudela_fontsize = 0
172 self.force_verbatim_b = 0
174 'mudela-fontsize' : {'init': 16},
175 'papersize' : {'init': 'a4'},
176 'num-column' : {'init': 1},
177 'tex-fontsize' : {'init': 10}
179 def clear_for_new_file(self):
180 for var in self.__data.keys():
181 self.__data[var] = {'init': self.__data[var]['init']}
182 def clear_for_new_block(self):
183 for var in self.__data.keys():
184 if self.__data[var].has_key('block'):
185 del self.__data[var]['block']
186 def __get_variable(self, var):
187 if self.__data[var].has_key('block'):
188 return self.__data[var]['block']
189 elif self.__data[var].has_key('file'):
190 return self.__data[var]['file']
192 return self.__data[var]['init']
193 def setPapersize(self, size, requester):
194 self.__data['papersize'][requester] = size
195 def getPapersize(self):
196 return self.__get_variable('papersize')
197 def setMudelaFontsize(self, size, requester):
198 self.__data['mudela-fontsize'][requester] = size
199 def getMudelaFontsize(self):
200 if self.force_mudela_fontsize:
201 return self.force_mudela_fontsize
202 return self.__get_variable('mudela-fontsize')
203 def setTexFontsize(self, size, requester):
204 self.__data['tex-fontsize'][requester] = size
205 def getTexFontsize(self):
206 return self.__get_variable('tex-fontsize')
207 def setNumColumn(self, num, requester):
208 self.__data['num-column'][requester] = num
209 def getNumColumn(self):
210 return self.__get_variable('num-column')
211 def getLineWidth(self):
212 return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
216 def __init__ (self, basename):
217 self.basename = basename
218 self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
219 self.file = open (self.temp_filename, 'w')
222 self.graphic_type = 'tex'
223 self.code_type = 'unknown'
224 self.code_type_override = None
225 def write (self, line):
226 # match only if there is nothing but whitespace before \begin HACK
227 if re.search('^\s*\\\\begin{mudela}', line):
228 self.scan_begin_statement(line)
230 if self.code_type == 'unknown':
231 if re.search('^\s*\\\\score', line) or \
232 re.search('^\s*\\\\paper', line) or \
233 re.search('^\s*\\\\header', line) or \
234 re.search('^\s*[A-Za-z]*\s*=', line):
235 self.code_type = 'ly'
236 self.__lines.append(line)
237 def scan_begin_statement(self, line):
238 r = begin_mudela_opts_re.search(line)
241 optlist = re.compile('[\s,]*').split(o)
244 if 'fragment' in optlist:
245 self.code_type_override = 'fly'
246 if 'nonfragment' in optlist:
247 self.code_type_override = 'ly'
249 self.graphic_type = 'eps'
250 for pt in fontsize_pt2i.keys():
252 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
253 def write_red_tape(self):
254 self.file.write ('\\include \"paper%d.ly\"\n' \
255 % Props.getMudelaFontsize())
257 s = fontsize_i2a[Props.getMudelaFontsize()]
258 if self.code_type == 'fly':
259 linewidth_str = 'linewidth = -1.\cm;'
261 linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth()
262 self.file.write("\\paper {"
265 + "castingalgorithm = \Gourlay; \n}")
266 #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
267 if self.code_type == 'fly':
268 self.file.write('\\score{\n\\notes{')
270 if self.code_type == 'unknown':
271 self.code_type = 'fly'
272 if self.code_type_override:
273 self.code_type = self.code_type_override
274 self.write_red_tape()
275 for l in self.__lines:
277 if self.code_type == 'fly':
278 self.file.write('}}')
282 inf = outdir + self.basename + '.ly'
283 outf = outdir + self.basename + '.tex'
284 if not os.path.isfile(inf):
287 status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
289 os.rename (self.temp_filename, inf)
290 if need_recompile_b(inf, outf):
291 out_files.append((self.graphic_type, inf))
292 def insert_me_string(self):
293 "Returns a string that can be used directly in latex."
294 if self.graphic_type == 'tex':
295 return ['tex', self.basename]
296 elif self.graphic_type == 'eps':
297 return ['eps', self.basename]
299 raise SomethingIsSeriouslyBroken
302 def __init__ (self, name):
303 self.output_fn = '%s/%s' % (outdir, name)
305 def open_verbatim (self, line, level):
306 self.__lines.append('\\begin{verbatim}\n')
308 s = re.sub('veryverbatim[\s,]*', '', line)
309 s = re.sub('intertext=\"([^\"]*)\"[\s,]*', '', s)
310 s = re.sub(',\s*\]', ']', s)
311 s = re.sub('\[\]', '', s)
312 self.__lines.append(s);
313 def close_verbatim (self):
314 self.__lines.append('\\end{verbatim}\n')
316 self.__lines.append(s)
317 def create_graphics(self):
320 for line in self.__lines:
321 if type(line)==type([]):
324 if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
325 s = s + ' ' + g[1]+'.ly'
327 e = os.system('cd %s; lilypond %s' %(outdir, s))
329 print "error: lilypond exited with value", e
333 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
334 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
335 g[1]+'.dvi', g[1]+'.eps')
336 def write_outfile(self):
337 file = open(self.output_fn+'.latex', 'w')
338 file.write('% Created by mudela-book\n')
339 for line in self.__lines:
340 if type(line)==type([]):
342 file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\
343 # TeX applies the prefix of the main source automatically.
345 # % (outdir+line[1]+'.tex'))
347 ps_dim = ps_dimention(outdir+line[1]+'.eps')
348 file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \
349 % (ps_dim[0], line[1]+'.eps'))
350 # % (ps_dim[0], outdir+line[1]+'.eps'))
356 def __init__ (self, filename):
357 for fn in [filename, filename+'.tex', filename+'.doc']:
359 self.infile = open (fn)
365 def get_lines (self):
366 lines = self.infile.readlines ()
367 (retlines, retdeps) = ([],[self.filename])
369 r_inp = input_re.search (line)
370 r_inc = include_re.search (line)
372 # Filename rules for \input :
374 # 1. will search for file with exact that name (tex-input.my will be found)
375 # 2. will search for file with .tex ext, (tex-input.my
376 # will find tex-input.my.tex)
377 # input: with .tex ext
378 # 1. will find exact match
380 # Filename rules for \include :
381 # 1. will only find files with name given to \include + .tex ext
384 t = Tex_input (r_inp.groups()[0])
386 retlines = retlines + ls[0]
387 retdeps = retdeps + ls[1]
389 print "warning: can't find %s, let's hope latex will" \
391 retlines.append (line)
394 t = Tex_input (r_inc.groups()[0]+'.tex')
396 ls[0].insert(0, '\\newpage\n')
397 ls[0].append('\\newpage\n')
398 retlines = retlines + ls[0]
399 retdeps = retdeps + ls[1]
401 print "warning: can't find %s, let's hope latex will" \
403 retlines.append (line)
405 r_mud = defined_mudela_cmd_re.search(line)
407 ss = "\\\\verb(?P<xx>[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \
408 % re.escape(r_mud.group()[1:])
409 if re.search(ss, line):
410 retlines.append(line)
413 opts = r_mud.groups()[2]
418 (start, rest) = string.split(line, r_mud.group(), 1)
419 retlines.append(start)#+'\n')
420 v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n')
422 l = re.sub(r'\\fontoptions', opts, l)
423 l = re.sub(r'\\maininput', r_mud.groups()[3], l)
425 r_mud = defined_mudela_cmd_re.search(rest)
427 retlines.append(rest)
431 retlines.append (line)
433 return (retlines, retdeps)
436 class Main_tex_input(Tex_input):
437 def __init__ (self, name, outname):
439 Tex_input.__init__ (self, name) # ugh
440 self.outname = outname
444 self.mudtex = Tex_output (self.outname)
448 # set to 'mudela' when we are processing mudela code,
449 # both verbatim and graphic-to-be
451 def set_sections (self, l):
452 if section_re.search (l):
453 self.section = self.section + 1
454 if chapter_re.search (l):
456 self.chapter = self.chapter + 1
458 def gen_basename (self):
459 return '%s-%d.%d.%d' % (self.outname, self.chapter,
460 self.section, self.fine_count)
461 def extract_papersize_from_documentclass(self, line):
462 pre = extract_papersize_re.search(line)
465 return pre.groups()[0]
466 def extract_fontsize_from_documentclass(self, line):
467 r = extract_fontsize_re.search(line)
469 return int(r.groups()[0])
471 preMudelaDef = postMudelaDef = 0
472 (lines, self.deps) = self.get_lines ()
476 if documentclass_re.search (line):
477 p = self.extract_papersize_from_documentclass (line)
479 Props.setPapersize(p, 'file')
480 f = self.extract_fontsize_from_documentclass (line)
482 Props.setTexFontsize (f, 'file')
483 elif twocolumn_re.search (line):
484 Props.setNumColumn (2, 'file')
485 elif onecolumn_re.search (line):
486 Props.setNumColumn (1, 'file')
487 elif preMudelaExample_re.search (line):
489 elif postMudelaExample_re.search (line):
491 elif begin_verbatim_re.search (line):
493 elif end_verbatim_re.search (line):
495 elif begin_document_re.search (line):
497 self.mudtex.write ('\\def\\preMudelaExample{}\n')
498 if not postMudelaDef:
499 self.mudtex.write ('\\def\\postMudelaExample{}\n')
500 elif begin_mudela_re.search (line) and not latex_verbatim:
501 Props.clear_for_new_block()
503 if self.mode == 'mudela':
506 r = begin_mudela_opts_re.search (line)
508 o = r.group()[1:][:-1]
509 optlist = re.compile('[ ,]*').split(o)
510 m = intertext_re.search(r.group())
512 self.intertext = m.groups()[0]
514 self.intertext = None
517 if ('veryverbatim' in optlist):
519 elif ('verbatim' in optlist) or (Props.force_verbatim_b):
524 self.mudtex.open_verbatim (line, self.verbatim)
525 self.mudela = Mudela_output (self.gen_basename ())
526 self.mudela.write (line)
528 elif end_mudela_re.search (line) and not latex_verbatim:
530 if self.mode != 'mudela':
534 if self.verbatim == 2:
535 self.mudtex.write (line)
536 self.mudtex.close_verbatim ()
538 if self.verbatim and self.intertext:
539 self.mudtex.write(self.intertext)
540 self.mudtex.write (self.mudela.insert_me_string())
543 self.fine_count = self.fine_count + 1
547 if self.mode == 'mudela':
548 self.mudela.write (line)
550 self.mudtex.write (line)
552 self.mudtex.write (line)
553 self.set_sections(line)
554 self.mudtex.create_graphics()
555 self.mudtex.write_outfile()
560 sys.stdout.write("""Usage: mudela-book [options] FILE\n
561 Generate hybrid LaTeX input from Latex + mudela
563 -h, --help print this help
564 -d, --outdir=DIR directory to put generated files
565 -o, --outname=FILE prefix for filenames
566 --default-mudela-fontsize=??pt default fontsize for music
567 --force-mudela-fontsize=??pt force fontsize for all inline mudela
568 --force-verbatim make all mudela verbatim\n
569 --dependencies write dependencies
570 --init mudela-book initfile
576 def write_deps (fn, out, deps):
577 out_fn = outdir + '/' + fn
578 print '`writing `%s\'\n\'' % out_fn
580 f = open (out_fn, 'w')
581 f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
582 reduce (lambda x,y: x + ' '+ y, deps)))
586 sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
589 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
592 (options, files) = getopt.getopt(
593 sys.argv[1:], 'hd:o:', ['outdir=', 'outname=',
594 'default-mudela-fontsize=',
595 'force-mudela-fontsize=',
596 'help', 'dependencies',
597 'force-verbatim', 'init='])
598 except getopt.error, msg:
606 if o == '--outname' or o == '-o':
609 print "Mudela-book is confused by --outname on multiple files"
612 if o == '--outdir' or o == '-d':
614 if o == '--help' or o == '-h':
616 if o == '--dependencies':
618 if o == '--default-mudela-fontsize':
619 if not fontsize_pt2i.has_key(a):
620 print "Error: illegal fontsize:", a
621 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
623 Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
624 if o == '--force-mudela-fontsize':
625 if not fontsize_pt2i.has_key(a):
626 print "Error: illegal fontsize:", a
627 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
629 Props.force_mudela_fontsize = fontsize_pt2i[a]
630 if o == '--force-verbatim':
631 Props.force_verbatim_b = 1
634 if outdir[-1:] != '/':
635 outdir = outdir + '/'
637 defined_mudela_cmd = {'mudela': r"""
638 \begin{mudela}[eps \fontoptions]
648 defined_mudela_cmd[i] = d[i]
650 c = defined_mudela_cmd.keys()[0]
651 for x in defined_mudela_cmd.keys()[1:]:
653 defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c)
655 if not os.path.isdir(outdir):
656 os.system('mkdir %s' % outdir)
658 for input_filename in files:
659 Props.clear_for_new_file()
663 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
664 my_depname = my_outname + '.dep'
665 inp = Main_tex_input (input_filename, my_outname)
667 # os.system('latex %s/%s.latex' % (outdir, my_outname))
669 write_deps (my_depname, my_outname, inp.deps)