lilypond-1.3.136
[lilypond.git] / scripts / lilypond-book.py
blobe395ca7d48c87d21d06f8246748cb9467c6039b2
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 = (self.m_geo_lmargin == None,
216 self.m_geo_width == None,
217 self.m_geo_rmargin == None)
219 if geo_opts == (1, 1, 1):
220 if self.m_geo_textwidth:
221 return self.m_geo_textwidth
222 w = self.get_paperwidth() * 0.8
223 return w - mp
224 elif geo_opts == (0, 1, 1):
225 if self.m_geo_textwidth:
226 return self.m_geo_textwidth
227 return self.f1(self.m_geo_lmargin, mp)
228 elif geo_opts == (1, 1, 0):
229 if self.m_geo_textwidth:
230 return self.m_geo_textwidth
231 return self.f1(self.m_geo_rmargin, mp)
232 elif geo_opts \
233 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
234 if self.m_geo_textwidth:
235 return self.m_geo_textwidth
236 return self.m_geo_width - mp
237 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
238 w = self.get_paperwidth() \
239 - self.m_geo_lmargin - self.m_geo_rmargin - mp
240 if w < 0:
241 w = 0
242 return w
243 raise "Never do this!"
244 def f1(self, m, mp):
245 tmp = self.get_paperwidth() - m * 2 - mp
246 if tmp < 0:
247 tmp = 0
248 return tmp
249 def f2(self):
250 tmp = self.get_paperwidth() - self.m_geo_lmargin \
251 - self.m_geo_rmargin
252 if tmp < 0:
253 return 0
254 return tmp
256 class TexiPaper:
257 def __init__(self):
258 self.m_papersize = 'letterpaper'
259 self.m_fontsize = 12
260 def get_linewidth(self):
261 return texi_linewidths[self.m_papersize][self.m_fontsize]
263 def mm2pt(x):
264 return x * 2.8452756
265 def in2pt(x):
266 return x * 72.26999
267 def em2pt(x, fontsize = 10):
268 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
269 def ex2pt(x, fontsize = 10):
270 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
272 def pt2pt(x):
273 return x
275 dimension_conversion_dict ={
276 'mm': mm2pt,
277 'in': in2pt,
278 'em': em2pt,
279 'ex': ex2pt,
280 'pt': pt2pt
284 # latex linewidths:
285 # indices are no. of columns, papersize, fontsize
286 # Why can't this be calculated?
287 latex_linewidths = {
288 'a4paper':{10: 345, 11: 360, 12: 390},
289 'a4paper-landscape': {10: 598, 11: 596, 12:592},
290 'a5paper':{10: 276, 11: 276, 12: 276},
291 'b5paper':{10: 345, 11: 356, 12: 356},
292 'letterpaper':{10: 345, 11: 360, 12: 390},
293 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
294 'legalpaper': {10: 345, 11: 360, 12: 390},
295 'executivepaper':{10: 345, 11: 360, 12: 379}}
297 texi_linewidths = {
298 'afourpaper': {12: mm2pt(160)},
299 'afourwide': {12: in2pt(6.5)},
300 'afourlatex': {12: mm2pt(150)},
301 'smallbook': {12: in2pt(5)},
302 'letterpaper': {12: in2pt(6)}}
304 option_definitions = [
305 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
306 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
307 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
308 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
309 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
310 ('DIR', 'I', 'include', 'include path'),
311 ('', 'M', 'dependencies', 'write dependencies'),
312 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
313 ('', 'n', 'no-lily', 'don\'t run lilypond'),
314 ('', '', 'no-pictures', "don\'t generate pictures"),
315 ('', '', 'read-lys', "don't write ly files."),
316 ('FILE', 'o', 'outname', 'filename main output file'),
317 ('FILE', '', 'outdir', "where to place generated files"),
318 ('', 'v', 'version', 'print version information' ),
319 ('', 'h', 'help', 'print help'),
322 # format specific strings, ie. regex-es for input, and % strings for output
323 output_dict= {
324 'latex': {
325 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
326 \context Staff <
327 \context Voice{
331 \end{lilypond}""",
332 'output-filename' : r'''
334 \verb+%s+:''',
335 'output-lilypond': r"""\begin[%s]{lilypond}
337 \end{lilypond}""",
338 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
339 'output-default-post': "\\def\postLilypondExample{}\n",
340 'output-default-pre': "\\def\preLilypondExample{}\n",
341 'usepackage-graphics': '\\usepackage{graphics}\n',
342 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
343 'output-tex': '\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n',
344 'pagebreak': r'\pagebreak',
346 'texi' : {'output-lilypond': """@lilypond[%s]
348 @end lilypond
349 """,
350 'output-filename' : r'''
352 @file{%s}:''',
353 'output-lilypond-fragment': """@lilypond[%s]
354 \context Staff\context Voice{ %s }
355 @end lilypond """,
356 'pagebreak': None,
357 'output-verbatim': r"""@example
359 @end example
360 """,
362 # do some tweaking: @ is needed in some ps stuff.
363 # override EndLilyPondOutput, since @tex is done
364 # in a sandbox, you can't do \input lilyponddefs at the
365 # top of the document.
367 # should also support fragment in
369 'output-all': r"""
370 @tex
371 \catcode`\@=12
372 \input lilyponddefs
373 \def\EndLilyPondOutput{}
374 \input %(fn)s.tex
375 \catcode`\@=0
376 @end tex
377 @html
379 <img src=%(fn)s.png>
380 @end html
381 """,
385 def output_verbatim (body):
386 if __main__.format == 'texi':
387 body = re.sub ('([@{}])', '@\\1', body)
388 return get_output ('output-verbatim') % body
391 re_dict = {
392 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
393 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
394 'option-sep' : ', *',
395 'header': r"\\documentclass\s*(\[.*?\])?",
396 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
397 'preamble-end': r'(?P<code>\\begin{document})',
398 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
399 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
400 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
401 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
402 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{lilypond}(?P<code>.*?)\\end{lilypond})",
403 'def-post-re': r"\\def\\postLilypondExample",
404 'def-pre-re': r"\\def\\preLilypondExample",
405 'usepackage-graphics': r"\usepackage{graphics}",
406 'intertext': r',?\s*intertext=\".*?\"',
407 'multiline-comment': no_match,
408 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
409 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
413 # why do we have distinction between @mbinclude and @include?
414 'texi': {
415 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
416 'input': no_match,
417 'header': no_match,
418 'preamble-end': no_match,
419 'landscape': no_match,
420 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
421 'verb': r"""(?P<code>@code{.*?})""",
422 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
423 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
424 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
425 'option-sep' : ', *',
426 'intertext': r',?\s*intertext=\".*?\"',
427 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
428 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
429 'numcols': no_match,
434 for r in re_dict.keys ():
435 olddict = re_dict[r]
436 newdict = {}
437 for k in olddict.keys ():
438 newdict[k] = re.compile (olddict[k])
439 re_dict[r] = newdict
442 def uniq (list):
443 list.sort ()
444 s = list
445 list = []
446 for x in s:
447 if x not in list:
448 list.append (x)
449 return list
452 def get_output (name):
453 return output_dict[format][name]
455 def get_re (name):
456 return re_dict[format][name]
458 def bounding_box_dimensions(fname):
459 if g_outdir:
460 fname = os.path.join(g_outdir, fname)
461 try:
462 fd = open(fname)
463 except IOError:
464 error ("Error opening `%s'" % fname)
465 str = fd.read ()
466 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
467 if s:
468 return (int(s.group(3))-int(s.group(1)),
469 int(s.group(4))-int(s.group(2)))
470 else:
471 return (0,0)
474 def error (str):
475 sys.stderr.write (str + "\n Exiting ... \n\n")
476 raise 'Exiting.'
479 def compose_full_body (body, opts):
480 """Construct the lilypond code to send to Lilypond.
481 Add stuff to BODY using OPTS as options."""
482 music_size = default_music_fontsize
483 latex_size = default_text_fontsize
484 for o in opts:
485 if g_force_lilypond_fontsize:
486 music_size = g_force_lilypond_fontsize
487 else:
488 m = re.match ('([0-9]+)pt', o)
489 if m:
490 music_size = string.atoi(m.group (1))
492 m = re.match ('latexfontsize=([0-9]+)pt', o)
493 if m:
494 latex_size = string.atoi (m.group (1))
496 if re.search ('\\\\score', body):
497 is_fragment = 0
498 else:
499 is_fragment = 1
500 if 'fragment' in opts:
501 is_fragment = 1
502 if 'nonfragment' in opts:
503 is_fragment = 0
505 if is_fragment and not 'multiline' in opts:
506 opts.append('singleline')
507 if 'singleline' in opts:
508 l = -1.0;
509 else:
510 l = __main__.paperguru.get_linewidth()
512 if 'relative' in opts:#ugh only when is_fragment
513 body = '\\relative c { %s }' % body
515 if is_fragment:
516 body = r"""\score {
517 \notes { %s }
518 \paper { }
519 }""" % body
521 opts = uniq (opts)
522 optstring = string.join (opts, ' ')
523 optstring = re.sub ('\n', ' ', optstring)
524 body = r"""
525 %% Generated automatically by: lilypond-book.py
526 %% options are %s %%ughUGH not original options
527 \include "paper%d.ly"
528 \paper { linewidth = %f \pt; }
529 """ % (optstring, music_size, l) + body
530 return body
532 def parse_options_string(s):
533 d = {}
534 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
535 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
536 r3 = re.compile("(\w+?)((,\s*)|$)")
537 while s:
538 m = r1.match(s)
539 if m:
540 s = s[m.end():]
541 d[m.group(2)] = re.split(",\s*", m.group(3))
542 continue
543 m = r2.match(s)
544 if m:
545 s = s[m.end():]
546 d[m.group(2)] = m.group(3)
547 continue
548 m = r3.match(s)
549 if m:
550 s = s[m.end():]
551 d[m.group(1)] = 1
552 continue
554 error ("format of option string invalid (was `%')" % s)
555 return d
557 def scan_latex_preamble(chunks):
558 # first we want to scan the \documentclass line
559 # it should be the first non-comment line
560 idx = 0
561 while 1:
562 if chunks[idx][0] == 'ignore':
563 idx = idx + 1
564 continue
565 m = get_re ('header').match(chunks[idx][1])
566 options = re.split (',[\n \t]*', m.group(1)[1:-1])
567 for o in options:
568 if o == 'landscape':
569 paperguru.m_landscape = 1
570 m = re.match("(.*?)paper", o)
571 if m:
572 paperguru.m_papersize = m.group()
573 else:
574 m = re.match("(\d\d)pt", o)
575 if m:
576 paperguru.m_fontsize = int(m.group(1))
578 break
579 while chunks[idx][0] != 'preamble-end':
580 if chunks[idx] == 'ignore':
581 idx = idx + 1
582 continue
583 m = get_re ('geometry').search(chunks[idx][1])
584 if m:
585 paperguru.m_use_geometry = 1
586 o = parse_options_string(m.group('options'))
587 for k in o.keys():
588 paperguru.set_geo_option(k, o[k])
589 idx = idx + 1
591 def scan_texi_preamble (chunks):
592 # this is not bulletproof..., it checks the first 10 chunks
593 for c in chunks[:10]:
594 if c[0] == 'input':
595 for s in ('afourpaper', 'afourwide', 'letterpaper',
596 'afourlatex', 'smallbook'):
597 if string.find(c[1], "@%s" % s) != -1:
598 paperguru.m_papersize = s
600 def scan_preamble (chunks):
601 if __main__.format == 'texi':
602 scan_texi_preamble(chunks)
603 else:
604 assert __main__.format == 'latex'
605 scan_latex_preamble(chunks)
608 def completize_preamble (chunks):
609 if __main__.format == 'texi':
610 return chunks
611 pre_b = post_b = graphics_b = None
612 for chunk in chunks:
613 if chunk[0] == 'preamble-end':
614 break
615 if chunk[0] == 'input':
616 m = get_re('def-pre-re').search(chunk[1])
617 if m:
618 pre_b = 1
619 if chunk[0] == 'input':
620 m = get_re('def-post-re').search(chunk[1])
621 if m:
622 post_b = 1
623 if chunk[0] == 'input':
624 m = get_re('usepackage-graphics').search(chunk[1])
625 if m:
626 graphics_b = 1
627 x = 0
628 while chunks[x][0] != 'preamble-end':
629 x = x + 1
630 if not pre_b:
631 chunks.insert(x, ('input', get_output ('output-default-pre')))
632 if not post_b:
633 chunks.insert(x, ('input', get_output ('output-default-post')))
634 if not graphics_b:
635 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
636 return chunks
639 read_files = []
640 def find_file (name):
642 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
645 f = None
646 nm = ''
647 for a in include_path:
648 try:
649 nm = os.path.join (a, name)
650 f = open (nm)
651 __main__.read_files.append (nm)
652 break
653 except IOError:
654 pass
655 if f:
656 sys.stderr.write ("Reading `%s'\n" % nm)
657 return (f.read (), nm)
658 else:
659 error ("File not found `%s'\n" % name)
660 return ('', '')
662 def do_ignore(match_object):
663 return [('ignore', match_object.group('code'))]
664 def do_preamble_end(match_object):
665 return [('preamble-end', match_object.group('code'))]
667 def make_verbatim(match_object):
668 return [('verbatim', match_object.group('code'))]
670 def make_verb(match_object):
671 return [('verb', match_object.group('code'))]
673 def do_include_file(m):
674 "m: MatchObject"
675 return [('input', get_output ('pagebreak'))] \
676 + read_doc_file(m.group('filename')) \
677 + [('input', get_output ('pagebreak'))]
679 def do_input_file(m):
680 return read_doc_file(m.group('filename'))
682 def make_lilypond(m):
683 if m.group('options'):
684 options = m.group('options')
685 else:
686 options = ''
687 return [('input', get_output('output-lilypond-fragment') %
688 (options, m.group('code')))]
690 def make_lilypond_file(m):
693 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
694 into a @lilypond .. @end lilypond block.
698 if m.group('options'):
699 options = m.group('options')
700 else:
701 options = ''
702 (content, nm) = find_file(m.group('filename'))
703 options = "filename=%s," % nm + options
705 return [('input', get_output('output-lilypond') %
706 (options, content))]
708 def make_lilypond_block(m):
709 if m.group('options'):
710 options = get_re('option-sep').split (m.group('options'))
711 else:
712 options = []
713 options = filter(lambda s: s != '', options)
714 return [('lilypond', m.group('code'), options)]
716 def do_columns(m):
717 if __main__.format != 'latex':
718 return []
719 if m.group('num') == 'one':
720 return [('numcols', m.group('code'), 1)]
721 if m.group('num') == 'two':
722 return [('numcols', m.group('code'), 2)]
724 def chop_chunks(chunks, re_name, func, use_match=0):
725 newchunks = []
726 for c in chunks:
727 if c[0] == 'input':
728 str = c[1]
729 while str:
730 m = get_re (re_name).search (str)
731 if m == None:
732 newchunks.append (('input', str))
733 str = ''
734 else:
735 if use_match:
736 newchunks.append (('input', str[:m.start ('match')]))
737 else:
738 newchunks.append (('input', str[:m.start (0)]))
739 #newchunks.extend(func(m))
740 # python 1.5 compatible:
741 newchunks = newchunks + func(m)
742 str = str [m.end(0):]
743 else:
744 newchunks.append(c)
745 return newchunks
747 def determine_format (str):
748 if __main__.format == '':
750 latex = re.search ('\\\\document', str[:200])
751 texinfo = re.search ('@node|@setfilename', str[:200])
753 f = ''
754 g = None
756 if texinfo and latex == None:
757 f = 'texi'
758 elif latex and texinfo == None:
759 f = 'latex'
760 else:
761 error("error: can't determine format, please specify")
762 __main__.format = f
764 if __main__.paperguru == None:
765 if __main__.format == 'texi':
766 g = TexiPaper()
767 else:
768 g = LatexPaper()
770 __main__.paperguru = g
773 def read_doc_file (filename):
774 """Read the input file, find verbatim chunks and do \input and \include
776 (str, path) = find_file(filename)
777 determine_format (str)
779 chunks = [('input', str)]
781 # we have to check for verbatim before doing include,
782 # because we don't want to include files that are mentioned
783 # inside a verbatim environment
784 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
785 chunks = chop_chunks(chunks, 'verb', make_verb)
786 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
787 #ugh fix input
788 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
789 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
790 return chunks
793 taken_file_names = {}
794 def schedule_lilypond_block (chunk):
795 """Take the body and options from CHUNK, figure out how the
796 real .ly should look, and what should be left MAIN_STR (meant
797 for the main file). The .ly is written, and scheduled in
798 TODO.
800 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
802 TODO has format [basename, extension, extension, ... ]
805 (type, body, opts) = chunk
806 assert type == 'lilypond'
807 file_body = compose_full_body (body, opts)
808 basename = 'lily-' + `abs(hash (file_body))`
809 for o in opts:
810 m = re.search ('filename="(.*?)"', o)
811 if m:
812 basename = m.group (1)
813 if not taken_file_names.has_key(basename):
814 taken_file_names[basename] = 0
815 else:
816 taken_file_names[basename] = taken_file_names[basename] + 1
817 basename = basename + "-%i" % taken_file_names[basename]
818 if not g_read_lys:
819 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
820 needed_filetypes = ['tex']
822 if format == 'texi':
823 needed_filetypes.append('eps')
824 needed_filetypes.append('png')
825 if 'eps' in opts and not ('eps' in needed_filetypes):
826 needed_filetypes.append('eps')
827 pathbase = os.path.join (g_outdir, basename)
828 def f(base, ext1, ext2):
829 a = os.path.isfile(base + ext2)
830 if (os.path.isfile(base + ext1) and
831 os.path.isfile(base + ext2) and
832 os.stat(base+ext1)[stat.ST_MTIME] >
833 os.stat(base+ext2)[stat.ST_MTIME]) or \
834 not os.path.isfile(base + ext2):
835 return 1
836 todo = []
837 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
838 todo.append('tex')
839 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
840 todo.append('eps')
841 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
842 todo.append('png')
843 newbody = ''
845 if 'printfilename' in opts:
846 for o in opts:
847 m= re.match ("filename=(.*)", o)
848 if m:
849 newbody = newbody + get_output ("output-filename") % m.group(1)
850 break
853 if 'verbatim' in opts:
854 newbody = output_verbatim (body)
856 for o in opts:
857 m = re.search ('intertext="(.*?)"', o)
858 if m:
859 newbody = newbody + m.group (1) + "\n\n"
860 if format == 'latex':
861 if 'eps' in opts:
862 s = 'output-eps'
863 else:
864 s = 'output-tex'
865 else: # format == 'texi'
866 s = 'output-all'
867 newbody = newbody + get_output (s) % {'fn': basename }
868 return ('lilypond', newbody, opts, todo, basename)
870 def process_lilypond_blocks(outname, chunks):#ugh rename
871 newchunks = []
872 # Count sections/chapters.
873 for c in chunks:
874 if c[0] == 'lilypond':
875 c = schedule_lilypond_block (c)
876 elif c[0] == 'numcols':
877 paperguru.m_num_cols = c[2]
878 newchunks.append (c)
879 return newchunks
882 def find_eps_dims (match):
883 "Fill in dimensions of EPS files."
885 fn =match.group (1)
886 dims = bounding_box_dimensions (fn)
887 if g_outdir:
888 fn = os.path.join(g_outdir, fn)
890 return '%ipt' % dims[0]
893 def system (cmd):
894 sys.stderr.write ("invoking `%s'\n" % cmd)
895 st = os.system (cmd)
896 if st:
897 error ('Error command exited with value %d\n' % st)
898 return st
900 def compile_all_files (chunks):
901 global foutn
902 eps = []
903 tex = []
904 png = []
906 for c in chunks:
907 if c[0] <> 'lilypond':
908 continue
909 base = c[4]
910 exts = c[3]
911 for e in exts:
912 if e == 'eps':
913 eps.append (base)
914 elif e == 'tex':
915 #ugh
916 if base + '.ly' not in tex:
917 tex.append (base + '.ly')
918 elif e == 'png' and g_do_pictures:
919 png.append (base)
920 d = os.getcwd()
921 if g_outdir:
922 os.chdir(g_outdir)
923 if tex:
924 # fixme: be sys-independent.
925 def incl_opt (x):
926 if g_outdir and x[0] <> '/' :
927 x = os.path.join (g_here_dir, x)
928 return ' -I %s' % x
930 incs = map (incl_opt, include_path)
931 lilyopts = string.join (incs, ' ' )
932 if do_deps:
933 lilyopts = lilyopts + ' --dependencies '
934 if g_outdir:
935 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
936 texfiles = string.join (tex, ' ')
937 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
940 # Ugh, fixing up dependencies for .tex generation
942 if do_deps:
943 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
944 for i in depfiles:
945 text=open (i).read ()
946 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
947 open (i, 'w').write (text)
949 for e in eps:
950 system(r"tex '\nonstopmode \input %s'" % e)
951 system(r"dvips -E -o %s %s" % (e + '.eps', e))
952 for g in png:
953 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
954 cmd = cmd % (g + '.eps', g + '.png')
955 system (cmd)
956 os.chdir (d)
959 def update_file (body, name):
961 write the body if it has changed
963 same = 0
964 try:
965 f = open (name)
966 fs = f.read (-1)
967 same = (fs == body)
968 except:
969 pass
971 if not same:
972 f = open (name , 'w')
973 f.write (body)
974 f.close ()
976 return not same
979 def getopt_args (opts):
980 "Construct arguments (LONG, SHORT) for getopt from list of options."
981 short = ''
982 long = []
983 for o in opts:
984 if o[1]:
985 short = short + o[1]
986 if o[0]:
987 short = short + ':'
988 if o[2]:
989 l = o[2]
990 if o[0]:
991 l = l + '='
992 long.append (l)
993 return (short, long)
995 def option_help_str (o):
996 "Transform one option description (4-tuple ) into neatly formatted string"
997 sh = ' '
998 if o[1]:
999 sh = '-%s' % o[1]
1001 sep = ' '
1002 if o[1] and o[2]:
1003 sep = ','
1005 long = ''
1006 if o[2]:
1007 long= '--%s' % o[2]
1009 arg = ''
1010 if o[0]:
1011 if o[2]:
1012 arg = '='
1013 arg = arg + o[0]
1014 return ' ' + sh + sep + long + arg
1017 def options_help_str (opts):
1018 "Convert a list of options into a neatly formatted string"
1019 w = 0
1020 strs =[]
1021 helps = []
1023 for o in opts:
1024 s = option_help_str (o)
1025 strs.append ((s, o[3]))
1026 if len (s) > w:
1027 w = len (s)
1029 str = ''
1030 for s in strs:
1031 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1032 return str
1034 def help():
1035 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1036 Generate hybrid LaTeX input from Latex + lilypond
1037 Options:
1038 """)
1039 sys.stdout.write (options_help_str (option_definitions))
1040 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1044 Report bugs to bug-gnu-music@gnu.org.
1046 Written by Tom Cato Amundsen <tca@gnu.org> and
1047 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1048 """)
1050 sys.exit (0)
1053 def write_deps (fn, target, chunks):
1054 global read_files
1055 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1056 f = open (os.path.join(g_outdir, fn), 'w')
1057 f.write ('%s%s: ' % (g_dep_prefix, target))
1058 for d in read_files:
1059 f.write ('%s ' % d)
1060 basenames=[]
1061 for c in chunks:
1062 if c[0] == 'lilypond':
1063 (type, body, opts, todo, basename) = c;
1064 basenames.append (basename)
1065 for d in basenames:
1066 if g_outdir:
1067 d=g_outdir + '/' + d
1068 if g_dep_prefix:
1069 #if not os.isfile (d): # thinko?
1070 if not re.search ('/', d):
1071 d = g_dep_prefix + d
1072 f.write ('%s.tex ' % d)
1073 f.write ('\n')
1074 #if len (basenames):
1075 # for d in basenames:
1076 # f.write ('%s.ly ' % d)
1077 # f.write (' : %s' % target)
1078 f.write ('\n')
1079 f.close ()
1080 read_files = []
1082 def identify():
1083 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1085 def print_version ():
1086 identify()
1087 sys.stdout.write (r"""Copyright 1998--1999
1088 Distributed under terms of the GNU General Public License. It comes with
1089 NO WARRANTY.
1090 """)
1093 def check_texidoc (chunks):
1094 n = []
1095 for c in chunks:
1096 if c[0] == 'lilypond':
1097 (type, body, opts, todo, basename) = c;
1098 pathbase = os.path.join (g_outdir, basename)
1099 if os.path.isfile (pathbase + '.texidoc'):
1100 body = '\n@include %s.texidoc\n' % basename + body
1101 c = (type, body, opts, todo, basename)
1102 n.append (c)
1103 return n
1105 def fix_epswidth (chunks):
1106 newchunks = []
1107 for c in chunks:
1108 if c[0] == 'lilypond' and 'eps' in c[2]:
1109 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", find_eps_dims, c[1])
1110 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1111 else:
1112 newchunks.append (c)
1113 return newchunks
1116 foutn=""
1117 def do_file(input_filename):
1118 global foutn
1119 file_settings = {}
1120 if outname:
1121 my_outname = outname
1122 else:
1123 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1124 my_depname = my_outname + '.dep'
1126 chunks = read_doc_file(input_filename)
1127 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1128 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1129 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1130 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1131 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1132 chunks = chop_chunks(chunks, 'numcols', do_columns)
1133 #print "-" * 50
1134 #for c in chunks: print "c:", c;
1135 #sys.exit()
1136 scan_preamble(chunks)
1137 chunks = process_lilypond_blocks(my_outname, chunks)
1139 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1141 # Do It.
1142 if __main__.g_run_lilypond:
1143 compile_all_files (chunks)
1144 chunks = fix_epswidth (chunks)
1146 if __main__.format == 'texi':
1147 chunks = check_texidoc (chunks)
1149 x = 0
1150 chunks = completize_preamble (chunks)
1151 sys.stderr.write ("Writing `%s'\n" % foutn)
1152 fout = open (foutn, 'w')
1153 for c in chunks:
1154 fout.write (c[1])
1155 fout.close ()
1156 # should chmod -w
1158 if do_deps:
1159 write_deps (my_depname, foutn, chunks)
1162 outname = ''
1163 try:
1164 (sh, long) = getopt_args (__main__.option_definitions)
1165 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1166 except getopt.error, msg:
1167 sys.stderr.write("error: %s" % msg)
1168 sys.exit(1)
1170 do_deps = 0
1171 for opt in options:
1172 o = opt[0]
1173 a = opt[1]
1175 if o == '--include' or o == '-I':
1176 include_path.append (a)
1177 elif o == '--version' or o == '-v':
1178 print_version ()
1179 sys.exit (0)
1180 elif o == '--format' or o == '-f':
1181 __main__.format = a
1182 elif o == '--outname' or o == '-o':
1183 if len(files) > 1:
1184 #HACK
1185 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1186 sys.exit(1)
1187 outname = a
1188 elif o == '--help' or o == '-h':
1189 help ()
1190 elif o == '--no-lily' or o == '-n':
1191 __main__.g_run_lilypond = 0
1192 elif o == '--dependencies' or o == '-M':
1193 do_deps = 1
1194 elif o == '--default-music-fontsize':
1195 default_music_fontsize = string.atoi (a)
1196 elif o == '--default-lilypond-fontsize':
1197 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1198 default_music_fontsize = string.atoi (a)
1199 elif o == '--force-music-fontsize':
1200 g_force_lilypond_fontsize = string.atoi(a)
1201 elif o == '--force-lilypond-fontsize':
1202 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1203 g_force_lilypond_fontsize = string.atoi(a)
1204 elif o == '--dep-prefix':
1205 g_dep_prefix = a
1206 elif o == '--no-pictures':
1207 g_do_pictures = 0
1208 elif o == '--read-lys':
1209 g_read_lys = 1
1210 elif o == '--outdir':
1211 g_outdir = a
1213 identify()
1214 if g_outdir:
1215 if os.path.isfile(g_outdir):
1216 error ("outdir is a file: %s" % g_outdir)
1217 if not os.path.exists(g_outdir):
1218 os.mkdir(g_outdir)
1219 for input_filename in files:
1220 do_file(input_filename)
1223 # Petr, ik zou willen dat ik iets zinvoller deed,
1224 # maar wat ik kan ik doen, het verandert toch niets?
1225 # --hwn 20/aug/99