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 # Handle bug in Python 1.6-2.1
52 # there are recursion limits for some patterns in Python 1.6 til 2.1.
53 # fix this by importing the 1.5.2 implementation pre instead. Fix by Mats.
55 if float (sys.version[0:3]) < 2.2:
65 # Attempt to fix problems with limited stack size set by Python!
66 # Sets unlimited stack size. Note that the resource module only
67 # is available on UNIX.
70 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
76 program_version = '@TOPLEVEL_VERSION@'
77 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
78 program_version = '1.4.12'
80 # if set, LILYPONDPREFIX must take prevalence
81 # if datadir is not set, we're doing a build and LILYPONDPREFIX
84 if os.environ.has_key ('LILYPONDPREFIX') :
85 datadir = os.environ['LILYPONDPREFIX']
89 while datadir[-1] == os.sep:
92 # Try to cater for bad installations of LilyPond, that have
93 # broken TeX setup. Just hope this doesn't hurt good TeX
94 # setups. Maybe we should check if kpsewhich can find
95 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
97 'MFINPUTS' : datadir + '/mf:',
98 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
99 'TFMFONTS' : datadir + '/tfm:',
100 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
101 'GS_LIB' : datadir + '/ps',
104 # tex needs lots of memory, more than it gets by default on Debian
105 non_path_environment = {
106 'extra_mem_top' : '1000000',
107 'extra_mem_bottom' : '1000000',
108 'pool_size' : '250000',
111 def setup_environment ():
112 for key in environment.keys ():
113 val = environment[key]
114 if os.environ.has_key (key):
115 val = val + os.pathsep + os.environ[key]
116 os.environ[key] = val
118 for key in non_path_environment.keys ():
119 val = non_path_environment[key]
120 print '%s=%s' % (key,val)
121 os.environ[key] = val
123 include_path = [os.getcwd()]
126 # g_ is for global (?)
128 g_here_dir = os.getcwd ()
131 g_force_lilypond_fontsize = 0
140 default_music_fontsize = 16
141 default_text_fontsize = 12
144 # this code is ugly. It should be cleaned
148 # the dimensions are from geometry.sty
149 'a0paper': (mm2pt(841), mm2pt(1189)),
150 'a1paper': (mm2pt(595), mm2pt(841)),
151 'a2paper': (mm2pt(420), mm2pt(595)),
152 'a3paper': (mm2pt(297), mm2pt(420)),
153 'a4paper': (mm2pt(210), mm2pt(297)),
154 'a5paper': (mm2pt(149), mm2pt(210)),
155 'b0paper': (mm2pt(1000), mm2pt(1414)),
156 'b1paper': (mm2pt(707), mm2pt(1000)),
157 'b2paper': (mm2pt(500), mm2pt(707)),
158 'b3paper': (mm2pt(353), mm2pt(500)),
159 'b4paper': (mm2pt(250), mm2pt(353)),
160 'b5paper': (mm2pt(176), mm2pt(250)),
161 'letterpaper': (in2pt(8.5), in2pt(11)),
162 'legalpaper': (in2pt(8.5), in2pt(14)),
163 'executivepaper': (in2pt(7.25), in2pt(10.5))}
164 self.m_use_geometry = None
165 self.m_papersize = 'letterpaper'
169 self.m_geo_landscape = 0
170 self.m_geo_width = None
171 self.m_geo_textwidth = None
172 self.m_geo_lmargin = None
173 self.m_geo_rmargin = None
174 self.m_geo_includemp = None
175 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
176 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
177 self.m_geo_x_marginparwidth = None
178 self.m_geo_x_marginparsep = None
180 def set_geo_option(self, name, value):
182 if type(value) == type([]):
183 value = map(conv_dimen_to_float, value)
185 value = conv_dimen_to_float(value)
187 if name == 'body' or name == 'text':
188 if type(value) == type([]):
189 self.m_geo_textwidth = value[0]
191 self.m_geo_textwidth = value
193 elif name == 'portrait':
194 self.m_geo_landscape = 0
195 elif name == 'reversemp' or name == 'reversemarginpar':
196 if self.m_geo_includemp == None:
197 self.m_geo_includemp = 1
198 elif name == 'marginparwidth' or name == 'marginpar':
199 self.m_geo_x_marginparwidth = value
200 self.m_geo_includemp = 1
201 elif name == 'marginparsep':
202 self.m_geo_x_marginparsep = value
203 self.m_geo_includemp = 1
204 elif name == 'scale':
205 if type(value) == type([]):
206 self.m_geo_width = self.get_paperwidth() * value[0]
208 self.m_geo_width = self.get_paperwidth() * value
209 elif name == 'hscale':
210 self.m_geo_width = self.get_paperwidth() * value
211 elif name == 'left' or name == 'lmargin':
212 self.m_geo_lmargin = value
213 elif name == 'right' or name == 'rmargin':
214 self.m_geo_rmargin = value
215 elif name == 'hdivide' or name == 'divide':
216 if value[0] not in ('*', ''):
217 self.m_geo_lmargin = value[0]
218 if value[1] not in ('*', ''):
219 self.m_geo_width = value[1]
220 if value[2] not in ('*', ''):
221 self.m_geo_rmargin = value[2]
222 elif name == 'hmargin':
223 if type(value) == type([]):
224 self.m_geo_lmargin = value[0]
225 self.m_geo_rmargin = value[1]
227 self.m_geo_lmargin = value
228 self.m_geo_rmargin = value
229 elif name == 'margin':#ugh there is a bug about this option in
230 # the geometry documentation
231 if type(value) == type([]):
232 self.m_geo_lmargin = value[0]
233 self.m_geo_rmargin = value[0]
235 self.m_geo_lmargin = value
236 self.m_geo_rmargin = value
237 elif name == 'total':
238 if type(value) == type([]):
239 self.m_geo_width = value[0]
241 self.m_geo_width = value
242 elif name == 'width' or name == 'totalwidth':
243 self.m_geo_width = value
244 elif name == 'paper' or name == 'papername':
245 self.m_papersize = value
246 elif name[-5:] == 'paper':
247 self.m_papersize = name
251 def __setattr__(self, name, value):
252 if type(value) == type("") and \
253 dimension_conversion_dict.has_key (value[-2:]):
254 f = dimension_conversion_dict[value[-2:]]
255 self.__dict__[name] = f(float(value[:-2]))
257 self.__dict__[name] = value
260 s = "LatexPaper:\n-----------"
261 for v in self.__dict__.keys():
263 s = s + str (v) + ' ' + str (self.__dict__[v])
264 s = s + "-----------"
267 def get_linewidth(self):
268 w = self._calc_linewidth()
269 if self.m_num_cols == 2:
273 def get_paperwidth(self):
274 #if self.m_use_geometry:
275 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
276 #return self.m_paperdef[self.m_papersize][self.m_landscape]
278 def _calc_linewidth(self):
279 # since geometry sometimes ignores 'includemp', this is
280 # more complicated than it should be
282 if self.m_geo_includemp:
283 if self.m_geo_x_marginparsep is not None:
284 mp = mp + self.m_geo_x_marginparsep
286 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
287 if self.m_geo_x_marginparwidth is not None:
288 mp = mp + self.m_geo_x_marginparwidth
290 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
292 #ugh test if this is necessary
296 if not self.m_use_geometry:
297 return latex_linewidths[self.m_papersize][self.m_fontsize]
299 geo_opts = (self.m_geo_lmargin == None,
300 self.m_geo_width == None,
301 self.m_geo_rmargin == None)
303 if geo_opts == (1, 1, 1):
304 if self.m_geo_textwidth:
305 return self.m_geo_textwidth
306 w = self.get_paperwidth() * 0.8
308 elif geo_opts == (0, 1, 1):
309 if self.m_geo_textwidth:
310 return self.m_geo_textwidth
311 return self.f1(self.m_geo_lmargin, mp)
312 elif geo_opts == (1, 1, 0):
313 if self.m_geo_textwidth:
314 return self.m_geo_textwidth
315 return self.f1(self.m_geo_rmargin, mp)
317 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
318 if self.m_geo_textwidth:
319 return self.m_geo_textwidth
320 return self.m_geo_width - mp
321 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
322 w = self.get_paperwidth() \
323 - self.m_geo_lmargin - self.m_geo_rmargin - mp
327 raise "Never do this!"
329 tmp = self.get_paperwidth() - m * 2 - mp
334 tmp = self.get_paperwidth() - self.m_geo_lmargin \
342 self.m_papersize = 'letterpaper'
344 def get_linewidth(self):
345 return texi_linewidths[self.m_papersize][self.m_fontsize]
351 def em2pt(x, fontsize = 10):
352 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
353 def ex2pt(x, fontsize = 10):
354 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
359 dimension_conversion_dict ={
361 'cm': lambda x: mm2pt(10*x),
368 # Convert numeric values, with or without specific dimension, to floats.
370 def conv_dimen_to_float(value):
371 if type(value) == type(""):
372 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
375 num = string.atof(m.group (1))
376 conv = dimension_conversion_dict[m.group(2)]
380 elif re.match ("^[0-9.]+$",value):
387 # indices are no. of columns, papersize, fontsize
388 # Why can't this be calculated?
390 'a4paper':{10: 345, 11: 360, 12: 390},
391 'a4paper-landscape': {10: 598, 11: 596, 12:592},
392 'a5paper':{10: 276, 11: 276, 12: 276},
393 'b5paper':{10: 345, 11: 356, 12: 356},
394 'letterpaper':{10: 345, 11: 360, 12: 390},
395 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
396 'legalpaper': {10: 345, 11: 360, 12: 390},
397 'executivepaper':{10: 345, 11: 360, 12: 379}}
400 'afourpaper': {12: mm2pt(160)},
401 'afourwide': {12: in2pt(6.5)},
402 'afourlatex': {12: mm2pt(150)},
403 'smallbook': {12: in2pt(5)},
404 'letterpaper': {12: in2pt(6)}}
406 option_definitions = [
407 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
408 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
409 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
410 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
411 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
412 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
413 ('DIR', 'I', 'include', 'include path'),
414 ('', 'M', 'dependencies', 'write dependencies'),
415 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
416 ('', 'n', 'no-lily', 'don\'t run lilypond'),
417 ('', '', 'no-pictures', "don\'t generate pictures"),
418 ('', '', 'read-lys', "don't write ly files."),
419 ('FILE', 'o', 'outname', 'filename main output file'),
420 ('FILE', '', 'outdir', "where to place generated files"),
421 ('', 'v', 'version', 'print version information' ),
422 ('', 'h', 'help', 'print help'),
425 # format specific strings, ie. regex-es for input, and % strings for output
428 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
435 'output-filename' : r'''
438 'output-lilypond': r"""\begin[%s]{lilypond}
442 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
444 'output-default-post': "\\def\postLilypondExample{}\n",
445 'output-default-pre': "\\def\preLilypondExample{}\n",
446 'usepackage-graphics': '\\usepackage{graphics}\n',
447 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
448 'output-noinline': r'''
449 %% generated: %(fn)s.eps
451 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
452 'pagebreak': r'\pagebreak',
455 'texi' : {'output-lilypond': """@lilypond[%s]
459 'output-filename' : r'''
462 'output-lilypond-fragment': """@lilypond[%s]
463 \context Staff\context Voice{ %s }
465 'output-noinline': r'''
466 @c generated: %(fn)s.png
469 'output-verbatim': r"""@example
474 # do some tweaking: @ is needed in some ps stuff.
475 # override EndLilyPondOutput, since @tex is done
476 # in a sandbox, you can't do \input lilyponddefs at the
477 # top of the document.
479 # should also support fragment in
485 \def\EndLilyPondOutput{}
491 <a href="%(fn)s.png">
492 <img border=0 src="%(fn)s.png" alt="[picture of music]">
499 def output_verbatim (body):
500 if __main__.format == 'texi':
501 body = re.sub ('([@{}])', '@\\1', body)
502 return get_output ('output-verbatim') % body
505 #warning: this uses extended regular expressions. Tread with care.
507 # legenda (?P name parameter
508 # *? match non-greedily.
511 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
512 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
513 'option-sep' : ',\s*',
514 'header': r"\\documentclass\s*(\[.*?\])?",
515 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
516 'preamble-end': r'(?P<code>\\begin{document})',
517 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
518 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
519 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
520 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
521 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
522 'def-post-re': r"\\def\\postLilypondExample",
523 'def-pre-re': r"\\def\\preLilypondExample",
524 'usepackage-graphics': r"\usepackage{graphics}",
525 'intertext': r',?\s*intertext=\".*?\"',
526 'multiline-comment': no_match,
527 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
528 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
532 # why do we have distinction between @mbinclude and @include?
536 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
539 'preamble-end': no_match,
540 'landscape': no_match,
541 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
542 'verb': r"""(?P<code>@code{.*?})""",
543 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
544 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
545 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
546 'option-sep' : ',\s*',
547 'intertext': r',?\s*intertext=\".*?\"',
548 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
549 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
555 for r in re_dict.keys ():
558 for k in olddict.keys ():
560 newdict[k] = re.compile (olddict[k])
562 print 'invalid regexp: %s' % olddict[k]
564 # we'd like to catch and reraise a more detailed error, but
565 # alas, the exceptions changed across the 1.5/2.1 boundary.
580 def get_output (name):
581 return output_dict[format][name]
584 return re_dict[format][name]
586 def bounding_box_dimensions(fname):
588 fname = os.path.join(g_outdir, fname)
592 error ("Error opening `%s'" % fname)
594 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
597 gs = map (lambda x: string.atoi (x), s.groups ())
598 return (int (gs[2] - gs[0] + 0.5),
599 int (gs[3] - gs[1] + 0.5))
604 sys.stderr.write (str + "\n Exiting ... \n\n")
608 def compose_full_body (body, opts):
609 """Construct the lilypond code to send to Lilypond.
610 Add stuff to BODY using OPTS as options."""
611 music_size = default_music_fontsize
612 latex_size = default_text_fontsize
616 if g_force_lilypond_fontsize:
617 music_size = g_force_lilypond_fontsize
619 m = re.match ('([0-9]+)pt', o)
621 music_size = string.atoi(m.group (1))
623 m = re.match ('latexfontsize=([0-9]+)pt', o)
625 latex_size = string.atoi (m.group (1))
627 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
629 f = float (m.group (1))
630 indent = 'indent = %f\\%s' % (f, m.group (2))
632 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
634 f = float (m.group (1))
635 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
637 if re.search ('\\\\score', body):
641 if 'fragment' in opts:
643 if 'nofragment' in opts:
646 if is_fragment and not 'multiline' in opts:
647 opts.append('singleline')
649 if 'singleline' in opts:
650 linewidth = 'linewidth = -1.0'
652 l = __main__.paperguru.get_linewidth ()
653 linewidth = 'linewidth = %f\pt' % l
655 if 'noindent' in opts:
656 indent = 'indent = 0.0\mm'
659 m= re.search ('relative(.*)', o)
663 v = string.atoi (m.group (1))
670 pitch = pitch + '\,' * v
672 pitch = pitch + '\'' * v
674 body = '\\relative %s { %s }' %(pitch, body)
683 optstring = string.join (opts, ' ')
684 optstring = re.sub ('\n', ' ', optstring)
686 %% Generated automatically by: lilypond-book.py
688 \include "paper%d.ly"
693 ''' % (optstring, music_size, linewidth, indent) + body
695 # ughUGH not original options
698 def parse_options_string(s):
700 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
701 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
702 r3 = re.compile("(\w+?)((,\s*)|$)")
707 d[m.group(2)] = re.split(",\s*", m.group(3))
712 d[m.group(2)] = m.group(3)
720 error ("format of option string invalid (was `%')" % s)
723 def scan_latex_preamble(chunks):
724 # first we want to scan the \documentclass line
725 # it should be the first non-comment line
728 if chunks[idx][0] == 'ignore':
731 m = get_re ('header').match(chunks[idx][1])
732 if m <> None and m.group (1):
733 options = re.split (',[\n \t]*', m.group(1)[1:-1])
738 paperguru.m_landscape = 1
739 m = re.match("(.*?)paper", o)
741 paperguru.m_papersize = m.group()
743 m = re.match("(\d\d)pt", o)
745 paperguru.m_fontsize = int(m.group(1))
748 while chunks[idx][0] != 'preamble-end':
749 if chunks[idx] == 'ignore':
752 m = get_re ('geometry').search(chunks[idx][1])
754 paperguru.m_use_geometry = 1
755 o = parse_options_string(m.group('options'))
757 paperguru.set_geo_option(k, o[k])
760 def scan_texi_preamble (chunks):
761 # this is not bulletproof..., it checks the first 10 chunks
762 for c in chunks[:10]:
764 for s in ('afourpaper', 'afourwide', 'letterpaper',
765 'afourlatex', 'smallbook'):
766 if string.find(c[1], "@%s" % s) != -1:
767 paperguru.m_papersize = s
769 def scan_preamble (chunks):
770 if __main__.format == 'texi':
771 scan_texi_preamble(chunks)
773 assert __main__.format == 'latex'
774 scan_latex_preamble(chunks)
777 def completize_preamble (chunks):
778 if __main__.format == 'texi':
780 pre_b = post_b = graphics_b = None
782 if chunk[0] == 'preamble-end':
784 if chunk[0] == 'input':
785 m = get_re('def-pre-re').search(chunk[1])
788 if chunk[0] == 'input':
789 m = get_re('def-post-re').search(chunk[1])
792 if chunk[0] == 'input':
793 m = get_re('usepackage-graphics').search(chunk[1])
797 while chunks[x][0] != 'preamble-end':
800 chunks.insert(x, ('input', get_output ('output-default-pre')))
802 chunks.insert(x, ('input', get_output ('output-default-post')))
804 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
809 def find_file (name):
811 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
816 for a in include_path:
818 nm = os.path.join (a, name)
820 __main__.read_files.append (nm)
825 sys.stderr.write ("Reading `%s'\n" % nm)
826 return (f.read (), nm)
828 error ("File not found `%s'\n" % name)
831 def do_ignore(match_object):
832 return [('ignore', match_object.group('code'))]
833 def do_preamble_end(match_object):
834 return [('preamble-end', match_object.group('code'))]
836 def make_verbatim(match_object):
837 return [('verbatim', match_object.group('code'))]
839 def make_verb(match_object):
840 return [('verb', match_object.group('code'))]
842 def do_include_file(m):
844 return [('input', get_output ('pagebreak'))] \
845 + read_doc_file(m.group('filename')) \
846 + [('input', get_output ('pagebreak'))]
848 def do_input_file(m):
849 return read_doc_file(m.group('filename'))
851 def make_lilypond(m):
852 if m.group('options'):
853 options = m.group('options')
856 return [('input', get_output('output-lilypond-fragment') %
857 (options, m.group('code')))]
859 def make_lilypond_file(m):
862 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
863 into a @lilypond .. @end lilypond block.
867 if m.group('options'):
868 options = m.group('options')
871 (content, nm) = find_file(m.group('filename'))
872 options = "filename=%s," % nm + options
874 return [('input', get_output('output-lilypond') %
877 def make_lilypond_block(m):
878 if m.group('options'):
879 options = get_re('option-sep').split (m.group('options'))
882 options = filter(lambda s: s != '', options)
883 return [('lilypond', m.group('code'), options)]
886 if __main__.format != 'latex':
888 if m.group('num') == 'one':
889 return [('numcols', m.group('code'), 1)]
890 if m.group('num') == 'two':
891 return [('numcols', m.group('code'), 2)]
893 def chop_chunks(chunks, re_name, func, use_match=0):
899 m = get_re (re_name).search (str)
901 newchunks.append (('input', str))
905 newchunks.append (('input', str[:m.start ('match')]))
907 newchunks.append (('input', str[:m.start (0)]))
908 #newchunks.extend(func(m))
909 # python 1.5 compatible:
910 newchunks = newchunks + func(m)
911 str = str [m.end(0):]
916 def determine_format (str):
917 if __main__.format == '':
919 latex = re.search ('\\\\document', str[:200])
920 texinfo = re.search ('@node|@setfilename', str[:200])
925 if texinfo and latex == None:
927 elif latex and texinfo == None:
930 error("error: can't determine format, please specify")
933 if __main__.paperguru == None:
934 if __main__.format == 'texi':
939 __main__.paperguru = g
942 def read_doc_file (filename):
943 """Read the input file, find verbatim chunks and do \input and \include
945 (str, path) = find_file(filename)
946 determine_format (str)
948 chunks = [('input', str)]
950 # we have to check for verbatim before doing include,
951 # because we don't want to include files that are mentioned
952 # inside a verbatim environment
953 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
954 chunks = chop_chunks(chunks, 'verb', make_verb)
955 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
957 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
958 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
962 taken_file_names = {}
963 def schedule_lilypond_block (chunk):
964 """Take the body and options from CHUNK, figure out how the
965 real .ly should look, and what should be left MAIN_STR (meant
966 for the main file). The .ly is written, and scheduled in
969 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
971 TODO has format [basename, extension, extension, ... ]
974 (type, body, opts) = chunk
975 assert type == 'lilypond'
976 file_body = compose_full_body (body, opts)
977 basename = 'lily-' + `abs(hash (file_body))`
979 m = re.search ('filename="(.*?)"', o)
981 basename = m.group (1)
982 if not taken_file_names.has_key(basename):
983 taken_file_names[basename] = 0
985 taken_file_names[basename] = taken_file_names[basename] + 1
986 basename = basename + "-%i" % taken_file_names[basename]
988 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
989 needed_filetypes = ['tex']
992 needed_filetypes.append('eps')
993 needed_filetypes.append('png')
994 if 'eps' in opts and not ('eps' in needed_filetypes):
995 needed_filetypes.append('eps')
996 pathbase = os.path.join (g_outdir, basename)
997 def f(base, ext1, ext2):
998 a = os.path.isfile(base + ext2)
999 if (os.path.isfile(base + ext1) and
1000 os.path.isfile(base + ext2) and
1001 os.stat(base+ext1)[stat.ST_MTIME] >
1002 os.stat(base+ext2)[stat.ST_MTIME]) or \
1003 not os.path.isfile(base + ext2):
1006 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1008 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1010 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1014 if 'printfilename' in opts:
1016 m= re.match ("filename=(.*)", o)
1018 newbody = newbody + get_output ("output-filename") % m.group(1)
1022 if 'verbatim' in opts:
1023 newbody = output_verbatim (body)
1026 m = re.search ('intertext="(.*?)"', o)
1028 newbody = newbody + m.group (1) + "\n\n"
1030 if 'noinline' in opts:
1031 s = 'output-noinline'
1032 elif format == 'latex':
1037 else: # format == 'texi'
1039 newbody = newbody + get_output (s) % {'fn': basename }
1040 return ('lilypond', newbody, opts, todo, basename)
1042 def process_lilypond_blocks(outname, chunks):#ugh rename
1044 # Count sections/chapters.
1046 if c[0] == 'lilypond':
1047 c = schedule_lilypond_block (c)
1048 elif c[0] == 'numcols':
1049 paperguru.m_num_cols = c[2]
1050 newchunks.append (c)
1056 sys.stderr.write ("invoking `%s'\n" % cmd)
1057 st = os.system (cmd)
1059 error ('Error command exited with value %d\n' % st)
1063 def get_bbox (filename):
1064 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1066 box = open (filename + '.bbox').read()
1067 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1070 gr = map (string.atoi, m.groups ())
1074 def make_pixmap (name):
1075 bbox = get_bbox (name + '.eps')
1077 fo = open (name + '.trans.eps' , 'w')
1078 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1083 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1084 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1086 cmd = r"""gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit > %s"""
1088 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1090 status = system (cmd)
1092 os.unlink (name + '.png')
1093 error ("Removing output file")
1095 def compile_all_files (chunks):
1102 if c[0] <> 'lilypond':
1111 if base + '.ly' not in tex:
1112 tex.append (base + '.ly')
1113 elif e == 'png' and g_do_pictures:
1119 # fixme: be sys-independent.
1121 if g_outdir and x[0] <> '/' :
1122 x = os.path.join (g_here_dir, x)
1125 incs = map (incl_opt, include_path)
1126 lilyopts = string.join (incs, ' ' )
1128 lilyopts = lilyopts + ' --dependencies '
1130 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1131 texfiles = string.join (tex, ' ')
1132 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1135 # Ugh, fixing up dependencies for .tex generation
1138 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1143 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1149 system(r"tex '\nonstopmode \input %s'" % e)
1150 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1158 def update_file (body, name):
1160 write the body if it has changed
1171 f = open (name , 'w')
1178 def getopt_args (opts):
1179 "Construct arguments (LONG, SHORT) for getopt from list of options."
1184 short = short + o[1]
1192 return (short, long)
1194 def option_help_str (o):
1195 "Transform one option description (4-tuple ) into neatly formatted string"
1213 return ' ' + sh + sep + long + arg
1216 def options_help_str (opts):
1217 "Convert a list of options into a neatly formatted string"
1223 s = option_help_str (o)
1224 strs.append ((s, o[3]))
1230 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1234 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1235 Generate hybrid LaTeX input from Latex + lilypond
1238 sys.stdout.write (options_help_str (option_definitions))
1239 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1243 Report bugs to bug-lilypond@gnu.org.
1245 Written by Tom Cato Amundsen <tca@gnu.org> and
1246 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1252 def write_deps (fn, target, chunks):
1254 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1255 f = open (os.path.join(g_outdir, fn), 'w')
1256 f.write ('%s%s: ' % (g_dep_prefix, target))
1257 for d in read_files:
1261 if c[0] == 'lilypond':
1262 (type, body, opts, todo, basename) = c;
1263 basenames.append (basename)
1266 d=g_outdir + '/' + d
1268 #if not os.isfile (d): # thinko?
1269 if not re.search ('/', d):
1270 d = g_dep_prefix + d
1271 f.write ('%s.tex ' % d)
1273 #if len (basenames):
1274 # for d in basenames:
1275 # f.write ('%s.ly ' % d)
1276 # f.write (' : %s' % target)
1282 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1284 def print_version ():
1286 sys.stdout.write (r"""Copyright 1998--1999
1287 Distributed under terms of the GNU General Public License. It comes with
1292 def check_texidoc (chunks):
1295 if c[0] == 'lilypond':
1296 (type, body, opts, todo, basename) = c;
1297 pathbase = os.path.join (g_outdir, basename)
1298 if os.path.isfile (pathbase + '.texidoc'):
1299 body = '\n@include %s.texidoc\n' % basename + body
1300 c = (type, body, opts, todo, basename)
1305 ## what's this? Docme --hwn
1307 def fix_epswidth (chunks):
1310 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1311 newchunks.append (c)
1316 m = re.match ('magnification=([0-9.]+)', o)
1318 mag = string.atof (m.group (1))
1320 def replace_eps_dim (match, lmag = mag):
1321 filename = match.group (1)
1322 dims = bounding_box_dimensions (filename)
1324 return '%fpt' % (dims[0] *lmag)
1326 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1327 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1333 def do_file(input_filename):
1337 my_outname = outname
1339 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1340 my_depname = my_outname + '.dep'
1342 chunks = read_doc_file(input_filename)
1343 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1344 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1345 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1346 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1347 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1348 chunks = chop_chunks(chunks, 'numcols', do_columns)
1350 #for c in chunks: print "c:", c;
1352 scan_preamble(chunks)
1353 chunks = process_lilypond_blocks(my_outname, chunks)
1355 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1358 if __main__.g_run_lilypond:
1359 compile_all_files (chunks)
1360 chunks = fix_epswidth (chunks)
1362 if __main__.format == 'texi':
1363 chunks = check_texidoc (chunks)
1366 chunks = completize_preamble (chunks)
1367 sys.stderr.write ("Writing `%s'\n" % foutn)
1368 fout = open (foutn, 'w')
1375 write_deps (my_depname, foutn, chunks)
1380 (sh, long) = getopt_args (__main__.option_definitions)
1381 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1382 except getopt.error, msg:
1383 sys.stderr.write("error: %s" % msg)
1391 if o == '--include' or o == '-I':
1392 include_path.append (a)
1393 elif o == '--version' or o == '-v':
1396 elif o == '--format' or o == '-f':
1398 elif o == '--outname' or o == '-o':
1401 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1404 elif o == '--help' or o == '-h':
1406 elif o == '--no-lily' or o == '-n':
1407 __main__.g_run_lilypond = 0
1408 elif o == '--dependencies' or o == '-M':
1410 elif o == '--default-music-fontsize':
1411 default_music_fontsize = string.atoi (a)
1412 elif o == '--default-lilypond-fontsize':
1413 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1414 default_music_fontsize = string.atoi (a)
1415 elif o == '--extra-options':
1417 elif o == '--force-music-fontsize':
1418 g_force_lilypond_fontsize = string.atoi(a)
1419 elif o == '--force-lilypond-fontsize':
1420 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1421 g_force_lilypond_fontsize = string.atoi(a)
1422 elif o == '--dep-prefix':
1424 elif o == '--no-pictures':
1426 elif o == '--read-lys':
1428 elif o == '--outdir':
1433 if os.path.isfile(g_outdir):
1434 error ("outdir is a file: %s" % g_outdir)
1435 if not os.path.exists(g_outdir):
1437 setup_environment ()
1438 for input_filename in files:
1439 do_file(input_filename)
1442 # Petr, ik zou willen dat ik iets zinvoller deed,
1443 # maar wat ik kan ik doen, het verandert toch niets?