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 ## Ugh we need to differentiate on origin:
486 ## lilypond-block origin wants an extra <p>, but
487 ## inline music doesn't.
488 ## possibly other center options?
490 <a href="%(fn)s.png">
491 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
495 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
502 'output-filename' : r'''
505 'output-lilypond': r'''\begin[%s]{lilypond}
509 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
511 'output-default-post': "\\def\postLilypondExample{}\n",
512 'output-default-pre': "\\def\preLilypondExample{}\n",
513 'usepackage-graphics': '\\usepackage{graphics}\n',
514 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
515 'output-noinline': r'''
516 %% generated: %(fn)s.eps
518 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
519 'pagebreak': r'\pagebreak',
522 'texi' : {'output-lilypond': '''@lilypond[%s]
526 'output-filename' : r'''
529 'output-lilypond-fragment': '''@lilypond[%s]
530 \context Staff\context Voice{ %s }
532 'output-noinline': r'''
533 @c generated: %(fn)s.png
536 'output-verbatim': r'''@example
541 # do some tweaking: @ is needed in some ps stuff.
542 # override EndLilyPondOutput, since @tex is done
543 # in a sandbox, you can't do \input lilyponddefs at the
544 # top of the document.
546 # should also support fragment in
548 # ugh, the <p> below breaks inline images...
554 \def\EndLilyPondOutput{}
560 <a href="%(fn)s.png">
561 <img border=0 src="%(fn)s.png" alt="[picture of music]">
569 def output_verbatim (body):
570 if __main__.format == 'html':
571 body = re.sub ('&', '&', body)
572 body = re.sub ('>', '>', body)
573 body = re.sub ('<', '<', body)
574 elif __main__.format == 'texi':
575 body = re.sub ('([@{}])', '@\\1', body)
576 return get_output ('output-verbatim') % body
579 #warning: this uses extended regular expressions. Tread with care.
583 # (?P -- name parameter
584 # *? -- match non-greedily.
591 'preamble-end': no_match,
592 'landscape': no_match,
593 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
594 'verb': r'''(?P<code><pre>.*?</pre>)''',
595 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
596 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
597 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
598 'option-sep' : '\s*',
599 'intertext': r',?\s*intertext=\".*?\"',
600 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
601 'singleline-comment': no_match,
605 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
606 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
607 'option-sep' : ',\s*',
608 'header': r"\\documentclass\s*(\[.*?\])?",
609 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
610 'preamble-end': r'(?P<code>\\begin{document})',
611 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
612 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
613 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
614 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
615 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
616 'def-post-re': r"\\def\\postLilypondExample",
617 'def-pre-re': r"\\def\\preLilypondExample",
618 'usepackage-graphics': r"\usepackage{graphics}",
619 'intertext': r',?\s*intertext=\".*?\"',
620 'multiline-comment': no_match,
621 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
622 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
626 # why do we have distinction between @mbinclude and @include?
630 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
633 'preamble-end': no_match,
634 'landscape': no_match,
635 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
636 'verb': r'''(?P<code>@code{.*?})''',
637 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
638 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
639 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
640 'option-sep' : ',\s*',
641 'intertext': r',?\s*intertext=\".*?\"',
642 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
643 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
649 for r in re_dict.keys ():
652 for k in olddict.keys ():
654 newdict[k] = re.compile (olddict[k])
656 print 'invalid regexp: %s' % olddict[k]
658 # we'd like to catch and reraise a more detailed error, but
659 # alas, the exceptions changed across the 1.5/2.1 boundary.
674 def get_output (name):
675 return output_dict[format][name]
678 return re_dict[format][name]
680 def bounding_box_dimensions(fname):
682 fname = os.path.join(g_outdir, fname)
686 error ("Error opening `%s'" % fname)
688 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
691 gs = map (lambda x: string.atoi (x), s.groups ())
692 return (int (gs[2] - gs[0] + 0.5),
693 int (gs[3] - gs[1] + 0.5))
698 sys.stderr.write (str + "\n Exiting ... \n\n")
702 def compose_full_body (body, opts):
703 '''Construct the lilypond code to send to Lilypond.
704 Add stuff to BODY using OPTS as options.'''
705 music_size = default_music_fontsize
706 latex_size = default_text_fontsize
710 if g_force_lilypond_fontsize:
711 music_size = g_force_lilypond_fontsize
713 m = re.match ('([0-9]+)pt', o)
715 music_size = string.atoi(m.group (1))
717 m = re.match ('latexfontsize=([0-9]+)pt', o)
719 latex_size = string.atoi (m.group (1))
721 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
723 f = float (m.group (1))
724 indent = 'indent = %f\\%s' % (f, m.group (2))
726 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
728 f = float (m.group (1))
729 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
731 if re.search ('\\\\score', body):
735 if 'fragment' in opts:
737 if 'nofragment' in opts:
740 if is_fragment and not 'multiline' in opts:
741 opts.append('singleline')
743 if 'singleline' in opts:
744 linewidth = 'linewidth = -1.0'
746 l = __main__.paperguru.get_linewidth ()
747 linewidth = 'linewidth = %f\pt' % l
749 if 'noindent' in opts:
750 indent = 'indent = 0.0\mm'
753 m= re.search ('relative(.*)', o)
757 v = string.atoi (m.group (1))
764 pitch = pitch + '\,' * v
766 pitch = pitch + '\'' * v
768 body = '\\relative %s { %s }' %(pitch, body)
777 optstring = string.join (opts, ' ')
778 optstring = re.sub ('\n', ' ', optstring)
780 %% Generated automatically by: lilypond-book.py
782 \include "paper%d.ly"
787 ''' % (optstring, music_size, linewidth, indent) + body
789 # ughUGH not original options
792 def parse_options_string(s):
794 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
795 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
796 r3 = re.compile("(\w+?)((,\s*)|$)")
801 d[m.group(2)] = re.split(",\s*", m.group(3))
806 d[m.group(2)] = m.group(3)
814 error ("format of option string invalid (was `%')" % s)
817 def scan_html_preamble (chunks):
820 def scan_latex_preamble(chunks):
821 # first we want to scan the \documentclass line
822 # it should be the first non-comment line
825 if chunks[idx][0] == 'ignore':
828 m = get_re ('header').match(chunks[idx][1])
829 if m <> None and m.group (1):
830 options = re.split (',[\n \t]*', m.group(1)[1:-1])
835 paperguru.m_landscape = 1
836 m = re.match("(.*?)paper", o)
838 paperguru.m_papersize = m.group()
840 m = re.match("(\d\d)pt", o)
842 paperguru.m_fontsize = int(m.group(1))
845 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
846 if chunks[idx] == 'ignore':
849 m = get_re ('geometry').search(chunks[idx][1])
851 paperguru.m_use_geometry = 1
852 o = parse_options_string(m.group('options'))
854 paperguru.set_geo_option(k, o[k])
857 def scan_texi_preamble (chunks):
858 # this is not bulletproof..., it checks the first 10 chunks
859 for c in chunks[:10]:
861 for s in ('afourpaper', 'afourwide', 'letterpaper',
862 'afourlatex', 'smallbook'):
863 if string.find(c[1], "@%s" % s) != -1:
864 paperguru.m_papersize = s
867 def scan_preamble (chunks):
868 if __main__.format == 'html':
869 scan_html_preamble (chunks)
870 elif __main__.format == 'latex':
871 scan_latex_preamble (chunks)
872 elif __main__.format == 'texi':
873 scan_texi_preamble (chunks)
876 def completize_preamble (chunks):
877 if __main__.format != 'latex':
879 pre_b = post_b = graphics_b = None
881 if chunk[0] == 'preamble-end':
883 if chunk[0] == 'input':
884 m = get_re('def-pre-re').search(chunk[1])
887 if chunk[0] == 'input':
888 m = get_re('def-post-re').search(chunk[1])
892 if chunk[0] == 'input':
893 m = get_re('usepackage-graphics').search(chunk[1])
897 while x < len (chunks) and chunks[x][0] != 'preamble-end':
904 chunks.insert(x, ('input', get_output ('output-default-pre')))
906 chunks.insert(x, ('input', get_output ('output-default-post')))
908 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
914 def find_file (name):
916 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
920 return (sys.stdin.read (), '<stdin>')
923 for a in include_path:
925 nm = os.path.join (a, name)
927 __main__.read_files.append (nm)
932 sys.stderr.write ("Reading `%s'\n" % nm)
933 return (f.read (), nm)
935 error ("File not found `%s'\n" % name)
938 def do_ignore(match_object):
939 return [('ignore', match_object.group('code'))]
940 def do_preamble_end(match_object):
941 return [('preamble-end', match_object.group('code'))]
943 def make_verbatim(match_object):
944 return [('verbatim', match_object.group('code'))]
946 def make_verb(match_object):
947 return [('verb', match_object.group('code'))]
949 def do_include_file(m):
951 return [('input', get_output ('pagebreak'))] \
952 + read_doc_file(m.group('filename')) \
953 + [('input', get_output ('pagebreak'))]
955 def do_input_file(m):
956 return read_doc_file(m.group('filename'))
958 def make_lilypond(m):
959 if m.group('options'):
960 options = m.group('options')
963 return [('input', get_output('output-lilypond-fragment') %
964 (options, m.group('code')))]
966 def make_lilypond_file(m):
969 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
970 into a @lilypond .. @end lilypond block.
974 if m.group('options'):
975 options = m.group('options')
978 (content, nm) = find_file(m.group('filename'))
979 options = "filename=%s," % nm + options
981 return [('input', get_output('output-lilypond') %
984 def make_lilypond_block(m):
988 if m.group('options'):
989 options = get_re('option-sep').split (m.group('options'))
992 options = filter(lambda s: s != '', options)
993 return [('lilypond', m.group('code'), options)]
996 if __main__.format != 'latex':
998 if m.group('num') == 'one':
999 return [('numcols', m.group('code'), 1)]
1000 if m.group('num') == 'two':
1001 return [('numcols', m.group('code'), 2)]
1003 def chop_chunks(chunks, re_name, func, use_match=0):
1009 m = get_re (re_name).search (str)
1011 newchunks.append (('input', str))
1015 newchunks.append (('input', str[:m.start ('match')]))
1017 newchunks.append (('input', str[:m.start (0)]))
1018 #newchunks.extend(func(m))
1019 # python 1.5 compatible:
1020 newchunks = newchunks + func(m)
1021 str = str [m.end(0):]
1026 def determine_format (str):
1027 if __main__.format == '':
1029 html = re.search ('(?i)<[dh]tml', str[:200])
1030 latex = re.search (r'''\\document''', str[:200])
1031 texi = re.search ('@node|@setfilename', str[:200])
1036 if html and not latex and not texi:
1038 elif latex and not html and not texi:
1040 elif texi and not html and not latex:
1043 error ("can't determine format, please specify")
1046 if __main__.paperguru == None:
1047 if __main__.format == 'html':
1049 elif __main__.format == 'latex':
1051 elif __main__.format == 'texi':
1054 __main__.paperguru = g
1057 def read_doc_file (filename):
1058 '''Read the input file, find verbatim chunks and do \input and \include
1060 (str, path) = find_file(filename)
1061 determine_format (str)
1063 chunks = [('input', str)]
1065 # we have to check for verbatim before doing include,
1066 # because we don't want to include files that are mentioned
1067 # inside a verbatim environment
1068 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1069 chunks = chop_chunks(chunks, 'verb', make_verb)
1070 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1072 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1073 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1077 taken_file_names = {}
1078 def schedule_lilypond_block (chunk):
1079 '''Take the body and options from CHUNK, figure out how the
1080 real .ly should look, and what should be left MAIN_STR (meant
1081 for the main file). The .ly is written, and scheduled in
1084 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1086 TODO has format [basename, extension, extension, ... ]
1089 (type, body, opts) = chunk
1090 assert type == 'lilypond'
1091 file_body = compose_full_body (body, opts)
1092 ## Hmm, we should hash only lilypond source, and skip the
1095 basename = 'lily-' + `abs(hash (file_body))`
1097 m = re.search ('filename="(.*?)"', o)
1099 basename = m.group (1)
1100 if not taken_file_names.has_key(basename):
1101 taken_file_names[basename] = 0
1103 taken_file_names[basename] = taken_file_names[basename] + 1
1104 basename = basename + "-%i" % taken_file_names[basename]
1106 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1107 needed_filetypes = ['tex']
1109 if format == 'html' or format == 'texi':
1110 needed_filetypes.append ('eps')
1111 needed_filetypes.append ('png')
1112 if 'eps' in opts and not ('eps' in needed_filetypes):
1113 needed_filetypes.append('eps')
1114 pathbase = os.path.join (g_outdir, basename)
1115 def f (base, ext1, ext2):
1116 a = os.path.isfile(base + ext2)
1117 if (os.path.isfile(base + ext1) and
1118 os.path.isfile(base + ext2) and
1119 os.stat(base+ext1)[stat.ST_MTIME] >
1120 os.stat(base+ext2)[stat.ST_MTIME]) or \
1121 not os.path.isfile(base + ext2):
1124 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1126 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1128 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1132 if 'printfilename' in opts:
1134 m= re.match ("filename=(.*)", o)
1136 newbody = newbody + get_output ("output-filename") % m.group(1)
1140 if 'verbatim' in opts:
1141 newbody = output_verbatim (body)
1144 m = re.search ('intertext="(.*?)"', o)
1146 newbody = newbody + m.group (1) + "\n\n"
1148 if 'noinline' in opts:
1149 s = 'output-noinline'
1150 elif format == 'latex':
1155 else: # format == 'html' or format == 'texi':
1157 newbody = newbody + get_output (s) % {'fn': basename }
1158 return ('lilypond', newbody, opts, todo, basename)
1160 def process_lilypond_blocks(chunks):#ugh rename
1162 # Count sections/chapters.
1164 if c[0] == 'lilypond':
1165 c = schedule_lilypond_block (c)
1166 elif c[0] == 'numcols':
1167 paperguru.m_num_cols = c[2]
1168 newchunks.append (c)
1174 sys.stderr.write ("invoking `%s'\n" % cmd)
1175 st = os.system (cmd)
1177 error ('Error command exited with value %d\n' % st)
1180 def quiet_system (cmd, name):
1182 progress ( _("Running %s...") % name)
1183 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1187 def get_bbox (filename):
1188 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1190 box = open (filename + '.bbox').read()
1191 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1194 gr = map (string.atoi, m.groups ())
1198 def make_pixmap (name):
1199 bbox = get_bbox (name + '.eps')
1201 fo = open (name + '.trans.eps' , 'w')
1202 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1207 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1208 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1210 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s'''
1212 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1215 status = quiet_system (cmd, 'gs')
1220 os.unlink (name + '.png')
1221 error ("Removing output file")
1223 def compile_all_files (chunks):
1230 if c[0] <> 'lilypond':
1239 if base + '.ly' not in tex:
1240 tex.append (base + '.ly')
1241 elif e == 'png' and g_do_pictures:
1247 # fixme: be sys-independent.
1249 if g_outdir and x[0] <> '/' :
1250 x = os.path.join (g_here_dir, x)
1253 incs = map (incl_opt, include_path)
1254 lilyopts = string.join (incs, ' ' )
1256 lilyopts = lilyopts + ' --dependencies '
1258 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1259 texfiles = string.join (tex, ' ')
1260 cmd = 'lilypond --header=texidoc %s %s %s' \
1261 % (lilyopts, g_extra_opts, texfiles)
1266 # Ugh, fixing up dependencies for .tex generation
1269 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1274 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1280 cmd = r"echo $TEXMF; tex '\nonstopmode \input %s'" % e
1281 quiet_system (cmd, 'TeX')
1283 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1284 quiet_system (cmd, 'dvips')
1292 def update_file (body, name):
1294 write the body if it has changed
1305 f = open (name , 'w')
1312 def getopt_args (opts):
1313 "Construct arguments (LONG, SHORT) for getopt from list of options."
1318 short = short + o[1]
1326 return (short, long)
1328 def option_help_str (o):
1329 "Transform one option description (4-tuple ) into neatly formatted string"
1347 return ' ' + sh + sep + long + arg
1350 def options_help_str (opts):
1351 "Convert a list of options into a neatly formatted string"
1357 s = option_help_str (o)
1358 strs.append ((s, o[3]))
1364 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1368 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1369 Generate hybrid LaTeX input from Latex + lilypond
1372 sys.stdout.write (options_help_str (option_definitions))
1373 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1377 Report bugs to bug-lilypond@gnu.org.
1379 Written by Tom Cato Amundsen <tca@gnu.org> and
1380 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1386 def write_deps (fn, target, chunks):
1388 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1389 f = open (os.path.join(g_outdir, fn), 'w')
1390 f.write ('%s%s: ' % (g_dep_prefix, target))
1391 for d in read_files:
1395 if c[0] == 'lilypond':
1396 (type, body, opts, todo, basename) = c;
1397 basenames.append (basename)
1400 d=g_outdir + '/' + d
1402 #if not os.isfile (d): # thinko?
1403 if not re.search ('/', d):
1404 d = g_dep_prefix + d
1405 f.write ('%s.tex ' % d)
1407 #if len (basenames):
1408 # for d in basenames:
1409 # f.write ('%s.ly ' % d)
1410 # f.write (' : %s' % target)
1415 def identify (stream):
1416 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1418 def print_version ():
1419 identify (sys.stdout)
1420 sys.stdout.write (r'''Copyright 1998--1999
1421 Distributed under terms of the GNU General Public License. It comes with
1426 def check_texidoc (chunks):
1429 if c[0] == 'lilypond':
1430 (type, body, opts, todo, basename) = c;
1431 pathbase = os.path.join (g_outdir, basename)
1432 if os.path.isfile (pathbase + '.texidoc'):
1433 body = '\n@include %s.texidoc\n' % basename + body
1434 c = (type, body, opts, todo, basename)
1439 ## what's this? Docme --hwn
1441 def fix_epswidth (chunks):
1444 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1445 newchunks.append (c)
1450 m = re.match ('magnification=([0-9.]+)', o)
1452 mag = string.atof (m.group (1))
1454 def replace_eps_dim (match, lmag = mag):
1455 filename = match.group (1)
1456 dims = bounding_box_dimensions (filename)
1458 return '%fpt' % (dims[0] *lmag)
1460 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1461 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1466 ##docme: why global?
1468 def do_file(input_filename):
1470 chunks = read_doc_file(input_filename)
1471 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1472 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1473 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1474 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1475 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1476 chunks = chop_chunks(chunks, 'numcols', do_columns)
1478 #for c in chunks: print "c:", c;
1480 scan_preamble(chunks)
1481 chunks = process_lilypond_blocks(chunks)
1484 if __main__.g_run_lilypond:
1485 compile_all_files (chunks)
1486 chunks = fix_epswidth (chunks)
1488 if __main__.format == 'texi':
1489 chunks = check_texidoc (chunks)
1492 chunks = completize_preamble (chunks)
1498 my_outname = outname
1499 elif input_filename == '-' or input_filename == "/dev/stdin":
1502 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1503 my_depname = my_outname + '.dep'
1505 if my_outname == '-' or my_outname == '/dev/stdout':
1508 __main__.do_deps = 0
1510 foutn = os.path.join (g_outdir, my_outname)
1511 sys.stderr.write ("Writing `%s'\n" % foutn)
1512 fout = open (foutn, 'w')
1519 write_deps (my_depname, foutn, chunks)
1524 (sh, long) = getopt_args (__main__.option_definitions)
1525 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1526 except getopt.error, msg:
1527 sys.stderr.write("error: %s" % msg)
1535 if o == '--include' or o == '-I':
1536 include_path.append (a)
1537 elif o == '--version' or o == '-v':
1540 elif o == '--verbose' or o == '-V':
1541 __main__.verbose_p = 1
1542 elif o == '--format' or o == '-f':
1544 elif o == '--outname' or o == '-o':
1547 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1550 elif o == '--help' or o == '-h':
1552 elif o == '--no-lily' or o == '-n':
1553 __main__.g_run_lilypond = 0
1554 elif o == '--dependencies' or o == '-M':
1556 elif o == '--default-music-fontsize':
1557 default_music_fontsize = string.atoi (a)
1558 elif o == '--default-lilypond-fontsize':
1559 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1560 default_music_fontsize = string.atoi (a)
1561 elif o == '--extra-options':
1563 elif o == '--force-music-fontsize':
1564 g_force_lilypond_fontsize = string.atoi(a)
1565 elif o == '--force-lilypond-fontsize':
1566 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1567 g_force_lilypond_fontsize = string.atoi(a)
1568 elif o == '--dep-prefix':
1570 elif o == '--no-pictures':
1572 elif o == '--no-music':
1574 elif o == '--read-lys':
1576 elif o == '--outdir':
1579 identify (sys.stderr)
1581 if os.path.isfile(g_outdir):
1582 error ("outdir is a file: %s" % g_outdir)
1583 if not os.path.exists(g_outdir):
1585 setup_environment ()
1586 for input_filename in files:
1587 do_file(input_filename)
1590 # Petr, ik zou willen dat ik iets zinvoller deed,
1591 # maar wat ik kan ik doen, het verandert toch niets?