JR patches.
[lilypond.git] / scripts / lilypond-book.py
blob38f4a51fd29f382ac45f2a982ef95be469b8d206
1 #!@PYTHON@
2 # vim: set noexpandtab:
3 # TODO:
4 # * junk --outdir for--output
5 # * Figure out clean set of options.
6 # *
7 # * texinfo: add support for @pagesize
9 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
10 # because the values are taken directly from texinfo.tex,
11 # geometry.sty and article.cls. Give me a hint, and I'll
12 # fix it.)
15 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
18 # This is was the idea for handling of comments:
19 # Multiline comments, @ignore .. @end ignore is scanned for
20 # in read_doc_file, and the chunks are marked as 'ignore', so
21 # lilypond-book will not touch them any more. The content of the
22 # chunks are written to the output file. Also 'include' and 'input'
23 # regex has to check if they are commented out.
25 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
26 # These three regex's has to check if they are on a commented line,
27 # % for latex, @c for texinfo.
29 # Then lines that are commented out with % (latex) and @c (Texinfo)
30 # are put into chunks marked 'ignore'. This cannot be done before
31 # searching for the lilypond-blocks because % is also the comment character
32 # for lilypond.
34 # The the rest of the rexeces are searched for. They don't have to test
35 # if they are on a commented out line.
38 import stat
39 import string
42 ################################################################
43 # Users of python modules should include this snippet
44 # and customize variables below.
46 # We'll suffer this path init stuff as long as we don't install our
47 # python packages in <prefix>/lib/pythonx.y (and don't kludge around
48 # it as we do with teTeX on Red Hat Linux: set some environment var
49 # (PYTHONPATH) in profile)
51 # If set, LILYPONDPREFIX must take prevalence
52 # if datadir is not set, we're doing a build and LILYPONDPREFIX
53 import getopt, os, sys
54 datadir = '@local_lilypond_datadir@'
55 if not os.path.isdir (datadir):
56 datadir = '@lilypond_datadir@'
57 if os.environ.has_key ('LILYPONDPREFIX') :
58 datadir = os.environ['LILYPONDPREFIX']
59 while datadir[-1] == os.sep:
60 datadir= datadir[:-1]
62 sys.path.insert (0, os.path.join (datadir, 'python'))
64 # Customize these
65 #if __name__ == '__main__':
67 import lilylib as ly
68 global _;_=ly._
69 global re;re = ly.re
71 # lilylib globals
72 program_name = 'lilypond-book'
73 verbose_p = 0
74 pseudo_filter_p = 0
75 original_dir = os.getcwd ()
76 #temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
77 #urg
78 temp_dir = '/tmp'
79 keep_temp_dir_p = 0
80 preview_resolution = 90
82 ## FIXME
83 ## ly2dvi: silly name?
84 ## do -P or -p by default?
85 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
86 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
87 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
88 'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
90 option_definitions = [
91 (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
92 (_ ("DIM"), '', 'default-music-fontsize', _ ("default fontsize for music. DIM is assumed to be in points")),
93 (_ ("DIM"), '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
94 (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
95 (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed be to in points")),
96 (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
97 ('', 'h', 'help', _ ("this help")),
98 (_ ("DIR"), 'I', 'include', _ ("include path")),
99 ('', 'M', 'dependencies', _ ("write dependencies")),
100 (_ ("PREF"), '', 'dep-prefix', _ ("prepend PREF before each -M dependency")),
101 ('', 'n', 'no-lily', _ ("don't run lilypond")),
102 ('', '', 'no-pictures', _ ("don't generate pictures")),
103 ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
104 ('', '', 'read-lys', _ ("don't write ly files.")),
105 (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
106 (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
107 (_ ('RES'), '', 'preview-resolution',
108 _ ("set the resolution of the preview to RES")),
109 ('', 'V', 'verbose', _ ("verbose")),
110 ('', 'v', 'version', _ ("print version information")),
111 ('', 'w', 'warranty', _ ("show warranty and copyright")),
114 # format specific strings, ie. regex-es for input, and % strings for output
116 # global variables
118 include_path = [os.getcwd ()]
121 lilypond_cmd = 'lilypond'
122 #lilypond_cmd = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
125 g_extra_opts = ''
126 g_here_dir = os.getcwd ()
127 g_dep_prefix = ''
128 g_outdir = ''
129 g_force_music_fontsize = 0
130 g_read_lys = 0
131 g_do_pictures = 1
132 g_do_music = 1
133 g_make_html = 0
135 format = ''
136 g_run_lilypond = 1
137 no_match = 'a\ba'
139 default_music_fontsize = 16
140 default_text_fontsize = 12
141 paperguru = None
144 class LatexPaper:
145 def __init__ (self):
146 self.m_document_preamble = []
147 self.m_num_cols = 1
148 self.m_multicols = 1
150 def find_latex_dims (self):
151 if g_outdir:
152 fname = os.path.join (g_outdir, "lily-tmp.tex")
153 else:
154 fname = "lily-tmp.tex"
155 try:
156 f = open (fname, "w")
157 except IOError:
158 error ("Error creating temporary file '%s'" % fname)
160 for s in self.m_document_preamble:
161 f.write (s)
162 f.write (r"""
163 \begin{document}
164 \typeout{---}
165 \typeout{\columnsep \the\columnsep}
166 \typeout{\textwidth \the\textwidth}
167 \typeout{---}
168 \end{document}
169 """)
170 f.close ()
171 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
173 cmd = "latex '\\nonstopmode \input %s'" % fname
174 # Ugh. (La)TeX writes progress and error messages on stdout
175 # Redirect to stderr
176 cmd += ' 1>/dev/stderr'
177 status = ly.system (cmd, ignore_error = 1)
178 signal = 0xf & status
179 exit_status = status >> 8
181 if status:
182 ly.error (_ ("LaTeX failed."))
183 ly.error (_ ("The error log is as follows:"))
185 #URG see ly2dvi
186 try:
187 lns = open ('lily-tmp.log').readlines ()
188 except:
189 lns = ''
190 countdown = -3
191 for ln in lns:
192 sys.stderr.write (ln)
193 if re.match ('^!', ln):
194 countdown = 3
196 if countdown == 0:
197 break
199 if countdown > 0:
200 countdown = countdown -1
202 sys.stderr.write (" ... (further messages elided)...\n")
203 sys.exit (1)
205 lns = open ('lily-tmp.log').readlines ()
206 for ln in lns:
207 ln = string.strip (ln)
208 m = re_dim.match (ln)
209 if m:
210 if m.groups ()[0] in ('textwidth', 'columnsep'):
211 self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
213 try:
214 os.remove (fname)
215 os.remove (os.path.splitext (fname)[0]+".aux")
216 os.remove (os.path.splitext (fname)[0]+".log")
217 except:
218 pass
220 if not self.__dict__.has_key ('m_textwidth'):
221 raise 'foo!'
223 def get_linewidth (self):
224 if self.m_num_cols == 1:
225 w = self.m_textwidth
226 else:
227 w = (self.m_textwidth - self.m_columnsep)/2
228 if self.m_multicols > 1:
229 return (w - self.m_columnsep* (self.m_multicols-1)) \
230 / self.m_multicols
231 return w
234 class HtmlPaper:
235 def __init__ (self):
236 self.m_papersize = 'letterpaper'
237 self.m_fontsize = 12
238 def get_linewidth (self):
239 return html_linewidths[self.m_papersize][self.m_fontsize]
241 class TexiPaper:
242 def __init__ (self):
243 self.m_papersize = 'letterpaper'
244 self.m_fontsize = 12
245 def get_linewidth (self):
246 return texi_linewidths[self.m_papersize][self.m_fontsize]
248 def mm2pt (x):
249 return x * 2.8452756
250 def in2pt (x):
251 return x * 72.26999
252 def em2pt (x, fontsize = 10):
253 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
254 def ex2pt (x, fontsize = 10):
255 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
257 def pt2pt (x):
258 return x
260 dimension_conversion_dict ={
261 'mm': mm2pt,
262 'cm': lambda x: mm2pt (10*x),
263 'in': in2pt,
264 'em': em2pt,
265 'ex': ex2pt,
266 'pt': pt2pt
269 # Convert numeric values, with or without specific dimension, to floats.
270 # Keep other strings
271 def conv_dimen_to_float (value):
272 if type (value) == type (""):
273 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
274 if m:
275 unit = m.group (2)
276 num = string.atof (m.group (1))
277 conv = dimension_conversion_dict[m.group (2)]
279 value = conv (num)
281 elif re.match ("^[0-9.]+$",value):
282 value = float (value)
284 return value
286 texi_linewidths = {
287 'afourpaper': {12: mm2pt (160)},
288 'afourwide': {12: in2pt (6.5)},
289 'afourlatex': {12: mm2pt (150)},
290 'smallbook': {12: in2pt (5)},
291 'letterpaper': {12: in2pt (6)}}
293 html_linewidths = {
294 'afourpaper': {12: mm2pt (160)},
295 'afourwide': {12: in2pt (6.5)},
296 'afourlatex': {12: mm2pt (150)},
297 'smallbook': {12: in2pt (5)},
298 'letterpaper': {12: in2pt (6)}}
300 output_dict= {
303 'html' : {
305 'output-lilypond': '''<lilypond%s>
307 </lilypond>''',
308 'output-filename' : r'''
309 <!-- %s >
310 <a href="%s">
311 <pre>%s</pre></a>:''',
312 'output-lilypond-fragment': '''<lilypond%s>
313 \context Staff\context Voice{ %s }
314 </lilypond>''',
315 'output-noinline': r'''
316 <!-- generated: %(fn)s.png !-->
317 ''',
318 ## maybe <hr> ?
319 'pagebreak': None,
320 # Verbatim text is always finished with \n. FIXME: For HTML,
321 # this newline should be removed.
322 'output-verbatim': r'''<pre>
323 %s</pre>''',
324 # Verbatim text is always finished with \n. FIXME: For HTML,
325 # this newline should be removed.
326 'output-small-verbatim': r'''<font size=-1><pre>
327 %s</pre></font>''',
328 ## Ugh we need to differentiate on origin:
329 ## lilypond-block origin wants an extra <p>, but
330 ## inline music doesn't.
331 ## possibly other center options?
332 'output-html': r'''
333 <a href="%(fn)s.png">
334 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
335 ''',
339 'latex': {
341 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
342 \context Staff <
343 \context Voice{
347 \end{lilypond}''',
348 'output-filename' : r'''\verb+%s+:\\
349 %% %s
350 %% %s
351 ''',
352 'output-lilypond': r'''\begin[%s]{lilypond}
354 \end{lilypond}
355 ''',
356 # verbatim text is always finished with \n
357 'output-verbatim': r'''\begin{verbatim}
358 %s\end{verbatim}
359 ''',
360 # verbatim text is always finished with \n
361 'output-small-verbatim': r'''{\small\begin{verbatim}
362 %s\end{verbatim}}
363 ''',
364 'output-default-post': "\\def\postLilypondExample{}\n",
365 'output-default-pre': "\\def\preLilypondExample{}\n",
366 'usepackage-graphics': '\\usepackage{graphics}\n',
367 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
368 'output-noinline': r'''
369 %% generated: %(fn)s.eps
370 ''',
371 'output-latex-quoted': r'''{\preLilypondExample
372 \input %(fn)s.tex
373 \postLilypondExample}''',
374 'output-latex-noquote': r'''{\parindent 0pt
375 \preLilypondExample
376 \input %(fn)s.tex
377 \postLilypondExample}''',
378 'pagebreak': r'\pagebreak',
382 'texi' : {
384 'output-lilypond': '''@lilypond[%s]
386 @end lilypond
387 ''',
388 'output-filename' : r'''@ifnothtml
389 @file{%s}:@*
390 @end ifnothtml
391 @ifhtml
392 @uref{%s,@file{%s}}
393 @end ifhtml
394 ''',
395 'output-lilypond-fragment': '''@lilypond[%s]
396 \context Staff\context Voice{ %s }
397 @end lilypond ''',
398 'output-noinline': r'''
399 @c generated: %(fn)s.png
400 ''',
401 'pagebreak': None,
402 # verbatim text is always finished with \n
403 'output-small-verbatim': r'''@smallexample
404 %s@end smallexample
405 ''',
406 # verbatim text is always finished with \n
407 'output-verbatim': r'''@example
408 %s@end example
409 ''',
410 # do some tweaking: @ is needed in some ps stuff.
412 # ugh, the <p> below breaks inline images...
413 'output-texi-noquote': r'''@tex
414 \catcode`\@=12
415 \parindent 0pt
416 \def\lilypondbook{}
417 \input %(fn)s.tex
418 \catcode`\@=0
419 @end tex
420 @html
421 <p><a href="%(fn)s.png">
422 <img border=0 src="%(fn)s.png" alt="[picture of music]">
423 </a><p>
424 @end html
425 ''',
426 'output-texi-quoted': r'''@quotation
427 @tex
428 \catcode`\@=12
429 \def\lilypondbook{}
430 \input %(fn)s.tex
431 \catcode`\@=0
432 @end tex
433 @html
434 <a href="%(fn)s.png">
435 <img border=0 src="%(fn)s.png" alt="[picture of music]">
436 </a>
437 @end html
438 @end quotation
439 ''',
444 def output_verbatim (body, small):
445 global format
446 if format == 'html':
447 body = re.sub ('&', '&amp;', body)
448 body = re.sub ('>', '&gt;', body)
449 body = re.sub ('<', '&lt;', body)
450 elif format == 'texi':
451 # clumsy workaround for python 2.2 pre bug.
452 body = re.sub ('@', '@@', body)
453 body = re.sub ('{', '@{', body)
454 body = re.sub ('}', '@}', body)
456 if small:
457 key = 'output-small-verbatim'
458 else:
459 key = 'output-verbatim'
460 return get_output (key) % body
463 # Warning: This uses extended regular expressions. Treat with care.
465 # legenda
467 # (?P<name>regex) -- assign result of REGEX to NAME
468 # *? -- match non-greedily.
469 # (?m) -- multiline regex: make ^ and $ match at each line
470 # (?s) -- make the dot match all characters including newline
471 re_dict = {
472 'html': {
473 'include': no_match,
474 'input': no_match,
475 'header': no_match,
476 'preamble-end': no_match,
477 'landscape': no_match,
478 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
479 'verb': r'''(?P<code><pre>.*?</pre>)''',
480 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
481 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
482 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
483 'option-sep' : '\s*',
484 'intertext': r',?\s*intertext=\".*?\"',
485 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
486 'singleline-comment': no_match,
487 'numcols': no_match,
488 'multicols': no_match,
491 'latex': {
492 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
493 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
494 'option-sep' : ',\s*',
495 'header': r"\n*\\documentclass\s*(\[.*?\])?",
496 'preamble-end': r'(?P<code>\\begin\s*{document})',
497 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
498 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
499 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
500 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
501 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
502 'def-post-re': r"\\def\\postLilypondExample",
503 'def-pre-re': r"\\def\\preLilypondExample",
504 'usepackage-graphics': r"\usepackage\s*{graphics}",
505 'intertext': r',?\s*intertext=\".*?\"',
506 'multiline-comment': no_match,
507 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
508 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
509 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
512 # why do we have distinction between @mbinclude and @include?
514 'texi': {
515 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
516 'input': no_match,
517 'header': no_match,
518 'preamble-end': no_match,
519 'landscape': no_match,
520 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
521 'verb': r'''(?P<code>@code{.*?})''',
522 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
523 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
524 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
525 'option-sep' : ',\s*',
526 'intertext': r',?\s*intertext=\".*?\"',
527 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
528 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
529 'numcols': no_match,
530 'multicols': no_match,
535 for r in re_dict.keys ():
536 olddict = re_dict[r]
537 newdict = {}
538 for k in olddict.keys ():
539 try:
540 newdict[k] = re.compile (olddict[k])
541 except:
542 print 'invalid regexp: %s' % olddict[k]
544 ## we'd like to catch and reraise a more
545 ## detailed error, but alas, the exceptions
546 ## changed across the 1.5/2.1 boundary.
548 raise "Invalid re"
549 re_dict[r] = newdict
552 def uniq (list):
553 list.sort ()
554 s = list
555 list = []
556 for x in s:
557 if x not in list:
558 list.append (x)
559 return list
562 def get_output (name):
563 return output_dict[format][name]
565 def get_re (name):
566 return re_dict[format][name]
568 def bounding_box_dimensions (fname):
569 if g_outdir:
570 fname = os.path.join (g_outdir, fname)
571 try:
572 fd = open (fname)
573 except IOError:
574 error ("Error opening `%s'" % fname)
575 str = fd.read ()
576 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
577 if s:
579 gs = map (lambda x: string.atoi (x), s.groups ())
580 return (int (gs[2] - gs[0] + 0.5),
581 int (gs[3] - gs[1] + 0.5))
582 else:
583 return (0,0)
585 def error (str):
586 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
587 raise 'Exiting.'
590 def compose_full_body (body, opts):
591 '''Construct the lilypond code to send to Lilypond.
592 Add stuff to BODY using OPTS as options.'''
593 music_size = default_music_fontsize
594 if g_force_music_fontsize:
595 music_size = g_force_music_fontsize
596 indent = ''
597 linewidth = ''
598 notime = ''
599 for o in opts:
600 if not g_force_music_fontsize:
601 m = re.match ('([0-9]+)pt', o)
602 if m:
603 music_size = string.atoi (m.group (1))
605 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
606 if m:
607 f = float (m.group (1))
608 indent = 'indent = %f\\%s' % (f, m.group (2))
610 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
611 if m:
612 f = float (m.group (1))
613 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
615 if re.search ('\\\\score', body):
616 is_fragment = 0
617 else:
618 is_fragment = 1
619 if 'fragment' in opts:
620 is_fragment = 1
621 if 'nofragment' in opts:
622 is_fragment = 0
624 if is_fragment and not 'multiline' in opts:
625 opts.append ('singleline')
627 if 'singleline' in opts:
628 if not linewidth:
629 linewidth = 'linewidth = -1.0'
630 if not indent:
631 indent = 'indent = 0.0\mm'
632 elif not linewidth:
633 global paperguru
634 l = paperguru.get_linewidth ()
635 linewidth = 'linewidth = %f\pt' % l
637 if 'noindent' in opts:
638 indent = 'indent = 0.0\mm'
640 if 'notime' in opts:
641 notime = r'''
642 \translator {
643 \StaffContext
644 \remove Time_signature_engraver
648 for o in opts:
649 m= re.search ('relative(.*)', o)
650 v = 0
651 if m:
652 try:
653 v = string.atoi (m.group (1))
654 except ValueError:
655 pass
657 v = v + 1
658 pitch = 'c'
659 if v < 0:
660 pitch = pitch + '\,' * v
661 elif v > 0:
662 pitch = pitch + '\'' * v
664 body = '\\relative %s { %s }' % (pitch, body)
666 if is_fragment:
667 body = r'''
668 \score {
669 \notes {
673 ''' % body
675 opts = uniq (opts)
676 optstring = string.join (opts, ' ')
677 optstring = re.sub ('\n', ' ', optstring)
678 body = r'''
679 %% Generated automatically by: lilypond-book.py
680 %% options are %s
681 \include "paper%d.ly"
682 \paper {
687 ''' % (optstring, music_size, linewidth, indent, notime) + body
689 # ughUGH not original options
690 return body
692 def scan_html_preamble (chunks):
693 return
695 def scan_latex_preamble (chunks):
696 # First we want to scan the \documentclass line
697 # it should be the first non-comment line.
698 # The only thing we really need to know about the \documentclass line
699 # is if there are one or two columns to begin with.
700 idx = 0
701 while 1:
702 if chunks[idx][0] == 'ignore':
703 idx = idx + 1
704 continue
705 m = get_re ('header').match (chunks[idx][1])
706 if not m:
707 error ("Latex documents must start with a \documentclass command")
708 if m.group (1):
709 options = re.split (',[\n \t]*', m.group (1)[1:-1])
710 else:
711 options = []
712 if 'twocolumn' in options:
713 paperguru.m_num_cols = 2
714 break
717 # Then we add everything before \begin{document} to
718 # paperguru.m_document_preamble so that we can later write this header
719 # to a temporary file in find_latex_dims() to find textwidth.
720 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
721 if chunks[idx] == 'ignore':
722 idx = idx + 1
723 continue
724 paperguru.m_document_preamble.append (chunks[idx][1])
725 idx = idx + 1
727 if len (chunks) == idx:
728 error ("Didn't find end of preamble (\\begin{document})")
730 paperguru.find_latex_dims ()
732 def scan_texi_preamble (chunks):
733 # this is not bulletproof..., it checks the first 10 chunks
734 for c in chunks[:10]:
735 if c[0] == 'input':
736 for s in ('afourpaper', 'afourwide', 'letterpaper',
737 'afourlatex', 'smallbook'):
738 if string.find (c[1], "@%s" % s) != -1:
739 paperguru.m_papersize = s
742 def scan_preamble (chunks):
743 global format
744 if format == 'html':
745 scan_html_preamble (chunks)
746 elif format == 'latex':
747 scan_latex_preamble (chunks)
748 elif format == 'texi':
749 scan_texi_preamble (chunks)
752 def completize_preamble (chunks):
753 global format
754 if format != 'latex':
755 return chunks
756 pre_b = post_b = graphics_b = None
757 for chunk in chunks:
758 if chunk[0] == 'preamble-end':
759 break
760 if chunk[0] == 'input':
761 m = get_re ('def-pre-re').search (chunk[1])
762 if m:
763 pre_b = 1
764 if chunk[0] == 'input':
765 m = get_re ('def-post-re').search (chunk[1])
766 if m:
767 post_b = 1
769 if chunk[0] == 'input':
770 m = get_re ('usepackage-graphics').search (chunk[1])
771 if m:
772 graphics_b = 1
773 x = 0
774 while x < len (chunks) and chunks[x][0] != 'preamble-end':
775 x = x + 1
777 if x == len (chunks):
778 return chunks
780 if not pre_b:
781 chunks.insert (x, ('input', get_output ('output-default-pre')))
782 if not post_b:
783 chunks.insert (x, ('input', get_output ('output-default-post')))
784 if not graphics_b:
785 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
787 return chunks
790 read_files = []
791 def find_file (name):
793 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
796 if name == '-':
797 return (sys.stdin.read (), '<stdin>')
798 f = None
799 nm = ''
800 for a in include_path:
801 try:
802 nm = os.path.join (a, name)
803 f = open (nm)
804 global read_files
805 read_files.append (nm)
806 break
807 except IOError:
808 pass
809 if f:
810 sys.stderr.write ("Reading `%s'\n" % nm)
811 return (f.read (), nm)
812 else:
813 error ("File not found `%s'\n" % name)
814 return ('', '')
816 def do_ignore (match_object):
817 return [('ignore', match_object.group ('code'))]
818 def do_preamble_end (match_object):
819 return [('preamble-end', match_object.group ('code'))]
821 def make_verbatim (match_object):
822 return [('verbatim', match_object.group ('code'))]
824 def make_verb (match_object):
825 return [('verb', match_object.group ('code'))]
827 def do_include_file (m):
828 "m: MatchObject"
829 return [('input', get_output ('pagebreak'))] \
830 + read_doc_file (m.group ('filename')) \
831 + [('input', get_output ('pagebreak'))]
833 def do_input_file (m):
834 return read_doc_file (m.group ('filename'))
836 def make_lilypond (m):
837 if m.group ('options'):
838 options = m.group ('options')
839 else:
840 options = ''
841 return [('input', get_output ('output-lilypond-fragment') %
842 (options, m.group ('code')))]
844 def make_lilypond_file (m):
847 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
848 into a @lilypond .. @end lilypond block.
852 if m.group ('options'):
853 options = m.group ('options')
854 else:
855 options = ''
856 (content, nm) = find_file (m.group ('filename'))
857 options = "filename=%s," % nm + options
859 return [('input', get_output ('output-lilypond') %
860 (options, content))]
862 def make_lilypond_block (m):
863 if not g_do_music:
864 return []
866 if m.group ('options'):
867 options = get_re ('option-sep').split (m.group ('options'))
868 else:
869 options = []
870 options = filter (lambda s: s != '', options)
871 return [('lilypond', m.group ('code'), options)]
873 def do_columns (m):
874 global format
875 if format != 'latex':
876 return []
877 if m.group ('num') == 'one':
878 return [('numcols', m.group ('code'), 1)]
879 if m.group ('num') == 'two':
880 return [('numcols', m.group ('code'), 2)]
882 def do_multicols (m):
883 global format
884 if format != 'latex':
885 return []
886 if m.group ('be') == 'begin':
887 return [('multicols', m.group ('code'), int (m.group ('num')))]
888 else:
889 return [('multicols', m.group ('code'), 1)]
890 return []
892 def chop_chunks (chunks, re_name, func, use_match=0):
893 newchunks = []
894 for c in chunks:
895 if c[0] == 'input':
896 str = c[1]
897 while str:
898 m = get_re (re_name).search (str)
899 if m == None:
900 newchunks.append (('input', str))
901 str = ''
902 else:
903 if use_match:
904 newchunks.append (('input', str[:m.start ('match')]))
905 else:
906 newchunks.append (('input', str[:m.start (0)]))
907 #newchunks.extend (func (m))
908 # python 1.5 compatible:
909 newchunks = newchunks + func (m)
910 str = str [m.end (0):]
911 else:
912 newchunks.append (c)
913 return newchunks
915 def determine_format (str):
916 global format
917 if format == '':
918 html = re.search ('(?i)<[dh]tml', str[:200])
919 latex = re.search (r'''\\document''', str[:200])
920 texi = re.search ('@node|@setfilename', str[:200])
922 f = ''
923 g = None
925 if html and not latex and not texi:
926 f = 'html'
927 elif latex and not html and not texi:
928 f = 'latex'
929 elif texi and not html and not latex:
930 f = 'texi'
931 else:
932 error ("can't determine format, please specify")
933 format = f
935 global paperguru
936 if paperguru == None:
937 if format == 'html':
938 g = HtmlPaper ()
939 elif format == 'latex':
940 g = LatexPaper ()
941 elif format == 'texi':
942 g = TexiPaper ()
944 paperguru = g
947 def read_doc_file (filename):
948 '''Read the input file, find verbatim chunks and do \input and \include
950 (str, path) = find_file (filename)
951 determine_format (str)
953 chunks = [('input', str)]
955 # we have to check for verbatim before doing include,
956 # because we don't want to include files that are mentioned
957 # inside a verbatim environment
958 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
960 chunks = chop_chunks (chunks, 'verb', make_verb)
961 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
962 #ugh fix input
963 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
964 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
965 return chunks
968 taken_file_names = {}
969 def schedule_lilypond_block (chunk):
970 '''Take the body and options from CHUNK, figure out how the
971 real .ly should look, and what should be left MAIN_STR (meant
972 for the main file). The .ly is written, and scheduled in
973 TODO.
975 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
977 TODO has format [basename, extension, extension, ... ]
979 (type, body, opts) = chunk
980 assert type == 'lilypond'
981 file_body = compose_full_body (body, opts)
982 ## Hmm, we should hash only lilypond source, and skip the
983 ## %options are ...
984 ## comment line
985 basename = 'lily-' + `abs (hash (file_body))`
986 for o in opts:
987 m = re.search ('filename="(.*?)"', o)
988 if m:
989 basename = m.group (1)
990 if not taken_file_names.has_key (basename):
991 taken_file_names[basename] = 0
992 else:
993 taken_file_names[basename] = taken_file_names[basename] + 1
994 basename = basename + "-%i" % taken_file_names[basename]
995 if not g_read_lys:
996 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
997 needed_filetypes = ['tex']
999 if format == 'html' or g_make_html:
1000 needed_filetypes.append ('eps')
1001 needed_filetypes.append ('png')
1002 if 'eps' in opts and not ('eps' in needed_filetypes):
1003 needed_filetypes.append ('eps')
1004 pathbase = os.path.join (g_outdir, basename)
1005 def f (base, ext1, ext2):
1006 a = os.path.isfile (base + ext2)
1007 if (os.path.isfile (base + ext1) and
1008 os.path.isfile (base + ext2) and
1009 os.stat (base+ext1)[stat.ST_MTIME] >
1010 os.stat (base+ext2)[stat.ST_MTIME]) or \
1011 not os.path.isfile (base + ext2):
1012 return 1
1013 todo = []
1014 if 'tex' in needed_filetypes and f (pathbase, '.ly', '.tex'):
1015 todo.append ('tex')
1016 if 'eps' in needed_filetypes and f (pathbase, '.tex', '.eps'):
1017 todo.append ('eps')
1018 if 'png' in needed_filetypes and f (pathbase, '.eps', '.png'):
1019 todo.append ('png')
1020 newbody = ''
1022 if 'printfilename' in opts:
1023 for o in opts:
1024 m= re.match ("filename=(.*)", o)
1025 if m:
1026 newbody = newbody + get_output ("output-filename") % (m.group (1), basename + '.ly', m.group (1))
1027 break
1030 if 'smallverbatim' in opts:
1031 newbody = newbody + output_verbatim (body, 1)
1032 elif 'verbatim' in opts:
1033 newbody = newbody + output_verbatim (body, 0)
1035 for o in opts:
1036 m = re.search ('intertext="(.*?)"', o)
1037 if m:
1038 newbody = newbody + "\n"
1039 if format == 'texi':
1040 newbody = newbody + "@noindent\n"
1041 elif format == 'latex':
1042 newbody = newbody + "\\noindent\n"
1043 newbody = newbody + m.group (1) + "\n"
1045 if 'noinline' in opts:
1046 s = 'output-noinline'
1047 elif format == 'latex':
1048 if 'eps' in opts:
1049 s = 'output-eps'
1050 else:
1051 if 'noquote' in opts:
1052 s = 'output-latex-noquote'
1053 else:
1054 s = 'output-latex-quoted'
1055 elif format == 'texi':
1056 if 'noquote' in opts:
1057 s = 'output-texi-noquote'
1058 else:
1059 s = 'output-texi-quoted'
1060 else: # format == 'html'
1061 s = 'output-html'
1062 newbody = newbody + get_output (s) % {'fn': basename }
1063 return ('lilypond', newbody, opts, todo, basename)
1065 def process_lilypond_blocks (chunks):#ugh rename
1066 newchunks = []
1067 # Count sections/chapters.
1068 for c in chunks:
1069 if c[0] == 'lilypond':
1070 c = schedule_lilypond_block (c)
1071 elif c[0] == 'numcols':
1072 paperguru.m_num_cols = c[2]
1073 elif c[0] == 'multicols':
1074 paperguru.m_multicols = c[2]
1075 newchunks.append (c)
1076 return newchunks
1078 def compile_all_files (chunks):
1079 global foutn
1080 eps = []
1081 tex = []
1082 png = []
1084 for c in chunks:
1085 if c[0] != 'lilypond':
1086 continue
1087 base = c[4]
1088 exts = c[3]
1089 for e in exts:
1090 if e == 'eps':
1091 eps.append (base)
1092 elif e == 'tex':
1093 #ugh
1094 if base + '.ly' not in tex:
1095 tex.append (base + '.ly')
1096 elif e == 'png' and g_do_pictures:
1097 png.append (base)
1098 d = os.getcwd ()
1099 if g_outdir:
1100 os.chdir (g_outdir)
1101 if tex:
1102 # fixme: be sys-independent.
1103 def incl_opt (x):
1104 if g_outdir and x[0] != '/' :
1105 x = os.path.join (g_here_dir, x)
1106 return ' -I %s' % x
1108 incs = map (incl_opt, include_path)
1109 lilyopts = string.join (incs)
1110 if do_deps:
1111 lilyopts += ' --dependencies'
1112 if g_outdir:
1113 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1114 lilyopts += ' --header=texidoc'
1115 texfiles = string.join (tex)
1116 cmd = string.join ((lilypond_cmd, lilyopts, g_extra_opts,
1117 texfiles))
1118 ly.system (cmd, ignore_error = 0, progress_p = 1)
1121 # Ugh, fixing up dependencies for .tex generation
1123 if do_deps:
1124 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1125 x), tex)
1127 for i in depfiles:
1128 f =open (i)
1129 text=f.read ()
1130 f.close ()
1131 text=re.sub ('\n([^:\n]*):',
1132 '\n' + foutn + ':', text)
1133 f = open (i, 'w')
1134 f.write (text)
1135 f.close ()
1137 def to_eps (file):
1138 cmd = r"latex '\nonstopmode \input %s'" % file
1139 # Ugh. (La)TeX writes progress and error messages on stdout
1140 # Redirect to stderr
1141 cmd += ' 1>/dev/stderr'
1142 ly.system (cmd)
1143 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1144 map (to_eps, eps)
1146 map (ly.make_preview, png)
1147 os.chdir (d)
1150 def update_file (body, name):
1152 write the body if it has changed
1154 same = 0
1155 try:
1156 f = open (name)
1157 fs = f.read (-1)
1158 same = (fs == body)
1159 except:
1160 pass
1162 if not same:
1163 f = open (name , 'w')
1164 f.write (body)
1165 f.close ()
1167 return not same
1170 def write_deps (fn, target, chunks):
1171 global read_files
1172 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1173 f = open (os.path.join (g_outdir, fn), 'w')
1174 f.write ('%s%s: ' % (g_dep_prefix, target))
1175 for d in read_files:
1176 f.write ('%s ' % d)
1177 basenames=[]
1178 for c in chunks:
1179 if c[0] == 'lilypond':
1180 (type, body, opts, todo, basename) = c;
1181 basenames.append (basename)
1182 for d in basenames:
1183 if g_outdir:
1184 d=g_outdir + '/' + d
1185 if g_dep_prefix:
1186 #if not os.isfile (d): # thinko?
1187 if not re.search ('/', d):
1188 d = g_dep_prefix + d
1189 f.write ('%s.tex ' % d)
1190 f.write ('\n')
1191 #if len (basenames):
1192 # for d in basenames:
1193 # f.write ('%s.ly ' % d)
1194 # f.write (' : %s' % target)
1195 f.write ('\n')
1196 f.close ()
1197 read_files = []
1199 def check_texidoc (chunks):
1200 n = []
1201 for c in chunks:
1202 if c[0] == 'lilypond':
1203 (type, body, opts, todo, basename) = c;
1204 pathbase = os.path.join (g_outdir, basename)
1205 if os.path.isfile (pathbase + '.texidoc'):
1206 body = '\n@include %s.texidoc\n' % basename + body
1207 c = (type, body, opts, todo, basename)
1208 n.append (c)
1209 return n
1212 ## what's this? Docme --hwn
1214 def fix_epswidth (chunks):
1215 newchunks = []
1216 for c in chunks:
1217 if c[0] != 'lilypond' or 'eps' not in c[2]:
1218 newchunks.append (c)
1219 continue
1221 mag = 1.0
1222 for o in c[2]:
1223 m = re.match ('magnification=([0-9.]+)', o)
1224 if m:
1225 mag = string.atof (m.group (1))
1227 def replace_eps_dim (match, lmag = mag):
1228 filename = match.group (1)
1229 dims = bounding_box_dimensions (filename)
1231 return '%fpt' % (dims[0] *lmag)
1233 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1234 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1236 return newchunks
1239 ##docme: why global?
1240 foutn=""
1242 def do_file (input_filename):
1243 chunks = read_doc_file (input_filename)
1244 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1245 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1246 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1247 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1248 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1249 chunks = chop_chunks (chunks, 'numcols', do_columns)
1250 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1251 #print "-" * 50
1252 #for c in chunks: print "c:", c;
1253 #sys.exit ()
1254 scan_preamble (chunks)
1255 chunks = process_lilypond_blocks (chunks)
1257 # Do It.
1258 global g_run_lilypond
1259 if g_run_lilypond:
1260 compile_all_files (chunks)
1261 chunks = fix_epswidth (chunks)
1263 global format
1264 if format == 'texi':
1265 chunks = check_texidoc (chunks)
1267 x = 0
1268 chunks = completize_preamble (chunks)
1270 global foutn
1272 if outname:
1273 my_outname = outname
1274 elif input_filename == '-' or input_filename == "/dev/stdin":
1275 my_outname = '-'
1276 else:
1277 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1278 my_depname = my_outname + '.dep'
1280 if my_outname == '-' or my_outname == '/dev/stdout':
1281 fout = sys.stdout
1282 foutn = "<stdout>"
1283 global do_deps
1284 do_deps = 0
1285 else:
1286 foutn = os.path.join (g_outdir, my_outname)
1287 sys.stderr.write ("Writing `%s'\n" % foutn)
1288 fout = open (foutn, 'w')
1289 for c in chunks:
1290 fout.write (c[1])
1291 fout.close ()
1292 # should chmod -w
1294 if do_deps:
1295 write_deps (my_depname, foutn, chunks)
1297 outname = ''
1298 try:
1299 (sh, long) = ly.getopt_args (option_definitions)
1300 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1302 except getopt.error, msg:
1303 sys.stderr.write ('\n')
1304 ly.error (_ ("getopt says: `%s\'" % s))
1305 sys.stderr.write ('\n')
1306 ly.help ()
1307 ly.exit (2)
1309 do_deps = 0
1310 for opt in options:
1311 o = opt[0]
1312 a = opt[1]
1314 if o == '--include' or o == '-I':
1315 include_path.append (a)
1316 elif o == '--version' or o == '-v':
1317 ly.identify (sys.stdout)
1318 sys.exit (0)
1319 elif o == '--verbose' or o == '-V':
1320 verbose_p = 1
1321 elif o == '--format' or o == '-f':
1322 format = a
1323 if a == 'texi-html':
1324 format = 'texi'
1325 g_make_html = 1
1326 elif o == '--outname' or o == '-o':
1327 if len (files) > 1:
1328 #HACK
1329 sys.stderr.write ("Lilypond-book is confused by --outname on multiple files")
1330 sys.exit (1)
1331 outname = a
1332 elif o == '--help' or o == '-h':
1333 ly.help ()
1334 sys.exit (0)
1335 elif o == '--no-lily' or o == '-n':
1336 g_run_lilypond = 0
1337 elif o == '--preview-resolution':
1338 preview_resolution = string.atoi (a)
1339 elif o == '--dependencies' or o == '-M':
1340 do_deps = 1
1341 elif o == '--default-music-fontsize':
1342 default_music_fontsize = string.atoi (a)
1343 elif o == '--default-lilypond-fontsize':
1344 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1345 default_music_fontsize = string.atoi (a)
1346 elif o == '--extra-options':
1347 g_extra_opts = a
1348 elif o == '--force-music-fontsize':
1349 g_force_music_fontsize = string.atoi (a)
1350 elif o == '--force-lilypond-fontsize':
1351 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1352 g_force_music_fontsize = string.atoi (a)
1353 elif o == '--dep-prefix':
1354 g_dep_prefix = a
1355 elif o == '--no-pictures':
1356 g_do_pictures = 0
1357 elif o == '--no-music':
1358 g_do_music = 0
1359 elif o == '--read-lys':
1360 g_read_lys = 1
1361 elif o == '--outdir':
1362 g_outdir = a
1363 elif o == '--warranty' or o == '-w':
1364 #status = os.system ('lilypond -w')
1365 if 1 or status:
1366 ly.warranty ()
1367 sys.exit (0)
1369 ly.identify (sys.stderr)
1371 if g_outdir:
1372 if os.path.isfile (g_outdir):
1373 error ("outdir is a file: %s" % g_outdir)
1374 if not os.path.exists (g_outdir):
1375 os.mkdir (g_outdir)
1377 if not files:
1378 ly.help ()
1379 ly.error (_ ("no files specified on command line"))
1380 ly.exit (2)
1382 ly.setup_environment ()
1384 for input_filename in files:
1385 do_file (input_filename)
1389 # Petr, ik zou willen dat ik iets zinvoller deed,
1390 # maar wat ik kan ik doen, het verandert toch niets?
1391 # --hwn 20/aug/99