2 # vim: set noexpandtab:
4 # * junk --outdir for --output
5 # * Figure out clean set of options.
7 # * EndLilyPondOutput is def'd as vfil. Causes large white gaps.
8 # * texinfo: add support for @pagesize
10 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
11 # because the values are taken directly from texinfo.tex,
12 # geometry.sty and article.cls. Give me a hint, and I'll
16 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
19 # This is was the idea for handling of comments:
20 # Multiline comments, @ignore .. @end ignore is scanned for
21 # in read_doc_file, and the chunks are marked as 'ignore', so
22 # lilypond-book will not touch them any more. The content of the
23 # chunks are written to the output file. Also 'include' and 'input'
24 # regex has to check if they are commented out.
26 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
27 # These three regex's has to check if they are on a commented line,
28 # % for latex, @c for texinfo.
30 # Then lines that are commented out with % (latex) and @c (Texinfo)
31 # are put into chunks marked 'ignore'. This cannot be done before
32 # searching for the lilypond-blocks because % is also the comment character
35 # The the rest of the rexeces are searched for. They don't have to test
36 # if they are on a commented out line.
50 program_version = '@TOPLEVEL_VERSION@'
51 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
52 program_version = '1.5.18'
54 # if set, LILYPONDPREFIX must take prevalence
55 # if datadir is not set, we're doing a build and LILYPONDPREFIX
58 if os.environ.has_key ('LILYPONDPREFIX') :
59 datadir = os.environ['LILYPONDPREFIX']
63 while datadir[-1] == os.sep:
66 # Try to cater for bad installations of LilyPond, that have
67 # broken TeX setup. Just hope this doesn't hurt good TeX
68 # setups. Maybe we should check if kpsewhich can find
69 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
71 'MFINPUTS' : datadir + '/mf:',
72 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
73 'TFMFONTS' : datadir + '/tfm:',
74 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
75 'GS_LIB' : datadir + '/ps',
78 # tex needs lots of memory, more than it gets by default on Debian
79 non_path_environment = {
80 'extra_mem_top' : '1000000',
81 'extra_mem_bottom' : '1000000',
82 'pool_size' : '250000',
85 def setup_environment ():
86 for key in environment.keys ():
87 val = environment[key]
88 if os.environ.has_key (key):
89 val = val + os.pathsep + os.environ[key]
92 for key in non_path_environment.keys ():
93 val = non_path_environment[key]
94 print '%s=%s' % (key,val)
97 include_path = [os.getcwd()]
100 # g_ is for global (?)
102 g_here_dir = os.getcwd ()
105 g_force_lilypond_fontsize = 0
114 default_music_fontsize = 16
115 default_text_fontsize = 12
118 # this code is ugly. It should be cleaned
122 # the dimensions are from geometry.sty
123 'a0paper': (mm2pt(841), mm2pt(1189)),
124 'a1paper': (mm2pt(595), mm2pt(841)),
125 'a2paper': (mm2pt(420), mm2pt(595)),
126 'a3paper': (mm2pt(297), mm2pt(420)),
127 'a4paper': (mm2pt(210), mm2pt(297)),
128 'a5paper': (mm2pt(149), mm2pt(210)),
129 'b0paper': (mm2pt(1000), mm2pt(1414)),
130 'b1paper': (mm2pt(707), mm2pt(1000)),
131 'b2paper': (mm2pt(500), mm2pt(707)),
132 'b3paper': (mm2pt(353), mm2pt(500)),
133 'b4paper': (mm2pt(250), mm2pt(353)),
134 'b5paper': (mm2pt(176), mm2pt(250)),
135 'letterpaper': (in2pt(8.5), in2pt(11)),
136 'legalpaper': (in2pt(8.5), in2pt(14)),
137 'executivepaper': (in2pt(7.25), in2pt(10.5))}
138 self.m_use_geometry = None
139 self.m_papersize = 'letterpaper'
143 self.m_geo_landscape = 0
144 self.m_geo_width = None
145 self.m_geo_textwidth = None
146 self.m_geo_lmargin = None
147 self.m_geo_rmargin = None
148 self.m_geo_includemp = None
149 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
150 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
151 self.m_geo_x_marginparwidth = None
152 self.m_geo_x_marginparsep = None
154 def set_geo_option(self, name, value):
156 if type(value) == type(""):
157 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
160 num = string.atof(m.group (1))
161 conv = dimension_conversion_dict[m.group(2)]
165 if name == 'body' or name == 'text':
166 if type(value) == type(""):
167 self.m_geo_textwidth = value
169 self.m_geo_textwidth = value[0]
171 elif name == 'portrait':
172 self.m_geo_landscape = 0
173 elif name == 'reversemp' or name == 'reversemarginpar':
174 if self.m_geo_includemp == None:
175 self.m_geo_includemp = 1
176 elif name == 'marginparwidth' or name == 'marginpar':
177 self.m_geo_x_marginparwidth = value
178 self.m_geo_includemp = 1
179 elif name == 'marginparsep':
180 self.m_geo_x_marginparsep = value
181 self.m_geo_includemp = 1
182 elif name == 'scale':
183 if type(value) == type(""):
184 self.m_geo_width = self.get_paperwidth() * float(value)
186 self.m_geo_width = self.get_paperwidth() * float(value[0])
187 elif name == 'hscale':
188 self.m_geo_width = self.get_paperwidth() * float(value)
189 elif name == 'left' or name == 'lmargin':
190 self.m_geo_lmargin = value
191 elif name == 'right' or name == 'rmargin':
192 self.m_geo_rmargin = value
193 elif name == 'hdivide' or name == 'divide':
194 if value[0] not in ('*', ''):
195 self.m_geo_lmargin = value[0]
196 if value[1] not in ('*', ''):
197 self.m_geo_width = value[1]
198 if value[2] not in ('*', ''):
199 self.m_geo_rmargin = value[2]
200 elif name == 'hmargin':
201 if type(value) == type(""):
202 self.m_geo_lmargin = value
203 self.m_geo_rmargin = value
205 self.m_geo_lmargin = value[0]
206 self.m_geo_rmargin = value[1]
207 elif name == 'margin':#ugh there is a bug about this option in
208 # the geometry documentation
209 if type(value) == type(""):
210 self.m_geo_lmargin = value
211 self.m_geo_rmargin = value
213 self.m_geo_lmargin = value[0]
214 self.m_geo_rmargin = value[0]
215 elif name == 'total':
216 if type(value) == type(""):
217 self.m_geo_width = value
219 self.m_geo_width = value[0]
220 elif name == 'width' or name == 'totalwidth':
221 self.m_geo_width = value
222 elif name == 'paper' or name == 'papername':
223 self.m_papersize = value
224 elif name[-5:] == 'paper':
225 self.m_papersize = name
228 # what is _set_dimen ?? /MB
229 #self._set_dimen('m_geo_'+name, value)
230 def __setattr__(self, name, value):
231 if type(value) == type("") and \
232 dimension_conversion_dict.has_key (value[-2:]):
233 f = dimension_conversion_dict[value[-2:]]
234 self.__dict__[name] = f(float(value[:-2]))
236 self.__dict__[name] = value
239 s = "LatexPaper:\n-----------"
240 for v in self.__dict__.keys():
242 s = s + str (v) + ' ' + str (self.__dict__[v])
243 s = s + "-----------"
246 def get_linewidth(self):
247 w = self._calc_linewidth()
248 if self.m_num_cols == 2:
252 def get_paperwidth(self):
253 #if self.m_use_geometry:
254 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
255 #return self.m_paperdef[self.m_papersize][self.m_landscape]
257 def _calc_linewidth(self):
258 # since geometry sometimes ignores 'includemp', this is
259 # more complicated than it should be
261 if self.m_geo_includemp:
262 if self.m_geo_x_marginparsep is not None:
263 mp = mp + self.m_geo_x_marginparsep
265 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
266 if self.m_geo_x_marginparwidth is not None:
267 mp = mp + self.m_geo_x_marginparwidth
269 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
271 #ugh test if this is necessary
275 if not self.m_use_geometry:
276 return latex_linewidths[self.m_papersize][self.m_fontsize]
278 geo_opts = (self.m_geo_lmargin == None,
279 self.m_geo_width == None,
280 self.m_geo_rmargin == None)
282 if geo_opts == (1, 1, 1):
283 if self.m_geo_textwidth:
284 return self.m_geo_textwidth
285 w = self.get_paperwidth() * 0.8
287 elif geo_opts == (0, 1, 1):
288 if self.m_geo_textwidth:
289 return self.m_geo_textwidth
290 return self.f1(self.m_geo_lmargin, mp)
291 elif geo_opts == (1, 1, 0):
292 if self.m_geo_textwidth:
293 return self.m_geo_textwidth
294 return self.f1(self.m_geo_rmargin, mp)
296 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
297 if self.m_geo_textwidth:
298 return self.m_geo_textwidth
299 return self.m_geo_width - mp
300 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
301 w = self.get_paperwidth() \
302 - self.m_geo_lmargin - self.m_geo_rmargin - mp
306 raise "Never do this!"
308 tmp = self.get_paperwidth() - m * 2 - mp
313 tmp = self.get_paperwidth() - self.m_geo_lmargin \
321 self.m_papersize = 'letterpaper'
323 def get_linewidth(self):
324 return texi_linewidths[self.m_papersize][self.m_fontsize]
330 def em2pt(x, fontsize = 10):
331 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
332 def ex2pt(x, fontsize = 10):
333 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
338 dimension_conversion_dict ={
340 'cm': lambda x: mm2pt(10*x),
349 # indices are no. of columns, papersize, fontsize
350 # Why can't this be calculated?
352 'a4paper':{10: 345, 11: 360, 12: 390},
353 'a4paper-landscape': {10: 598, 11: 596, 12:592},
354 'a5paper':{10: 276, 11: 276, 12: 276},
355 'b5paper':{10: 345, 11: 356, 12: 356},
356 'letterpaper':{10: 345, 11: 360, 12: 390},
357 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
358 'legalpaper': {10: 345, 11: 360, 12: 390},
359 'executivepaper':{10: 345, 11: 360, 12: 379}}
362 'afourpaper': {12: mm2pt(160)},
363 'afourwide': {12: in2pt(6.5)},
364 'afourlatex': {12: mm2pt(150)},
365 'smallbook': {12: in2pt(5)},
366 'letterpaper': {12: in2pt(6)}}
368 option_definitions = [
369 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
370 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
371 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
372 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
373 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
374 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
375 ('DIR', 'I', 'include', 'include path'),
376 ('', 'M', 'dependencies', 'write dependencies'),
377 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
378 ('', 'n', 'no-lily', 'don\'t run lilypond'),
379 ('', '', 'no-pictures', "don\'t generate pictures"),
380 ('', '', 'read-lys', "don't write ly files."),
381 ('FILE', 'o', 'outname', 'filename main output file'),
382 ('FILE', '', 'outdir', "where to place generated files"),
383 ('', 'v', 'version', 'print version information' ),
384 ('', 'h', 'help', 'print help'),
387 # format specific strings, ie. regex-es for input, and % strings for output
390 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
397 'output-filename' : r'''
400 'output-lilypond': r"""\begin[%s]{lilypond}
403 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
404 'output-default-post': "\\def\postLilypondExample{}\n",
405 'output-default-pre': "\\def\preLilypondExample{}\n",
406 'usepackage-graphics': '\\usepackage{graphics}\n',
407 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
408 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
409 'pagebreak': r'\pagebreak',
411 'texi' : {'output-lilypond': """@lilypond[%s]
415 'output-filename' : r'''
418 'output-lilypond-fragment': """@lilypond[%s]
419 \context Staff\context Voice{ %s }
422 'output-verbatim': r"""@example
427 # do some tweaking: @ is needed in some ps stuff.
428 # override EndLilyPondOutput, since @tex is done
429 # in a sandbox, you can't do \input lilyponddefs at the
430 # top of the document.
432 # should also support fragment in
438 \def\EndLilyPondOutput{}
444 <a href="%(fn)s.png">
445 <img border=0 src="%(fn)s.png" alt="[picture of music]">
452 def output_verbatim (body):
453 if __main__.format == 'texi':
454 body = re.sub ('([@{}])', '@\\1', body)
455 return get_output ('output-verbatim') % body
459 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
460 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
461 'option-sep' : ',\s*',
462 'header': r"\\documentclass\s*(\[.*?\])?",
463 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
464 'preamble-end': r'(?P<code>\\begin{document})',
465 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
466 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
467 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
468 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
469 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
470 'def-post-re': r"\\def\\postLilypondExample",
471 'def-pre-re': r"\\def\\preLilypondExample",
472 'usepackage-graphics': r"\usepackage{graphics}",
473 'intertext': r',?\s*intertext=\".*?\"',
474 'multiline-comment': no_match,
475 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
476 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
480 # why do we have distinction between @mbinclude and @include?
482 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
485 'preamble-end': no_match,
486 'landscape': no_match,
487 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
488 'verb': r"""(?P<code>@code{.*?})""",
489 'lilypond-file': '(?m)^(?!@c)(?P<match>@lilypondfile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
490 'lilypond' : '(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
491 # pyton2.2b2 barfs on this
492 'lilypond-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s))""",
494 # 1.5.2 barfs on this.
495 # 'lilypond-block': r"""(?m)^(?!@c)(?P<match>@lilypond(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end lilypond\s)""",
496 'option-sep' : ',\s*',
497 'intertext': r',?\s*intertext=\".*?\"',
498 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
499 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
505 for r in re_dict.keys ():
508 for k in olddict.keys ():
509 newdict[k] = re.compile (olddict[k])
523 def get_output (name):
524 return output_dict[format][name]
527 return re_dict[format][name]
529 def bounding_box_dimensions(fname):
531 fname = os.path.join(g_outdir, fname)
535 error ("Error opening `%s'" % fname)
537 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
539 return (int (s.group (3) - s.group (1) + 0.5),
540 int (s.group (4) - s.group (2) + 0.5))
545 sys.stderr.write (str + "\n Exiting ... \n\n")
549 def compose_full_body (body, opts):
550 """Construct the lilypond code to send to Lilypond.
551 Add stuff to BODY using OPTS as options."""
552 music_size = default_music_fontsize
553 latex_size = default_text_fontsize
555 if g_force_lilypond_fontsize:
556 music_size = g_force_lilypond_fontsize
558 m = re.match ('([0-9]+)pt', o)
560 music_size = string.atoi(m.group (1))
562 m = re.match ('latexfontsize=([0-9]+)pt', o)
564 latex_size = string.atoi (m.group (1))
566 if re.search ('\\\\score', body):
570 if 'fragment' in opts:
572 if 'nofragment' in opts:
575 if is_fragment and not 'multiline' in opts:
576 opts.append('singleline')
577 if 'singleline' in opts:
580 l = __main__.paperguru.get_linewidth()
583 m= re.search ('relative(.*)', o)
587 v = string.atoi (m.group (1))
594 pitch = pitch + '\,' * v
596 pitch = pitch + '\'' * v
598 body = '\\relative %s { %s }' %(pitch, body)
607 optstring = string.join (opts, ' ')
608 optstring = re.sub ('\n', ' ', optstring)
610 %% Generated automatically by: lilypond-book.py
612 \include "paper%d.ly"
613 \paper { linewidth = %f \pt }
614 """ % (optstring, music_size, l) + body
616 # ughUGH not original options
619 def parse_options_string(s):
621 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
622 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
623 r3 = re.compile("(\w+?)((,\s*)|$)")
628 d[m.group(2)] = re.split(",\s*", m.group(3))
633 d[m.group(2)] = m.group(3)
641 error ("format of option string invalid (was `%')" % s)
644 def scan_latex_preamble(chunks):
645 # first we want to scan the \documentclass line
646 # it should be the first non-comment line
649 if chunks[idx][0] == 'ignore':
652 m = get_re ('header').match(chunks[idx][1])
653 if m <> None and m.group (1):
654 options = re.split (',[\n \t]*', m.group(1)[1:-1])
659 paperguru.m_landscape = 1
660 m = re.match("(.*?)paper", o)
662 paperguru.m_papersize = m.group()
664 m = re.match("(\d\d)pt", o)
666 paperguru.m_fontsize = int(m.group(1))
669 while chunks[idx][0] != 'preamble-end':
670 if chunks[idx] == 'ignore':
673 m = get_re ('geometry').search(chunks[idx][1])
675 paperguru.m_use_geometry = 1
676 o = parse_options_string(m.group('options'))
678 paperguru.set_geo_option(k, o[k])
681 def scan_texi_preamble (chunks):
682 # this is not bulletproof..., it checks the first 10 chunks
683 for c in chunks[:10]:
685 for s in ('afourpaper', 'afourwide', 'letterpaper',
686 'afourlatex', 'smallbook'):
687 if string.find(c[1], "@%s" % s) != -1:
688 paperguru.m_papersize = s
690 def scan_preamble (chunks):
691 if __main__.format == 'texi':
692 scan_texi_preamble(chunks)
694 assert __main__.format == 'latex'
695 scan_latex_preamble(chunks)
698 def completize_preamble (chunks):
699 if __main__.format == 'texi':
701 pre_b = post_b = graphics_b = None
703 if chunk[0] == 'preamble-end':
705 if chunk[0] == 'input':
706 m = get_re('def-pre-re').search(chunk[1])
709 if chunk[0] == 'input':
710 m = get_re('def-post-re').search(chunk[1])
713 if chunk[0] == 'input':
714 m = get_re('usepackage-graphics').search(chunk[1])
718 while chunks[x][0] != 'preamble-end':
721 chunks.insert(x, ('input', get_output ('output-default-pre')))
723 chunks.insert(x, ('input', get_output ('output-default-post')))
725 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
730 def find_file (name):
732 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
737 for a in include_path:
739 nm = os.path.join (a, name)
741 __main__.read_files.append (nm)
746 sys.stderr.write ("Reading `%s'\n" % nm)
747 return (f.read (), nm)
749 error ("File not found `%s'\n" % name)
752 def do_ignore(match_object):
753 return [('ignore', match_object.group('code'))]
754 def do_preamble_end(match_object):
755 return [('preamble-end', match_object.group('code'))]
757 def make_verbatim(match_object):
758 return [('verbatim', match_object.group('code'))]
760 def make_verb(match_object):
761 return [('verb', match_object.group('code'))]
763 def do_include_file(m):
765 return [('input', get_output ('pagebreak'))] \
766 + read_doc_file(m.group('filename')) \
767 + [('input', get_output ('pagebreak'))]
769 def do_input_file(m):
770 return read_doc_file(m.group('filename'))
772 def make_lilypond(m):
773 if m.group('options'):
774 options = m.group('options')
777 return [('input', get_output('output-lilypond-fragment') %
778 (options, m.group('code')))]
780 def make_lilypond_file(m):
783 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
784 into a @lilypond .. @end lilypond block.
788 if m.group('options'):
789 options = m.group('options')
792 (content, nm) = find_file(m.group('filename'))
793 options = "filename=%s," % nm + options
795 return [('input', get_output('output-lilypond') %
798 def make_lilypond_block(m):
799 if m.group('options'):
800 options = get_re('option-sep').split (m.group('options'))
803 options = filter(lambda s: s != '', options)
804 return [('lilypond', m.group('code'), options)]
807 if __main__.format != 'latex':
809 if m.group('num') == 'one':
810 return [('numcols', m.group('code'), 1)]
811 if m.group('num') == 'two':
812 return [('numcols', m.group('code'), 2)]
814 def chop_chunks(chunks, re_name, func, use_match=0):
820 m = get_re (re_name).search (str)
822 newchunks.append (('input', str))
826 newchunks.append (('input', str[:m.start ('match')]))
828 newchunks.append (('input', str[:m.start (0)]))
829 #newchunks.extend(func(m))
830 # python 1.5 compatible:
831 newchunks = newchunks + func(m)
832 str = str [m.end(0):]
837 def determine_format (str):
838 if __main__.format == '':
840 latex = re.search ('\\\\document', str[:200])
841 texinfo = re.search ('@node|@setfilename', str[:200])
846 if texinfo and latex == None:
848 elif latex and texinfo == None:
851 error("error: can't determine format, please specify")
854 if __main__.paperguru == None:
855 if __main__.format == 'texi':
860 __main__.paperguru = g
863 def read_doc_file (filename):
864 """Read the input file, find verbatim chunks and do \input and \include
866 (str, path) = find_file(filename)
867 determine_format (str)
869 chunks = [('input', str)]
871 # we have to check for verbatim before doing include,
872 # because we don't want to include files that are mentioned
873 # inside a verbatim environment
874 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
875 chunks = chop_chunks(chunks, 'verb', make_verb)
876 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
878 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
879 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
883 taken_file_names = {}
884 def schedule_lilypond_block (chunk):
885 """Take the body and options from CHUNK, figure out how the
886 real .ly should look, and what should be left MAIN_STR (meant
887 for the main file). The .ly is written, and scheduled in
890 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
892 TODO has format [basename, extension, extension, ... ]
895 (type, body, opts) = chunk
896 assert type == 'lilypond'
897 file_body = compose_full_body (body, opts)
898 basename = 'lily-' + `abs(hash (file_body))`
900 m = re.search ('filename="(.*?)"', o)
902 basename = m.group (1)
903 if not taken_file_names.has_key(basename):
904 taken_file_names[basename] = 0
906 taken_file_names[basename] = taken_file_names[basename] + 1
907 basename = basename + "-%i" % taken_file_names[basename]
909 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
910 needed_filetypes = ['tex']
913 needed_filetypes.append('eps')
914 needed_filetypes.append('png')
915 if 'eps' in opts and not ('eps' in needed_filetypes):
916 needed_filetypes.append('eps')
917 pathbase = os.path.join (g_outdir, basename)
918 def f(base, ext1, ext2):
919 a = os.path.isfile(base + ext2)
920 if (os.path.isfile(base + ext1) and
921 os.path.isfile(base + ext2) and
922 os.stat(base+ext1)[stat.ST_MTIME] >
923 os.stat(base+ext2)[stat.ST_MTIME]) or \
924 not os.path.isfile(base + ext2):
927 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
929 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
931 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
935 if 'printfilename' in opts:
937 m= re.match ("filename=(.*)", o)
939 newbody = newbody + get_output ("output-filename") % m.group(1)
943 if 'verbatim' in opts:
944 newbody = output_verbatim (body)
947 m = re.search ('intertext="(.*?)"', o)
949 newbody = newbody + m.group (1) + "\n\n"
950 if format == 'latex':
955 else: # format == 'texi'
957 newbody = newbody + get_output (s) % {'fn': basename }
958 return ('lilypond', newbody, opts, todo, basename)
960 def process_lilypond_blocks(outname, chunks):#ugh rename
962 # Count sections/chapters.
964 if c[0] == 'lilypond':
965 c = schedule_lilypond_block (c)
966 elif c[0] == 'numcols':
967 paperguru.m_num_cols = c[2]
974 sys.stderr.write ("invoking `%s'\n" % cmd)
977 error ('Error command exited with value %d\n' % st)
981 def get_bbox (filename):
986 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
988 gr = map (string.atoi, m.groups ())
993 def make_pixmap (name):
994 bbox = get_bbox (name + '.eps')
996 fo = open (name + '.trans.eps' , 'w')
997 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1002 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1003 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1005 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1007 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1009 status = system (cmd)
1011 os.unlink (name + '.png')
1012 error ("Removing output file")
1014 def compile_all_files (chunks):
1021 if c[0] <> 'lilypond':
1030 if base + '.ly' not in tex:
1031 tex.append (base + '.ly')
1032 elif e == 'png' and g_do_pictures:
1038 # fixme: be sys-independent.
1040 if g_outdir and x[0] <> '/' :
1041 x = os.path.join (g_here_dir, x)
1044 incs = map (incl_opt, include_path)
1045 lilyopts = string.join (incs, ' ' )
1047 lilyopts = lilyopts + ' --dependencies '
1049 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1050 texfiles = string.join (tex, ' ')
1051 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1054 # Ugh, fixing up dependencies for .tex generation
1057 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1062 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1068 system(r"tex '\nonstopmode \input %s'" % e)
1069 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1077 def update_file (body, name):
1079 write the body if it has changed
1090 f = open (name , 'w')
1097 def getopt_args (opts):
1098 "Construct arguments (LONG, SHORT) for getopt from list of options."
1103 short = short + o[1]
1111 return (short, long)
1113 def option_help_str (o):
1114 "Transform one option description (4-tuple ) into neatly formatted string"
1132 return ' ' + sh + sep + long + arg
1135 def options_help_str (opts):
1136 "Convert a list of options into a neatly formatted string"
1142 s = option_help_str (o)
1143 strs.append ((s, o[3]))
1149 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1153 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1154 Generate hybrid LaTeX input from Latex + lilypond
1157 sys.stdout.write (options_help_str (option_definitions))
1158 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1162 Report bugs to bug-lilypond@gnu.org.
1164 Written by Tom Cato Amundsen <tca@gnu.org> and
1165 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1171 def write_deps (fn, target, chunks):
1173 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1174 f = open (os.path.join(g_outdir, fn), 'w')
1175 f.write ('%s%s: ' % (g_dep_prefix, target))
1176 for d in read_files:
1180 if c[0] == 'lilypond':
1181 (type, body, opts, todo, basename) = c;
1182 basenames.append (basename)
1185 d=g_outdir + '/' + d
1187 #if not os.isfile (d): # thinko?
1188 if not re.search ('/', d):
1189 d = g_dep_prefix + d
1190 f.write ('%s.tex ' % d)
1192 #if len (basenames):
1193 # for d in basenames:
1194 # f.write ('%s.ly ' % d)
1195 # f.write (' : %s' % target)
1201 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1203 def print_version ():
1205 sys.stdout.write (r"""Copyright 1998--1999
1206 Distributed under terms of the GNU General Public License. It comes with
1211 def check_texidoc (chunks):
1214 if c[0] == 'lilypond':
1215 (type, body, opts, todo, basename) = c;
1216 pathbase = os.path.join (g_outdir, basename)
1217 if os.path.isfile (pathbase + '.texidoc'):
1218 body = '\n@include %s.texidoc\n' % basename + body
1219 c = (type, body, opts, todo, basename)
1224 ## what's this? Docme --hwn
1226 def fix_epswidth (chunks):
1229 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1230 newchunks.append (c)
1235 m = re.match ('magnification=([0-9.]+)', o)
1237 mag = string.atof (m.group (1))
1239 def replace_eps_dim (match, lmag = mag):
1240 filename = match.group (1)
1241 dims = bounding_box_dimensions (filename)
1243 return '%fpt' % (dims[0] *lmag)
1245 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1246 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1252 def do_file(input_filename):
1256 my_outname = outname
1258 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1259 my_depname = my_outname + '.dep'
1261 chunks = read_doc_file(input_filename)
1262 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1263 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1264 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1265 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1266 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1267 chunks = chop_chunks(chunks, 'numcols', do_columns)
1269 #for c in chunks: print "c:", c;
1271 scan_preamble(chunks)
1272 chunks = process_lilypond_blocks(my_outname, chunks)
1274 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1277 if __main__.g_run_lilypond:
1278 compile_all_files (chunks)
1279 chunks = fix_epswidth (chunks)
1281 if __main__.format == 'texi':
1282 chunks = check_texidoc (chunks)
1285 chunks = completize_preamble (chunks)
1286 sys.stderr.write ("Writing `%s'\n" % foutn)
1287 fout = open (foutn, 'w')
1294 write_deps (my_depname, foutn, chunks)
1299 (sh, long) = getopt_args (__main__.option_definitions)
1300 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1301 except getopt.error, msg:
1302 sys.stderr.write("error: %s" % msg)
1310 if o == '--include' or o == '-I':
1311 include_path.append (a)
1312 elif o == '--version' or o == '-v':
1315 elif o == '--format' or o == '-f':
1317 elif o == '--outname' or o == '-o':
1320 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1323 elif o == '--help' or o == '-h':
1325 elif o == '--no-lily' or o == '-n':
1326 __main__.g_run_lilypond = 0
1327 elif o == '--dependencies' or o == '-M':
1329 elif o == '--default-music-fontsize':
1330 default_music_fontsize = string.atoi (a)
1331 elif o == '--default-lilypond-fontsize':
1332 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1333 default_music_fontsize = string.atoi (a)
1334 elif o == '--extra-options':
1336 elif o == '--force-music-fontsize':
1337 g_force_lilypond_fontsize = string.atoi(a)
1338 elif o == '--force-lilypond-fontsize':
1339 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1340 g_force_lilypond_fontsize = string.atoi(a)
1341 elif o == '--dep-prefix':
1343 elif o == '--no-pictures':
1345 elif o == '--read-lys':
1347 elif o == '--outdir':
1352 if os.path.isfile(g_outdir):
1353 error ("outdir is a file: %s" % g_outdir)
1354 if not os.path.exists(g_outdir):
1356 setup_environment ()
1357 for input_filename in files:
1358 do_file(input_filename)
1361 # Petr, ik zou willen dat ik iets zinvoller deed,
1362 # maar wat ik kan ik doen, het verandert toch niets?