2 # vim: set noexpandtab:
4 # * junk --outdir for --output
5 # * Figure out clean set of options.
7 # * EndLilyPondOutput is def'd as vfil. Causes large white gaps.
8 # * texinfo: add support for @pagesize
10 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
11 # because the values are taken directly from texinfo.tex,
12 # geometry.sty and article.cls. Give me a hint, and I'll
16 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
19 # This is was the idea for handling of comments:
20 # Multiline comments, @ignore .. @end ignore is scanned for
21 # in read_doc_file, and the chunks are marked as 'ignore', so
22 # lilypond-book will not touch them any more. The content of the
23 # chunks are written to the output file. Also 'include' and 'input'
24 # regex has to check if they are commented out.
26 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
27 # These three regex's has to check if they are on a commented line,
28 # % for latex, @c for texinfo.
30 # Then lines that are commented out with % (latex) and @c (Texinfo)
31 # are put into chunks marked 'ignore'. This cannot be done before
32 # searching for the lilypond-blocks because % is also the comment character
35 # The the rest of the rexeces are searched for. They don't have to test
36 # if they are on a commented out line.
48 # Handle bug in Python 1.6-2.1
50 # there are recursion limits for some patterns in Python 1.6 til 2.1.
51 # fix this by importing the 1.5.2 implementation pre instead. Fix by Mats.
53 if float (sys.version[0:3]) < 2.2:
63 # Attempt to fix problems with limited stack size set by Python!
64 # Sets unlimited stack size. Note that the resource module only
65 # is available on UNIX.
68 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
72 errorport = sys.stderr
79 gettext.bindtextdomain ('lilypond', localedir)
80 gettext.textdomain ('lilypond')
87 errorport.write (s + '\n')
90 program_version = '@TOPLEVEL_VERSION@'
91 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
92 program_version = '1.5.53'
94 # if set, LILYPONDPREFIX must take prevalence
95 # if datadir is not set, we're doing a build and LILYPONDPREFIX
98 if os.environ.has_key ('LILYPONDPREFIX') :
99 datadir = os.environ['LILYPONDPREFIX']
101 datadir = '@datadir@'
103 while datadir[-1] == os.sep:
104 datadir= datadir[:-1]
106 kpse = os.popen ('kpsexpand \$TEXMF').read()
107 kpse = re.sub('[ \t\n]+$','', kpse)
108 type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
111 # TODO: * prevent multiple addition.
112 # * clean TEXINPUTS, MFINPUTS, TFMFONTS,
113 # as these take prevalence over $TEXMF
114 # and thus may break tex run?
115 'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
116 'GS_FONTPATH' : type1_paths,
117 'GS_LIB' : datadir + '/ps',
120 # tex needs lots of memory, more than it gets by default on Debian
121 non_path_environment = {
122 'extra_mem_top' : '1000000',
123 'extra_mem_bottom' : '1000000',
124 'pool_size' : '250000',
127 def setup_environment ():
128 # $TEXMF is special, previous value is already taken care of
129 if os.environ.has_key ('TEXMF'):
130 del os.environ['TEXMF']
132 for key in environment.keys ():
133 val = environment[key]
134 if os.environ.has_key (key):
135 val = val + os.pathsep + os.environ[key]
136 os.environ[key] = val
138 for key in non_path_environment.keys ():
139 val = non_path_environment[key]
140 os.environ[key] = val
142 include_path = [os.getcwd()]
145 # g_ is for global (?)
147 g_here_dir = os.getcwd ()
150 g_force_lilypond_fontsize = 0
160 default_music_fontsize = 16
161 default_text_fontsize = 12
164 # this code is ugly. It should be cleaned
168 # the dimensions are from geometry.sty
169 'a0paper': (mm2pt(841), mm2pt(1189)),
170 'a1paper': (mm2pt(595), mm2pt(841)),
171 'a2paper': (mm2pt(420), mm2pt(595)),
172 'a3paper': (mm2pt(297), mm2pt(420)),
173 'a4paper': (mm2pt(210), mm2pt(297)),
174 'a5paper': (mm2pt(149), mm2pt(210)),
175 'b0paper': (mm2pt(1000), mm2pt(1414)),
176 'b1paper': (mm2pt(707), mm2pt(1000)),
177 'b2paper': (mm2pt(500), mm2pt(707)),
178 'b3paper': (mm2pt(353), mm2pt(500)),
179 'b4paper': (mm2pt(250), mm2pt(353)),
180 'b5paper': (mm2pt(176), mm2pt(250)),
181 'letterpaper': (in2pt(8.5), in2pt(11)),
182 'legalpaper': (in2pt(8.5), in2pt(14)),
183 'executivepaper': (in2pt(7.25), in2pt(10.5))}
184 self.m_use_geometry = None
185 self.m_papersize = 'letterpaper'
189 self.m_geo_landscape = 0
190 self.m_geo_width = None
191 self.m_geo_textwidth = None
192 self.m_geo_lmargin = None
193 self.m_geo_rmargin = None
194 self.m_geo_columnsep = 0.0
195 self.m_geo_includemp = None
196 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
197 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
198 self.m_geo_x_marginparwidth = None
199 self.m_geo_x_marginparsep = None
201 def set_geo_option(self, name, value):
203 if type(value) == type([]):
204 value = map(conv_dimen_to_float, value)
206 value = conv_dimen_to_float(value)
208 if name == 'body' or name == 'text':
209 if type(value) == type([]):
210 self.m_geo_textwidth = value[0]
212 self.m_geo_textwidth = value
214 elif name == 'portrait':
215 self.m_geo_landscape = 0
216 elif name == 'reversemp' or name == 'reversemarginpar':
217 if self.m_geo_includemp == None:
218 self.m_geo_includemp = 1
219 elif name == 'marginparwidth' or name == 'marginpar':
220 self.m_geo_x_marginparwidth = value
221 self.m_geo_includemp = 1
222 elif name == 'marginparsep':
223 self.m_geo_x_marginparsep = value
224 self.m_geo_includemp = 1
225 elif name == 'scale':
226 if type(value) == type([]):
227 self.m_geo_width = self.get_paperwidth() * value[0]
229 self.m_geo_width = self.get_paperwidth() * value
230 elif name == 'hscale':
231 self.m_geo_width = self.get_paperwidth() * value
232 elif name == 'left' or name == 'lmargin':
233 self.m_geo_lmargin = value
234 elif name == 'right' or name == 'rmargin':
235 self.m_geo_rmargin = value
236 elif name == 'hdivide' or name == 'divide':
237 if value[0] not in ('*', ''):
238 self.m_geo_lmargin = value[0]
239 if value[1] not in ('*', ''):
240 self.m_geo_width = value[1]
241 if value[2] not in ('*', ''):
242 self.m_geo_rmargin = value[2]
243 elif name == 'hmargin':
244 if type(value) == type([]):
245 self.m_geo_lmargin = value[0]
246 self.m_geo_rmargin = value[1]
248 self.m_geo_lmargin = value
249 self.m_geo_rmargin = value
250 elif name == 'margin':#ugh there is a bug about this option in
251 # the geometry documentation
252 if type(value) == type([]):
253 self.m_geo_lmargin = value[0]
254 self.m_geo_rmargin = value[0]
256 self.m_geo_lmargin = value
257 self.m_geo_rmargin = value
258 elif name == 'columnsep':
259 self.m_geo_columnsep = value
260 elif name == 'total':
261 if type(value) == type([]):
262 self.m_geo_width = value[0]
264 self.m_geo_width = value
265 elif name == 'width' or name == 'totalwidth':
266 self.m_geo_width = value
267 elif name == 'paper' or name == 'papername':
268 self.m_papersize = value
269 elif name[-5:] == 'paper':
270 self.m_papersize = name
274 def __setattr__(self, name, value):
275 if type(value) == type("") and \
276 dimension_conversion_dict.has_key (value[-2:]):
277 f = dimension_conversion_dict[value[-2:]]
278 self.__dict__[name] = f(float(value[:-2]))
280 self.__dict__[name] = value
283 s = "LatexPaper:\n-----------"
284 for v in self.__dict__.keys():
286 s = s + str (v) + ' ' + str (self.__dict__[v])
287 s = s + "-----------"
290 def get_linewidth(self):
291 w = self._calc_linewidth()
292 if self.m_num_cols == 2:
296 def get_paperwidth(self):
297 #if self.m_use_geometry:
298 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
299 #return self.m_paperdef[self.m_papersize][self.m_landscape]
301 def _calc_linewidth(self):
302 # since geometry sometimes ignores 'includemp', this is
303 # more complicated than it should be
305 if self.m_geo_includemp:
306 if self.m_geo_x_marginparsep is not None:
307 mp = mp + self.m_geo_x_marginparsep
309 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
310 if self.m_geo_x_marginparwidth is not None:
311 mp = mp + self.m_geo_x_marginparwidth
313 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
315 #ugh test if this is necessary
318 if self.m_num_cols == 1:
319 self.m_geo_columnsep = 0.0
321 if not self.m_use_geometry:
322 return latex_linewidths[self.m_papersize][self.m_fontsize]
324 geo_opts = (self.m_geo_lmargin == None,
325 self.m_geo_width == None,
326 self.m_geo_rmargin == None)
328 if geo_opts == (1, 1, 1):
329 if self.m_geo_textwidth:
330 return self.m_geo_textwidth - self.m_geo_columnsep
331 w = self.get_paperwidth() * 0.8 - self.m_geo_columnsep
333 elif geo_opts == (0, 1, 1):
334 if self.m_geo_textwidth:
335 return self.m_geo_textwidth - self.m_geo_columnsep
336 return self.f1(self.m_geo_lmargin, mp)
337 elif geo_opts == (1, 1, 0):
338 if self.m_geo_textwidth:
339 return self.m_geo_textwidth - self.m_geo_columnsep
340 return self.f1(self.m_geo_rmargin, mp)
342 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
343 if self.m_geo_textwidth:
344 return self.m_geo_textwidth - self.m_geo_columnsep
345 return self.m_geo_width - mp - self.m_geo_columnsep
346 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
347 w = self.get_paperwidth() - self.m_geo_lmargin \
348 - self.m_geo_rmargin - mp - self.m_geo_columnsep
352 raise "Never do this!"
354 tmp = self.get_paperwidth() - m * 2 - mp - self.m_geo_columnsep
359 tmp = self.get_paperwidth() - self.m_geo_lmargin \
367 self.m_papersize = 'letterpaper'
369 def get_linewidth(self):
370 return html_linewidths[self.m_papersize][self.m_fontsize]
374 self.m_papersize = 'letterpaper'
376 def get_linewidth(self):
377 return texi_linewidths[self.m_papersize][self.m_fontsize]
383 def em2pt(x, fontsize = 10):
384 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
385 def ex2pt(x, fontsize = 10):
386 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
391 dimension_conversion_dict ={
393 'cm': lambda x: mm2pt(10*x),
400 # Convert numeric values, with or without specific dimension, to floats.
402 def conv_dimen_to_float(value):
403 if type(value) == type(""):
404 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
407 num = string.atof(m.group (1))
408 conv = dimension_conversion_dict[m.group(2)]
412 elif re.match ("^[0-9.]+$",value):
419 # indices are no. of columns, papersize, fontsize
420 # Why can't this be calculated?
422 'a4paper':{10: 345, 11: 360, 12: 390},
423 'a4paper-landscape': {10: 598, 11: 596, 12:592},
424 'a5paper':{10: 276, 11: 276, 12: 276},
425 'b5paper':{10: 345, 11: 356, 12: 356},
426 'letterpaper':{10: 345, 11: 360, 12: 390},
427 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
428 'legalpaper': {10: 345, 11: 360, 12: 390},
429 'executivepaper':{10: 345, 11: 360, 12: 379}}
432 'afourpaper': {12: mm2pt(160)},
433 'afourwide': {12: in2pt(6.5)},
434 'afourlatex': {12: mm2pt(150)},
435 'smallbook': {12: in2pt(5)},
436 'letterpaper': {12: in2pt(6)}}
439 'afourpaper': {12: mm2pt(160)},
440 'afourwide': {12: in2pt(6.5)},
441 'afourlatex': {12: mm2pt(150)},
442 'smallbook': {12: in2pt(5)},
443 'letterpaper': {12: in2pt(6)}}
445 option_definitions = [
446 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
447 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
448 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
449 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
450 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
451 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
452 ('', 'h', 'help', 'this help'),
453 ('DIR', 'I', 'include', 'include path'),
454 ('', 'M', 'dependencies', 'write dependencies'),
455 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
456 ('', 'n', 'no-lily', 'don\'t run lilypond'),
457 ('', '', 'no-pictures', "don\'t generate pictures"),
458 ('', '', 'no-music', "strip all lilypond blocks from output"),
459 ('', '', 'read-lys', "don't write ly files."),
460 ('FILE', 'o', 'outname', 'filename main output file'),
461 ('FILE', '', 'outdir', "where to place generated files"),
462 ('', 'V', 'verbose', 'verbose' ),
463 ('', 'v', 'version', 'print version information' ),
466 # format specific strings, ie. regex-es for input, and % strings for output
468 'html' : {'output-lilypond': '''<lilypond%s>
471 'output-filename' : r'''
474 'output-lilypond-fragment': '''<lilypond%s>
475 \context Staff\context Voice{ %s }
477 'output-noinline': r'''
478 <!-- generated: %(fn)s.png !-->
482 'output-verbatim': r'''<pre>
485 'output-small-verbatim': r'''<font size=-1><pre>
489 ## Ugh we need to differentiate on origin:
490 ## lilypond-block origin wants an extra <p>, but
491 ## inline music doesn't.
492 ## possibly other center options?
494 <a href="%(fn)s.png">
495 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
499 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
506 'output-filename' : r'''
509 'output-lilypond': r'''\begin[%s]{lilypond}
513 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
515 'output-small-verbatim': r'''{\small\begin{verbatim}%s\end{verbatim}}%%''',
516 'output-default-post': "\\def\postLilypondExample{}\n",
517 'output-default-pre': "\\def\preLilypondExample{}\n",
518 'usepackage-graphics': '\\usepackage{graphics}\n',
519 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
520 'output-noinline': r'''
521 %% generated: %(fn)s.eps
523 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
524 'pagebreak': r'\pagebreak',
527 'texi' : {'output-lilypond': '''@lilypond[%s]
531 'output-filename' : r'''
534 'output-lilypond-fragment': '''@lilypond[%s]
535 \context Staff\context Voice{ %s }
537 'output-noinline': r'''
538 @c generated: %(fn)s.png
541 'output-small-verbatim': r'''@smallexample
545 'output-verbatim': r'''@example
550 # do some tweaking: @ is needed in some ps stuff.
551 # override EndLilyPondOutput, since @tex is done
552 # in a sandbox, you can't do \input lilyponddefs at the
553 # top of the document.
555 # should also support fragment in
557 # ugh, the <p> below breaks inline images...
563 \def\EndLilyPondOutput{}
569 <a href="%(fn)s.png">
570 <img border=0 src="%(fn)s.png" alt="[picture of music]">
578 def output_verbatim (body, small):
579 if __main__.format == 'html':
580 body = re.sub ('&', '&', body)
581 body = re.sub ('>', '>', body)
582 body = re.sub ('<', '<', body)
583 elif __main__.format == 'texi':
584 body = re.sub ('([@{}])', '@\\1', body)
587 key = 'output-small-verbatim'
589 key = 'output-verbatim'
590 return get_output (key) % body
593 #warning: this uses extended regular expressions. Tread with care.
597 # (?P -- name parameter
598 # *? -- match non-greedily.
605 'preamble-end': no_match,
606 'landscape': no_match,
607 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
608 'verb': r'''(?P<code><pre>.*?</pre>)''',
609 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
610 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
611 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
612 'option-sep' : '\s*',
613 'intertext': r',?\s*intertext=\".*?\"',
614 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
615 'singleline-comment': no_match,
619 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
620 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
621 'option-sep' : ',\s*',
622 'header': r"\\documentclass\s*(\[.*?\])?",
623 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
624 'preamble-end': r'(?P<code>\\begin{document})',
625 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
626 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
627 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
628 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
629 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
630 'def-post-re': r"\\def\\postLilypondExample",
631 'def-pre-re': r"\\def\\preLilypondExample",
632 'usepackage-graphics': r"\usepackage{graphics}",
633 'intertext': r',?\s*intertext=\".*?\"',
634 'multiline-comment': no_match,
635 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
636 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
640 # why do we have distinction between @mbinclude and @include?
644 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
647 'preamble-end': no_match,
648 'landscape': no_match,
649 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
650 'verb': r'''(?P<code>@code{.*?})''',
651 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
652 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
653 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
654 'option-sep' : ',\s*',
655 'intertext': r',?\s*intertext=\".*?\"',
656 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
657 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
663 for r in re_dict.keys ():
666 for k in olddict.keys ():
668 newdict[k] = re.compile (olddict[k])
670 print 'invalid regexp: %s' % olddict[k]
672 # we'd like to catch and reraise a more detailed error, but
673 # alas, the exceptions changed across the 1.5/2.1 boundary.
688 def get_output (name):
689 return output_dict[format][name]
692 return re_dict[format][name]
694 def bounding_box_dimensions(fname):
696 fname = os.path.join(g_outdir, fname)
700 error ("Error opening `%s'" % fname)
702 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
705 gs = map (lambda x: string.atoi (x), s.groups ())
706 return (int (gs[2] - gs[0] + 0.5),
707 int (gs[3] - gs[1] + 0.5))
712 sys.stderr.write (str + "\n Exiting ... \n\n")
716 def compose_full_body (body, opts):
717 '''Construct the lilypond code to send to Lilypond.
718 Add stuff to BODY using OPTS as options.'''
719 music_size = default_music_fontsize
720 latex_size = default_text_fontsize
724 if g_force_lilypond_fontsize:
725 music_size = g_force_lilypond_fontsize
727 m = re.match ('([0-9]+)pt', o)
729 music_size = string.atoi(m.group (1))
731 m = re.match ('latexfontsize=([0-9]+)pt', o)
733 latex_size = string.atoi (m.group (1))
735 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
737 f = float (m.group (1))
738 indent = 'indent = %f\\%s' % (f, m.group (2))
740 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
742 f = float (m.group (1))
743 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
745 if re.search ('\\\\score', body):
749 if 'fragment' in opts:
751 if 'nofragment' in opts:
754 if is_fragment and not 'multiline' in opts:
755 opts.append('singleline')
757 if 'singleline' in opts:
758 linewidth = 'linewidth = -1.0'
760 l = __main__.paperguru.get_linewidth ()
761 linewidth = 'linewidth = %f\pt' % l
763 if 'noindent' in opts:
764 indent = 'indent = 0.0\mm'
767 m= re.search ('relative(.*)', o)
771 v = string.atoi (m.group (1))
778 pitch = pitch + '\,' * v
780 pitch = pitch + '\'' * v
782 body = '\\relative %s { %s }' %(pitch, body)
791 optstring = string.join (opts, ' ')
792 optstring = re.sub ('\n', ' ', optstring)
794 %% Generated automatically by: lilypond-book.py
796 \include "paper%d.ly"
801 ''' % (optstring, music_size, linewidth, indent) + body
803 # ughUGH not original options
806 def parse_options_string(s):
808 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
809 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
810 r3 = re.compile("(\w+?)((,\s*)|$)")
815 d[m.group(2)] = re.split(",\s*", m.group(3))
820 d[m.group(2)] = m.group(3)
828 error ("format of option string invalid (was `%')" % s)
831 def scan_html_preamble (chunks):
834 def scan_latex_preamble(chunks):
835 # first we want to scan the \documentclass line
836 # it should be the first non-comment line
839 if chunks[idx][0] == 'ignore':
842 m = get_re ('header').match(chunks[idx][1])
843 if m <> None and m.group (1):
844 options = re.split (',[\n \t]*', m.group(1)[1:-1])
849 paperguru.m_landscape = 1
850 m = re.match("(.*?)paper", o)
852 paperguru.m_papersize = m.group()
854 m = re.match("(\d\d)pt", o)
856 paperguru.m_fontsize = int(m.group(1))
859 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
860 if chunks[idx] == 'ignore':
863 m = get_re ('geometry').search(chunks[idx][1])
865 paperguru.m_use_geometry = 1
866 o = parse_options_string(m.group('options'))
868 paperguru.set_geo_option(k, o[k])
871 def scan_texi_preamble (chunks):
872 # this is not bulletproof..., it checks the first 10 chunks
873 for c in chunks[:10]:
875 for s in ('afourpaper', 'afourwide', 'letterpaper',
876 'afourlatex', 'smallbook'):
877 if string.find(c[1], "@%s" % s) != -1:
878 paperguru.m_papersize = s
881 def scan_preamble (chunks):
882 if __main__.format == 'html':
883 scan_html_preamble (chunks)
884 elif __main__.format == 'latex':
885 scan_latex_preamble (chunks)
886 elif __main__.format == 'texi':
887 scan_texi_preamble (chunks)
890 def completize_preamble (chunks):
891 if __main__.format != 'latex':
893 pre_b = post_b = graphics_b = None
895 if chunk[0] == 'preamble-end':
897 if chunk[0] == 'input':
898 m = get_re('def-pre-re').search(chunk[1])
901 if chunk[0] == 'input':
902 m = get_re('def-post-re').search(chunk[1])
906 if chunk[0] == 'input':
907 m = get_re('usepackage-graphics').search(chunk[1])
911 while x < len (chunks) and chunks[x][0] != 'preamble-end':
918 chunks.insert(x, ('input', get_output ('output-default-pre')))
920 chunks.insert(x, ('input', get_output ('output-default-post')))
922 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
928 def find_file (name):
930 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
934 return (sys.stdin.read (), '<stdin>')
937 for a in include_path:
939 nm = os.path.join (a, name)
941 __main__.read_files.append (nm)
946 sys.stderr.write ("Reading `%s'\n" % nm)
947 return (f.read (), nm)
949 error ("File not found `%s'\n" % name)
952 def do_ignore(match_object):
953 return [('ignore', match_object.group('code'))]
954 def do_preamble_end(match_object):
955 return [('preamble-end', match_object.group('code'))]
957 def make_verbatim(match_object):
958 return [('verbatim', match_object.group('code'))]
960 def make_verb(match_object):
961 return [('verb', match_object.group('code'))]
963 def do_include_file(m):
965 return [('input', get_output ('pagebreak'))] \
966 + read_doc_file(m.group('filename')) \
967 + [('input', get_output ('pagebreak'))]
969 def do_input_file(m):
970 return read_doc_file(m.group('filename'))
972 def make_lilypond(m):
973 if m.group('options'):
974 options = m.group('options')
977 return [('input', get_output('output-lilypond-fragment') %
978 (options, m.group('code')))]
980 def make_lilypond_file(m):
983 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
984 into a @lilypond .. @end lilypond block.
988 if m.group('options'):
989 options = m.group('options')
992 (content, nm) = find_file(m.group('filename'))
993 options = "filename=%s," % nm + options
995 return [('input', get_output('output-lilypond') %
998 def make_lilypond_block(m):
1002 if m.group('options'):
1003 options = get_re('option-sep').split (m.group('options'))
1006 options = filter(lambda s: s != '', options)
1007 return [('lilypond', m.group('code'), options)]
1010 if __main__.format != 'latex':
1012 if m.group('num') == 'one':
1013 return [('numcols', m.group('code'), 1)]
1014 if m.group('num') == 'two':
1015 return [('numcols', m.group('code'), 2)]
1017 def chop_chunks(chunks, re_name, func, use_match=0):
1023 m = get_re (re_name).search (str)
1025 newchunks.append (('input', str))
1029 newchunks.append (('input', str[:m.start ('match')]))
1031 newchunks.append (('input', str[:m.start (0)]))
1032 #newchunks.extend(func(m))
1033 # python 1.5 compatible:
1034 newchunks = newchunks + func(m)
1035 str = str [m.end(0):]
1040 def determine_format (str):
1041 if __main__.format == '':
1043 html = re.search ('(?i)<[dh]tml', str[:200])
1044 latex = re.search (r'''\\document''', str[:200])
1045 texi = re.search ('@node|@setfilename', str[:200])
1050 if html and not latex and not texi:
1052 elif latex and not html and not texi:
1054 elif texi and not html and not latex:
1057 error ("can't determine format, please specify")
1060 if __main__.paperguru == None:
1061 if __main__.format == 'html':
1063 elif __main__.format == 'latex':
1065 elif __main__.format == 'texi':
1068 __main__.paperguru = g
1071 def read_doc_file (filename):
1072 '''Read the input file, find verbatim chunks and do \input and \include
1074 (str, path) = find_file(filename)
1075 determine_format (str)
1077 chunks = [('input', str)]
1079 # we have to check for verbatim before doing include,
1080 # because we don't want to include files that are mentioned
1081 # inside a verbatim environment
1082 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1083 chunks = chop_chunks(chunks, 'verb', make_verb)
1084 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1086 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1087 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1091 taken_file_names = {}
1092 def schedule_lilypond_block (chunk):
1093 '''Take the body and options from CHUNK, figure out how the
1094 real .ly should look, and what should be left MAIN_STR (meant
1095 for the main file). The .ly is written, and scheduled in
1098 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1100 TODO has format [basename, extension, extension, ... ]
1103 (type, body, opts) = chunk
1104 assert type == 'lilypond'
1105 file_body = compose_full_body (body, opts)
1106 ## Hmm, we should hash only lilypond source, and skip the
1109 basename = 'lily-' + `abs(hash (file_body))`
1111 m = re.search ('filename="(.*?)"', o)
1113 basename = m.group (1)
1114 if not taken_file_names.has_key(basename):
1115 taken_file_names[basename] = 0
1117 taken_file_names[basename] = taken_file_names[basename] + 1
1118 basename = basename + "-%i" % taken_file_names[basename]
1120 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1121 needed_filetypes = ['tex']
1123 if format == 'html' or format == 'texi':
1124 needed_filetypes.append ('eps')
1125 needed_filetypes.append ('png')
1126 if 'eps' in opts and not ('eps' in needed_filetypes):
1127 needed_filetypes.append('eps')
1128 pathbase = os.path.join (g_outdir, basename)
1129 def f (base, ext1, ext2):
1130 a = os.path.isfile(base + ext2)
1131 if (os.path.isfile(base + ext1) and
1132 os.path.isfile(base + ext2) and
1133 os.stat(base+ext1)[stat.ST_MTIME] >
1134 os.stat(base+ext2)[stat.ST_MTIME]) or \
1135 not os.path.isfile(base + ext2):
1138 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1140 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1142 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1146 if 'printfilename' in opts:
1148 m= re.match ("filename=(.*)", o)
1150 newbody = newbody + get_output ("output-filename") % m.group(1)
1154 if 'smallverbatim' in opts:
1155 newbody = output_verbatim (body, 1)
1156 elif 'verbatim' in opts:
1157 newbody = output_verbatim (body, 0)
1160 m = re.search ('intertext="(.*?)"', o)
1162 newbody = newbody + m.group (1) + "\n\n"
1164 if 'noinline' in opts:
1165 s = 'output-noinline'
1166 elif format == 'latex':
1171 else: # format == 'html' or format == 'texi':
1173 newbody = newbody + get_output (s) % {'fn': basename }
1174 return ('lilypond', newbody, opts, todo, basename)
1176 def process_lilypond_blocks(chunks):#ugh rename
1178 # Count sections/chapters.
1180 if c[0] == 'lilypond':
1181 c = schedule_lilypond_block (c)
1182 elif c[0] == 'numcols':
1183 paperguru.m_num_cols = c[2]
1184 newchunks.append (c)
1190 sys.stderr.write ("invoking `%s'\n" % cmd)
1191 st = os.system (cmd)
1193 error ('Error command exited with value %d\n' % st)
1196 def quiet_system (cmd, name):
1198 progress ( _("Running %s...") % name)
1199 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1203 def get_bbox (filename):
1204 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1206 box = open (filename + '.bbox').read()
1207 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1210 gr = map (string.atoi, m.groups ())
1214 def make_pixmap (name):
1215 bbox = get_bbox (name + '.eps')
1217 fo = open (name + '.trans.eps' , 'w')
1218 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1223 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1224 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1226 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s'''
1228 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1231 status = quiet_system (cmd, 'gs')
1236 os.unlink (name + '.png')
1237 error ("Removing output file")
1239 def compile_all_files (chunks):
1246 if c[0] <> 'lilypond':
1255 if base + '.ly' not in tex:
1256 tex.append (base + '.ly')
1257 elif e == 'png' and g_do_pictures:
1263 # fixme: be sys-independent.
1265 if g_outdir and x[0] <> '/' :
1266 x = os.path.join (g_here_dir, x)
1269 incs = map (incl_opt, include_path)
1270 lilyopts = string.join (incs, ' ' )
1272 lilyopts = lilyopts + ' --dependencies '
1274 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1275 texfiles = string.join (tex, ' ')
1276 cmd = 'lilypond --header=texidoc %s %s %s' \
1277 % (lilyopts, g_extra_opts, texfiles)
1282 # Ugh, fixing up dependencies for .tex generation
1285 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1290 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1296 cmd = r"echo $TEXMF; tex '\nonstopmode \input %s'" % e
1297 quiet_system (cmd, 'TeX')
1299 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1300 quiet_system (cmd, 'dvips')
1308 def update_file (body, name):
1310 write the body if it has changed
1321 f = open (name , 'w')
1328 def getopt_args (opts):
1329 "Construct arguments (LONG, SHORT) for getopt from list of options."
1334 short = short + o[1]
1342 return (short, long)
1344 def option_help_str (o):
1345 "Transform one option description (4-tuple ) into neatly formatted string"
1363 return ' ' + sh + sep + long + arg
1366 def options_help_str (opts):
1367 "Convert a list of options into a neatly formatted string"
1373 s = option_help_str (o)
1374 strs.append ((s, o[3]))
1380 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1384 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1385 Generate hybrid LaTeX input from Latex + lilypond
1388 sys.stdout.write (options_help_str (option_definitions))
1389 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1393 Report bugs to bug-lilypond@gnu.org.
1395 Written by Tom Cato Amundsen <tca@gnu.org> and
1396 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1402 def write_deps (fn, target, chunks):
1404 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1405 f = open (os.path.join(g_outdir, fn), 'w')
1406 f.write ('%s%s: ' % (g_dep_prefix, target))
1407 for d in read_files:
1411 if c[0] == 'lilypond':
1412 (type, body, opts, todo, basename) = c;
1413 basenames.append (basename)
1416 d=g_outdir + '/' + d
1418 #if not os.isfile (d): # thinko?
1419 if not re.search ('/', d):
1420 d = g_dep_prefix + d
1421 f.write ('%s.tex ' % d)
1423 #if len (basenames):
1424 # for d in basenames:
1425 # f.write ('%s.ly ' % d)
1426 # f.write (' : %s' % target)
1431 def identify (stream):
1432 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1434 def print_version ():
1435 identify (sys.stdout)
1436 sys.stdout.write (r'''Copyright 1998--1999
1437 Distributed under terms of the GNU General Public License. It comes with
1442 def check_texidoc (chunks):
1445 if c[0] == 'lilypond':
1446 (type, body, opts, todo, basename) = c;
1447 pathbase = os.path.join (g_outdir, basename)
1448 if os.path.isfile (pathbase + '.texidoc'):
1449 body = '\n@include %s.texidoc\n' % basename + body
1450 c = (type, body, opts, todo, basename)
1455 ## what's this? Docme --hwn
1457 def fix_epswidth (chunks):
1460 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1461 newchunks.append (c)
1466 m = re.match ('magnification=([0-9.]+)', o)
1468 mag = string.atof (m.group (1))
1470 def replace_eps_dim (match, lmag = mag):
1471 filename = match.group (1)
1472 dims = bounding_box_dimensions (filename)
1474 return '%fpt' % (dims[0] *lmag)
1476 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1477 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1482 ##docme: why global?
1484 def do_file(input_filename):
1486 chunks = read_doc_file(input_filename)
1487 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1488 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1489 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1490 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1491 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1492 chunks = chop_chunks(chunks, 'numcols', do_columns)
1494 #for c in chunks: print "c:", c;
1496 scan_preamble(chunks)
1497 chunks = process_lilypond_blocks(chunks)
1500 if __main__.g_run_lilypond:
1501 compile_all_files (chunks)
1502 chunks = fix_epswidth (chunks)
1504 if __main__.format == 'texi':
1505 chunks = check_texidoc (chunks)
1508 chunks = completize_preamble (chunks)
1514 my_outname = outname
1515 elif input_filename == '-' or input_filename == "/dev/stdin":
1518 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1519 my_depname = my_outname + '.dep'
1521 if my_outname == '-' or my_outname == '/dev/stdout':
1524 __main__.do_deps = 0
1526 foutn = os.path.join (g_outdir, my_outname)
1527 sys.stderr.write ("Writing `%s'\n" % foutn)
1528 fout = open (foutn, 'w')
1535 write_deps (my_depname, foutn, chunks)
1540 (sh, long) = getopt_args (__main__.option_definitions)
1541 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1542 except getopt.error, msg:
1543 sys.stderr.write("error: %s" % msg)
1551 if o == '--include' or o == '-I':
1552 include_path.append (a)
1553 elif o == '--version' or o == '-v':
1556 elif o == '--verbose' or o == '-V':
1557 __main__.verbose_p = 1
1558 elif o == '--format' or o == '-f':
1560 elif o == '--outname' or o == '-o':
1563 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1566 elif o == '--help' or o == '-h':
1568 elif o == '--no-lily' or o == '-n':
1569 __main__.g_run_lilypond = 0
1570 elif o == '--dependencies' or o == '-M':
1572 elif o == '--default-music-fontsize':
1573 default_music_fontsize = string.atoi (a)
1574 elif o == '--default-lilypond-fontsize':
1575 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1576 default_music_fontsize = string.atoi (a)
1577 elif o == '--extra-options':
1579 elif o == '--force-music-fontsize':
1580 g_force_lilypond_fontsize = string.atoi(a)
1581 elif o == '--force-lilypond-fontsize':
1582 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1583 g_force_lilypond_fontsize = string.atoi(a)
1584 elif o == '--dep-prefix':
1586 elif o == '--no-pictures':
1588 elif o == '--no-music':
1590 elif o == '--read-lys':
1592 elif o == '--outdir':
1595 identify (sys.stderr)
1597 if os.path.isfile(g_outdir):
1598 error ("outdir is a file: %s" % g_outdir)
1599 if not os.path.exists(g_outdir):
1601 setup_environment ()
1602 for input_filename in files:
1603 do_file(input_filename)
1606 # Petr, ik zou willen dat ik iets zinvoller deed,
1607 # maar wat ik kan ik doen, het verandert toch niets?