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>.*?)\])?\n(?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 # print re_name, str[0:150] +'...'+ str[-50:]
1006 m = get_re (re_name).search (str)
1008 newchunks.append (('input', str))
1012 newchunks.append (('input', str[:m.start ('match')]))
1014 newchunks.append (('input', str[:m.start (0)]))
1015 #newchunks.extend(func(m))
1016 # python 1.5 compatible:
1017 newchunks = newchunks + func(m)
1018 str = str [m.end(0):]
1023 def determine_format (str):
1024 if __main__.format == '':
1026 html = re.search ('(?i)<[dh]tml', str[:200])
1027 latex = re.search (r'''\\document''', str[:200])
1028 texi = re.search ('@node|@setfilename', str[:200])
1033 if html and not latex and not texi:
1035 elif latex and not html and not texi:
1037 elif texi and not html and not latex:
1040 error ("can't determine format, please specify")
1043 if __main__.paperguru == None:
1044 if __main__.format == 'html':
1046 elif __main__.format == 'latex':
1048 elif __main__.format == 'texi':
1051 __main__.paperguru = g
1054 def read_doc_file (filename):
1055 '''Read the input file, find verbatim chunks and do \input and \include
1057 (str, path) = find_file(filename)
1058 determine_format (str)
1060 chunks = [('input', str)]
1062 # we have to check for verbatim before doing include,
1063 # because we don't want to include files that are mentioned
1064 # inside a verbatim environment
1065 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1066 chunks = chop_chunks(chunks, 'verb', make_verb)
1067 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1069 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1070 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1074 taken_file_names = {}
1075 def schedule_lilypond_block (chunk):
1076 '''Take the body and options from CHUNK, figure out how the
1077 real .ly should look, and what should be left MAIN_STR (meant
1078 for the main file). The .ly is written, and scheduled in
1081 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1083 TODO has format [basename, extension, extension, ... ]
1086 (type, body, opts) = chunk
1087 assert type == 'lilypond'
1088 file_body = compose_full_body (body, opts)
1089 ## Hmm, we should hash only lilypond source, and skip the
1092 basename = 'lily-' + `abs(hash (file_body))`
1094 m = re.search ('filename="(.*?)"', o)
1096 basename = m.group (1)
1097 if not taken_file_names.has_key(basename):
1098 taken_file_names[basename] = 0
1100 taken_file_names[basename] = taken_file_names[basename] + 1
1101 basename = basename + "-%i" % taken_file_names[basename]
1103 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1104 needed_filetypes = ['tex']
1106 if format == 'html' or format == 'texi':
1107 needed_filetypes.append ('eps')
1108 needed_filetypes.append ('png')
1109 if 'eps' in opts and not ('eps' in needed_filetypes):
1110 needed_filetypes.append('eps')
1111 pathbase = os.path.join (g_outdir, basename)
1112 def f (base, ext1, ext2):
1113 a = os.path.isfile(base + ext2)
1114 if (os.path.isfile(base + ext1) and
1115 os.path.isfile(base + ext2) and
1116 os.stat(base+ext1)[stat.ST_MTIME] >
1117 os.stat(base+ext2)[stat.ST_MTIME]) or \
1118 not os.path.isfile(base + ext2):
1121 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1123 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1125 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1129 if 'printfilename' in opts:
1131 m= re.match ("filename=(.*)", o)
1133 newbody = newbody + get_output ("output-filename") % m.group(1)
1137 if 'verbatim' in opts:
1138 newbody = output_verbatim (body)
1141 m = re.search ('intertext="(.*?)"', o)
1143 newbody = newbody + m.group (1) + "\n\n"
1145 if 'noinline' in opts:
1146 s = 'output-noinline'
1147 elif format == 'latex':
1152 else: # format == 'html' or format == 'texi':
1154 newbody = newbody + get_output (s) % {'fn': basename }
1155 return ('lilypond', newbody, opts, todo, basename)
1157 def process_lilypond_blocks(chunks):#ugh rename
1159 # Count sections/chapters.
1161 if c[0] == 'lilypond':
1162 c = schedule_lilypond_block (c)
1163 elif c[0] == 'numcols':
1164 paperguru.m_num_cols = c[2]
1165 newchunks.append (c)
1171 sys.stderr.write ("invoking `%s'\n" % cmd)
1172 st = os.system (cmd)
1174 error ('Error command exited with value %d\n' % st)
1177 def quiet_system (cmd, name):
1179 progress ( _("Running %s...") % name)
1180 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1184 def get_bbox (filename):
1185 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1187 box = open (filename + '.bbox').read()
1188 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1191 gr = map (string.atoi, m.groups ())
1195 def make_pixmap (name):
1196 bbox = get_bbox (name + '.eps')
1198 fo = open (name + '.trans.eps' , 'w')
1199 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1204 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1205 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1207 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s'''
1209 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1210 quiet_system (cmd, 'gs')
1213 status = system (cmd)
1215 os.unlink (name + '.png')
1216 error ("Removing output file")
1218 def compile_all_files (chunks):
1225 if c[0] <> 'lilypond':
1234 if base + '.ly' not in tex:
1235 tex.append (base + '.ly')
1236 elif e == 'png' and g_do_pictures:
1242 # fixme: be sys-independent.
1244 if g_outdir and x[0] <> '/' :
1245 x = os.path.join (g_here_dir, x)
1248 incs = map (incl_opt, include_path)
1249 lilyopts = string.join (incs, ' ' )
1251 lilyopts = lilyopts + ' --dependencies '
1253 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1254 texfiles = string.join (tex, ' ')
1255 cmd = 'lilypond --header=texidoc %s %s %s' \
1256 % (lilyopts, g_extra_opts, texfiles)
1261 # Ugh, fixing up dependencies for .tex generation
1264 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1269 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1275 cmd = r"echo $TEXMF; tex '\nonstopmode \input %s'" % e
1276 quiet_system (cmd, 'TeX')
1278 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1279 quiet_system (cmd, 'dvips')
1287 def update_file (body, name):
1289 write the body if it has changed
1300 f = open (name , 'w')
1307 def getopt_args (opts):
1308 "Construct arguments (LONG, SHORT) for getopt from list of options."
1313 short = short + o[1]
1321 return (short, long)
1323 def option_help_str (o):
1324 "Transform one option description (4-tuple ) into neatly formatted string"
1342 return ' ' + sh + sep + long + arg
1345 def options_help_str (opts):
1346 "Convert a list of options into a neatly formatted string"
1352 s = option_help_str (o)
1353 strs.append ((s, o[3]))
1359 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1363 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1364 Generate hybrid LaTeX input from Latex + lilypond
1367 sys.stdout.write (options_help_str (option_definitions))
1368 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1372 Report bugs to bug-lilypond@gnu.org.
1374 Written by Tom Cato Amundsen <tca@gnu.org> and
1375 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1381 def write_deps (fn, target, chunks):
1383 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1384 f = open (os.path.join(g_outdir, fn), 'w')
1385 f.write ('%s%s: ' % (g_dep_prefix, target))
1386 for d in read_files:
1390 if c[0] == 'lilypond':
1391 (type, body, opts, todo, basename) = c;
1392 basenames.append (basename)
1395 d=g_outdir + '/' + d
1397 #if not os.isfile (d): # thinko?
1398 if not re.search ('/', d):
1399 d = g_dep_prefix + d
1400 f.write ('%s.tex ' % d)
1402 #if len (basenames):
1403 # for d in basenames:
1404 # f.write ('%s.ly ' % d)
1405 # f.write (' : %s' % target)
1410 def identify (stream):
1411 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1413 def print_version ():
1414 identify (sys.stdout)
1415 sys.stdout.write (r'''Copyright 1998--1999
1416 Distributed under terms of the GNU General Public License. It comes with
1421 def check_texidoc (chunks):
1424 if c[0] == 'lilypond':
1425 (type, body, opts, todo, basename) = c;
1426 pathbase = os.path.join (g_outdir, basename)
1427 if os.path.isfile (pathbase + '.texidoc'):
1428 body = '\n@include %s.texidoc\n' % basename + body
1429 c = (type, body, opts, todo, basename)
1434 ## what's this? Docme --hwn
1436 def fix_epswidth (chunks):
1439 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1440 newchunks.append (c)
1445 m = re.match ('magnification=([0-9.]+)', o)
1447 mag = string.atof (m.group (1))
1449 def replace_eps_dim (match, lmag = mag):
1450 filename = match.group (1)
1451 dims = bounding_box_dimensions (filename)
1453 return '%fpt' % (dims[0] *lmag)
1455 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1456 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1461 ##docme: why global?
1463 def do_file(input_filename):
1465 chunks = read_doc_file(input_filename)
1466 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1467 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1468 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1469 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1470 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1471 chunks = chop_chunks(chunks, 'numcols', do_columns)
1473 #for c in chunks: print "c:", c;
1475 scan_preamble(chunks)
1476 chunks = process_lilypond_blocks(chunks)
1479 if __main__.g_run_lilypond:
1480 compile_all_files (chunks)
1481 chunks = fix_epswidth (chunks)
1483 if __main__.format == 'texi':
1484 chunks = check_texidoc (chunks)
1487 chunks = completize_preamble (chunks)
1493 my_outname = outname
1494 elif input_filename == '-' or input_filename == "/dev/stdin":
1497 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1498 my_depname = my_outname + '.dep'
1500 if my_outname == '-' or my_outname == '/dev/stdout':
1503 __main__.do_deps = 0
1505 foutn = os.path.join (g_outdir, my_outname)
1506 sys.stderr.write ("Writing `%s'\n" % foutn)
1507 fout = open (foutn, 'w')
1514 write_deps (my_depname, foutn, chunks)
1519 (sh, long) = getopt_args (__main__.option_definitions)
1520 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1521 except getopt.error, msg:
1522 sys.stderr.write("error: %s" % msg)
1530 if o == '--include' or o == '-I':
1531 include_path.append (a)
1532 elif o == '--version' or o == '-v':
1535 elif o == '--verbose' or o == '-V':
1536 __main__.verbose_p = 1
1537 elif o == '--format' or o == '-f':
1539 elif o == '--outname' or o == '-o':
1542 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1545 elif o == '--help' or o == '-h':
1547 elif o == '--no-lily' or o == '-n':
1548 __main__.g_run_lilypond = 0
1549 elif o == '--dependencies' or o == '-M':
1551 elif o == '--default-music-fontsize':
1552 default_music_fontsize = string.atoi (a)
1553 elif o == '--default-lilypond-fontsize':
1554 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1555 default_music_fontsize = string.atoi (a)
1556 elif o == '--extra-options':
1558 elif o == '--force-music-fontsize':
1559 g_force_lilypond_fontsize = string.atoi(a)
1560 elif o == '--force-lilypond-fontsize':
1561 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1562 g_force_lilypond_fontsize = string.atoi(a)
1563 elif o == '--dep-prefix':
1565 elif o == '--no-pictures':
1567 elif o == '--no-music':
1569 elif o == '--read-lys':
1571 elif o == '--outdir':
1574 identify (sys.stderr)
1576 if os.path.isfile(g_outdir):
1577 error ("outdir is a file: %s" % g_outdir)
1578 if not os.path.exists(g_outdir):
1580 setup_environment ()
1581 for input_filename in files:
1582 do_file(input_filename)
1585 # Petr, ik zou willen dat ik iets zinvoller deed,
1586 # maar wat ik kan ik doen, het verandert toch niets?