2 # All non-english comments are NOT in swedish, they are norwegian!
5 # * mudela-book should treat \begin{verbatim} that contains inline mudela
7 # * make mudela-book understand usepackage{geometry}
8 # * check that linewidth set in \paper is not wider than actual linewidth?
9 # * the following fails because mudelabook doesn't care that the
10 # last } after \end{mudela} finishes the marginpar:
15 # * force-verbatim is maybe not that useful since latex fails with footnotes,
16 # marginpars and others
21 # much rewritten by new author. I think the work has been split more
22 # logical between different classes.
25 # - speedup, do all mudela parsing at the same time to avoid multiple
26 # guile startup that takes some seconds on my computer
28 # - fixed default latex fontsize, it should be 10pt not 11pt
29 # - verbatim option no longer visible
30 # - mudela-book produces .dvi output
31 # - change to use castingalgorithm = \Gourlay instead of \Wordwrap. It gives
32 # better result on small linewidths.
33 # - mudela-book-doc.doc rewritten
35 # - junked floating and fragment options, added eps option
36 # - mudela-book will scan the mudela code to find out if it has to add
37 # paper-definition and \score{\notes{...}}
38 # - possible to define commands that look like this: \mudela{ c d e }
40 # - removed init/mudela-book-defs.py, \mudela is defined inside mudela-book
41 # - fragment and nonfragment options will override autodetection of type of
42 # in mudela-block (voice contents or complete code)
52 program_version = '0.5.1'
56 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen',
57 20:'twenty', 26:'twentysix'}
58 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
60 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
61 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
62 extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt')
63 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
64 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
65 section_re = re.compile ('\\\\section')
66 chapter_re = re.compile ('\\\\chapter')
67 input_re = re.compile ('^\\\\input{([^}]*)')
68 include_re = re.compile ('^\\\\include{([^}]*)')
69 begin_document_re = re.compile ('^ *\\\\begin{document}')
70 documentclass_re = re.compile('\\\\documentclass')
71 twocolumn_re = re.compile('\\\\twocolumn')
72 onecolumn_re = re.compile('\\\\onecolumn')
73 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
74 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
75 boundingBox_re = re.compile('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)')
77 def file_exist_b(name):
85 def ps_dimention(fname):
87 lines = fd.readlines()
89 s = boundingBox_re.search(line)
92 return (int(s.groups()[2])-int(s.groups()[0]),
93 int(s.groups()[3])-int(s.groups()[1]))
98 class SomethingIsSeriouslyBroken:
101 def file_mtime (name):
102 return os.stat (name)[8] #mod time
104 def need_recompile_b(infile, outfile):
105 indate = file_mtime (infile)
107 outdate = file_mtime (outfile)
108 return indate > outdate
113 # executes os.system(command) if infile is newer than
114 # outfile or outfile don't exist
116 def compile (command, workingdir, infile, outfile):
117 "Test if infile is newer than outfile. If so, cd to workingdir"
118 "and execute command"
119 indate = file_mtime (workingdir+infile)
121 outdate = file_mtime (workingdir+outfile)
122 recompile = indate > outdate
128 sys.stderr.write ('invoking `%s\'\n' % command)
130 status = os.system (command)
132 status = os.system ('cd %s; %s' %(workingdir, command))
142 1: {'a4':{10: 345, 11: 360, 12: 390},
143 'a5':{10: 276, 11: 276, 12: 276},
144 'b5':{10: 345, 11: 356, 12: 356},
145 'letter':{10: 345, 11: 360, 12: 390},
146 'legal': {10: 345, 11: 360, 12: 390},
147 'executive':{10: 345, 11: 360, 12: 379}},
148 2: {'a4':{10: 167, 11: 175, 12: 190},
149 'a5':{10: 133, 11: 133, 12: 133},
150 'b5':{10: 167, 11: 173, 12: 173},
151 'letter':{10: 167, 11: 175, 12: 190},
152 'legal':{10: 167, 11: 175, 12: 190},
153 'executive':{10: 167, 11: 175, 12: 184}}}
154 # >0 --> force all mudela to this pt size
155 self.force_mudela_fontsize = 0
156 self.force_verbatim_b = 0
158 'mudela-fontsize' : {'init': 16},
159 'papersize' : {'init': 'a4'},
160 'num-column' : {'init': 1},
161 'tex-fontsize' : {'init': 10}
163 def clear_for_new_file(self):
164 for var in self.__data.keys():
165 self.__data[var] = {'init': self.__data[var]['init']}
166 def clear_for_new_block(self):
167 for var in self.__data.keys():
168 if self.__data[var].has_key('block'):
169 del self.__data[var]['block']
170 def __get_variable(self, var):
171 if self.__data[var].has_key('block'):
172 return self.__data[var]['block']
173 elif self.__data[var].has_key('file'):
174 return self.__data[var]['file']
176 return self.__data[var]['init']
177 def setPapersize(self, size, requester):
178 self.__data['papersize'][requester] = size
179 def getPapersize(self):
180 return self.__get_variable('papersize')
181 def setMudelaFontsize(self, size, requester):
182 self.__data['mudela-fontsize'][requester] = size
183 def getMudelaFontsize(self):
184 if self.force_mudela_fontsize:
185 return self.force_mudela_fontsize
186 return self.__get_variable('mudela-fontsize')
187 def setTexFontsize(self, size, requester):
188 self.__data['tex-fontsize'][requester] = size
189 def getTexFontsize(self):
190 return self.__get_variable('tex-fontsize')
191 def setNumColumn(self, num, requester):
192 self.__data['num-column'][requester] = num
193 def getNumColumn(self):
194 return self.__get_variable('num-column')
195 def getLineWidth(self):
196 return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
200 def __init__ (self, basename):
201 self.basename = basename
202 self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
203 self.file = open (self.temp_filename, 'w')
206 self.graphic_type = 'tex'
207 self.code_type = 'unknown'
208 self.code_type_override = None
209 def write (self, line):
210 # match only if there is nothing but whitespace before \begin HACK
211 if re.search('^\s*\\\\begin{mudela}', line):
212 self.scan_begin_statement(line)
214 if self.code_type == 'unknown':
215 if re.search('^\s*\\\\score', line) or \
216 re.search('^\s*\\\\paper', line) or \
217 re.search('^\s*\\\\header', line) or \
218 re.search('^\s*[A-Za-z]*\s*=', line):
219 self.code_type = 'ly'
220 self.__lines.append(line)
221 def scan_begin_statement(self, line):
222 r = begin_mudela_opts_re.search(line)
225 optlist = re.compile('[\s,]*').split(o)
228 if 'fragment' in optlist:
229 self.code_type_override = 'fly'
230 if 'nonfragment' in optlist:
231 self.code_type_override = 'ly'
233 self.graphic_type = 'eps'
234 for pt in fontsize_pt2i.keys():
236 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
237 def write_red_tape(self):
238 self.file.write ('\\include \"paper%d.ly\"\n' \
239 % Props.getMudelaFontsize())
241 s = fontsize_i2a[Props.getMudelaFontsize()]
242 if self.code_type == 'fly':
243 linewidth_str = 'linewidth = -1.\cm;'
245 linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth()
246 self.file.write("\\paper {"
249 + "castingalgorithm = \Gourlay; \n}")
250 #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
251 if self.code_type == 'fly':
252 self.file.write('\\score{\n\\notes{')
254 if self.code_type == 'unknown':
255 self.code_type = 'fly'
256 if self.code_type_override:
257 self.code_type = self.code_type_override
258 print "override:", self.code_type_override
259 self.write_red_tape()
260 for l in self.__lines:
262 if self.code_type == 'fly':
263 self.file.write('}}')
267 inf = outdir + self.basename + '.ly'
268 outf = outdir + self.basename + '.tex'
269 if not os.path.isfile(inf):
272 status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
274 os.rename (self.temp_filename, inf)
275 if need_recompile_b(inf, outf):
276 out_files.append((self.graphic_type, inf))
277 def insert_me_string(self):
278 "Returns a string that can be used directly in latex."
279 if self.graphic_type == 'tex':
280 return ['tex', self.basename]
281 elif self.graphic_type == 'eps':
282 return ['eps', self.basename]
284 raise SomethingIsSeriouslyBroken
287 def __init__ (self, name):
288 self.output_fn = '%s/%s' % (outdir, name)
290 def open_verbatim (self, line):
291 self.__lines.append('\\begin{verbatim}\n')
292 s = re.sub('[\s,]*verbatim[\s]*', '', line)
293 s = re.sub('\[\]', '', s)
294 self.__lines.append(s);
295 def close_verbatim (self):
296 self.__lines.append('\\end{verbatim}\n')
298 self.__lines.append(s)
299 def create_graphics(self):
302 for line in self.__lines:
303 if type(line)==type([]):
306 if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
307 s = s + ' ' + g[1]+'.ly'
309 e = os.system('cd %s; lilypond %s' %(outdir, s))
311 print "error: lilypond exited with value", e
315 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
316 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
317 g[1]+'.dvi', g[1]+'.eps')
318 def write_outfile(self):
319 file = open(self.output_fn+'.latex', 'w')
320 file.write('% Created by mudela-book\n')
321 for line in self.__lines:
322 if type(line)==type([]):
324 file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\
325 # TeX applies the prefix of the main source automatically.
327 # % (outdir+line[1]+'.tex'))
329 ps_dim = ps_dimention(outdir+line[1]+'.eps')
330 file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \
331 % (ps_dim[0], line[1]+'.eps'))
332 # % (ps_dim[0], outdir+line[1]+'.eps'))
338 def __init__ (self, filename):
339 for fn in [filename, filename+'.tex', filename+'.doc']:
341 self.infile = open (fn)
347 def get_lines (self):
348 lines = self.infile.readlines ()
349 (retlines, retdeps) = ([],[self.filename])
351 r_inp = input_re.search (line)
352 r_inc = include_re.search (line)
354 # Filename rules for \input :
356 # 1. will search for file with exact that name (tex-input.my will be found)
357 # 2. will search for file with .tex ext, (tex-input.my
358 # will find tex-input.my.tex)
359 # input: with .tex ext
360 # 1. will find exact match
362 # Filename rules for \include :
363 # 1. will only find files with name given to \include + .tex ext
366 t = Tex_input (r_inp.groups()[0])
368 retlines = retlines + ls[0]
369 retdeps = retdeps + ls[1]
371 print "warning: can't find %s, let's hope latex will" \
373 retlines.append (line)
376 t = Tex_input (r_inc.groups()[0]+'.tex')
378 ls[0].insert(0, '\\newpage\n')
379 ls[0].append('\\newpage\n')
380 retlines = retlines + ls[0]
381 retdeps = retdeps + ls[1]
383 print "warning: can't find %s, let's hope latex will" \
385 retlines.append (line)
387 r_mud = defined_mudela_cmd_re.search(line)
389 ss = "\\\\verb(?P<xx>[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \
390 % re.escape(r_mud.group()[1:])
391 if re.search(ss, line):
392 retlines.append(line)
395 opts = r_mud.groups()[2]
400 (start, rest) = string.split(line, r_mud.group(), 1)
401 retlines.append(start)#+'\n')
402 v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n')
404 l = re.sub(r'\\fontoptions', opts, l)
405 l = re.sub(r'\\maininput', r_mud.groups()[3], l)
407 r_mud = defined_mudela_cmd_re.search(rest)
409 retlines.append(rest)
413 retlines.append (line)
415 return (retlines, retdeps)
418 class Main_tex_input(Tex_input):
419 def __init__ (self, name, outname):
421 Tex_input.__init__ (self, name) # ugh
422 self.outname = outname
426 self.mudtex = Tex_output (self.outname)
430 # set to 'mudela' when we are processing mudela code,
431 # both verbatim and graphic-to-be
433 def set_sections (self, l):
434 if section_re.search (l):
435 self.section = self.section + 1
436 if chapter_re.search (l):
438 self.chapter = self.chapter + 1
440 def gen_basename (self):
441 return '%s-%d.%d.%d' % (self.outname, self.chapter,
442 self.section, self.fine_count)
443 def extract_papersize_from_documentclass(self, line):
444 pre = extract_papersize_re.search(line)
447 return pre.groups()[0]
448 def extract_fontsize_from_documentclass(self, line):
449 r = extract_fontsize_re.search(line)
451 return int(r.groups()[0])
453 preMudelaDef = postMudelaDef = 0
454 (lines, self.deps) = self.get_lines ()
456 if documentclass_re.search (line):
457 p = self.extract_papersize_from_documentclass (line)
459 Props.setPapersize(p, 'file')
460 f = self.extract_fontsize_from_documentclass (line)
462 Props.setTexFontsize (f, 'file')
463 elif twocolumn_re.search (line):
464 Props.setNumColumn (2, 'file')
465 elif onecolumn_re.search (line):
466 Props.setNumColumn (1, 'file')
467 elif preMudelaExample_re.search (line):
469 elif postMudelaExample_re.search (line):
471 elif begin_document_re.search (line):
473 self.mudtex.write ('\\def\\preMudelaExample{}\n')
474 if not postMudelaDef:
475 self.mudtex.write ('\\def\\postMudelaExample{}\n')
476 elif begin_mudela_re.search (line):
477 Props.clear_for_new_block()
479 if self.mode == 'mudela':
482 r = begin_mudela_opts_re.search (line)
484 o = r.group()[1:][:-1]
485 optlist = re.compile('[ ,]*').split(o)
488 if ('verbatim' in optlist) or (Props.force_verbatim_b):
490 self.mudtex.open_verbatim (line)
494 self.mudela = Mudela_output (self.gen_basename ())
495 elif end_mudela_re.search (line):
497 if self.mode != 'mudela':
501 self.mudtex.write (self.mudela.insert_me_string())
504 self.fine_count = self.fine_count + 1
506 self.mudtex.write (line)
507 self.mudtex.close_verbatim ()
511 if self.mode == 'mudela' and not self.verbatim:
512 self.mudela.write (line)
514 self.mudtex.write (line)
515 self.set_sections(line)
516 self.mudtex.create_graphics()
517 self.mudtex.write_outfile()
522 sys.stdout.write("""Usage: mudela-book [options] FILE\n
523 Generate hybrid LaTeX input from Latex + mudela
525 -h, --help print this help
526 -d, --outdir=DIR directory to put generated files
527 -o, --outname=FILE prefix for filenames
528 --default-mudela-fontsize=??pt default fontsize for music
529 --force-mudela-fontsize=??pt force fontsize for all inline mudela
530 --force-verbatim make all mudela verbatim\n
531 --dependencies write dependencies
532 --init mudela-book initfile
538 def write_deps (fn, out, deps):
539 out_fn = outdir + '/' + fn
540 print '`writing `%s\'\n\'' % out_fn
542 f = open (out_fn, 'w')
543 f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
544 reduce (lambda x,y: x + ' '+ y, deps)))
548 sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
551 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
554 (options, files) = getopt.getopt(
555 sys.argv[1:], 'hd:o:', ['outdir=', 'outname=',
556 'default-mudela-fontsize=',
557 'force-mudela-fontsize=',
558 'help', 'dependencies',
559 'force-verbatim', 'init='])
560 except getopt.error, msg:
568 if o == '--outname' or o == '-o':
571 print "Mudela-book is confused by --outname on multiple files"
574 if o == '--outdir' or o == '-d':
576 if o == '--help' or o == '-h':
578 if o == '--dependencies':
580 if o == '--default-mudela-fontsize':
581 if not fontsize_pt2i.has_key(a):
582 print "Error: illegal fontsize:", a
583 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
585 Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
586 if o == '--force-mudela-fontsize':
587 if not fontsize_pt2i.has_key(a):
588 print "Error: illegal fontsize:", a
589 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
591 Props.force_mudela_fontsize = fontsize_pt2i[a]
592 if o == '--force-verbatim':
593 Props.force_verbatim_b = 1
596 if outdir[-1:] != '/':
597 outdir = outdir + '/'
599 defined_mudela_cmd = {'mudela': r"""
600 \begin{mudela}[eps \fontoptions]
610 defined_mudela_cmd[i] = d[i]
612 c = defined_mudela_cmd.keys()[0]
613 for x in defined_mudela_cmd.keys()[1:]:
615 defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c)
617 if not os.path.isdir(outdir):
618 os.system('mkdir %s' % outdir)
620 for input_filename in files:
621 Props.clear_for_new_file()
625 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
626 my_depname = my_outname + '.dep'
627 inp = Main_tex_input (input_filename, my_outname)
629 # os.system('latex %s/%s.latex' % (outdir, my_outname))
631 write_deps (my_depname, my_outname, inp.deps)