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
15 # This is was the idea for handling of comments:
16 # Multiline comments, @ignore .. @end ignore is scanned for
17 # in read_doc_file, and the chunks are marked as 'ignore', so
18 # lilypond-book will not touch them any more. The content of the
19 # chunks are written to the output file. Also 'include' and 'input'
20 # regex has to check if they are commented out.
22 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
23 # These three regex's has to check if they are on a commented line,
24 # % for latex, @c for texinfo.
26 # Then lines that are commented out with % (latex) and @c (Texinfo)
27 # are put into chunks marked 'ignore'. This cannot be done before
28 # searching for the lilypond-blocks because % is also the comment character
31 # The the rest of the rexeces are searched for. They don't have to test
32 # if they are on a commented out line.
44 program_version = '@TOPLEVEL_VERSION@'
45 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
46 program_version = '1.5.18'
49 # Try to cater for bad installations of LilyPond, that have
50 # broken TeX setup. Just hope this doesn't hurt good TeX
51 # setups. Maybe we should check if kpsewhich can find
52 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
56 'MFINPUTS' : datadir + '/mf:',
57 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
58 'TFMFONTS' : datadir + '/tfm:',
59 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
60 'GS_LIB' : datadir + '/ps',
63 def setup_environment ():
64 for key in environment.keys ():
65 val = environment[key]
66 if os.environ.has_key (key):
67 val = val + os.pathsep + os.environ[key]
72 include_path = [os.getcwd()]
75 # g_ is for global (?)
77 g_here_dir = os.getcwd ()
80 g_force_lilypond_fontsize = 0
88 default_music_fontsize = 16
89 default_text_fontsize = 12
92 # this code is ugly. It should be cleaned
96 # the dimensions are from geometry.sty
97 'a0paper': (mm2pt(841), mm2pt(1189)),
98 'a1paper': (mm2pt(595), mm2pt(841)),
99 'a2paper': (mm2pt(420), mm2pt(595)),
100 'a3paper': (mm2pt(297), mm2pt(420)),
101 'a4paper': (mm2pt(210), mm2pt(297)),
102 'a5paper': (mm2pt(149), mm2pt(210)),
103 'b0paper': (mm2pt(1000), mm2pt(1414)),
104 'b1paper': (mm2pt(707), mm2pt(1000)),
105 'b2paper': (mm2pt(500), mm2pt(707)),
106 'b3paper': (mm2pt(353), mm2pt(500)),
107 'b4paper': (mm2pt(250), mm2pt(353)),
108 'b5paper': (mm2pt(176), mm2pt(250)),
109 'letterpaper': (in2pt(8.5), in2pt(11)),
110 'legalpaper': (in2pt(8.5), in2pt(14)),
111 'executivepaper': (in2pt(7.25), in2pt(10.5))}
112 self.m_use_geometry = None
113 self.m_papersize = 'letterpaper'
117 self.m_geo_landscape = 0
118 self.m_geo_width = None
119 self.m_geo_textwidth = None
120 self.m_geo_lmargin = None
121 self.m_geo_rmargin = None
122 self.m_geo_includemp = None
123 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
124 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
125 self.m_geo_x_marginparwidth = None
126 self.m_geo_x_marginparsep = None
128 def set_geo_option(self, name, value):
129 if name == 'body' or name == 'text':
130 if type(value) == type(""):
131 self.m_geo_textwidth = value
133 self.m_geo_textwidth = value[0]
135 elif name == 'portrait':
136 self.m_geo_landscape = 0
137 elif name == 'reversemp' or name == 'reversemarginpar':
138 if self.m_geo_includemp == None:
139 self.m_geo_includemp = 1
140 elif name == 'marginparwidth' or name == 'marginpar':
141 self.m_geo_x_marginparwidth = value
142 self.m_geo_includemp = 1
143 elif name == 'marginparsep':
144 self.m_geo_x_marginparsep = value
145 self.m_geo_includemp = 1
146 elif name == 'scale':
147 if type(value) == type(""):
148 self.m_geo_width = self.get_paperwidth() * float(value)
150 self.m_geo_width = self.get_paperwidth() * float(value[0])
151 elif name == 'hscale':
152 self.m_geo_width = self.get_paperwidth() * float(value)
153 elif name == 'left' or name == 'lmargin':
154 self.m_geo_lmargin = value
155 elif name == 'right' or name == 'rmargin':
156 self.m_geo_rmargin = value
157 elif name == 'hdivide' or name == 'divide':
158 if value[0] not in ('*', ''):
159 self.m_geo_lmargin = value[0]
160 if value[1] not in ('*', ''):
161 self.m_geo_width = value[1]
162 if value[2] not in ('*', ''):
163 self.m_geo_rmargin = value[2]
164 elif name == 'hmargin':
165 if type(value) == type(""):
166 self.m_geo_lmargin = value
167 self.m_geo_rmargin = value
169 self.m_geo_lmargin = value[0]
170 self.m_geo_rmargin = value[1]
171 elif name == 'margin':#ugh there is a bug about this option in
172 # the geometry documentation
173 if type(value) == type(""):
174 self.m_geo_lmargin = value
175 self.m_geo_rmargin = value
177 self.m_geo_lmargin = value[0]
178 self.m_geo_rmargin = value[0]
179 elif name == 'total':
180 if type(value) == type(""):
181 self.m_geo_width = value
183 self.m_geo_width = value[0]
184 elif name == 'width' or name == 'totalwidth':
185 self.m_geo_width = value
186 elif name == 'paper' or name == 'papername':
187 self.m_papersize = value
188 elif name[-5:] == 'paper':
189 self.m_papersize = name
192 # what is _set_dimen ?? /MB
193 #self._set_dimen('m_geo_'+name, value)
194 def __setattr__(self, name, value):
195 if type(value) == type("") and \
196 dimension_conversion_dict.has_key (value[-2:]):
197 f = dimension_conversion_dict[value[-2:]]
198 self.__dict__[name] = f(float(value[:-2]))
200 self.__dict__[name] = value
203 s = "LatexPaper:\n-----------"
204 for v in self.__dict__.keys():
206 s = s + str (v) + ' ' + str (self.__dict__[v])
207 s = s + "-----------"
210 def get_linewidth(self):
211 w = self._calc_linewidth()
212 if self.m_num_cols == 2:
216 def get_paperwidth(self):
217 #if self.m_use_geometry:
218 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
219 #return self.m_paperdef[self.m_papersize][self.m_landscape]
221 def _calc_linewidth(self):
222 # since geometry sometimes ignores 'includemp', this is
223 # more complicated than it should be
225 if self.m_geo_includemp:
226 if self.m_geo_x_marginparsep is not None:
227 mp = mp + self.m_geo_x_marginparsep
229 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
230 if self.m_geo_x_marginparwidth is not None:
231 mp = mp + self.m_geo_x_marginparwidth
233 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
235 #ugh test if this is necessary
239 if not self.m_use_geometry:
240 return latex_linewidths[self.m_papersize][self.m_fontsize]
242 geo_opts = (self.m_geo_lmargin == None,
243 self.m_geo_width == None,
244 self.m_geo_rmargin == None)
246 if geo_opts == (1, 1, 1):
247 if self.m_geo_textwidth:
248 return self.m_geo_textwidth
249 w = self.get_paperwidth() * 0.8
251 elif geo_opts == (0, 1, 1):
252 if self.m_geo_textwidth:
253 return self.m_geo_textwidth
254 return self.f1(self.m_geo_lmargin, mp)
255 elif geo_opts == (1, 1, 0):
256 if self.m_geo_textwidth:
257 return self.m_geo_textwidth
258 return self.f1(self.m_geo_rmargin, mp)
260 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
261 if self.m_geo_textwidth:
262 return self.m_geo_textwidth
263 return self.m_geo_width - mp
264 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
265 w = self.get_paperwidth() \
266 - self.m_geo_lmargin - self.m_geo_rmargin - mp
270 raise "Never do this!"
272 tmp = self.get_paperwidth() - m * 2 - mp
277 tmp = self.get_paperwidth() - self.m_geo_lmargin \
285 self.m_papersize = 'letterpaper'
287 def get_linewidth(self):
288 return texi_linewidths[self.m_papersize][self.m_fontsize]
294 def em2pt(x, fontsize = 10):
295 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
296 def ex2pt(x, fontsize = 10):
297 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
302 dimension_conversion_dict ={
312 # indices are no. of columns, papersize, fontsize
313 # Why can't this be calculated?
315 'a4paper':{10: 345, 11: 360, 12: 390},
316 'a4paper-landscape': {10: 598, 11: 596, 12:592},
317 'a5paper':{10: 276, 11: 276, 12: 276},
318 'b5paper':{10: 345, 11: 356, 12: 356},
319 'letterpaper':{10: 345, 11: 360, 12: 390},
320 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
321 'legalpaper': {10: 345, 11: 360, 12: 390},
322 'executivepaper':{10: 345, 11: 360, 12: 379}}
325 'afourpaper': {12: mm2pt(160)},
326 'afourwide': {12: in2pt(6.5)},
327 'afourlatex': {12: mm2pt(150)},
328 'smallbook': {12: in2pt(5)},
329 'letterpaper': {12: in2pt(6)}}
331 option_definitions = [
332 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
333 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
334 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
335 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
336 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
337 ('DIR', 'I', 'include', 'include path'),
338 ('', 'M', 'dependencies', 'write dependencies'),
339 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
340 ('', 'n', 'no-lily', 'don\'t run lilypond'),
341 ('', '', 'no-pictures', "don\'t generate pictures"),
342 ('', '', 'read-lys', "don't write ly files."),
343 ('FILE', 'o', 'outname', 'filename main output file'),
344 ('FILE', '', 'outdir', "where to place generated files"),
345 ('', 'v', 'version', 'print version information' ),
346 ('', 'h', 'help', 'print help'),
349 # format specific strings, ie. regex-es for input, and % strings for output
352 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
359 'output-filename' : r'''
362 'output-lilypond': r"""\begin[%s]{lilypond}
365 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
366 'output-default-post': "\\def\postLilypondExample{}\n",
367 'output-default-pre': "\\def\preLilypondExample{}\n",
368 'usepackage-graphics': '\\usepackage{graphics}\n',
369 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
370 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
371 'pagebreak': r'\pagebreak',
373 'texi' : {'output-lilypond': """@lilypond[%s]
377 'output-filename' : r'''
380 'output-lilypond-fragment': """@lilypond[%s]
381 \context Staff\context Voice{ %s }
384 'output-verbatim': r"""@example
389 # do some tweaking: @ is needed in some ps stuff.
390 # override EndLilyPondOutput, since @tex is done
391 # in a sandbox, you can't do \input lilyponddefs at the
392 # top of the document.
394 # should also support fragment in
400 \def\EndLilyPondOutput{}
406 <a href="%(fn)s.png">
407 <img border=0 src="%(fn)s.png" alt="[picture of music]">
414 def output_verbatim (body):
415 if __main__.format == 'texi':
416 body = re.sub ('([@{}])', '@\\1', body)
417 return get_output ('output-verbatim') % body
421 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
422 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
423 'option-sep' : ',\s*',
424 'header': r"\\documentclass\s*(\[.*?\])?",
425 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
426 'preamble-end': r'(?P<code>\\begin{document})',
427 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
428 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
429 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
430 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
431 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
432 'def-post-re': r"\\def\\postLilypondExample",
433 'def-pre-re': r"\\def\\preLilypondExample",
434 'usepackage-graphics': r"\usepackage{graphics}",
435 'intertext': r',?\s*intertext=\".*?\"',
436 'multiline-comment': no_match,
437 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
438 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
442 # why do we have distinction between @mbinclude and @include?
444 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
447 'preamble-end': no_match,
448 'landscape': no_match,
449 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
450 'verb': r"""(?P<code>@code{.*?})""",
451 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
452 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
453 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
454 'option-sep' : ',\s*',
455 'intertext': r',?\s*intertext=\".*?\"',
456 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
457 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
463 for r in re_dict.keys ():
466 for k in olddict.keys ():
467 newdict[k] = re.compile (olddict[k])
481 def get_output (name):
482 return output_dict[format][name]
485 return re_dict[format][name]
487 def bounding_box_dimensions(fname):
489 fname = os.path.join(g_outdir, fname)
493 error ("Error opening `%s'" % fname)
495 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
497 return (int(s.group(3))-int(s.group(1)),
498 int(s.group(4))-int(s.group(2)))
503 sys.stderr.write (str + "\n Exiting ... \n\n")
507 def compose_full_body (body, opts):
508 """Construct the lilypond code to send to Lilypond.
509 Add stuff to BODY using OPTS as options."""
510 music_size = default_music_fontsize
511 latex_size = default_text_fontsize
513 if g_force_lilypond_fontsize:
514 music_size = g_force_lilypond_fontsize
516 m = re.match ('([0-9]+)pt', o)
518 music_size = string.atoi(m.group (1))
520 m = re.match ('latexfontsize=([0-9]+)pt', o)
522 latex_size = string.atoi (m.group (1))
524 if re.search ('\\\\score', body):
528 if 'fragment' in opts:
530 if 'nofragment' in opts:
533 if is_fragment and not 'multiline' in opts:
534 opts.append('singleline')
535 if 'singleline' in opts:
538 l = __main__.paperguru.get_linewidth()
541 m= re.search ('relative(.*)', o)
545 v = string.atoi (m.group (1))
552 pitch = pitch + '\,' * v
554 pitch = pitch + '\'' * v
556 body = '\\relative %s { %s }' %(pitch, body)
565 optstring = string.join (opts, ' ')
566 optstring = re.sub ('\n', ' ', optstring)
568 %% Generated automatically by: lilypond-book.py
570 \include "paper%d.ly"
571 \paper { linewidth = %f \pt }
572 """ % (optstring, music_size, l) + body
574 # ughUGH not original options
577 def parse_options_string(s):
579 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
580 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
581 r3 = re.compile("(\w+?)((,\s*)|$)")
586 d[m.group(2)] = re.split(",\s*", m.group(3))
591 d[m.group(2)] = m.group(3)
599 error ("format of option string invalid (was `%')" % s)
602 def scan_latex_preamble(chunks):
603 # first we want to scan the \documentclass line
604 # it should be the first non-comment line
607 if chunks[idx][0] == 'ignore':
610 m = get_re ('header').match(chunks[idx][1])
612 options = re.split (',[\n \t]*', m.group(1)[1:-1])
617 paperguru.m_landscape = 1
618 m = re.match("(.*?)paper", o)
620 paperguru.m_papersize = m.group()
622 m = re.match("(\d\d)pt", o)
624 paperguru.m_fontsize = int(m.group(1))
627 while chunks[idx][0] != 'preamble-end':
628 if chunks[idx] == 'ignore':
631 m = get_re ('geometry').search(chunks[idx][1])
633 paperguru.m_use_geometry = 1
634 o = parse_options_string(m.group('options'))
636 paperguru.set_geo_option(k, o[k])
639 def scan_texi_preamble (chunks):
640 # this is not bulletproof..., it checks the first 10 chunks
641 for c in chunks[:10]:
643 for s in ('afourpaper', 'afourwide', 'letterpaper',
644 'afourlatex', 'smallbook'):
645 if string.find(c[1], "@%s" % s) != -1:
646 paperguru.m_papersize = s
648 def scan_preamble (chunks):
649 if __main__.format == 'texi':
650 scan_texi_preamble(chunks)
652 assert __main__.format == 'latex'
653 scan_latex_preamble(chunks)
656 def completize_preamble (chunks):
657 if __main__.format == 'texi':
659 pre_b = post_b = graphics_b = None
661 if chunk[0] == 'preamble-end':
663 if chunk[0] == 'input':
664 m = get_re('def-pre-re').search(chunk[1])
667 if chunk[0] == 'input':
668 m = get_re('def-post-re').search(chunk[1])
671 if chunk[0] == 'input':
672 m = get_re('usepackage-graphics').search(chunk[1])
676 while chunks[x][0] != 'preamble-end':
679 chunks.insert(x, ('input', get_output ('output-default-pre')))
681 chunks.insert(x, ('input', get_output ('output-default-post')))
683 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
688 def find_file (name):
690 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
695 for a in include_path:
697 nm = os.path.join (a, name)
699 __main__.read_files.append (nm)
704 sys.stderr.write ("Reading `%s'\n" % nm)
705 return (f.read (), nm)
707 error ("File not found `%s'\n" % name)
710 def do_ignore(match_object):
711 return [('ignore', match_object.group('code'))]
712 def do_preamble_end(match_object):
713 return [('preamble-end', match_object.group('code'))]
715 def make_verbatim(match_object):
716 return [('verbatim', match_object.group('code'))]
718 def make_verb(match_object):
719 return [('verb', match_object.group('code'))]
721 def do_include_file(m):
723 return [('input', get_output ('pagebreak'))] \
724 + read_doc_file(m.group('filename')) \
725 + [('input', get_output ('pagebreak'))]
727 def do_input_file(m):
728 return read_doc_file(m.group('filename'))
730 def make_lilypond(m):
731 if m.group('options'):
732 options = m.group('options')
735 return [('input', get_output('output-lilypond-fragment') %
736 (options, m.group('code')))]
738 def make_lilypond_file(m):
741 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
742 into a @lilypond .. @end lilypond block.
746 if m.group('options'):
747 options = m.group('options')
750 (content, nm) = find_file(m.group('filename'))
751 options = "filename=%s," % nm + options
753 return [('input', get_output('output-lilypond') %
756 def make_lilypond_block(m):
757 if m.group('options'):
758 options = get_re('option-sep').split (m.group('options'))
761 options = filter(lambda s: s != '', options)
762 return [('lilypond', m.group('code'), options)]
765 if __main__.format != 'latex':
767 if m.group('num') == 'one':
768 return [('numcols', m.group('code'), 1)]
769 if m.group('num') == 'two':
770 return [('numcols', m.group('code'), 2)]
772 def chop_chunks(chunks, re_name, func, use_match=0):
778 m = get_re (re_name).search (str)
780 newchunks.append (('input', str))
784 newchunks.append (('input', str[:m.start ('match')]))
786 newchunks.append (('input', str[:m.start (0)]))
787 #newchunks.extend(func(m))
788 # python 1.5 compatible:
789 newchunks = newchunks + func(m)
790 str = str [m.end(0):]
795 def determine_format (str):
796 if __main__.format == '':
798 latex = re.search ('\\\\document', str[:200])
799 texinfo = re.search ('@node|@setfilename', str[:200])
804 if texinfo and latex == None:
806 elif latex and texinfo == None:
809 error("error: can't determine format, please specify")
812 if __main__.paperguru == None:
813 if __main__.format == 'texi':
818 __main__.paperguru = g
821 def read_doc_file (filename):
822 """Read the input file, find verbatim chunks and do \input and \include
824 (str, path) = find_file(filename)
825 determine_format (str)
827 chunks = [('input', str)]
829 # we have to check for verbatim before doing include,
830 # because we don't want to include files that are mentioned
831 # inside a verbatim environment
832 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
833 chunks = chop_chunks(chunks, 'verb', make_verb)
834 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
836 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
837 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
841 taken_file_names = {}
842 def schedule_lilypond_block (chunk):
843 """Take the body and options from CHUNK, figure out how the
844 real .ly should look, and what should be left MAIN_STR (meant
845 for the main file). The .ly is written, and scheduled in
848 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
850 TODO has format [basename, extension, extension, ... ]
853 (type, body, opts) = chunk
854 assert type == 'lilypond'
855 file_body = compose_full_body (body, opts)
856 basename = 'lily-' + `abs(hash (file_body))`
858 m = re.search ('filename="(.*?)"', o)
860 basename = m.group (1)
861 if not taken_file_names.has_key(basename):
862 taken_file_names[basename] = 0
864 taken_file_names[basename] = taken_file_names[basename] + 1
865 basename = basename + "-%i" % taken_file_names[basename]
867 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
868 needed_filetypes = ['tex']
871 needed_filetypes.append('eps')
872 needed_filetypes.append('png')
873 if 'eps' in opts and not ('eps' in needed_filetypes):
874 needed_filetypes.append('eps')
875 pathbase = os.path.join (g_outdir, basename)
876 def f(base, ext1, ext2):
877 a = os.path.isfile(base + ext2)
878 if (os.path.isfile(base + ext1) and
879 os.path.isfile(base + ext2) and
880 os.stat(base+ext1)[stat.ST_MTIME] >
881 os.stat(base+ext2)[stat.ST_MTIME]) or \
882 not os.path.isfile(base + ext2):
885 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
887 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
889 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
893 if 'printfilename' in opts:
895 m= re.match ("filename=(.*)", o)
897 newbody = newbody + get_output ("output-filename") % m.group(1)
901 if 'verbatim' in opts:
902 newbody = output_verbatim (body)
905 m = re.search ('intertext="(.*?)"', o)
907 newbody = newbody + m.group (1) + "\n\n"
908 if format == 'latex':
913 else: # format == 'texi'
915 newbody = newbody + get_output (s) % {'fn': basename }
916 return ('lilypond', newbody, opts, todo, basename)
918 def process_lilypond_blocks(outname, chunks):#ugh rename
920 # Count sections/chapters.
922 if c[0] == 'lilypond':
923 c = schedule_lilypond_block (c)
924 elif c[0] == 'numcols':
925 paperguru.m_num_cols = c[2]
930 def find_eps_dims (match):
931 "Fill in dimensions of EPS files."
934 dims = bounding_box_dimensions (fn)
936 fn = os.path.join(g_outdir, fn)
938 return '%ipt' % dims[0]
942 sys.stderr.write ("invoking `%s'\n" % cmd)
945 error ('Error command exited with value %d\n' % st)
949 def get_bbox (filename):
954 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
956 gr = map (string.atoi, m.groups ())
961 def make_pixmap (name):
962 bbox = get_bbox (name + '.eps')
964 fo = open (name + '.trans.eps' , 'w')
965 fo.write ('%d %d translate\n' % (-bbox[0], -bbox[1]))
969 x = (bbox[2] - bbox[0]) * res / 72.
970 y = (bbox[3] - bbox[1]) * res / 72.
972 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
974 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
976 status = system (cmd)
978 os.unlink (name + '.png')
979 error ("Removing output file")
981 def compile_all_files (chunks):
988 if c[0] <> 'lilypond':
997 if base + '.ly' not in tex:
998 tex.append (base + '.ly')
999 elif e == 'png' and g_do_pictures:
1005 # fixme: be sys-independent.
1007 if g_outdir and x[0] <> '/' :
1008 x = os.path.join (g_here_dir, x)
1011 incs = map (incl_opt, include_path)
1012 lilyopts = string.join (incs, ' ' )
1014 lilyopts = lilyopts + ' --dependencies '
1016 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1017 texfiles = string.join (tex, ' ')
1018 system ('lilypond --header=texidoc %s %s' % (lilyopts, texfiles))
1021 # Ugh, fixing up dependencies for .tex generation
1024 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1029 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1035 system(r"tex '\nonstopmode \input %s'" % e)
1036 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1044 def update_file (body, name):
1046 write the body if it has changed
1057 f = open (name , 'w')
1064 def getopt_args (opts):
1065 "Construct arguments (LONG, SHORT) for getopt from list of options."
1070 short = short + o[1]
1078 return (short, long)
1080 def option_help_str (o):
1081 "Transform one option description (4-tuple ) into neatly formatted string"
1099 return ' ' + sh + sep + long + arg
1102 def options_help_str (opts):
1103 "Convert a list of options into a neatly formatted string"
1109 s = option_help_str (o)
1110 strs.append ((s, o[3]))
1116 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1120 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1121 Generate hybrid LaTeX input from Latex + lilypond
1124 sys.stdout.write (options_help_str (option_definitions))
1125 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1129 Report bugs to bug-gnu-music@gnu.org.
1131 Written by Tom Cato Amundsen <tca@gnu.org> and
1132 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1138 def write_deps (fn, target, chunks):
1140 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1141 f = open (os.path.join(g_outdir, fn), 'w')
1142 f.write ('%s%s: ' % (g_dep_prefix, target))
1143 for d in read_files:
1147 if c[0] == 'lilypond':
1148 (type, body, opts, todo, basename) = c;
1149 basenames.append (basename)
1152 d=g_outdir + '/' + d
1154 #if not os.isfile (d): # thinko?
1155 if not re.search ('/', d):
1156 d = g_dep_prefix + d
1157 f.write ('%s.tex ' % d)
1159 #if len (basenames):
1160 # for d in basenames:
1161 # f.write ('%s.ly ' % d)
1162 # f.write (' : %s' % target)
1168 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1170 def print_version ():
1172 sys.stdout.write (r"""Copyright 1998--1999
1173 Distributed under terms of the GNU General Public License. It comes with
1178 def check_texidoc (chunks):
1181 if c[0] == 'lilypond':
1182 (type, body, opts, todo, basename) = c;
1183 pathbase = os.path.join (g_outdir, basename)
1184 if os.path.isfile (pathbase + '.texidoc'):
1185 body = '\n@include %s.texidoc\n' % basename + body
1186 c = (type, body, opts, todo, basename)
1190 def fix_epswidth (chunks):
1193 if c[0] == 'lilypond' and 'eps' in c[2]:
1194 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", find_eps_dims, c[1])
1195 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1197 newchunks.append (c)
1202 def do_file(input_filename):
1206 my_outname = outname
1208 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1209 my_depname = my_outname + '.dep'
1211 chunks = read_doc_file(input_filename)
1212 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1213 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1214 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1215 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1216 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1217 chunks = chop_chunks(chunks, 'numcols', do_columns)
1219 #for c in chunks: print "c:", c;
1221 scan_preamble(chunks)
1222 chunks = process_lilypond_blocks(my_outname, chunks)
1224 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1227 if __main__.g_run_lilypond:
1228 compile_all_files (chunks)
1229 chunks = fix_epswidth (chunks)
1231 if __main__.format == 'texi':
1232 chunks = check_texidoc (chunks)
1235 chunks = completize_preamble (chunks)
1236 sys.stderr.write ("Writing `%s'\n" % foutn)
1237 fout = open (foutn, 'w')
1244 write_deps (my_depname, foutn, chunks)
1249 (sh, long) = getopt_args (__main__.option_definitions)
1250 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1251 except getopt.error, msg:
1252 sys.stderr.write("error: %s" % msg)
1260 if o == '--include' or o == '-I':
1261 include_path.append (a)
1262 elif o == '--version' or o == '-v':
1265 elif o == '--format' or o == '-f':
1267 elif o == '--outname' or o == '-o':
1270 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1273 elif o == '--help' or o == '-h':
1275 elif o == '--no-lily' or o == '-n':
1276 __main__.g_run_lilypond = 0
1277 elif o == '--dependencies' or o == '-M':
1279 elif o == '--default-music-fontsize':
1280 default_music_fontsize = string.atoi (a)
1281 elif o == '--default-lilypond-fontsize':
1282 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1283 default_music_fontsize = string.atoi (a)
1284 elif o == '--force-music-fontsize':
1285 g_force_lilypond_fontsize = string.atoi(a)
1286 elif o == '--force-lilypond-fontsize':
1287 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1288 g_force_lilypond_fontsize = string.atoi(a)
1289 elif o == '--dep-prefix':
1291 elif o == '--no-pictures':
1293 elif o == '--read-lys':
1295 elif o == '--outdir':
1300 if os.path.isfile(g_outdir):
1301 error ("outdir is a file: %s" % g_outdir)
1302 if not os.path.exists(g_outdir):
1304 setup_environment ()
1305 for input_filename in files:
1306 do_file(input_filename)
1309 # Petr, ik zou willen dat ik iets zinvoller deed,
1310 # maar wat ik kan ik doen, het verandert toch niets?