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 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
471 'option-sep' : ',\s*',
472 'intertext': r',?\s*intertext=\".*?\"',
473 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
474 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
480 for r in re_dict.keys ():
483 for k in olddict.keys ():
484 newdict[k] = re.compile (olddict[k])
498 def get_output (name):
499 return output_dict[format][name]
502 return re_dict[format][name]
504 def bounding_box_dimensions(fname):
506 fname = os.path.join(g_outdir, fname)
510 error ("Error opening `%s'" % fname)
512 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
514 return (int(s.group(3))-int(s.group(1)),
515 int(s.group(4))-int(s.group(2)))
520 sys.stderr.write (str + "\n Exiting ... \n\n")
524 def compose_full_body (body, opts):
525 """Construct the lilypond code to send to Lilypond.
526 Add stuff to BODY using OPTS as options."""
527 music_size = default_music_fontsize
528 latex_size = default_text_fontsize
530 if g_force_lilypond_fontsize:
531 music_size = g_force_lilypond_fontsize
533 m = re.match ('([0-9]+)pt', o)
535 music_size = string.atoi(m.group (1))
537 m = re.match ('latexfontsize=([0-9]+)pt', o)
539 latex_size = string.atoi (m.group (1))
541 if re.search ('\\\\score', body):
545 if 'fragment' in opts:
547 if 'nofragment' in opts:
550 if is_fragment and not 'multiline' in opts:
551 opts.append('singleline')
552 if 'singleline' in opts:
555 l = __main__.paperguru.get_linewidth()
558 m= re.search ('relative(.*)', o)
562 v = string.atoi (m.group (1))
569 pitch = pitch + '\,' * v
571 pitch = pitch + '\'' * v
573 body = '\\relative %s { %s }' %(pitch, body)
582 optstring = string.join (opts, ' ')
583 optstring = re.sub ('\n', ' ', optstring)
585 %% Generated automatically by: lilypond-book.py
587 \include "paper%d.ly"
588 \paper { linewidth = %f \pt }
589 """ % (optstring, music_size, l) + body
591 # ughUGH not original options
594 def parse_options_string(s):
596 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
597 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
598 r3 = re.compile("(\w+?)((,\s*)|$)")
603 d[m.group(2)] = re.split(",\s*", m.group(3))
608 d[m.group(2)] = m.group(3)
616 error ("format of option string invalid (was `%')" % s)
619 def scan_latex_preamble(chunks):
620 # first we want to scan the \documentclass line
621 # it should be the first non-comment line
624 if chunks[idx][0] == 'ignore':
627 m = get_re ('header').match(chunks[idx][1])
628 if m <> None and m.group (1):
629 options = re.split (',[\n \t]*', m.group(1)[1:-1])
634 paperguru.m_landscape = 1
635 m = re.match("(.*?)paper", o)
637 paperguru.m_papersize = m.group()
639 m = re.match("(\d\d)pt", o)
641 paperguru.m_fontsize = int(m.group(1))
644 while chunks[idx][0] != 'preamble-end':
645 if chunks[idx] == 'ignore':
648 m = get_re ('geometry').search(chunks[idx][1])
650 paperguru.m_use_geometry = 1
651 o = parse_options_string(m.group('options'))
653 paperguru.set_geo_option(k, o[k])
656 def scan_texi_preamble (chunks):
657 # this is not bulletproof..., it checks the first 10 chunks
658 for c in chunks[:10]:
660 for s in ('afourpaper', 'afourwide', 'letterpaper',
661 'afourlatex', 'smallbook'):
662 if string.find(c[1], "@%s" % s) != -1:
663 paperguru.m_papersize = s
665 def scan_preamble (chunks):
666 if __main__.format == 'texi':
667 scan_texi_preamble(chunks)
669 assert __main__.format == 'latex'
670 scan_latex_preamble(chunks)
673 def completize_preamble (chunks):
674 if __main__.format == 'texi':
676 pre_b = post_b = graphics_b = None
678 if chunk[0] == 'preamble-end':
680 if chunk[0] == 'input':
681 m = get_re('def-pre-re').search(chunk[1])
684 if chunk[0] == 'input':
685 m = get_re('def-post-re').search(chunk[1])
688 if chunk[0] == 'input':
689 m = get_re('usepackage-graphics').search(chunk[1])
693 while chunks[x][0] != 'preamble-end':
696 chunks.insert(x, ('input', get_output ('output-default-pre')))
698 chunks.insert(x, ('input', get_output ('output-default-post')))
700 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
705 def find_file (name):
707 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
712 for a in include_path:
714 nm = os.path.join (a, name)
716 __main__.read_files.append (nm)
721 sys.stderr.write ("Reading `%s'\n" % nm)
722 return (f.read (), nm)
724 error ("File not found `%s'\n" % name)
727 def do_ignore(match_object):
728 return [('ignore', match_object.group('code'))]
729 def do_preamble_end(match_object):
730 return [('preamble-end', match_object.group('code'))]
732 def make_verbatim(match_object):
733 return [('verbatim', match_object.group('code'))]
735 def make_verb(match_object):
736 return [('verb', match_object.group('code'))]
738 def do_include_file(m):
740 return [('input', get_output ('pagebreak'))] \
741 + read_doc_file(m.group('filename')) \
742 + [('input', get_output ('pagebreak'))]
744 def do_input_file(m):
745 return read_doc_file(m.group('filename'))
747 def make_lilypond(m):
748 if m.group('options'):
749 options = m.group('options')
752 return [('input', get_output('output-lilypond-fragment') %
753 (options, m.group('code')))]
755 def make_lilypond_file(m):
758 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
759 into a @lilypond .. @end lilypond block.
763 if m.group('options'):
764 options = m.group('options')
767 (content, nm) = find_file(m.group('filename'))
768 options = "filename=%s," % nm + options
770 return [('input', get_output('output-lilypond') %
773 def make_lilypond_block(m):
774 if m.group('options'):
775 options = get_re('option-sep').split (m.group('options'))
778 options = filter(lambda s: s != '', options)
779 return [('lilypond', m.group('code'), options)]
782 if __main__.format != 'latex':
784 if m.group('num') == 'one':
785 return [('numcols', m.group('code'), 1)]
786 if m.group('num') == 'two':
787 return [('numcols', m.group('code'), 2)]
789 def chop_chunks(chunks, re_name, func, use_match=0):
795 m = get_re (re_name).search (str)
797 newchunks.append (('input', str))
801 newchunks.append (('input', str[:m.start ('match')]))
803 newchunks.append (('input', str[:m.start (0)]))
804 #newchunks.extend(func(m))
805 # python 1.5 compatible:
806 newchunks = newchunks + func(m)
807 str = str [m.end(0):]
812 def determine_format (str):
813 if __main__.format == '':
815 latex = re.search ('\\\\document', str[:200])
816 texinfo = re.search ('@node|@setfilename', str[:200])
821 if texinfo and latex == None:
823 elif latex and texinfo == None:
826 error("error: can't determine format, please specify")
829 if __main__.paperguru == None:
830 if __main__.format == 'texi':
835 __main__.paperguru = g
838 def read_doc_file (filename):
839 """Read the input file, find verbatim chunks and do \input and \include
841 (str, path) = find_file(filename)
842 determine_format (str)
844 chunks = [('input', str)]
846 # we have to check for verbatim before doing include,
847 # because we don't want to include files that are mentioned
848 # inside a verbatim environment
849 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
850 chunks = chop_chunks(chunks, 'verb', make_verb)
851 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
853 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
854 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
858 taken_file_names = {}
859 def schedule_lilypond_block (chunk):
860 """Take the body and options from CHUNK, figure out how the
861 real .ly should look, and what should be left MAIN_STR (meant
862 for the main file). The .ly is written, and scheduled in
865 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
867 TODO has format [basename, extension, extension, ... ]
870 (type, body, opts) = chunk
871 assert type == 'lilypond'
872 file_body = compose_full_body (body, opts)
873 basename = 'lily-' + `abs(hash (file_body))`
875 m = re.search ('filename="(.*?)"', o)
877 basename = m.group (1)
878 if not taken_file_names.has_key(basename):
879 taken_file_names[basename] = 0
881 taken_file_names[basename] = taken_file_names[basename] + 1
882 basename = basename + "-%i" % taken_file_names[basename]
884 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
885 needed_filetypes = ['tex']
888 needed_filetypes.append('eps')
889 needed_filetypes.append('png')
890 if 'eps' in opts and not ('eps' in needed_filetypes):
891 needed_filetypes.append('eps')
892 pathbase = os.path.join (g_outdir, basename)
893 def f(base, ext1, ext2):
894 a = os.path.isfile(base + ext2)
895 if (os.path.isfile(base + ext1) and
896 os.path.isfile(base + ext2) and
897 os.stat(base+ext1)[stat.ST_MTIME] >
898 os.stat(base+ext2)[stat.ST_MTIME]) or \
899 not os.path.isfile(base + ext2):
902 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
904 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
906 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
910 if 'printfilename' in opts:
912 m= re.match ("filename=(.*)", o)
914 newbody = newbody + get_output ("output-filename") % m.group(1)
918 if 'verbatim' in opts:
919 newbody = output_verbatim (body)
922 m = re.search ('intertext="(.*?)"', o)
924 newbody = newbody + m.group (1) + "\n\n"
925 if format == 'latex':
930 else: # format == 'texi'
932 newbody = newbody + get_output (s) % {'fn': basename }
933 return ('lilypond', newbody, opts, todo, basename)
935 def process_lilypond_blocks(outname, chunks):#ugh rename
937 # Count sections/chapters.
939 if c[0] == 'lilypond':
940 c = schedule_lilypond_block (c)
941 elif c[0] == 'numcols':
942 paperguru.m_num_cols = c[2]
949 sys.stderr.write ("invoking `%s'\n" % cmd)
952 error ('Error command exited with value %d\n' % st)
956 def get_bbox (filename):
961 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
963 gr = map (string.atoi, m.groups ())
968 def make_pixmap (name):
969 bbox = get_bbox (name + '.eps')
971 fo = open (name + '.trans.eps' , 'w')
972 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
977 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
978 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
980 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
982 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
984 status = system (cmd)
986 os.unlink (name + '.png')
987 error ("Removing output file")
989 def compile_all_files (chunks):
996 if c[0] <> 'lilypond':
1005 if base + '.ly' not in tex:
1006 tex.append (base + '.ly')
1007 elif e == 'png' and g_do_pictures:
1013 # fixme: be sys-independent.
1015 if g_outdir and x[0] <> '/' :
1016 x = os.path.join (g_here_dir, x)
1019 incs = map (incl_opt, include_path)
1020 lilyopts = string.join (incs, ' ' )
1022 lilyopts = lilyopts + ' --dependencies '
1024 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1025 texfiles = string.join (tex, ' ')
1026 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
1029 # Ugh, fixing up dependencies for .tex generation
1032 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1037 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1043 system(r"tex '\nonstopmode \input %s'" % e)
1044 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1052 def update_file (body, name):
1054 write the body if it has changed
1065 f = open (name , 'w')
1072 def getopt_args (opts):
1073 "Construct arguments (LONG, SHORT) for getopt from list of options."
1078 short = short + o[1]
1086 return (short, long)
1088 def option_help_str (o):
1089 "Transform one option description (4-tuple ) into neatly formatted string"
1107 return ' ' + sh + sep + long + arg
1110 def options_help_str (opts):
1111 "Convert a list of options into a neatly formatted string"
1117 s = option_help_str (o)
1118 strs.append ((s, o[3]))
1124 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1128 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1129 Generate hybrid LaTeX input from Latex + lilypond
1132 sys.stdout.write (options_help_str (option_definitions))
1133 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1137 Report bugs to bug-gnu-music@gnu.org.
1139 Written by Tom Cato Amundsen <tca@gnu.org> and
1140 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1146 def write_deps (fn, target, chunks):
1148 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1149 f = open (os.path.join(g_outdir, fn), 'w')
1150 f.write ('%s%s: ' % (g_dep_prefix, target))
1151 for d in read_files:
1155 if c[0] == 'lilypond':
1156 (type, body, opts, todo, basename) = c;
1157 basenames.append (basename)
1160 d=g_outdir + '/' + d
1162 #if not os.isfile (d): # thinko?
1163 if not re.search ('/', d):
1164 d = g_dep_prefix + d
1165 f.write ('%s.tex ' % d)
1167 #if len (basenames):
1168 # for d in basenames:
1169 # f.write ('%s.ly ' % d)
1170 # f.write (' : %s' % target)
1176 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1178 def print_version ():
1180 sys.stdout.write (r"""Copyright 1998--1999
1181 Distributed under terms of the GNU General Public License. It comes with
1186 def check_texidoc (chunks):
1189 if c[0] == 'lilypond':
1190 (type, body, opts, todo, basename) = c;
1191 pathbase = os.path.join (g_outdir, basename)
1192 if os.path.isfile (pathbase + '.texidoc'):
1193 body = '\n@include %s.texidoc\n' % basename + body
1194 c = (type, body, opts, todo, basename)
1199 ## what's this? Docme --hwn
1201 def fix_epswidth (chunks):
1204 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1205 newchunks.append (c)
1210 m = re.match ('magnification=([0-9.]+)', o)
1212 mag = string.atof (m.group (1))
1214 def replace_eps_dim (match, lmag = mag):
1215 filename = match.group (1)
1216 dims = bounding_box_dimensions (filename)
1218 return '%fpt' % (dims[0] *lmag)
1220 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1221 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1227 def do_file(input_filename):
1231 my_outname = outname
1233 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1234 my_depname = my_outname + '.dep'
1236 chunks = read_doc_file(input_filename)
1237 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1238 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1239 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1240 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1241 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1242 chunks = chop_chunks(chunks, 'numcols', do_columns)
1244 #for c in chunks: print "c:", c;
1246 scan_preamble(chunks)
1247 chunks = process_lilypond_blocks(my_outname, chunks)
1249 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1252 if __main__.g_run_lilypond:
1253 compile_all_files (chunks)
1254 chunks = fix_epswidth (chunks)
1256 if __main__.format == 'texi':
1257 chunks = check_texidoc (chunks)
1260 chunks = completize_preamble (chunks)
1261 sys.stderr.write ("Writing `%s'\n" % foutn)
1262 fout = open (foutn, 'w')
1269 write_deps (my_depname, foutn, chunks)
1274 (sh, long) = getopt_args (__main__.option_definitions)
1275 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1276 except getopt.error, msg:
1277 sys.stderr.write("error: %s" % msg)
1285 if o == '--include' or o == '-I':
1286 include_path.append (a)
1287 elif o == '--version' or o == '-v':
1290 elif o == '--format' or o == '-f':
1292 elif o == '--outname' or o == '-o':
1295 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1298 elif o == '--help' or o == '-h':
1300 elif o == '--no-lily' or o == '-n':
1301 __main__.g_run_lilypond = 0
1302 elif o == '--dependencies' or o == '-M':
1304 elif o == '--default-music-fontsize':
1305 default_music_fontsize = string.atoi (a)
1306 elif o == '--default-lilypond-fontsize':
1307 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1308 default_music_fontsize = string.atoi (a)
1309 elif o == '--force-music-fontsize':
1310 g_force_lilypond_fontsize = string.atoi(a)
1311 elif o == '--force-lilypond-fontsize':
1312 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1313 g_force_lilypond_fontsize = string.atoi(a)
1314 elif o == '--dep-prefix':
1316 elif o == '--no-pictures':
1318 elif o == '--read-lys':
1320 elif o == '--outdir':
1325 if os.path.isfile(g_outdir):
1326 error ("outdir is a file: %s" % g_outdir)
1327 if not os.path.exists(g_outdir):
1329 setup_environment ()
1330 for input_filename in files:
1331 do_file(input_filename)
1334 # Petr, ik zou willen dat ik iets zinvoller deed,
1335 # maar wat ik kan ik doen, het verandert toch niets?