lilypond-1.1.13
[lilypond.git] / scripts / old-mudela-book.py
blobcf176a6c1b45dd9f3da89174e9a204913409441c
1 #!@PYTHON@
2 # All non-english comments are NOT in swedish, they are norwegian!
4 # TODO: center option (??)
5 # * the verbatim option should not be visible in the created latex file
6 # * what the h.. does castingalgorithm do/mean???
7 # * the following fails because mudelabook doesn't care that the
8 # last } after \end{mudela} finishes the marginpar:
9 # \marginpar{
10 # \begin{mudela}[fragment]
11 # c d e f g
12 # \end{mudela}}
13 # * fragments should know about margins
15 # log:
16 # 0.3:
17 # rewrite in Python.
18 # 0.4:
19 # much rewritten by new author. I think the work has been split more
20 # logical between different classes.
21 # 0.4.1:
22 # - cleanup
23 # - speedup, do all mudela parsing at the same time to avoid multiple
24 # guile startup that takes some seconds on my computer
26 import os
27 import string
28 import re
29 import getopt
30 import sys
32 outdir = 'out'
33 program_version = '0.4.1'
35 out_files = []
37 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen', 20:'twenty', 26:'twentysix'}
38 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
40 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
41 extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}')
42 extract_fontsize1_re = re.compile('\\\\documentclass\[[^\]]*\]\{[^\}]*\}')
43 extract_fontsize2_re = re.compile('[ ,\[]*([0-9]*)pt')
44 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
45 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
46 section_re = re.compile ('\\\\section')
47 chapter_re = re.compile ('\\\\chapter')
48 input_re = re.compile ('^\\\\input{([^}]*)')
49 include_re = re.compile ('^\\\\include{([^}]*)')
50 begin_document_re = re.compile ('^ *\\\\begin{document}')
51 documentclass_re = re.compile('\\\\documentclass')
52 twocolumn_re = re.compile('\\\\twocolumn')
53 onecolumn_re = re.compile('\\\\onecolumn')
54 preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample')
55 postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample')
56 boundingBox_re = re.compile('%%BoundingBox: ([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
58 def file_exist_b(name):
59 try:
60 f = open(name)
61 except IOError:
62 return 0
63 f.close ()
64 return 1
66 def ps_dimention(fname):
67 fd = open(fname)
68 lines = fd.readlines()
69 for line in lines:
70 s = boundingBox_re.search(line)
71 if s:
72 break
73 return (int(s.groups()[2])-int(s.groups()[0]),
74 int(s.groups()[3])-int(s.groups()[1]))
77 class CompileStatus:
78 pass
79 class SomethingIsSeriouslyBroken:
80 pass
82 def file_mtime (name):
83 return os.stat (name)[8] #mod time
85 def need_recompile_b(infile, outfile):
86 indate = file_mtime (infile)
87 try:
88 outdate = file_mtime (outfile)
89 return indate > outdate
90 except os.error:
91 return 1
94 # executes os.system(command) if infile is newer than
95 # outfile or outfile don't exist
97 def compile (command, workingdir, infile, outfile):
98 "Test if infile is newer than outfile. If so, cd to workingdir"
99 "and execute command"
100 indate = file_mtime (workingdir+infile)
101 try:
102 outdate = file_mtime (workingdir+outfile)
103 recompile = indate > outdate
105 except os.error:
106 recompile = 1
108 if recompile:
109 sys.stderr.write ('invoking `%s\'\n' % command)
110 if workingdir == '':
111 status = os.system (command)
112 else:
113 status = os.system ('cd %s; %s' %(workingdir, command))
114 if status:
115 raise CompileStatus
117 class Properties:
119 # init
121 def __init__(self):
122 self.__linewidth = {
123 1: {'a4':{10: 345, 11: 360, 12: 390},
124 'a5':{10: 276, 11: 276, 12: 276},
125 'b5':{10: 345, 11: 356, 12: 356},
126 'letter':{10: 345, 11: 360, 12: 390},
127 'legal': {10: 345, 11: 360, 12: 390},
128 'executive':{10: 345, 11: 360, 12: 379}},
129 2: {'a4':{10: 167, 11: 175, 12: 190},
130 'a5':{10: 133, 11: 133, 12: 133},
131 'b5':{10: 167, 11: 173, 12: 173},
132 'letter':{10: 167, 11: 175, 12: 190},
133 'legal':{10: 167, 11: 175, 12: 190},
134 'executive':{10: 167, 11: 175, 12: 184}}}
135 # >0 --> force all mudela to this pt size
136 self.force_mudela_fontsize = 0
137 self.force_verbatim_b = 0
138 self.__data = {
139 'mudela-fontsize' : {'init': 16},
140 'papersize' : {'init': 'a4'},
141 'num-column' : {'init': 1},
142 'tex-fontsize' : {'init': 11}
144 def clear_for_new_file(self):
145 for var in self.__data.keys():
146 self.__data[var] = {'init': self.__data[var]['init']}
147 def clear_for_new_block(self):
148 for var in self.__data.keys():
149 if self.__data[var].has_key('block'):
150 del self.__data[var]['block']
151 def __get_variable(self, var):
152 if self.__data[var].has_key('block'):
153 return self.__data[var]['block']
154 elif self.__data[var].has_key('file'):
155 return self.__data[var]['file']
156 else:
157 return self.__data[var]['init']
158 def setPapersize(self, size, requester):
159 self.__data['papersize'][requester] = size
160 def getPapersize(self):
161 return self.__get_variable('papersize')
162 def setMudelaFontsize(self, size, requester):
163 self.__data['mudela-fontsize'][requester] = size
164 def getMudelaFontsize(self):
165 if self.force_mudela_fontsize:
166 return self.force_mudela_fontsize
167 return self.__get_variable('mudela-fontsize')
168 def setTexFontsize(self, size, requester):
169 self.__data['tex-fontsize'][requester] = size
170 def getTexFontsize(self):
171 return self.__get_variable('tex-fontsize')
172 def setNumColumn(self, num, requester):
173 self.__data['num-column'][requester] = num
174 def getNumColumn(self):
175 return self.__get_variable('num-column')
176 def getLineWidth(self):
177 return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()]
180 class Mudela_output:
181 def __init__ (self, basename):
182 self.basename = basename
183 self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
184 self.file = open (self.temp_filename, 'w')
185 # 'tex' or 'eps'
186 self.graphic_type = 'tex'
187 self.fragment = 0
188 def write (self, line):
189 # match only if there is nothing but whitespace before \begin HACK
190 if re.search('^\s*\\\\begin{mudela}', line):
191 self.scan_begin_statement(line)
192 self.write_red_tape()
193 else:
194 self.file.write (line)
195 def scan_begin_statement(self, line):
196 r = begin_mudela_opts_re.search(line)
197 if r:
198 o = r.group()[1:][:-1]
199 optlist = re.compile('[ ,]*').split(o)
200 else:
201 optlist = []
202 if 'floating' in optlist:
203 self.graphic_type = 'eps'
204 else:
205 self.graphic_type = 'tex'
206 if 'fragment' in optlist:
207 self.fragment = 1
208 else:
209 self.fragment = 0
210 for pt in fontsize_pt2i.keys():
211 if pt in optlist:
212 Props.setMudelaFontsize(fontsize_pt2i[pt], 'block')
213 def write_red_tape(self):
214 self.file.write ('\\include \"paper%d.ly\"\n' % Props.getMudelaFontsize())
215 s = fontsize_i2a[Props.getMudelaFontsize()]
216 if self.fragment:
217 self.file.write("\\paper {" # mudela 1.0.4
218 + "\\paper_%s\n linewidth = -1.\\pt;" % s
219 + "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
220 self.file.write("\\score{\n\\notes{")
221 else:
222 self.file.write ("\paper {"
223 + "\\paper_%s\n linewidth = %i.\\pt;" % \
224 (s, Props.getLineWidth()) \
225 + "castingalgorithm = \Wordwrap; indent = 2.\cm;\n}")
226 def close (self):
227 if self.fragment:
228 self.file.write ('}\\paper { } }\n')
229 self.file.close ()
231 inf = outdir + self.basename + '.ly'
232 outf = outdir + self.basename + '.tex'
233 if not os.path.isfile(inf):
234 status = 1
235 else:
236 status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
237 if status:
238 os.rename (self.temp_filename, inf)
239 if need_recompile_b(inf, outf):
240 out_files.append((self.graphic_type, inf))
241 def insert_me_string(self):
242 "Returns a string that can be used directly in latex."
243 if self.graphic_type == 'tex':
244 return ['tex', self.basename]
245 elif self.graphic_type == 'eps':
246 return ['eps', self.basename]
247 else:
248 raise SomethingIsSeriouslyBroken
250 class Tex_output:
251 def __init__ (self, name):
252 self.output_fn = '%s/%s' % (outdir, name)
253 self.__lines = []
254 def open_verbatim (self):
255 self.__lines.append('\\begin{verbatim}\n')
256 def close_verbatim (self):
257 self.__lines.append('\\end{verbatim}\n')
258 def write (self, s):
259 self.__lines.append(s)
260 def create_graphics(self):
261 s = ''
262 g_vec = []
263 for line in self.__lines:
264 if type(line)==type([]):
265 g_vec.append(line)
266 for g in g_vec:
267 if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'):
268 s = s + ' ' + g[1]+'.ly'
269 if s != '':
270 os.system('cd %s; lilypond %s' %(outdir, s))
271 for g in g_vec:
272 if g[0] == 'eps':
273 compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi')
274 compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir,
275 g[1]+'.dvi', g[1]+'.eps')
276 def write_outfile(self):
277 outfn = self.output_fn+'.latex'
278 sys.stderr.write ('Writing output to `%s\'\n'% outfn)
279 file = open(outfn, 'w')
280 file.write('% Created by mudela-book\n')
281 for line in self.__lines:
282 if type(line)==type([]):
283 if line[0] == 'tex':
284 file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\
285 # % (outdir+line[1]+'.tex'))
287 # TeX applies the prefix of the main source automatically.
288 % (line[1]+'.tex'))
289 if line[0] == 'eps':
290 ps_dim = ps_dimention(outdir+line[1]+'.eps')
291 file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \
292 # % (ps_dim[0], outdir+line[1]+'.eps'))
293 % (ps_dim[0], line[1]+'.eps'))
295 else:
296 file.write(line)
297 file.close()
298 class Tex_input:
299 def __init__ (self, filename):
300 for fn in [filename, filename+'.tex', filename+'.doc']:
301 try:
302 self.infile = open (fn)
303 except:
304 continue
305 self.filename = fn
306 break
307 def get_lines (self):
308 lines = self.infile.readlines ()
309 (retlines, retdeps) = ([],[self.filename])
310 for line in lines:
311 r_inp = input_re.search (line)
312 r_inc = include_re.search (line)
313 # Filename rules for \input :
314 # input: no .tex ext
315 # 1. will search for file with exact that name (tex-input.my will be found)
316 # 2. will search for file with .tex ext, (tex-input.my
317 # will find tex-input.my.tex)
318 # input: with .tex ext
319 # 1. will find exact match
321 # Filename rules for \include :
322 # 1. will only find files with name given to \include + .tex ext
323 if r_inp:
324 t = Tex_input (r_inp.groups()[0])
325 ls = t.get_lines ()
326 retlines = retlines + ls[0]
327 retdeps = retdeps + ls[1]
328 elif r_inc:
329 t = Tex_input (r_inc.groups()[0]+'.tex')
330 ls =t.get_lines ()
331 ls[0].insert(0, '\\newpage\n')
332 ls[0].append('\\newpage\n')
333 retlines = retlines + ls[0]
334 retdeps = retdeps + ls[1]
335 else:
336 retlines.append (line)
337 return (retlines, retdeps)
340 class Main_tex_input(Tex_input):
341 def __init__ (self, name, outname):
343 Tex_input.__init__ (self, name) # ugh
344 self.outname = outname
345 self.chapter = 0
346 self.section = 0
347 self.fine_count =0
348 self.mudtex = Tex_output (self.outname)
349 self.mudela = None
350 self.deps = []
351 self.verbatim = 0
352 # set to 'mudela' when we are processing mudela code,
353 # both verbatim and graphic-to-be
354 self.mode = 'latex'
355 def set_sections (self, l):
356 if section_re.search (l):
357 self.section = self.section + 1
358 if chapter_re.search (l):
359 self.section = 0
360 self.chapter = self.chapter + 1
362 def gen_basename (self):
363 return '%s-%d.%d.%d' % (self.outname,self.chapter,self.section,self.fine_count)
364 #return '%s/%s-%d.%d.%d' % (outdir, self.outname,self.chapter,self.section,self.fine_count)
366 def extract_papersize_from_documentclass(self, line):
367 pre = extract_papersize_re.search(line)
368 if not pre:
369 return None
370 return pre.groups()[0]
371 def extract_fontsize_from_documentclass(self, line):
372 if extract_fontsize1_re.search(line):
373 r = extract_fontsize2_re.search(line)
374 if r:
375 return int(r.groups()[0])
376 def do_it(self):
377 preMudelaDef = postMudelaDef = 0
378 (lines, self.deps) = self.get_lines ()
379 for line in lines:
380 if documentclass_re.search (line):
381 p = self.extract_papersize_from_documentclass (line)
382 if p:
383 Props.setPapersize(p, 'file')
384 f = self.extract_fontsize_from_documentclass (line)
385 if f:
386 Props.setTexFontsize (f, 'file')
387 elif twocolumn_re.search (line):
388 Props.setNumColumn (2, 'file')
389 elif onecolumn_re.search (line):
390 Props.setNumColumn (1, 'file')
391 elif preMudelaExample_re.search (line):
392 preMudelaDef = 1
393 elif postMudelaExample_re.search (line):
394 postMudelaDef = 1
395 elif begin_document_re.search (line):
396 if not preMudelaDef:
397 self.mudtex.write ('\\def\\preMudelaExample{}\n')
398 if not postMudelaDef:
399 self.mudtex.write ('\\def\\postMudelaExample{}\n')
400 elif begin_mudela_re.search (line):
401 Props.clear_for_new_block()
402 if __debug__:
403 if self.mode == 'mudela':
404 raise AssertionError
405 self.mode = 'mudela'
406 r = begin_mudela_opts_re.search (line)
407 if r:
408 o = r.group()[1:][:-1]
409 optlist = re.compile('[ ,]*').split(o)
410 else:
411 optlist = []
412 if ('verbatim' in optlist) or (Props.force_verbatim_b):
413 self.verbatim = 1
414 self.mudtex.open_verbatim ()
415 else:
416 self.verbatim = 0
417 self.mudela = Mudela_output (self.gen_basename ())
419 elif end_mudela_re.search (line):
420 if __debug__:
421 if self.mode != 'mudela':
422 raise AssertionError
423 if self.mudela:
424 self.mudela.close ()
425 self.mudtex.write (self.mudela.insert_me_string())
426 del self.mudela
427 self.mudela = None
428 self.fine_count = self.fine_count + 1
429 else:
430 self.mudtex.write (line)
431 self.mudtex.close_verbatim ()
432 self.mode = 'latex'
433 continue
435 if self.mode == 'mudela' and not self.verbatim:
436 self.mudela.write (line)
437 else:
438 self.mudtex.write (line)
439 self.set_sections(line)
440 self.mudtex.create_graphics()
441 self.mudtex.write_outfile()
442 del self.mudtex
445 def help():
446 sys.stdout.write("""Usage: mudela-book [options] FILE\n
447 Generate hybrid LaTeX input from Latex + mudela
448 Options:\n
449 -h, --help print this help
450 -d, --outdir=DIR directory to put generated files
451 -o, --outname=FILE prefix for filenames
452 --default-mudela-fontsize=??pt default fontsize for music
453 --force-mudela-fontsize=??pt force fontsize for all inline mudela
454 --force-verbatim make all mudela verbatim\n"""
456 sys.exit (0)
459 def write_deps (fn, out, deps):
460 out_fn = outdir + '/' + fn
461 out_fn = re.sub ('//', '/', out_fn)
462 print 'writing `%s\'\n' % out_fn
464 f = open (out_fn, 'w')
465 f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
466 reduce (lambda x,y: x + ' '+ y, deps)))
467 f.close ()
469 def identify():
470 sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
472 def main():
473 global outdir
474 outname = ''
475 try:
476 (options, files) = getopt.getopt(
477 sys.argv[1:], 'hd:o:', ['outdir=', 'outname=',
478 'default-mudela-fontsize=',
479 'force-mudela-fontsize=',
480 'help', 'dependencies',
481 'force-verbatim'])
482 except getopt.error, msg:
483 print "error:", msg
484 sys.exit(1)
486 do_deps = 0
487 for opt in options:
488 o = opt[0]
489 a = opt[1]
490 if o == '--outname' or o == '-o':
491 if len(files) > 1:
492 #HACK
493 print "Mudela-book is confused by --outname on multiple files"
494 sys.exit(1)
495 outname = a
496 if o == '--outdir' or o == '-d':
497 outdir = a
498 if o == '--help' or o == '-h':
499 help ()
500 if o == '--dependencies':
501 do_deps = 1
502 if o == '--default-mudela-fontsize':
503 if not fontsize_pt2i.has_key(a):
504 print "Error: illegal fontsize:", a
505 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
506 sys.exit()
507 Props.setMudelaFontsize(fontsize_pt2i[a], 'init')
508 if o == '--force-mudela-fontsize':
509 if not fontsize_pt2i.has_key(a):
510 print "Error: illegal fontsize:", a
511 print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt"
512 sys.exit()
513 Props.force_mudela_fontsize = fontsize_pt2i[a]
514 if o == '--force-verbatim':
515 Props.force_verbatim_b = 1
517 if outdir[-1:] != '/':
518 outdir = outdir + '/'
520 if not os.path.isdir(outdir):
521 os.system('mkdir %s' % outdir)
523 for input_filename in files:
524 Props.clear_for_new_file()
525 if outname:
526 my_outname = outname
527 else:
528 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
529 my_depname = my_outname + '.dep'
530 inp = Main_tex_input (input_filename, my_outname)
531 inp.do_it ()
534 if do_deps:
535 write_deps (my_depname, my_outname, inp.deps)
537 identify()
538 Props = Properties()
539 main()