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'
55 # Try to cater for bad installations of LilyPond, that have
56 # broken TeX setup. Just hope this doesn't hurt good TeX
57 # setups. Maybe we should check if kpsewhich can find
58 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
62 'MFINPUTS' : datadir + '/mf:',
63 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
64 'TFMFONTS' : datadir + '/tfm:',
65 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
66 'GS_LIB' : datadir + '/ps',
69 def setup_environment ():
70 for key in environment.keys ():
71 val = environment[key]
72 if os.environ.has_key (key):
73 val = val + os.pathsep + os.environ[key]
78 include_path = [os.getcwd()]
81 # g_ is for global (?)
83 g_here_dir = os.getcwd ()
86 g_force_lilypond_fontsize = 0
94 default_music_fontsize = 16
95 default_text_fontsize = 12
98 # this code is ugly. It should be cleaned
102 # the dimensions are from geometry.sty
103 'a0paper': (mm2pt(841), mm2pt(1189)),
104 'a1paper': (mm2pt(595), mm2pt(841)),
105 'a2paper': (mm2pt(420), mm2pt(595)),
106 'a3paper': (mm2pt(297), mm2pt(420)),
107 'a4paper': (mm2pt(210), mm2pt(297)),
108 'a5paper': (mm2pt(149), mm2pt(210)),
109 'b0paper': (mm2pt(1000), mm2pt(1414)),
110 'b1paper': (mm2pt(707), mm2pt(1000)),
111 'b2paper': (mm2pt(500), mm2pt(707)),
112 'b3paper': (mm2pt(353), mm2pt(500)),
113 'b4paper': (mm2pt(250), mm2pt(353)),
114 'b5paper': (mm2pt(176), mm2pt(250)),
115 'letterpaper': (in2pt(8.5), in2pt(11)),
116 'legalpaper': (in2pt(8.5), in2pt(14)),
117 'executivepaper': (in2pt(7.25), in2pt(10.5))}
118 self.m_use_geometry = None
119 self.m_papersize = 'letterpaper'
123 self.m_geo_landscape = 0
124 self.m_geo_width = None
125 self.m_geo_textwidth = None
126 self.m_geo_lmargin = None
127 self.m_geo_rmargin = None
128 self.m_geo_includemp = None
129 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
130 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
131 self.m_geo_x_marginparwidth = None
132 self.m_geo_x_marginparsep = None
134 def set_geo_option(self, name, value):
136 if type(value) == type(""):
137 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
140 num = string.atof(m.group (1))
141 conv = dimension_conversion_dict[m.group(2)]
145 if name == 'body' or name == 'text':
146 if type(value) == type(""):
147 self.m_geo_textwidth = value
149 self.m_geo_textwidth = value[0]
151 elif name == 'portrait':
152 self.m_geo_landscape = 0
153 elif name == 'reversemp' or name == 'reversemarginpar':
154 if self.m_geo_includemp == None:
155 self.m_geo_includemp = 1
156 elif name == 'marginparwidth' or name == 'marginpar':
157 self.m_geo_x_marginparwidth = value
158 self.m_geo_includemp = 1
159 elif name == 'marginparsep':
160 self.m_geo_x_marginparsep = value
161 self.m_geo_includemp = 1
162 elif name == 'scale':
163 if type(value) == type(""):
164 self.m_geo_width = self.get_paperwidth() * float(value)
166 self.m_geo_width = self.get_paperwidth() * float(value[0])
167 elif name == 'hscale':
168 self.m_geo_width = self.get_paperwidth() * float(value)
169 elif name == 'left' or name == 'lmargin':
170 self.m_geo_lmargin = value
171 elif name == 'right' or name == 'rmargin':
172 self.m_geo_rmargin = value
173 elif name == 'hdivide' or name == 'divide':
174 if value[0] not in ('*', ''):
175 self.m_geo_lmargin = value[0]
176 if value[1] not in ('*', ''):
177 self.m_geo_width = value[1]
178 if value[2] not in ('*', ''):
179 self.m_geo_rmargin = value[2]
180 elif name == 'hmargin':
181 if type(value) == type(""):
182 self.m_geo_lmargin = value
183 self.m_geo_rmargin = value
185 self.m_geo_lmargin = value[0]
186 self.m_geo_rmargin = value[1]
187 elif name == 'margin':#ugh there is a bug about this option in
188 # the geometry documentation
189 if type(value) == type(""):
190 self.m_geo_lmargin = value
191 self.m_geo_rmargin = value
193 self.m_geo_lmargin = value[0]
194 self.m_geo_rmargin = value[0]
195 elif name == 'total':
196 if type(value) == type(""):
197 self.m_geo_width = value
199 self.m_geo_width = value[0]
200 elif name == 'width' or name == 'totalwidth':
201 self.m_geo_width = value
202 elif name == 'paper' or name == 'papername':
203 self.m_papersize = value
204 elif name[-5:] == 'paper':
205 self.m_papersize = name
208 # what is _set_dimen ?? /MB
209 #self._set_dimen('m_geo_'+name, value)
210 def __setattr__(self, name, value):
211 if type(value) == type("") and \
212 dimension_conversion_dict.has_key (value[-2:]):
213 f = dimension_conversion_dict[value[-2:]]
214 self.__dict__[name] = f(float(value[:-2]))
216 self.__dict__[name] = value
219 s = "LatexPaper:\n-----------"
220 for v in self.__dict__.keys():
222 s = s + str (v) + ' ' + str (self.__dict__[v])
223 s = s + "-----------"
226 def get_linewidth(self):
227 w = self._calc_linewidth()
228 if self.m_num_cols == 2:
232 def get_paperwidth(self):
233 #if self.m_use_geometry:
234 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
235 #return self.m_paperdef[self.m_papersize][self.m_landscape]
237 def _calc_linewidth(self):
238 # since geometry sometimes ignores 'includemp', this is
239 # more complicated than it should be
241 if self.m_geo_includemp:
242 if self.m_geo_x_marginparsep is not None:
243 mp = mp + self.m_geo_x_marginparsep
245 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
246 if self.m_geo_x_marginparwidth is not None:
247 mp = mp + self.m_geo_x_marginparwidth
249 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
251 #ugh test if this is necessary
255 if not self.m_use_geometry:
256 return latex_linewidths[self.m_papersize][self.m_fontsize]
258 geo_opts = (self.m_geo_lmargin == None,
259 self.m_geo_width == None,
260 self.m_geo_rmargin == None)
262 if geo_opts == (1, 1, 1):
263 if self.m_geo_textwidth:
264 return self.m_geo_textwidth
265 w = self.get_paperwidth() * 0.8
267 elif geo_opts == (0, 1, 1):
268 if self.m_geo_textwidth:
269 return self.m_geo_textwidth
270 return self.f1(self.m_geo_lmargin, mp)
271 elif geo_opts == (1, 1, 0):
272 if self.m_geo_textwidth:
273 return self.m_geo_textwidth
274 return self.f1(self.m_geo_rmargin, mp)
276 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
277 if self.m_geo_textwidth:
278 return self.m_geo_textwidth
279 return self.m_geo_width - mp
280 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
281 w = self.get_paperwidth() \
282 - self.m_geo_lmargin - self.m_geo_rmargin - mp
286 raise "Never do this!"
288 tmp = self.get_paperwidth() - m * 2 - mp
293 tmp = self.get_paperwidth() - self.m_geo_lmargin \
301 self.m_papersize = 'letterpaper'
303 def get_linewidth(self):
304 return texi_linewidths[self.m_papersize][self.m_fontsize]
310 def em2pt(x, fontsize = 10):
311 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
312 def ex2pt(x, fontsize = 10):
313 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
318 dimension_conversion_dict ={
320 'cm': lambda x: mm2pt(10*x),
329 # indices are no. of columns, papersize, fontsize
330 # Why can't this be calculated?
332 'a4paper':{10: 345, 11: 360, 12: 390},
333 'a4paper-landscape': {10: 598, 11: 596, 12:592},
334 'a5paper':{10: 276, 11: 276, 12: 276},
335 'b5paper':{10: 345, 11: 356, 12: 356},
336 'letterpaper':{10: 345, 11: 360, 12: 390},
337 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
338 'legalpaper': {10: 345, 11: 360, 12: 390},
339 'executivepaper':{10: 345, 11: 360, 12: 379}}
342 'afourpaper': {12: mm2pt(160)},
343 'afourwide': {12: in2pt(6.5)},
344 'afourlatex': {12: mm2pt(150)},
345 'smallbook': {12: in2pt(5)},
346 'letterpaper': {12: in2pt(6)}}
348 option_definitions = [
349 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
350 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
351 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
352 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
353 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
354 ('DIR', 'I', 'include', 'include path'),
355 ('', 'M', 'dependencies', 'write dependencies'),
356 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
357 ('', 'n', 'no-lily', 'don\'t run lilypond'),
358 ('', '', 'no-pictures', "don\'t generate pictures"),
359 ('', '', 'read-lys', "don't write ly files."),
360 ('FILE', 'o', 'outname', 'filename main output file'),
361 ('FILE', '', 'outdir', "where to place generated files"),
362 ('', 'v', 'version', 'print version information' ),
363 ('', 'h', 'help', 'print help'),
366 # format specific strings, ie. regex-es for input, and % strings for output
369 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
376 'output-filename' : r'''
379 'output-lilypond': r"""\begin[%s]{lilypond}
382 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
383 'output-default-post': "\\def\postLilypondExample{}\n",
384 'output-default-pre': "\\def\preLilypondExample{}\n",
385 'usepackage-graphics': '\\usepackage{graphics}\n',
386 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
387 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
388 'pagebreak': r'\pagebreak',
390 'texi' : {'output-lilypond': """@lilypond[%s]
394 'output-filename' : r'''
397 'output-lilypond-fragment': """@lilypond[%s]
398 \context Staff\context Voice{ %s }
401 'output-verbatim': r"""@example
406 # do some tweaking: @ is needed in some ps stuff.
407 # override EndLilyPondOutput, since @tex is done
408 # in a sandbox, you can't do \input lilyponddefs at the
409 # top of the document.
411 # should also support fragment in
417 \def\EndLilyPondOutput{}
423 <a href="%(fn)s.png">
424 <img border=0 src="%(fn)s.png" alt="[picture of music]">
431 def output_verbatim (body):
432 if __main__.format == 'texi':
433 body = re.sub ('([@{}])', '@\\1', body)
434 return get_output ('output-verbatim') % body
438 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
439 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
440 'option-sep' : ',\s*',
441 'header': r"\\documentclass\s*(\[.*?\])?",
442 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
443 'preamble-end': r'(?P<code>\\begin{document})',
444 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
445 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
446 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
447 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
448 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
449 'def-post-re': r"\\def\\postLilypondExample",
450 'def-pre-re': r"\\def\\preLilypondExample",
451 'usepackage-graphics': r"\usepackage{graphics}",
452 'intertext': r',?\s*intertext=\".*?\"',
453 'multiline-comment': no_match,
454 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
455 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
459 # why do we have distinction between @mbinclude and @include?
461 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
464 'preamble-end': no_match,
465 'landscape': no_match,
466 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
467 'verb': r"""(?P<code>@code{.*?})""",
468 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
469 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
470 # pyton2.2b2 barfs on this
471 # 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
472 'lilypond-block': r"""(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s)""",
473 'option-sep' : ',\s*',
474 'intertext': r',?\s*intertext=\".*?\"',
475 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
476 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
482 for r in re_dict.keys ():
485 for k in olddict.keys ():
486 newdict[k] = re.compile (olddict[k])
500 def get_output (name):
501 return output_dict[format][name]
504 return re_dict[format][name]
506 def bounding_box_dimensions(fname):
508 fname = os.path.join(g_outdir, fname)
512 error ("Error opening `%s'" % fname)
514 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
516 return (int (s.group (3) - s.group (1) + 0.5),
517 int (s.group (4) - s.group (2) + 0.5))
522 sys.stderr.write (str + "\n Exiting ... \n\n")
526 def compose_full_body (body, opts):
527 """Construct the lilypond code to send to Lilypond.
528 Add stuff to BODY using OPTS as options."""
529 music_size = default_music_fontsize
530 latex_size = default_text_fontsize
532 if g_force_lilypond_fontsize:
533 music_size = g_force_lilypond_fontsize
535 m = re.match ('([0-9]+)pt', o)
537 music_size = string.atoi(m.group (1))
539 m = re.match ('latexfontsize=([0-9]+)pt', o)
541 latex_size = string.atoi (m.group (1))
543 if re.search ('\\\\score', body):
547 if 'fragment' in opts:
549 if 'nofragment' in opts:
552 if is_fragment and not 'multiline' in opts:
553 opts.append('singleline')
554 if 'singleline' in opts:
557 l = __main__.paperguru.get_linewidth()
560 m= re.search ('relative(.*)', o)
564 v = string.atoi (m.group (1))
571 pitch = pitch + '\,' * v
573 pitch = pitch + '\'' * v
575 body = '\\relative %s { %s }' %(pitch, body)
584 optstring = string.join (opts, ' ')
585 optstring = re.sub ('\n', ' ', optstring)
587 %% Generated automatically by: lilypond-book.py
589 \include "paper%d.ly"
590 \paper { linewidth = %f \pt }
591 """ % (optstring, music_size, l) + body
593 # ughUGH not original options
596 def parse_options_string(s):
598 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
599 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
600 r3 = re.compile("(\w+?)((,\s*)|$)")
605 d[m.group(2)] = re.split(",\s*", m.group(3))
610 d[m.group(2)] = m.group(3)
618 error ("format of option string invalid (was `%')" % s)
621 def scan_latex_preamble(chunks):
622 # first we want to scan the \documentclass line
623 # it should be the first non-comment line
626 if chunks[idx][0] == 'ignore':
629 m = get_re ('header').match(chunks[idx][1])
630 if m <> None and m.group (1):
631 options = re.split (',[\n \t]*', m.group(1)[1:-1])
636 paperguru.m_landscape = 1
637 m = re.match("(.*?)paper", o)
639 paperguru.m_papersize = m.group()
641 m = re.match("(\d\d)pt", o)
643 paperguru.m_fontsize = int(m.group(1))
646 while chunks[idx][0] != 'preamble-end':
647 if chunks[idx] == 'ignore':
650 m = get_re ('geometry').search(chunks[idx][1])
652 paperguru.m_use_geometry = 1
653 o = parse_options_string(m.group('options'))
655 paperguru.set_geo_option(k, o[k])
658 def scan_texi_preamble (chunks):
659 # this is not bulletproof..., it checks the first 10 chunks
660 for c in chunks[:10]:
662 for s in ('afourpaper', 'afourwide', 'letterpaper',
663 'afourlatex', 'smallbook'):
664 if string.find(c[1], "@%s" % s) != -1:
665 paperguru.m_papersize = s
667 def scan_preamble (chunks):
668 if __main__.format == 'texi':
669 scan_texi_preamble(chunks)
671 assert __main__.format == 'latex'
672 scan_latex_preamble(chunks)
675 def completize_preamble (chunks):
676 if __main__.format == 'texi':
678 pre_b = post_b = graphics_b = None
680 if chunk[0] == 'preamble-end':
682 if chunk[0] == 'input':
683 m = get_re('def-pre-re').search(chunk[1])
686 if chunk[0] == 'input':
687 m = get_re('def-post-re').search(chunk[1])
690 if chunk[0] == 'input':
691 m = get_re('usepackage-graphics').search(chunk[1])
695 while chunks[x][0] != 'preamble-end':
698 chunks.insert(x, ('input', get_output ('output-default-pre')))
700 chunks.insert(x, ('input', get_output ('output-default-post')))
702 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
707 def find_file (name):
709 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
714 for a in include_path:
716 nm = os.path.join (a, name)
718 __main__.read_files.append (nm)
723 sys.stderr.write ("Reading `%s'\n" % nm)
724 return (f.read (), nm)
726 error ("File not found `%s'\n" % name)
729 def do_ignore(match_object):
730 return [('ignore', match_object.group('code'))]
731 def do_preamble_end(match_object):
732 return [('preamble-end', match_object.group('code'))]
734 def make_verbatim(match_object):
735 return [('verbatim', match_object.group('code'))]
737 def make_verb(match_object):
738 return [('verb', match_object.group('code'))]
740 def do_include_file(m):
742 return [('input', get_output ('pagebreak'))] \
743 + read_doc_file(m.group('filename')) \
744 + [('input', get_output ('pagebreak'))]
746 def do_input_file(m):
747 return read_doc_file(m.group('filename'))
749 def make_lilypond(m):
750 if m.group('options'):
751 options = m.group('options')
754 return [('input', get_output('output-lilypond-fragment') %
755 (options, m.group('code')))]
757 def make_lilypond_file(m):
760 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
761 into a @lilypond .. @end lilypond block.
765 if m.group('options'):
766 options = m.group('options')
769 (content, nm) = find_file(m.group('filename'))
770 options = "filename=%s," % nm + options
772 return [('input', get_output('output-lilypond') %
775 def make_lilypond_block(m):
776 if m.group('options'):
777 options = get_re('option-sep').split (m.group('options'))
780 options = filter(lambda s: s != '', options)
781 return [('lilypond', m.group('code'), options)]
784 if __main__.format != 'latex':
786 if m.group('num') == 'one':
787 return [('numcols', m.group('code'), 1)]
788 if m.group('num') == 'two':
789 return [('numcols', m.group('code'), 2)]
791 def chop_chunks(chunks, re_name, func, use_match=0):
797 m = get_re (re_name).search (str)
799 newchunks.append (('input', str))
803 newchunks.append (('input', str[:m.start ('match')]))
805 newchunks.append (('input', str[:m.start (0)]))
806 #newchunks.extend(func(m))
807 # python 1.5 compatible:
808 newchunks = newchunks + func(m)
809 str = str [m.end(0):]
814 def determine_format (str):
815 if __main__.format == '':
817 latex = re.search ('\\\\document', str[:200])
818 texinfo = re.search ('@node|@setfilename', str[:200])
823 if texinfo and latex == None:
825 elif latex and texinfo == None:
828 error("error: can't determine format, please specify")
831 if __main__.paperguru == None:
832 if __main__.format == 'texi':
837 __main__.paperguru = g
840 def read_doc_file (filename):
841 """Read the input file, find verbatim chunks and do \input and \include
843 (str, path) = find_file(filename)
844 determine_format (str)
846 chunks = [('input', str)]
848 # we have to check for verbatim before doing include,
849 # because we don't want to include files that are mentioned
850 # inside a verbatim environment
851 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
852 chunks = chop_chunks(chunks, 'verb', make_verb)
853 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
855 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
856 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
860 taken_file_names = {}
861 def schedule_lilypond_block (chunk):
862 """Take the body and options from CHUNK, figure out how the
863 real .ly should look, and what should be left MAIN_STR (meant
864 for the main file). The .ly is written, and scheduled in
867 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
869 TODO has format [basename, extension, extension, ... ]
872 (type, body, opts) = chunk
873 assert type == 'lilypond'
874 file_body = compose_full_body (body, opts)
875 basename = 'lily-' + `abs(hash (file_body))`
877 m = re.search ('filename="(.*?)"', o)
879 basename = m.group (1)
880 if not taken_file_names.has_key(basename):
881 taken_file_names[basename] = 0
883 taken_file_names[basename] = taken_file_names[basename] + 1
884 basename = basename + "-%i" % taken_file_names[basename]
886 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
887 needed_filetypes = ['tex']
890 needed_filetypes.append('eps')
891 needed_filetypes.append('png')
892 if 'eps' in opts and not ('eps' in needed_filetypes):
893 needed_filetypes.append('eps')
894 pathbase = os.path.join (g_outdir, basename)
895 def f(base, ext1, ext2):
896 a = os.path.isfile(base + ext2)
897 if (os.path.isfile(base + ext1) and
898 os.path.isfile(base + ext2) and
899 os.stat(base+ext1)[stat.ST_MTIME] >
900 os.stat(base+ext2)[stat.ST_MTIME]) or \
901 not os.path.isfile(base + ext2):
904 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
906 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
908 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
912 if 'printfilename' in opts:
914 m= re.match ("filename=(.*)", o)
916 newbody = newbody + get_output ("output-filename") % m.group(1)
920 if 'verbatim' in opts:
921 newbody = output_verbatim (body)
924 m = re.search ('intertext="(.*?)"', o)
926 newbody = newbody + m.group (1) + "\n\n"
927 if format == 'latex':
932 else: # format == 'texi'
934 newbody = newbody + get_output (s) % {'fn': basename }
935 return ('lilypond', newbody, opts, todo, basename)
937 def process_lilypond_blocks(outname, chunks):#ugh rename
939 # Count sections/chapters.
941 if c[0] == 'lilypond':
942 c = schedule_lilypond_block (c)
943 elif c[0] == 'numcols':
944 paperguru.m_num_cols = c[2]
951 sys.stderr.write ("invoking `%s'\n" % cmd)
954 error ('Error command exited with value %d\n' % st)
958 def get_bbox (filename):
963 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
965 gr = map (string.atoi, m.groups ())
970 def make_pixmap (name):
971 bbox = get_bbox (name + '.eps')
973 fo = open (name + '.trans.eps' , 'w')
974 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
979 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
980 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
982 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
984 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
986 status = system (cmd)
988 os.unlink (name + '.png')
989 error ("Removing output file")
991 def compile_all_files (chunks):
998 if c[0] <> 'lilypond':
1007 if base + '.ly' not in tex:
1008 tex.append (base + '.ly')
1009 elif e == 'png' and g_do_pictures:
1015 # fixme: be sys-independent.
1017 if g_outdir and x[0] <> '/' :
1018 x = os.path.join (g_here_dir, x)
1021 incs = map (incl_opt, include_path)
1022 lilyopts = string.join (incs, ' ' )
1024 lilyopts = lilyopts + ' --dependencies '
1026 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1027 texfiles = string.join (tex, ' ')
1028 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
1031 # Ugh, fixing up dependencies for .tex generation
1034 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1039 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1045 system(r"tex '\nonstopmode \input %s'" % e)
1046 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1054 def update_file (body, name):
1056 write the body if it has changed
1067 f = open (name , 'w')
1074 def getopt_args (opts):
1075 "Construct arguments (LONG, SHORT) for getopt from list of options."
1080 short = short + o[1]
1088 return (short, long)
1090 def option_help_str (o):
1091 "Transform one option description (4-tuple ) into neatly formatted string"
1109 return ' ' + sh + sep + long + arg
1112 def options_help_str (opts):
1113 "Convert a list of options into a neatly formatted string"
1119 s = option_help_str (o)
1120 strs.append ((s, o[3]))
1126 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1130 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1131 Generate hybrid LaTeX input from Latex + lilypond
1134 sys.stdout.write (options_help_str (option_definitions))
1135 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1139 Report bugs to bug-gnu-music@gnu.org.
1141 Written by Tom Cato Amundsen <tca@gnu.org> and
1142 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1148 def write_deps (fn, target, chunks):
1150 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1151 f = open (os.path.join(g_outdir, fn), 'w')
1152 f.write ('%s%s: ' % (g_dep_prefix, target))
1153 for d in read_files:
1157 if c[0] == 'lilypond':
1158 (type, body, opts, todo, basename) = c;
1159 basenames.append (basename)
1162 d=g_outdir + '/' + d
1164 #if not os.isfile (d): # thinko?
1165 if not re.search ('/', d):
1166 d = g_dep_prefix + d
1167 f.write ('%s.tex ' % d)
1169 #if len (basenames):
1170 # for d in basenames:
1171 # f.write ('%s.ly ' % d)
1172 # f.write (' : %s' % target)
1178 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1180 def print_version ():
1182 sys.stdout.write (r"""Copyright 1998--1999
1183 Distributed under terms of the GNU General Public License. It comes with
1188 def check_texidoc (chunks):
1191 if c[0] == 'lilypond':
1192 (type, body, opts, todo, basename) = c;
1193 pathbase = os.path.join (g_outdir, basename)
1194 if os.path.isfile (pathbase + '.texidoc'):
1195 body = '\n@include %s.texidoc\n' % basename + body
1196 c = (type, body, opts, todo, basename)
1201 ## what's this? Docme --hwn
1203 def fix_epswidth (chunks):
1206 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1207 newchunks.append (c)
1212 m = re.match ('magnification=([0-9.]+)', o)
1214 mag = string.atof (m.group (1))
1216 def replace_eps_dim (match, lmag = mag):
1217 filename = match.group (1)
1218 dims = bounding_box_dimensions (filename)
1220 return '%fpt' % (dims[0] *lmag)
1222 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1223 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1229 def do_file(input_filename):
1233 my_outname = outname
1235 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1236 my_depname = my_outname + '.dep'
1238 chunks = read_doc_file(input_filename)
1239 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1240 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1241 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1242 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1243 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1244 chunks = chop_chunks(chunks, 'numcols', do_columns)
1246 #for c in chunks: print "c:", c;
1248 scan_preamble(chunks)
1249 chunks = process_lilypond_blocks(my_outname, chunks)
1251 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1254 if __main__.g_run_lilypond:
1255 compile_all_files (chunks)
1256 chunks = fix_epswidth (chunks)
1258 if __main__.format == 'texi':
1259 chunks = check_texidoc (chunks)
1262 chunks = completize_preamble (chunks)
1263 sys.stderr.write ("Writing `%s'\n" % foutn)
1264 fout = open (foutn, 'w')
1271 write_deps (my_depname, foutn, chunks)
1276 (sh, long) = getopt_args (__main__.option_definitions)
1277 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1278 except getopt.error, msg:
1279 sys.stderr.write("error: %s" % msg)
1287 if o == '--include' or o == '-I':
1288 include_path.append (a)
1289 elif o == '--version' or o == '-v':
1292 elif o == '--format' or o == '-f':
1294 elif o == '--outname' or o == '-o':
1297 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1300 elif o == '--help' or o == '-h':
1302 elif o == '--no-lily' or o == '-n':
1303 __main__.g_run_lilypond = 0
1304 elif o == '--dependencies' or o == '-M':
1306 elif o == '--default-music-fontsize':
1307 default_music_fontsize = string.atoi (a)
1308 elif o == '--default-lilypond-fontsize':
1309 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1310 default_music_fontsize = string.atoi (a)
1311 elif o == '--force-music-fontsize':
1312 g_force_lilypond_fontsize = string.atoi(a)
1313 elif o == '--force-lilypond-fontsize':
1314 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1315 g_force_lilypond_fontsize = string.atoi(a)
1316 elif o == '--dep-prefix':
1318 elif o == '--no-pictures':
1320 elif o == '--read-lys':
1322 elif o == '--outdir':
1327 if os.path.isfile(g_outdir):
1328 error ("outdir is a file: %s" % g_outdir)
1329 if not os.path.exists(g_outdir):
1331 setup_environment ()
1332 for input_filename in files:
1333 do_file(input_filename)
1336 # Petr, ik zou willen dat ik iets zinvoller deed,
1337 # maar wat ik kan ik doen, het verandert toch niets?