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?
484 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
487 'preamble-end': no_match,
488 'landscape': no_match,
489 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
490 'verb': r"""(?P<code>@code{.*?})""",
491 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
492 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
493 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
494 'option-sep' : ',\s*',
495 'intertext': r',?\s*intertext=\".*?\"',
496 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
497 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
503 for r in re_dict.keys ():
506 for k in olddict.keys ():
508 newdict[k] = re.compile (olddict[k])
510 print 'invalid regexp: %s' % olddict[k]
512 # we'd like to catch and reraise a more detailed error, but
513 # alas, the exceptions changed across the 1.5/2.1 boundary.
528 def get_output (name):
529 return output_dict[format][name]
532 return re_dict[format][name]
534 def bounding_box_dimensions(fname):
536 fname = os.path.join(g_outdir, fname)
540 error ("Error opening `%s'" % fname)
542 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
544 return (int (s.group (3) - s.group (1) + 0.5),
545 int (s.group (4) - s.group (2) + 0.5))
550 sys.stderr.write (str + "\n Exiting ... \n\n")
554 def compose_full_body (body, opts):
555 """Construct the lilypond code to send to Lilypond.
556 Add stuff to BODY using OPTS as options."""
557 music_size = default_music_fontsize
558 latex_size = default_text_fontsize
560 if g_force_lilypond_fontsize:
561 music_size = g_force_lilypond_fontsize
563 m = re.match ('([0-9]+)pt', o)
565 music_size = string.atoi(m.group (1))
567 m = re.match ('latexfontsize=([0-9]+)pt', o)
569 latex_size = string.atoi (m.group (1))
571 if re.search ('\\\\score', body):
575 if 'fragment' in opts:
577 if 'nofragment' in opts:
580 if is_fragment and not 'multiline' in opts:
581 opts.append('singleline')
582 if 'singleline' in opts:
585 l = __main__.paperguru.get_linewidth()
588 m= re.search ('relative(.*)', o)
592 v = string.atoi (m.group (1))
599 pitch = pitch + '\,' * v
601 pitch = pitch + '\'' * v
603 body = '\\relative %s { %s }' %(pitch, body)
612 optstring = string.join (opts, ' ')
613 optstring = re.sub ('\n', ' ', optstring)
615 %% Generated automatically by: lilypond-book.py
617 \include "paper%d.ly"
618 \paper { linewidth = %f \pt }
619 """ % (optstring, music_size, l) + body
621 # ughUGH not original options
624 def parse_options_string(s):
626 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
627 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
628 r3 = re.compile("(\w+?)((,\s*)|$)")
633 d[m.group(2)] = re.split(",\s*", m.group(3))
638 d[m.group(2)] = m.group(3)
646 error ("format of option string invalid (was `%')" % s)
649 def scan_latex_preamble(chunks):
650 # first we want to scan the \documentclass line
651 # it should be the first non-comment line
654 if chunks[idx][0] == 'ignore':
657 m = get_re ('header').match(chunks[idx][1])
658 if m <> None and m.group (1):
659 options = re.split (',[\n \t]*', m.group(1)[1:-1])
664 paperguru.m_landscape = 1
665 m = re.match("(.*?)paper", o)
667 paperguru.m_papersize = m.group()
669 m = re.match("(\d\d)pt", o)
671 paperguru.m_fontsize = int(m.group(1))
674 while chunks[idx][0] != 'preamble-end':
675 if chunks[idx] == 'ignore':
678 m = get_re ('geometry').search(chunks[idx][1])
680 paperguru.m_use_geometry = 1
681 o = parse_options_string(m.group('options'))
683 paperguru.set_geo_option(k, o[k])
686 def scan_texi_preamble (chunks):
687 # this is not bulletproof..., it checks the first 10 chunks
688 for c in chunks[:10]:
690 for s in ('afourpaper', 'afourwide', 'letterpaper',
691 'afourlatex', 'smallbook'):
692 if string.find(c[1], "@%s" % s) != -1:
693 paperguru.m_papersize = s
695 def scan_preamble (chunks):
696 if __main__.format == 'texi':
697 scan_texi_preamble(chunks)
699 assert __main__.format == 'latex'
700 scan_latex_preamble(chunks)
703 def completize_preamble (chunks):
704 if __main__.format == 'texi':
706 pre_b = post_b = graphics_b = None
708 if chunk[0] == 'preamble-end':
710 if chunk[0] == 'input':
711 m = get_re('def-pre-re').search(chunk[1])
714 if chunk[0] == 'input':
715 m = get_re('def-post-re').search(chunk[1])
718 if chunk[0] == 'input':
719 m = get_re('usepackage-graphics').search(chunk[1])
723 while chunks[x][0] != 'preamble-end':
726 chunks.insert(x, ('input', get_output ('output-default-pre')))
728 chunks.insert(x, ('input', get_output ('output-default-post')))
730 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
735 def find_file (name):
737 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
742 for a in include_path:
744 nm = os.path.join (a, name)
746 __main__.read_files.append (nm)
751 sys.stderr.write ("Reading `%s'\n" % nm)
752 return (f.read (), nm)
754 error ("File not found `%s'\n" % name)
757 def do_ignore(match_object):
758 return [('ignore', match_object.group('code'))]
759 def do_preamble_end(match_object):
760 return [('preamble-end', match_object.group('code'))]
762 def make_verbatim(match_object):
763 return [('verbatim', match_object.group('code'))]
765 def make_verb(match_object):
766 return [('verb', match_object.group('code'))]
768 def do_include_file(m):
770 return [('input', get_output ('pagebreak'))] \
771 + read_doc_file(m.group('filename')) \
772 + [('input', get_output ('pagebreak'))]
774 def do_input_file(m):
775 return read_doc_file(m.group('filename'))
777 def make_lilypond(m):
778 if m.group('options'):
779 options = m.group('options')
782 return [('input', get_output('output-lilypond-fragment') %
783 (options, m.group('code')))]
785 def make_lilypond_file(m):
788 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
789 into a @lilypond .. @end lilypond block.
793 if m.group('options'):
794 options = m.group('options')
797 (content, nm) = find_file(m.group('filename'))
798 options = "filename=%s," % nm + options
800 return [('input', get_output('output-lilypond') %
803 def make_lilypond_block(m):
804 if m.group('options'):
805 options = get_re('option-sep').split (m.group('options'))
808 options = filter(lambda s: s != '', options)
809 return [('lilypond', m.group('code'), options)]
812 if __main__.format != 'latex':
814 if m.group('num') == 'one':
815 return [('numcols', m.group('code'), 1)]
816 if m.group('num') == 'two':
817 return [('numcols', m.group('code'), 2)]
819 def chop_chunks(chunks, re_name, func, use_match=0):
825 m = get_re (re_name).search (str)
827 newchunks.append (('input', str))
831 newchunks.append (('input', str[:m.start ('match')]))
833 newchunks.append (('input', str[:m.start (0)]))
834 #newchunks.extend(func(m))
835 # python 1.5 compatible:
836 newchunks = newchunks + func(m)
837 str = str [m.end(0):]
842 def determine_format (str):
843 if __main__.format == '':
845 latex = re.search ('\\\\document', str[:200])
846 texinfo = re.search ('@node|@setfilename', str[:200])
851 if texinfo and latex == None:
853 elif latex and texinfo == None:
856 error("error: can't determine format, please specify")
859 if __main__.paperguru == None:
860 if __main__.format == 'texi':
865 __main__.paperguru = g
868 def read_doc_file (filename):
869 """Read the input file, find verbatim chunks and do \input and \include
871 (str, path) = find_file(filename)
872 determine_format (str)
874 chunks = [('input', str)]
876 # we have to check for verbatim before doing include,
877 # because we don't want to include files that are mentioned
878 # inside a verbatim environment
879 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
880 chunks = chop_chunks(chunks, 'verb', make_verb)
881 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
883 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
884 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
888 taken_file_names = {}
889 def schedule_lilypond_block (chunk):
890 """Take the body and options from CHUNK, figure out how the
891 real .ly should look, and what should be left MAIN_STR (meant
892 for the main file). The .ly is written, and scheduled in
895 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
897 TODO has format [basename, extension, extension, ... ]
900 (type, body, opts) = chunk
901 assert type == 'lilypond'
902 file_body = compose_full_body (body, opts)
903 basename = 'lily-' + `abs(hash (file_body))`
905 m = re.search ('filename="(.*?)"', o)
907 basename = m.group (1)
908 if not taken_file_names.has_key(basename):
909 taken_file_names[basename] = 0
911 taken_file_names[basename] = taken_file_names[basename] + 1
912 basename = basename + "-%i" % taken_file_names[basename]
914 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
915 needed_filetypes = ['tex']
918 needed_filetypes.append('eps')
919 needed_filetypes.append('png')
920 if 'eps' in opts and not ('eps' in needed_filetypes):
921 needed_filetypes.append('eps')
922 pathbase = os.path.join (g_outdir, basename)
923 def f(base, ext1, ext2):
924 a = os.path.isfile(base + ext2)
925 if (os.path.isfile(base + ext1) and
926 os.path.isfile(base + ext2) and
927 os.stat(base+ext1)[stat.ST_MTIME] >
928 os.stat(base+ext2)[stat.ST_MTIME]) or \
929 not os.path.isfile(base + ext2):
932 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
934 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
936 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
940 if 'printfilename' in opts:
942 m= re.match ("filename=(.*)", o)
944 newbody = newbody + get_output ("output-filename") % m.group(1)
948 if 'verbatim' in opts:
949 newbody = output_verbatim (body)
952 m = re.search ('intertext="(.*?)"', o)
954 newbody = newbody + m.group (1) + "\n\n"
955 if format == 'latex':
960 else: # format == 'texi'
962 newbody = newbody + get_output (s) % {'fn': basename }
963 return ('lilypond', newbody, opts, todo, basename)
965 def process_lilypond_blocks(outname, chunks):#ugh rename
967 # Count sections/chapters.
969 if c[0] == 'lilypond':
970 c = schedule_lilypond_block (c)
971 elif c[0] == 'numcols':
972 paperguru.m_num_cols = c[2]
979 sys.stderr.write ("invoking `%s'\n" % cmd)
982 error ('Error command exited with value %d\n' % st)
986 def get_bbox (filename):
991 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', l)
993 gr = map (string.atoi, m.groups ())
998 def make_pixmap (name):
999 bbox = get_bbox (name + '.eps')
1001 fo = open (name + '.trans.eps' , 'w')
1002 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1007 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1008 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1010 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1012 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1014 status = system (cmd)
1016 os.unlink (name + '.png')
1017 error ("Removing output file")
1019 def compile_all_files (chunks):
1026 if c[0] <> 'lilypond':
1035 if base + '.ly' not in tex:
1036 tex.append (base + '.ly')
1037 elif e == 'png' and g_do_pictures:
1043 # fixme: be sys-independent.
1045 if g_outdir and x[0] <> '/' :
1046 x = os.path.join (g_here_dir, x)
1049 incs = map (incl_opt, include_path)
1050 lilyopts = string.join (incs, ' ' )
1052 lilyopts = lilyopts + ' --dependencies '
1054 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1055 texfiles = string.join (tex, ' ')
1056 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1059 # Ugh, fixing up dependencies for .tex generation
1062 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1067 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1073 system(r"tex '\nonstopmode \input %s'" % e)
1074 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1082 def update_file (body, name):
1084 write the body if it has changed
1095 f = open (name , 'w')
1102 def getopt_args (opts):
1103 "Construct arguments (LONG, SHORT) for getopt from list of options."
1108 short = short + o[1]
1116 return (short, long)
1118 def option_help_str (o):
1119 "Transform one option description (4-tuple ) into neatly formatted string"
1137 return ' ' + sh + sep + long + arg
1140 def options_help_str (opts):
1141 "Convert a list of options into a neatly formatted string"
1147 s = option_help_str (o)
1148 strs.append ((s, o[3]))
1154 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1158 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1159 Generate hybrid LaTeX input from Latex + lilypond
1162 sys.stdout.write (options_help_str (option_definitions))
1163 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1167 Report bugs to bug-lilypond@gnu.org.
1169 Written by Tom Cato Amundsen <tca@gnu.org> and
1170 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1176 def write_deps (fn, target, chunks):
1178 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1179 f = open (os.path.join(g_outdir, fn), 'w')
1180 f.write ('%s%s: ' % (g_dep_prefix, target))
1181 for d in read_files:
1185 if c[0] == 'lilypond':
1186 (type, body, opts, todo, basename) = c;
1187 basenames.append (basename)
1190 d=g_outdir + '/' + d
1192 #if not os.isfile (d): # thinko?
1193 if not re.search ('/', d):
1194 d = g_dep_prefix + d
1195 f.write ('%s.tex ' % d)
1197 #if len (basenames):
1198 # for d in basenames:
1199 # f.write ('%s.ly ' % d)
1200 # f.write (' : %s' % target)
1206 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1208 def print_version ():
1210 sys.stdout.write (r"""Copyright 1998--1999
1211 Distributed under terms of the GNU General Public License. It comes with
1216 def check_texidoc (chunks):
1219 if c[0] == 'lilypond':
1220 (type, body, opts, todo, basename) = c;
1221 pathbase = os.path.join (g_outdir, basename)
1222 if os.path.isfile (pathbase + '.texidoc'):
1223 body = '\n@include %s.texidoc\n' % basename + body
1224 c = (type, body, opts, todo, basename)
1229 ## what's this? Docme --hwn
1231 def fix_epswidth (chunks):
1234 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1235 newchunks.append (c)
1240 m = re.match ('magnification=([0-9.]+)', o)
1242 mag = string.atof (m.group (1))
1244 def replace_eps_dim (match, lmag = mag):
1245 filename = match.group (1)
1246 dims = bounding_box_dimensions (filename)
1248 return '%fpt' % (dims[0] *lmag)
1250 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1251 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1257 def do_file(input_filename):
1261 my_outname = outname
1263 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1264 my_depname = my_outname + '.dep'
1266 chunks = read_doc_file(input_filename)
1267 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1268 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1269 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1270 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1271 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1272 chunks = chop_chunks(chunks, 'numcols', do_columns)
1274 #for c in chunks: print "c:", c;
1276 scan_preamble(chunks)
1277 chunks = process_lilypond_blocks(my_outname, chunks)
1279 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1282 if __main__.g_run_lilypond:
1283 compile_all_files (chunks)
1284 chunks = fix_epswidth (chunks)
1286 if __main__.format == 'texi':
1287 chunks = check_texidoc (chunks)
1290 chunks = completize_preamble (chunks)
1291 sys.stderr.write ("Writing `%s'\n" % foutn)
1292 fout = open (foutn, 'w')
1299 write_deps (my_depname, foutn, chunks)
1304 (sh, long) = getopt_args (__main__.option_definitions)
1305 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1306 except getopt.error, msg:
1307 sys.stderr.write("error: %s" % msg)
1315 if o == '--include' or o == '-I':
1316 include_path.append (a)
1317 elif o == '--version' or o == '-v':
1320 elif o == '--format' or o == '-f':
1322 elif o == '--outname' or o == '-o':
1325 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1328 elif o == '--help' or o == '-h':
1330 elif o == '--no-lily' or o == '-n':
1331 __main__.g_run_lilypond = 0
1332 elif o == '--dependencies' or o == '-M':
1334 elif o == '--default-music-fontsize':
1335 default_music_fontsize = string.atoi (a)
1336 elif o == '--default-lilypond-fontsize':
1337 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1338 default_music_fontsize = string.atoi (a)
1339 elif o == '--extra-options':
1341 elif o == '--force-music-fontsize':
1342 g_force_lilypond_fontsize = string.atoi(a)
1343 elif o == '--force-lilypond-fontsize':
1344 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1345 g_force_lilypond_fontsize = string.atoi(a)
1346 elif o == '--dep-prefix':
1348 elif o == '--no-pictures':
1350 elif o == '--read-lys':
1352 elif o == '--outdir':
1357 if os.path.isfile(g_outdir):
1358 error ("outdir is a file: %s" % g_outdir)
1359 if not os.path.exists(g_outdir):
1361 setup_environment ()
1362 for input_filename in files:
1363 do_file(input_filename)
1366 # Petr, ik zou willen dat ik iets zinvoller deed,
1367 # maar wat ik kan ik doen, het verandert toch niets?