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
113 default_music_fontsize = 16
114 default_text_fontsize = 12
117 # this code is ugly. It should be cleaned
121 # the dimensions are from geometry.sty
122 'a0paper': (mm2pt(841), mm2pt(1189)),
123 'a1paper': (mm2pt(595), mm2pt(841)),
124 'a2paper': (mm2pt(420), mm2pt(595)),
125 'a3paper': (mm2pt(297), mm2pt(420)),
126 'a4paper': (mm2pt(210), mm2pt(297)),
127 'a5paper': (mm2pt(149), mm2pt(210)),
128 'b0paper': (mm2pt(1000), mm2pt(1414)),
129 'b1paper': (mm2pt(707), mm2pt(1000)),
130 'b2paper': (mm2pt(500), mm2pt(707)),
131 'b3paper': (mm2pt(353), mm2pt(500)),
132 'b4paper': (mm2pt(250), mm2pt(353)),
133 'b5paper': (mm2pt(176), mm2pt(250)),
134 'letterpaper': (in2pt(8.5), in2pt(11)),
135 'legalpaper': (in2pt(8.5), in2pt(14)),
136 'executivepaper': (in2pt(7.25), in2pt(10.5))}
137 self.m_use_geometry = None
138 self.m_papersize = 'letterpaper'
142 self.m_geo_landscape = 0
143 self.m_geo_width = None
144 self.m_geo_textwidth = None
145 self.m_geo_lmargin = None
146 self.m_geo_rmargin = None
147 self.m_geo_includemp = None
148 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
149 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
150 self.m_geo_x_marginparwidth = None
151 self.m_geo_x_marginparsep = None
153 def set_geo_option(self, name, value):
155 if type(value) == type(""):
156 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
159 num = string.atof(m.group (1))
160 conv = dimension_conversion_dict[m.group(2)]
164 if name == 'body' or name == 'text':
165 if type(value) == type(""):
166 self.m_geo_textwidth = value
168 self.m_geo_textwidth = value[0]
170 elif name == 'portrait':
171 self.m_geo_landscape = 0
172 elif name == 'reversemp' or name == 'reversemarginpar':
173 if self.m_geo_includemp == None:
174 self.m_geo_includemp = 1
175 elif name == 'marginparwidth' or name == 'marginpar':
176 self.m_geo_x_marginparwidth = value
177 self.m_geo_includemp = 1
178 elif name == 'marginparsep':
179 self.m_geo_x_marginparsep = value
180 self.m_geo_includemp = 1
181 elif name == 'scale':
182 if type(value) == type(""):
183 self.m_geo_width = self.get_paperwidth() * float(value)
185 self.m_geo_width = self.get_paperwidth() * float(value[0])
186 elif name == 'hscale':
187 self.m_geo_width = self.get_paperwidth() * float(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 if type(value) == type(""):
201 self.m_geo_lmargin = value
202 self.m_geo_rmargin = value
204 self.m_geo_lmargin = value[0]
205 self.m_geo_rmargin = value[1]
206 elif name == 'margin':#ugh there is a bug about this option in
207 # the geometry documentation
208 if type(value) == type(""):
209 self.m_geo_lmargin = value
210 self.m_geo_rmargin = value
212 self.m_geo_lmargin = value[0]
213 self.m_geo_rmargin = value[0]
214 elif name == 'total':
215 if type(value) == type(""):
216 self.m_geo_width = value
218 self.m_geo_width = value[0]
219 elif name == 'width' or name == 'totalwidth':
220 self.m_geo_width = value
221 elif name == 'paper' or name == 'papername':
222 self.m_papersize = value
223 elif name[-5:] == 'paper':
224 self.m_papersize = name
227 # what is _set_dimen ?? /MB
228 #self._set_dimen('m_geo_'+name, value)
229 def __setattr__(self, name, value):
230 if type(value) == type("") and \
231 dimension_conversion_dict.has_key (value[-2:]):
232 f = dimension_conversion_dict[value[-2:]]
233 self.__dict__[name] = f(float(value[:-2]))
235 self.__dict__[name] = value
238 s = "LatexPaper:\n-----------"
239 for v in self.__dict__.keys():
241 s = s + str (v) + ' ' + str (self.__dict__[v])
242 s = s + "-----------"
245 def get_linewidth(self):
246 w = self._calc_linewidth()
247 if self.m_num_cols == 2:
251 def get_paperwidth(self):
252 #if self.m_use_geometry:
253 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
254 #return self.m_paperdef[self.m_papersize][self.m_landscape]
256 def _calc_linewidth(self):
257 # since geometry sometimes ignores 'includemp', this is
258 # more complicated than it should be
260 if self.m_geo_includemp:
261 if self.m_geo_x_marginparsep is not None:
262 mp = mp + self.m_geo_x_marginparsep
264 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
265 if self.m_geo_x_marginparwidth is not None:
266 mp = mp + self.m_geo_x_marginparwidth
268 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
270 #ugh test if this is necessary
274 if not self.m_use_geometry:
275 return latex_linewidths[self.m_papersize][self.m_fontsize]
277 geo_opts = (self.m_geo_lmargin == None,
278 self.m_geo_width == None,
279 self.m_geo_rmargin == None)
281 if geo_opts == (1, 1, 1):
282 if self.m_geo_textwidth:
283 return self.m_geo_textwidth
284 w = self.get_paperwidth() * 0.8
286 elif geo_opts == (0, 1, 1):
287 if self.m_geo_textwidth:
288 return self.m_geo_textwidth
289 return self.f1(self.m_geo_lmargin, mp)
290 elif geo_opts == (1, 1, 0):
291 if self.m_geo_textwidth:
292 return self.m_geo_textwidth
293 return self.f1(self.m_geo_rmargin, mp)
295 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
296 if self.m_geo_textwidth:
297 return self.m_geo_textwidth
298 return self.m_geo_width - mp
299 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
300 w = self.get_paperwidth() \
301 - self.m_geo_lmargin - self.m_geo_rmargin - mp
305 raise "Never do this!"
307 tmp = self.get_paperwidth() - m * 2 - mp
312 tmp = self.get_paperwidth() - self.m_geo_lmargin \
320 self.m_papersize = 'letterpaper'
322 def get_linewidth(self):
323 return texi_linewidths[self.m_papersize][self.m_fontsize]
329 def em2pt(x, fontsize = 10):
330 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
331 def ex2pt(x, fontsize = 10):
332 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
337 dimension_conversion_dict ={
339 'cm': lambda x: mm2pt(10*x),
348 # indices are no. of columns, papersize, fontsize
349 # Why can't this be calculated?
351 'a4paper':{10: 345, 11: 360, 12: 390},
352 'a4paper-landscape': {10: 598, 11: 596, 12:592},
353 'a5paper':{10: 276, 11: 276, 12: 276},
354 'b5paper':{10: 345, 11: 356, 12: 356},
355 'letterpaper':{10: 345, 11: 360, 12: 390},
356 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
357 'legalpaper': {10: 345, 11: 360, 12: 390},
358 'executivepaper':{10: 345, 11: 360, 12: 379}}
361 'afourpaper': {12: mm2pt(160)},
362 'afourwide': {12: in2pt(6.5)},
363 'afourlatex': {12: mm2pt(150)},
364 'smallbook': {12: in2pt(5)},
365 'letterpaper': {12: in2pt(6)}}
367 option_definitions = [
368 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
369 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
370 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
371 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
372 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
373 ('DIR', 'I', 'include', 'include path'),
374 ('', 'M', 'dependencies', 'write dependencies'),
375 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
376 ('', 'n', 'no-lily', 'don\'t run lilypond'),
377 ('', '', 'no-pictures', "don\'t generate pictures"),
378 ('', '', 'read-lys', "don't write ly files."),
379 ('FILE', 'o', 'outname', 'filename main output file'),
380 ('FILE', '', 'outdir', "where to place generated files"),
381 ('', 'v', 'version', 'print version information' ),
382 ('', 'h', 'help', 'print help'),
385 # format specific strings, ie. regex-es for input, and % strings for output
388 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
395 'output-filename' : r'''
398 'output-lilypond': r"""\begin[%s]{lilypond}
401 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
402 'output-default-post': "\\def\postLilypondExample{}\n",
403 'output-default-pre': "\\def\preLilypondExample{}\n",
404 'usepackage-graphics': '\\usepackage{graphics}\n',
405 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
406 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
407 'pagebreak': r'\pagebreak',
409 'texi' : {'output-lilypond': """@lilypond[%s]
413 'output-filename' : r'''
416 'output-lilypond-fragment': """@lilypond[%s]
417 \context Staff\context Voice{ %s }
420 'output-verbatim': r"""@example
425 # do some tweaking: @ is needed in some ps stuff.
426 # override EndLilyPondOutput, since @tex is done
427 # in a sandbox, you can't do \input lilyponddefs at the
428 # top of the document.
430 # should also support fragment in
436 \def\EndLilyPondOutput{}
442 <a href="%(fn)s.png">
443 <img border=0 src="%(fn)s.png" alt="[picture of music]">
450 def output_verbatim (body):
451 if __main__.format == 'texi':
452 body = re.sub ('([@{}])', '@\\1', body)
453 return get_output ('output-verbatim') % body
457 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
458 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
459 'option-sep' : ',\s*',
460 'header': r"\\documentclass\s*(\[.*?\])?",
461 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
462 'preamble-end': r'(?P<code>\\begin{document})',
463 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
464 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
465 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
466 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
467 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
468 'def-post-re': r"\\def\\postLilypondExample",
469 'def-pre-re': r"\\def\\preLilypondExample",
470 'usepackage-graphics': r"\usepackage{graphics}",
471 'intertext': r',?\s*intertext=\".*?\"',
472 'multiline-comment': no_match,
473 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
474 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
478 # why do we have distinction between @mbinclude and @include?
480 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
483 'preamble-end': no_match,
484 'landscape': no_match,
485 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
486 'verb': r"""(?P<code>@code{.*?})""",
487 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
488 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
489 # pyton2.2b2 barfs on this
490 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
492 # 1.5.2 barfs on this.
493 # 'lilypond-block': r"""(?m)^(?!@c)(?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 ():
507 newdict[k] = re.compile (olddict[k])
521 def get_output (name):
522 return output_dict[format][name]
525 return re_dict[format][name]
527 def bounding_box_dimensions(fname):
529 fname = os.path.join(g_outdir, fname)
533 error ("Error opening `%s'" % fname)
535 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
537 return (int (s.group (3) - s.group (1) + 0.5),
538 int (s.group (4) - s.group (2) + 0.5))
543 sys.stderr.write (str + "\n Exiting ... \n\n")
547 def compose_full_body (body, opts):
548 """Construct the lilypond code to send to Lilypond.
549 Add stuff to BODY using OPTS as options."""
550 music_size = default_music_fontsize
551 latex_size = default_text_fontsize
553 if g_force_lilypond_fontsize:
554 music_size = g_force_lilypond_fontsize
556 m = re.match ('([0-9]+)pt', o)
558 music_size = string.atoi(m.group (1))
560 m = re.match ('latexfontsize=([0-9]+)pt', o)
562 latex_size = string.atoi (m.group (1))
564 if re.search ('\\\\score', body):
568 if 'fragment' in opts:
570 if 'nofragment' in opts:
573 if is_fragment and not 'multiline' in opts:
574 opts.append('singleline')
575 if 'singleline' in opts:
578 l = __main__.paperguru.get_linewidth()
581 m= re.search ('relative(.*)', o)
585 v = string.atoi (m.group (1))
592 pitch = pitch + '\,' * v
594 pitch = pitch + '\'' * v
596 body = '\\relative %s { %s }' %(pitch, body)
605 optstring = string.join (opts, ' ')
606 optstring = re.sub ('\n', ' ', optstring)
608 %% Generated automatically by: lilypond-book.py
610 \include "paper%d.ly"
611 \paper { linewidth = %f \pt }
612 """ % (optstring, music_size, l) + body
614 # ughUGH not original options
617 def parse_options_string(s):
619 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
620 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
621 r3 = re.compile("(\w+?)((,\s*)|$)")
626 d[m.group(2)] = re.split(",\s*", m.group(3))
631 d[m.group(2)] = m.group(3)
639 error ("format of option string invalid (was `%')" % s)
642 def scan_latex_preamble(chunks):
643 # first we want to scan the \documentclass line
644 # it should be the first non-comment line
647 if chunks[idx][0] == 'ignore':
650 m = get_re ('header').match(chunks[idx][1])
651 if m <> None and m.group (1):
652 options = re.split (',[\n \t]*', m.group(1)[1:-1])
657 paperguru.m_landscape = 1
658 m = re.match("(.*?)paper", o)
660 paperguru.m_papersize = m.group()
662 m = re.match("(\d\d)pt", o)
664 paperguru.m_fontsize = int(m.group(1))
667 while chunks[idx][0] != 'preamble-end':
668 if chunks[idx] == 'ignore':
671 m = get_re ('geometry').search(chunks[idx][1])
673 paperguru.m_use_geometry = 1
674 o = parse_options_string(m.group('options'))
676 paperguru.set_geo_option(k, o[k])
679 def scan_texi_preamble (chunks):
680 # this is not bulletproof..., it checks the first 10 chunks
681 for c in chunks[:10]:
683 for s in ('afourpaper', 'afourwide', 'letterpaper',
684 'afourlatex', 'smallbook'):
685 if string.find(c[1], "@%s" % s) != -1:
686 paperguru.m_papersize = s
688 def scan_preamble (chunks):
689 if __main__.format == 'texi':
690 scan_texi_preamble(chunks)
692 assert __main__.format == 'latex'
693 scan_latex_preamble(chunks)
696 def completize_preamble (chunks):
697 if __main__.format == 'texi':
699 pre_b = post_b = graphics_b = None
701 if chunk[0] == 'preamble-end':
703 if chunk[0] == 'input':
704 m = get_re('def-pre-re').search(chunk[1])
707 if chunk[0] == 'input':
708 m = get_re('def-post-re').search(chunk[1])
711 if chunk[0] == 'input':
712 m = get_re('usepackage-graphics').search(chunk[1])
716 while chunks[x][0] != 'preamble-end':
719 chunks.insert(x, ('input', get_output ('output-default-pre')))
721 chunks.insert(x, ('input', get_output ('output-default-post')))
723 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
728 def find_file (name):
730 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
735 for a in include_path:
737 nm = os.path.join (a, name)
739 __main__.read_files.append (nm)
744 sys.stderr.write ("Reading `%s'\n" % nm)
745 return (f.read (), nm)
747 error ("File not found `%s'\n" % name)
750 def do_ignore(match_object):
751 return [('ignore', match_object.group('code'))]
752 def do_preamble_end(match_object):
753 return [('preamble-end', match_object.group('code'))]
755 def make_verbatim(match_object):
756 return [('verbatim', match_object.group('code'))]
758 def make_verb(match_object):
759 return [('verb', match_object.group('code'))]
761 def do_include_file(m):
763 return [('input', get_output ('pagebreak'))] \
764 + read_doc_file(m.group('filename')) \
765 + [('input', get_output ('pagebreak'))]
767 def do_input_file(m):
768 return read_doc_file(m.group('filename'))
770 def make_lilypond(m):
771 if m.group('options'):
772 options = m.group('options')
775 return [('input', get_output('output-lilypond-fragment') %
776 (options, m.group('code')))]
778 def make_lilypond_file(m):
781 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
782 into a @lilypond .. @end lilypond block.
786 if m.group('options'):
787 options = m.group('options')
790 (content, nm) = find_file(m.group('filename'))
791 options = "filename=%s," % nm + options
793 return [('input', get_output('output-lilypond') %
796 def make_lilypond_block(m):
797 if m.group('options'):
798 options = get_re('option-sep').split (m.group('options'))
801 options = filter(lambda s: s != '', options)
802 return [('lilypond', m.group('code'), options)]
805 if __main__.format != 'latex':
807 if m.group('num') == 'one':
808 return [('numcols', m.group('code'), 1)]
809 if m.group('num') == 'two':
810 return [('numcols', m.group('code'), 2)]
812 def chop_chunks(chunks, re_name, func, use_match=0):
818 m = get_re (re_name).search (str)
820 newchunks.append (('input', str))
824 newchunks.append (('input', str[:m.start ('match')]))
826 newchunks.append (('input', str[:m.start (0)]))
827 #newchunks.extend(func(m))
828 # python 1.5 compatible:
829 newchunks = newchunks + func(m)
830 str = str [m.end(0):]
835 def determine_format (str):
836 if __main__.format == '':
838 latex = re.search ('\\\\document', str[:200])
839 texinfo = re.search ('@node|@setfilename', str[:200])
844 if texinfo and latex == None:
846 elif latex and texinfo == None:
849 error("error: can't determine format, please specify")
852 if __main__.paperguru == None:
853 if __main__.format == 'texi':
858 __main__.paperguru = g
861 def read_doc_file (filename):
862 """Read the input file, find verbatim chunks and do \input and \include
864 (str, path) = find_file(filename)
865 determine_format (str)
867 chunks = [('input', str)]
869 # we have to check for verbatim before doing include,
870 # because we don't want to include files that are mentioned
871 # inside a verbatim environment
872 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
873 chunks = chop_chunks(chunks, 'verb', make_verb)
874 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
876 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
877 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
881 taken_file_names = {}
882 def schedule_lilypond_block (chunk):
883 """Take the body and options from CHUNK, figure out how the
884 real .ly should look, and what should be left MAIN_STR (meant
885 for the main file). The .ly is written, and scheduled in
888 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
890 TODO has format [basename, extension, extension, ... ]
893 (type, body, opts) = chunk
894 assert type == 'lilypond'
895 file_body = compose_full_body (body, opts)
896 basename = 'lily-' + `abs(hash (file_body))`
898 m = re.search ('filename="(.*?)"', o)
900 basename = m.group (1)
901 if not taken_file_names.has_key(basename):
902 taken_file_names[basename] = 0
904 taken_file_names[basename] = taken_file_names[basename] + 1
905 basename = basename + "-%i" % taken_file_names[basename]
907 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
908 needed_filetypes = ['tex']
911 needed_filetypes.append('eps')
912 needed_filetypes.append('png')
913 if 'eps' in opts and not ('eps' in needed_filetypes):
914 needed_filetypes.append('eps')
915 pathbase = os.path.join (g_outdir, basename)
916 def f(base, ext1, ext2):
917 a = os.path.isfile(base + ext2)
918 if (os.path.isfile(base + ext1) and
919 os.path.isfile(base + ext2) and
920 os.stat(base+ext1)[stat.ST_MTIME] >
921 os.stat(base+ext2)[stat.ST_MTIME]) or \
922 not os.path.isfile(base + ext2):
925 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
927 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
929 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
933 if 'printfilename' in opts:
935 m= re.match ("filename=(.*)", o)
937 newbody = newbody + get_output ("output-filename") % m.group(1)
941 if 'verbatim' in opts:
942 newbody = output_verbatim (body)
945 m = re.search ('intertext="(.*?)"', o)
947 newbody = newbody + m.group (1) + "\n\n"
948 if format == 'latex':
953 else: # format == 'texi'
955 newbody = newbody + get_output (s) % {'fn': basename }
956 return ('lilypond', newbody, opts, todo, basename)
958 def process_lilypond_blocks(outname, chunks):#ugh rename
960 # Count sections/chapters.
962 if c[0] == 'lilypond':
963 c = schedule_lilypond_block (c)
964 elif c[0] == 'numcols':
965 paperguru.m_num_cols = c[2]
972 sys.stderr.write ("invoking `%s'\n" % cmd)
975 error ('Error command exited with value %d\n' % st)
979 def get_bbox (filename):
984 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
986 gr = map (string.atoi, m.groups ())
991 def make_pixmap (name):
992 bbox = get_bbox (name + '.eps')
994 fo = open (name + '.trans.eps' , 'w')
995 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1000 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1001 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1003 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1005 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1007 status = system (cmd)
1009 os.unlink (name + '.png')
1010 error ("Removing output file")
1012 def compile_all_files (chunks):
1019 if c[0] <> 'lilypond':
1028 if base + '.ly' not in tex:
1029 tex.append (base + '.ly')
1030 elif e == 'png' and g_do_pictures:
1036 # fixme: be sys-independent.
1038 if g_outdir and x[0] <> '/' :
1039 x = os.path.join (g_here_dir, x)
1042 incs = map (incl_opt, include_path)
1043 lilyopts = string.join (incs, ' ' )
1045 lilyopts = lilyopts + ' --dependencies '
1047 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1048 texfiles = string.join (tex, ' ')
1049 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
1052 # Ugh, fixing up dependencies for .tex generation
1055 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1060 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1066 system(r"tex '\nonstopmode \input %s'" % e)
1067 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1075 def update_file (body, name):
1077 write the body if it has changed
1088 f = open (name , 'w')
1095 def getopt_args (opts):
1096 "Construct arguments (LONG, SHORT) for getopt from list of options."
1101 short = short + o[1]
1109 return (short, long)
1111 def option_help_str (o):
1112 "Transform one option description (4-tuple ) into neatly formatted string"
1130 return ' ' + sh + sep + long + arg
1133 def options_help_str (opts):
1134 "Convert a list of options into a neatly formatted string"
1140 s = option_help_str (o)
1141 strs.append ((s, o[3]))
1147 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1151 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1152 Generate hybrid LaTeX input from Latex + lilypond
1155 sys.stdout.write (options_help_str (option_definitions))
1156 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1160 Report bugs to bug-gnu-music@gnu.org.
1162 Written by Tom Cato Amundsen <tca@gnu.org> and
1163 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1169 def write_deps (fn, target, chunks):
1171 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1172 f = open (os.path.join(g_outdir, fn), 'w')
1173 f.write ('%s%s: ' % (g_dep_prefix, target))
1174 for d in read_files:
1178 if c[0] == 'lilypond':
1179 (type, body, opts, todo, basename) = c;
1180 basenames.append (basename)
1183 d=g_outdir + '/' + d
1185 #if not os.isfile (d): # thinko?
1186 if not re.search ('/', d):
1187 d = g_dep_prefix + d
1188 f.write ('%s.tex ' % d)
1190 #if len (basenames):
1191 # for d in basenames:
1192 # f.write ('%s.ly ' % d)
1193 # f.write (' : %s' % target)
1199 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1201 def print_version ():
1203 sys.stdout.write (r"""Copyright 1998--1999
1204 Distributed under terms of the GNU General Public License. It comes with
1209 def check_texidoc (chunks):
1212 if c[0] == 'lilypond':
1213 (type, body, opts, todo, basename) = c;
1214 pathbase = os.path.join (g_outdir, basename)
1215 if os.path.isfile (pathbase + '.texidoc'):
1216 body = '\n@include %s.texidoc\n' % basename + body
1217 c = (type, body, opts, todo, basename)
1222 ## what's this? Docme --hwn
1224 def fix_epswidth (chunks):
1227 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1228 newchunks.append (c)
1233 m = re.match ('magnification=([0-9.]+)', o)
1235 mag = string.atof (m.group (1))
1237 def replace_eps_dim (match, lmag = mag):
1238 filename = match.group (1)
1239 dims = bounding_box_dimensions (filename)
1241 return '%fpt' % (dims[0] *lmag)
1243 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1244 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1250 def do_file(input_filename):
1254 my_outname = outname
1256 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1257 my_depname = my_outname + '.dep'
1259 chunks = read_doc_file(input_filename)
1260 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1261 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1262 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1263 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1264 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1265 chunks = chop_chunks(chunks, 'numcols', do_columns)
1267 #for c in chunks: print "c:", c;
1269 scan_preamble(chunks)
1270 chunks = process_lilypond_blocks(my_outname, chunks)
1272 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1275 if __main__.g_run_lilypond:
1276 compile_all_files (chunks)
1277 chunks = fix_epswidth (chunks)
1279 if __main__.format == 'texi':
1280 chunks = check_texidoc (chunks)
1283 chunks = completize_preamble (chunks)
1284 sys.stderr.write ("Writing `%s'\n" % foutn)
1285 fout = open (foutn, 'w')
1292 write_deps (my_depname, foutn, chunks)
1297 (sh, long) = getopt_args (__main__.option_definitions)
1298 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1299 except getopt.error, msg:
1300 sys.stderr.write("error: %s" % msg)
1308 if o == '--include' or o == '-I':
1309 include_path.append (a)
1310 elif o == '--version' or o == '-v':
1313 elif o == '--format' or o == '-f':
1315 elif o == '--outname' or o == '-o':
1318 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1321 elif o == '--help' or o == '-h':
1323 elif o == '--no-lily' or o == '-n':
1324 __main__.g_run_lilypond = 0
1325 elif o == '--dependencies' or o == '-M':
1327 elif o == '--default-music-fontsize':
1328 default_music_fontsize = string.atoi (a)
1329 elif o == '--default-lilypond-fontsize':
1330 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1331 default_music_fontsize = string.atoi (a)
1332 elif o == '--force-music-fontsize':
1333 g_force_lilypond_fontsize = string.atoi(a)
1334 elif o == '--force-lilypond-fontsize':
1335 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1336 g_force_lilypond_fontsize = string.atoi(a)
1337 elif o == '--dep-prefix':
1339 elif o == '--no-pictures':
1341 elif o == '--read-lys':
1343 elif o == '--outdir':
1348 if os.path.isfile(g_outdir):
1349 error ("outdir is a file: %s" % g_outdir)
1350 if not os.path.exists(g_outdir):
1352 setup_environment ()
1353 for input_filename in files:
1354 do_file(input_filename)
1357 # Petr, ik zou willen dat ik iets zinvoller deed,
1358 # maar wat ik kan ik doen, het verandert toch niets?