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.
50 # Handle bug in Python 1.6-2.1
52 # there are recursion limits for some patterns in Python 1.6 til 2.1.
53 # fix this by importing pre instead. Fix by Mats.
55 # todo: should check Python version first.
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
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
151 default_music_fontsize = 16
152 default_text_fontsize = 12
155 # this code is ugly. It should be cleaned
159 # the dimensions are from geometry.sty
160 'a0paper': (mm2pt(841), mm2pt(1189)),
161 'a1paper': (mm2pt(595), mm2pt(841)),
162 'a2paper': (mm2pt(420), mm2pt(595)),
163 'a3paper': (mm2pt(297), mm2pt(420)),
164 'a4paper': (mm2pt(210), mm2pt(297)),
165 'a5paper': (mm2pt(149), mm2pt(210)),
166 'b0paper': (mm2pt(1000), mm2pt(1414)),
167 'b1paper': (mm2pt(707), mm2pt(1000)),
168 'b2paper': (mm2pt(500), mm2pt(707)),
169 'b3paper': (mm2pt(353), mm2pt(500)),
170 'b4paper': (mm2pt(250), mm2pt(353)),
171 'b5paper': (mm2pt(176), mm2pt(250)),
172 'letterpaper': (in2pt(8.5), in2pt(11)),
173 'legalpaper': (in2pt(8.5), in2pt(14)),
174 'executivepaper': (in2pt(7.25), in2pt(10.5))}
175 self.m_use_geometry = None
176 self.m_papersize = 'letterpaper'
180 self.m_geo_landscape = 0
181 self.m_geo_width = None
182 self.m_geo_textwidth = None
183 self.m_geo_lmargin = None
184 self.m_geo_rmargin = None
185 self.m_geo_includemp = None
186 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
187 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
188 self.m_geo_x_marginparwidth = None
189 self.m_geo_x_marginparsep = None
191 def set_geo_option(self, name, value):
193 if type(value) == type([]):
194 value = map(conv_dimen_to_float, value)
196 value = conv_dimen_to_float(value)
198 if name == 'body' or name == 'text':
199 if type(value) == type([]):
200 self.m_geo_textwidth = value[0]
202 self.m_geo_textwidth = value
204 elif name == 'portrait':
205 self.m_geo_landscape = 0
206 elif name == 'reversemp' or name == 'reversemarginpar':
207 if self.m_geo_includemp == None:
208 self.m_geo_includemp = 1
209 elif name == 'marginparwidth' or name == 'marginpar':
210 self.m_geo_x_marginparwidth = value
211 self.m_geo_includemp = 1
212 elif name == 'marginparsep':
213 self.m_geo_x_marginparsep = value
214 self.m_geo_includemp = 1
215 elif name == 'scale':
216 if type(value) == type([]):
217 self.m_geo_width = self.get_paperwidth() * value[0]
219 self.m_geo_width = self.get_paperwidth() * value
220 elif name == 'hscale':
221 self.m_geo_width = self.get_paperwidth() * value
222 elif name == 'left' or name == 'lmargin':
223 self.m_geo_lmargin = value
224 elif name == 'right' or name == 'rmargin':
225 self.m_geo_rmargin = value
226 elif name == 'hdivide' or name == 'divide':
227 if value[0] not in ('*', ''):
228 self.m_geo_lmargin = value[0]
229 if value[1] not in ('*', ''):
230 self.m_geo_width = value[1]
231 if value[2] not in ('*', ''):
232 self.m_geo_rmargin = value[2]
233 elif name == 'hmargin':
234 if type(value) == type([]):
235 self.m_geo_lmargin = value[0]
236 self.m_geo_rmargin = value[1]
238 self.m_geo_lmargin = value
239 self.m_geo_rmargin = value
240 elif name == 'margin':#ugh there is a bug about this option in
241 # the geometry documentation
242 if type(value) == type([]):
243 self.m_geo_lmargin = value[0]
244 self.m_geo_rmargin = value[0]
246 self.m_geo_lmargin = value
247 self.m_geo_rmargin = value
248 elif name == 'total':
249 if type(value) == type([]):
250 self.m_geo_width = value[0]
252 self.m_geo_width = value
253 elif name == 'width' or name == 'totalwidth':
254 self.m_geo_width = value
255 elif name == 'paper' or name == 'papername':
256 self.m_papersize = value
257 elif name[-5:] == 'paper':
258 self.m_papersize = name
262 def __setattr__(self, name, value):
263 if type(value) == type("") and \
264 dimension_conversion_dict.has_key (value[-2:]):
265 f = dimension_conversion_dict[value[-2:]]
266 self.__dict__[name] = f(float(value[:-2]))
268 self.__dict__[name] = value
271 s = "LatexPaper:\n-----------"
272 for v in self.__dict__.keys():
274 s = s + str (v) + ' ' + str (self.__dict__[v])
275 s = s + "-----------"
278 def get_linewidth(self):
279 w = self._calc_linewidth()
280 if self.m_num_cols == 2:
284 def get_paperwidth(self):
285 #if self.m_use_geometry:
286 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
287 #return self.m_paperdef[self.m_papersize][self.m_landscape]
289 def _calc_linewidth(self):
290 # since geometry sometimes ignores 'includemp', this is
291 # more complicated than it should be
293 if self.m_geo_includemp:
294 if self.m_geo_x_marginparsep is not None:
295 mp = mp + self.m_geo_x_marginparsep
297 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
298 if self.m_geo_x_marginparwidth is not None:
299 mp = mp + self.m_geo_x_marginparwidth
301 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
303 #ugh test if this is necessary
307 if not self.m_use_geometry:
308 return latex_linewidths[self.m_papersize][self.m_fontsize]
310 geo_opts = (self.m_geo_lmargin == None,
311 self.m_geo_width == None,
312 self.m_geo_rmargin == None)
314 if geo_opts == (1, 1, 1):
315 if self.m_geo_textwidth:
316 return self.m_geo_textwidth
317 w = self.get_paperwidth() * 0.8
319 elif geo_opts == (0, 1, 1):
320 if self.m_geo_textwidth:
321 return self.m_geo_textwidth
322 return self.f1(self.m_geo_lmargin, mp)
323 elif geo_opts == (1, 1, 0):
324 if self.m_geo_textwidth:
325 return self.m_geo_textwidth
326 return self.f1(self.m_geo_rmargin, mp)
328 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
329 if self.m_geo_textwidth:
330 return self.m_geo_textwidth
331 return self.m_geo_width - mp
332 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
333 w = self.get_paperwidth() \
334 - self.m_geo_lmargin - self.m_geo_rmargin - mp
338 raise "Never do this!"
340 tmp = self.get_paperwidth() - m * 2 - mp
345 tmp = self.get_paperwidth() - self.m_geo_lmargin \
353 self.m_papersize = 'letterpaper'
355 def get_linewidth(self):
356 return html_linewidths[self.m_papersize][self.m_fontsize]
360 self.m_papersize = 'letterpaper'
362 def get_linewidth(self):
363 return texi_linewidths[self.m_papersize][self.m_fontsize]
369 def em2pt(x, fontsize = 10):
370 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
371 def ex2pt(x, fontsize = 10):
372 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
377 dimension_conversion_dict ={
379 'cm': lambda x: mm2pt(10*x),
386 # Convert numeric values, with or without specific dimension, to floats.
388 def conv_dimen_to_float(value):
389 if type(value) == type(""):
390 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
393 num = string.atof(m.group (1))
394 conv = dimension_conversion_dict[m.group(2)]
398 elif re.match ("^[0-9.]+$",value):
405 # indices are no. of columns, papersize, fontsize
406 # Why can't this be calculated?
408 'a4paper':{10: 345, 11: 360, 12: 390},
409 'a4paper-landscape': {10: 598, 11: 596, 12:592},
410 'a5paper':{10: 276, 11: 276, 12: 276},
411 'b5paper':{10: 345, 11: 356, 12: 356},
412 'letterpaper':{10: 345, 11: 360, 12: 390},
413 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
414 'legalpaper': {10: 345, 11: 360, 12: 390},
415 'executivepaper':{10: 345, 11: 360, 12: 379}}
418 'afourpaper': {12: mm2pt(160)},
419 'afourwide': {12: in2pt(6.5)},
420 'afourlatex': {12: mm2pt(150)},
421 'smallbook': {12: in2pt(5)},
422 'letterpaper': {12: in2pt(6)}}
425 'afourpaper': {12: mm2pt(160)},
426 'afourwide': {12: in2pt(6.5)},
427 'afourlatex': {12: mm2pt(150)},
428 'smallbook': {12: in2pt(5)},
429 'letterpaper': {12: in2pt(6)}}
431 option_definitions = [
432 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
433 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
434 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
435 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
436 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
437 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
438 ('', 'h', 'help', 'this help'),
439 ('DIR', 'I', 'include', 'include path'),
440 ('', 'M', 'dependencies', 'write dependencies'),
441 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
442 ('', 'n', 'no-lily', 'don\'t run lilypond'),
443 ('', '', 'no-pictures', "don\'t generate pictures"),
444 ('', '', 'read-lys', "don't write ly files."),
445 ('FILE', 'o', 'outname', 'filename main output file'),
446 ('FILE', '', 'outdir', "where to place generated files"),
447 ('', 'V', 'verbose', 'verbose' ),
448 ('', 'v', 'version', 'print version information' ),
451 # format specific strings, ie. regex-es for input, and % strings for output
453 'html' : {'output-lilypond': '''<lilypond%s>
456 'output-filename' : r'''
459 'output-lilypond-fragment': '''<lilypond%s>
460 \context Staff\context Voice{ %s }
462 'output-noinline': r'''
463 <!-- generated: %(fn)s.png !-->
467 'output-verbatim': r'''<pre>
470 ## Ugh we need to differentiate on origin:
471 ## lilypond-block origin wants an extra <p>, but
472 ## inline music doesn't.
473 ## possibly other center options?
475 <a href="%(fn)s.png">
476 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
480 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
487 'output-filename' : r'''
490 'output-lilypond': r'''\begin[%s]{lilypond}
494 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
496 'output-default-post': "\\def\postLilypondExample{}\n",
497 'output-default-pre': "\\def\preLilypondExample{}\n",
498 'usepackage-graphics': '\\usepackage{graphics}\n',
499 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
500 'output-noinline': r'''
501 %% generated: %(fn)s.eps
503 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
504 'pagebreak': r'\pagebreak',
507 'texi' : {'output-lilypond': '''@lilypond[%s]
511 'output-filename' : r'''
514 'output-lilypond-fragment': '''@lilypond[%s]
515 \context Staff\context Voice{ %s }
517 'output-noinline': r'''
518 @c generated: %(fn)s.png
521 'output-verbatim': r'''@example
526 # do some tweaking: @ is needed in some ps stuff.
527 # override EndLilyPondOutput, since @tex is done
528 # in a sandbox, you can't do \input lilyponddefs at the
529 # top of the document.
531 # should also support fragment in
533 # ugh, the <p> below breaks inline images...
539 \def\EndLilyPondOutput{}
545 <a href="%(fn)s.png">
546 <img border=0 src="%(fn)s.png" alt="[picture of music]">
554 def output_verbatim (body):
555 if __main__.format == 'html':
556 body = re.sub ('&', '&', body)
557 body = re.sub ('>', '>', body)
558 body = re.sub ('<', '<', body)
559 elif __main__.format == 'texi':
560 body = re.sub ('([@{}])', '@\\1', body)
561 return get_output ('output-verbatim') % body
564 #warning: this uses extended regular expressions. Tread with care.
566 # legenda (?P name parameter
567 # *? match non-greedily.
574 'preamble-end': no_match,
575 'landscape': no_match,
576 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
577 'verb': r'''(?P<code><pre>.*?</pre>)''',
578 'lilypond-file': '(?m)(?P<match><lilypondfile(?P<options>[^>]*)?>\s*(?P<filename>.*?)\s*</lilypondfile>)',
579 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
580 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]*)?>(?P<code>.*?)</lilypond>)''',
581 'option-sep' : '\s*',
582 'intertext': r',?\s*intertext=\".*?\"',
583 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
584 'singleline-comment': no_match,
588 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
589 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
590 'option-sep' : ',\s*',
591 'header': r"\\documentclass\s*(\[.*?\])?",
592 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
593 'preamble-end': r'(?P<code>\\begin{document})',
594 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
595 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
596 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
597 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
598 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
599 'def-post-re': r"\\def\\postLilypondExample",
600 'def-pre-re': r"\\def\\preLilypondExample",
601 'usepackage-graphics': r"\usepackage{graphics}",
602 'intertext': r',?\s*intertext=\".*?\"',
603 'multiline-comment': no_match,
604 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
605 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
609 # why do we have distinction between @mbinclude and @include?
613 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
616 'preamble-end': no_match,
617 'landscape': no_match,
618 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
619 'verb': r'''(?P<code>@code{.*?})''',
620 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
621 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
622 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
623 'option-sep' : ',\s*',
624 'intertext': r',?\s*intertext=\".*?\"',
625 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
626 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
632 for r in re_dict.keys ():
635 for k in olddict.keys ():
637 newdict[k] = re.compile (olddict[k])
639 print 'invalid regexp: %s' % olddict[k]
641 # we'd like to catch and reraise a more detailed error, but
642 # alas, the exceptions changed across the 1.5/2.1 boundary.
657 def get_output (name):
658 return output_dict[format][name]
661 return re_dict[format][name]
663 def bounding_box_dimensions(fname):
665 fname = os.path.join(g_outdir, fname)
669 error ("Error opening `%s'" % fname)
671 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
674 gs = map (lambda x: string.atoi (x), s.groups ())
675 return (int (gs[2] - gs[0] + 0.5),
676 int (gs[3] - gs[1] + 0.5))
681 sys.stderr.write (str + "\n Exiting ... \n\n")
685 def compose_full_body (body, opts):
686 '''Construct the lilypond code to send to Lilypond.
687 Add stuff to BODY using OPTS as options.'''
688 music_size = default_music_fontsize
689 latex_size = default_text_fontsize
693 if g_force_lilypond_fontsize:
694 music_size = g_force_lilypond_fontsize
696 m = re.match ('([0-9]+)pt', o)
698 music_size = string.atoi(m.group (1))
700 m = re.match ('latexfontsize=([0-9]+)pt', o)
702 latex_size = string.atoi (m.group (1))
704 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
706 f = float (m.group (1))
707 indent = 'indent = %f\\%s' % (f, m.group (2))
709 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
711 f = float (m.group (1))
712 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
714 if re.search ('\\\\score', body):
718 if 'fragment' in opts:
720 if 'nofragment' in opts:
723 if is_fragment and not 'multiline' in opts:
724 opts.append('singleline')
726 if 'singleline' in opts:
727 linewidth = 'linewidth = -1.0'
729 l = __main__.paperguru.get_linewidth ()
730 linewidth = 'linewidth = %f\pt' % l
732 if 'noindent' in opts:
733 indent = 'indent = 0.0\mm'
736 m= re.search ('relative(.*)', o)
740 v = string.atoi (m.group (1))
747 pitch = pitch + '\,' * v
749 pitch = pitch + '\'' * v
751 body = '\\relative %s { %s }' %(pitch, body)
760 optstring = string.join (opts, ' ')
761 optstring = re.sub ('\n', ' ', optstring)
763 %% Generated automatically by: lilypond-book.py
765 \include "paper%d.ly"
770 ''' % (optstring, music_size, linewidth, indent) + body
772 # ughUGH not original options
775 def parse_options_string(s):
777 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
778 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
779 r3 = re.compile("(\w+?)((,\s*)|$)")
784 d[m.group(2)] = re.split(",\s*", m.group(3))
789 d[m.group(2)] = m.group(3)
797 error ("format of option string invalid (was `%')" % s)
800 def scan_html_preamble (chunks):
803 def scan_latex_preamble(chunks):
804 # first we want to scan the \documentclass line
805 # it should be the first non-comment line
808 if chunks[idx][0] == 'ignore':
811 m = get_re ('header').match(chunks[idx][1])
812 if m <> None and m.group (1):
813 options = re.split (',[\n \t]*', m.group(1)[1:-1])
818 paperguru.m_landscape = 1
819 m = re.match("(.*?)paper", o)
821 paperguru.m_papersize = m.group()
823 m = re.match("(\d\d)pt", o)
825 paperguru.m_fontsize = int(m.group(1))
828 while chunks[idx][0] != 'preamble-end':
829 if chunks[idx] == 'ignore':
832 m = get_re ('geometry').search(chunks[idx][1])
834 paperguru.m_use_geometry = 1
835 o = parse_options_string(m.group('options'))
837 paperguru.set_geo_option(k, o[k])
840 def scan_texi_preamble (chunks):
841 # this is not bulletproof..., it checks the first 10 chunks
842 for c in chunks[:10]:
844 for s in ('afourpaper', 'afourwide', 'letterpaper',
845 'afourlatex', 'smallbook'):
846 if string.find(c[1], "@%s" % s) != -1:
847 paperguru.m_papersize = s
850 def scan_preamble (chunks):
851 if __main__.format == 'html':
852 scan_html_preamble (chunks)
853 elif __main__.format == 'latex':
854 scan_latex_preamble (chunks)
855 elif __main__.format == 'texi':
856 scan_texi_preamble (chunks)
859 def completize_preamble (chunks):
860 if __main__.format != 'latex':
862 pre_b = post_b = graphics_b = None
864 if chunk[0] == 'preamble-end':
866 if chunk[0] == 'input':
867 m = get_re('def-pre-re').search(chunk[1])
870 if chunk[0] == 'input':
871 m = get_re('def-post-re').search(chunk[1])
874 if chunk[0] == 'input':
875 m = get_re('usepackage-graphics').search(chunk[1])
879 while chunks[x][0] != 'preamble-end':
882 chunks.insert(x, ('input', get_output ('output-default-pre')))
884 chunks.insert(x, ('input', get_output ('output-default-post')))
886 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
891 def find_file (name):
893 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
897 return (sys.stdin.read (), '<stdin>')
900 for a in include_path:
902 nm = os.path.join (a, name)
904 __main__.read_files.append (nm)
909 sys.stderr.write ("Reading `%s'\n" % nm)
910 return (f.read (), nm)
912 error ("File not found `%s'\n" % name)
915 def do_ignore(match_object):
916 return [('ignore', match_object.group('code'))]
917 def do_preamble_end(match_object):
918 return [('preamble-end', match_object.group('code'))]
920 def make_verbatim(match_object):
921 return [('verbatim', match_object.group('code'))]
923 def make_verb(match_object):
924 return [('verb', match_object.group('code'))]
926 def do_include_file(m):
928 return [('input', get_output ('pagebreak'))] \
929 + read_doc_file(m.group('filename')) \
930 + [('input', get_output ('pagebreak'))]
932 def do_input_file(m):
933 return read_doc_file(m.group('filename'))
935 def make_lilypond(m):
936 if m.group('options'):
937 options = m.group('options')
940 return [('input', get_output('output-lilypond-fragment') %
941 (options, m.group('code')))]
943 def make_lilypond_file(m):
946 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
947 into a @lilypond .. @end lilypond block.
951 if m.group('options'):
952 options = m.group('options')
955 (content, nm) = find_file(m.group('filename'))
956 options = "filename=%s," % nm + options
958 return [('input', get_output('output-lilypond') %
961 def make_lilypond_block(m):
962 if m.group('options'):
963 options = get_re('option-sep').split (m.group('options'))
966 options = filter(lambda s: s != '', options)
967 return [('lilypond', m.group('code'), options)]
970 if __main__.format != 'latex':
972 if m.group('num') == 'one':
973 return [('numcols', m.group('code'), 1)]
974 if m.group('num') == 'two':
975 return [('numcols', m.group('code'), 2)]
977 def chop_chunks(chunks, re_name, func, use_match=0):
983 m = get_re (re_name).search (str)
985 newchunks.append (('input', str))
989 newchunks.append (('input', str[:m.start ('match')]))
991 newchunks.append (('input', str[:m.start (0)]))
992 #newchunks.extend(func(m))
993 # python 1.5 compatible:
994 newchunks = newchunks + func(m)
995 str = str [m.end(0):]
1000 def determine_format (str):
1001 if __main__.format == '':
1003 html = re.search ('(?i)<[dh]tml', str[:200])
1004 latex = re.search ('''\\document''', str[:200])
1005 texi = re.search ('@node|@setfilename', str[:200])
1010 if html and not latex and not texi:
1012 elif latex and not html and not texi:
1014 elif texi and not html and not latex:
1017 error ("can't determine format, please specify")
1020 if __main__.paperguru == None:
1021 if __main__.format == 'html':
1023 elif __main__.format == 'latex':
1025 elif __main__.format == 'texi':
1028 __main__.paperguru = g
1031 def read_doc_file (filename):
1032 '''Read the input file, find verbatim chunks and do \input and \include
1034 (str, path) = find_file(filename)
1035 determine_format (str)
1037 chunks = [('input', str)]
1039 # we have to check for verbatim before doing include,
1040 # because we don't want to include files that are mentioned
1041 # inside a verbatim environment
1042 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1043 chunks = chop_chunks(chunks, 'verb', make_verb)
1044 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1046 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1047 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1051 taken_file_names = {}
1052 def schedule_lilypond_block (chunk):
1053 '''Take the body and options from CHUNK, figure out how the
1054 real .ly should look, and what should be left MAIN_STR (meant
1055 for the main file). The .ly is written, and scheduled in
1058 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1060 TODO has format [basename, extension, extension, ... ]
1063 (type, body, opts) = chunk
1064 assert type == 'lilypond'
1065 file_body = compose_full_body (body, opts)
1066 ## Hmm, we should hash only lilypond source, and skip the
1069 basename = 'lily-' + `abs(hash (file_body))`
1071 m = re.search ('filename="(.*?)"', o)
1073 basename = m.group (1)
1074 if not taken_file_names.has_key(basename):
1075 taken_file_names[basename] = 0
1077 taken_file_names[basename] = taken_file_names[basename] + 1
1078 basename = basename + "-%i" % taken_file_names[basename]
1080 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1081 needed_filetypes = ['tex']
1083 if format == 'html' or format == 'texi':
1084 needed_filetypes.append ('eps')
1085 needed_filetypes.append ('png')
1086 if 'eps' in opts and not ('eps' in needed_filetypes):
1087 needed_filetypes.append('eps')
1088 pathbase = os.path.join (g_outdir, basename)
1089 def f (base, ext1, ext2):
1090 a = os.path.isfile(base + ext2)
1091 if (os.path.isfile(base + ext1) and
1092 os.path.isfile(base + ext2) and
1093 os.stat(base+ext1)[stat.ST_MTIME] >
1094 os.stat(base+ext2)[stat.ST_MTIME]) or \
1095 not os.path.isfile(base + ext2):
1098 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1100 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1102 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1106 if 'printfilename' in opts:
1108 m= re.match ("filename=(.*)", o)
1110 newbody = newbody + get_output ("output-filename") % m.group(1)
1114 if 'verbatim' in opts:
1115 newbody = output_verbatim (body)
1118 m = re.search ('intertext="(.*?)"', o)
1120 newbody = newbody + m.group (1) + "\n\n"
1122 if 'noinline' in opts:
1123 s = 'output-noinline'
1124 elif format == 'latex':
1129 else: # format == 'html' or format == 'texi':
1131 newbody = newbody + get_output (s) % {'fn': basename }
1132 return ('lilypond', newbody, opts, todo, basename)
1134 def process_lilypond_blocks(outname, chunks):#ugh rename
1136 # Count sections/chapters.
1138 if c[0] == 'lilypond':
1139 c = schedule_lilypond_block (c)
1140 elif c[0] == 'numcols':
1141 paperguru.m_num_cols = c[2]
1142 newchunks.append (c)
1148 sys.stderr.write ("invoking `%s'\n" % cmd)
1149 st = os.system (cmd)
1151 error ('Error command exited with value %d\n' % st)
1155 def get_bbox (filename):
1156 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1158 box = open (filename + '.bbox').read()
1159 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1162 gr = map (string.atoi, m.groups ())
1166 def make_pixmap (name):
1167 bbox = get_bbox (name + '.eps')
1169 fo = open (name + '.trans.eps' , 'w')
1170 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1175 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1176 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1178 cmd = r'''gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
1180 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1182 progress ( _("Running %s...") % 'gs')
1183 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1186 status = system (cmd)
1188 os.unlink (name + '.png')
1189 error ("Removing output file")
1191 def compile_all_files (chunks):
1198 if c[0] <> 'lilypond':
1207 if base + '.ly' not in tex:
1208 tex.append (base + '.ly')
1209 elif e == 'png' and g_do_pictures:
1215 # fixme: be sys-independent.
1217 if g_outdir and x[0] <> '/' :
1218 x = os.path.join (g_here_dir, x)
1221 incs = map (incl_opt, include_path)
1222 lilyopts = string.join (incs, ' ' )
1224 lilyopts = lilyopts + ' --dependencies '
1226 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1227 texfiles = string.join (tex, ' ')
1228 cmd = 'lilypond --header=texidoc %s %s %s' \
1229 % (lilyopts, g_extra_opts, texfiles)
1231 progress ( _("Running %s...") % 'LilyPond')
1232 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1236 # Ugh, fixing up dependencies for .tex generation
1239 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1244 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1250 cmd = r"tex '\nonstopmode \input %s'" % e
1252 progress ( _("Running %s...") % 'TeX')
1253 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1256 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1258 progress ( _("Running %s...") % 'dvips')
1259 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1268 def update_file (body, name):
1270 write the body if it has changed
1281 f = open (name , 'w')
1288 def getopt_args (opts):
1289 "Construct arguments (LONG, SHORT) for getopt from list of options."
1294 short = short + o[1]
1302 return (short, long)
1304 def option_help_str (o):
1305 "Transform one option description (4-tuple ) into neatly formatted string"
1323 return ' ' + sh + sep + long + arg
1326 def options_help_str (opts):
1327 "Convert a list of options into a neatly formatted string"
1333 s = option_help_str (o)
1334 strs.append ((s, o[3]))
1340 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1344 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1345 Generate hybrid LaTeX input from Latex + lilypond
1348 sys.stdout.write (options_help_str (option_definitions))
1349 sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1353 Report bugs to bug-lilypond@gnu.org.
1355 Written by Tom Cato Amundsen <tca@gnu.org> and
1356 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1362 def write_deps (fn, target, chunks):
1364 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1365 f = open (os.path.join(g_outdir, fn), 'w')
1366 f.write ('%s%s: ' % (g_dep_prefix, target))
1367 for d in read_files:
1371 if c[0] == 'lilypond':
1372 (type, body, opts, todo, basename) = c;
1373 basenames.append (basename)
1376 d=g_outdir + '/' + d
1378 #if not os.isfile (d): # thinko?
1379 if not re.search ('/', d):
1380 d = g_dep_prefix + d
1381 f.write ('%s.tex ' % d)
1383 #if len (basenames):
1384 # for d in basenames:
1385 # f.write ('%s.ly ' % d)
1386 # f.write (' : %s' % target)
1391 def identify (stream):
1392 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1394 def print_version ():
1395 identify (sys.stdout)
1396 sys.stdout.write (r'''Copyright 1998--1999
1397 Distributed under terms of the GNU General Public License. It comes with
1402 def check_texidoc (chunks):
1405 if c[0] == 'lilypond':
1406 (type, body, opts, todo, basename) = c;
1407 pathbase = os.path.join (g_outdir, basename)
1408 if os.path.isfile (pathbase + '.texidoc'):
1409 body = '\n@include %s.texidoc\n' % basename + body
1410 c = (type, body, opts, todo, basename)
1415 ## what's this? Docme --hwn
1417 def fix_epswidth (chunks):
1420 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1421 newchunks.append (c)
1426 m = re.match ('magnification=([0-9.]+)', o)
1428 mag = string.atof (m.group (1))
1430 def replace_eps_dim (match, lmag = mag):
1431 filename = match.group (1)
1432 dims = bounding_box_dimensions (filename)
1434 return '%fpt' % (dims[0] *lmag)
1436 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1437 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1443 def do_file(input_filename):
1447 my_outname = outname
1448 elif input_filename == '-' or input_filename == "/dev/stdin":
1451 my_outname = os.path.basename (os.path.splitext(input_filename)[0])
1452 my_depname = my_outname + '.dep'
1454 chunks = read_doc_file(input_filename)
1455 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1456 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1457 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1458 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1459 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1460 chunks = chop_chunks(chunks, 'numcols', do_columns)
1462 #for c in chunks: print "c:", c;
1464 scan_preamble(chunks)
1465 chunks = process_lilypond_blocks(my_outname, chunks)
1468 if __main__.g_run_lilypond:
1469 compile_all_files (chunks)
1470 chunks = fix_epswidth (chunks)
1472 if __main__.format == 'texi':
1473 chunks = check_texidoc (chunks)
1476 chunks = completize_preamble (chunks)
1477 if my_outname == '-' or my_outname == '/dev/stdout':
1480 __main__.do_deps = 0
1483 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1484 ## foutn = os.path.join (g_outdir, my_outname)
1485 sys.stderr.write ("Writing `%s'\n" % foutn)
1486 fout = open (foutn, 'w')
1493 write_deps (my_depname, foutn, chunks)
1498 (sh, long) = getopt_args (__main__.option_definitions)
1499 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1500 except getopt.error, msg:
1501 sys.stderr.write("error: %s" % msg)
1509 if o == '--include' or o == '-I':
1510 include_path.append (a)
1511 elif o == '--version' or o == '-v':
1514 elif o == '--verbose' or o == '-V':
1515 __main__.verbose_p = 1
1516 elif o == '--format' or o == '-f':
1518 elif o == '--outname' or o == '-o':
1521 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1524 elif o == '--help' or o == '-h':
1526 elif o == '--no-lily' or o == '-n':
1527 __main__.g_run_lilypond = 0
1528 elif o == '--dependencies' or o == '-M':
1530 elif o == '--default-music-fontsize':
1531 default_music_fontsize = string.atoi (a)
1532 elif o == '--default-lilypond-fontsize':
1533 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1534 default_music_fontsize = string.atoi (a)
1535 elif o == '--extra-options':
1537 elif o == '--force-music-fontsize':
1538 g_force_lilypond_fontsize = string.atoi(a)
1539 elif o == '--force-lilypond-fontsize':
1540 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1541 g_force_lilypond_fontsize = string.atoi(a)
1542 elif o == '--dep-prefix':
1544 elif o == '--no-pictures':
1546 elif o == '--read-lys':
1548 elif o == '--outdir':
1551 identify (sys.stderr)
1553 if os.path.isfile(g_outdir):
1554 error ("outdir is a file: %s" % g_outdir)
1555 if not os.path.exists(g_outdir):
1557 setup_environment ()
1558 for input_filename in files:
1559 do_file(input_filename)
1562 # Petr, ik zou willen dat ik iets zinvoller deed,
1563 # maar wat ik kan ik doen, het verandert toch niets?