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 # Try to cater for bad installations of LilyPond, that have
107 # broken TeX setup. Just hope this doesn't hurt good TeX
108 # setups. Maybe we should check if kpsewhich can find
109 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
111 'MFINPUTS' : datadir + '/mf:',
112 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
113 'TFMFONTS' : datadir + '/tfm:',
114 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
115 'GS_LIB' : datadir + '/ps',
118 # tex needs lots of memory, more than it gets by default on Debian
119 non_path_environment = {
120 'extra_mem_top' : '1000000',
121 'extra_mem_bottom' : '1000000',
122 'pool_size' : '250000',
125 def setup_environment ():
126 for key in environment.keys ():
127 val = environment[key]
128 if os.environ.has_key (key):
129 val = val + os.pathsep + os.environ[key]
130 os.environ[key] = val
132 for key in non_path_environment.keys ():
133 val = non_path_environment[key]
134 os.environ[key] = val
136 include_path = [os.getcwd()]
139 # g_ is for global (?)
141 g_here_dir = os.getcwd ()
144 g_force_lilypond_fontsize = 0
154 default_music_fontsize = 16
155 default_text_fontsize = 12
158 # this code is ugly. It should be cleaned
162 # the dimensions are from geometry.sty
163 'a0paper': (mm2pt(841), mm2pt(1189)),
164 'a1paper': (mm2pt(595), mm2pt(841)),
165 'a2paper': (mm2pt(420), mm2pt(595)),
166 'a3paper': (mm2pt(297), mm2pt(420)),
167 'a4paper': (mm2pt(210), mm2pt(297)),
168 'a5paper': (mm2pt(149), mm2pt(210)),
169 'b0paper': (mm2pt(1000), mm2pt(1414)),
170 'b1paper': (mm2pt(707), mm2pt(1000)),
171 'b2paper': (mm2pt(500), mm2pt(707)),
172 'b3paper': (mm2pt(353), mm2pt(500)),
173 'b4paper': (mm2pt(250), mm2pt(353)),
174 'b5paper': (mm2pt(176), mm2pt(250)),
175 'letterpaper': (in2pt(8.5), in2pt(11)),
176 'legalpaper': (in2pt(8.5), in2pt(14)),
177 'executivepaper': (in2pt(7.25), in2pt(10.5))}
178 self.m_use_geometry = None
179 self.m_papersize = 'letterpaper'
183 self.m_geo_landscape = 0
184 self.m_geo_width = None
185 self.m_geo_textwidth = None
186 self.m_geo_lmargin = None
187 self.m_geo_rmargin = None
188 self.m_geo_includemp = None
189 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
190 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
191 self.m_geo_x_marginparwidth = None
192 self.m_geo_x_marginparsep = None
194 def set_geo_option(self, name, value):
196 if type(value) == type([]):
197 value = map(conv_dimen_to_float, value)
199 value = conv_dimen_to_float(value)
201 if name == 'body' or name == 'text':
202 if type(value) == type([]):
203 self.m_geo_textwidth = value[0]
205 self.m_geo_textwidth = value
207 elif name == 'portrait':
208 self.m_geo_landscape = 0
209 elif name == 'reversemp' or name == 'reversemarginpar':
210 if self.m_geo_includemp == None:
211 self.m_geo_includemp = 1
212 elif name == 'marginparwidth' or name == 'marginpar':
213 self.m_geo_x_marginparwidth = value
214 self.m_geo_includemp = 1
215 elif name == 'marginparsep':
216 self.m_geo_x_marginparsep = value
217 self.m_geo_includemp = 1
218 elif name == 'scale':
219 if type(value) == type([]):
220 self.m_geo_width = self.get_paperwidth() * value[0]
222 self.m_geo_width = self.get_paperwidth() * value
223 elif name == 'hscale':
224 self.m_geo_width = self.get_paperwidth() * value
225 elif name == 'left' or name == 'lmargin':
226 self.m_geo_lmargin = value
227 elif name == 'right' or name == 'rmargin':
228 self.m_geo_rmargin = value
229 elif name == 'hdivide' or name == 'divide':
230 if value[0] not in ('*', ''):
231 self.m_geo_lmargin = value[0]
232 if value[1] not in ('*', ''):
233 self.m_geo_width = value[1]
234 if value[2] not in ('*', ''):
235 self.m_geo_rmargin = value[2]
236 elif name == 'hmargin':
237 if type(value) == type([]):
238 self.m_geo_lmargin = value[0]
239 self.m_geo_rmargin = value[1]
241 self.m_geo_lmargin = value
242 self.m_geo_rmargin = value
243 elif name == 'margin':#ugh there is a bug about this option in
244 # the geometry documentation
245 if type(value) == type([]):
246 self.m_geo_lmargin = value[0]
247 self.m_geo_rmargin = value[0]
249 self.m_geo_lmargin = value
250 self.m_geo_rmargin = value
251 elif name == 'total':
252 if type(value) == type([]):
253 self.m_geo_width = value[0]
255 self.m_geo_width = value
256 elif name == 'width' or name == 'totalwidth':
257 self.m_geo_width = value
258 elif name == 'paper' or name == 'papername':
259 self.m_papersize = value
260 elif name[-5:] == 'paper':
261 self.m_papersize = name
265 def __setattr__(self, name, value):
266 if type(value) == type("") and \
267 dimension_conversion_dict.has_key (value[-2:]):
268 f = dimension_conversion_dict[value[-2:]]
269 self.__dict__[name] = f(float(value[:-2]))
271 self.__dict__[name] = value
274 s = "LatexPaper:\n-----------"
275 for v in self.__dict__.keys():
277 s = s + str (v) + ' ' + str (self.__dict__[v])
278 s = s + "-----------"
281 def get_linewidth(self):
282 w = self._calc_linewidth()
283 if self.m_num_cols == 2:
287 def get_paperwidth(self):
288 #if self.m_use_geometry:
289 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
290 #return self.m_paperdef[self.m_papersize][self.m_landscape]
292 def _calc_linewidth(self):
293 # since geometry sometimes ignores 'includemp', this is
294 # more complicated than it should be
296 if self.m_geo_includemp:
297 if self.m_geo_x_marginparsep is not None:
298 mp = mp + self.m_geo_x_marginparsep
300 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
301 if self.m_geo_x_marginparwidth is not None:
302 mp = mp + self.m_geo_x_marginparwidth
304 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
306 #ugh test if this is necessary
310 if not self.m_use_geometry:
311 return latex_linewidths[self.m_papersize][self.m_fontsize]
313 geo_opts = (self.m_geo_lmargin == None,
314 self.m_geo_width == None,
315 self.m_geo_rmargin == None)
317 if geo_opts == (1, 1, 1):
318 if self.m_geo_textwidth:
319 return self.m_geo_textwidth
320 w = self.get_paperwidth() * 0.8
322 elif geo_opts == (0, 1, 1):
323 if self.m_geo_textwidth:
324 return self.m_geo_textwidth
325 return self.f1(self.m_geo_lmargin, mp)
326 elif geo_opts == (1, 1, 0):
327 if self.m_geo_textwidth:
328 return self.m_geo_textwidth
329 return self.f1(self.m_geo_rmargin, mp)
331 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
332 if self.m_geo_textwidth:
333 return self.m_geo_textwidth
334 return self.m_geo_width - mp
335 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
336 w = self.get_paperwidth() \
337 - self.m_geo_lmargin - self.m_geo_rmargin - mp
341 raise "Never do this!"
343 tmp = self.get_paperwidth() - m * 2 - mp
348 tmp = self.get_paperwidth() - self.m_geo_lmargin \
356 self.m_papersize = 'letterpaper'
358 def get_linewidth(self):
359 return html_linewidths[self.m_papersize][self.m_fontsize]
363 self.m_papersize = 'letterpaper'
365 def get_linewidth(self):
366 return texi_linewidths[self.m_papersize][self.m_fontsize]
372 def em2pt(x, fontsize = 10):
373 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
374 def ex2pt(x, fontsize = 10):
375 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
380 dimension_conversion_dict ={
382 'cm': lambda x: mm2pt(10*x),
389 # Convert numeric values, with or without specific dimension, to floats.
391 def conv_dimen_to_float(value):
392 if type(value) == type(""):
393 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
396 num = string.atof(m.group (1))
397 conv = dimension_conversion_dict[m.group(2)]
401 elif re.match ("^[0-9.]+$",value):
408 # indices are no. of columns, papersize, fontsize
409 # Why can't this be calculated?
411 'a4paper':{10: 345, 11: 360, 12: 390},
412 'a4paper-landscape': {10: 598, 11: 596, 12:592},
413 'a5paper':{10: 276, 11: 276, 12: 276},
414 'b5paper':{10: 345, 11: 356, 12: 356},
415 'letterpaper':{10: 345, 11: 360, 12: 390},
416 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
417 'legalpaper': {10: 345, 11: 360, 12: 390},
418 'executivepaper':{10: 345, 11: 360, 12: 379}}
421 'afourpaper': {12: mm2pt(160)},
422 'afourwide': {12: in2pt(6.5)},
423 'afourlatex': {12: mm2pt(150)},
424 'smallbook': {12: in2pt(5)},
425 'letterpaper': {12: in2pt(6)}}
428 'afourpaper': {12: mm2pt(160)},
429 'afourwide': {12: in2pt(6.5)},
430 'afourlatex': {12: mm2pt(150)},
431 'smallbook': {12: in2pt(5)},
432 'letterpaper': {12: in2pt(6)}}
434 option_definitions = [
435 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
436 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
437 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
438 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
439 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
440 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
441 ('', 'h', 'help', 'this help'),
442 ('DIR', 'I', 'include', 'include path'),
443 ('', 'M', 'dependencies', 'write dependencies'),
444 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
445 ('', 'n', 'no-lily', 'don\'t run lilypond'),
446 ('', '', 'no-pictures', "don\'t generate pictures"),
447 ('', '', 'no-music', "strip all lilypond blocks from output"),
448 ('', '', 'read-lys', "don't write ly files."),
449 ('FILE', 'o', 'outname', 'filename main output file'),
450 ('FILE', '', 'outdir', "where to place generated files"),
451 ('', 'V', 'verbose', 'verbose' ),
452 ('', 'v', 'version', 'print version information' ),
455 # format specific strings, ie. regex-es for input, and % strings for output
457 'html' : {'output-lilypond': '''<lilypond%s>
460 'output-filename' : r'''
463 'output-lilypond-fragment': '''<lilypond%s>
464 \context Staff\context Voice{ %s }
466 'output-noinline': r'''
467 <!-- generated: %(fn)s.png !-->
471 'output-verbatim': r'''<pre>
474 ## Ugh we need to differentiate on origin:
475 ## lilypond-block origin wants an extra <p>, but
476 ## inline music doesn't.
477 ## possibly other center options?
479 <a href="%(fn)s.png">
480 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
484 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
491 'output-filename' : r'''
494 'output-lilypond': r'''\begin[%s]{lilypond}
498 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
500 'output-default-post': "\\def\postLilypondExample{}\n",
501 'output-default-pre': "\\def\preLilypondExample{}\n",
502 'usepackage-graphics': '\\usepackage{graphics}\n',
503 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
504 'output-noinline': r'''
505 %% generated: %(fn)s.eps
507 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
508 'pagebreak': r'\pagebreak',
511 'texi' : {'output-lilypond': '''@lilypond[%s]
515 'output-filename' : r'''
518 'output-lilypond-fragment': '''@lilypond[%s]
519 \context Staff\context Voice{ %s }
521 'output-noinline': r'''
522 @c generated: %(fn)s.png
525 'output-verbatim': r'''@example
530 # do some tweaking: @ is needed in some ps stuff.
531 # override EndLilyPondOutput, since @tex is done
532 # in a sandbox, you can't do \input lilyponddefs at the
533 # top of the document.
535 # should also support fragment in
537 # ugh, the <p> below breaks inline images...
543 \def\EndLilyPondOutput{}
549 <a href="%(fn)s.png">
550 <img border=0 src="%(fn)s.png" alt="[picture of music]">
558 def output_verbatim (body):
559 if __main__.format == 'html':
560 body = re.sub ('&', '&', body)
561 body = re.sub ('>', '>', body)
562 body = re.sub ('<', '<', body)
563 elif __main__.format == 'texi':
564 body = re.sub ('([@{}])', '@\\1', body)
565 return get_output ('output-verbatim') % body
568 #warning: this uses extended regular expressions. Tread with care.
570 # legenda (?P name parameter
571 # *? match non-greedily.
578 'preamble-end': no_match,
579 'landscape': no_match,
580 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
581 'verb': r'''(?P<code><pre>.*?</pre>)''',
582 'lilypond-file': '(?m)(?P<match><lilypondfile(?P<options>[^>]*)?>\s*(?P<filename>.*?)\s*</lilypondfile>)',
583 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
584 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]*)?>(?P<code>.*?)</lilypond>)''',
585 'option-sep' : '\s*',
586 'intertext': r',?\s*intertext=\".*?\"',
587 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
588 'singleline-comment': no_match,
592 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
593 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
594 'option-sep' : ',\s*',
595 'header': r"\\documentclass\s*(\[.*?\])?",
596 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
597 'preamble-end': r'(?P<code>\\begin{document})',
598 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
599 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
600 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
601 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
602 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
603 'def-post-re': r"\\def\\postLilypondExample",
604 'def-pre-re': r"\\def\\preLilypondExample",
605 'usepackage-graphics': r"\usepackage{graphics}",
606 'intertext': r',?\s*intertext=\".*?\"',
607 'multiline-comment': no_match,
608 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
609 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
613 # why do we have distinction between @mbinclude and @include?
617 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
620 'preamble-end': no_match,
621 'landscape': no_match,
622 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
623 'verb': r'''(?P<code>@code{.*?})''',
624 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
625 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
626 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
627 'option-sep' : ',\s*',
628 'intertext': r',?\s*intertext=\".*?\"',
629 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
630 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
636 for r in re_dict.keys ():
639 for k in olddict.keys ():
641 newdict[k] = re.compile (olddict[k])
643 print 'invalid regexp: %s' % olddict[k]
645 # we'd like to catch and reraise a more detailed error, but
646 # alas, the exceptions changed across the 1.5/2.1 boundary.
661 def get_output (name):
662 return output_dict[format][name]
665 return re_dict[format][name]
667 def bounding_box_dimensions(fname):
669 fname = os.path.join(g_outdir, fname)
673 error ("Error opening `%s'" % fname)
675 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
678 gs = map (lambda x: string.atoi (x), s.groups ())
679 return (int (gs[2] - gs[0] + 0.5),
680 int (gs[3] - gs[1] + 0.5))
685 sys.stderr.write (str + "\n Exiting ... \n\n")
689 def compose_full_body (body, opts):
690 '''Construct the lilypond code to send to Lilypond.
691 Add stuff to BODY using OPTS as options.'''
692 music_size = default_music_fontsize
693 latex_size = default_text_fontsize
697 if g_force_lilypond_fontsize:
698 music_size = g_force_lilypond_fontsize
700 m = re.match ('([0-9]+)pt', o)
702 music_size = string.atoi(m.group (1))
704 m = re.match ('latexfontsize=([0-9]+)pt', o)
706 latex_size = string.atoi (m.group (1))
708 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
710 f = float (m.group (1))
711 indent = 'indent = %f\\%s' % (f, m.group (2))
713 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
715 f = float (m.group (1))
716 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
718 if re.search ('\\\\score', body):
722 if 'fragment' in opts:
724 if 'nofragment' in opts:
727 if is_fragment and not 'multiline' in opts:
728 opts.append('singleline')
730 if 'singleline' in opts:
731 linewidth = 'linewidth = -1.0'
733 l = __main__.paperguru.get_linewidth ()
734 linewidth = 'linewidth = %f\pt' % l
736 if 'noindent' in opts:
737 indent = 'indent = 0.0\mm'
740 m= re.search ('relative(.*)', o)
744 v = string.atoi (m.group (1))
751 pitch = pitch + '\,' * v
753 pitch = pitch + '\'' * v
755 body = '\\relative %s { %s }' %(pitch, body)
764 optstring = string.join (opts, ' ')
765 optstring = re.sub ('\n', ' ', optstring)
767 %% Generated automatically by: lilypond-book.py
769 \include "paper%d.ly"
774 ''' % (optstring, music_size, linewidth, indent) + body
776 # ughUGH not original options
779 def parse_options_string(s):
781 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
782 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
783 r3 = re.compile("(\w+?)((,\s*)|$)")
788 d[m.group(2)] = re.split(",\s*", m.group(3))
793 d[m.group(2)] = m.group(3)
801 error ("format of option string invalid (was `%')" % s)
804 def scan_html_preamble (chunks):
807 def scan_latex_preamble(chunks):
808 # first we want to scan the \documentclass line
809 # it should be the first non-comment line
812 if chunks[idx][0] == 'ignore':
815 m = get_re ('header').match(chunks[idx][1])
816 if m <> None and m.group (1):
817 options = re.split (',[\n \t]*', m.group(1)[1:-1])
822 paperguru.m_landscape = 1
823 m = re.match("(.*?)paper", o)
825 paperguru.m_papersize = m.group()
827 m = re.match("(\d\d)pt", o)
829 paperguru.m_fontsize = int(m.group(1))
832 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
833 if chunks[idx] == 'ignore':
836 m = get_re ('geometry').search(chunks[idx][1])
838 paperguru.m_use_geometry = 1
839 o = parse_options_string(m.group('options'))
841 paperguru.set_geo_option(k, o[k])
844 def scan_texi_preamble (chunks):
845 # this is not bulletproof..., it checks the first 10 chunks
846 for c in chunks[:10]:
848 for s in ('afourpaper', 'afourwide', 'letterpaper',
849 'afourlatex', 'smallbook'):
850 if string.find(c[1], "@%s" % s) != -1:
851 paperguru.m_papersize = s
854 def scan_preamble (chunks):
855 if __main__.format == 'html':
856 scan_html_preamble (chunks)
857 elif __main__.format == 'latex':
858 scan_latex_preamble (chunks)
859 elif __main__.format == 'texi':
860 scan_texi_preamble (chunks)
863 def completize_preamble (chunks):
864 if __main__.format != 'latex':
866 pre_b = post_b = graphics_b = None
868 if chunk[0] == 'preamble-end':
870 if chunk[0] == 'input':
871 m = get_re('def-pre-re').search(chunk[1])
874 if chunk[0] == 'input':
875 m = get_re('def-post-re').search(chunk[1])
879 if chunk[0] == 'input':
880 m = get_re('usepackage-graphics').search(chunk[1])
884 while x < len (chunks) and chunks[x][0] != 'preamble-end':
891 chunks.insert(x, ('input', get_output ('output-default-pre')))
893 chunks.insert(x, ('input', get_output ('output-default-post')))
895 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
901 def find_file (name):
903 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
907 return (sys.stdin.read (), '<stdin>')
910 for a in include_path:
912 nm = os.path.join (a, name)
914 __main__.read_files.append (nm)
919 sys.stderr.write ("Reading `%s'\n" % nm)
920 return (f.read (), nm)
922 error ("File not found `%s'\n" % name)
925 def do_ignore(match_object):
926 return [('ignore', match_object.group('code'))]
927 def do_preamble_end(match_object):
928 return [('preamble-end', match_object.group('code'))]
930 def make_verbatim(match_object):
931 return [('verbatim', match_object.group('code'))]
933 def make_verb(match_object):
934 return [('verb', match_object.group('code'))]
936 def do_include_file(m):
938 return [('input', get_output ('pagebreak'))] \
939 + read_doc_file(m.group('filename')) \
940 + [('input', get_output ('pagebreak'))]
942 def do_input_file(m):
943 return read_doc_file(m.group('filename'))
945 def make_lilypond(m):
946 if m.group('options'):
947 options = m.group('options')
950 return [('input', get_output('output-lilypond-fragment') %
951 (options, m.group('code')))]
953 def make_lilypond_file(m):
956 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
957 into a @lilypond .. @end lilypond block.
961 if m.group('options'):
962 options = m.group('options')
965 (content, nm) = find_file(m.group('filename'))
966 options = "filename=%s," % nm + options
968 return [('input', get_output('output-lilypond') %
971 def make_lilypond_block(m):
975 if m.group('options'):
976 options = get_re('option-sep').split (m.group('options'))
979 options = filter(lambda s: s != '', options)
980 return [('lilypond', m.group('code'), options)]
983 if __main__.format != 'latex':
985 if m.group('num') == 'one':
986 return [('numcols', m.group('code'), 1)]
987 if m.group('num') == 'two':
988 return [('numcols', m.group('code'), 2)]
990 def chop_chunks(chunks, re_name, func, use_match=0):
996 m = get_re (re_name).search (str)
998 newchunks.append (('input', str))
1002 newchunks.append (('input', str[:m.start ('match')]))
1004 newchunks.append (('input', str[:m.start (0)]))
1005 #newchunks.extend(func(m))
1006 # python 1.5 compatible:
1007 newchunks = newchunks + func(m)
1008 str = str [m.end(0):]
1013 def determine_format (str):
1014 if __main__.format == '':
1016 html = re.search ('(?i)<[dh]tml', str[:200])
1017 latex = re.search (r'''\\document''', str[:200])
1018 texi = re.search ('@node|@setfilename', str[:200])
1023 if html and not latex and not texi:
1025 elif latex and not html and not texi:
1027 elif texi and not html and not latex:
1030 error ("can't determine format, please specify")
1033 if __main__.paperguru == None:
1034 if __main__.format == 'html':
1036 elif __main__.format == 'latex':
1038 elif __main__.format == 'texi':
1041 __main__.paperguru = g
1044 def read_doc_file (filename):
1045 '''Read the input file, find verbatim chunks and do \input and \include
1047 (str, path) = find_file(filename)
1048 determine_format (str)
1050 chunks = [('input', str)]
1052 # we have to check for verbatim before doing include,
1053 # because we don't want to include files that are mentioned
1054 # inside a verbatim environment
1055 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1056 chunks = chop_chunks(chunks, 'verb', make_verb)
1057 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1059 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1060 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1064 taken_file_names = {}
1065 def schedule_lilypond_block (chunk):
1066 '''Take the body and options from CHUNK, figure out how the
1067 real .ly should look, and what should be left MAIN_STR (meant
1068 for the main file). The .ly is written, and scheduled in
1071 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1073 TODO has format [basename, extension, extension, ... ]
1076 (type, body, opts) = chunk
1077 assert type == 'lilypond'
1078 file_body = compose_full_body (body, opts)
1079 ## Hmm, we should hash only lilypond source, and skip the
1082 basename = 'lily-' + `abs(hash (file_body))`
1084 m = re.search ('filename="(.*?)"', o)
1086 basename = m.group (1)
1087 if not taken_file_names.has_key(basename):
1088 taken_file_names[basename] = 0
1090 taken_file_names[basename] = taken_file_names[basename] + 1
1091 basename = basename + "-%i" % taken_file_names[basename]
1093 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1094 needed_filetypes = ['tex']
1096 if format == 'html' or format == 'texi':
1097 needed_filetypes.append ('eps')
1098 needed_filetypes.append ('png')
1099 if 'eps' in opts and not ('eps' in needed_filetypes):
1100 needed_filetypes.append('eps')
1101 pathbase = os.path.join (g_outdir, basename)
1102 def f (base, ext1, ext2):
1103 a = os.path.isfile(base + ext2)
1104 if (os.path.isfile(base + ext1) and
1105 os.path.isfile(base + ext2) and
1106 os.stat(base+ext1)[stat.ST_MTIME] >
1107 os.stat(base+ext2)[stat.ST_MTIME]) or \
1108 not os.path.isfile(base + ext2):
1111 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1113 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1115 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1119 if 'printfilename' in opts:
1121 m= re.match ("filename=(.*)", o)
1123 newbody = newbody + get_output ("output-filename") % m.group(1)
1127 if 'verbatim' in opts:
1128 newbody = output_verbatim (body)
1131 m = re.search ('intertext="(.*?)"', o)
1133 newbody = newbody + m.group (1) + "\n\n"
1135 if 'noinline' in opts:
1136 s = 'output-noinline'
1137 elif format == 'latex':
1142 else: # format == 'html' or format == 'texi':
1144 newbody = newbody + get_output (s) % {'fn': basename }
1145 return ('lilypond', newbody, opts, todo, basename)
1147 def process_lilypond_blocks(chunks):#ugh rename
1149 # Count sections/chapters.
1151 if c[0] == 'lilypond':
1152 c = schedule_lilypond_block (c)
1153 elif c[0] == 'numcols':
1154 paperguru.m_num_cols = c[2]
1155 newchunks.append (c)
1161 sys.stderr.write ("invoking `%s'\n" % cmd)
1162 st = os.system (cmd)
1164 error ('Error command exited with value %d\n' % st)
1167 def quiet_system (cmd, name):
1169 progress ( _("Running %s...") % name)
1170 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1174 def get_bbox (filename):
1175 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1177 box = open (filename + '.bbox').read()
1178 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1181 gr = map (string.atoi, m.groups ())
1185 def make_pixmap (name):
1186 bbox = get_bbox (name + '.eps')
1188 fo = open (name + '.trans.eps' , 'w')
1189 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1194 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1195 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1197 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s'''
1199 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1200 quiet_system (cmd, 'gs')
1203 status = system (cmd)
1205 os.unlink (name + '.png')
1206 error ("Removing output file")
1208 def compile_all_files (chunks):
1215 if c[0] <> 'lilypond':
1224 if base + '.ly' not in tex:
1225 tex.append (base + '.ly')
1226 elif e == 'png' and g_do_pictures:
1232 # fixme: be sys-independent.
1234 if g_outdir and x[0] <> '/' :
1235 x = os.path.join (g_here_dir, x)
1238 incs = map (incl_opt, include_path)
1239 lilyopts = string.join (incs, ' ' )
1241 lilyopts = lilyopts + ' --dependencies '
1243 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1244 texfiles = string.join (tex, ' ')
1245 cmd = 'lilypond --header=texidoc %s %s %s' \
1246 % (lilyopts, g_extra_opts, texfiles)
1247 quiet_system (cmd, 'LilyPond')
1250 # Ugh, fixing up dependencies for .tex generation
1253 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1258 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1264 cmd = r"tex '\nonstopmode \input %s'" % e
1265 quiet_system (cmd, 'TeX')
1267 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1268 quiet_system (cmd, 'dvips')
1276 def update_file (body, name):
1278 write the body if it has changed
1289 f = open (name , 'w')
1296 def getopt_args (opts):
1297 "Construct arguments (LONG, SHORT) for getopt from list of options."
1302 short = short + o[1]
1310 return (short, long)
1312 def option_help_str (o):
1313 "Transform one option description (4-tuple ) into neatly formatted string"
1331 return ' ' + sh + sep + long + arg
1334 def options_help_str (opts):
1335 "Convert a list of options into a neatly formatted string"
1341 s = option_help_str (o)
1342 strs.append ((s, o[3]))
1348 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1352 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1353 Generate hybrid LaTeX input from Latex + lilypond
1356 sys.stdout.write (options_help_str (option_definitions))
1357 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1361 Report bugs to bug-lilypond@gnu.org.
1363 Written by Tom Cato Amundsen <tca@gnu.org> and
1364 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1370 def write_deps (fn, target, chunks):
1372 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1373 f = open (os.path.join(g_outdir, fn), 'w')
1374 f.write ('%s%s: ' % (g_dep_prefix, target))
1375 for d in read_files:
1379 if c[0] == 'lilypond':
1380 (type, body, opts, todo, basename) = c;
1381 basenames.append (basename)
1384 d=g_outdir + '/' + d
1386 #if not os.isfile (d): # thinko?
1387 if not re.search ('/', d):
1388 d = g_dep_prefix + d
1389 f.write ('%s.tex ' % d)
1391 #if len (basenames):
1392 # for d in basenames:
1393 # f.write ('%s.ly ' % d)
1394 # f.write (' : %s' % target)
1399 def identify (stream):
1400 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1402 def print_version ():
1403 identify (sys.stdout)
1404 sys.stdout.write (r'''Copyright 1998--1999
1405 Distributed under terms of the GNU General Public License. It comes with
1410 def check_texidoc (chunks):
1413 if c[0] == 'lilypond':
1414 (type, body, opts, todo, basename) = c;
1415 pathbase = os.path.join (g_outdir, basename)
1416 if os.path.isfile (pathbase + '.texidoc'):
1417 body = '\n@include %s.texidoc\n' % basename + body
1418 c = (type, body, opts, todo, basename)
1423 ## what's this? Docme --hwn
1425 def fix_epswidth (chunks):
1428 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1429 newchunks.append (c)
1434 m = re.match ('magnification=([0-9.]+)', o)
1436 mag = string.atof (m.group (1))
1438 def replace_eps_dim (match, lmag = mag):
1439 filename = match.group (1)
1440 dims = bounding_box_dimensions (filename)
1442 return '%fpt' % (dims[0] *lmag)
1444 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1445 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1450 ##docme: why global?
1452 def do_file(input_filename):
1456 my_outname = outname
1457 elif input_filename == '-' or input_filename == "/dev/stdin":
1460 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1461 my_depname = my_outname + '.dep'
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)
1486 if my_outname == '-' or my_outname == '/dev/stdout':
1489 __main__.do_deps = 0
1491 foutn = os.path.join (g_outdir, my_outname)
1492 sys.stderr.write ("Writing `%s'\n" % foutn)
1493 fout = open (foutn, 'w')
1500 write_deps (my_depname, foutn, chunks)
1505 (sh, long) = getopt_args (__main__.option_definitions)
1506 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1507 except getopt.error, msg:
1508 sys.stderr.write("error: %s" % msg)
1516 if o == '--include' or o == '-I':
1517 include_path.append (a)
1518 elif o == '--version' or o == '-v':
1521 elif o == '--verbose' or o == '-V':
1522 __main__.verbose_p = 1
1523 elif o == '--format' or o == '-f':
1525 elif o == '--outname' or o == '-o':
1528 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1531 elif o == '--help' or o == '-h':
1533 elif o == '--no-lily' or o == '-n':
1534 __main__.g_run_lilypond = 0
1535 elif o == '--dependencies' or o == '-M':
1537 elif o == '--default-music-fontsize':
1538 default_music_fontsize = string.atoi (a)
1539 elif o == '--default-lilypond-fontsize':
1540 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1541 default_music_fontsize = string.atoi (a)
1542 elif o == '--extra-options':
1544 elif o == '--force-music-fontsize':
1545 g_force_lilypond_fontsize = string.atoi(a)
1546 elif o == '--force-lilypond-fontsize':
1547 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1548 g_force_lilypond_fontsize = string.atoi(a)
1549 elif o == '--dep-prefix':
1551 elif o == '--no-pictures':
1553 elif o == '--no-music':
1555 elif o == '--read-lys':
1557 elif o == '--outdir':
1560 identify (sys.stderr)
1562 if os.path.isfile(g_outdir):
1563 error ("outdir is a file: %s" % g_outdir)
1564 if not os.path.exists(g_outdir):
1566 setup_environment ()
1567 for input_filename in files:
1568 do_file(input_filename)
1571 # Petr, ik zou willen dat ik iets zinvoller deed,
1572 # maar wat ik kan ik doen, het verandert toch niets?