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 program_version = '@TOPLEVEL_VERSION@'
51 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
52 program_version = '1.5.18'
54 # if set, LILYPONDPREFIX must take prevalence
55 # if datadir is not set, we're doing a build and LILYPONDPREFIX
58 if os.environ.has_key ('LILYPONDPREFIX') :
59 datadir = os.environ['LILYPONDPREFIX']
63 while datadir[-1] == os.sep:
66 # Try to cater for bad installations of LilyPond, that have
67 # broken TeX setup. Just hope this doesn't hurt good TeX
68 # setups. Maybe we should check if kpsewhich can find
69 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
71 'MFINPUTS' : datadir + '/mf:',
72 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
73 'TFMFONTS' : datadir + '/tfm:',
74 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
75 'GS_LIB' : datadir + '/ps',
78 # tex needs lots of memory, more than it gets by default on Debian
79 non_path_environment = {
80 'extra_mem_top' : '1000000',
81 'extra_mem_bottom' : '1000000',
82 'pool_size' : '250000',
85 def setup_environment ():
86 for key in environment.keys ():
87 val = environment[key]
88 if os.environ.has_key (key):
89 val = val + os.pathsep + os.environ[key]
92 for key in non_path_environment.keys ():
93 val = non_path_environment[key]
94 print '%s=%s' % (key,val)
97 include_path = [os.getcwd()]
100 # g_ is for global (?)
102 g_here_dir = os.getcwd ()
105 g_force_lilypond_fontsize = 0
114 default_music_fontsize = 16
115 default_text_fontsize = 12
118 # this code is ugly. It should be cleaned
122 # the dimensions are from geometry.sty
123 'a0paper': (mm2pt(841), mm2pt(1189)),
124 'a1paper': (mm2pt(595), mm2pt(841)),
125 'a2paper': (mm2pt(420), mm2pt(595)),
126 'a3paper': (mm2pt(297), mm2pt(420)),
127 'a4paper': (mm2pt(210), mm2pt(297)),
128 'a5paper': (mm2pt(149), mm2pt(210)),
129 'b0paper': (mm2pt(1000), mm2pt(1414)),
130 'b1paper': (mm2pt(707), mm2pt(1000)),
131 'b2paper': (mm2pt(500), mm2pt(707)),
132 'b3paper': (mm2pt(353), mm2pt(500)),
133 'b4paper': (mm2pt(250), mm2pt(353)),
134 'b5paper': (mm2pt(176), mm2pt(250)),
135 'letterpaper': (in2pt(8.5), in2pt(11)),
136 'legalpaper': (in2pt(8.5), in2pt(14)),
137 'executivepaper': (in2pt(7.25), in2pt(10.5))}
138 self.m_use_geometry = None
139 self.m_papersize = 'letterpaper'
143 self.m_geo_landscape = 0
144 self.m_geo_width = None
145 self.m_geo_textwidth = None
146 self.m_geo_lmargin = None
147 self.m_geo_rmargin = None
148 self.m_geo_includemp = None
149 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
150 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
151 self.m_geo_x_marginparwidth = None
152 self.m_geo_x_marginparsep = None
154 def set_geo_option(self, name, value):
156 if type(value) == type(""):
157 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
160 num = string.atof(m.group (1))
161 conv = dimension_conversion_dict[m.group(2)]
166 m = re.match ("^[0-9.]+$",value)
170 if name == 'body' or name == 'text':
171 self.m_geo_textwidth = value
173 elif name == 'portrait':
174 self.m_geo_landscape = 0
175 elif name == 'reversemp' or name == 'reversemarginpar':
176 if self.m_geo_includemp == None:
177 self.m_geo_includemp = 1
178 elif name == 'marginparwidth' or name == 'marginpar':
179 self.m_geo_x_marginparwidth = value
180 self.m_geo_includemp = 1
181 elif name == 'marginparsep':
182 self.m_geo_x_marginparsep = value
183 self.m_geo_includemp = 1
184 elif name == 'scale':
185 self.m_geo_width = self.get_paperwidth() * value
186 elif name == 'hscale':
187 self.m_geo_width = self.get_paperwidth() * value
188 elif name == 'left' or name == 'lmargin':
189 self.m_geo_lmargin = value
190 elif name == 'right' or name == 'rmargin':
191 self.m_geo_rmargin = value
192 elif name == 'hdivide' or name == 'divide':
193 if value[0] not in ('*', ''):
194 self.m_geo_lmargin = value[0]
195 if value[1] not in ('*', ''):
196 self.m_geo_width = value[1]
197 if value[2] not in ('*', ''):
198 self.m_geo_rmargin = value[2]
199 elif name == 'hmargin':
200 self.m_geo_lmargin = value
201 self.m_geo_rmargin = value
202 elif name == 'margin':#ugh there is a bug about this option in
203 # the geometry documentation
204 self.m_geo_lmargin = value
205 self.m_geo_rmargin = value
206 elif name == 'total':
207 self.m_geo_width = value
208 elif name == 'width' or name == 'totalwidth':
209 self.m_geo_width = value
210 elif name == 'paper' or name == 'papername':
211 self.m_papersize = value
212 elif name[-5:] == 'paper':
213 self.m_papersize = name
216 # what is _set_dimen ?? /MB
217 #self._set_dimen('m_geo_'+name, value)
218 def __setattr__(self, name, value):
219 if type(value) == type("") and \
220 dimension_conversion_dict.has_key (value[-2:]):
221 f = dimension_conversion_dict[value[-2:]]
222 self.__dict__[name] = f(float(value[:-2]))
224 self.__dict__[name] = value
227 s = "LatexPaper:\n-----------"
228 for v in self.__dict__.keys():
230 s = s + str (v) + ' ' + str (self.__dict__[v])
231 s = s + "-----------"
234 def get_linewidth(self):
235 w = self._calc_linewidth()
236 if self.m_num_cols == 2:
240 def get_paperwidth(self):
241 #if self.m_use_geometry:
242 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
243 #return self.m_paperdef[self.m_papersize][self.m_landscape]
245 def _calc_linewidth(self):
246 # since geometry sometimes ignores 'includemp', this is
247 # more complicated than it should be
249 if self.m_geo_includemp:
250 if self.m_geo_x_marginparsep is not None:
251 mp = mp + self.m_geo_x_marginparsep
253 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
254 if self.m_geo_x_marginparwidth is not None:
255 mp = mp + self.m_geo_x_marginparwidth
257 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
259 #ugh test if this is necessary
263 if not self.m_use_geometry:
264 return latex_linewidths[self.m_papersize][self.m_fontsize]
266 geo_opts = (self.m_geo_lmargin == None,
267 self.m_geo_width == None,
268 self.m_geo_rmargin == None)
270 if geo_opts == (1, 1, 1):
271 if self.m_geo_textwidth:
272 return self.m_geo_textwidth
273 w = self.get_paperwidth() * 0.8
275 elif geo_opts == (0, 1, 1):
276 if self.m_geo_textwidth:
277 return self.m_geo_textwidth
278 return self.f1(self.m_geo_lmargin, mp)
279 elif geo_opts == (1, 1, 0):
280 if self.m_geo_textwidth:
281 return self.m_geo_textwidth
282 return self.f1(self.m_geo_rmargin, mp)
284 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
285 if self.m_geo_textwidth:
286 return self.m_geo_textwidth
287 return self.m_geo_width - mp
288 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
289 w = self.get_paperwidth() \
290 - self.m_geo_lmargin - self.m_geo_rmargin - mp
294 raise "Never do this!"
296 tmp = self.get_paperwidth() - m * 2 - mp
301 tmp = self.get_paperwidth() - self.m_geo_lmargin \
309 self.m_papersize = 'letterpaper'
311 def get_linewidth(self):
312 return texi_linewidths[self.m_papersize][self.m_fontsize]
318 def em2pt(x, fontsize = 10):
319 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
320 def ex2pt(x, fontsize = 10):
321 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
326 dimension_conversion_dict ={
328 'cm': lambda x: mm2pt(10*x),
337 # indices are no. of columns, papersize, fontsize
338 # Why can't this be calculated?
340 'a4paper':{10: 345, 11: 360, 12: 390},
341 'a4paper-landscape': {10: 598, 11: 596, 12:592},
342 'a5paper':{10: 276, 11: 276, 12: 276},
343 'b5paper':{10: 345, 11: 356, 12: 356},
344 'letterpaper':{10: 345, 11: 360, 12: 390},
345 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
346 'legalpaper': {10: 345, 11: 360, 12: 390},
347 'executivepaper':{10: 345, 11: 360, 12: 379}}
350 'afourpaper': {12: mm2pt(160)},
351 'afourwide': {12: in2pt(6.5)},
352 'afourlatex': {12: mm2pt(150)},
353 'smallbook': {12: in2pt(5)},
354 'letterpaper': {12: in2pt(6)}}
356 option_definitions = [
357 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
358 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
359 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
360 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
361 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
362 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
363 ('DIR', 'I', 'include', 'include path'),
364 ('', 'M', 'dependencies', 'write dependencies'),
365 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
366 ('', 'n', 'no-lily', 'don\'t run lilypond'),
367 ('', '', 'no-pictures', "don\'t generate pictures"),
368 ('', '', 'read-lys', "don't write ly files."),
369 ('FILE', 'o', 'outname', 'filename main output file'),
370 ('FILE', '', 'outdir', "where to place generated files"),
371 ('', 'v', 'version', 'print version information' ),
372 ('', 'h', 'help', 'print help'),
375 # format specific strings, ie. regex-es for input, and % strings for output
378 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
385 'output-filename' : r'''
388 'output-lilypond': r"""\begin[%s]{lilypond}
391 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
392 'output-default-post': "\\def\postLilypondExample{}\n",
393 'output-default-pre': "\\def\preLilypondExample{}\n",
394 'usepackage-graphics': '\\usepackage{graphics}\n',
395 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
396 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
397 'pagebreak': r'\pagebreak',
399 'texi' : {'output-lilypond': """@lilypond[%s]
403 'output-filename' : r'''
406 'output-lilypond-fragment': """@lilypond[%s]
407 \context Staff\context Voice{ %s }
410 'output-verbatim': r"""@example
415 # do some tweaking: @ is needed in some ps stuff.
416 # override EndLilyPondOutput, since @tex is done
417 # in a sandbox, you can't do \input lilyponddefs at the
418 # top of the document.
420 # should also support fragment in
426 \def\EndLilyPondOutput{}
432 <a href="%(fn)s.png">
433 <img border=0 src="%(fn)s.png" alt="[picture of music]">
440 def output_verbatim (body):
441 if __main__.format == 'texi':
442 body = re.sub ('([@{}])', '@\\1', body)
443 return get_output ('output-verbatim') % body
447 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
448 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
449 'option-sep' : ',\s*',
450 'header': r"\\documentclass\s*(\[.*?\])?",
451 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
452 'preamble-end': r'(?P<code>\\begin{document})',
453 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
454 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
455 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
456 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
457 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
458 'def-post-re': r"\\def\\postLilypondExample",
459 'def-pre-re': r"\\def\\preLilypondExample",
460 'usepackage-graphics': r"\usepackage{graphics}",
461 'intertext': r',?\s*intertext=\".*?\"',
462 'multiline-comment': no_match,
463 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
464 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
468 # why do we have distinction between @mbinclude and @include?
472 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
475 'preamble-end': no_match,
476 'landscape': no_match,
477 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
478 'verb': r"""(?P<code>@code{.*?})""",
479 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
480 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
481 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
482 'option-sep' : ',\s*',
483 'intertext': r',?\s*intertext=\".*?\"',
484 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
485 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
491 for r in re_dict.keys ():
494 for k in olddict.keys ():
496 newdict[k] = re.compile (olddict[k])
498 print 'invalid regexp: %s' % olddict[k]
500 # we'd like to catch and reraise a more detailed error, but
501 # alas, the exceptions changed across the 1.5/2.1 boundary.
516 def get_output (name):
517 return output_dict[format][name]
520 return re_dict[format][name]
522 def bounding_box_dimensions(fname):
524 fname = os.path.join(g_outdir, fname)
528 error ("Error opening `%s'" % fname)
530 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
533 gs = map (lambda x: string.atoi (x), s.groups ())
534 return (int (gs[2] - gs[0] + 0.5),
535 int (gs[3] - gs[1] + 0.5))
540 sys.stderr.write (str + "\n Exiting ... \n\n")
544 def compose_full_body (body, opts):
545 """Construct the lilypond code to send to Lilypond.
546 Add stuff to BODY using OPTS as options."""
547 music_size = default_music_fontsize
548 latex_size = default_text_fontsize
550 if g_force_lilypond_fontsize:
551 music_size = g_force_lilypond_fontsize
553 m = re.match ('([0-9]+)pt', o)
555 music_size = string.atoi(m.group (1))
557 m = re.match ('latexfontsize=([0-9]+)pt', o)
559 latex_size = string.atoi (m.group (1))
561 if re.search ('\\\\score', body):
565 if 'fragment' in opts:
567 if 'nofragment' in opts:
570 if is_fragment and not 'multiline' in opts:
571 opts.append('singleline')
572 if 'singleline' in opts:
575 l = __main__.paperguru.get_linewidth()
578 m= re.search ('relative(.*)', o)
582 v = string.atoi (m.group (1))
589 pitch = pitch + '\,' * v
591 pitch = pitch + '\'' * v
593 body = '\\relative %s { %s }' %(pitch, body)
602 optstring = string.join (opts, ' ')
603 optstring = re.sub ('\n', ' ', optstring)
605 %% Generated automatically by: lilypond-book.py
607 \include "paper%d.ly"
608 \paper { linewidth = %f \pt }
609 """ % (optstring, music_size, l) + body
611 # ughUGH not original options
614 def parse_options_string(s):
616 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
617 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
618 r3 = re.compile("(\w+?)((,\s*)|$)")
623 d[m.group(2)] = re.split(",\s*", m.group(3))
628 d[m.group(2)] = m.group(3)
636 error ("format of option string invalid (was `%')" % s)
639 def scan_latex_preamble(chunks):
640 # first we want to scan the \documentclass line
641 # it should be the first non-comment line
644 if chunks[idx][0] == 'ignore':
647 m = get_re ('header').match(chunks[idx][1])
648 if m <> None and m.group (1):
649 options = re.split (',[\n \t]*', m.group(1)[1:-1])
654 paperguru.m_landscape = 1
655 m = re.match("(.*?)paper", o)
657 paperguru.m_papersize = m.group()
659 m = re.match("(\d\d)pt", o)
661 paperguru.m_fontsize = int(m.group(1))
664 while chunks[idx][0] != 'preamble-end':
665 if chunks[idx] == 'ignore':
668 m = get_re ('geometry').search(chunks[idx][1])
670 paperguru.m_use_geometry = 1
671 o = parse_options_string(m.group('options'))
673 paperguru.set_geo_option(k, o[k])
676 def scan_texi_preamble (chunks):
677 # this is not bulletproof..., it checks the first 10 chunks
678 for c in chunks[:10]:
680 for s in ('afourpaper', 'afourwide', 'letterpaper',
681 'afourlatex', 'smallbook'):
682 if string.find(c[1], "@%s" % s) != -1:
683 paperguru.m_papersize = s
685 def scan_preamble (chunks):
686 if __main__.format == 'texi':
687 scan_texi_preamble(chunks)
689 assert __main__.format == 'latex'
690 scan_latex_preamble(chunks)
693 def completize_preamble (chunks):
694 if __main__.format == 'texi':
696 pre_b = post_b = graphics_b = None
698 if chunk[0] == 'preamble-end':
700 if chunk[0] == 'input':
701 m = get_re('def-pre-re').search(chunk[1])
704 if chunk[0] == 'input':
705 m = get_re('def-post-re').search(chunk[1])
708 if chunk[0] == 'input':
709 m = get_re('usepackage-graphics').search(chunk[1])
713 while chunks[x][0] != 'preamble-end':
716 chunks.insert(x, ('input', get_output ('output-default-pre')))
718 chunks.insert(x, ('input', get_output ('output-default-post')))
720 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
725 def find_file (name):
727 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
732 for a in include_path:
734 nm = os.path.join (a, name)
736 __main__.read_files.append (nm)
741 sys.stderr.write ("Reading `%s'\n" % nm)
742 return (f.read (), nm)
744 error ("File not found `%s'\n" % name)
747 def do_ignore(match_object):
748 return [('ignore', match_object.group('code'))]
749 def do_preamble_end(match_object):
750 return [('preamble-end', match_object.group('code'))]
752 def make_verbatim(match_object):
753 return [('verbatim', match_object.group('code'))]
755 def make_verb(match_object):
756 return [('verb', match_object.group('code'))]
758 def do_include_file(m):
760 return [('input', get_output ('pagebreak'))] \
761 + read_doc_file(m.group('filename')) \
762 + [('input', get_output ('pagebreak'))]
764 def do_input_file(m):
765 return read_doc_file(m.group('filename'))
767 def make_lilypond(m):
768 if m.group('options'):
769 options = m.group('options')
772 return [('input', get_output('output-lilypond-fragment') %
773 (options, m.group('code')))]
775 def make_lilypond_file(m):
778 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
779 into a @lilypond .. @end lilypond block.
783 if m.group('options'):
784 options = m.group('options')
787 (content, nm) = find_file(m.group('filename'))
788 options = "filename=%s," % nm + options
790 return [('input', get_output('output-lilypond') %
793 def make_lilypond_block(m):
794 if m.group('options'):
795 options = get_re('option-sep').split (m.group('options'))
798 options = filter(lambda s: s != '', options)
799 return [('lilypond', m.group('code'), options)]
802 if __main__.format != 'latex':
804 if m.group('num') == 'one':
805 return [('numcols', m.group('code'), 1)]
806 if m.group('num') == 'two':
807 return [('numcols', m.group('code'), 2)]
809 def chop_chunks(chunks, re_name, func, use_match=0):
815 m = get_re (re_name).search (str)
817 newchunks.append (('input', str))
821 newchunks.append (('input', str[:m.start ('match')]))
823 newchunks.append (('input', str[:m.start (0)]))
824 #newchunks.extend(func(m))
825 # python 1.5 compatible:
826 newchunks = newchunks + func(m)
827 str = str [m.end(0):]
832 def determine_format (str):
833 if __main__.format == '':
835 latex = re.search ('\\\\document', str[:200])
836 texinfo = re.search ('@node|@setfilename', str[:200])
841 if texinfo and latex == None:
843 elif latex and texinfo == None:
846 error("error: can't determine format, please specify")
849 if __main__.paperguru == None:
850 if __main__.format == 'texi':
855 __main__.paperguru = g
858 def read_doc_file (filename):
859 """Read the input file, find verbatim chunks and do \input and \include
861 (str, path) = find_file(filename)
862 determine_format (str)
864 chunks = [('input', str)]
866 # we have to check for verbatim before doing include,
867 # because we don't want to include files that are mentioned
868 # inside a verbatim environment
869 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
870 chunks = chop_chunks(chunks, 'verb', make_verb)
871 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
873 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
874 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
878 taken_file_names = {}
879 def schedule_lilypond_block (chunk):
880 """Take the body and options from CHUNK, figure out how the
881 real .ly should look, and what should be left MAIN_STR (meant
882 for the main file). The .ly is written, and scheduled in
885 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
887 TODO has format [basename, extension, extension, ... ]
890 (type, body, opts) = chunk
891 assert type == 'lilypond'
892 file_body = compose_full_body (body, opts)
893 basename = 'lily-' + `abs(hash (file_body))`
895 m = re.search ('filename="(.*?)"', o)
897 basename = m.group (1)
898 if not taken_file_names.has_key(basename):
899 taken_file_names[basename] = 0
901 taken_file_names[basename] = taken_file_names[basename] + 1
902 basename = basename + "-%i" % taken_file_names[basename]
904 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
905 needed_filetypes = ['tex']
908 needed_filetypes.append('eps')
909 needed_filetypes.append('png')
910 if 'eps' in opts and not ('eps' in needed_filetypes):
911 needed_filetypes.append('eps')
912 pathbase = os.path.join (g_outdir, basename)
913 def f(base, ext1, ext2):
914 a = os.path.isfile(base + ext2)
915 if (os.path.isfile(base + ext1) and
916 os.path.isfile(base + ext2) and
917 os.stat(base+ext1)[stat.ST_MTIME] >
918 os.stat(base+ext2)[stat.ST_MTIME]) or \
919 not os.path.isfile(base + ext2):
922 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
924 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
926 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
930 if 'printfilename' in opts:
932 m= re.match ("filename=(.*)", o)
934 newbody = newbody + get_output ("output-filename") % m.group(1)
938 if 'verbatim' in opts:
939 newbody = output_verbatim (body)
942 m = re.search ('intertext="(.*?)"', o)
944 newbody = newbody + m.group (1) + "\n\n"
945 if format == 'latex':
950 else: # format == 'texi'
952 newbody = newbody + get_output (s) % {'fn': basename }
953 return ('lilypond', newbody, opts, todo, basename)
955 def process_lilypond_blocks(outname, chunks):#ugh rename
957 # Count sections/chapters.
959 if c[0] == 'lilypond':
960 c = schedule_lilypond_block (c)
961 elif c[0] == 'numcols':
962 paperguru.m_num_cols = c[2]
969 sys.stderr.write ("invoking `%s'\n" % cmd)
972 error ('Error command exited with value %d\n' % st)
976 def get_bbox (filename):
977 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
979 box = open (filename + '.bbox').read()
980 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
983 gr = map (string.atoi, m.groups ())
987 def make_pixmap (name):
988 bbox = get_bbox (name + '.eps')
990 fo = open (name + '.trans.eps' , 'w')
991 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
996 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
997 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
999 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1001 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1003 status = system (cmd)
1005 os.unlink (name + '.png')
1006 error ("Removing output file")
1008 def compile_all_files (chunks):
1015 if c[0] <> 'lilypond':
1024 if base + '.ly' not in tex:
1025 tex.append (base + '.ly')
1026 elif e == 'png' and g_do_pictures:
1032 # fixme: be sys-independent.
1034 if g_outdir and x[0] <> '/' :
1035 x = os.path.join (g_here_dir, x)
1038 incs = map (incl_opt, include_path)
1039 lilyopts = string.join (incs, ' ' )
1041 lilyopts = lilyopts + ' --dependencies '
1043 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1044 texfiles = string.join (tex, ' ')
1045 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1048 # Ugh, fixing up dependencies for .tex generation
1051 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1056 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1062 system(r"tex '\nonstopmode \input %s'" % e)
1063 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1071 def update_file (body, name):
1073 write the body if it has changed
1084 f = open (name , 'w')
1091 def getopt_args (opts):
1092 "Construct arguments (LONG, SHORT) for getopt from list of options."
1097 short = short + o[1]
1105 return (short, long)
1107 def option_help_str (o):
1108 "Transform one option description (4-tuple ) into neatly formatted string"
1126 return ' ' + sh + sep + long + arg
1129 def options_help_str (opts):
1130 "Convert a list of options into a neatly formatted string"
1136 s = option_help_str (o)
1137 strs.append ((s, o[3]))
1143 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1147 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1148 Generate hybrid LaTeX input from Latex + lilypond
1151 sys.stdout.write (options_help_str (option_definitions))
1152 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1156 Report bugs to bug-lilypond@gnu.org.
1158 Written by Tom Cato Amundsen <tca@gnu.org> and
1159 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1165 def write_deps (fn, target, chunks):
1167 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1168 f = open (os.path.join(g_outdir, fn), 'w')
1169 f.write ('%s%s: ' % (g_dep_prefix, target))
1170 for d in read_files:
1174 if c[0] == 'lilypond':
1175 (type, body, opts, todo, basename) = c;
1176 basenames.append (basename)
1179 d=g_outdir + '/' + d
1181 #if not os.isfile (d): # thinko?
1182 if not re.search ('/', d):
1183 d = g_dep_prefix + d
1184 f.write ('%s.tex ' % d)
1186 #if len (basenames):
1187 # for d in basenames:
1188 # f.write ('%s.ly ' % d)
1189 # f.write (' : %s' % target)
1195 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1197 def print_version ():
1199 sys.stdout.write (r"""Copyright 1998--1999
1200 Distributed under terms of the GNU General Public License. It comes with
1205 def check_texidoc (chunks):
1208 if c[0] == 'lilypond':
1209 (type, body, opts, todo, basename) = c;
1210 pathbase = os.path.join (g_outdir, basename)
1211 if os.path.isfile (pathbase + '.texidoc'):
1212 body = '\n@include %s.texidoc\n' % basename + body
1213 c = (type, body, opts, todo, basename)
1218 ## what's this? Docme --hwn
1220 def fix_epswidth (chunks):
1223 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1224 newchunks.append (c)
1229 m = re.match ('magnification=([0-9.]+)', o)
1231 mag = string.atof (m.group (1))
1233 def replace_eps_dim (match, lmag = mag):
1234 filename = match.group (1)
1235 dims = bounding_box_dimensions (filename)
1237 return '%fpt' % (dims[0] *lmag)
1239 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1240 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1246 def do_file(input_filename):
1250 my_outname = outname
1252 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1253 my_depname = my_outname + '.dep'
1255 chunks = read_doc_file(input_filename)
1256 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1257 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1258 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1259 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1260 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1261 chunks = chop_chunks(chunks, 'numcols', do_columns)
1263 #for c in chunks: print "c:", c;
1265 scan_preamble(chunks)
1266 chunks = process_lilypond_blocks(my_outname, chunks)
1268 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1271 if __main__.g_run_lilypond:
1272 compile_all_files (chunks)
1273 chunks = fix_epswidth (chunks)
1275 if __main__.format == 'texi':
1276 chunks = check_texidoc (chunks)
1279 chunks = completize_preamble (chunks)
1280 sys.stderr.write ("Writing `%s'\n" % foutn)
1281 fout = open (foutn, 'w')
1288 write_deps (my_depname, foutn, chunks)
1293 (sh, long) = getopt_args (__main__.option_definitions)
1294 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1295 except getopt.error, msg:
1296 sys.stderr.write("error: %s" % msg)
1304 if o == '--include' or o == '-I':
1305 include_path.append (a)
1306 elif o == '--version' or o == '-v':
1309 elif o == '--format' or o == '-f':
1311 elif o == '--outname' or o == '-o':
1314 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1317 elif o == '--help' or o == '-h':
1319 elif o == '--no-lily' or o == '-n':
1320 __main__.g_run_lilypond = 0
1321 elif o == '--dependencies' or o == '-M':
1323 elif o == '--default-music-fontsize':
1324 default_music_fontsize = string.atoi (a)
1325 elif o == '--default-lilypond-fontsize':
1326 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1327 default_music_fontsize = string.atoi (a)
1328 elif o == '--extra-options':
1330 elif o == '--force-music-fontsize':
1331 g_force_lilypond_fontsize = string.atoi(a)
1332 elif o == '--force-lilypond-fontsize':
1333 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1334 g_force_lilypond_fontsize = string.atoi(a)
1335 elif o == '--dep-prefix':
1337 elif o == '--no-pictures':
1339 elif o == '--read-lys':
1341 elif o == '--outdir':
1346 if os.path.isfile(g_outdir):
1347 error ("outdir is a file: %s" % g_outdir)
1348 if not os.path.exists(g_outdir):
1350 setup_environment ()
1351 for input_filename in files:
1352 do_file(input_filename)
1355 # Petr, ik zou willen dat ik iets zinvoller deed,
1356 # maar wat ik kan ik doen, het verandert toch niets?