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_includemp = None
195 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
196 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
197 self.m_geo_x_marginparwidth = None
198 self.m_geo_x_marginparsep = None
200 def set_geo_option(self, name, value):
202 if type(value) == type([]):
203 value = map(conv_dimen_to_float, value)
205 value = conv_dimen_to_float(value)
207 if name == 'body' or name == 'text':
208 if type(value) == type([]):
209 self.m_geo_textwidth = value[0]
211 self.m_geo_textwidth = value
213 elif name == 'portrait':
214 self.m_geo_landscape = 0
215 elif name == 'reversemp' or name == 'reversemarginpar':
216 if self.m_geo_includemp == None:
217 self.m_geo_includemp = 1
218 elif name == 'marginparwidth' or name == 'marginpar':
219 self.m_geo_x_marginparwidth = value
220 self.m_geo_includemp = 1
221 elif name == 'marginparsep':
222 self.m_geo_x_marginparsep = value
223 self.m_geo_includemp = 1
224 elif name == 'scale':
225 if type(value) == type([]):
226 self.m_geo_width = self.get_paperwidth() * value[0]
228 self.m_geo_width = self.get_paperwidth() * value
229 elif name == 'hscale':
230 self.m_geo_width = self.get_paperwidth() * value
231 elif name == 'left' or name == 'lmargin':
232 self.m_geo_lmargin = value
233 elif name == 'right' or name == 'rmargin':
234 self.m_geo_rmargin = value
235 elif name == 'hdivide' or name == 'divide':
236 if value[0] not in ('*', ''):
237 self.m_geo_lmargin = value[0]
238 if value[1] not in ('*', ''):
239 self.m_geo_width = value[1]
240 if value[2] not in ('*', ''):
241 self.m_geo_rmargin = value[2]
242 elif name == 'hmargin':
243 if type(value) == type([]):
244 self.m_geo_lmargin = value[0]
245 self.m_geo_rmargin = value[1]
247 self.m_geo_lmargin = value
248 self.m_geo_rmargin = value
249 elif name == 'margin':#ugh there is a bug about this option in
250 # the geometry documentation
251 if type(value) == type([]):
252 self.m_geo_lmargin = value[0]
253 self.m_geo_rmargin = value[0]
255 self.m_geo_lmargin = value
256 self.m_geo_rmargin = value
257 elif name == 'total':
258 if type(value) == type([]):
259 self.m_geo_width = value[0]
261 self.m_geo_width = value
262 elif name == 'width' or name == 'totalwidth':
263 self.m_geo_width = value
264 elif name == 'paper' or name == 'papername':
265 self.m_papersize = value
266 elif name[-5:] == 'paper':
267 self.m_papersize = name
271 def __setattr__(self, name, value):
272 if type(value) == type("") and \
273 dimension_conversion_dict.has_key (value[-2:]):
274 f = dimension_conversion_dict[value[-2:]]
275 self.__dict__[name] = f(float(value[:-2]))
277 self.__dict__[name] = value
280 s = "LatexPaper:\n-----------"
281 for v in self.__dict__.keys():
283 s = s + str (v) + ' ' + str (self.__dict__[v])
284 s = s + "-----------"
287 def get_linewidth(self):
288 w = self._calc_linewidth()
289 if self.m_num_cols == 2:
293 def get_paperwidth(self):
294 #if self.m_use_geometry:
295 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
296 #return self.m_paperdef[self.m_papersize][self.m_landscape]
298 def _calc_linewidth(self):
299 # since geometry sometimes ignores 'includemp', this is
300 # more complicated than it should be
302 if self.m_geo_includemp:
303 if self.m_geo_x_marginparsep is not None:
304 mp = mp + self.m_geo_x_marginparsep
306 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
307 if self.m_geo_x_marginparwidth is not None:
308 mp = mp + self.m_geo_x_marginparwidth
310 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
312 #ugh test if this is necessary
316 if not self.m_use_geometry:
317 return latex_linewidths[self.m_papersize][self.m_fontsize]
319 geo_opts = (self.m_geo_lmargin == None,
320 self.m_geo_width == None,
321 self.m_geo_rmargin == None)
323 if geo_opts == (1, 1, 1):
324 if self.m_geo_textwidth:
325 return self.m_geo_textwidth
326 w = self.get_paperwidth() * 0.8
328 elif geo_opts == (0, 1, 1):
329 if self.m_geo_textwidth:
330 return self.m_geo_textwidth
331 return self.f1(self.m_geo_lmargin, mp)
332 elif geo_opts == (1, 1, 0):
333 if self.m_geo_textwidth:
334 return self.m_geo_textwidth
335 return self.f1(self.m_geo_rmargin, mp)
337 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
338 if self.m_geo_textwidth:
339 return self.m_geo_textwidth
340 return self.m_geo_width - mp
341 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
342 w = self.get_paperwidth() \
343 - self.m_geo_lmargin - self.m_geo_rmargin - mp
347 raise "Never do this!"
349 tmp = self.get_paperwidth() - m * 2 - mp
354 tmp = self.get_paperwidth() - self.m_geo_lmargin \
362 self.m_papersize = 'letterpaper'
364 def get_linewidth(self):
365 return html_linewidths[self.m_papersize][self.m_fontsize]
369 self.m_papersize = 'letterpaper'
371 def get_linewidth(self):
372 return texi_linewidths[self.m_papersize][self.m_fontsize]
378 def em2pt(x, fontsize = 10):
379 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
380 def ex2pt(x, fontsize = 10):
381 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
386 dimension_conversion_dict ={
388 'cm': lambda x: mm2pt(10*x),
395 # Convert numeric values, with or without specific dimension, to floats.
397 def conv_dimen_to_float(value):
398 if type(value) == type(""):
399 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
402 num = string.atof(m.group (1))
403 conv = dimension_conversion_dict[m.group(2)]
407 elif re.match ("^[0-9.]+$",value):
414 # indices are no. of columns, papersize, fontsize
415 # Why can't this be calculated?
417 'a4paper':{10: 345, 11: 360, 12: 390},
418 'a4paper-landscape': {10: 598, 11: 596, 12:592},
419 'a5paper':{10: 276, 11: 276, 12: 276},
420 'b5paper':{10: 345, 11: 356, 12: 356},
421 'letterpaper':{10: 345, 11: 360, 12: 390},
422 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
423 'legalpaper': {10: 345, 11: 360, 12: 390},
424 'executivepaper':{10: 345, 11: 360, 12: 379}}
427 'afourpaper': {12: mm2pt(160)},
428 'afourwide': {12: in2pt(6.5)},
429 'afourlatex': {12: mm2pt(150)},
430 'smallbook': {12: in2pt(5)},
431 'letterpaper': {12: in2pt(6)}}
434 'afourpaper': {12: mm2pt(160)},
435 'afourwide': {12: in2pt(6.5)},
436 'afourlatex': {12: mm2pt(150)},
437 'smallbook': {12: in2pt(5)},
438 'letterpaper': {12: in2pt(6)}}
440 option_definitions = [
441 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
442 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
443 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
444 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
445 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
446 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
447 ('', 'h', 'help', 'this help'),
448 ('DIR', 'I', 'include', 'include path'),
449 ('', 'M', 'dependencies', 'write dependencies'),
450 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
451 ('', 'n', 'no-lily', 'don\'t run lilypond'),
452 ('', '', 'no-pictures', "don\'t generate pictures"),
453 ('', '', 'no-music', "strip all lilypond blocks from output"),
454 ('', '', 'read-lys', "don't write ly files."),
455 ('FILE', 'o', 'outname', 'filename main output file'),
456 ('FILE', '', 'outdir', "where to place generated files"),
457 ('', 'V', 'verbose', 'verbose' ),
458 ('', 'v', 'version', 'print version information' ),
461 # format specific strings, ie. regex-es for input, and % strings for output
463 'html' : {'output-lilypond': '''<lilypond%s>
466 'output-filename' : r'''
469 'output-lilypond-fragment': '''<lilypond%s>
470 \context Staff\context Voice{ %s }
472 'output-noinline': r'''
473 <!-- generated: %(fn)s.png !-->
477 'output-verbatim': r'''<pre>
480 ## Ugh we need to differentiate on origin:
481 ## lilypond-block origin wants an extra <p>, but
482 ## inline music doesn't.
483 ## possibly other center options?
485 <a href="%(fn)s.png">
486 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
490 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
497 'output-filename' : r'''
500 'output-lilypond': r'''\begin[%s]{lilypond}
504 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
506 'output-default-post': "\\def\postLilypondExample{}\n",
507 'output-default-pre': "\\def\preLilypondExample{}\n",
508 'usepackage-graphics': '\\usepackage{graphics}\n',
509 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
510 'output-noinline': r'''
511 %% generated: %(fn)s.eps
513 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
514 'pagebreak': r'\pagebreak',
517 'texi' : {'output-lilypond': '''@lilypond[%s]
521 'output-filename' : r'''
524 'output-lilypond-fragment': '''@lilypond[%s]
525 \context Staff\context Voice{ %s }
527 'output-noinline': r'''
528 @c generated: %(fn)s.png
531 'output-verbatim': r'''@example
536 # do some tweaking: @ is needed in some ps stuff.
537 # override EndLilyPondOutput, since @tex is done
538 # in a sandbox, you can't do \input lilyponddefs at the
539 # top of the document.
541 # should also support fragment in
543 # ugh, the <p> below breaks inline images...
549 \def\EndLilyPondOutput{}
555 <a href="%(fn)s.png">
556 <img border=0 src="%(fn)s.png" alt="[picture of music]">
564 def output_verbatim (body):
565 if __main__.format == 'html':
566 body = re.sub ('&', '&', body)
567 body = re.sub ('>', '>', body)
568 body = re.sub ('<', '<', body)
569 elif __main__.format == 'texi':
570 body = re.sub ('([@{}])', '@\\1', body)
571 return get_output ('output-verbatim') % body
574 #warning: this uses extended regular expressions. Tread with care.
578 # (?P -- name parameter
579 # *? -- match non-greedily.
586 'preamble-end': no_match,
587 'landscape': no_match,
588 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
589 'verb': r'''(?P<code><pre>.*?</pre>)''',
590 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
591 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
592 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
593 'option-sep' : '\s*',
594 'intertext': r',?\s*intertext=\".*?\"',
595 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
596 'singleline-comment': no_match,
600 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
601 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
602 'option-sep' : ',\s*',
603 'header': r"\\documentclass\s*(\[.*?\])?",
604 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
605 'preamble-end': r'(?P<code>\\begin{document})',
606 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
607 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
608 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
609 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
610 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
611 'def-post-re': r"\\def\\postLilypondExample",
612 'def-pre-re': r"\\def\\preLilypondExample",
613 'usepackage-graphics': r"\usepackage{graphics}",
614 'intertext': r',?\s*intertext=\".*?\"',
615 'multiline-comment': no_match,
616 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
617 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
621 # why do we have distinction between @mbinclude and @include?
625 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
628 'preamble-end': no_match,
629 'landscape': no_match,
630 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
631 'verb': r'''(?P<code>@code{.*?})''',
632 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
633 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
634 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
635 'option-sep' : ',\s*',
636 'intertext': r',?\s*intertext=\".*?\"',
637 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
638 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
644 for r in re_dict.keys ():
647 for k in olddict.keys ():
649 newdict[k] = re.compile (olddict[k])
651 print 'invalid regexp: %s' % olddict[k]
653 # we'd like to catch and reraise a more detailed error, but
654 # alas, the exceptions changed across the 1.5/2.1 boundary.
669 def get_output (name):
670 return output_dict[format][name]
673 return re_dict[format][name]
675 def bounding_box_dimensions(fname):
677 fname = os.path.join(g_outdir, fname)
681 error ("Error opening `%s'" % fname)
683 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
686 gs = map (lambda x: string.atoi (x), s.groups ())
687 return (int (gs[2] - gs[0] + 0.5),
688 int (gs[3] - gs[1] + 0.5))
693 sys.stderr.write (str + "\n Exiting ... \n\n")
697 def compose_full_body (body, opts):
698 '''Construct the lilypond code to send to Lilypond.
699 Add stuff to BODY using OPTS as options.'''
700 music_size = default_music_fontsize
701 latex_size = default_text_fontsize
705 if g_force_lilypond_fontsize:
706 music_size = g_force_lilypond_fontsize
708 m = re.match ('([0-9]+)pt', o)
710 music_size = string.atoi(m.group (1))
712 m = re.match ('latexfontsize=([0-9]+)pt', o)
714 latex_size = string.atoi (m.group (1))
716 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
718 f = float (m.group (1))
719 indent = 'indent = %f\\%s' % (f, m.group (2))
721 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
723 f = float (m.group (1))
724 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
726 if re.search ('\\\\score', body):
730 if 'fragment' in opts:
732 if 'nofragment' in opts:
735 if is_fragment and not 'multiline' in opts:
736 opts.append('singleline')
738 if 'singleline' in opts:
739 linewidth = 'linewidth = -1.0'
741 l = __main__.paperguru.get_linewidth ()
742 linewidth = 'linewidth = %f\pt' % l
744 if 'noindent' in opts:
745 indent = 'indent = 0.0\mm'
748 m= re.search ('relative(.*)', o)
752 v = string.atoi (m.group (1))
759 pitch = pitch + '\,' * v
761 pitch = pitch + '\'' * v
763 body = '\\relative %s { %s }' %(pitch, body)
772 optstring = string.join (opts, ' ')
773 optstring = re.sub ('\n', ' ', optstring)
775 %% Generated automatically by: lilypond-book.py
777 \include "paper%d.ly"
782 ''' % (optstring, music_size, linewidth, indent) + body
784 # ughUGH not original options
787 def parse_options_string(s):
789 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
790 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
791 r3 = re.compile("(\w+?)((,\s*)|$)")
796 d[m.group(2)] = re.split(",\s*", m.group(3))
801 d[m.group(2)] = m.group(3)
809 error ("format of option string invalid (was `%')" % s)
812 def scan_html_preamble (chunks):
815 def scan_latex_preamble(chunks):
816 # first we want to scan the \documentclass line
817 # it should be the first non-comment line
820 if chunks[idx][0] == 'ignore':
823 m = get_re ('header').match(chunks[idx][1])
824 if m <> None and m.group (1):
825 options = re.split (',[\n \t]*', m.group(1)[1:-1])
830 paperguru.m_landscape = 1
831 m = re.match("(.*?)paper", o)
833 paperguru.m_papersize = m.group()
835 m = re.match("(\d\d)pt", o)
837 paperguru.m_fontsize = int(m.group(1))
840 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
841 if chunks[idx] == 'ignore':
844 m = get_re ('geometry').search(chunks[idx][1])
846 paperguru.m_use_geometry = 1
847 o = parse_options_string(m.group('options'))
849 paperguru.set_geo_option(k, o[k])
852 def scan_texi_preamble (chunks):
853 # this is not bulletproof..., it checks the first 10 chunks
854 for c in chunks[:10]:
856 for s in ('afourpaper', 'afourwide', 'letterpaper',
857 'afourlatex', 'smallbook'):
858 if string.find(c[1], "@%s" % s) != -1:
859 paperguru.m_papersize = s
862 def scan_preamble (chunks):
863 if __main__.format == 'html':
864 scan_html_preamble (chunks)
865 elif __main__.format == 'latex':
866 scan_latex_preamble (chunks)
867 elif __main__.format == 'texi':
868 scan_texi_preamble (chunks)
871 def completize_preamble (chunks):
872 if __main__.format != 'latex':
874 pre_b = post_b = graphics_b = None
876 if chunk[0] == 'preamble-end':
878 if chunk[0] == 'input':
879 m = get_re('def-pre-re').search(chunk[1])
882 if chunk[0] == 'input':
883 m = get_re('def-post-re').search(chunk[1])
887 if chunk[0] == 'input':
888 m = get_re('usepackage-graphics').search(chunk[1])
892 while x < len (chunks) and chunks[x][0] != 'preamble-end':
899 chunks.insert(x, ('input', get_output ('output-default-pre')))
901 chunks.insert(x, ('input', get_output ('output-default-post')))
903 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
909 def find_file (name):
911 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
915 return (sys.stdin.read (), '<stdin>')
918 for a in include_path:
920 nm = os.path.join (a, name)
922 __main__.read_files.append (nm)
927 sys.stderr.write ("Reading `%s'\n" % nm)
928 return (f.read (), nm)
930 error ("File not found `%s'\n" % name)
933 def do_ignore(match_object):
934 return [('ignore', match_object.group('code'))]
935 def do_preamble_end(match_object):
936 return [('preamble-end', match_object.group('code'))]
938 def make_verbatim(match_object):
939 return [('verbatim', match_object.group('code'))]
941 def make_verb(match_object):
942 return [('verb', match_object.group('code'))]
944 def do_include_file(m):
946 return [('input', get_output ('pagebreak'))] \
947 + read_doc_file(m.group('filename')) \
948 + [('input', get_output ('pagebreak'))]
950 def do_input_file(m):
951 return read_doc_file(m.group('filename'))
953 def make_lilypond(m):
954 if m.group('options'):
955 options = m.group('options')
958 return [('input', get_output('output-lilypond-fragment') %
959 (options, m.group('code')))]
961 def make_lilypond_file(m):
964 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
965 into a @lilypond .. @end lilypond block.
969 if m.group('options'):
970 options = m.group('options')
973 (content, nm) = find_file(m.group('filename'))
974 options = "filename=%s," % nm + options
976 return [('input', get_output('output-lilypond') %
979 def make_lilypond_block(m):
983 if m.group('options'):
984 options = get_re('option-sep').split (m.group('options'))
987 options = filter(lambda s: s != '', options)
988 return [('lilypond', m.group('code'), options)]
991 if __main__.format != 'latex':
993 if m.group('num') == 'one':
994 return [('numcols', m.group('code'), 1)]
995 if m.group('num') == 'two':
996 return [('numcols', m.group('code'), 2)]
998 def chop_chunks(chunks, re_name, func, use_match=0):
1004 m = get_re (re_name).search (str)
1006 newchunks.append (('input', str))
1010 newchunks.append (('input', str[:m.start ('match')]))
1012 newchunks.append (('input', str[:m.start (0)]))
1013 #newchunks.extend(func(m))
1014 # python 1.5 compatible:
1015 newchunks = newchunks + func(m)
1016 str = str [m.end(0):]
1021 def determine_format (str):
1022 if __main__.format == '':
1024 html = re.search ('(?i)<[dh]tml', str[:200])
1025 latex = re.search (r'''\\document''', str[:200])
1026 texi = re.search ('@node|@setfilename', str[:200])
1031 if html and not latex and not texi:
1033 elif latex and not html and not texi:
1035 elif texi and not html and not latex:
1038 error ("can't determine format, please specify")
1041 if __main__.paperguru == None:
1042 if __main__.format == 'html':
1044 elif __main__.format == 'latex':
1046 elif __main__.format == 'texi':
1049 __main__.paperguru = g
1052 def read_doc_file (filename):
1053 '''Read the input file, find verbatim chunks and do \input and \include
1055 (str, path) = find_file(filename)
1056 determine_format (str)
1058 chunks = [('input', str)]
1060 # we have to check for verbatim before doing include,
1061 # because we don't want to include files that are mentioned
1062 # inside a verbatim environment
1063 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1064 chunks = chop_chunks(chunks, 'verb', make_verb)
1065 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1067 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1068 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1072 taken_file_names = {}
1073 def schedule_lilypond_block (chunk):
1074 '''Take the body and options from CHUNK, figure out how the
1075 real .ly should look, and what should be left MAIN_STR (meant
1076 for the main file). The .ly is written, and scheduled in
1079 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1081 TODO has format [basename, extension, extension, ... ]
1084 (type, body, opts) = chunk
1085 assert type == 'lilypond'
1086 file_body = compose_full_body (body, opts)
1087 ## Hmm, we should hash only lilypond source, and skip the
1090 basename = 'lily-' + `abs(hash (file_body))`
1092 m = re.search ('filename="(.*?)"', o)
1094 basename = m.group (1)
1095 if not taken_file_names.has_key(basename):
1096 taken_file_names[basename] = 0
1098 taken_file_names[basename] = taken_file_names[basename] + 1
1099 basename = basename + "-%i" % taken_file_names[basename]
1101 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1102 needed_filetypes = ['tex']
1104 if format == 'html' or format == 'texi':
1105 needed_filetypes.append ('eps')
1106 needed_filetypes.append ('png')
1107 if 'eps' in opts and not ('eps' in needed_filetypes):
1108 needed_filetypes.append('eps')
1109 pathbase = os.path.join (g_outdir, basename)
1110 def f (base, ext1, ext2):
1111 a = os.path.isfile(base + ext2)
1112 if (os.path.isfile(base + ext1) and
1113 os.path.isfile(base + ext2) and
1114 os.stat(base+ext1)[stat.ST_MTIME] >
1115 os.stat(base+ext2)[stat.ST_MTIME]) or \
1116 not os.path.isfile(base + ext2):
1119 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1121 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1123 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1127 if 'printfilename' in opts:
1129 m= re.match ("filename=(.*)", o)
1131 newbody = newbody + get_output ("output-filename") % m.group(1)
1135 if 'verbatim' in opts:
1136 newbody = output_verbatim (body)
1139 m = re.search ('intertext="(.*?)"', o)
1141 newbody = newbody + m.group (1) + "\n\n"
1143 if 'noinline' in opts:
1144 s = 'output-noinline'
1145 elif format == 'latex':
1150 else: # format == 'html' or format == 'texi':
1152 newbody = newbody + get_output (s) % {'fn': basename }
1153 return ('lilypond', newbody, opts, todo, basename)
1155 def process_lilypond_blocks(chunks):#ugh rename
1157 # Count sections/chapters.
1159 if c[0] == 'lilypond':
1160 c = schedule_lilypond_block (c)
1161 elif c[0] == 'numcols':
1162 paperguru.m_num_cols = c[2]
1163 newchunks.append (c)
1169 sys.stderr.write ("invoking `%s'\n" % cmd)
1170 st = os.system (cmd)
1172 error ('Error command exited with value %d\n' % st)
1175 def quiet_system (cmd, name):
1177 progress ( _("Running %s...") % name)
1178 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1182 def get_bbox (filename):
1183 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1185 box = open (filename + '.bbox').read()
1186 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1189 gr = map (string.atoi, m.groups ())
1193 def make_pixmap (name):
1194 bbox = get_bbox (name + '.eps')
1196 fo = open (name + '.trans.eps' , 'w')
1197 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1202 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1203 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1205 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s'''
1207 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1208 quiet_system (cmd, 'gs')
1211 status = system (cmd)
1213 os.unlink (name + '.png')
1214 error ("Removing output file")
1216 def compile_all_files (chunks):
1223 if c[0] <> 'lilypond':
1232 if base + '.ly' not in tex:
1233 tex.append (base + '.ly')
1234 elif e == 'png' and g_do_pictures:
1240 # fixme: be sys-independent.
1242 if g_outdir and x[0] <> '/' :
1243 x = os.path.join (g_here_dir, x)
1246 incs = map (incl_opt, include_path)
1247 lilyopts = string.join (incs, ' ' )
1249 lilyopts = lilyopts + ' --dependencies '
1251 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1252 texfiles = string.join (tex, ' ')
1253 cmd = 'lilypond --header=texidoc %s %s %s' \
1254 % (lilyopts, g_extra_opts, texfiles)
1259 # Ugh, fixing up dependencies for .tex generation
1262 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1267 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1273 cmd = r"echo $TEXMF; tex '\nonstopmode \input %s'" % e
1274 quiet_system (cmd, 'TeX')
1276 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1277 quiet_system (cmd, 'dvips')
1285 def update_file (body, name):
1287 write the body if it has changed
1298 f = open (name , 'w')
1305 def getopt_args (opts):
1306 "Construct arguments (LONG, SHORT) for getopt from list of options."
1311 short = short + o[1]
1319 return (short, long)
1321 def option_help_str (o):
1322 "Transform one option description (4-tuple ) into neatly formatted string"
1340 return ' ' + sh + sep + long + arg
1343 def options_help_str (opts):
1344 "Convert a list of options into a neatly formatted string"
1350 s = option_help_str (o)
1351 strs.append ((s, o[3]))
1357 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1361 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1362 Generate hybrid LaTeX input from Latex + lilypond
1365 sys.stdout.write (options_help_str (option_definitions))
1366 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1370 Report bugs to bug-lilypond@gnu.org.
1372 Written by Tom Cato Amundsen <tca@gnu.org> and
1373 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1379 def write_deps (fn, target, chunks):
1381 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1382 f = open (os.path.join(g_outdir, fn), 'w')
1383 f.write ('%s%s: ' % (g_dep_prefix, target))
1384 for d in read_files:
1388 if c[0] == 'lilypond':
1389 (type, body, opts, todo, basename) = c;
1390 basenames.append (basename)
1393 d=g_outdir + '/' + d
1395 #if not os.isfile (d): # thinko?
1396 if not re.search ('/', d):
1397 d = g_dep_prefix + d
1398 f.write ('%s.tex ' % d)
1400 #if len (basenames):
1401 # for d in basenames:
1402 # f.write ('%s.ly ' % d)
1403 # f.write (' : %s' % target)
1408 def identify (stream):
1409 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1411 def print_version ():
1412 identify (sys.stdout)
1413 sys.stdout.write (r'''Copyright 1998--1999
1414 Distributed under terms of the GNU General Public License. It comes with
1419 def check_texidoc (chunks):
1422 if c[0] == 'lilypond':
1423 (type, body, opts, todo, basename) = c;
1424 pathbase = os.path.join (g_outdir, basename)
1425 if os.path.isfile (pathbase + '.texidoc'):
1426 body = '\n@include %s.texidoc\n' % basename + body
1427 c = (type, body, opts, todo, basename)
1432 ## what's this? Docme --hwn
1434 def fix_epswidth (chunks):
1437 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1438 newchunks.append (c)
1443 m = re.match ('magnification=([0-9.]+)', o)
1445 mag = string.atof (m.group (1))
1447 def replace_eps_dim (match, lmag = mag):
1448 filename = match.group (1)
1449 dims = bounding_box_dimensions (filename)
1451 return '%fpt' % (dims[0] *lmag)
1453 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1454 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1459 ##docme: why global?
1461 def do_file(input_filename):
1463 chunks = read_doc_file(input_filename)
1464 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1465 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1466 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1467 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1468 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1469 chunks = chop_chunks(chunks, 'numcols', do_columns)
1471 #for c in chunks: print "c:", c;
1473 scan_preamble(chunks)
1474 chunks = process_lilypond_blocks(chunks)
1477 if __main__.g_run_lilypond:
1478 compile_all_files (chunks)
1479 chunks = fix_epswidth (chunks)
1481 if __main__.format == 'texi':
1482 chunks = check_texidoc (chunks)
1485 chunks = completize_preamble (chunks)
1491 my_outname = outname
1492 elif input_filename == '-' or input_filename == "/dev/stdin":
1495 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1496 my_depname = my_outname + '.dep'
1498 if my_outname == '-' or my_outname == '/dev/stdout':
1501 __main__.do_deps = 0
1503 foutn = os.path.join (g_outdir, my_outname)
1504 sys.stderr.write ("Writing `%s'\n" % foutn)
1505 fout = open (foutn, 'w')
1512 write_deps (my_depname, foutn, chunks)
1517 (sh, long) = getopt_args (__main__.option_definitions)
1518 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1519 except getopt.error, msg:
1520 sys.stderr.write("error: %s" % msg)
1528 if o == '--include' or o == '-I':
1529 include_path.append (a)
1530 elif o == '--version' or o == '-v':
1533 elif o == '--verbose' or o == '-V':
1534 __main__.verbose_p = 1
1535 elif o == '--format' or o == '-f':
1537 elif o == '--outname' or o == '-o':
1540 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1543 elif o == '--help' or o == '-h':
1545 elif o == '--no-lily' or o == '-n':
1546 __main__.g_run_lilypond = 0
1547 elif o == '--dependencies' or o == '-M':
1549 elif o == '--default-music-fontsize':
1550 default_music_fontsize = string.atoi (a)
1551 elif o == '--default-lilypond-fontsize':
1552 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1553 default_music_fontsize = string.atoi (a)
1554 elif o == '--extra-options':
1556 elif o == '--force-music-fontsize':
1557 g_force_lilypond_fontsize = string.atoi(a)
1558 elif o == '--force-lilypond-fontsize':
1559 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1560 g_force_lilypond_fontsize = string.atoi(a)
1561 elif o == '--dep-prefix':
1563 elif o == '--no-pictures':
1565 elif o == '--no-music':
1567 elif o == '--read-lys':
1569 elif o == '--outdir':
1572 identify (sys.stderr)
1574 if os.path.isfile(g_outdir):
1575 error ("outdir is a file: %s" % g_outdir)
1576 if not os.path.exists(g_outdir):
1578 setup_environment ()
1579 for input_filename in files:
1580 do_file(input_filename)
1583 # Petr, ik zou willen dat ik iets zinvoller deed,
1584 # maar wat ik kan ik doen, het verandert toch niets?