lilypond-1.3.130
[lilypond.git] / scripts / lilypond-book.py
blobea8f9ce871a8d7240a75be47dd9ff4f50bcbb505
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
6 # * EndLilyPondOutput is def'd as vfil. Causes large white gaps.
7 # * texinfo: add support for @pagesize
9 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
10 # because the values are taken directly from texinfo.tex,
11 # geometry.sty and article.cls. Give me a hint, and I'll
12 # fix it.)
14 # This is was the idea for handling of comments:
15 # Multiline comments, @ignore .. @end ignore is scanned for
16 # in read_doc_file, and the chunks are marked as 'ignore', so
17 # lilypond-book will not touch them any more. The content of the
18 # chunks are written to the output file. Also 'include' and 'input'
19 # regex has to check if they are commented out.
21 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
22 # These three regex's has to check if they are on a commented line,
23 # % for latex, @c for texinfo.
25 # Then lines that are commented out with % (latex) and @c (Texinfo)
26 # are put into chunks marked 'ignore'. This cannot be done before
27 # searching for the lilypond-blocks because % is also the comment character
28 # for lilypond.
30 # The the rest of the rexeces are searched for. They don't have to test
31 # if they are on a commented out line.
33 import os
34 import stat
35 import string
36 import re
37 import getopt
38 import sys
39 import __main__
40 import operator
43 program_version = '@TOPLEVEL_VERSION@'
44 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
45 program_version = '1.3.113'
47 include_path = [os.getcwd()]
50 # g_ is for global (?)
52 g_here_dir = os.getcwd ()
53 g_dep_prefix = ''
54 g_outdir = ''
55 g_force_lilypond_fontsize = 0
56 g_read_lys = 0
57 g_do_pictures = 1
58 g_num_cols = 1
59 format = ''
60 g_run_lilypond = 1
61 no_match = 'a\ba'
63 default_music_fontsize = 16
64 default_text_fontsize = 12
65 paperguru = None
67 # this code is ugly. It should be cleaned
68 class LatexPaper:
69 def __init__(self):
70 self.m_paperdef = {
71 # the dimensions are from geometry.sty
72 'a0paper': (mm2pt(841), mm2pt(1189)),
73 'a1paper': (mm2pt(595), mm2pt(841)),
74 'a2paper': (mm2pt(420), mm2pt(595)),
75 'a3paper': (mm2pt(297), mm2pt(420)),
76 'a4paper': (mm2pt(210), mm2pt(297)),
77 'a5paper': (mm2pt(149), mm2pt(210)),
78 'b0paper': (mm2pt(1000), mm2pt(1414)),
79 'b1paper': (mm2pt(707), mm2pt(1000)),
80 'b2paper': (mm2pt(500), mm2pt(707)),
81 'b3paper': (mm2pt(353), mm2pt(500)),
82 'b4paper': (mm2pt(250), mm2pt(353)),
83 'b5paper': (mm2pt(176), mm2pt(250)),
84 'letterpaper': (in2pt(8.5), in2pt(11)),
85 'legalpaper': (in2pt(8.5), in2pt(14)),
86 'executivepaper': (in2pt(7.25), in2pt(10.5))}
87 self.m_use_geometry = None
88 self.m_papersize = 'letterpaper'
89 self.m_fontsize = 10
90 self.m_num_cols = 1
91 self.m_landscape = 0
92 self.m_geo_landscape = 0
93 self.m_geo_width = None
94 self.m_geo_textwidth = None
95 self.m_geo_lmargin = None
96 self.m_geo_rmargin = None
97 self.m_geo_includemp = None
98 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
99 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
100 self.m_geo_x_marginparwidth = None
101 self.m_geo_x_marginparsep = None
102 self.__body = None
103 def set_geo_option(self, name, value):
104 if name == 'body' or name == 'text':
105 if type(value) == type(""):
106 self.m_geo_textwidth = value
107 else:
108 self.m_geo_textwidth = value[0]
109 self.__body = 1
110 elif name == 'portrait':
111 self.m_geo_landscape = 0
112 elif name == 'reversemp' or name == 'reversemarginpar':
113 if self.m_geo_includemp == None:
114 self.m_geo_includemp = 1
115 elif name == 'marginparwidth' or name == 'marginpar':
116 self.m_geo_x_marginparwidth = value
117 self.m_geo_includemp = 1
118 elif name == 'marginparsep':
119 self.m_geo_x_marginparsep = value
120 self.m_geo_includemp = 1
121 elif name == 'scale':
122 if type(value) == type(""):
123 self.m_geo_width = self.get_paperwidth() * float(value)
124 else:
125 self.m_geo_width = self.get_paperwidth() * float(value[0])
126 elif name == 'hscale':
127 self.m_geo_width = self.get_paperwidth() * float(value)
128 elif name == 'left' or name == 'lmargin':
129 self.m_geo_lmargin = value
130 elif name == 'right' or name == 'rmargin':
131 self.m_geo_rmargin = value
132 elif name == 'hdivide' or name == 'divide':
133 if value[0] not in ('*', ''):
134 self.m_geo_lmargin = value[0]
135 if value[1] not in ('*', ''):
136 self.m_geo_width = value[1]
137 if value[2] not in ('*', ''):
138 self.m_geo_rmargin = value[2]
139 elif name == 'hmargin':
140 if type(value) == type(""):
141 self.m_geo_lmargin = value
142 self.m_geo_rmargin = value
143 else:
144 self.m_geo_lmargin = value[0]
145 self.m_geo_rmargin = value[1]
146 elif name == 'margin':#ugh there is a bug about this option in
147 # the geometry documentation
148 if type(value) == type(""):
149 self.m_geo_lmargin = value
150 self.m_geo_rmargin = value
151 else:
152 self.m_geo_lmargin = value[0]
153 self.m_geo_rmargin = value[0]
154 elif name == 'total':
155 if type(value) == type(""):
156 self.m_geo_width = value
157 else:
158 self.m_geo_width = value[0]
159 elif name == 'width' or name == 'totalwidth':
160 self.m_geo_width = value
161 elif name == 'paper' or name == 'papername':
162 self.m_papersize = value
163 elif name[-5:] == 'paper':
164 self.m_papersize = name
165 else:
166 self._set_dimen('m_geo_'+name, value)
167 def __setattr__(self, name, value):
168 if type(value) == type("") and \
169 dimension_conversion_dict.has_key (value[-2:]):
170 f = dimension_conversion_dict[dim]
171 self.__dict__[name] = f(float(value[:-2]))
172 else:
173 self.__dict__[name] = value
175 def __str__(self):
176 s = "LatexPaper:\n-----------"
177 for v in self.__dict__.keys():
178 if v[:2] == 'm_':
179 s = s + str (v) + ' ' + str (self.__dict__[v])
180 s = s + "-----------"
181 return s
183 def get_linewidth(self):
184 w = self._calc_linewidth()
185 if self.m_num_cols == 2:
186 return (w - 10) / 2
187 else:
188 return w
189 def get_paperwidth(self):
190 #if self.m_use_geometry:
191 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
192 #return self.m_paperdef[self.m_papersize][self.m_landscape]
194 def _calc_linewidth(self):
195 # since geometry sometimes ignores 'includemp', this is
196 # more complicated than it should be
197 mp = 0
198 if self.m_geo_includemp:
199 if self.m_geo_x_marginparsep is not None:
200 mp = mp + self.m_geo_x_marginparsep
201 else:
202 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
203 if self.m_geo_x_marginparwidth is not None:
204 mp = mp + self.m_geo_x_marginparwidth
205 else:
206 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
208 #ugh test if this is necessary
209 if self.__body:
210 mp = 0
212 if not self.m_use_geometry:
213 return latex_linewidths[self.m_papersize][self.m_fontsize]
214 else:
215 geo_opts = (a == None, b == None, c == None)
217 if geo_opts == (1, 1, 1):
218 if self.m_geo_textwidth:
219 return self.m_geo_textwidth
220 w = self.get_paperwidth() * 0.8
221 return w - mp
222 elif geo_opts == (0, 1, 1):
223 if self.m_geo_textwidth:
224 return self.m_geo_textwidth
225 return self.f1(self.m_geo_lmargin, mp)
226 elif geo_opts == (1, 1, 0):
227 if self.m_geo_textwidth:
228 return self.m_geo_textwidth
229 return self.f1(self.m_geo_rmargin, mp)
230 elif geo_opts \
231 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
232 if self.m_geo_textwidth:
233 return self.m_geo_textwidth
234 return self.m_geo_width - mp
235 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
236 w = self.get_paperwidth() \
237 - self.m_geo_lmargin - self.m_geo_rmargin - mp
238 if w < 0:
239 w = 0
240 return w
241 raise "Never do this!"
242 def f1(self, m, mp):
243 tmp = self.get_paperwidth() - m * 2 - mp
244 if tmp < 0:
245 tmp = 0
246 return tmp
247 def f2(self):
248 tmp = self.get_paperwidth() - self.m_geo_lmargin \
249 - self.m_geo_rmargin
250 if tmp < 0:
251 return 0
252 return tmp
254 class TexiPaper:
255 def __init__(self):
256 self.m_papersize = 'letterpaper'
257 self.m_fontsize = 12
258 def get_linewidth(self):
259 return texi_linewidths[self.m_papersize][self.m_fontsize]
261 def mm2pt(x):
262 return x * 2.8452756
263 def in2pt(x):
264 return x * 72.26999
265 def em2pt(x, fontsize = 10):
266 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
267 def ex2pt(x, fontsize = 10):
268 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
270 def pt2pt(x):
271 return x
273 dimension_conversion_dict ={
274 'mm': mm2pt,
275 'in': in2pt,
276 'em': em2pt,
277 'ex': ex2pt,
278 'pt': pt2pt
282 # latex linewidths:
283 # indices are no. of columns, papersize, fontsize
284 # Why can't this be calculated?
285 latex_linewidths = {
286 'a4paper':{10: 345, 11: 360, 12: 390},
287 'a4paper-landscape': {10: 598, 11: 596, 12:592},
288 'a5paper':{10: 276, 11: 276, 12: 276},
289 'b5paper':{10: 345, 11: 356, 12: 356},
290 'letterpaper':{10: 345, 11: 360, 12: 390},
291 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
292 'legalpaper': {10: 345, 11: 360, 12: 390},
293 'executivepaper':{10: 345, 11: 360, 12: 379}}
295 texi_linewidths = {
296 'afourpaper': {12: mm2pt(160)},
297 'afourwide': {12: in2pt(6.5)},
298 'afourlatex': {12: mm2pt(150)},
299 'smallbook': {12: in2pt(5)},
300 'letterpaper': {12: in2pt(6)}}
302 option_definitions = [
303 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
304 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
305 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
306 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
307 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
308 ('DIR', 'I', 'include', 'include path'),
309 ('', 'M', 'dependencies', 'write dependencies'),
310 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
311 ('', 'n', 'no-lily', 'don\'t run lilypond'),
312 ('', '', 'no-pictures', "don\'t generate pictures"),
313 ('', '', 'read-lys', "don't write ly files."),
314 ('FILE', 'o', 'outname', 'filename main output file'),
315 ('FILE', '', 'outdir', "where to place generated files"),
316 ('', 'v', 'version', 'print version information' ),
317 ('', 'h', 'help', 'print help'),
320 # format specific strings, ie. regex-es for input, and % strings for output
321 output_dict= {
322 'latex': {
323 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
324 \context Staff <
325 \context Voice{
329 \end{lilypond}""",
330 'output-filename' : r'''
332 \verb+%s+:''',
333 'output-lilypond': r"""\begin[%s]{lilypond}
335 \end{lilypond}""",
336 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
337 'output-default-post': "\\def\postLilypondExample{}\n",
338 'output-default-pre': "\\def\preLilypondExample{}\n",
339 'usepackage-graphics': '\\usepackage{graphics}\n',
340 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
341 'output-tex': '\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n',
342 'pagebreak': r'\pagebreak',
344 'texi' : {'output-lilypond': """@lilypond[%s]
346 @end lilypond
347 """,
348 'output-filename' : r'''
350 @file{%s}:''',
351 'output-lilypond-fragment': """@lilypond[%s]
352 \context Staff\context Voice{ %s }
353 @end lilypond """,
354 'pagebreak': None,
355 'output-verbatim': r"""@example
357 @end example
358 """,
360 # do some tweaking: @ is needed in some ps stuff.
361 # override EndLilyPondOutput, since @tex is done
362 # in a sandbox, you can't do \input lilyponddefs at the
363 # top of the document.
365 # should also support fragment in
367 'output-all': r"""
368 @tex
369 \catcode`\@=12
370 \input lilyponddefs
371 \def\EndLilyPondOutput{}
372 \input %(fn)s.tex
373 \catcode`\@=0
374 @end tex
375 @html
377 <img src=%(fn)s.png>
378 @end html
379 """,
383 def output_verbatim (body):
384 if __main__.format == 'texi':
385 body = re.sub ('([@{}])', '@\\1', body)
386 return get_output ('output-verbatim') % body
389 re_dict = {
390 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
391 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
392 'option-sep' : ', *',
393 'header': r"\\documentclass\s*(\[.*?\])?",
394 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
395 'preamble-end': r'(?P<code>\\begin{document})',
396 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
397 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
398 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
399 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
400 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{lilypond}(?P<code>.*?)\\end{lilypond})",
401 'def-post-re': r"\\def\\postLilypondExample",
402 'def-pre-re': r"\\def\\preLilypondExample",
403 'usepackage-graphics': r"\usepackage{graphics}",
404 'intertext': r',?\s*intertext=\".*?\"',
405 'multiline-comment': no_match,
406 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
407 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
411 # why do we have distinction between @mbinclude and @include?
412 'texi': {
413 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
414 'input': no_match,
415 'header': no_match,
416 'preamble-end': no_match,
417 'landscape': no_match,
418 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
419 'verb': r"""(?P<code>@code{.*?})""",
420 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
421 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
422 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
423 'option-sep' : ', *',
424 'intertext': r',?\s*intertext=\".*?\"',
425 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
426 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
427 'numcols': no_match,
432 for r in re_dict.keys ():
433 olddict = re_dict[r]
434 newdict = {}
435 for k in olddict.keys ():
436 newdict[k] = re.compile (olddict[k])
437 re_dict[r] = newdict
440 def uniq (list):
441 list.sort ()
442 s = list
443 list = []
444 for x in s:
445 if x not in list:
446 list.append (x)
447 return list
450 def get_output (name):
451 return output_dict[format][name]
453 def get_re (name):
454 return re_dict[format][name]
456 def bounding_box_dimensions(fname):
457 if g_outdir:
458 fname = os.path.join(g_outdir, fname)
459 try:
460 fd = open(fname)
461 except IOError:
462 error ("Error opening `%s'" % fname)
463 str = fd.read ()
464 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
465 if s:
466 return (int(s.group(3))-int(s.group(1)),
467 int(s.group(4))-int(s.group(2)))
468 else:
469 return (0,0)
472 def error (str):
473 sys.stderr.write (str + "\n Exiting ... \n\n")
474 raise 'Exiting.'
477 def compose_full_body (body, opts):
478 """Construct the lilypond code to send to Lilypond.
479 Add stuff to BODY using OPTS as options."""
480 music_size = default_music_fontsize
481 latex_size = default_text_fontsize
482 for o in opts:
483 if g_force_lilypond_fontsize:
484 music_size = g_force_lilypond_fontsize
485 else:
486 m = re.match ('([0-9]+)pt', o)
487 if m:
488 music_size = string.atoi(m.group (1))
490 m = re.match ('latexfontsize=([0-9]+)pt', o)
491 if m:
492 latex_size = string.atoi (m.group (1))
494 if re.search ('\\\\score', body):
495 is_fragment = 0
496 else:
497 is_fragment = 1
498 if 'fragment' in opts:
499 is_fragment = 1
500 if 'nonfragment' in opts:
501 is_fragment = 0
503 if is_fragment and not 'multiline' in opts:
504 opts.append('singleline')
505 if 'singleline' in opts:
506 l = -1.0;
507 else:
508 l = __main__.paperguru.get_linewidth()
510 if 'relative' in opts:#ugh only when is_fragment
511 body = '\\relative c { %s }' % body
513 if is_fragment:
514 body = r"""\score {
515 \notes { %s }
516 \paper { }
517 }""" % body
519 opts = uniq (opts)
520 optstring = string.join (opts, ' ')
521 optstring = re.sub ('\n', ' ', optstring)
522 body = r"""
523 %% Generated by lilypond-book.py; options are %s %%ughUGH not original options
524 \include "paper%d.ly"
525 \paper { linewidth = %f \pt; }
526 """ % (optstring, music_size, l) + body
527 return body
529 def parse_options_string(s):
530 d = {}
531 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
532 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
533 r3 = re.compile("(\w+?)((,\s*)|$)")
534 while s:
535 m = r1.match(s)
536 if m:
537 s = s[m.end():]
538 d[m.group(2)] = re.split(",\s*", m.group(3))
539 continue
540 m = r2.match(s)
541 if m:
542 s = s[m.end():]
543 d[m.group(2)] = m.group(3)
544 continue
545 m = r3.match(s)
546 if m:
547 s = s[m.end():]
548 d[m.group(1)] = 1
549 continue
551 error ("format of option string invalid (was `%')" % s)
552 return d
554 def scan_latex_preamble(chunks):
555 # first we want to scan the \documentclass line
556 # it should be the first non-comment line
557 idx = 0
558 while 1:
559 if chunks[idx][0] == 'ignore':
560 idx = idx + 1
561 continue
562 m = get_re ('header').match(chunks[idx][1])
563 options = re.split (',[\n \t]*', m.group(1)[1:-1])
564 for o in options:
565 if o == 'landscape':
566 paperguru.m_landscape = 1
567 m = re.match("(.*?)paper", o)
568 if m:
569 paperguru.m_papersize = m.group()
570 else:
571 m = re.match("(\d\d)pt", o)
572 if m:
573 paperguru.m_fontsize = int(m.group(1))
575 break
576 while chunks[idx][0] != 'preamble-end':
577 if chunks[idx] == 'ignore':
578 idx = idx + 1
579 continue
580 m = get_re ('geometry').search(chunks[idx][1])
581 if m:
582 paperguru.m_use_geometry = 1
583 o = parse_options_string(m.group('options'))
584 for k in o.keys():
585 paperguru.set_geo_option(k, o[k])
586 idx = idx + 1
588 def scan_texi_preamble (chunks):
589 # this is not bulletproof..., it checks the first 10 chunks
590 for c in chunks[:10]:
591 if c[0] == 'input':
592 for s in ('afourpaper', 'afourwide', 'letterpaper',
593 'afourlatex', 'smallbook'):
594 if string.find(c[1], "@%s" % s) != -1:
595 paperguru.m_papersize = s
597 def scan_preamble (chunks):
598 if __main__.format == 'texi':
599 scan_texi_preamble(chunks)
600 else:
601 assert __main__.format == 'latex'
602 scan_latex_preamble(chunks)
605 def completize_preamble (chunks):
606 if __main__.format == 'texi':
607 return chunks
608 pre_b = post_b = graphics_b = None
609 for chunk in chunks:
610 if chunk[0] == 'preamble-end':
611 break
612 if chunk[0] == 'input':
613 m = get_re('def-pre-re').search(chunk[1])
614 if m:
615 pre_b = 1
616 if chunk[0] == 'input':
617 m = get_re('def-post-re').search(chunk[1])
618 if m:
619 post_b = 1
620 if chunk[0] == 'input':
621 m = get_re('usepackage-graphics').search(chunk[1])
622 if m:
623 graphics_b = 1
624 x = 0
625 while chunks[x][0] != 'preamble-end':
626 x = x + 1
627 if not pre_b:
628 chunks.insert(x, ('input', get_output ('output-default-pre')))
629 if not post_b:
630 chunks.insert(x, ('input', get_output ('output-default-post')))
631 if not graphics_b:
632 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
633 return chunks
636 read_files = []
637 def find_file (name):
639 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
642 f = None
643 nm = ''
644 for a in include_path:
645 try:
646 nm = os.path.join (a, name)
647 f = open (nm)
648 __main__.read_files.append (nm)
649 break
650 except IOError:
651 pass
652 if f:
653 sys.stderr.write ("Reading `%s'\n" % nm)
654 return (f.read (), nm)
655 else:
656 error ("File not found `%s'\n" % name)
657 return ('', '')
659 def do_ignore(match_object):
660 return [('ignore', match_object.group('code'))]
661 def do_preamble_end(match_object):
662 return [('preamble-end', match_object.group('code'))]
664 def make_verbatim(match_object):
665 return [('verbatim', match_object.group('code'))]
667 def make_verb(match_object):
668 return [('verb', match_object.group('code'))]
670 def do_include_file(m):
671 "m: MatchObject"
672 return [('input', get_output ('pagebreak'))] \
673 + read_doc_file(m.group('filename')) \
674 + [('input', get_output ('pagebreak'))]
676 def do_input_file(m):
677 return read_doc_file(m.group('filename'))
679 def make_lilypond(m):
680 if m.group('options'):
681 options = m.group('options')
682 else:
683 options = ''
684 return [('input', get_output('output-lilypond-fragment') %
685 (options, m.group('code')))]
687 def make_lilypond_file(m):
690 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
691 into a @lilypond .. @end lilypond block.
695 if m.group('options'):
696 options = m.group('options')
697 else:
698 options = ''
699 (content, nm) = find_file(m.group('filename'))
700 options = "filename=%s," % nm + options
702 return [('input', get_output('output-lilypond') %
703 (options, content))]
705 def make_lilypond_block(m):
706 if m.group('options'):
707 options = get_re('option-sep').split (m.group('options'))
708 else:
709 options = []
710 options = filter(lambda s: s != '', options)
711 return [('lilypond', m.group('code'), options)]
713 def do_columns(m):
714 if __main__.format != 'latex':
715 return []
716 if m.group('num') == 'one':
717 return [('numcols', m.group('code'), 1)]
718 if m.group('num') == 'two':
719 return [('numcols', m.group('code'), 2)]
721 def chop_chunks(chunks, re_name, func, use_match=0):
722 newchunks = []
723 for c in chunks:
724 if c[0] == 'input':
725 str = c[1]
726 while str:
727 m = get_re (re_name).search (str)
728 if m == None:
729 newchunks.append (('input', str))
730 str = ''
731 else:
732 if use_match:
733 newchunks.append (('input', str[:m.start ('match')]))
734 else:
735 newchunks.append (('input', str[:m.start (0)]))
736 #newchunks.extend(func(m))
737 # python 1.5 compatible:
738 newchunks = newchunks + func(m)
739 str = str [m.end(0):]
740 else:
741 newchunks.append(c)
742 return newchunks
744 def determine_format (str):
745 if __main__.format == '':
747 latex = re.search ('\\\\document', str[:200])
748 texinfo = re.search ('@node|@setfilename', str[:200])
750 f = ''
751 g = None
753 if texinfo and latex == None:
754 f = 'texi'
755 elif latex and texinfo == None:
756 f = 'latex'
757 else:
758 error("error: can't determine format, please specify")
759 __main__.format = f
761 if __main__.paperguru == None:
762 if __main__.format == 'texi':
763 g = TexiPaper()
764 else:
765 g = LatexPaper()
767 __main__.paperguru = g
770 def read_doc_file (filename):
771 """Read the input file, find verbatim chunks and do \input and \include
773 (str, path) = find_file(filename)
774 determine_format (str)
776 chunks = [('input', str)]
778 # we have to check for verbatim before doing include,
779 # because we don't want to include files that are mentioned
780 # inside a verbatim environment
781 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
782 chunks = chop_chunks(chunks, 'verb', make_verb)
783 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
784 #ugh fix input
785 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
786 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
787 return chunks
790 taken_file_names = {}
791 def schedule_lilypond_block (chunk):
792 """Take the body and options from CHUNK, figure out how the
793 real .ly should look, and what should be left MAIN_STR (meant
794 for the main file). The .ly is written, and scheduled in
795 TODO.
797 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
799 TODO has format [basename, extension, extension, ... ]
802 (type, body, opts) = chunk
803 assert type == 'lilypond'
804 file_body = compose_full_body (body, opts)
805 basename = `abs(hash (file_body))`
806 for o in opts:
807 m = re.search ('filename="(.*?)"', o)
808 if m:
809 basename = m.group (1)
810 if not taken_file_names.has_key(basename):
811 taken_file_names[basename] = 0
812 else:
813 taken_file_names[basename] = taken_file_names[basename] + 1
814 basename = basename + "-%i" % taken_file_names[basename]
815 if not g_read_lys:
816 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
817 needed_filetypes = ['tex']
819 if format == 'texi':
820 needed_filetypes.append('eps')
821 needed_filetypes.append('png')
822 if 'eps' in opts and not ('eps' in needed_filetypes):
823 needed_filetypes.append('eps')
824 pathbase = os.path.join (g_outdir, basename)
825 def f(base, ext1, ext2):
826 a = os.path.isfile(base + ext2)
827 if (os.path.isfile(base + ext1) and
828 os.path.isfile(base + ext2) and
829 os.stat(base+ext1)[stat.ST_MTIME] >
830 os.stat(base+ext2)[stat.ST_MTIME]) or \
831 not os.path.isfile(base + ext2):
832 return 1
833 todo = []
834 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
835 todo.append('tex')
836 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
837 todo.append('eps')
838 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
839 todo.append('png')
840 newbody = ''
842 if 'printfilename' in opts:
843 for o in opts:
844 m= re.match ("filename=(.*)", o)
845 if m:
846 newbody = newbody + get_output ("output-filename") % m.group(1)
847 break
850 if 'verbatim' in opts:
851 newbody = output_verbatim (body)
853 for o in opts:
854 m = re.search ('intertext="(.*?)"', o)
855 if m:
856 newbody = newbody + m.group (1) + "\n\n"
857 if format == 'latex':
858 if 'eps' in opts:
859 s = 'output-eps'
860 else:
861 s = 'output-tex'
862 else: # format == 'texi'
863 s = 'output-all'
864 newbody = newbody + get_output (s) % {'fn': basename }
865 return ('lilypond', newbody, opts, todo, basename)
867 def process_lilypond_blocks(outname, chunks):#ugh rename
868 newchunks = []
869 # Count sections/chapters.
870 for c in chunks:
871 if c[0] == 'lilypond':
872 c = schedule_lilypond_block (c)
873 elif c[0] == 'numcols':
874 paperguru.m_num_cols = c[2]
875 newchunks.append (c)
876 return newchunks
879 def find_eps_dims (match):
880 "Fill in dimensions of EPS files."
882 fn =match.group (1)
883 dims = bounding_box_dimensions (fn)
884 if g_outdir:
885 fn = os.path.join(g_outdir, fn)
887 return '%ipt' % dims[0]
890 def system (cmd):
891 sys.stderr.write ("invoking `%s'\n" % cmd)
892 st = os.system (cmd)
893 if st:
894 error ('Error command exited with value %d\n' % st)
895 return st
897 def compile_all_files (chunks):
898 eps = []
899 tex = []
900 png = []
902 for c in chunks:
903 if c[0] <> 'lilypond':
904 continue
905 base = c[4]
906 exts = c[3]
907 for e in exts:
908 if e == 'eps':
909 eps.append (base)
910 elif e == 'tex':
911 #ugh
912 if base + '.ly' not in tex:
913 tex.append (base + '.ly')
914 elif e == 'png' and g_do_pictures:
915 png.append (base)
916 d = os.getcwd()
917 if g_outdir:
918 os.chdir(g_outdir)
919 if tex:
920 # fixme: be sys-independent.
921 def incl_opt (x):
922 if g_outdir and x[0] <> '/' :
923 x = os.path.join (g_here_dir, x)
924 return ' -I %s' % x
926 incs = map (incl_opt, include_path)
927 lilyopts = string.join (incs, ' ' )
928 texfiles = string.join (tex, ' ')
929 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
930 for e in eps:
931 system(r"tex '\nonstopmode \input %s'" % e)
932 system(r"dvips -E -o %s %s" % (e + '.eps', e))
933 for g in png:
934 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
935 cmd = cmd % (g + '.eps', g + '.png')
936 system (cmd)
937 if g_outdir:
938 os.chdir(d)
941 def update_file (body, name):
943 write the body if it has changed
945 same = 0
946 try:
947 f = open (name)
948 fs = f.read (-1)
949 same = (fs == body)
950 except:
951 pass
953 if not same:
954 f = open (name , 'w')
955 f.write (body)
956 f.close ()
958 return not same
961 def getopt_args (opts):
962 "Construct arguments (LONG, SHORT) for getopt from list of options."
963 short = ''
964 long = []
965 for o in opts:
966 if o[1]:
967 short = short + o[1]
968 if o[0]:
969 short = short + ':'
970 if o[2]:
971 l = o[2]
972 if o[0]:
973 l = l + '='
974 long.append (l)
975 return (short, long)
977 def option_help_str (o):
978 "Transform one option description (4-tuple ) into neatly formatted string"
979 sh = ' '
980 if o[1]:
981 sh = '-%s' % o[1]
983 sep = ' '
984 if o[1] and o[2]:
985 sep = ','
987 long = ''
988 if o[2]:
989 long= '--%s' % o[2]
991 arg = ''
992 if o[0]:
993 if o[2]:
994 arg = '='
995 arg = arg + o[0]
996 return ' ' + sh + sep + long + arg
999 def options_help_str (opts):
1000 "Convert a list of options into a neatly formatted string"
1001 w = 0
1002 strs =[]
1003 helps = []
1005 for o in opts:
1006 s = option_help_str (o)
1007 strs.append ((s, o[3]))
1008 if len (s) > w:
1009 w = len (s)
1011 str = ''
1012 for s in strs:
1013 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1014 return str
1016 def help():
1017 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1018 Generate hybrid LaTeX input from Latex + lilypond
1019 Options:
1020 """)
1021 sys.stdout.write (options_help_str (option_definitions))
1022 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1026 Report bugs to bug-gnu-music@gnu.org.
1028 Written by Tom Cato Amundsen <tca@gnu.org> and
1029 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1030 """)
1032 sys.exit (0)
1035 def write_deps (fn, target):
1036 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
1037 f = open (os.path.join(g_outdir, fn), 'w')
1038 f.write ('%s%s: ' % (g_dep_prefix, target))
1039 for d in __main__.read_files:
1040 f.write ('%s ' % d)
1041 f.write ('\n')
1042 f.close ()
1043 __main__.read_files = []
1045 def identify():
1046 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1048 def print_version ():
1049 identify()
1050 sys.stdout.write (r"""Copyright 1998--1999
1051 Distributed under terms of the GNU General Public License. It comes with
1052 NO WARRANTY.
1053 """)
1056 def check_texidoc (chunks):
1057 n = []
1058 for c in chunks:
1059 if c[0] == 'lilypond':
1060 (type, body, opts, todo, basename) = c;
1061 pathbase = os.path.join (g_outdir, basename)
1062 if os.path.isfile (pathbase + '.texidoc'):
1063 body = '\n@include %s.texidoc\n' % basename + body
1064 c = (type, body, opts, todo, basename)
1065 n.append (c)
1066 return n
1068 def fix_epswidth (chunks):
1069 newchunks = []
1070 for c in chunks:
1071 if c[0] == 'lilypond' and 'eps' in c[2]:
1072 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", find_eps_dims, c[1])
1073 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1074 else:
1075 newchunks.append (c)
1076 return newchunks
1079 def do_file(input_filename):
1080 file_settings = {}
1081 if outname:
1082 my_outname = outname
1083 else:
1084 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1085 my_depname = my_outname + '.dep'
1087 chunks = read_doc_file(input_filename)
1088 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1089 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1090 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1091 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1092 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1093 chunks = chop_chunks(chunks, 'numcols', do_columns)
1094 #print "-" * 50
1095 #for c in chunks: print "c:", c;
1096 #sys.exit()
1097 scan_preamble(chunks)
1098 chunks = process_lilypond_blocks(my_outname, chunks)
1100 # Do It.
1101 if __main__.g_run_lilypond:
1102 compile_all_files (chunks)
1103 chunks = fix_epswidth (chunks)
1105 if __main__.format == 'texi':
1106 chunks = check_texidoc (chunks)
1108 x = 0
1109 chunks = completize_preamble (chunks)
1110 foutn = os.path.join(g_outdir, my_outname + '.' + format)
1111 sys.stderr.write ("Writing `%s'\n" % foutn)
1112 fout = open (foutn, 'w')
1113 for c in chunks:
1114 fout.write (c[1])
1115 fout.close ()
1116 # should chmod -w
1118 if do_deps:
1119 write_deps (my_depname, foutn)
1122 outname = ''
1123 try:
1124 (sh, long) = getopt_args (__main__.option_definitions)
1125 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1126 except getopt.error, msg:
1127 sys.stderr.write("error: %s" % msg)
1128 sys.exit(1)
1130 do_deps = 0
1131 for opt in options:
1132 o = opt[0]
1133 a = opt[1]
1135 if o == '--include' or o == '-I':
1136 include_path.append (a)
1137 elif o == '--version' or o == '-v':
1138 print_version ()
1139 sys.exit (0)
1140 elif o == '--format' or o == '-f':
1141 __main__.format = a
1142 elif o == '--outname' or o == '-o':
1143 if len(files) > 1:
1144 #HACK
1145 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1146 sys.exit(1)
1147 outname = a
1148 elif o == '--help' or o == '-h':
1149 help ()
1150 elif o == '--no-lily' or o == '-n':
1151 __main__.g_run_lilypond = 0
1152 elif o == '--dependencies' or o == '-M':
1153 do_deps = 1
1154 elif o == '--default-music-fontsize':
1155 default_music_fontsize = string.atoi (a)
1156 elif o == '--default-lilypond-fontsize':
1157 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1158 default_music_fontsize = string.atoi (a)
1159 elif o == '--force-music-fontsize':
1160 g_force_lilypond_fontsize = string.atoi(a)
1161 elif o == '--force-lilypond-fontsize':
1162 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1163 g_force_lilypond_fontsize = string.atoi(a)
1164 elif o == '--dep-prefix':
1165 g_dep_prefix = a
1166 elif o == '--no-pictures':
1167 g_do_pictures = 0
1168 elif o == '--read-lys':
1169 g_read_lys = 1
1170 elif o == '--outdir':
1171 g_outdir = a
1173 identify()
1174 if g_outdir:
1175 if os.path.isfile(g_outdir):
1176 error ("outdir is a file: %s" % g_outdir)
1177 if not os.path.exists(g_outdir):
1178 os.mkdir(g_outdir)
1179 for input_filename in files:
1180 do_file(input_filename)
1183 # Petr, ik zou willen dat ik iets zinvoller deed,
1184 # maar wat ik kan ik doen, het verandert toch niets?
1185 # --hwn 20/aug/99