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': "\\begin{verbatim}%s\\end{verbatim}",
430 'output-default-post': "\\def\postLilypondExample{}\n",
431 'output-default-pre': "\\def\preLilypondExample{}\n",
432 'usepackage-graphics': '\\usepackage{graphics}\n',
433 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
434 'output-noinline': r'''
435 %% generated: %(fn)s.eps
437 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
438 'pagebreak': r'\pagebreak',
441 'texi' : {'output-lilypond': """@lilypond[%s]
445 'output-filename' : r'''
448 'output-lilypond-fragment': """@lilypond[%s]
449 \context Staff\context Voice{ %s }
451 'output-noinline': r'''
452 @c generated: %(fn)s.png
455 'output-verbatim': r"""@example
460 # do some tweaking: @ is needed in some ps stuff.
461 # override EndLilyPondOutput, since @tex is done
462 # in a sandbox, you can't do \input lilyponddefs at the
463 # top of the document.
465 # should also support fragment in
471 \def\EndLilyPondOutput{}
477 <a href="%(fn)s.png">
478 <img border=0 src="%(fn)s.png" alt="[picture of music]">
485 def output_verbatim (body):
486 if __main__.format == 'texi':
487 body = re.sub ('([@{}])', '@\\1', body)
488 return get_output ('output-verbatim') % body
491 #warning: this uses extended regular expressions. Tread with care.
493 # legenda (?P name parameter
494 # *? match non-greedily.
497 'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
498 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
499 'option-sep' : ',\s*',
500 'header': r"\\documentclass\s*(\[.*?\])?",
501 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
502 'preamble-end': r'(?P<code>\\begin{document})',
503 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
504 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
505 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
506 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
507 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
508 'def-post-re': r"\\def\\postLilypondExample",
509 'def-pre-re': r"\\def\\preLilypondExample",
510 'usepackage-graphics': r"\usepackage{graphics}",
511 'intertext': r',?\s*intertext=\".*?\"',
512 'multiline-comment': no_match,
513 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
514 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
518 # why do we have distinction between @mbinclude and @include?
522 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
525 'preamble-end': no_match,
526 'landscape': no_match,
527 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
528 'verb': r"""(?P<code>@code{.*?})""",
529 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
530 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
531 'lilypond-block': r"""(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s""",
532 'option-sep' : ',\s*',
533 'intertext': r',?\s*intertext=\".*?\"',
534 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
535 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
541 for r in re_dict.keys ():
544 for k in olddict.keys ():
546 newdict[k] = re.compile (olddict[k])
548 print 'invalid regexp: %s' % olddict[k]
550 # we'd like to catch and reraise a more detailed error, but
551 # alas, the exceptions changed across the 1.5/2.1 boundary.
566 def get_output (name):
567 return output_dict[format][name]
570 return re_dict[format][name]
572 def bounding_box_dimensions(fname):
574 fname = os.path.join(g_outdir, fname)
578 error ("Error opening `%s'" % fname)
580 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
583 gs = map (lambda x: string.atoi (x), s.groups ())
584 return (int (gs[2] - gs[0] + 0.5),
585 int (gs[3] - gs[1] + 0.5))
590 sys.stderr.write (str + "\n Exiting ... \n\n")
594 def compose_full_body (body, opts):
595 """Construct the lilypond code to send to Lilypond.
596 Add stuff to BODY using OPTS as options."""
597 music_size = default_music_fontsize
598 latex_size = default_text_fontsize
602 if g_force_lilypond_fontsize:
603 music_size = g_force_lilypond_fontsize
605 m = re.match ('([0-9]+)pt', o)
607 music_size = string.atoi(m.group (1))
609 m = re.match ('latexfontsize=([0-9]+)pt', o)
611 latex_size = string.atoi (m.group (1))
613 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
615 f = float (m.group (1))
616 indent = 'indent = %f\\%s' % (f, m.group (2))
618 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
620 f = float (m.group (1))
621 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
623 if re.search ('\\\\score', body):
627 if 'fragment' in opts:
629 if 'nofragment' in opts:
632 if is_fragment and not 'multiline' in opts:
633 opts.append('singleline')
635 if 'singleline' in opts:
636 linewidth = 'linewidth = -1.0'
638 l = __main__.paperguru.get_linewidth ()
639 linewidth = 'linewidth = %f\pt' % l
641 if 'noindent' in opts:
642 indent = 'indent = 0.0\mm'
645 m= re.search ('relative(.*)', o)
649 v = string.atoi (m.group (1))
656 pitch = pitch + '\,' * v
658 pitch = pitch + '\'' * v
660 body = '\\relative %s { %s }' %(pitch, body)
669 optstring = string.join (opts, ' ')
670 optstring = re.sub ('\n', ' ', optstring)
672 %% Generated automatically by: lilypond-book.py
674 \include "paper%d.ly"
679 ''' % (optstring, music_size, linewidth, indent) + body
681 # ughUGH not original options
684 def parse_options_string(s):
686 r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
687 r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
688 r3 = re.compile("(\w+?)((,\s*)|$)")
693 d[m.group(2)] = re.split(",\s*", m.group(3))
698 d[m.group(2)] = m.group(3)
706 error ("format of option string invalid (was `%')" % s)
709 def scan_latex_preamble(chunks):
710 # first we want to scan the \documentclass line
711 # it should be the first non-comment line
714 if chunks[idx][0] == 'ignore':
717 m = get_re ('header').match(chunks[idx][1])
718 if m <> None and m.group (1):
719 options = re.split (',[\n \t]*', m.group(1)[1:-1])
724 paperguru.m_landscape = 1
725 m = re.match("(.*?)paper", o)
727 paperguru.m_papersize = m.group()
729 m = re.match("(\d\d)pt", o)
731 paperguru.m_fontsize = int(m.group(1))
734 while chunks[idx][0] != 'preamble-end':
735 if chunks[idx] == 'ignore':
738 m = get_re ('geometry').search(chunks[idx][1])
740 paperguru.m_use_geometry = 1
741 o = parse_options_string(m.group('options'))
743 paperguru.set_geo_option(k, o[k])
746 def scan_texi_preamble (chunks):
747 # this is not bulletproof..., it checks the first 10 chunks
748 for c in chunks[:10]:
750 for s in ('afourpaper', 'afourwide', 'letterpaper',
751 'afourlatex', 'smallbook'):
752 if string.find(c[1], "@%s" % s) != -1:
753 paperguru.m_papersize = s
755 def scan_preamble (chunks):
756 if __main__.format == 'texi':
757 scan_texi_preamble(chunks)
759 assert __main__.format == 'latex'
760 scan_latex_preamble(chunks)
763 def completize_preamble (chunks):
764 if __main__.format == 'texi':
766 pre_b = post_b = graphics_b = None
768 if chunk[0] == 'preamble-end':
770 if chunk[0] == 'input':
771 m = get_re('def-pre-re').search(chunk[1])
774 if chunk[0] == 'input':
775 m = get_re('def-post-re').search(chunk[1])
778 if chunk[0] == 'input':
779 m = get_re('usepackage-graphics').search(chunk[1])
783 while chunks[x][0] != 'preamble-end':
786 chunks.insert(x, ('input', get_output ('output-default-pre')))
788 chunks.insert(x, ('input', get_output ('output-default-post')))
790 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
795 def find_file (name):
797 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
802 for a in include_path:
804 nm = os.path.join (a, name)
806 __main__.read_files.append (nm)
811 sys.stderr.write ("Reading `%s'\n" % nm)
812 return (f.read (), nm)
814 error ("File not found `%s'\n" % name)
817 def do_ignore(match_object):
818 return [('ignore', match_object.group('code'))]
819 def do_preamble_end(match_object):
820 return [('preamble-end', match_object.group('code'))]
822 def make_verbatim(match_object):
823 return [('verbatim', match_object.group('code'))]
825 def make_verb(match_object):
826 return [('verb', match_object.group('code'))]
828 def do_include_file(m):
830 return [('input', get_output ('pagebreak'))] \
831 + read_doc_file(m.group('filename')) \
832 + [('input', get_output ('pagebreak'))]
834 def do_input_file(m):
835 return read_doc_file(m.group('filename'))
837 def make_lilypond(m):
838 if m.group('options'):
839 options = m.group('options')
842 return [('input', get_output('output-lilypond-fragment') %
843 (options, m.group('code')))]
845 def make_lilypond_file(m):
848 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
849 into a @lilypond .. @end lilypond block.
853 if m.group('options'):
854 options = m.group('options')
857 (content, nm) = find_file(m.group('filename'))
858 options = "filename=%s," % nm + options
860 return [('input', get_output('output-lilypond') %
863 def make_lilypond_block(m):
864 if m.group('options'):
865 options = get_re('option-sep').split (m.group('options'))
868 options = filter(lambda s: s != '', options)
869 return [('lilypond', m.group('code'), options)]
872 if __main__.format != 'latex':
874 if m.group('num') == 'one':
875 return [('numcols', m.group('code'), 1)]
876 if m.group('num') == 'two':
877 return [('numcols', m.group('code'), 2)]
879 def chop_chunks(chunks, re_name, func, use_match=0):
885 m = get_re (re_name).search (str)
887 newchunks.append (('input', str))
891 newchunks.append (('input', str[:m.start ('match')]))
893 newchunks.append (('input', str[:m.start (0)]))
894 #newchunks.extend(func(m))
895 # python 1.5 compatible:
896 newchunks = newchunks + func(m)
897 str = str [m.end(0):]
902 def determine_format (str):
903 if __main__.format == '':
905 latex = re.search ('\\\\document', str[:200])
906 texinfo = re.search ('@node|@setfilename', str[:200])
911 if texinfo and latex == None:
913 elif latex and texinfo == None:
916 error("error: can't determine format, please specify")
919 if __main__.paperguru == None:
920 if __main__.format == 'texi':
925 __main__.paperguru = g
928 def read_doc_file (filename):
929 """Read the input file, find verbatim chunks and do \input and \include
931 (str, path) = find_file(filename)
932 determine_format (str)
934 chunks = [('input', str)]
936 # we have to check for verbatim before doing include,
937 # because we don't want to include files that are mentioned
938 # inside a verbatim environment
939 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
940 chunks = chop_chunks(chunks, 'verb', make_verb)
941 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
943 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
944 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
948 taken_file_names = {}
949 def schedule_lilypond_block (chunk):
950 """Take the body and options from CHUNK, figure out how the
951 real .ly should look, and what should be left MAIN_STR (meant
952 for the main file). The .ly is written, and scheduled in
955 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
957 TODO has format [basename, extension, extension, ... ]
960 (type, body, opts) = chunk
961 assert type == 'lilypond'
962 file_body = compose_full_body (body, opts)
963 basename = 'lily-' + `abs(hash (file_body))`
965 m = re.search ('filename="(.*?)"', o)
967 basename = m.group (1)
968 if not taken_file_names.has_key(basename):
969 taken_file_names[basename] = 0
971 taken_file_names[basename] = taken_file_names[basename] + 1
972 basename = basename + "-%i" % taken_file_names[basename]
974 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
975 needed_filetypes = ['tex']
978 needed_filetypes.append('eps')
979 needed_filetypes.append('png')
980 if 'eps' in opts and not ('eps' in needed_filetypes):
981 needed_filetypes.append('eps')
982 pathbase = os.path.join (g_outdir, basename)
983 def f(base, ext1, ext2):
984 a = os.path.isfile(base + ext2)
985 if (os.path.isfile(base + ext1) and
986 os.path.isfile(base + ext2) and
987 os.stat(base+ext1)[stat.ST_MTIME] >
988 os.stat(base+ext2)[stat.ST_MTIME]) or \
989 not os.path.isfile(base + ext2):
992 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
994 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
996 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1000 if 'printfilename' in opts:
1002 m= re.match ("filename=(.*)", o)
1004 newbody = newbody + get_output ("output-filename") % m.group(1)
1008 if 'verbatim' in opts:
1009 newbody = output_verbatim (body)
1012 m = re.search ('intertext="(.*?)"', o)
1014 newbody = newbody + m.group (1) + "\n\n"
1016 if 'noinline' in opts:
1017 s = 'output-noinline'
1018 elif format == 'latex':
1023 else: # format == 'texi'
1025 newbody = newbody + get_output (s) % {'fn': basename }
1026 return ('lilypond', newbody, opts, todo, basename)
1028 def process_lilypond_blocks(outname, chunks):#ugh rename
1030 # Count sections/chapters.
1032 if c[0] == 'lilypond':
1033 c = schedule_lilypond_block (c)
1034 elif c[0] == 'numcols':
1035 paperguru.m_num_cols = c[2]
1036 newchunks.append (c)
1042 sys.stderr.write ("invoking `%s'\n" % cmd)
1043 st = os.system (cmd)
1045 error ('Error command exited with value %d\n' % st)
1049 def get_bbox (filename):
1050 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1052 box = open (filename + '.bbox').read()
1053 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1056 gr = map (string.atoi, m.groups ())
1060 def make_pixmap (name):
1061 bbox = get_bbox (name + '.eps')
1063 fo = open (name + '.trans.eps' , 'w')
1064 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1069 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1070 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1072 cmd = r"""gs -g%dx%d -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s"""
1074 cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1076 status = system (cmd)
1078 os.unlink (name + '.png')
1079 error ("Removing output file")
1081 def compile_all_files (chunks):
1088 if c[0] <> 'lilypond':
1097 if base + '.ly' not in tex:
1098 tex.append (base + '.ly')
1099 elif e == 'png' and g_do_pictures:
1105 # fixme: be sys-independent.
1107 if g_outdir and x[0] <> '/' :
1108 x = os.path.join (g_here_dir, x)
1111 incs = map (incl_opt, include_path)
1112 lilyopts = string.join (incs, ' ' )
1114 lilyopts = lilyopts + ' --dependencies '
1116 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1117 texfiles = string.join (tex, ' ')
1118 system ('lilypond --header=texidoc %s %s %s' % (lilyopts, g_extra_opts, texfiles))
1121 # Ugh, fixing up dependencies for .tex generation
1124 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1129 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1135 system(r"tex '\nonstopmode \input %s'" % e)
1136 system(r"dvips -E -o %s %s" % (e + '.eps', e))
1144 def update_file (body, name):
1146 write the body if it has changed
1157 f = open (name , 'w')
1164 def getopt_args (opts):
1165 "Construct arguments (LONG, SHORT) for getopt from list of options."
1170 short = short + o[1]
1178 return (short, long)
1180 def option_help_str (o):
1181 "Transform one option description (4-tuple ) into neatly formatted string"
1199 return ' ' + sh + sep + long + arg
1202 def options_help_str (opts):
1203 "Convert a list of options into a neatly formatted string"
1209 s = option_help_str (o)
1210 strs.append ((s, o[3]))
1216 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1220 sys.stdout.write("""Usage: lilypond-book [options] FILE\n
1221 Generate hybrid LaTeX input from Latex + lilypond
1224 sys.stdout.write (options_help_str (option_definitions))
1225 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
1229 Report bugs to bug-lilypond@gnu.org.
1231 Written by Tom Cato Amundsen <tca@gnu.org> and
1232 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1238 def write_deps (fn, target, chunks):
1240 sys.stdout.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1241 f = open (os.path.join(g_outdir, fn), 'w')
1242 f.write ('%s%s: ' % (g_dep_prefix, target))
1243 for d in read_files:
1247 if c[0] == 'lilypond':
1248 (type, body, opts, todo, basename) = c;
1249 basenames.append (basename)
1252 d=g_outdir + '/' + d
1254 #if not os.isfile (d): # thinko?
1255 if not re.search ('/', d):
1256 d = g_dep_prefix + d
1257 f.write ('%s.tex ' % d)
1259 #if len (basenames):
1260 # for d in basenames:
1261 # f.write ('%s.ly ' % d)
1262 # f.write (' : %s' % target)
1268 sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1270 def print_version ():
1272 sys.stdout.write (r"""Copyright 1998--1999
1273 Distributed under terms of the GNU General Public License. It comes with
1278 def check_texidoc (chunks):
1281 if c[0] == 'lilypond':
1282 (type, body, opts, todo, basename) = c;
1283 pathbase = os.path.join (g_outdir, basename)
1284 if os.path.isfile (pathbase + '.texidoc'):
1285 body = '\n@include %s.texidoc\n' % basename + body
1286 c = (type, body, opts, todo, basename)
1291 ## what's this? Docme --hwn
1293 def fix_epswidth (chunks):
1296 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1297 newchunks.append (c)
1302 m = re.match ('magnification=([0-9.]+)', o)
1304 mag = string.atof (m.group (1))
1306 def replace_eps_dim (match, lmag = mag):
1307 filename = match.group (1)
1308 dims = bounding_box_dimensions (filename)
1310 return '%fpt' % (dims[0] *lmag)
1312 body = re.sub (r"""\\lilypondepswidth{(.*?)}""", replace_eps_dim, c[1])
1313 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1319 def do_file(input_filename):
1323 my_outname = outname
1325 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
1326 my_depname = my_outname + '.dep'
1328 chunks = read_doc_file(input_filename)
1329 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1330 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1331 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1332 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1333 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1334 chunks = chop_chunks(chunks, 'numcols', do_columns)
1336 #for c in chunks: print "c:", c;
1338 scan_preamble(chunks)
1339 chunks = process_lilypond_blocks(my_outname, chunks)
1341 foutn = os.path.join (g_outdir, my_outname + '.' + format)
1344 if __main__.g_run_lilypond:
1345 compile_all_files (chunks)
1346 chunks = fix_epswidth (chunks)
1348 if __main__.format == 'texi':
1349 chunks = check_texidoc (chunks)
1352 chunks = completize_preamble (chunks)
1353 sys.stderr.write ("Writing `%s'\n" % foutn)
1354 fout = open (foutn, 'w')
1361 write_deps (my_depname, foutn, chunks)
1366 (sh, long) = getopt_args (__main__.option_definitions)
1367 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1368 except getopt.error, msg:
1369 sys.stderr.write("error: %s" % msg)
1377 if o == '--include' or o == '-I':
1378 include_path.append (a)
1379 elif o == '--version' or o == '-v':
1382 elif o == '--format' or o == '-f':
1384 elif o == '--outname' or o == '-o':
1387 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1390 elif o == '--help' or o == '-h':
1392 elif o == '--no-lily' or o == '-n':
1393 __main__.g_run_lilypond = 0
1394 elif o == '--dependencies' or o == '-M':
1396 elif o == '--default-music-fontsize':
1397 default_music_fontsize = string.atoi (a)
1398 elif o == '--default-lilypond-fontsize':
1399 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1400 default_music_fontsize = string.atoi (a)
1401 elif o == '--extra-options':
1403 elif o == '--force-music-fontsize':
1404 g_force_lilypond_fontsize = string.atoi(a)
1405 elif o == '--force-lilypond-fontsize':
1406 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1407 g_force_lilypond_fontsize = string.atoi(a)
1408 elif o == '--dep-prefix':
1410 elif o == '--no-pictures':
1412 elif o == '--read-lys':
1414 elif o == '--outdir':
1419 if os.path.isfile(g_outdir):
1420 error ("outdir is a file: %s" % g_outdir)
1421 if not os.path.exists(g_outdir):
1423 setup_environment ()
1424 for input_filename in files:
1425 do_file(input_filename)
1428 # Petr, ik zou willen dat ik iets zinvoller deed,
1429 # maar wat ik kan ik doen, het verandert toch niets?