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 pre instead. Fix by Mats.
53 # todo: should check Python version first.
61 # Attempt to fix problems with limited stack size set by Python!
62 # Sets unlimited stack size. Note that the resource module only
63 # is available on UNIX.
66 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
70 errorport = sys.stderr
77 gettext.bindtextdomain ('lilypond', localedir)
78 gettext.textdomain ('lilypond')
85 errorport.write (s + '\n')
88 program_version = '@TOPLEVEL_VERSION@'
89 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
90 program_version = '1.5.53'
92 # if set, LILYPONDPREFIX must take prevalence
93 # if datadir is not set, we're doing a build and LILYPONDPREFIX
96 if os.environ.has_key ('LILYPONDPREFIX') :
97 datadir = os.environ['LILYPONDPREFIX']
101 while datadir[-1] == os.sep:
102 datadir= datadir[:-1]
104 # Try to cater for bad installations of LilyPond, that have
105 # broken TeX setup. Just hope this doesn't hurt good TeX
106 # setups. Maybe we should check if kpsewhich can find
107 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
109 'MFINPUTS' : datadir + '/mf:',
110 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
111 'TFMFONTS' : datadir + '/tfm:',
112 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
113 'GS_LIB' : datadir + '/ps',
116 # tex needs lots of memory, more than it gets by default on Debian
117 non_path_environment = {
118 'extra_mem_top' : '1000000',
119 'extra_mem_bottom' : '1000000',
120 'pool_size' : '250000',
123 def setup_environment ():
124 for key in environment.keys ():
125 val = environment[key]
126 if os.environ.has_key (key):
127 val = val + os.pathsep + os.environ[key]
128 os.environ[key] = val
130 for key in non_path_environment.keys ():
131 val = non_path_environment[key]
132 os.environ[key] = val
134 include_path = [os.getcwd()]
137 # g_ is for global (?)
139 g_here_dir = os.getcwd ()
142 g_force_lilypond_fontsize = 0
152 default_music_fontsize = 16
153 default_text_fontsize = 12
156 # this code is ugly. It should be cleaned
160 # the dimensions are from geometry.sty
161 'a0paper': (mm2pt(841), mm2pt(1189)),
162 'a1paper': (mm2pt(595), mm2pt(841)),
163 'a2paper': (mm2pt(420), mm2pt(595)),
164 'a3paper': (mm2pt(297), mm2pt(420)),
165 'a4paper': (mm2pt(210), mm2pt(297)),
166 'a5paper': (mm2pt(149), mm2pt(210)),
167 'b0paper': (mm2pt(1000), mm2pt(1414)),
168 'b1paper': (mm2pt(707), mm2pt(1000)),
169 'b2paper': (mm2pt(500), mm2pt(707)),
170 'b3paper': (mm2pt(353), mm2pt(500)),
171 'b4paper': (mm2pt(250), mm2pt(353)),
172 'b5paper': (mm2pt(176), mm2pt(250)),
173 'letterpaper': (in2pt(8.5), in2pt(11)),
174 'legalpaper': (in2pt(8.5), in2pt(14)),
175 'executivepaper': (in2pt(7.25), in2pt(10.5))}
176 self.m_use_geometry = None
177 self.m_papersize = 'letterpaper'
181 self.m_geo_landscape = 0
182 self.m_geo_width = None
183 self.m_geo_textwidth = None
184 self.m_geo_lmargin = None
185 self.m_geo_rmargin = None
186 self.m_geo_includemp = None
187 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
188 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
189 self.m_geo_x_marginparwidth = None
190 self.m_geo_x_marginparsep = None
192 def set_geo_option(self, name, value):
194 if type(value) == type([]):
195 value = map(conv_dimen_to_float, value)
197 value = conv_dimen_to_float(value)
199 if name == 'body' or name == 'text':
200 if type(value) == type([]):
201 self.m_geo_textwidth = value[0]
203 self.m_geo_textwidth = value
205 elif name == 'portrait':
206 self.m_geo_landscape = 0
207 elif name == 'reversemp' or name == 'reversemarginpar':
208 if self.m_geo_includemp == None:
209 self.m_geo_includemp = 1
210 elif name == 'marginparwidth' or name == 'marginpar':
211 self.m_geo_x_marginparwidth = value
212 self.m_geo_includemp = 1
213 elif name == 'marginparsep':
214 self.m_geo_x_marginparsep = value
215 self.m_geo_includemp = 1
216 elif name == 'scale':
217 if type(value) == type([]):
218 self.m_geo_width = self.get_paperwidth() * value[0]
220 self.m_geo_width = self.get_paperwidth() * value
221 elif name == 'hscale':
222 self.m_geo_width = self.get_paperwidth() * value
223 elif name == 'left' or name == 'lmargin':
224 self.m_geo_lmargin = value
225 elif name == 'right' or name == 'rmargin':
226 self.m_geo_rmargin = value
227 elif name == 'hdivide' or name == 'divide':
228 if value[0] not in ('*', ''):
229 self.m_geo_lmargin = value[0]
230 if value[1] not in ('*', ''):
231 self.m_geo_width = value[1]
232 if value[2] not in ('*', ''):
233 self.m_geo_rmargin = value[2]
234 elif name == 'hmargin':
235 if type(value) == type([]):
236 self.m_geo_lmargin = value[0]
237 self.m_geo_rmargin = value[1]
239 self.m_geo_lmargin = value
240 self.m_geo_rmargin = value
241 elif name == 'margin':#ugh there is a bug about this option in
242 # the geometry documentation
243 if type(value) == type([]):
244 self.m_geo_lmargin = value[0]
245 self.m_geo_rmargin = value[0]
247 self.m_geo_lmargin = value
248 self.m_geo_rmargin = value
249 elif name == 'total':
250 if type(value) == type([]):
251 self.m_geo_width = value[0]
253 self.m_geo_width = value
254 elif name == 'width' or name == 'totalwidth':
255 self.m_geo_width = value
256 elif name == 'paper' or name == 'papername':
257 self.m_papersize = value
258 elif name[-5:] == 'paper':
259 self.m_papersize = name
263 def __setattr__(self, name, value):
264 if type(value) == type("") and \
265 dimension_conversion_dict.has_key (value[-2:]):
266 f = dimension_conversion_dict[value[-2:]]
267 self.__dict__[name] = f(float(value[:-2]))
269 self.__dict__[name] = value
272 s = "LatexPaper:\n-----------"
273 for v in self.__dict__.keys():
275 s = s + str (v) + ' ' + str (self.__dict__[v])
276 s = s + "-----------"
279 def get_linewidth(self):
280 w = self._calc_linewidth()
281 if self.m_num_cols == 2:
285 def get_paperwidth(self):
286 #if self.m_use_geometry:
287 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
288 #return self.m_paperdef[self.m_papersize][self.m_landscape]
290 def _calc_linewidth(self):
291 # since geometry sometimes ignores 'includemp', this is
292 # more complicated than it should be
294 if self.m_geo_includemp:
295 if self.m_geo_x_marginparsep is not None:
296 mp = mp + self.m_geo_x_marginparsep
298 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
299 if self.m_geo_x_marginparwidth is not None:
300 mp = mp + self.m_geo_x_marginparwidth
302 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
304 #ugh test if this is necessary
308 if not self.m_use_geometry:
309 return latex_linewidths[self.m_papersize][self.m_fontsize]
311 geo_opts = (self.m_geo_lmargin == None,
312 self.m_geo_width == None,
313 self.m_geo_rmargin == None)
315 if geo_opts == (1, 1, 1):
316 if self.m_geo_textwidth:
317 return self.m_geo_textwidth
318 w = self.get_paperwidth() * 0.8
320 elif geo_opts == (0, 1, 1):
321 if self.m_geo_textwidth:
322 return self.m_geo_textwidth
323 return self.f1(self.m_geo_lmargin, mp)
324 elif geo_opts == (1, 1, 0):
325 if self.m_geo_textwidth:
326 return self.m_geo_textwidth
327 return self.f1(self.m_geo_rmargin, mp)
329 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
330 if self.m_geo_textwidth:
331 return self.m_geo_textwidth
332 return self.m_geo_width - mp
333 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
334 w = self.get_paperwidth() \
335 - self.m_geo_lmargin - self.m_geo_rmargin - mp
339 raise "Never do this!"
341 tmp = self.get_paperwidth() - m * 2 - mp
346 tmp = self.get_paperwidth() - self.m_geo_lmargin \
354 self.m_papersize = 'letterpaper'
356 def get_linewidth(self):
357 return html_linewidths[self.m_papersize][self.m_fontsize]
361 self.m_papersize = 'letterpaper'
363 def get_linewidth(self):
364 return texi_linewidths[self.m_papersize][self.m_fontsize]
370 def em2pt(x, fontsize = 10):
371 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
372 def ex2pt(x, fontsize = 10):
373 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
378 dimension_conversion_dict ={
380 'cm': lambda x: mm2pt(10*x),
387 # Convert numeric values, with or without specific dimension, to floats.
389 def conv_dimen_to_float(value):
390 if type(value) == type(""):
391 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
394 num = string.atof(m.group (1))
395 conv = dimension_conversion_dict[m.group(2)]
399 elif re.match ("^[0-9.]+$",value):
406 # indices are no. of columns, papersize, fontsize
407 # Why can't this be calculated?
409 'a4paper':{10: 345, 11: 360, 12: 390},
410 'a4paper-landscape': {10: 598, 11: 596, 12:592},
411 'a5paper':{10: 276, 11: 276, 12: 276},
412 'b5paper':{10: 345, 11: 356, 12: 356},
413 'letterpaper':{10: 345, 11: 360, 12: 390},
414 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
415 'legalpaper': {10: 345, 11: 360, 12: 390},
416 'executivepaper':{10: 345, 11: 360, 12: 379}}
419 'afourpaper': {12: mm2pt(160)},
420 'afourwide': {12: in2pt(6.5)},
421 'afourlatex': {12: mm2pt(150)},
422 'smallbook': {12: in2pt(5)},
423 'letterpaper': {12: in2pt(6)}}
426 'afourpaper': {12: mm2pt(160)},
427 'afourwide': {12: in2pt(6.5)},
428 'afourlatex': {12: mm2pt(150)},
429 'smallbook': {12: in2pt(5)},
430 'letterpaper': {12: in2pt(6)}}
432 option_definitions = [
433 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
434 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
435 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
436 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
437 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
438 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
439 ('', 'h', 'help', 'this help'),
440 ('DIR', 'I', 'include', 'include path'),
441 ('', 'M', 'dependencies', 'write dependencies'),
442 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
443 ('', 'n', 'no-lily', 'don\'t run lilypond'),
444 ('', '', 'no-pictures', "don\'t generate pictures"),
445 ('', '', 'no-music', "strip all lilypond blocks from output"),
446 ('', '', 'read-lys', "don't write ly files."),
447 ('FILE', 'o', 'outname', 'filename main output file'),
448 ('FILE', '', 'outdir', "where to place generated files"),
449 ('', 'V', 'verbose', 'verbose' ),
450 ('', 'v', 'version', 'print version information' ),
453 # format specific strings, ie. regex-es for input, and % strings for output
455 'html' : {'output-lilypond': '''<lilypond%s>
458 'output-filename' : r'''
461 'output-lilypond-fragment': '''<lilypond%s>
462 \context Staff\context Voice{ %s }
464 'output-noinline': r'''
465 <!-- generated: %(fn)s.png !-->
469 'output-verbatim': r'''<pre>
472 ## Ugh we need to differentiate on origin:
473 ## lilypond-block origin wants an extra <p>, but
474 ## inline music doesn't.
475 ## possibly other center options?
477 <a href="%(fn)s.png">
478 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
482 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
489 'output-filename' : r'''
492 'output-lilypond': r'''\begin[%s]{lilypond}
496 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
498 'output-default-post': "\\def\postLilypondExample{}\n",
499 'output-default-pre': "\\def\preLilypondExample{}\n",
500 'usepackage-graphics': '\\usepackage{graphics}\n',
501 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
502 'output-noinline': r'''
503 %% generated: %(fn)s.eps
505 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
506 'pagebreak': r'\pagebreak',
509 'texi' : {'output-lilypond': '''@lilypond[%s]
513 'output-filename' : r'''
516 'output-lilypond-fragment': '''@lilypond[%s]
517 \context Staff\context Voice{ %s }
519 'output-noinline': r'''
520 @c generated: %(fn)s.png
523 'output-verbatim': r'''@example
528 # do some tweaking: @ is needed in some ps stuff.
529 # override EndLilyPondOutput, since @tex is done
530 # in a sandbox, you can't do \input lilyponddefs at the
531 # top of the document.
533 # should also support fragment in
535 # ugh, the <p> below breaks inline images...
541 \def\EndLilyPondOutput{}
547 <a href="%(fn)s.png">
548 <img border=0 src="%(fn)s.png" alt="[picture of music]">
556 def output_verbatim (body):
557 if __main__.format == 'html':
558 body = re.sub ('&', '&', body)
559 body = re.sub ('>', '>', body)
560 body = re.sub ('<', '<', body)
561 elif __main__.format == 'texi':
562 body = re.sub ('([@{}])', '@\\1', body)
563 return get_output ('output-verbatim') % body
566 #warning: this uses extended regular expressions. Tread with care.
568 # legenda (?P name parameter
569 # *? match non-greedily.
576 'preamble-end': no_match,
577 'landscape': no_match,
578 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
579 'verb': r'''(?P<code><pre>.*?</pre>)''',
580 'lilypond-file': '(?m)(?P<match><lilypondfile(?P<options>[^>]*)?>\s*(?P<filename>.*?)\s*</lilypondfile>)',
581 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
582 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]*)?>(?P<code>.*?)</lilypond>)''',
583 'option-sep' : '\s*',
584 'intertext': r',?\s*intertext=\".*?\"',
585 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
586 'singleline-comment': no_match,
590 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
591 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
592 'option-sep' : ',\s*',
593 'header': r"\\documentclass\s*(\[.*?\])?",
594 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
595 'preamble-end': r'(?P<code>\\begin{document})',
596 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
597 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
598 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
599 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
600 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
601 'def-post-re': r"\\def\\postLilypondExample",
602 'def-pre-re': r"\\def\\preLilypondExample",
603 'usepackage-graphics': r"\usepackage{graphics}",
604 'intertext': r',?\s*intertext=\".*?\"',
605 'multiline-comment': no_match,
606 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
607 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
611 # why do we have distinction between @mbinclude and @include?
615 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
618 'preamble-end': no_match,
619 'landscape': no_match,
620 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
621 'verb': r'''(?P<code>@code{.*?})''',
622 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
623 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
624 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
625 'option-sep' : ',\s*',
626 'intertext': r',?\s*intertext=\".*?\"',
627 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
628 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
634 for r in re_dict.keys ():
637 for k in olddict.keys ():
639 newdict[k] = re.compile (olddict[k])
641 print 'invalid regexp: %s' % olddict[k]
643 # we'd like to catch and reraise a more detailed error, but
644 # alas, the exceptions changed across the 1.5/2.1 boundary.
659 def get_output (name):
660 return output_dict[format][name]
663 return re_dict[format][name]
665 def bounding_box_dimensions(fname):
667 fname = os.path.join(g_outdir, fname)
671 error ("Error opening `%s'" % fname)
673 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
676 gs = map (lambda x: string.atoi (x), s.groups ())
677 return (int (gs[2] - gs[0] + 0.5),
678 int (gs[3] - gs[1] + 0.5))
683 sys.stderr.write (str + "\n Exiting ... \n\n")
687 def compose_full_body (body, opts):
688 '''Construct the lilypond code to send to Lilypond.
689 Add stuff to BODY using OPTS as options.'''
690 music_size = default_music_fontsize
691 latex_size = default_text_fontsize
695 if g_force_lilypond_fontsize:
696 music_size = g_force_lilypond_fontsize
698 m = re.match ('([0-9]+)pt', o)
700 music_size = string.atoi(m.group (1))
702 m = re.match ('latexfontsize=([0-9]+)pt', o)
704 latex_size = string.atoi (m.group (1))
706 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
708 f = float (m.group (1))
709 indent = 'indent = %f\\%s' % (f, m.group (2))
711 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
713 f = float (m.group (1))
714 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
716 if re.search ('\\\\score', body):
720 if 'fragment' in opts:
722 if 'nofragment' in opts:
725 if is_fragment and not 'multiline' in opts:
726 opts.append('singleline')
728 if 'singleline' in opts:
729 linewidth = 'linewidth = -1.0'
731 l = __main__.paperguru.get_linewidth ()
732 linewidth = 'linewidth = %f\pt' % l
734 if 'noindent' in opts:
735 indent = 'indent = 0.0\mm'
738 m= re.search ('relative(.*)', o)
742 v = string.atoi (m.group (1))
749 pitch = pitch + '\,' * v
751 pitch = pitch + '\'' * v
753 body = '\\relative %s { %s }' %(pitch, body)
762 optstring = string.join (opts, ' ')
763 optstring = re.sub ('\n', ' ', optstring)
765 %% Generated automatically by: lilypond-book.py
767 \include "paper%d.ly"
772 ''' % (optstring, music_size, linewidth, indent) + body
774 # ughUGH not original options
777 def parse_options_string(s):
779 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
780 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
781 r3 = re.compile("(\w+?)((,\s*)|$)")
786 d[m.group(2)] = re.split(",\s*", m.group(3))
791 d[m.group(2)] = m.group(3)
799 error ("format of option string invalid (was `%')" % s)
802 def scan_html_preamble (chunks):
805 def scan_latex_preamble(chunks):
806 # first we want to scan the \documentclass line
807 # it should be the first non-comment line
810 if chunks[idx][0] == 'ignore':
813 m = get_re ('header').match(chunks[idx][1])
814 if m <> None and m.group (1):
815 options = re.split (',[\n \t]*', m.group(1)[1:-1])
820 paperguru.m_landscape = 1
821 m = re.match("(.*?)paper", o)
823 paperguru.m_papersize = m.group()
825 m = re.match("(\d\d)pt", o)
827 paperguru.m_fontsize = int(m.group(1))
830 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
831 if chunks[idx] == 'ignore':
834 m = get_re ('geometry').search(chunks[idx][1])
836 paperguru.m_use_geometry = 1
837 o = parse_options_string(m.group('options'))
839 paperguru.set_geo_option(k, o[k])
842 def scan_texi_preamble (chunks):
843 # this is not bulletproof..., it checks the first 10 chunks
844 for c in chunks[:10]:
846 for s in ('afourpaper', 'afourwide', 'letterpaper',
847 'afourlatex', 'smallbook'):
848 if string.find(c[1], "@%s" % s) != -1:
849 paperguru.m_papersize = s
852 def scan_preamble (chunks):
853 if __main__.format == 'html':
854 scan_html_preamble (chunks)
855 elif __main__.format == 'latex':
856 scan_latex_preamble (chunks)
857 elif __main__.format == 'texi':
858 scan_texi_preamble (chunks)
861 def completize_preamble (chunks):
862 if __main__.format != 'latex':
864 pre_b = post_b = graphics_b = None
866 if chunk[0] == 'preamble-end':
868 if chunk[0] == 'input':
869 m = get_re('def-pre-re').search(chunk[1])
872 if chunk[0] == 'input':
873 m = get_re('def-post-re').search(chunk[1])
877 if chunk[0] == 'input':
878 m = get_re('usepackage-graphics').search(chunk[1])
882 while x < len (chunks) and chunks[x][0] != 'preamble-end':
889 chunks.insert(x, ('input', get_output ('output-default-pre')))
891 chunks.insert(x, ('input', get_output ('output-default-post')))
893 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
899 def find_file (name):
901 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
905 return (sys.stdin.read (), '<stdin>')
908 for a in include_path:
910 nm = os.path.join (a, name)
912 __main__.read_files.append (nm)
917 sys.stderr.write ("Reading `%s'\n" % nm)
918 return (f.read (), nm)
920 error ("File not found `%s'\n" % name)
923 def do_ignore(match_object):
924 return [('ignore', match_object.group('code'))]
925 def do_preamble_end(match_object):
926 return [('preamble-end', match_object.group('code'))]
928 def make_verbatim(match_object):
929 return [('verbatim', match_object.group('code'))]
931 def make_verb(match_object):
932 return [('verb', match_object.group('code'))]
934 def do_include_file(m):
936 return [('input', get_output ('pagebreak'))] \
937 + read_doc_file(m.group('filename')) \
938 + [('input', get_output ('pagebreak'))]
940 def do_input_file(m):
941 return read_doc_file(m.group('filename'))
943 def make_lilypond(m):
944 if m.group('options'):
945 options = m.group('options')
948 return [('input', get_output('output-lilypond-fragment') %
949 (options, m.group('code')))]
951 def make_lilypond_file(m):
954 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
955 into a @lilypond .. @end lilypond block.
959 if m.group('options'):
960 options = m.group('options')
963 (content, nm) = find_file(m.group('filename'))
964 options = "filename=%s," % nm + options
966 return [('input', get_output('output-lilypond') %
969 def make_lilypond_block(m):
973 if m.group('options'):
974 options = get_re('option-sep').split (m.group('options'))
977 options = filter(lambda s: s != '', options)
978 return [('lilypond', m.group('code'), options)]
981 if __main__.format != 'latex':
983 if m.group('num') == 'one':
984 return [('numcols', m.group('code'), 1)]
985 if m.group('num') == 'two':
986 return [('numcols', m.group('code'), 2)]
988 def chop_chunks(chunks, re_name, func, use_match=0):
994 m = get_re (re_name).search (str)
996 newchunks.append (('input', str))
1000 newchunks.append (('input', str[:m.start ('match')]))
1002 newchunks.append (('input', str[:m.start (0)]))
1003 #newchunks.extend(func(m))
1004 # python 1.5 compatible:
1005 newchunks = newchunks + func(m)
1006 str = str [m.end(0):]
1011 def determine_format (str):
1012 if __main__.format == '':
1014 html = re.search ('(?i)<[dh]tml', str[:200])
1015 latex = re.search ('''\\document''', str[:200])
1016 texi = re.search ('@node|@setfilename', str[:200])
1021 if html and not latex and not texi:
1023 elif latex and not html and not texi:
1025 elif texi and not html and not latex:
1028 error ("can't determine format, please specify")
1031 if __main__.paperguru == None:
1032 if __main__.format == 'html':
1034 elif __main__.format == 'latex':
1036 elif __main__.format == 'texi':
1039 __main__.paperguru = g
1042 def read_doc_file (filename):
1043 '''Read the input file, find verbatim chunks and do \input and \include
1045 (str, path) = find_file(filename)
1046 determine_format (str)
1048 chunks = [('input', str)]
1050 # we have to check for verbatim before doing include,
1051 # because we don't want to include files that are mentioned
1052 # inside a verbatim environment
1053 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1054 chunks = chop_chunks(chunks, 'verb', make_verb)
1055 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1057 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1058 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1062 taken_file_names = {}
1063 def schedule_lilypond_block (chunk):
1064 '''Take the body and options from CHUNK, figure out how the
1065 real .ly should look, and what should be left MAIN_STR (meant
1066 for the main file). The .ly is written, and scheduled in
1069 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1071 TODO has format [basename, extension, extension, ... ]
1074 (type, body, opts) = chunk
1075 assert type == 'lilypond'
1076 file_body = compose_full_body (body, opts)
1077 ## Hmm, we should hash only lilypond source, and skip the
1080 basename = 'lily-' + `abs(hash (file_body))`
1082 m = re.search ('filename="(.*?)"', o)
1084 basename = m.group (1)
1085 if not taken_file_names.has_key(basename):
1086 taken_file_names[basename] = 0
1088 taken_file_names[basename] = taken_file_names[basename] + 1
1089 basename = basename + "-%i" % taken_file_names[basename]
1091 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1092 needed_filetypes = ['tex']
1094 if format == 'html' or format == 'texi':
1095 needed_filetypes.append ('eps')
1096 needed_filetypes.append ('png')
1097 if 'eps' in opts and not ('eps' in needed_filetypes):
1098 needed_filetypes.append('eps')
1099 pathbase = os.path.join (g_outdir, basename)
1100 def f (base, ext1, ext2):
1101 a = os.path.isfile(base + ext2)
1102 if (os.path.isfile(base + ext1) and
1103 os.path.isfile(base + ext2) and
1104 os.stat(base+ext1)[stat.ST_MTIME] >
1105 os.stat(base+ext2)[stat.ST_MTIME]) or \
1106 not os.path.isfile(base + ext2):
1109 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1111 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1113 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1117 if 'printfilename' in opts:
1119 m= re.match ("filename=(.*)", o)
1121 newbody = newbody + get_output ("output-filename") % m.group(1)
1125 if 'verbatim' in opts:
1126 newbody = output_verbatim (body)
1129 m = re.search ('intertext="(.*?)"', o)
1131 newbody = newbody + m.group (1) + "\n\n"
1133 if 'noinline' in opts:
1134 s = 'output-noinline'
1135 elif format == 'latex':
1140 else: # format == 'html' or format == 'texi':
1142 newbody = newbody + get_output (s) % {'fn': basename }
1143 return ('lilypond', newbody, opts, todo, basename)
1145 def process_lilypond_blocks(chunks):#ugh rename
1147 # Count sections/chapters.
1149 if c[0] == 'lilypond':
1150 c = schedule_lilypond_block (c)
1151 elif c[0] == 'numcols':
1152 paperguru.m_num_cols = c[2]
1153 newchunks.append (c)
1159 sys.stderr.write ("invoking `%s'\n" % cmd)
1160 st = os.system (cmd)
1162 error ('Error command exited with value %d\n' % st)
1165 def quiet_system (cmd, name):
1167 progress ( _("Running %s...") % name)
1168 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1172 def get_bbox (filename):
1173 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1175 box = open (filename + '.bbox').read()
1176 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1179 gr = map (string.atoi, m.groups ())
1183 def make_pixmap (name):
1184 bbox = get_bbox (name + '.eps')
1186 fo = open (name + '.trans.eps' , 'w')
1187 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1192 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1193 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1195 cmd = r'''gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
1197 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1198 quiet_system (cmd, 'gs')
1201 status = system (cmd)
1203 os.unlink (name + '.png')
1204 error ("Removing output file")
1206 def compile_all_files (chunks):
1213 if c[0] <> 'lilypond':
1222 if base + '.ly' not in tex:
1223 tex.append (base + '.ly')
1224 elif e == 'png' and g_do_pictures:
1230 # fixme: be sys-independent.
1232 if g_outdir and x[0] <> '/' :
1233 x = os.path.join (g_here_dir, x)
1236 incs = map (incl_opt, include_path)
1237 lilyopts = string.join (incs, ' ' )
1239 lilyopts = lilyopts + ' --dependencies '
1241 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1242 texfiles = string.join (tex, ' ')
1243 cmd = 'lilypond --header=texidoc %s %s %s' \
1244 % (lilyopts, g_extra_opts, texfiles)
1245 quiet_system (cmd, 'LilyPond')
1248 # Ugh, fixing up dependencies for .tex generation
1251 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1256 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1262 cmd = r"tex '\nonstopmode \input %s'" % e
1263 quiet_system (cmd, 'TeX')
1265 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1266 quiet_system (cmd, 'dvips')
1274 def update_file (body, name):
1276 write the body if it has changed
1287 f = open (name , 'w')
1294 def getopt_args (opts):
1295 "Construct arguments (LONG, SHORT) for getopt from list of options."
1300 short = short + o[1]
1308 return (short, long)
1310 def option_help_str (o):
1311 "Transform one option description (4-tuple ) into neatly formatted string"
1329 return ' ' + sh + sep + long + arg
1332 def options_help_str (opts):
1333 "Convert a list of options into a neatly formatted string"
1339 s = option_help_str (o)
1340 strs.append ((s, o[3]))
1346 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1350 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1351 Generate hybrid LaTeX input from Latex + lilypond
1354 sys.stdout.write (options_help_str (option_definitions))
1355 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1359 Report bugs to bug-lilypond@gnu.org.
1361 Written by Tom Cato Amundsen <tca@gnu.org> and
1362 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1368 def write_deps (fn, target, chunks):
1370 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1371 f = open (os.path.join(g_outdir, fn), 'w')
1372 f.write ('%s%s: ' % (g_dep_prefix, target))
1373 for d in read_files:
1377 if c[0] == 'lilypond':
1378 (type, body, opts, todo, basename) = c;
1379 basenames.append (basename)
1382 d=g_outdir + '/' + d
1384 #if not os.isfile (d): # thinko?
1385 if not re.search ('/', d):
1386 d = g_dep_prefix + d
1387 f.write ('%s.tex ' % d)
1389 #if len (basenames):
1390 # for d in basenames:
1391 # f.write ('%s.ly ' % d)
1392 # f.write (' : %s' % target)
1397 def identify (stream):
1398 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1400 def print_version ():
1401 identify (sys.stdout)
1402 sys.stdout.write (r'''Copyright 1998--1999
1403 Distributed under terms of the GNU General Public License. It comes with
1408 def check_texidoc (chunks):
1411 if c[0] == 'lilypond':
1412 (type, body, opts, todo, basename) = c;
1413 pathbase = os.path.join (g_outdir, basename)
1414 if os.path.isfile (pathbase + '.texidoc'):
1415 body = '\n@include %s.texidoc\n' % basename + body
1416 c = (type, body, opts, todo, basename)
1421 ## what's this? Docme --hwn
1423 def fix_epswidth (chunks):
1426 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1427 newchunks.append (c)
1432 m = re.match ('magnification=([0-9.]+)', o)
1434 mag = string.atof (m.group (1))
1436 def replace_eps_dim (match, lmag = mag):
1437 filename = match.group (1)
1438 dims = bounding_box_dimensions (filename)
1440 return '%fpt' % (dims[0] *lmag)
1442 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1443 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1448 ##docme: why global?
1450 def do_file(input_filename):
1454 my_outname = outname
1455 elif input_filename == '-' or input_filename == "/dev/stdin":
1458 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1459 my_depname = my_outname + '.dep'
1461 chunks = read_doc_file(input_filename)
1462 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1463 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1464 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1465 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1466 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1467 chunks = chop_chunks(chunks, 'numcols', do_columns)
1469 #for c in chunks: print "c:", c;
1471 scan_preamble(chunks)
1472 chunks = process_lilypond_blocks(chunks)
1475 if __main__.g_run_lilypond:
1476 compile_all_files (chunks)
1477 chunks = fix_epswidth (chunks)
1479 if __main__.format == 'texi':
1480 chunks = check_texidoc (chunks)
1483 chunks = completize_preamble (chunks)
1484 if my_outname == '-' or my_outname == '/dev/stdout':
1487 __main__.do_deps = 0
1489 foutn = os.path.join (g_outdir, my_outname)
1490 sys.stderr.write ("Writing `%s'\n" % foutn)
1491 fout = open (foutn, 'w')
1498 write_deps (my_depname, foutn, chunks)
1503 (sh, long) = getopt_args (__main__.option_definitions)
1504 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1505 except getopt.error, msg:
1506 sys.stderr.write("error: %s" % msg)
1514 if o == '--include' or o == '-I':
1515 include_path.append (a)
1516 elif o == '--version' or o == '-v':
1519 elif o == '--verbose' or o == '-V':
1520 __main__.verbose_p = 1
1521 elif o == '--format' or o == '-f':
1523 elif o == '--outname' or o == '-o':
1526 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1529 elif o == '--help' or o == '-h':
1531 elif o == '--no-lily' or o == '-n':
1532 __main__.g_run_lilypond = 0
1533 elif o == '--dependencies' or o == '-M':
1535 elif o == '--default-music-fontsize':
1536 default_music_fontsize = string.atoi (a)
1537 elif o == '--default-lilypond-fontsize':
1538 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1539 default_music_fontsize = string.atoi (a)
1540 elif o == '--extra-options':
1542 elif o == '--force-music-fontsize':
1543 g_force_lilypond_fontsize = string.atoi(a)
1544 elif o == '--force-lilypond-fontsize':
1545 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1546 g_force_lilypond_fontsize = string.atoi(a)
1547 elif o == '--dep-prefix':
1549 elif o == '--no-pictures':
1551 elif o == '--no-music':
1553 elif o == '--read-lys':
1555 elif o == '--outdir':
1558 identify (sys.stderr)
1560 if os.path.isfile(g_outdir):
1561 error ("outdir is a file: %s" % g_outdir)
1562 if not os.path.exists(g_outdir):
1564 setup_environment ()
1565 for input_filename in files:
1566 do_file(input_filename)
1569 # Petr, ik zou willen dat ik iets zinvoller deed,
1570 # maar wat ik kan ik doen, het verandert toch niets?