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)]
165 if name == 'body' or name == 'text':
166 if type(value) == type(""):
167 self.m_geo_textwidth = value
169 self.m_geo_textwidth = value[0]
171 elif name == 'portrait':
172 self.m_geo_landscape = 0
173 elif name == 'reversemp' or name == 'reversemarginpar':
174 if self.m_geo_includemp == None:
175 self.m_geo_includemp = 1
176 elif name == 'marginparwidth' or name == 'marginpar':
177 self.m_geo_x_marginparwidth = value
178 self.m_geo_includemp = 1
179 elif name == 'marginparsep':
180 self.m_geo_x_marginparsep = value
181 self.m_geo_includemp = 1
182 elif name == 'scale':
183 if type(value) == type(""):
184 self.m_geo_width = self.get_paperwidth() * float(value)
186 self.m_geo_width = self.get_paperwidth() * float(value[0])
187 elif name == 'hscale':
188 self.m_geo_width = self.get_paperwidth() * float(value)
189 elif name == 'left' or name == 'lmargin':
190 self.m_geo_lmargin = value
191 elif name == 'right' or name == 'rmargin':
192 self.m_geo_rmargin = value
193 elif name == 'hdivide' or name == 'divide':
194 if value[0] not in ('*', ''):
195 self.m_geo_lmargin = value[0]
196 if value[1] not in ('*', ''):
197 self.m_geo_width = value[1]
198 if value[2] not in ('*', ''):
199 self.m_geo_rmargin = value[2]
200 elif name == 'hmargin':
201 if type(value) == type(""):
202 self.m_geo_lmargin = value
203 self.m_geo_rmargin = value
205 self.m_geo_lmargin = value[0]
206 self.m_geo_rmargin = value[1]
207 elif name == 'margin':#ugh there is a bug about this option in
208 # the geometry documentation
209 if type(value) == type(""):
210 self.m_geo_lmargin = value
211 self.m_geo_rmargin = value
213 self.m_geo_lmargin = value[0]
214 self.m_geo_rmargin = value[0]
215 elif name == 'total':
216 if type(value) == type(""):
217 self.m_geo_width = value
219 self.m_geo_width = value[0]
220 elif name == 'width' or name == 'totalwidth':
221 self.m_geo_width = value
222 elif name == 'paper' or name == 'papername':
223 self.m_papersize = value
224 elif name[-5:] == 'paper':
225 self.m_papersize = name
228 # what is _set_dimen ?? /MB
229 #self._set_dimen('m_geo_'+name, value)
230 def __setattr__(self, name, value):
231 if type(value) == type("") and \
232 dimension_conversion_dict.has_key (value[-2:]):
233 f = dimension_conversion_dict[value[-2:]]
234 self.__dict__[name] = f(float(value[:-2]))
236 self.__dict__[name] = value
239 s = "LatexPaper:\n-----------"
240 for v in self.__dict__.keys():
242 s = s + str (v) + ' ' + str (self.__dict__[v])
243 s = s + "-----------"
246 def get_linewidth(self):
247 w = self._calc_linewidth()
248 if self.m_num_cols == 2:
252 def get_paperwidth(self):
253 #if self.m_use_geometry:
254 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
255 #return self.m_paperdef[self.m_papersize][self.m_landscape]
257 def _calc_linewidth(self):
258 # since geometry sometimes ignores 'includemp', this is
259 # more complicated than it should be
261 if self.m_geo_includemp:
262 if self.m_geo_x_marginparsep is not None:
263 mp = mp + self.m_geo_x_marginparsep
265 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
266 if self.m_geo_x_marginparwidth is not None:
267 mp = mp + self.m_geo_x_marginparwidth
269 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
271 #ugh test if this is necessary
275 if not self.m_use_geometry:
276 return latex_linewidths[self.m_papersize][self.m_fontsize]
278 geo_opts = (self.m_geo_lmargin == None,
279 self.m_geo_width == None,
280 self.m_geo_rmargin == None)
282 if geo_opts == (1, 1, 1):
283 if self.m_geo_textwidth:
284 return self.m_geo_textwidth
285 w = self.get_paperwidth() * 0.8
287 elif geo_opts == (0, 1, 1):
288 if self.m_geo_textwidth:
289 return self.m_geo_textwidth
290 return self.f1(self.m_geo_lmargin, mp)
291 elif geo_opts == (1, 1, 0):
292 if self.m_geo_textwidth:
293 return self.m_geo_textwidth
294 return self.f1(self.m_geo_rmargin, mp)
296 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
297 if self.m_geo_textwidth:
298 return self.m_geo_textwidth
299 return self.m_geo_width - mp
300 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
301 w = self.get_paperwidth() \
302 - self.m_geo_lmargin - self.m_geo_rmargin - mp
306 raise "Never do this!"
308 tmp = self.get_paperwidth() - m * 2 - mp
313 tmp = self.get_paperwidth() - self.m_geo_lmargin \
321 self.m_papersize = 'letterpaper'
323 def get_linewidth(self):
324 return texi_linewidths[self.m_papersize][self.m_fontsize]
330 def em2pt(x, fontsize = 10):
331 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
332 def ex2pt(x, fontsize = 10):
333 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
338 dimension_conversion_dict ={
340 'cm': lambda x: mm2pt(10*x),
349 # indices are no. of columns, papersize, fontsize
350 # Why can't this be calculated?
352 'a4paper':{10: 345, 11: 360, 12: 390},
353 'a4paper-landscape': {10: 598, 11: 596, 12:592},
354 'a5paper':{10: 276, 11: 276, 12: 276},
355 'b5paper':{10: 345, 11: 356, 12: 356},
356 'letterpaper':{10: 345, 11: 360, 12: 390},
357 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
358 'legalpaper': {10: 345, 11: 360, 12: 390},
359 'executivepaper':{10: 345, 11: 360, 12: 379}}
362 'afourpaper': {12: mm2pt(160)},
363 'afourwide': {12: in2pt(6.5)},
364 'afourlatex': {12: mm2pt(150)},
365 'smallbook': {12: in2pt(5)},
366 'letterpaper': {12: in2pt(6)}}
368 option_definitions = [
369 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
370 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
371 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
372 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
373 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
374 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
375 ('DIR', 'I', 'include', 'include path'),
376 ('', 'M', 'dependencies', 'write dependencies'),
377 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
378 ('', 'n', 'no-lily', 'don\'t run lilypond'),
379 ('', '', 'no-pictures', "don\'t generate pictures"),
380 ('', '', 'read-lys', "don't write ly files."),
381 ('FILE', 'o', 'outname', 'filename main output file'),
382 ('FILE', '', 'outdir', "where to place generated files"),
383 ('', 'v', 'version', 'print version information' ),
384 ('', 'h', 'help', 'print help'),
387 # format specific strings, ie. regex-es for input, and % strings for output
390 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
397 'output-filename' : r'''
400 'output-lilypond': r"""\begin[%s]{lilypond}
403 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
404 'output-default-post': "\\def\postLilypondExample{}\n",
405 'output-default-pre': "\\def\preLilypondExample{}\n",
406 'usepackage-graphics': '\\usepackage{graphics}\n',
407 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
408 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
409 'pagebreak': r'\pagebreak',
411 'texi' : {'output-lilypond': """@lilypond[%s]
415 'output-filename' : r'''
418 'output-lilypond-fragment': """@lilypond[%s]
419 \context Staff\context Voice{ %s }
422 'output-verbatim': r"""@example
427 # do some tweaking: @ is needed in some ps stuff.
428 # override EndLilyPondOutput, since @tex is done
429 # in a sandbox, you can't do \input lilyponddefs at the
430 # top of the document.
432 # should also support fragment in
438 \def\EndLilyPondOutput{}
444 <a href="%(fn)s.png">
445 <img border=0 src="%(fn)s.png" alt="[picture of music]">
452 def output_verbatim (body):
453 if __main__.format == 'texi':
454 body = re.sub ('([@{}])', '@\\1', body)
455 return get_output ('output-verbatim') % body
459 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
460 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
461 'option-sep' : ',\s*',
462 'header': r"\\documentclass\s*(\[.*?\])?",
463 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
464 'preamble-end': r'(?P<code>\\begin{document})',
465 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
466 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
467 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
468 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
469 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
470 'def-post-re': r"\\def\\postLilypondExample",
471 'def-pre-re': r"\\def\\preLilypondExample",
472 'usepackage-graphics': r"\usepackage{graphics}",
473 'intertext': r',?\s*intertext=\".*?\"',
474 'multiline-comment': no_match,
475 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
476 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
480 # why do we have distinction between @mbinclude and @include?
484 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
487 'preamble-end': no_match,
488 'landscape': no_match,
489 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
490 'verb': r"""(?P<code>@code{.*?})""",
491 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
492 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
493 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
494 'option-sep' : ',\s*',
495 'intertext': r',?\s*intertext=\".*?\"',
496 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
497 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
503 for r in re_dict.keys ():
506 for k in olddict.keys ():
508 newdict[k] = re.compile (olddict[k])
510 print 'invalid regexp: %s' % olddict[k]
512 # we'd like to catch and reraise a more detailed error, but
513 # alas, the exceptions changed across the 1.5/2.1 boundary.
528 def get_output (name):
529 return output_dict[format][name]
532 return re_dict[format][name]
534 def bounding_box_dimensions(fname):
536 fname = os.path.join(g_outdir, fname)
540 error ("Error opening `%s'" % fname)
542 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
545 gs = map (lambda x: string.atoi (x), s.groups ())
546 return (int (gs[2] - gs[0] + 0.5),
547 int (gs[3] - gs[1] + 0.5))
552 sys.stderr.write (str + "\n Exiting ... \n\n")
556 def compose_full_body (body, opts):
557 """Construct the lilypond code to send to Lilypond.
558 Add stuff to BODY using OPTS as options."""
559 music_size = default_music_fontsize
560 latex_size = default_text_fontsize
562 if g_force_lilypond_fontsize:
563 music_size = g_force_lilypond_fontsize
565 m = re.match ('([0-9]+)pt', o)
567 music_size = string.atoi(m.group (1))
569 m = re.match ('latexfontsize=([0-9]+)pt', o)
571 latex_size = string.atoi (m.group (1))
573 if re.search ('\\\\score', body):
577 if 'fragment' in opts:
579 if 'nofragment' in opts:
582 if is_fragment and not 'multiline' in opts:
583 opts.append('singleline')
584 if 'singleline' in opts:
587 l = __main__.paperguru.get_linewidth()
590 m= re.search ('relative(.*)', o)
594 v = string.atoi (m.group (1))
601 pitch = pitch + '\,' * v
603 pitch = pitch + '\'' * v
605 body = '\\relative %s { %s }' %(pitch, body)
614 optstring = string.join (opts, ' ')
615 optstring = re.sub ('\n', ' ', optstring)
617 %% Generated automatically by: lilypond-book.py
619 \include "paper%d.ly"
620 \paper { linewidth = %f \pt }
621 """ % (optstring, music_size, l) + body
623 # ughUGH not original options
626 def parse_options_string(s):
628 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
629 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
630 r3 = re.compile("(\w+?)((,\s*)|$)")
635 d[m.group(2)] = re.split(",\s*", m.group(3))
640 d[m.group(2)] = m.group(3)
648 error ("format of option string invalid (was `%')" % s)
651 def scan_latex_preamble(chunks):
652 # first we want to scan the \documentclass line
653 # it should be the first non-comment line
656 if chunks[idx][0] == 'ignore':
659 m = get_re ('header').match(chunks[idx][1])
660 if m <> None and m.group (1):
661 options = re.split (',[\n \t]*', m.group(1)[1:-1])
666 paperguru.m_landscape = 1
667 m = re.match("(.*?)paper", o)
669 paperguru.m_papersize = m.group()
671 m = re.match("(\d\d)pt", o)
673 paperguru.m_fontsize = int(m.group(1))
676 while chunks[idx][0] != 'preamble-end':
677 if chunks[idx] == 'ignore':
680 m = get_re ('geometry').search(chunks[idx][1])
682 paperguru.m_use_geometry = 1
683 o = parse_options_string(m.group('options'))
685 paperguru.set_geo_option(k, o[k])
688 def scan_texi_preamble (chunks):
689 # this is not bulletproof..., it checks the first 10 chunks
690 for c in chunks[:10]:
692 for s in ('afourpaper', 'afourwide', 'letterpaper',
693 'afourlatex', 'smallbook'):
694 if string.find(c[1], "@%s" % s) != -1:
695 paperguru.m_papersize = s
697 def scan_preamble (chunks):
698 if __main__.format == 'texi':
699 scan_texi_preamble(chunks)
701 assert __main__.format == 'latex'
702 scan_latex_preamble(chunks)
705 def completize_preamble (chunks):
706 if __main__.format == 'texi':
708 pre_b = post_b = graphics_b = None
710 if chunk[0] == 'preamble-end':
712 if chunk[0] == 'input':
713 m = get_re('def-pre-re').search(chunk[1])
716 if chunk[0] == 'input':
717 m = get_re('def-post-re').search(chunk[1])
720 if chunk[0] == 'input':
721 m = get_re('usepackage-graphics').search(chunk[1])
725 while chunks[x][0] != 'preamble-end':
728 chunks.insert(x, ('input', get_output ('output-default-pre')))
730 chunks.insert(x, ('input', get_output ('output-default-post')))
732 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
737 def find_file (name):
739 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
744 for a in include_path:
746 nm = os.path.join (a, name)
748 __main__.read_files.append (nm)
753 sys.stderr.write ("Reading `%s'\n" % nm)
754 return (f.read (), nm)
756 error ("File not found `%s'\n" % name)
759 def do_ignore(match_object):
760 return [('ignore', match_object.group('code'))]
761 def do_preamble_end(match_object):
762 return [('preamble-end', match_object.group('code'))]
764 def make_verbatim(match_object):
765 return [('verbatim', match_object.group('code'))]
767 def make_verb(match_object):
768 return [('verb', match_object.group('code'))]
770 def do_include_file(m):
772 return [('input', get_output ('pagebreak'))] \
773 + read_doc_file(m.group('filename')) \
774 + [('input', get_output ('pagebreak'))]
776 def do_input_file(m):
777 return read_doc_file(m.group('filename'))
779 def make_lilypond(m):
780 if m.group('options'):
781 options = m.group('options')
784 return [('input', get_output('output-lilypond-fragment') %
785 (options, m.group('code')))]
787 def make_lilypond_file(m):
790 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
791 into a @lilypond .. @end lilypond block.
795 if m.group('options'):
796 options = m.group('options')
799 (content, nm) = find_file(m.group('filename'))
800 options = "filename=%s," % nm + options
802 return [('input', get_output('output-lilypond') %
805 def make_lilypond_block(m):
806 if m.group('options'):
807 options = get_re('option-sep').split (m.group('options'))
810 options = filter(lambda s: s != '', options)
811 return [('lilypond', m.group('code'), options)]
814 if __main__.format != 'latex':
816 if m.group('num') == 'one':
817 return [('numcols', m.group('code'), 1)]
818 if m.group('num') == 'two':
819 return [('numcols', m.group('code'), 2)]
821 def chop_chunks(chunks, re_name, func, use_match=0):
827 m = get_re (re_name).search (str)
829 newchunks.append (('input', str))
833 newchunks.append (('input', str[:m.start ('match')]))
835 newchunks.append (('input', str[:m.start (0)]))
836 #newchunks.extend(func(m))
837 # python 1.5 compatible:
838 newchunks = newchunks + func(m)
839 str = str [m.end(0):]
844 def determine_format (str):
845 if __main__.format == '':
847 latex = re.search ('\\\\document', str[:200])
848 texinfo = re.search ('@node|@setfilename', str[:200])
853 if texinfo and latex == None:
855 elif latex and texinfo == None:
858 error("error: can't determine format, please specify")
861 if __main__.paperguru == None:
862 if __main__.format == 'texi':
867 __main__.paperguru = g
870 def read_doc_file (filename):
871 """Read the input file, find verbatim chunks and do \input and \include
873 (str, path) = find_file(filename)
874 determine_format (str)
876 chunks = [('input', str)]
878 # we have to check for verbatim before doing include,
879 # because we don't want to include files that are mentioned
880 # inside a verbatim environment
881 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
882 chunks = chop_chunks(chunks, 'verb', make_verb)
883 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
885 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
886 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
890 taken_file_names = {}
891 def schedule_lilypond_block (chunk):
892 """Take the body and options from CHUNK, figure out how the
893 real .ly should look, and what should be left MAIN_STR (meant
894 for the main file). The .ly is written, and scheduled in
897 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
899 TODO has format [basename, extension, extension, ... ]
902 (type, body, opts) = chunk
903 assert type == 'lilypond'
904 file_body = compose_full_body (body, opts)
905 basename = 'lily-' + `abs(hash (file_body))`
907 m = re.search ('filename="(.*?)"', o)
909 basename = m.group (1)
910 if not taken_file_names.has_key(basename):
911 taken_file_names[basename] = 0
913 taken_file_names[basename] = taken_file_names[basename] + 1
914 basename = basename + "-%i" % taken_file_names[basename]
916 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
917 needed_filetypes = ['tex']
920 needed_filetypes.append('eps')
921 needed_filetypes.append('png')
922 if 'eps' in opts and not ('eps' in needed_filetypes):
923 needed_filetypes.append('eps')
924 pathbase = os.path.join (g_outdir, basename)
925 def f(base, ext1, ext2):
926 a = os.path.isfile(base + ext2)
927 if (os.path.isfile(base + ext1) and
928 os.path.isfile(base + ext2) and
929 os.stat(base+ext1)[stat.ST_MTIME] >
930 os.stat(base+ext2)[stat.ST_MTIME]) or \
931 not os.path.isfile(base + ext2):
934 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
936 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
938 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
942 if 'printfilename' in opts:
944 m= re.match ("filename=(.*)", o)
946 newbody = newbody + get_output ("output-filename") % m.group(1)
950 if 'verbatim' in opts:
951 newbody = output_verbatim (body)
954 m = re.search ('intertext="(.*?)"', o)
956 newbody = newbody + m.group (1) + "\n\n"
957 if format == 'latex':
962 else: # format == 'texi'
964 newbody = newbody + get_output (s) % {'fn': basename }
965 return ('lilypond', newbody, opts, todo, basename)
967 def process_lilypond_blocks(outname, chunks):#ugh rename
969 # Count sections/chapters.
971 if c[0] == 'lilypond':
972 c = schedule_lilypond_block (c)
973 elif c[0] == 'numcols':
974 paperguru.m_num_cols = c[2]
981 sys.stderr.write ("invoking `%s'\n" % cmd)
984 error ('Error command exited with value %d\n' % st)
988 def get_bbox (filename):
989 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
991 box = open (filename + '.bbox').read()
992 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
995 gr = map (string.atoi, m.groups ())
999 def make_pixmap (name):
1000 bbox = get_bbox (name + '.eps')
1002 fo = open (name + '.trans.eps' , 'w')
1003 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1008 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1009 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1011 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1013 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1015 status = system (cmd)
1017 os.unlink (name + '.png')
1018 error ("Removing output file")
1020 def compile_all_files (chunks):
1027 if c[0] <> 'lilypond':
1036 if base + '.ly' not in tex:
1037 tex.append (base + '.ly')
1038 elif e == 'png' and g_do_pictures:
1044 # fixme: be sys-independent.
1046 if g_outdir and x[0] <> '/' :
1047 x = os.path.join (g_here_dir, x)
1050 incs = map (incl_opt, include_path)
1051 lilyopts = string.join (incs, ' ' )
1053 lilyopts = lilyopts + ' --dependencies '
1055 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1056 texfiles = string.join (tex, ' ')
1057 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1060 # Ugh, fixing up dependencies for .tex generation
1063 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1068 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1074 system(r"tex '\nonstopmode \input %s'" % e)
1075 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1083 def update_file (body, name):
1085 write the body if it has changed
1096 f = open (name , 'w')
1103 def getopt_args (opts):
1104 "Construct arguments (LONG, SHORT) for getopt from list of options."
1109 short = short + o[1]
1117 return (short, long)
1119 def option_help_str (o):
1120 "Transform one option description (4-tuple ) into neatly formatted string"
1138 return ' ' + sh + sep + long + arg
1141 def options_help_str (opts):
1142 "Convert a list of options into a neatly formatted string"
1148 s = option_help_str (o)
1149 strs.append ((s, o[3]))
1155 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1159 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1160 Generate hybrid LaTeX input from Latex + lilypond
1163 sys.stdout.write (options_help_str (option_definitions))
1164 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1168 Report bugs to bug-lilypond@gnu.org.
1170 Written by Tom Cato Amundsen <tca@gnu.org> and
1171 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1177 def write_deps (fn, target, chunks):
1179 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1180 f = open (os.path.join(g_outdir, fn), 'w')
1181 f.write ('%s%s: ' % (g_dep_prefix, target))
1182 for d in read_files:
1186 if c[0] == 'lilypond':
1187 (type, body, opts, todo, basename) = c;
1188 basenames.append (basename)
1191 d=g_outdir + '/' + d
1193 #if not os.isfile (d): # thinko?
1194 if not re.search ('/', d):
1195 d = g_dep_prefix + d
1196 f.write ('%s.tex ' % d)
1198 #if len (basenames):
1199 # for d in basenames:
1200 # f.write ('%s.ly ' % d)
1201 # f.write (' : %s' % target)
1207 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1209 def print_version ():
1211 sys.stdout.write (r"""Copyright 1998--1999
1212 Distributed under terms of the GNU General Public License. It comes with
1217 def check_texidoc (chunks):
1220 if c[0] == 'lilypond':
1221 (type, body, opts, todo, basename) = c;
1222 pathbase = os.path.join (g_outdir, basename)
1223 if os.path.isfile (pathbase + '.texidoc'):
1224 body = '\n@include %s.texidoc\n' % basename + body
1225 c = (type, body, opts, todo, basename)
1230 ## what's this? Docme --hwn
1232 def fix_epswidth (chunks):
1235 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1236 newchunks.append (c)
1241 m = re.match ('magnification=([0-9.]+)', o)
1243 mag = string.atof (m.group (1))
1245 def replace_eps_dim (match, lmag = mag):
1246 filename = match.group (1)
1247 dims = bounding_box_dimensions (filename)
1249 return '%fpt' % (dims[0] *lmag)
1251 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1252 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1258 def do_file(input_filename):
1262 my_outname = outname
1264 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1265 my_depname = my_outname + '.dep'
1267 chunks = read_doc_file(input_filename)
1268 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1269 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1270 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1271 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1272 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1273 chunks = chop_chunks(chunks, 'numcols', do_columns)
1275 #for c in chunks: print "c:", c;
1277 scan_preamble(chunks)
1278 chunks = process_lilypond_blocks(my_outname, chunks)
1280 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1283 if __main__.g_run_lilypond:
1284 compile_all_files (chunks)
1285 chunks = fix_epswidth (chunks)
1287 if __main__.format == 'texi':
1288 chunks = check_texidoc (chunks)
1291 chunks = completize_preamble (chunks)
1292 sys.stderr.write ("Writing `%s'\n" % foutn)
1293 fout = open (foutn, 'w')
1300 write_deps (my_depname, foutn, chunks)
1305 (sh, long) = getopt_args (__main__.option_definitions)
1306 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1307 except getopt.error, msg:
1308 sys.stderr.write("error: %s" % msg)
1316 if o == '--include' or o == '-I':
1317 include_path.append (a)
1318 elif o == '--version' or o == '-v':
1321 elif o == '--format' or o == '-f':
1323 elif o == '--outname' or o == '-o':
1326 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1329 elif o == '--help' or o == '-h':
1331 elif o == '--no-lily' or o == '-n':
1332 __main__.g_run_lilypond = 0
1333 elif o == '--dependencies' or o == '-M':
1335 elif o == '--default-music-fontsize':
1336 default_music_fontsize = string.atoi (a)
1337 elif o == '--default-lilypond-fontsize':
1338 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1339 default_music_fontsize = string.atoi (a)
1340 elif o == '--extra-options':
1342 elif o == '--force-music-fontsize':
1343 g_force_lilypond_fontsize = string.atoi(a)
1344 elif o == '--force-lilypond-fontsize':
1345 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1346 g_force_lilypond_fontsize = string.atoi(a)
1347 elif o == '--dep-prefix':
1349 elif o == '--no-pictures':
1351 elif o == '--read-lys':
1353 elif o == '--outdir':
1358 if os.path.isfile(g_outdir):
1359 error ("outdir is a file: %s" % g_outdir)
1360 if not os.path.exists(g_outdir):
1362 setup_environment ()
1363 for input_filename in files:
1364 do_file(input_filename)
1367 # Petr, ik zou willen dat ik iets zinvoller deed,
1368 # maar wat ik kan ik doen, het verandert toch niets?