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 pre instead. Fix by Mats.
55 # todo: should check Python version first.
63 program_version = '@TOPLEVEL_VERSION@'
64 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
65 program_version = '1.5.53'
67 # if set, LILYPONDPREFIX must take prevalence
68 # if datadir is not set, we're doing a build and LILYPONDPREFIX
71 if os.environ.has_key ('LILYPONDPREFIX') :
72 datadir = os.environ['LILYPONDPREFIX']
76 while datadir[-1] == os.sep:
79 # Try to cater for bad installations of LilyPond, that have
80 # broken TeX setup. Just hope this doesn't hurt good TeX
81 # setups. Maybe we should check if kpsewhich can find
82 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
84 'MFINPUTS' : datadir + '/mf:',
85 'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
86 'TFMFONTS' : datadir + '/tfm:',
87 'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
88 'GS_LIB' : datadir + '/ps',
91 # tex needs lots of memory, more than it gets by default on Debian
92 non_path_environment = {
93 'extra_mem_top' : '1000000',
94 'extra_mem_bottom' : '1000000',
95 'pool_size' : '250000',
98 def setup_environment ():
99 for key in environment.keys ():
100 val = environment[key]
101 if os.environ.has_key (key):
102 val = val + os.pathsep + os.environ[key]
103 os.environ[key] = val
105 for key in non_path_environment.keys ():
106 val = non_path_environment[key]
107 print '%s=%s' % (key,val)
108 os.environ[key] = val
110 include_path = [os.getcwd()]
113 # g_ is for global (?)
115 g_here_dir = os.getcwd ()
118 g_force_lilypond_fontsize = 0
127 default_music_fontsize = 16
128 default_text_fontsize = 12
131 # this code is ugly. It should be cleaned
135 # the dimensions are from geometry.sty
136 'a0paper': (mm2pt(841), mm2pt(1189)),
137 'a1paper': (mm2pt(595), mm2pt(841)),
138 'a2paper': (mm2pt(420), mm2pt(595)),
139 'a3paper': (mm2pt(297), mm2pt(420)),
140 'a4paper': (mm2pt(210), mm2pt(297)),
141 'a5paper': (mm2pt(149), mm2pt(210)),
142 'b0paper': (mm2pt(1000), mm2pt(1414)),
143 'b1paper': (mm2pt(707), mm2pt(1000)),
144 'b2paper': (mm2pt(500), mm2pt(707)),
145 'b3paper': (mm2pt(353), mm2pt(500)),
146 'b4paper': (mm2pt(250), mm2pt(353)),
147 'b5paper': (mm2pt(176), mm2pt(250)),
148 'letterpaper': (in2pt(8.5), in2pt(11)),
149 'legalpaper': (in2pt(8.5), in2pt(14)),
150 'executivepaper': (in2pt(7.25), in2pt(10.5))}
151 self.m_use_geometry = None
152 self.m_papersize = 'letterpaper'
156 self.m_geo_landscape = 0
157 self.m_geo_width = None
158 self.m_geo_textwidth = None
159 self.m_geo_lmargin = None
160 self.m_geo_rmargin = None
161 self.m_geo_includemp = None
162 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
163 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
164 self.m_geo_x_marginparwidth = None
165 self.m_geo_x_marginparsep = None
167 def set_geo_option(self, name, value):
169 if type(value) == type([]):
170 value = map(conv_dimen_to_float, value)
172 value = conv_dimen_to_float(value)
174 if name == 'body' or name == 'text':
175 if type(value) == type([]):
176 self.m_geo_textwidth = value[0]
178 self.m_geo_textwidth = value
180 elif name == 'portrait':
181 self.m_geo_landscape = 0
182 elif name == 'reversemp' or name == 'reversemarginpar':
183 if self.m_geo_includemp == None:
184 self.m_geo_includemp = 1
185 elif name == 'marginparwidth' or name == 'marginpar':
186 self.m_geo_x_marginparwidth = value
187 self.m_geo_includemp = 1
188 elif name == 'marginparsep':
189 self.m_geo_x_marginparsep = value
190 self.m_geo_includemp = 1
191 elif name == 'scale':
192 if type(value) == type([]):
193 self.m_geo_width = self.get_paperwidth() * value[0]
195 self.m_geo_width = self.get_paperwidth() * value
196 elif name == 'hscale':
197 self.m_geo_width = self.get_paperwidth() * value
198 elif name == 'left' or name == 'lmargin':
199 self.m_geo_lmargin = value
200 elif name == 'right' or name == 'rmargin':
201 self.m_geo_rmargin = value
202 elif name == 'hdivide' or name == 'divide':
203 if value[0] not in ('*', ''):
204 self.m_geo_lmargin = value[0]
205 if value[1] not in ('*', ''):
206 self.m_geo_width = value[1]
207 if value[2] not in ('*', ''):
208 self.m_geo_rmargin = value[2]
209 elif name == 'hmargin':
210 if type(value) == type([]):
211 self.m_geo_lmargin = value[0]
212 self.m_geo_rmargin = value[1]
214 self.m_geo_lmargin = value
215 self.m_geo_rmargin = value
216 elif name == 'margin':#ugh there is a bug about this option in
217 # the geometry documentation
218 if type(value) == type([]):
219 self.m_geo_lmargin = value[0]
220 self.m_geo_rmargin = value[0]
222 self.m_geo_lmargin = value
223 self.m_geo_rmargin = value
224 elif name == 'total':
225 if type(value) == type([]):
226 self.m_geo_width = value[0]
228 self.m_geo_width = value
229 elif name == 'width' or name == 'totalwidth':
230 self.m_geo_width = value
231 elif name == 'paper' or name == 'papername':
232 self.m_papersize = value
233 elif name[-5:] == 'paper':
234 self.m_papersize = name
238 def __setattr__(self, name, value):
239 if type(value) == type("") and \
240 dimension_conversion_dict.has_key (value[-2:]):
241 f = dimension_conversion_dict[value[-2:]]
242 self.__dict__[name] = f(float(value[:-2]))
244 self.__dict__[name] = value
247 s = "LatexPaper:\n-----------"
248 for v in self.__dict__.keys():
250 s = s + str (v) + ' ' + str (self.__dict__[v])
251 s = s + "-----------"
254 def get_linewidth(self):
255 w = self._calc_linewidth()
256 if self.m_num_cols == 2:
260 def get_paperwidth(self):
261 #if self.m_use_geometry:
262 return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
263 #return self.m_paperdef[self.m_papersize][self.m_landscape]
265 def _calc_linewidth(self):
266 # since geometry sometimes ignores 'includemp', this is
267 # more complicated than it should be
269 if self.m_geo_includemp:
270 if self.m_geo_x_marginparsep is not None:
271 mp = mp + self.m_geo_x_marginparsep
273 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
274 if self.m_geo_x_marginparwidth is not None:
275 mp = mp + self.m_geo_x_marginparwidth
277 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
279 #ugh test if this is necessary
283 if not self.m_use_geometry:
284 return latex_linewidths[self.m_papersize][self.m_fontsize]
286 geo_opts = (self.m_geo_lmargin == None,
287 self.m_geo_width == None,
288 self.m_geo_rmargin == None)
290 if geo_opts == (1, 1, 1):
291 if self.m_geo_textwidth:
292 return self.m_geo_textwidth
293 w = self.get_paperwidth() * 0.8
295 elif geo_opts == (0, 1, 1):
296 if self.m_geo_textwidth:
297 return self.m_geo_textwidth
298 return self.f1(self.m_geo_lmargin, mp)
299 elif geo_opts == (1, 1, 0):
300 if self.m_geo_textwidth:
301 return self.m_geo_textwidth
302 return self.f1(self.m_geo_rmargin, mp)
304 in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
305 if self.m_geo_textwidth:
306 return self.m_geo_textwidth
307 return self.m_geo_width - mp
308 elif geo_opts in ((0, 1, 0), (0, 0, 0)):
309 w = self.get_paperwidth() \
310 - self.m_geo_lmargin - self.m_geo_rmargin - mp
314 raise "Never do this!"
316 tmp = self.get_paperwidth() - m * 2 - mp
321 tmp = self.get_paperwidth() - self.m_geo_lmargin \
329 self.m_papersize = 'letterpaper'
331 def get_linewidth(self):
332 return texi_linewidths[self.m_papersize][self.m_fontsize]
338 def em2pt(x, fontsize = 10):
339 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
340 def ex2pt(x, fontsize = 10):
341 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
346 dimension_conversion_dict ={
348 'cm': lambda x: mm2pt(10*x),
355 # Convert numeric values, with or without specific dimension, to floats.
357 def conv_dimen_to_float(value):
358 if type(value) == type(""):
359 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
362 num = string.atof(m.group (1))
363 conv = dimension_conversion_dict[m.group(2)]
367 elif re.match ("^[0-9.]+$",value):
374 # indices are no. of columns, papersize, fontsize
375 # Why can't this be calculated?
377 'a4paper':{10: 345, 11: 360, 12: 390},
378 'a4paper-landscape': {10: 598, 11: 596, 12:592},
379 'a5paper':{10: 276, 11: 276, 12: 276},
380 'b5paper':{10: 345, 11: 356, 12: 356},
381 'letterpaper':{10: 345, 11: 360, 12: 390},
382 'letterpaper-landscape':{10: 598, 11: 596, 12:596},
383 'legalpaper': {10: 345, 11: 360, 12: 390},
384 'executivepaper':{10: 345, 11: 360, 12: 379}}
387 'afourpaper': {12: mm2pt(160)},
388 'afourwide': {12: in2pt(6.5)},
389 'afourlatex': {12: mm2pt(150)},
390 'smallbook': {12: in2pt(5)},
391 'letterpaper': {12: in2pt(6)}}
393 option_definitions = [
394 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
395 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
396 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
397 ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
398 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
399 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
400 ('DIR', 'I', 'include', 'include path'),
401 ('', 'M', 'dependencies', 'write dependencies'),
402 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
403 ('', 'n', 'no-lily', 'don\'t run lilypond'),
404 ('', '', 'no-pictures', "don\'t generate pictures"),
405 ('', '', 'read-lys', "don't write ly files."),
406 ('FILE', 'o', 'outname', 'filename main output file'),
407 ('FILE', '', 'outdir', "where to place generated files"),
408 ('', 'v', 'version', 'print version information' ),
409 ('', 'h', 'help', 'print help'),
412 # format specific strings, ie. regex-es for input, and % strings for output
415 'output-lilypond-fragment' : r"""\begin[eps,singleline,%s]{lilypond}
422 'output-filename' : r'''
425 'output-lilypond': r"""\begin[%s]{lilypond}
429 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
431 'output-default-post': "\\def\postLilypondExample{}\n",
432 'output-default-pre': "\\def\preLilypondExample{}\n",
433 'usepackage-graphics': '\\usepackage{graphics}\n',
434 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
435 'output-noinline': r'''
436 %% generated: %(fn)s.eps
438 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
439 'pagebreak': r'\pagebreak',
442 'texi' : {'output-lilypond': """@lilypond[%s]
446 'output-filename' : r'''
449 'output-lilypond-fragment': """@lilypond[%s]
450 \context Staff\context Voice{ %s }
452 'output-noinline': r'''
453 @c generated: %(fn)s.png
456 'output-verbatim': r"""@example
461 # do some tweaking: @ is needed in some ps stuff.
462 # override EndLilyPondOutput, since @tex is done
463 # in a sandbox, you can't do \input lilyponddefs at the
464 # top of the document.
466 # should also support fragment in
472 \def\EndLilyPondOutput{}
478 <a href="%(fn)s.png">
479 <img border=0 src="%(fn)s.png" alt="[picture of music]">
486 def output_verbatim (body):
487 if __main__.format == 'texi':
488 body = re.sub ('([@{}])', '@\\1', body)
489 return get_output ('output-verbatim') % body
492 #warning: this uses extended regular expressions. Tread with care.
494 # legenda (?P name parameter
495 # *? match non-greedily.
498 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
499 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
500 'option-sep' : ',\s*',
501 'header': r"\\documentclass\s*(\[.*?\])?",
502 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
503 'preamble-end': r'(?P<code>\\begin{document})',
504 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
505 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
506 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
507 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
508 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
509 'def-post-re': r"\\def\\postLilypondExample",
510 'def-pre-re': r"\\def\\preLilypondExample",
511 'usepackage-graphics': r"\usepackage{graphics}",
512 'intertext': r',?\s*intertext=\".*?\"',
513 'multiline-comment': no_match,
514 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
515 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
519 # why do we have distinction between @mbinclude and @include?
523 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
526 'preamble-end': no_match,
527 'landscape': no_match,
528 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
529 'verb': r"""(?P<code>@code{.*?})""",
530 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
531 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
532 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
533 'option-sep' : ',\s*',
534 'intertext': r',?\s*intertext=\".*?\"',
535 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
536 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
542 for r in re_dict.keys ():
545 for k in olddict.keys ():
547 newdict[k] = re.compile (olddict[k])
549 print 'invalid regexp: %s' % olddict[k]
551 # we'd like to catch and reraise a more detailed error, but
552 # alas, the exceptions changed across the 1.5/2.1 boundary.
567 def get_output (name):
568 return output_dict[format][name]
571 return re_dict[format][name]
573 def bounding_box_dimensions(fname):
575 fname = os.path.join(g_outdir, fname)
579 error ("Error opening `%s'" % fname)
581 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
584 gs = map (lambda x: string.atoi (x), s.groups ())
585 return (int (gs[2] - gs[0] + 0.5),
586 int (gs[3] - gs[1] + 0.5))
591 sys.stderr.write (str + "\n Exiting ... \n\n")
595 def compose_full_body (body, opts):
596 """Construct the lilypond code to send to Lilypond.
597 Add stuff to BODY using OPTS as options."""
598 music_size = default_music_fontsize
599 latex_size = default_text_fontsize
603 if g_force_lilypond_fontsize:
604 music_size = g_force_lilypond_fontsize
606 m = re.match ('([0-9]+)pt', o)
608 music_size = string.atoi(m.group (1))
610 m = re.match ('latexfontsize=([0-9]+)pt', o)
612 latex_size = string.atoi (m.group (1))
614 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
616 f = float (m.group (1))
617 indent = 'indent = %f\\%s' % (f, m.group (2))
619 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
621 f = float (m.group (1))
622 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
624 if re.search ('\\\\score', body):
628 if 'fragment' in opts:
630 if 'nofragment' in opts:
633 if is_fragment and not 'multiline' in opts:
634 opts.append('singleline')
636 if 'singleline' in opts:
637 linewidth = 'linewidth = -1.0'
639 l = __main__.paperguru.get_linewidth ()
640 linewidth = 'linewidth = %f\pt' % l
642 if 'noindent' in opts:
643 indent = 'indent = 0.0\mm'
646 m= re.search ('relative(.*)', o)
650 v = string.atoi (m.group (1))
657 pitch = pitch + '\,' * v
659 pitch = pitch + '\'' * v
661 body = '\\relative %s { %s }' %(pitch, body)
670 optstring = string.join (opts, ' ')
671 optstring = re.sub ('\n', ' ', optstring)
673 %% Generated automatically by: lilypond-book.py
675 \include "paper%d.ly"
680 ''' % (optstring, music_size, linewidth, indent) + body
682 # ughUGH not original options
685 def parse_options_string(s):
687 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
688 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
689 r3 = re.compile("(\w+?)((,\s*)|$)")
694 d[m.group(2)] = re.split(",\s*", m.group(3))
699 d[m.group(2)] = m.group(3)
707 error ("format of option string invalid (was `%')" % s)
710 def scan_latex_preamble(chunks):
711 # first we want to scan the \documentclass line
712 # it should be the first non-comment line
715 if chunks[idx][0] == 'ignore':
718 m = get_re ('header').match(chunks[idx][1])
719 if m <> None and m.group (1):
720 options = re.split (',[\n \t]*', m.group(1)[1:-1])
725 paperguru.m_landscape = 1
726 m = re.match("(.*?)paper", o)
728 paperguru.m_papersize = m.group()
730 m = re.match("(\d\d)pt", o)
732 paperguru.m_fontsize = int(m.group(1))
735 while chunks[idx][0] != 'preamble-end':
736 if chunks[idx] == 'ignore':
739 m = get_re ('geometry').search(chunks[idx][1])
741 paperguru.m_use_geometry = 1
742 o = parse_options_string(m.group('options'))
744 paperguru.set_geo_option(k, o[k])
747 def scan_texi_preamble (chunks):
748 # this is not bulletproof..., it checks the first 10 chunks
749 for c in chunks[:10]:
751 for s in ('afourpaper', 'afourwide', 'letterpaper',
752 'afourlatex', 'smallbook'):
753 if string.find(c[1], "@%s" % s) != -1:
754 paperguru.m_papersize = s
756 def scan_preamble (chunks):
757 if __main__.format == 'texi':
758 scan_texi_preamble(chunks)
760 assert __main__.format == 'latex'
761 scan_latex_preamble(chunks)
764 def completize_preamble (chunks):
765 if __main__.format == 'texi':
767 pre_b = post_b = graphics_b = None
769 if chunk[0] == 'preamble-end':
771 if chunk[0] == 'input':
772 m = get_re('def-pre-re').search(chunk[1])
775 if chunk[0] == 'input':
776 m = get_re('def-post-re').search(chunk[1])
779 if chunk[0] == 'input':
780 m = get_re('usepackage-graphics').search(chunk[1])
784 while chunks[x][0] != 'preamble-end':
787 chunks.insert(x, ('input', get_output ('output-default-pre')))
789 chunks.insert(x, ('input', get_output ('output-default-post')))
791 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
796 def find_file (name):
798 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
803 for a in include_path:
805 nm = os.path.join (a, name)
807 __main__.read_files.append (nm)
812 sys.stderr.write ("Reading `%s'\n" % nm)
813 return (f.read (), nm)
815 error ("File not found `%s'\n" % name)
818 def do_ignore(match_object):
819 return [('ignore', match_object.group('code'))]
820 def do_preamble_end(match_object):
821 return [('preamble-end', match_object.group('code'))]
823 def make_verbatim(match_object):
824 return [('verbatim', match_object.group('code'))]
826 def make_verb(match_object):
827 return [('verb', match_object.group('code'))]
829 def do_include_file(m):
831 return [('input', get_output ('pagebreak'))] \
832 + read_doc_file(m.group('filename')) \
833 + [('input', get_output ('pagebreak'))]
835 def do_input_file(m):
836 return read_doc_file(m.group('filename'))
838 def make_lilypond(m):
839 if m.group('options'):
840 options = m.group('options')
843 return [('input', get_output('output-lilypond-fragment') %
844 (options, m.group('code')))]
846 def make_lilypond_file(m):
849 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
850 into a @lilypond .. @end lilypond block.
854 if m.group('options'):
855 options = m.group('options')
858 (content, nm) = find_file(m.group('filename'))
859 options = "filename=%s," % nm + options
861 return [('input', get_output('output-lilypond') %
864 def make_lilypond_block(m):
865 if m.group('options'):
866 options = get_re('option-sep').split (m.group('options'))
869 options = filter(lambda s: s != '', options)
870 return [('lilypond', m.group('code'), options)]
873 if __main__.format != 'latex':
875 if m.group('num') == 'one':
876 return [('numcols', m.group('code'), 1)]
877 if m.group('num') == 'two':
878 return [('numcols', m.group('code'), 2)]
880 def chop_chunks(chunks, re_name, func, use_match=0):
886 m = get_re (re_name).search (str)
888 newchunks.append (('input', str))
892 newchunks.append (('input', str[:m.start ('match')]))
894 newchunks.append (('input', str[:m.start (0)]))
895 #newchunks.extend(func(m))
896 # python 1.5 compatible:
897 newchunks = newchunks + func(m)
898 str = str [m.end(0):]
903 def determine_format (str):
904 if __main__.format == '':
906 latex = re.search ('\\\\document', str[:200])
907 texinfo = re.search ('@node|@setfilename', str[:200])
912 if texinfo and latex == None:
914 elif latex and texinfo == None:
917 error("error: can't determine format, please specify")
920 if __main__.paperguru == None:
921 if __main__.format == 'texi':
926 __main__.paperguru = g
929 def read_doc_file (filename):
930 """Read the input file, find verbatim chunks and do \input and \include
932 (str, path) = find_file(filename)
933 determine_format (str)
935 chunks = [('input', str)]
937 # we have to check for verbatim before doing include,
938 # because we don't want to include files that are mentioned
939 # inside a verbatim environment
940 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
941 chunks = chop_chunks(chunks, 'verb', make_verb)
942 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
944 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
945 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
949 taken_file_names = {}
950 def schedule_lilypond_block (chunk):
951 """Take the body and options from CHUNK, figure out how the
952 real .ly should look, and what should be left MAIN_STR (meant
953 for the main file). The .ly is written, and scheduled in
956 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
958 TODO has format [basename, extension, extension, ... ]
961 (type, body, opts) = chunk
962 assert type == 'lilypond'
963 file_body = compose_full_body (body, opts)
964 basename = 'lily-' + `abs(hash (file_body))`
966 m = re.search ('filename="(.*?)"', o)
968 basename = m.group (1)
969 if not taken_file_names.has_key(basename):
970 taken_file_names[basename] = 0
972 taken_file_names[basename] = taken_file_names[basename] + 1
973 basename = basename + "-%i" % taken_file_names[basename]
975 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
976 needed_filetypes = ['tex']
979 needed_filetypes.append('eps')
980 needed_filetypes.append('png')
981 if 'eps' in opts and not ('eps' in needed_filetypes):
982 needed_filetypes.append('eps')
983 pathbase = os.path.join (g_outdir, basename)
984 def f(base, ext1, ext2):
985 a = os.path.isfile(base + ext2)
986 if (os.path.isfile(base + ext1) and
987 os.path.isfile(base + ext2) and
988 os.stat(base+ext1)[stat.ST_MTIME] >
989 os.stat(base+ext2)[stat.ST_MTIME]) or \
990 not os.path.isfile(base + ext2):
993 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
995 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
997 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1001 if 'printfilename' in opts:
1003 m= re.match ("filename=(.*)", o)
1005 newbody = newbody + get_output ("output-filename") % m.group(1)
1009 if 'verbatim' in opts:
1010 newbody = output_verbatim (body)
1013 m = re.search ('intertext="(.*?)"', o)
1015 newbody = newbody + m.group (1) + "\n\n"
1017 if 'noinline' in opts:
1018 s = 'output-noinline'
1019 elif format == 'latex':
1024 else: # format == 'texi'
1026 newbody = newbody + get_output (s) % {'fn': basename }
1027 return ('lilypond', newbody, opts, todo, basename)
1029 def process_lilypond_blocks(outname, chunks):#ugh rename
1031 # Count sections/chapters.
1033 if c[0] == 'lilypond':
1034 c = schedule_lilypond_block (c)
1035 elif c[0] == 'numcols':
1036 paperguru.m_num_cols = c[2]
1037 newchunks.append (c)
1043 sys.stderr.write ("invoking `%s'\n" % cmd)
1044 st = os.system (cmd)
1046 error ('Error command exited with value %d\n' % st)
1050 def get_bbox (filename):
1051 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1053 box = open (filename + '.bbox').read()
1054 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1057 gr = map (string.atoi, m.groups ())
1061 def make_pixmap (name):
1062 bbox = get_bbox (name + '.eps')
1064 fo = open (name + '.trans.eps' , 'w')
1065 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1070 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1071 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1073 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1075 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1077 status = system (cmd)
1079 os.unlink (name + '.png')
1080 error ("Removing output file")
1082 def compile_all_files (chunks):
1089 if c[0] <> 'lilypond':
1098 if base + '.ly' not in tex:
1099 tex.append (base + '.ly')
1100 elif e == 'png' and g_do_pictures:
1106 # fixme: be sys-independent.
1108 if g_outdir and x[0] <> '/' :
1109 x = os.path.join (g_here_dir, x)
1112 incs = map (incl_opt, include_path)
1113 lilyopts = string.join (incs, ' ' )
1115 lilyopts = lilyopts + ' --dependencies '
1117 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1118 texfiles = string.join (tex, ' ')
1119 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1122 # Ugh, fixing up dependencies for .tex generation
1125 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1130 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1136 system(r"tex '\nonstopmode \input %s'" % e)
1137 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1145 def update_file (body, name):
1147 write the body if it has changed
1158 f = open (name , 'w')
1165 def getopt_args (opts):
1166 "Construct arguments (LONG, SHORT) for getopt from list of options."
1171 short = short + o[1]
1179 return (short, long)
1181 def option_help_str (o):
1182 "Transform one option description (4-tuple ) into neatly formatted string"
1200 return ' ' + sh + sep + long + arg
1203 def options_help_str (opts):
1204 "Convert a list of options into a neatly formatted string"
1210 s = option_help_str (o)
1211 strs.append ((s, o[3]))
1217 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1221 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1222 Generate hybrid LaTeX input from Latex + lilypond
1225 sys.stdout.write (options_help_str (option_definitions))
1226 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1230 Report bugs to bug-lilypond@gnu.org.
1232 Written by Tom Cato Amundsen <tca@gnu.org> and
1233 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1239 def write_deps (fn, target, chunks):
1241 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1242 f = open (os.path.join(g_outdir, fn), 'w')
1243 f.write ('%s%s: ' % (g_dep_prefix, target))
1244 for d in read_files:
1248 if c[0] == 'lilypond':
1249 (type, body, opts, todo, basename) = c;
1250 basenames.append (basename)
1253 d=g_outdir + '/' + d
1255 #if not os.isfile (d): # thinko?
1256 if not re.search ('/', d):
1257 d = g_dep_prefix + d
1258 f.write ('%s.tex ' % d)
1260 #if len (basenames):
1261 # for d in basenames:
1262 # f.write ('%s.ly ' % d)
1263 # f.write (' : %s' % target)
1269 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1271 def print_version ():
1273 sys.stdout.write (r"""Copyright 1998--1999
1274 Distributed under terms of the GNU General Public License. It comes with
1279 def check_texidoc (chunks):
1282 if c[0] == 'lilypond':
1283 (type, body, opts, todo, basename) = c;
1284 pathbase = os.path.join (g_outdir, basename)
1285 if os.path.isfile (pathbase + '.texidoc'):
1286 body = '\n@include %s.texidoc\n' % basename + body
1287 c = (type, body, opts, todo, basename)
1292 ## what's this? Docme --hwn
1294 def fix_epswidth (chunks):
1297 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1298 newchunks.append (c)
1303 m = re.match ('magnification=([0-9.]+)', o)
1305 mag = string.atof (m.group (1))
1307 def replace_eps_dim (match, lmag = mag):
1308 filename = match.group (1)
1309 dims = bounding_box_dimensions (filename)
1311 return '%fpt' % (dims[0] *lmag)
1313 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1314 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1320 def do_file(input_filename):
1324 my_outname = outname
1326 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1327 my_depname = my_outname + '.dep'
1329 chunks = read_doc_file(input_filename)
1330 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1331 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1332 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1333 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1334 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1335 chunks = chop_chunks(chunks, 'numcols', do_columns)
1337 #for c in chunks: print "c:", c;
1339 scan_preamble(chunks)
1340 chunks = process_lilypond_blocks(my_outname, chunks)
1342 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1345 if __main__.g_run_lilypond:
1346 compile_all_files (chunks)
1347 chunks = fix_epswidth (chunks)
1349 if __main__.format == 'texi':
1350 chunks = check_texidoc (chunks)
1353 chunks = completize_preamble (chunks)
1354 sys.stderr.write ("Writing `%s'\n" % foutn)
1355 fout = open (foutn, 'w')
1362 write_deps (my_depname, foutn, chunks)
1367 (sh, long) = getopt_args (__main__.option_definitions)
1368 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1369 except getopt.error, msg:
1370 sys.stderr.write("error: %s" % msg)
1378 if o == '--include' or o == '-I':
1379 include_path.append (a)
1380 elif o == '--version' or o == '-v':
1383 elif o == '--format' or o == '-f':
1385 elif o == '--outname' or o == '-o':
1388 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1391 elif o == '--help' or o == '-h':
1393 elif o == '--no-lily' or o == '-n':
1394 __main__.g_run_lilypond = 0
1395 elif o == '--dependencies' or o == '-M':
1397 elif o == '--default-music-fontsize':
1398 default_music_fontsize = string.atoi (a)
1399 elif o == '--default-lilypond-fontsize':
1400 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1401 default_music_fontsize = string.atoi (a)
1402 elif o == '--extra-options':
1404 elif o == '--force-music-fontsize':
1405 g_force_lilypond_fontsize = string.atoi(a)
1406 elif o == '--force-lilypond-fontsize':
1407 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1408 g_force_lilypond_fontsize = string.atoi(a)
1409 elif o == '--dep-prefix':
1411 elif o == '--no-pictures':
1413 elif o == '--read-lys':
1415 elif o == '--outdir':
1420 if os.path.isfile(g_outdir):
1421 error ("outdir is a file: %s" % g_outdir)
1422 if not os.path.exists(g_outdir):
1424 setup_environment ()
1425 for input_filename in files:
1426 do_file(input_filename)
1429 # Petr, ik zou willen dat ik iets zinvoller deed,
1430 # maar wat ik kan ik doen, het verandert toch niets?