lilypond-1.3.122
[lilypond.git] / scripts / mudela-book.py
blobd37806d3a91145d281b7a7b12a53a7b6a9161030
1 #!@PYTHON@
2 # vim: set noexpandtab:
3 # TODO:
4 # * Figure out clean set of options. Hmm, isn't it pretty ok now?
5 # * add support for .lilyrc
8 # todo: dimension handling (all the x2y) is clumsy.
10 # This is was the idea for handling of comments:
11 # Multiline comments, @ignore .. @end ignore is scanned for
12 # in read_doc_file, and the chunks are marked as 'ignore', so
13 # mudela-book will not touch them any more. The content of the
14 # chunks are written to the output file. Also 'include' and 'input'
15 # regex has to check if they are commented out.
17 # Then it is scanned for 'mudela', 'mudela-file' and 'mudela-block'.
18 # These three regex's has to check if they are on a commented line,
19 # % for latex, @c for texinfo.
21 # Then lines that are commented out with % (latex) and @c (Texinfo)
22 # are put into chunks marked 'ignore'. This cannot be done before
23 # searching for the mudela-blocks because % is also the comment character
24 # for lilypond.
26 # The the rest of the rexeces are searched for. They don't have to test
27 # if they are on a commented out line.
29 import os
30 import stat
31 import string
32 import re
33 import getopt
34 import sys
35 import __main__
36 import operator
39 program_version = '@TOPLEVEL_VERSION@'
40 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
41 program_version = '1.3.106'
43 include_path = [os.getcwd()]
46 # g_ is for global (?)
48 g_here_dir = os.getcwd ()
49 g_dep_prefix = ''
50 g_outdir = ''
51 g_force_mudela_fontsize = 0
52 g_read_lys = 0
53 g_do_pictures = 1
54 g_num_cols = 1
55 format = ''
56 g_run_lilypond = 1
57 no_match = 'a\ba'
59 default_music_fontsize = 16
60 default_text_fontsize = 12
63 class LatexPaper:
64 def __init__(self):
65 self.m_paperdef = {
66 # the dimentions are from geometry.sty
67 'a0paper': (mm2pt(841), mm2pt(1189)),
68 'a1paper': (mm2pt(595), mm2pt(841)),
69 'a2paper': (mm2pt(420), mm2pt(595)),
70 'a3paper': (mm2pt(297), mm2pt(420)),
71 'a4paper': (mm2pt(210), mm2pt(297)),
72 'a5paper': (mm2pt(149), mm2pt(210)),
73 'b0paper': (mm2pt(1000), mm2pt(1414)),
74 'b1paper': (mm2pt(707), mm2pt(1000)),
75 'b2paper': (mm2pt(500), mm2pt(707)),
76 'b3paper': (mm2pt(353), mm2pt(500)),
77 'b4paper': (mm2pt(250), mm2pt(353)),
78 'b5paper': (mm2pt(176), mm2pt(250)),
79 'letterpaper': (in2pt(8.5), in2pt(11)),
80 'legalpaper': (in2pt(8.5), in2pt(14)),
81 'executivepaper': (in2pt(7.25), in2pt(10.5))}
82 self.m_use_geometry = None
83 self.m_papersize = 'letterpaper'
84 self.m_fontsize = 10
85 self.m_num_cols = 1
86 self.m_landscape = 0
87 self.m_geo_landscape = 0
88 self.m_geo_width = None
89 self.m_geo_textwidth = None
90 self.m_geo_lmargin = None
91 self.m_geo_rmargin = None
92 self.m_geo_includemp = None
93 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
94 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
95 self.m_geo_x_marginparwidth = None
96 self.m_geo_x_marginparsep = None
97 self.__body = None
98 def set_geo_option(self, name, value):
99 if name == 'body' or name == 'text':
100 if type(value) == type(""):
101 self._set_dimen('m_geo_textwidth', value)
102 else:
103 self._set_dimen('m_geo_textwidth', value[0])
104 self.__body = 1
105 elif name == 'portrait':
106 self.m_geo_landscape = 0
107 elif name == 'reversemp' or name == 'reversemarginpar':
108 if self.m_geo_includemp == None:
109 self.m_geo_includemp = 1
110 elif name == 'marginparwidth' or name == 'marginpar':
111 self._set_dimen('m_geo_x_marginparwidth', value)
112 self.m_geo_includemp = 1
113 elif name == 'marginparsep':
114 self._set_dimen('m_geo_x_marginparsep', value)
115 self.m_geo_includemp = 1
116 elif name == 'scale':
117 if type(value) == type(""):
118 self.m_geo_width = self.get_paperwidth() * float(value)
119 else:
120 self.m_geo_width = self.get_paperwidth() * float(value[0])
121 elif name == 'hscale':
122 self.m_geo_width = self.get_paperwidth() * float(value)
123 elif name == 'left' or name == 'lmargin':
124 self._set_dimen('m_geo_lmargin', value)
125 elif name == 'right' or name == 'rmargin':
126 self._set_dimen('m_geo_rmargin', value)
127 elif name == 'hdivide' or name == 'divide':
128 if value[0] not in ('*', ''):
129 self._set_dimen('m_geo_lmargin', value[0])
130 if value[1] not in ('*', ''):
131 self._set_dimen('m_geo_width', value[1])
132 if value[2] not in ('*', ''):
133 self._set_dimen('m_geo_rmargin', value[2])
134 elif name == 'hmargin':
135 if type(value) == type(""):
136 self._set_dimen('m_geo_lmargin', value)
137 self._set_dimen('m_geo_rmargin', value)
138 else:
139 self._set_dimen('m_geo_lmargin', value[0])
140 self._set_dimen('m_geo_rmargin', value[1])
141 elif name == 'margin':#ugh there is a bug about this option in
142 # the geometry documentation
143 if type(value) == type(""):
144 self._set_dimen('m_geo_lmargin', value)
145 self._set_dimen('m_geo_rmargin', value)
146 else:
147 self._set_dimen('m_geo_lmargin', value[0])
148 self._set_dimen('m_geo_rmargin', value[0])
149 elif name == 'total':
150 if type(value) == type(""):
151 self._set_dimen('m_geo_width', value)
152 else:
153 self._set_dimen('m_geo_width', value[0])
154 elif name == 'width' or name == 'totalwidth':
155 self._set_dimen('m_geo_width', value)
156 elif name == 'paper' or name == 'papername':
157 self.m_papersize = value
158 elif name[-5:] == 'paper':
159 self.m_papersize = name
160 else:
161 self._set_dimen('m_geo_'+name, value)
162 def _set_dimen(self, name, value):
163 if type(value) == type("") and value[-2:] == 'pt':
164 self.__dict__[name] = float(value[:-2])
165 elif type(value) == type("") and value[-2:] == 'mm':
166 self.__dict__[name] = mm2pt(float(value[:-2]))
167 elif type(value) == type("") and value[-2:] == 'cm':
168 self.__dict__[name] = 10 * mm2pt(float(value[:-2]))
169 elif type(value) == type("") and value[-2:] == 'in':
170 self.__dict__[name] = in2pt(float(value[:-2]))
171 else:
172 self.__dict__[name] = value
173 def display(self):
174 print "LatexPaper:\n-----------"
175 for v in self.__dict__.keys():
176 if v[:2] == 'm_':
177 print v, self.__dict__[v]
178 print "-----------"
179 def get_linewidth(self):
180 w = self._calc_linewidth()
181 if self.m_num_cols == 2:
182 return (w - 10) / 2
183 else:
184 return w
185 def get_paperwidth(self):
186 #if self.m_use_geometry:
187 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
188 #return self.m_paperdef[self.m_papersize][self.m_landscape]
190 def _calc_linewidth(self):
191 # since geometry sometimes ignores 'includemp', this is
192 # more complicated than it should be
193 mp = 0
194 if self.m_geo_includemp:
195 if self.m_geo_x_marginparsep is not None:
196 mp = mp + self.m_geo_x_marginparsep
197 else:
198 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
199 if self.m_geo_x_marginparwidth is not None:
200 mp = mp + self.m_geo_x_marginparwidth
201 else:
202 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
203 if self.__body:#ugh test if this is necessary
204 mp = 0
205 def tNone(a, b, c):
206 return a == None, b == None, c == None
207 if not self.m_use_geometry:
208 return latex_linewidths[self.m_papersize][self.m_fontsize]
209 else:
210 if tNone(self.m_geo_lmargin, self.m_geo_width,
211 self.m_geo_rmargin) == (1, 1, 1):
212 if self.m_geo_textwidth:
213 return self.m_geo_textwidth
214 w = self.get_paperwidth() * 0.8
215 return w - mp
216 elif tNone(self.m_geo_lmargin, self.m_geo_width,
217 self.m_geo_rmargin) == (0, 1, 1):
218 if self.m_geo_textwidth:
219 return self.m_geo_textwidth
220 return self.f1(self.m_geo_lmargin, mp)
221 elif tNone(self.m_geo_lmargin, self.m_geo_width,
222 self.m_geo_rmargin) == (1, 1, 0):
223 if self.m_geo_textwidth:
224 return self.m_geo_textwidth
225 return self.f1(self.m_geo_rmargin, mp)
226 elif tNone(self.m_geo_lmargin, self.m_geo_width,
227 self.m_geo_rmargin) \
228 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
229 if self.m_geo_textwidth:
230 return self.m_geo_textwidth
231 return self.m_geo_width - mp
232 elif tNone(self.m_geo_lmargin, self.m_geo_width,
233 self.m_geo_rmargin) in ((0, 1, 0), (0, 0, 0)):
234 w = self.get_paperwidth() - self.m_geo_lmargin - self.m_geo_rmargin - mp
235 if w < 0:
236 w = 0
237 return w
238 raise "Never do this!"
239 def f1(self, m, mp):
240 tmp = self.get_paperwidth() - m * 2 - mp
241 if tmp < 0:
242 tmp = 0
243 return tmp
244 def f2(self):
245 tmp = self.get_paperwidth() - self.m_geo_lmargin \
246 - self.m_geo_rmargin
247 if tmp < 0:
248 return 0
249 return tmp
251 class TexiPaper:
252 def __init__(self):
253 self.m_papersize = 'a4'
254 self.m_fontsize = 12
255 def get_linewidth(self):
256 return texi_linewidths[self.m_papersize][self.m_fontsize]
258 def mm2pt(x):
259 return x * 2.8452756
260 def in2pt(x):
261 return x * 72.26999
262 def em2pt(x, fontsize):
263 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
264 def ex2pt(x, fontsize):
265 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
267 # latex linewidths:
268 # indices are no. of columns, papersize, fontsize
269 # Why can't this be calculated?
270 latex_linewidths = {
271 'a4paper':{10: 345, 11: 360, 12: 390},
272 'a4paper-landscape': {10: 598, 11: 596, 12:592},
273 'a5paper':{10: 276, 11: 276, 12: 276},
274 'b5paper':{10: 345, 11: 356, 12: 356},
275 'letterpaper':{10: 345, 11: 360, 12: 390},
276 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
277 'legalpaper': {10: 345, 11: 360, 12: 390},
278 'executivepaper':{10: 345, 11: 360, 12: 379}}
280 texi_linewidths = {
281 'a4': {12: 455},
282 'a4wide': {12: 470},
283 'smallbook': {12: 361},
284 'texidefault': {12: 433}}
286 option_definitions = [
287 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
288 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
289 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
290 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
291 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
292 ('DIR', 'I', 'include', 'include path'),
293 ('', 'M', 'dependencies', 'write dependencies'),
294 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
295 ('', 'n', 'no-lily', 'don\'t run lilypond'),
296 ('', '', 'no-pictures', "don\'t generate pictures"),
297 ('', '', 'read-lys', "don't write ly files."),
298 ('FILE', 'o', 'outname', 'filename main output file'),
299 ('FILE', '', 'outdir', "where to place generated files"),
300 ('', 'v', 'version', 'print version information' ),
301 ('', 'h', 'help', 'print help'),
304 # format specific strings, ie. regex-es for input, and % strings for output
305 output_dict= {
306 'latex': {
307 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
308 \context Staff <
309 \context Voice{
313 \end{mudela}""",
314 'output-mudela':r"""\begin[%s]{mudela}
316 \end{mudela}""",
317 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
318 'output-default-post': "\\def\postMudelaExample{}\n",
319 'output-default-pre': "\\def\preMudelaExample{}\n",
320 'usepackage-graphics': '\\usepackage{graphics}\n',
321 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
322 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
323 'pagebreak': r'\pagebreak',
325 'texi' : {'output-mudela': """@mudela[%s]
327 @end mudela
328 """,
329 'output-mudela-fragment': """@mudela[%s]
330 \context Staff\context Voice{ %s }
331 @end mudela """,
332 'pagebreak': None,
333 'output-verbatim': r"""@example
335 @end example
336 """,
338 # do some tweaking: @ is needed in some ps stuff.
339 # override EndLilyPondOutput, since @tex is done
340 # in a sandbox, you can't do \input lilyponddefs at the
341 # top of the document.
343 # should also support fragment in
345 'output-all': r"""@tex
346 \catcode`\@=12
347 \input lilyponddefs
348 \def\EndLilyPondOutput{}
349 \input %(fn)s.tex
350 \catcode`\@=0
351 @end tex
352 @html
354 <img src=%(fn)s.png>
355 @end html
356 """,
360 def output_verbatim (body):
361 if __main__.format == 'texi':
362 body = re.sub ('([@{}])', '@\\1', body)
363 return get_output ('output-verbatim') % body
366 re_dict = {
367 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
368 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
369 'option-sep' : ', *',
370 'header': r"\\documentclass\s*(\[.*?\])?",
371 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
372 'preamble-end': r'(?P<code>\\begin{document})',
373 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
374 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
375 'mudela-file': r'(?m)^[^%\n]*?(?P<match>\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
376 'mudela' : r'(?m)^[^%\n]*?(?P<match>\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
377 'mudela-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela})",
378 'def-post-re': r"\\def\\postMudelaExample",
379 'def-pre-re': r"\\def\\preMudelaExample",
380 'usepackage-graphics': r"\usepackage{graphics}",
381 'intertext': r',?\s*intertext=\".*?\"',
382 'multiline-comment': no_match,
383 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
384 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
387 'texi': {
388 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
389 'input': no_match,
390 'header': no_match,
391 'preamble-end': no_match,
392 'landscape': no_match,
393 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
394 'verb': r"""(?P<code>@code{.*?})""",
395 'mudela-file': '(?m)^(?!@c)(?P<match>@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
396 'mudela' : '(?m)^(?!@c)(?P<match>@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
397 'mudela-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s))""",
398 'option-sep' : ', *',
399 'intertext': r',?\s*intertext=\".*?\"',
400 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
401 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
402 'numcols': no_match,
407 for r in re_dict.keys ():
408 olddict = re_dict[r]
409 newdict = {}
410 for k in olddict.keys ():
411 newdict[k] = re.compile (olddict[k])
412 re_dict[r] = newdict
415 def uniq (list):
416 list.sort ()
417 s = list
418 list = []
419 for x in s:
420 if x not in list:
421 list.append (x)
422 return list
425 def get_output (name):
426 return output_dict[format][name]
428 def get_re (name):
429 return re_dict[format][name]
431 def bounding_box_dimensions(fname):
432 try:
433 fd = open(fname)
434 except IOError:
435 error ("Error opening `%s'" % fname)
436 str = fd.read ()
437 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
438 if s:
439 return (int(s.group(3))-int(s.group(1)),
440 int(s.group(4))-int(s.group(2)))
441 else:
442 return (0,0)
445 def error (str):
446 sys.stderr.write (str + "\n Exiting ... \n\n")
447 raise 'Exiting.'
450 def compose_full_body (body, opts):
451 """Construct the mudela code to send to Lilypond.
452 Add stuff to BODY using OPTS as options."""
453 music_size = default_music_fontsize
454 latex_size = default_text_fontsize
455 for o in opts:
456 if g_force_mudela_fontsize:
457 music_size = g_force_mudela_fontsize
458 else:
459 m = re.match ('([0-9]+)pt', o)
460 if m:
461 music_size = string.atoi(m.group (1))
463 m = re.match ('latexfontsize=([0-9]+)pt', o)
464 if m:
465 latex_size = string.atoi (m.group (1))
467 if re.search ('\\\\score', body):
468 is_fragment = 0
469 else:
470 is_fragment = 1
471 if 'fragment' in opts:
472 is_fragment = 1
473 if 'nonfragment' in opts:
474 is_fragment = 0
476 if is_fragment and not 'multiline' in opts:
477 opts.append('singleline')
478 if 'singleline' in opts:
479 l = -1.0;
480 else:
481 l = paperguru.get_linewidth()
483 if 'relative' in opts:#ugh only when is_fragment
484 body = '\\relative c { %s }' % body
486 if is_fragment:
487 body = r"""\score {
488 \notes { %s }
489 \paper { }
490 }""" % body
492 opts = uniq (opts)
493 optstring = string.join (opts, ' ')
494 optstring = re.sub ('\n', ' ', optstring)
495 body = r"""
496 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
497 \include "paper%d.ly"
498 \paper { linewidth = %f \pt; }
499 """ % (optstring, music_size, l) + body
500 return body
502 def parse_options_string(s):
503 d = {}
504 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
505 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
506 r3 = re.compile("(\w+?)((,\s*)|$)")
507 while s:
508 m = r1.match(s)
509 if m:
510 s = s[m.end():]
511 d[m.group(2)] = re.split(",\s*", m.group(3))
512 continue
513 m = r2.match(s)
514 if m:
515 s = s[m.end():]
516 d[m.group(2)] = m.group(3)
517 continue
518 m = r3.match(s)
519 if m:
520 s = s[m.end():]
521 d[m.group(1)] = 1
522 continue
523 print "trøbbel:%s:" % s
524 return d
526 def scan_latex_preamble(chunks):
527 # first we want to scan the \documentclass line
528 # it should be the first non-comment line
529 idx = 0
530 while 1:
531 if chunks[idx][0] == 'ignore':
532 idx = idx + 1
533 continue
534 m = get_re ('header').match(chunks[idx][1])
535 options = re.split (',[\n \t]*', m.group(1)[1:-1])
536 for o in options:
537 if o == 'landscape':
538 paperguru.m_landscape = 1
539 m = re.match("(.*?)paper", o)
540 if m:
541 paperguru.m_papersize = m.group()
542 else:
543 m = re.match("(\d\d)pt", o)
544 if m:
545 paperguru.m_fontsize = int(m.group(1))
547 break
548 while chunks[idx][0] != 'preamble-end':
549 if chunks[idx] == 'ignore':
550 idx = idx + 1
551 continue
552 m = get_re ('geometry').search(chunks[idx][1])
553 if m:
554 paperguru.m_use_geometry = 1
555 o = parse_options_string(m.group('options'))
556 for k in o.keys():
557 paperguru.set_geo_option(k, o[k])
558 idx = idx + 1
560 def scan_texi_preamble (chunks):
561 # this is not bulletproof..., it checks the first 10 chunks
562 idx = 0
563 while 1:
564 if chunks[idx][0] == 'input':
565 if string.find(chunks[idx][1], "@afourpaper") != -1:
566 paperguru.m_papersize = 'a4'
567 elif string.find(chunks[idx][1], "@afourwide") != -1:
568 paperguru.m_papersize = 'a4wide'
569 elif string.find(chunks[idx][1], "@smallbook") != -1:
570 paperguru.m_papersize = 'smallbook'
571 idx = idx + 1
572 if idx == 10 or idx == len(chunks):
573 break
575 def scan_preamble (chunks):
576 if __main__.format == 'texi':
577 scan_texi_preamble(chunks)
578 else:
579 assert __main__.format == 'latex'
580 scan_latex_preamble(chunks)
583 def completize_preamble (chunks):
584 if __main__.format == 'texi':
585 return chunks
586 pre_b = post_b = graphics_b = None
587 for chunk in chunks:
588 if chunk[0] == 'preamble-end':
589 break
590 if chunk[0] == 'input':
591 m = get_re('def-pre-re').search(chunk[1])
592 if m:
593 pre_b = 1
594 if chunk[0] == 'input':
595 m = get_re('def-post-re').search(chunk[1])
596 if m:
597 post_b = 1
598 if chunk[0] == 'input':
599 m = get_re('usepackage-graphics').search(chunk[1])
600 if m:
601 graphics_b = 1
602 x = 0
603 while chunks[x][0] != 'preamble-end':
604 x = x + 1
605 if not pre_b:
606 chunks.insert(x, ('input', get_output ('output-default-pre')))
607 if not post_b:
608 chunks.insert(x, ('input', get_output ('output-default-post')))
609 if not graphics_b:
610 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
611 return chunks
614 read_files = []
615 def find_file (name):
616 f = None
617 for a in include_path:
618 try:
619 nm = os.path.join (a, name)
620 f = open (nm)
621 __main__.read_files.append (nm)
622 break
623 except IOError:
624 pass
625 if f:
626 return f.read ()
627 else:
628 error ("File not found `%s'\n" % name)
629 return ''
631 def do_ignore(match_object):
632 return [('ignore', match_object.group('code'))]
633 def do_preamble_end(match_object):
634 return [('preamble-end', match_object.group('code'))]
636 def make_verbatim(match_object):
637 return [('verbatim', match_object.group('code'))]
639 def make_verb(match_object):
640 return [('verb', match_object.group('code'))]
642 def do_include_file(m):
643 "m: MatchObject"
644 return [('input', get_output ('pagebreak'))] \
645 + read_doc_file(m.group('filename')) \
646 + [('input', get_output ('pagebreak'))]
648 def do_input_file(m):
649 return read_doc_file(m.group('filename'))
651 def make_mudela(m):
652 if m.group('options'):
653 options = m.group('options')
654 else:
655 options = ''
656 return [('input', get_output('output-mudela-fragment') %
657 (options, m.group('code')))]
659 def make_mudela_file(m):
660 if m.group('options'):
661 options = m.group('options')
662 else:
663 options = ''
664 return [('input', get_output('output-mudela') %
665 (options, find_file(m.group('filename'))))]
667 def make_mudela_block(m):
668 if m.group('options'):
669 options = get_re('option-sep').split (m.group('options'))
670 else:
671 options = []
672 options = filter(lambda s: s != '', options)
673 return [('mudela', m.group('code'), options)]
675 def do_columns(m):
676 if __main__.format != 'latex':
677 return []
678 if m.group('num') == 'one':
679 return [('numcols', m.group('code'), 1)]
680 if m.group('num') == 'two':
681 return [('numcols', m.group('code'), 2)]
683 def chop_chunks(chunks, re_name, func, use_match=0):
684 newchunks = []
685 for c in chunks:
686 if c[0] == 'input':
687 str = c[1]
688 while str:
689 m = get_re (re_name).search (str)
690 if m == None:
691 newchunks.append (('input', str))
692 str = ''
693 else:
694 if use_match:
695 newchunks.append (('input', str[:m.start ('match')]))
696 else:
697 newchunks.append (('input', str[:m.start (0)]))
698 #newchunks.extend(func(m))
699 # python 1.5 compatible:
700 newchunks = newchunks + func(m)
701 str = str [m.end(0):]
702 else:
703 newchunks.append(c)
704 return newchunks
706 def read_doc_file (filename):
707 """Read the input file, find verbatim chunks and do \input and \include
709 str = ''
710 str = find_file(filename)
712 if __main__.format == '':
713 latex = re.search ('\\\\document', str[:200])
714 texinfo = re.search ('@node|@setfilename', str[:200])
715 if (texinfo and latex) or not (texinfo or latex):
716 error("error: can't determine format, please specify")
717 if texinfo:
718 __main__.format = 'texi'
719 else:
720 __main__.format = 'latex'
721 if __main__.format == 'texi':
722 __main__.paperguru = TexiPaper()
723 else:
724 __main__.paperguru = LatexPaper()
725 chunks = [('input', str)]
726 # we have to check for verbatim before doing include,
727 # because we don't want to include files that are mentioned
728 # inside a verbatim environment
729 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
730 chunks = chop_chunks(chunks, 'verb', make_verb)
731 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
732 #ugh fix input
733 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
734 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
735 return chunks
738 taken_file_names = {}
739 def schedule_mudela_block (chunk):
740 """Take the body and options from CHUNK, figure out how the
741 real .ly should look, and what should be left MAIN_STR (meant
742 for the main file). The .ly is written, and scheduled in
743 TODO.
745 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
747 TODO has format [basename, extension, extension, ... ]
750 (type, body, opts) = chunk
751 assert type == 'mudela'
752 file_body = compose_full_body (body, opts)
753 basename = `abs(hash (file_body))`
754 for o in opts:
755 m = re.search ('filename="(.*?)"', o)
756 if m:
757 basename = m.group (1)
758 if not taken_file_names.has_key(basename):
759 taken_file_names[basename] = 0
760 else:
761 taken_file_names[basename] = taken_file_names[basename] + 1
762 basename = basename + "-%i" % taken_file_names[basename]
763 if not g_read_lys:
764 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
765 needed_filetypes = ['tex']
767 if format == 'texi':
768 needed_filetypes.append('eps')
769 needed_filetypes.append('png')
770 if 'eps' in opts and not ('eps' in needed_filetypes):
771 needed_filetypes.append('eps')
772 outname = os.path.join(g_outdir, basename)
773 def f(base, ext1, ext2):
774 a = os.path.isfile(base + ext2)
775 if (os.path.isfile(base + ext1) and
776 os.path.isfile(base + ext2) and
777 os.stat(base+ext1)[stat.ST_MTIME] >
778 os.stat(base+ext2)[stat.ST_MTIME]) or \
779 not os.path.isfile(base + ext2):
780 return 1
781 todo = []
782 if 'tex' in needed_filetypes and f(outname, '.ly', '.tex'):
783 todo.append('tex')
784 if 'eps' in needed_filetypes and f(outname, '.tex', '.eps'):
785 todo.append('eps')
786 if 'png' in needed_filetypes and f(outname, '.eps', '.png'):
787 todo.append('png')
788 newbody = ''
789 if 'verbatim' in opts:
790 newbody = output_verbatim (body)
792 for o in opts:
793 m = re.search ('intertext="(.*?)"', o)
794 if m:
795 newbody = newbody + m.group (1) + "\n\n"
796 if format == 'latex':
797 if 'eps' in opts:
798 s = 'output-eps'
799 else:
800 s = 'output-tex'
801 else: # format == 'texi'
802 s = 'output-all'
803 newbody = newbody + get_output(s) % {'fn': basename }
804 return ('mudela', newbody, opts, todo, basename)
806 def process_mudela_blocks(outname, chunks):#ugh rename
807 newchunks = []
808 # Count sections/chapters.
809 for c in chunks:
810 if c[0] == 'mudela':
811 c = schedule_mudela_block (c)
812 elif c[0] == 'numcols':
813 paperguru.m_num_cols = c[2]
814 newchunks.append (c)
815 return newchunks
818 def find_eps_dims (match):
819 "Fill in dimensions of EPS files."
821 fn =match.group (1)
822 dims = bounding_box_dimensions (fn)
823 if g_outdir:
824 fn = os.path.join(g_outdir, fn)
826 return '%ipt' % dims[0]
829 def system (cmd):
830 sys.stderr.write ("invoking `%s'\n" % cmd)
831 st = os.system (cmd)
832 if st:
833 error ('Error command exited with value %d\n' % st)
834 return st
836 def compile_all_files (chunks):
837 eps = []
838 tex = []
839 png = []
841 for c in chunks:
842 if c[0] <> 'mudela':
843 continue
844 base = c[4]
845 exts = c[3]
846 for e in exts:
847 if e == 'eps':
848 eps.append (base)
849 elif e == 'tex':
850 #ugh
851 if base + '.ly' not in tex:
852 tex.append (base + '.ly')
853 elif e == 'png' and g_do_pictures:
854 png.append (base)
855 d = os.getcwd()
856 if g_outdir:
857 os.chdir(g_outdir)
858 if tex:
859 # fixme: be sys-independent.
860 def incl_opt (x):
861 if g_outdir and x[0] <> '/' :
862 x = os.path.join (g_here_dir, x)
863 return ' -I %s' % x
865 incs = map (incl_opt, include_path)
866 lilyopts = string.join (incs, ' ' )
867 texfiles = string.join (tex, ' ')
868 system ('lilypond %s %s' % (lilyopts, texfiles))
869 for e in eps:
870 system(r"tex '\nonstopmode \input %s'" % e)
871 system(r"dvips -E -o %s %s" % (e + '.eps', e))
872 for g in png:
873 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
874 cmd = cmd % (g + '.eps', g + '.png')
875 system (cmd)
876 if g_outdir:
877 os.chdir(d)
880 def update_file (body, name):
882 write the body if it has changed
884 same = 0
885 try:
886 f = open (name)
887 fs = f.read (-1)
888 same = (fs == body)
889 except:
890 pass
892 if not same:
893 f = open (name , 'w')
894 f.write (body)
895 f.close ()
897 return not same
900 def getopt_args (opts):
901 "Construct arguments (LONG, SHORT) for getopt from list of options."
902 short = ''
903 long = []
904 for o in opts:
905 if o[1]:
906 short = short + o[1]
907 if o[0]:
908 short = short + ':'
909 if o[2]:
910 l = o[2]
911 if o[0]:
912 l = l + '='
913 long.append (l)
914 return (short, long)
916 def option_help_str (o):
917 "Transform one option description (4-tuple ) into neatly formatted string"
918 sh = ' '
919 if o[1]:
920 sh = '-%s' % o[1]
922 sep = ' '
923 if o[1] and o[2]:
924 sep = ','
926 long = ''
927 if o[2]:
928 long= '--%s' % o[2]
930 arg = ''
931 if o[0]:
932 if o[2]:
933 arg = '='
934 arg = arg + o[0]
935 return ' ' + sh + sep + long + arg
938 def options_help_str (opts):
939 "Convert a list of options into a neatly formatted string"
940 w = 0
941 strs =[]
942 helps = []
944 for o in opts:
945 s = option_help_str (o)
946 strs.append ((s, o[3]))
947 if len (s) > w:
948 w = len (s)
950 str = ''
951 for s in strs:
952 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
953 return str
955 def help():
956 sys.stdout.write("""Usage: mudela-book [options] FILE\n
957 Generate hybrid LaTeX input from Latex + mudela
958 Options:
959 """)
960 sys.stdout.write (options_help_str (option_definitions))
961 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
965 Report bugs to bug-gnu-music@gnu.org.
967 Written by Tom Cato Amundsen <tca@gnu.org> and
968 Han-Wen Nienhuys <hanwen@cs.uu.nl>
969 """)
971 sys.exit (0)
974 def write_deps (fn, target):
975 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
976 f = open (os.path.join(g_outdir, fn), 'w')
977 f.write ('%s%s: ' % (g_dep_prefix, target))
978 for d in __main__.read_files:
979 f.write ('%s ' % d)
980 f.write ('\n')
981 f.close ()
982 __main__.read_files = []
984 def identify():
985 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
987 def print_version ():
988 identify()
989 sys.stdout.write (r"""Copyright 1998--1999
990 Distributed under terms of the GNU General Public License. It comes with
991 NO WARRANTY.
992 """)
994 def do_file(input_filename):
995 file_settings = {}
996 if outname:
997 my_outname = outname
998 else:
999 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1000 my_depname = my_outname + '.dep'
1002 chunks = read_doc_file(input_filename)
1003 chunks = chop_chunks(chunks, 'mudela', make_mudela, 1)
1004 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file, 1)
1005 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block, 1)
1006 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1007 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1008 chunks = chop_chunks(chunks, 'numcols', do_columns)
1009 #print "-" * 50
1010 #for c in chunks: print "c:", c;
1011 #sys.exit()
1012 scan_preamble(chunks)
1013 chunks = process_mudela_blocks(my_outname, chunks)
1014 # Do It.
1015 if __main__.g_run_lilypond:
1016 compile_all_files (chunks)
1017 newchunks = []
1018 # finishing touch.
1019 for c in chunks:
1020 if c[0] == 'mudela' and 'eps' in c[2]:
1021 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
1022 newchunks.append (('mudela', body))
1023 else:
1024 newchunks.append (c)
1025 chunks = newchunks
1026 x = 0
1027 chunks = completize_preamble (chunks)
1028 foutn = os.path.join(g_outdir, my_outname + '.' + format)
1029 sys.stderr.write ("Writing `%s'\n" % foutn)
1030 fout = open (foutn, 'w')
1031 for c in chunks:
1032 fout.write (c[1])
1033 fout.close ()
1035 if do_deps:
1036 write_deps (my_depname, foutn)
1039 outname = ''
1040 try:
1041 (sh, long) = getopt_args (__main__.option_definitions)
1042 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1043 except getopt.error, msg:
1044 sys.stderr.write("error: %s" % msg)
1045 sys.exit(1)
1047 do_deps = 0
1048 for opt in options:
1049 o = opt[0]
1050 a = opt[1]
1052 if o == '--include' or o == '-I':
1053 include_path.append (a)
1054 elif o == '--version' or o == '-v':
1055 print_version ()
1056 sys.exit (0)
1057 elif o == '--format' or o == '-f':
1058 __main__.format = a
1059 elif o == '--outname' or o == '-o':
1060 if len(files) > 1:
1061 #HACK
1062 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
1063 sys.exit(1)
1064 outname = a
1065 elif o == '--help' or o == '-h':
1066 help ()
1067 elif o == '--no-lily' or o == '-n':
1068 __main__.g_run_lilypond = 0
1069 elif o == '--dependencies' or o == '-M':
1070 do_deps = 1
1071 elif o == '--default-music-fontsize':
1072 default_music_fontsize = string.atoi (a)
1073 elif o == '--default-mudela-fontsize':
1074 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
1075 default_music_fontsize = string.atoi (a)
1076 elif o == '--force-music-fontsize':
1077 g_force_mudela_fontsize = string.atoi(a)
1078 elif o == '--force-mudela-fontsize':
1079 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
1080 g_force_mudela_fontsize = string.atoi(a)
1081 elif o == '--dep-prefix':
1082 g_dep_prefix = a
1083 elif o == '--no-pictures':
1084 g_do_pictures = 0
1085 elif o == '--read-lys':
1086 g_read_lys = 1
1087 elif o == '--outdir':
1088 g_outdir = a
1090 identify()
1091 if g_outdir:
1092 if os.path.isfile(g_outdir):
1093 error ("outdir is a file: %s" % g_outdir)
1094 if not os.path.exists(g_outdir):
1095 os.mkdir(g_outdir)
1096 for input_filename in files:
1097 do_file(input_filename)
1100 # Petr, ik zou willen dat ik iets zinvoller deed,
1101 # maar wat ik kan ik doen, het verandert toch niets?
1102 # --hwn 20/aug/99