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 }
48 program_version = '0.5'
52 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen',
53 20:'twenty', 26:'twentysix'}
54 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
56 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
57 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
58 extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt')
59 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
60 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
61 section_re = re.compile ('\\\\section')
62 chapter_re = re.compile ('\\\\chapter')
63 input_re = re.compile ('^\\\\input{([^}]*)')
64 include_re = re.compile ('^\\\\include{([^}]*)')
65 begin_document_re = re.compile ('^ *\\\\begin{document}')
66 documentclass_re = re.compile('\\\\documentclass')
67 twocolumn_re = re.compile('\\\\twocolumn')
68 onecolumn_re = re.compile('\\\\onecolumn')
69 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
70 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
71 boundingBox_re = re.compile('%%BoundingBox: ([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
73 def file_exist_b(name):
81 def ps_dimention(fname):
83 lines = fd.readlines()
85 s = boundingBox_re.search(line)
88 return (int(s.groups()[2])-int(s.groups()[0]),
89 int(s.groups()[3])-int(s.groups()[1]))
94 class SomethingIsSeriouslyBroken:
97 def file_mtime (name):
98 return os.stat (name)[8] #mod time
100 def need_recompile_b(infile, outfile):
101 indate = file_mtime (infile)
103 outdate = file_mtime (outfile)
104 return indate > outdate
109 # executes os.system(command) if infile is newer than
110 # outfile or outfile don't exist
112 def compile (command, workingdir, infile, outfile):
113 "Test if infile is newer than outfile. If so, cd to workingdir"
114 "and execute command"
115 indate = file_mtime (workingdir+infile)
117 outdate = file_mtime (workingdir+outfile)
118 recompile = indate > outdate
124 sys.stderr.write ('invoking `%s\'\n' % command)
126 status = os.system (command)
128 status = os.system ('cd %s; %s' %(workingdir, command))
138 1: {'a4':{10: 345, 11: 360, 12: 390},
139 'a5':{10: 276, 11: 276, 12: 276},
140 'b5':{10: 345, 11: 356, 12: 356},
141 'letter':{10: 345, 11: 360, 12: 390},
142 'legal': {10: 345, 11: 360, 12: 390},
143 'executive':{10: 345, 11: 360, 12: 379}},
144 2: {'a4':{10: 167, 11: 175, 12: 190},
145 'a5':{10: 133, 11: 133, 12: 133},
146 'b5':{10: 167, 11: 173, 12: 173},
147 'letter':{10: 167, 11: 175, 12: 190},
148 'legal':{10: 167, 11: 175, 12: 190},
149 'executive':{10: 167, 11: 175, 12: 184}}}
150 # >0 --> force all mudela to this pt size
151 self.force_mudela_fontsize = 0
152 self.force_verbatim_b = 0
154 'mudela-fontsize' : {'init': 16},
155 'papersize' : {'init': 'a4'},
156 'num-column' : {'init': 1},
157 'tex-fontsize' : {'init': 10}
159 def clear_for_new_file(self):
160 for var in self.__data.keys():
161 self.__data[var] = {'init': self.__data[var]['init']}
162 def clear_for_new_block(self):
163 for var in self.__data.keys():
164 if self.__data[var].has_key('block'):
165 del self.__data[var]['block']
166 def __get_variable(self, var):
167 if self.__data[var].has_key('block'):
168 return self.__data[var]['block']
169 elif self.__data[var].has_key('file'):
170 return self.__data[var]['file']
172 return self.__data[var]['init']
173 def setPapersize(self, size, requester):
174 self.__data['papersize'][requester] = size
175 def getPapersize(self):
176 return self.__get_variable('papersize')
177 def setMudelaFontsize(self, size, requester):
178 self.__data['mudela-fontsize'][requester] = size
179 def getMudelaFontsize(self):
180 if self.force_mudela_fontsize:
181 return self.force_mudela_fontsize
182 return self.__get_variable('mudela-fontsize')
183 def setTexFontsize(self, size, requester):
184 self.__data['tex-fontsize'][requester] = size
185 def getTexFontsize(self):
186 return self.__get_variable('tex-fontsize')
187 def setNumColumn(self, num, requester):
188 self.__data['num-column'][requester] = num
189 def getNumColumn(self):
190 return self.__get_variable('num-column')
191 def getLineWidth(self):
192 return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
196 def __init__ (self, basename):
197 self.basename = basename
198 self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
199 self.file = open (self.temp_filename, 'w')
202 self.graphic_type = 'tex'
203 self.code_type = 'unknown'
204 def write (self, line):
205 # match only if there is nothing but whitespace before \begin HACK
206 if re.search('^\s*\\\\begin{mudela}', line):
207 self.scan_begin_statement(line)
209 if self.code_type == 'unknown':
210 if re.search('^\s*\\\\score', line) or \
211 re.search('^\s*\\\\paper', line) or \
212 re.search('^\s*\\\\header', line) or \
213 re.search('^\s*[A-Za-z]*\s*=', line):
214 self.code_type = 'ly'
215 self.__lines.append(line)
216 def scan_begin_statement(self, line):
217 r = begin_mudela_opts_re.search(line)
220 optlist = re.compile('[\s,]*').split(o)
223 if 'fragment' in optlist:
224 print "warning: obsolete option: fragment"
225 if 'floating' in optlist:
226 print "warning: obsolete option: floating, change to eps"
228 self.graphic_type = 'eps'
229 for pt in fontsize_pt2i.keys():
231 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
232 def write_red_tape(self):
233 self.file.write ('\\include \"paper%d.ly\"\n' \
234 % Props.getMudelaFontsize())
236 s = fontsize_i2a[Props.getMudelaFontsize()]
237 if self.code_type == 'fly':
238 linewidth_str = 'linewidth = -1.\cm;'
240 linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth()
241 self.file.write("\\paper {"
244 + "castingalgorithm = \Gourlay; \n}")
245 #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
246 if self.code_type == 'fly':
247 self.file.write('\\score{\n\\notes{')
249 if self.code_type == 'unknown':
250 self.code_type = 'fly'
251 if self.code_type == 'ly':
252 self.write_red_tape()
253 for l in self.__lines:
256 self.write_red_tape()
257 for l in self.__lines:
259 self.file.write('}}')
263 inf = outdir + self.basename + '.ly'
264 outf = outdir + self.basename + '.tex'
265 if not os.path.isfile(inf):
268 status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
270 os.rename (self.temp_filename, inf)
271 if need_recompile_b(inf, outf):
272 out_files.append((self.graphic_type, inf))
273 def insert_me_string(self):
274 "Returns a string that can be used directly in latex."
275 if self.graphic_type == 'tex':
276 return ['tex', self.basename]
277 elif self.graphic_type == 'eps':
278 return ['eps', self.basename]
280 raise SomethingIsSeriouslyBroken
283 def __init__ (self, name):
284 self.output_fn = '%s/%s' % (outdir, name)
286 def open_verbatim (self, line):
287 self.__lines.append('\\begin{verbatim}\n')
288 s = re.sub('[\s,]*verbatim[\s]*', '', line)
289 s = re.sub('\[\]', '', s)
290 self.__lines.append(s);
291 def close_verbatim (self):
292 self.__lines.append('\\end{verbatim}\n')
294 self.__lines.append(s)
295 def create_graphics(self):
298 for line in self.__lines:
299 if type(line)==type([]):
302 if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
303 s = s + ' ' + g[1]+'.ly'
305 e = os.system('cd %s; lilypond %s' %(outdir, s))
307 print "error: lilypond exited with value", e
311 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
312 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
313 g[1]+'.dvi', g[1]+'.eps')
314 def write_outfile(self):
315 file = open(self.output_fn+'.latex', 'w')
316 file.write('% Created by mudela-book\n')
317 for line in self.__lines:
318 if type(line)==type([]):
320 file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\
321 # TeX applies the prefix of the main source automatically.
323 # % (outdir+line[1]+'.tex'))
325 ps_dim = ps_dimention(outdir+line[1]+'.eps')
326 file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \
327 % (ps_dim[0], line[1]+'.eps'))
328 # % (ps_dim[0], outdir+line[1]+'.eps'))
333 def __init__ (self, filename):
334 for fn in [filename, filename+'.tex', filename+'.doc']:
336 self.infile = open (fn)
342 def get_lines (self):
343 lines = self.infile.readlines ()
344 (retlines, retdeps) = ([],[self.filename])
346 r_inp = input_re.search (line)
347 r_inc = include_re.search (line)
349 # Filename rules for \input :
351 # 1. will search for file with exact that name (tex-input.my will be found)
352 # 2. will search for file with .tex ext, (tex-input.my
353 # will find tex-input.my.tex)
354 # input: with .tex ext
355 # 1. will find exact match
357 # Filename rules for \include :
358 # 1. will only find files with name given to \include + .tex ext
361 t = Tex_input (r_inp.groups()[0])
363 retlines = retlines + ls[0]
364 retdeps = retdeps + ls[1]
366 print "warning: can't find %s, let's hope latex will" \
368 retlines.append (line)
371 t = Tex_input (r_inc.groups()[0]+'.tex')
373 ls[0].insert(0, '\\newpage\n')
374 ls[0].append('\\newpage\n')
375 retlines = retlines + ls[0]
376 retdeps = retdeps + ls[1]
378 print "warning: can't find %s, let's hope latex will" \
380 retlines.append (line)
382 r_mud = defined_mudela_cmd_re.search(line)
384 ss = "\\\\verb(?P<xx>[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \
385 % re.escape(r_mud.group()[1:])
386 if re.search(ss, line):
387 retlines.append(line)
390 opts = r_mud.groups()[2]
395 (start, rest) = string.split(line, r_mud.group(), 1)
396 retlines.append(start+'\n')
397 v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n')
399 l = re.sub(r'\\fontoptions', opts, l)
400 l = re.sub(r'\\maininput', r_mud.groups()[3], l)
402 r_mud = defined_mudela_cmd_re.search(rest)
404 retlines.append(rest)
408 retlines.append (line)
410 return (retlines, retdeps)
413 class Main_tex_input(Tex_input):
414 def __init__ (self, name, outname):
416 Tex_input.__init__ (self, name) # ugh
417 self.outname = outname
421 self.mudtex = Tex_output (self.outname)
425 # set to 'mudela' when we are processing mudela code,
426 # both verbatim and graphic-to-be
428 def set_sections (self, l):
429 if section_re.search (l):
430 self.section = self.section + 1
431 if chapter_re.search (l):
433 self.chapter = self.chapter + 1
435 def gen_basename (self):
436 return '%s-%d.%d.%d' % (self.outname, self.chapter,
437 self.section, self.fine_count)
438 def extract_papersize_from_documentclass(self, line):
439 pre = extract_papersize_re.search(line)
442 return pre.groups()[0]
443 def extract_fontsize_from_documentclass(self, line):
444 r = extract_fontsize_re.search(line)
446 return int(r.groups()[0])
448 preMudelaDef = postMudelaDef = 0
449 (lines, self.deps) = self.get_lines ()
451 if documentclass_re.search (line):
452 p = self.extract_papersize_from_documentclass (line)
454 Props.setPapersize(p, 'file')
455 f = self.extract_fontsize_from_documentclass (line)
457 Props.setTexFontsize (f, 'file')
458 elif twocolumn_re.search (line):
459 Props.setNumColumn (2, 'file')
460 elif onecolumn_re.search (line):
461 Props.setNumColumn (1, 'file')
462 elif preMudelaExample_re.search (line):
464 elif postMudelaExample_re.search (line):
466 elif begin_document_re.search (line):
468 self.mudtex.write ('\\def\\preMudelaExample{}\n')
469 if not postMudelaDef:
470 self.mudtex.write ('\\def\\postMudelaExample{}\n')
471 elif begin_mudela_re.search (line):
472 Props.clear_for_new_block()
474 if self.mode == 'mudela':
477 r = begin_mudela_opts_re.search (line)
479 o = r.group()[1:][:-1]
480 optlist = re.compile('[ ,]*').split(o)
483 if ('verbatim' in optlist) or (Props.force_verbatim_b):
485 self.mudtex.open_verbatim (line)
489 self.mudela = Mudela_output (self.gen_basename ())
490 elif end_mudela_re.search (line):
492 if self.mode != 'mudela':
496 self.mudtex.write (self.mudela.insert_me_string())
499 self.fine_count = self.fine_count + 1
501 self.mudtex.write (line)
502 self.mudtex.close_verbatim ()
506 if self.mode == 'mudela' and not self.verbatim:
507 self.mudela.write (line)
509 self.mudtex.write (line)
510 self.set_sections(line)
511 self.mudtex.create_graphics()
512 self.mudtex.write_outfile()
517 sys.stdout.write("""Usage: mudela-book [options] FILE\n
518 Generate hybrid LaTeX input from Latex + mudela
520 -h, --help print this help
521 -d, --outdir=DIR directory to put generated files
522 -o, --outname=FILE prefix for filenames
523 --default-mudela-fontsize=??pt default fontsize for music
524 --force-mudela-fontsize=??pt force fontsize for all inline mudela
525 --force-verbatim make all mudela verbatim\n
526 --dependencies write dependencies
527 --init mudela-book initfile
533 def write_deps (fn, out, deps):
534 out_fn = outdir + '/' + fn
535 print '`writing `%s\'\n\'' % out_fn
537 f = open (out_fn, 'w')
538 f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
539 reduce (lambda x,y: x + ' '+ y, deps)))
543 sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
546 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
549 (options, files) = getopt.getopt(
550 sys.argv[1:], 'hd:o:', ['outdir=', 'outname=',
551 'default-mudela-fontsize=',
552 'force-mudela-fontsize=',
553 'help', 'dependencies',
554 'force-verbatim', 'init='])
555 except getopt.error, msg:
563 if o == '--outname' or o == '-o':
566 print "Mudela-book is confused by --outname on multiple files"
569 if o == '--outdir' or o == '-d':
571 if o == '--help' or o == '-h':
573 if o == '--dependencies':
575 if o == '--default-mudela-fontsize':
576 if not fontsize_pt2i.has_key(a):
577 print "Error: illegal fontsize:", a
578 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
580 Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
581 if o == '--force-mudela-fontsize':
582 if not fontsize_pt2i.has_key(a):
583 print "Error: illegal fontsize:", a
584 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
586 Props.force_mudela_fontsize = fontsize_pt2i[a]
587 if o == '--force-verbatim':
588 Props.force_verbatim_b = 1
591 if outdir[-1:] != '/':
592 outdir = outdir + '/'
594 std_init_filename = ''
595 for p in string.split(os.environ['LILYINCLUDE'], ':'):
597 std_init_filename = p+os.sep+'mudela-book-defs.py'
601 if std_init_filename == '':
602 print "error: Can't find mudela-book-defs.py"
604 f = open(std_init_filename)
607 defined_mudela_cmd = eval(s)
615 defined_mudela_cmd[i] = d[i]
618 c = defined_mudela_cmd.keys()[0]
619 for x in defined_mudela_cmd.keys()[1:]:
621 defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c)
623 if not os.path.isdir(outdir):
624 os.system('mkdir %s' % outdir)
626 for input_filename in files:
627 Props.clear_for_new_file()
631 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
632 my_depname = my_outname + '.dep'
633 inp = Main_tex_input (input_filename, my_outname)
635 # os.system('latex %s/%s.latex' % (outdir, my_outname))
637 write_deps (my_depname, my_outname, inp.deps)