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}
57 # - bf: \mudela{ \times 2/3{...} }
58 # * \t in \times is not tab character and
59 # * dont treat the first '}' as command ending
69 program_version = '0.5.3'
73 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen',
74 20:'twenty', 26:'twentysix'}
75 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
77 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
78 begin_verbatim_re = re.compile ('^ *\\\\begin{verbatim}')
79 end_verbatim_re = re.compile ('^ *\\\\end{verbatim}')
80 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
81 extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt')
82 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
83 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
84 section_re = re.compile ('\\\\section')
85 chapter_re = re.compile ('\\\\chapter')
86 input_re = re.compile ('^\\\\input{([^}]*)')
87 include_re = re.compile ('^\\\\include{([^}]*)')
88 begin_document_re = re.compile ('^ *\\\\begin{document}')
89 documentclass_re = re.compile('\\\\documentclass')
90 twocolumn_re = re.compile('\\\\twocolumn')
91 onecolumn_re = re.compile('\\\\onecolumn')
92 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
93 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
94 boundingBox_re = re.compile('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)')
95 intertext_re = re.compile("intertext=\"([^\"]*)\"")
97 def file_exist_b(name):
105 def ps_dimention(fname):
107 lines = fd.readlines()
109 s = boundingBox_re.search(line)
112 return (int(s.groups()[2])-int(s.groups()[0]),
113 int(s.groups()[3])-int(s.groups()[1]))
118 class SomethingIsSeriouslyBroken:
121 def file_mtime (name):
122 return os.stat (name)[8] #mod time
124 def need_recompile_b(infile, outfile):
125 indate = file_mtime (infile)
127 outdate = file_mtime (outfile)
128 return indate > outdate
133 # executes os.system(command) if infile is newer than
134 # outfile or outfile don't exist
136 def compile (command, workingdir, infile, outfile):
137 "Test if infile is newer than outfile. If so, cd to workingdir"
138 "and execute command"
139 indate = file_mtime (workingdir+infile)
141 outdate = file_mtime (workingdir+outfile)
142 recompile = indate > outdate
148 sys.stderr.write ('invoking `%s\'\n' % command)
150 status = os.system (command)
152 status = os.system ('cd %s; %s' %(workingdir, command))
162 1: {'a4':{10: 345, 11: 360, 12: 390},
163 'a5':{10: 276, 11: 276, 12: 276},
164 'b5':{10: 345, 11: 356, 12: 356},
165 'letter':{10: 345, 11: 360, 12: 390},
166 'legal': {10: 345, 11: 360, 12: 390},
167 'executive':{10: 345, 11: 360, 12: 379}},
168 2: {'a4':{10: 167, 11: 175, 12: 190},
169 'a5':{10: 133, 11: 133, 12: 133},
170 'b5':{10: 167, 11: 173, 12: 173},
171 'letter':{10: 167, 11: 175, 12: 190},
172 'legal':{10: 167, 11: 175, 12: 190},
173 'executive':{10: 167, 11: 175, 12: 184}}}
174 # >0 --> force all mudela to this pt size
175 self.force_mudela_fontsize = 0
176 self.force_verbatim_b = 0
178 'mudela-fontsize' : {'init': 16},
179 'papersize' : {'init': 'a4'},
180 'num-column' : {'init': 1},
181 'tex-fontsize' : {'init': 10}
183 def clear_for_new_file(self):
184 for var in self.__data.keys():
185 self.__data[var] = {'init': self.__data[var]['init']}
186 def clear_for_new_block(self):
187 for var in self.__data.keys():
188 if self.__data[var].has_key('block'):
189 del self.__data[var]['block']
190 def __get_variable(self, var):
191 if self.__data[var].has_key('block'):
192 return self.__data[var]['block']
193 elif self.__data[var].has_key('file'):
194 return self.__data[var]['file']
196 return self.__data[var]['init']
197 def setPapersize(self, size, requester):
198 self.__data['papersize'][requester] = size
199 def getPapersize(self):
200 return self.__get_variable('papersize')
201 def setMudelaFontsize(self, size, requester):
202 self.__data['mudela-fontsize'][requester] = size
203 def getMudelaFontsize(self):
204 if self.force_mudela_fontsize:
205 return self.force_mudela_fontsize
206 return self.__get_variable('mudela-fontsize')
207 def setTexFontsize(self, size, requester):
208 self.__data['tex-fontsize'][requester] = size
209 def getTexFontsize(self):
210 return self.__get_variable('tex-fontsize')
211 def setNumColumn(self, num, requester):
212 self.__data['num-column'][requester] = num
213 def getNumColumn(self):
214 return self.__get_variable('num-column')
215 def getLineWidth(self):
216 return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
220 def __init__ (self, basename):
221 self.basename = basename
222 self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
223 self.file = open (self.temp_filename, 'w')
226 self.graphic_type = 'tex'
227 self.code_type = 'unknown'
228 self.code_type_override = None
229 def write (self, line):
230 # match only if there is nothing but whitespace before \begin HACK
231 if re.search('^\s*\\\\begin{mudela}', line):
232 self.scan_begin_statement(line)
234 if self.code_type == 'unknown':
235 if re.search('^\s*\\\\score', line) or \
236 re.search('^\s*\\\\paper', line) or \
237 re.search('^\s*\\\\header', line) or \
238 re.search('^\s*[A-Za-z]*\s*=', line):
239 self.code_type = 'ly'
240 self.__lines.append(line)
241 def scan_begin_statement(self, line):
242 r = begin_mudela_opts_re.search(line)
245 optlist = re.compile('[\s,]*').split(o)
248 if 'fragment' in optlist:
249 self.code_type_override = 'fly'
250 if 'nonfragment' in optlist:
251 self.code_type_override = 'ly'
253 self.graphic_type = 'eps'
254 for pt in fontsize_pt2i.keys():
256 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
257 def write_red_tape(self):
258 self.file.write ('\\include \"paper%d.ly\"\n' \
259 % Props.getMudelaFontsize())
261 s = fontsize_i2a[Props.getMudelaFontsize()]
262 if self.code_type == 'fly':
263 linewidth_str = 'linewidth = -1.\cm;'
265 linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth()
266 self.file.write("\\paper {"
269 + "castingalgorithm = \Gourlay; \n}")
270 #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
271 if self.code_type == 'fly':
272 self.file.write('\\score{\n\\notes{')
274 if self.code_type == 'unknown':
275 self.code_type = 'fly'
276 if self.code_type_override:
277 self.code_type = self.code_type_override
278 self.write_red_tape()
279 for l in self.__lines:
281 if self.code_type == 'fly':
282 self.file.write('}}')
286 inf = outdir + self.basename + '.ly'
287 outf = outdir + self.basename + '.tex'
288 if not os.path.isfile(inf):
291 status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
293 os.rename (self.temp_filename, inf)
294 if need_recompile_b(inf, outf):
295 out_files.append((self.graphic_type, inf))
296 def insert_me_string(self):
297 "Returns a string that can be used directly in latex."
298 if self.graphic_type == 'tex':
299 return ['tex', self.basename]
300 elif self.graphic_type == 'eps':
301 return ['eps', self.basename]
303 raise SomethingIsSeriouslyBroken
306 def __init__ (self, name):
307 self.output_fn = '%s/%s' % (outdir, name)
309 def open_verbatim (self, line, level):
310 self.__lines.append('\\begin{verbatim}\n')
312 s = re.sub('veryverbatim[\s,]*', '', line)
313 s = re.sub('intertext=\"([^\"]*)\"[\s,]*', '', s)
314 s = re.sub(',\s*\]', ']', s)
315 s = re.sub('\[\]', '', s)
316 self.__lines.append(s);
317 def close_verbatim (self):
318 self.__lines.append('\\end{verbatim}\n')
320 self.__lines.append(s)
321 def create_graphics(self):
324 for line in self.__lines:
325 if type(line)==type([]):
328 if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
329 s = s + ' ' + g[1]+'.ly'
331 e = os.system('cd %s; lilypond %s' %(outdir, s))
333 print "error: lilypond exited with value", e
337 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
338 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
339 g[1]+'.dvi', g[1]+'.eps')
340 def write_outfile(self):
341 file = open(self.output_fn+'.latex', 'w')
342 file.write('% Created by mudela-book\n')
343 for line in self.__lines:
344 if type(line)==type([]):
346 file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\
347 # TeX applies the prefix of the main source automatically.
349 # % (outdir+line[1]+'.tex'))
351 ps_dim = ps_dimention(outdir+line[1]+'.eps')
352 file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \
353 % (ps_dim[0], line[1]+'.eps'))
354 # % (ps_dim[0], outdir+line[1]+'.eps'))
359 # given parameter s="\mudela[some options]{CODE} some text and commands"
360 # it returns a tuple:
362 # where the last number is the index of the ending '}'
363 def extract_command(s):
367 for idx in range(len(s)):
369 if not start_found_b:
375 if (start_found_b == 1) and (count == 0):
377 return s[start+1:idx], idx
380 def __init__ (self, filename):
381 for fn in [filename, filename+'.tex', filename+'.doc']:
383 self.infile = open (fn)
389 def get_lines (self):
390 lines = self.infile.readlines ()
391 (retlines, retdeps) = ([],[self.filename])
393 r_inp = input_re.search (line)
394 r_inc = include_re.search (line)
396 # Filename rules for \input :
398 # 1. will search for file with exact that name (tex-input.my will be found)
399 # 2. will search for file with .tex ext, (tex-input.my
400 # will find tex-input.my.tex)
401 # input: with .tex ext
402 # 1. will find exact match
404 # Filename rules for \include :
405 # 1. will only find files with name given to \include + .tex ext
408 t = Tex_input (r_inp.groups()[0])
410 retlines = retlines + ls[0]
411 retdeps = retdeps + ls[1]
413 print "warning: can't find %s, let's hope latex will" \
415 retlines.append (line)
418 t = Tex_input (r_inc.groups()[0]+'.tex')
420 ls[0].insert(0, '\\newpage\n')
421 ls[0].append('\\newpage\n')
422 retlines = retlines + ls[0]
423 retdeps = retdeps + ls[1]
425 print "warning: can't find %s, let's hope latex will" \
427 retlines.append (line)
429 # This code should be rewritten, it looks terrible
430 r_mud = defined_mudela_cmd_re.search(line)
432 ss = "\\\\verb(?P<xx>[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \
433 % re.escape(r_mud.group()[1:])
434 # just append the line if the command is inside \verb|..|
435 if re.search(ss, line):
436 retlines.append(line)
439 opts = r_mud.groups()[2]
440 cmd_start_idx = r_mud.span()[0]
441 if cmd_start_idx > 0:
442 retlines.append(line[:cmd_start_idx])
444 cmd_data, rest_idx = extract_command(line[cmd_start_idx:])
445 rest_idx = rest_idx + cmd_start_idx + 1
451 v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n')
453 l = string.replace(l, '\\fontoptions', opts)
454 l = string.replace(l, '\\maininput', cmd_data)
456 r_mud = defined_mudela_cmd_re.search(line[rest_idx:])
462 retlines.append(line[rest_idx:])
464 line = line[rest_idx:]
466 retlines.append (line)
467 return (retlines, retdeps)
470 class Main_tex_input(Tex_input):
471 def __init__ (self, name, outname):
473 Tex_input.__init__ (self, name) # ugh
474 self.outname = outname
478 self.mudtex = Tex_output (self.outname)
482 # set to 'mudela' when we are processing mudela code,
483 # both verbatim and graphic-to-be
485 def set_sections (self, l):
486 if section_re.search (l):
487 self.section = self.section + 1
488 if chapter_re.search (l):
490 self.chapter = self.chapter + 1
492 def gen_basename (self):
493 return '%s-%d.%d.%d' % (self.outname, self.chapter,
494 self.section, self.fine_count)
495 def extract_papersize_from_documentclass(self, line):
496 pre = extract_papersize_re.search(line)
499 return pre.groups()[0]
500 def extract_fontsize_from_documentclass(self, line):
501 r = extract_fontsize_re.search(line)
503 return int(r.groups()[0])
505 preMudelaDef = postMudelaDef = 0
506 (lines, self.deps) = self.get_lines ()
510 if documentclass_re.search (line):
511 p = self.extract_papersize_from_documentclass (line)
513 Props.setPapersize(p, 'file')
514 f = self.extract_fontsize_from_documentclass (line)
516 Props.setTexFontsize (f, 'file')
517 elif twocolumn_re.search (line):
518 Props.setNumColumn (2, 'file')
519 elif onecolumn_re.search (line):
520 Props.setNumColumn (1, 'file')
521 elif preMudelaExample_re.search (line):
523 elif postMudelaExample_re.search (line):
525 elif begin_verbatim_re.search (line):
527 elif end_verbatim_re.search (line):
529 elif begin_document_re.search (line):
531 self.mudtex.write ('\\def\\preMudelaExample{}\n')
532 if not postMudelaDef:
533 self.mudtex.write ('\\def\\postMudelaExample{}\n')
534 elif begin_mudela_re.search (line) and not latex_verbatim:
535 Props.clear_for_new_block()
537 if self.mode == 'mudela':
540 r = begin_mudela_opts_re.search (line)
542 o = r.group()[1:][:-1]
543 optlist = re.compile('[ ,]*').split(o)
544 m = intertext_re.search(r.group())
546 self.intertext = m.groups()[0]
548 self.intertext = None
551 if ('veryverbatim' in optlist):
553 elif ('verbatim' in optlist) or (Props.force_verbatim_b):
558 self.mudtex.open_verbatim (line, self.verbatim)
559 self.mudela = Mudela_output (self.gen_basename ())
560 self.mudela.write (line)
562 elif end_mudela_re.search (line) and not latex_verbatim:
564 if self.mode != 'mudela':
568 if self.verbatim == 2:
569 self.mudtex.write (line)
570 self.mudtex.close_verbatim ()
572 if self.verbatim and self.intertext:
573 self.mudtex.write(self.intertext)
574 self.mudtex.write (self.mudela.insert_me_string())
577 self.fine_count = self.fine_count + 1
581 if self.mode == 'mudela':
582 self.mudela.write (line)
584 self.mudtex.write (line)
586 self.mudtex.write (line)
587 self.set_sections(line)
588 self.mudtex.create_graphics()
589 self.mudtex.write_outfile()
594 sys.stdout.write("""Usage: mudela-book [options] FILE\n
595 Generate hybrid LaTeX input from Latex + mudela
597 -h, --help print this help
598 -d, --outdir=DIR directory to put generated files
599 -o, --outname=FILE prefix for filenames
600 --default-mudela-fontsize=??pt default fontsize for music
601 --force-mudela-fontsize=??pt force fontsize for all inline mudela
602 --force-verbatim make all mudela verbatim\n
603 --dependencies write dependencies
604 --init mudela-book initfile
610 def write_deps (fn, out, deps):
611 out_fn = outdir + '/' + fn
612 print '`writing `%s\'\n\'' % out_fn
614 f = open (out_fn, 'w')
615 f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
616 reduce (lambda x,y: x + ' '+ y, deps)))
620 sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
623 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
626 (options, files) = getopt.getopt(
627 sys.argv[1:], 'hd:o:', ['outdir=', 'outname=',
628 'default-mudela-fontsize=',
629 'force-mudela-fontsize=',
630 'help', 'dependencies',
631 'force-verbatim', 'init='])
632 except getopt.error, msg:
640 if o == '--outname' or o == '-o':
643 print "Mudela-book is confused by --outname on multiple files"
646 if o == '--outdir' or o == '-d':
648 if o == '--help' or o == '-h':
650 if o == '--dependencies':
652 if o == '--default-mudela-fontsize':
653 if not fontsize_pt2i.has_key(a):
654 print "Error: illegal fontsize:", a
655 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
657 Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
658 if o == '--force-mudela-fontsize':
659 if not fontsize_pt2i.has_key(a):
660 print "Error: illegal fontsize:", a
661 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
663 Props.force_mudela_fontsize = fontsize_pt2i[a]
664 if o == '--force-verbatim':
665 Props.force_verbatim_b = 1
668 if outdir[-1:] != '/':
669 outdir = outdir + '/'
671 defined_mudela_cmd = {'mudela': r"""
672 \begin{mudela}[eps \fontoptions]
686 defined_mudela_cmd[i] = d[i]
688 c = defined_mudela_cmd.keys()[0]
689 for x in defined_mudela_cmd.keys()[1:]:
691 defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c)
693 if not os.path.isdir(outdir):
694 os.system('mkdir %s' % outdir)
696 for input_filename in files:
697 Props.clear_for_new_file()
701 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
702 my_depname = my_outname + '.dep'
703 inp = Main_tex_input (input_filename, my_outname)
705 # os.system('latex %s/%s.latex' % (outdir, my_outname))
707 write_deps (my_depname, my_outname, inp.deps)