]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
* input/regression/markup-note.ly: new file
[lilypond.git] / scripts / lilypond-book.py
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
8
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.)
13
14 #
15 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
16 #
17
18
19 #
20 # This is a slightly hairy program. The general approach is as follows 
21 # The input string is chopped up in chunks, i.e. ,  a list of tuples
22 #
23 #   with the format  (TAG_STR, MAIN_STR, OPTIONS, TODO, BASE)
24 #
25 # This list is build step by step: first ignore and verbatim commands are handled,
26 # delivering a list of chunks.
27
28 # then all chunks containing lilypnod commands are chopped up
29 #
30 # when all chunks have their final form, all bodies from lilypond blocks are 
31 # extracted, and if applicable, written do disk and run through lilypond.
32
33
34
35 # This is was the idea for handling of comments:
36
37
38 #       Multiline comments, @ignore .. @end ignore is scanned for
39 #       in read_doc_file, and the chunks are marked as 'ignore', so
40 #       lilypond-book will not touch them any more. The content of the
41 #       chunks are written to the output file. Also 'include' and 'input'
42 #       regex has to check if they are commented out.
43 #
44
45 #       Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
46 #       These three regex's has to check if they are on a commented line,
47 #       % for latex, @c for texinfo.
48 #
49 #       Then lines that are commented out with % (latex) and @c (Texinfo)
50 #       are put into chunks marked 'ignore'. This cannot be done before
51 #       searching for the lilypond-blocks because % is also the comment character
52 #       for lilypond.
53 #
54 #       The the rest of the rexeces are searched for. They don't have to test
55 #       if they are on a commented out line.
56
57 import glob
58 import stat
59 import string
60
61
62 ################################################################
63 # Users of python modules should include this snippet
64 # and customize variables below.
65
66 # We'll suffer this path init stuff as long as we don't install our
67 # python packages in <prefix>/lib/pythonx.y (and don't kludge around
68 # it as we do with teTeX on Red Hat Linux: set some environment var
69 # (PYTHONPATH) in profile)
70
71 # If set, LILYPONDPREFIX must take prevalence
72 # if datadir is not set, we're doing a build and LILYPONDPREFIX
73 import getopt, os, sys
74 datadir = '@local_lilypond_datadir@'
75 if not os.path.isdir (datadir):
76         datadir = '@lilypond_datadir@'
77 if os.environ.has_key ('LILYPONDPREFIX') :
78         datadir = os.environ['LILYPONDPREFIX']
79         while datadir[-1] == os.sep:
80                 datadir= datadir[:-1]
81
82 sys.path.insert (0, os.path.join (datadir, 'python'))
83
84 # Customize these
85 #if __name__ == '__main__':
86
87 import lilylib as ly
88 global _;_=ly._
89 global re;re = ly.re
90
91 # lilylib globals
92 program_name = 'lilypond-book'
93 verbose_p = 0
94 pseudo_filter_p = 0
95 original_dir = os.getcwd ()
96 #temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
97 #urg
98 temp_dir = '/tmp'
99 keep_temp_dir_p = 0
100 preview_resolution = 90
101
102 ## FIXME
103 ## ly2dvi: silly name?
104 ## do -P or -p by default?
105 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
106 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
107 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
108              'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
109
110 option_definitions = [
111         (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
112         (_ ("DIM"),  '', 'default-music-fontsize', _ ("default fontsize for music.  DIM is assumed to be in points")),
113         (_ ("DIM"),  '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
114         (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
115         (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed be to in points")),
116         (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
117         ('', 'h', 'help', _ ("this help")),
118         (_ ("DIR"), 'I', 'include', _ ("include path")),
119         ('', 'M', 'dependencies', _ ("write dependencies")),
120         (_ ("PREF"), '',  'dep-prefix', _ ("prepend PREF before each -M dependency")),
121         ('', 'n', 'no-lily', _ ("don't run lilypond")),
122         ('', '', 'no-pictures', _ ("don't generate pictures")),
123         ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
124         (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
125         (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
126         (_ ('RES'), '', 'preview-resolution',
127          _ ("set the resolution of the preview to RES")),
128         ('', 'V', 'verbose', _ ("verbose")),
129         ('', 'v', 'version', _ ("print version information")),
130         ('', 'w', 'warranty', _ ("show warranty and copyright")),
131         ]
132
133 # format specific strings, ie. regex-es for input, and % strings for output
134
135 # global variables
136
137 include_path = [os.getcwd ()]
138
139
140 #lilypond_binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp  --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
141
142 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
143
144 # only use installed binary  when we're installed too.
145 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
146         lilypond_binary = 'lilypond'
147
148
149
150 ly2dvi_binary = os.path.join ('@bindir@', 'ly2dvi')
151
152 # only use installed binary  when we're installed too.
153 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
154         ly2dvi_binary = 'ly2dvi'
155
156
157
158 g_extra_opts = ''
159 g_here_dir = os.getcwd ()
160 g_dep_prefix = ''
161 g_outdir = ''
162 g_force_music_fontsize = 0
163 g_do_pictures = 1
164 g_do_music = 1
165 g_make_html = 0
166
167 format = ''
168 g_run_lilypond = 1
169 no_match = 'a\ba'
170
171 default_music_fontsize = 16
172 default_text_fontsize = 12
173 paperguru = None
174
175 ################################################################
176 # Dimension handling for LaTeX.
177
178 class LatexPaper:
179         def __init__ (self):
180                 self.m_document_preamble = []
181                 self.m_num_cols = 1
182                 self.m_multicols = 1
183
184         def find_latex_dims (self):
185                 if g_outdir:
186                         fname = os.path.join (g_outdir, "lily-tmp.tex")
187                 else:
188                         fname = "lily-tmp.tex"
189                 try:
190                         f = open (fname, "w")
191                 except IOError:
192                         error ("Error creating temporary file '%s'" % fname)
193
194                 for s in self.m_document_preamble:
195                         f.write (s)
196                 f.write (r"""
197 \begin{document}
198 \typeout{---}
199 \typeout{\columnsep \the\columnsep}
200 \typeout{\textwidth \the\textwidth}
201 \typeout{---}
202 \end{document}
203                 """)
204                 f.close ()
205                 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
206
207                 cmd = "latex '\\nonstopmode \input %s'" % fname
208                 # Ugh.  (La)TeX writes progress and error messages on stdout
209                 # Redirect to stderr
210                 cmd += ' 1>/dev/stderr'
211                 status = ly.system (cmd, ignore_error = 1)
212                 signal = 0xf & status
213                 exit_status = status >> 8
214                 
215                 if status:
216                         ly.error (_ ("LaTeX failed."))
217                         ly.error (_ ("The error log is as follows:"))
218                         
219                         #URG see ly2dvi
220                         try:
221                                 lns = open ('lily-tmp.log').readlines ()
222                         except:
223                                 lns = ''
224                         countdown = -3
225                         for ln in lns:
226                                 sys.stderr.write (ln)
227                                 if re.match ('^!', ln):
228                                         countdown = 3
229
230                                 if countdown == 0:
231                                         break
232
233                                 if countdown > 0:
234                                         countdown = countdown -1
235
236                         sys.stderr.write ("  ... (further messages elided)...\n")
237                         sys.exit (1)
238
239                 lns = open ('lily-tmp.log').readlines ()
240                 for ln in lns:
241                         ln = string.strip (ln)
242                         m = re_dim.match (ln)
243                         if m:
244                                 if m.groups ()[0] in ('textwidth', 'columnsep'):
245                                         self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
246
247                 try:
248                         os.remove (fname)
249                         os.remove (os.path.splitext (fname)[0]+".aux")
250                         os.remove (os.path.splitext (fname)[0]+".log")
251                 except:
252                         pass
253
254                 if not self.__dict__.has_key ('m_textwidth'):
255                         raise 'foo!'
256
257         def get_linewidth (self):
258                 if self.m_num_cols == 1:
259                         w = self.m_textwidth
260                 else:
261                         w = (self.m_textwidth - self.m_columnsep)/2
262                 if self.m_multicols > 1:
263                         return (w - self.m_columnsep* (self.m_multicols-1)) \
264                            / self.m_multicols
265                 return w
266
267
268 class HtmlPaper:
269         def __init__ (self):
270                 self.m_papersize = 'letterpaper'
271                 self.m_fontsize = 12
272         def get_linewidth (self):
273                 return html_linewidths[self.m_papersize][self.m_fontsize]
274
275 class TexiPaper:
276         def __init__ (self):
277                 self.m_papersize = 'letterpaper'
278                 self.m_fontsize = 12
279         def get_linewidth (self):
280                 return texi_linewidths[self.m_papersize][self.m_fontsize]
281
282 def mm2pt (x):
283         return x * 2.8452756
284 def in2pt (x):
285         return x * 72.26999
286 def em2pt (x, fontsize = 10):
287         return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
288 def ex2pt (x, fontsize = 10):
289         return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
290
291 def pt2pt (x):
292         return x
293
294 dimension_conversion_dict ={
295         'mm': mm2pt,
296         'cm': lambda x: mm2pt (10*x),
297         'in': in2pt,
298         'em': em2pt,
299         'ex': ex2pt,
300         'pt': pt2pt
301         }
302
303 # Convert numeric values, with or without specific dimension, to floats.
304 # Keep other strings
305 def conv_dimen_to_float (value):
306         if type (value) == type (""):
307                 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
308                 if m:
309                         unit = m.group (2)
310                         num = string.atof (m.group (1))
311                         conv =  dimension_conversion_dict[m.group (2)]
312
313                         value = conv (num)
314
315                 elif re.match ("^[0-9.]+$",value):
316                         value = float (value)
317
318         return value
319
320 texi_linewidths = {
321         'afourpaper': {12: mm2pt (160)},
322         'afourwide': {12: in2pt (6.5)},
323         'afourlatex': {12: mm2pt (150)},
324         'smallbook': {12: in2pt (5)},
325         'letterpaper': {12: in2pt (6)}}
326
327 html_linewidths = {
328         'afourpaper': {12: mm2pt (160)},
329         'afourwide': {12: in2pt (6.5)},
330         'afourlatex': {12: mm2pt (150)},
331         'smallbook': {12: in2pt (5)},
332         'letterpaper': {12: in2pt (6)}}
333
334
335 ################################################################
336 # How to output various structures. 
337 output_dict= {
338
339
340         'html' : {
341
342                 'output-filename' : r'''
343 <!-- %s >
344 <a href="%s">
345 <pre>%s</pre></a>:''',
346                 'output-lilypond-fragment': '''<lilypond%s>
347 \context Staff\context Voice{ %s }
348 </lilypond>''',
349                 'output-noinline': r'''
350 <!-- generated: %(fn)s.png !-->
351 ''',
352                 ## maybe <hr> ?
353                 'pagebreak': None,
354                 # Verbatim text is always finished with \n.  FIXME: For HTML,
355                 # this newline should be removed.
356                 'output-verbatim': r'''<pre>
357 %s</pre>''',
358                 # Verbatim text is always finished with \n.  FIXME: For HTML,
359                 # this newline should be removed.
360                 'output-small-verbatim': r'''<font size=-1><pre>
361 %s</pre></font>''',
362                 ## Ugh we need to differentiate on origin:
363                 ## lilypond-block origin wants an extra <p>, but
364                 ## inline music doesn't.
365                 ## possibly other center options?
366                 'output-html': r'''
367 <a href="%(fn)s.ly">
368 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
369 ''',
370                 },
371
372
373         'latex': {
374
375                 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
376   \context Staff <
377     \context Voice{
378       %s
379     }
380   >
381 \end{lilypond}''',
382                 'output-filename' : r'''\verb+%s+:\\
383 %% %s
384 %% %s
385 ''',
386
387                 # verbatim text is always finished with \n
388                 'output-verbatim': r'''\begin{verbatim}
389 %s\end{verbatim}
390 ''',
391                 # verbatim text is always finished with \n
392                 'output-small-verbatim': r'''{\small\begin{verbatim}
393 %s\end{verbatim}}
394 ''',
395                 'output-default-post': "\\def\postLilypondExample{}\n",
396                 'output-default-pre': "\\def\preLilypondExample{}\n",
397                 'usepackage-graphics': '\\usepackage{graphics}\n',
398                 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
399                 'output-noinline': r'''
400 %% generated: %(fn)s.eps
401 ''',
402                 'output-latex-quoted': r'''{\preLilypondExample
403 \input %(fn)s.tex
404 \postLilypondExample}''',
405                 'output-latex-noquote': r'''{\parindent 0pt
406 \preLilypondExample
407 \input %(fn)s.tex
408 \postLilypondExample}''',
409                 'pagebreak': r'\pagebreak',
410                 },
411
412
413         'texi' : {
414
415
416                 'output-filename' : r'''@ifnothtml
417 @file{%s}:@*
418 @end ifnothtml
419 @ifhtml
420 @uref{%s,@file{%s}}
421 @end ifhtml
422 ''',
423                 'output-lilypond-fragment': '''@lilypond[%s]
424 \context Staff\context Voice{ %s }
425 @end lilypond ''',
426                 'output-noinline': r'''
427 @c generated: %(fn)s.png
428 ''',
429                 'pagebreak': None,
430                 # verbatim text is always finished with \n
431                 'output-small-verbatim': r'''@smallexample
432 %s@end smallexample
433 ''',
434                 # verbatim text is always finished with \n
435                 'output-verbatim': r'''@example
436 %s@end example
437 ''',
438                 # do some tweaking: @ is needed in some ps stuff.
439                 #
440                 # ugh, the <p> below breaks inline images...
441                 'output-texi-noquote': r'''@tex
442 \catcode`\@=12
443 \parindent 0pt
444 \def\lilypondbook{}
445 \input %(fn)s.tex
446 \catcode`\@=0
447 @end tex
448 @html
449 <p><a href="%(fn)s.ly">
450 <img border=0 src="%(fn)s.png" alt="[picture of music]">
451 </a><p>
452 @end html
453 ''',
454                 'output-texi-quoted': r'''@quotation
455 @tex
456 \catcode`\@=12
457 \def\lilypondbook{}
458 \input %(fn)s.tex
459 \catcode`\@=0
460 @end tex
461 @html
462 <a href="%(fn)s.ly">
463 <img border=0 src="%(fn)s.png" alt="[picture of music]">
464 </a>
465 @end html
466 @end quotation
467 ''',
468                 }
469
470         }
471
472 def output_verbatim (body, small):
473         global format
474         if format == 'html':
475                 body = re.sub ('&', '&amp;', body)
476                 body = re.sub ('>', '&gt;', body)
477                 body = re.sub ('<', '&lt;', body)
478         elif format == 'texi':
479                 # clumsy workaround for python 2.2 pre bug.
480                 body = re.sub ('@', '@@', body)
481                 body = re.sub ('{', '@{', body)
482                 body = re.sub ('}', '@}', body)
483
484         if small:
485                 key = 'output-small-verbatim'
486         else:
487                 key = 'output-verbatim'
488         return get_output (key) % body
489
490
491 ################################################################
492 # Recognize special sequences in the input 
493
494
495 # Warning: This uses extended regular expressions.  Tread with care.
496 #
497 # legenda
498 #
499 # (?P<name>regex) -- assign result of REGEX to NAME
500 # *? -- match non-greedily.
501 # (?m) -- multiline regex: make ^ and $ match at each line
502 # (?s) -- make the dot match all characters including newline
503 re_dict = {
504         'html': {
505                 'include':  no_match,
506                 'input': no_match,
507                 'header': no_match,
508                 'preamble-end': no_match,
509                 'landscape': no_match,
510                 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
511                 'verb': r'''(?P<code><pre>.*?</pre>)''',
512                 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
513                 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
514                 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
515                 'option-sep' : '\s*',
516                 'intertext': r',?\s*intertext=\".*?\"',
517                 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
518                 'singleline-comment': no_match,
519                 'numcols': no_match,
520                 'multicols': no_match,
521                 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
522                 },
523
524         'latex': {
525                 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
526                 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
527                 'option-sep' : ',\s*',
528                 'header': r"\n*\\documentclass\s*(\[.*?\])?",
529                 'preamble-end': r'(?P<code>\\begin\s*{document})',
530                 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
531                 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
532                 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
533                 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
534                 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
535                 'def-post-re': r"\\def\\postLilypondExample",
536                 'def-pre-re': r"\\def\\preLilypondExample",
537                 'usepackage-graphics': r"\usepackage\s*{graphics}",
538                 'intertext': r',?\s*intertext=\".*?\"',
539                 'multiline-comment': no_match,
540                 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
541                 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
542                 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
543                 'ly2dvi': no_match,
544
545                 },
546
547         # why do we have distinction between @mbinclude and @include?
548
549         'texi': {
550                 'include':  '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
551                 'input': no_match,
552                 'header': no_match,
553                 'preamble-end': no_match,
554                 'landscape': no_match,
555                 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
556                 'verb': r'''(?P<code>@code{.*?})''',
557                 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
558                 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
559                 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
560                 'option-sep' : ',\s*',
561                 'intertext': r',?\s*intertext=\".*?\"',
562                 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
563                 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
564                 'numcols': no_match,
565                 'multicols': no_match,
566                 'ly2dvi': no_match,
567                 }
568         }
569
570
571 for r in re_dict.keys ():
572         olddict = re_dict[r]
573         newdict = {}
574         for k in olddict.keys ():
575                 try:
576                         newdict[k] = re.compile (olddict[k])
577                 except:
578                         print 'invalid regexp: %s' % olddict[k]
579
580                         ## we'd like to catch and reraise a more
581                         ## detailed error, but alas, the exceptions
582                         ## changed across the 1.5/2.1 boundary.
583
584                         raise "Invalid re"
585         re_dict[r] = newdict
586
587
588 def uniq (list):
589         list.sort ()
590         s = list
591         list = []
592         for x in s:
593                 if x not in list:
594                         list.append (x)
595         return list
596
597
598 def get_output (name):
599         return  output_dict[format][name]
600
601 def get_re (name):
602         return  re_dict[format][name]
603
604 def bounding_box_dimensions (fname):
605         if g_outdir:
606                 fname = os.path.join (g_outdir, fname)
607         try:
608                 fd = open (fname)
609         except IOError:
610                 error ("Error opening `%s'" % fname)
611         str = fd.read ()
612         s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
613         if s:
614
615                 gs = map (lambda x: string.atoi (x), s.groups ())
616                 return (int (gs[2] - gs[0] + 0.5),
617                         int (gs[3] - gs[1] + 0.5))
618         else:
619                 return (0,0)
620
621 def error (str):
622         sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
623         raise 'Exiting.'
624
625
626 def compose_full_body (body, opts):
627         '''Construct the lilypond code to send to Lilypond.
628         Add stuff to BODY using OPTS as options.'''
629         music_size = default_music_fontsize
630         if g_force_music_fontsize:
631                 music_size = g_force_music_fontsize
632         indent = ''
633         linewidth = ''
634         notime = ''
635         for o in opts:
636                 if not g_force_music_fontsize:
637                         m = re.match ('([0-9]+)pt', o)
638                         if m:
639                                 music_size = string.atoi (m.group (1))
640
641                 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
642                 if m:
643                         f = float (m.group (1))
644                         indent = 'indent = %f\\%s' % (f, m.group (2))
645
646                 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
647                 if m:
648                         f = float (m.group (1))
649                         linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
650
651         if re.search ('\\\\score', body):
652                 is_fragment = 0
653         else:
654                 is_fragment = 1
655         if 'fragment' in opts:
656                 is_fragment = 1
657         if 'nofragment' in opts:
658                 is_fragment = 0
659
660         if is_fragment and not 'multiline' in opts:
661                 opts.append ('singleline')
662
663         if 'raggedright' in opts or  'singleline' in opts:
664                 if not linewidth:
665                         linewidth = 'raggedright = ##t'
666                 if not indent:
667                         indent = 'indent = 0.0\mm'
668         elif not linewidth:
669                 global paperguru
670                 l = paperguru.get_linewidth ()
671                 linewidth = 'linewidth = %f\pt' % l
672
673         if 'noindent' in opts:
674                 indent = 'indent = 0.0\mm'
675
676         if 'notime' in opts:
677                 notime = r'''
678 \translator {
679   \StaffContext
680   \remove Time_signature_engraver
681 }
682 '''
683
684         for o in opts:
685                 m= re.search ('relative(.*)', o)
686                 v = 0
687                 if m:
688                         try:
689                                 v = string.atoi (m.group (1))
690                         except ValueError:
691                                 pass
692
693                         v = v + 1
694                         pitch = 'c'
695                         if v < 0:
696                                 pitch = pitch + '\,' * v
697                         elif v > 0:
698                                 pitch = pitch + '\'' * v
699
700                         body = '\\relative %s { %s }' % (pitch, body)
701
702         if is_fragment:
703                 body = r'''
704 \score {
705   \notes {
706 %s
707   }
708 }
709 ''' % body
710
711         opts = uniq (opts)
712         optstring = string.join (opts, ' ')
713         optstring = re.sub ('\n', ' ', optstring)
714         body = r'''
715 %% Generated automatically by: lilypond-book.py
716 %% options are %s
717 \include "paper%d.ly"
718 \paper  {
719   %s
720   %s
721   %s
722 }
723 ''' % (optstring, music_size, linewidth, indent, notime) + body
724
725         # ughUGH not original options
726         return body
727
728 def scan_html_preamble (chunks):
729         return
730
731 def scan_latex_preamble (chunks):
732         # First we want to scan the \documentclass line
733         # it should be the first non-comment line.
734         # The only thing we really need to know about the \documentclass line
735         # is if there are one or two columns to begin with.
736         idx = 0
737         while 1:
738                 if chunks[idx][0] == 'ignore':
739                         idx = idx + 1
740                         continue
741                 m = get_re ('header').match (chunks[idx][1])
742                 if not m:
743                         error ("Latex documents must start with a \documentclass command")
744                 if m.group (1):
745                         options = re.split (',[\n \t]*', m.group (1)[1:-1])
746                 else:
747                         options = []
748                 if 'twocolumn' in options:
749                         paperguru.m_num_cols = 2
750                 break
751
752
753         # Then we add everything before \begin{document} to
754         # paperguru.m_document_preamble so that we can later write this header
755         # to a temporary file in find_latex_dims() to find textwidth.
756         while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
757                 if chunks[idx] == 'ignore':
758                         idx = idx + 1
759                         continue
760                 paperguru.m_document_preamble.append (chunks[idx][1])
761                 idx = idx + 1
762
763         if len (chunks) == idx:
764                 error ("Didn't find end of preamble (\\begin{document})")
765
766         paperguru.find_latex_dims ()
767
768 def scan_texi_preamble (chunks):
769         # this is not bulletproof..., it checks the first 10 chunks
770         for c in chunks[:10]:
771                 if c[0] == 'input':
772                         for s in ('afourpaper', 'afourwide', 'letterpaper',
773                                   'afourlatex', 'smallbook'):
774                                 if string.find (c[1], "@%s" % s) != -1:
775                                         paperguru.m_papersize = s
776
777
778 def scan_preamble (chunks):
779         global format
780         if format == 'html':
781                 scan_html_preamble (chunks)
782         elif format == 'latex':
783                 scan_latex_preamble (chunks)
784         elif format == 'texi':
785                 scan_texi_preamble (chunks)
786
787
788 def completize_preamble (chunks):
789         global format
790         if format != 'latex':
791                 return chunks
792         pre_b = post_b = graphics_b = None
793         for chunk in chunks:
794                 if chunk[0] == 'preamble-end':
795                         break
796                 if chunk[0] == 'input':
797                         m = get_re ('def-pre-re').search (chunk[1])
798                         if m:
799                                 pre_b = 1
800                 if chunk[0] == 'input':
801                         m = get_re ('def-post-re').search (chunk[1])
802                         if m:
803                                 post_b = 1
804
805                 if chunk[0] == 'input':
806                         m = get_re ('usepackage-graphics').search (chunk[1])
807                         if m:
808                                 graphics_b = 1
809         x = 0
810         while x < len (chunks) and   chunks[x][0] != 'preamble-end':
811                 x = x + 1
812
813         if x == len (chunks):
814                 return chunks
815
816         if not pre_b:
817                 chunks.insert (x, ('input', get_output ('output-default-pre')))
818         if not post_b:
819                 chunks.insert (x, ('input', get_output ('output-default-post')))
820         if not graphics_b:
821                 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
822
823         return chunks
824
825
826 read_files = []
827 def find_file (name):
828         '''
829         Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
830         '''
831
832         if name == '-':
833                 return (sys.stdin.read (), '<stdin>')
834         f = None
835         nm = ''
836         for a in include_path:
837                 try:
838                         nm = os.path.join (a, name)
839                         f = open (nm)
840                         global read_files
841                         read_files.append (nm)
842                         break
843                 except IOError:
844                         pass
845         if f:
846                 sys.stderr.write ("Reading `%s'\n" % nm)
847                 return (f.read (), nm)
848         else:
849                 error ("File not found `%s'\n" % name)
850                 return ('', '')
851
852 def do_ignore (match_object):
853         return [('ignore', match_object.group ('code'))]
854 def do_preamble_end (match_object):
855         return [('preamble-end', match_object.group ('code'))]
856
857 def make_verbatim (match_object):
858         return [('verbatim', match_object.group ('code'))]
859
860 def make_verb (match_object):
861         return [('verb', match_object.group ('code'))]
862
863 def do_include_file (m):
864         "m: MatchObject"
865         return [('input', get_output ('pagebreak'))] \
866              + read_doc_file (m.group ('filename')) \
867              + [('input', get_output ('pagebreak'))]
868
869 def do_input_file (m):
870         return read_doc_file (m.group ('filename'))
871
872 def make_lilypond (m):
873         if m.group ('options'):
874                 options = m.group ('options')
875         else:
876                 options = ''
877         return [('input', get_output ('output-lilypond-fragment') %
878                         (options, m.group ('code')))]
879
880 def make_lilypond_file (m):
881         '''
882
883         Find @lilypondfile{bla.ly} occurences and substitute bla.ly
884         into a @lilypond .. @end lilypond block.
885
886         '''
887
888         if m.group ('options'):
889                 options = get_re ('option-sep').split (m.group ('options'))
890         else:
891                 options = []
892         (content, nm) = find_file (m.group ('filename'))
893         options.append ("filename=%s" % nm)
894
895         return [('lilypond', content, options)]
896
897 def make_ly2dvi_block (m):
898         '''
899
900         Find <ly2dvifile .. >
901         '''
902
903         return [('ly2dvi', m.group ('filename'), m.group ('options'))]
904
905
906 def make_lilypond_block (m):
907         if not g_do_music:
908                 return []
909
910         if m.group ('options'):
911                 options = get_re ('option-sep').split (m.group ('options'))
912         else:
913                 options = []
914         options = filter (lambda s: s != '', options)
915         return [('lilypond', m.group ('code'), options)]
916
917
918 def do_columns (m):
919         global format
920         if format != 'latex':
921                 return []
922         if m.group ('num') == 'one':
923                 return [('numcols', m.group ('code'), 1)]
924         if m.group ('num') == 'two':
925                 return [('numcols', m.group ('code'), 2)]
926
927 def do_multicols (m):
928         global format
929         if format != 'latex':
930                 return []
931         if m.group ('be') == 'begin':
932                 return [('multicols', m.group ('code'), int (m.group ('num')))]
933         else:
934                 return [('multicols', m.group ('code'), 1)]
935         return []
936
937 def chop_chunks (chunks, re_name, func, use_match=0):
938         newchunks = []
939         for c in chunks:
940                 if c[0] == 'input':
941                         str = c[1]
942                         while str:
943                                 m = get_re (re_name).search (str)
944                                 if m == None:
945                                         newchunks.append (('input', str))
946                                         str = ''
947                                 else:
948                                         if use_match:
949                                                 newchunks.append (('input', str[:m.start ('match')]))
950                                         else:
951                                                 newchunks.append (('input', str[:m.start (0)]))
952                                         #newchunks.extend (func (m))
953                                         # python 1.5 compatible:
954                                         newchunks = newchunks + func (m)
955                                         str = str [m.end (0):]
956                 else:
957                         newchunks.append (c)
958         return newchunks
959
960 def determine_format (str):
961         global format
962         if format == '':
963                 html = re.search ('(?i)<[dh]tml', str[:200])
964                 latex = re.search (r'''\\document''', str[:200])
965                 texi = re.search ('@node|@setfilename', str[:200])
966
967                 f = ''
968                 g = None
969
970                 if html and not latex and not texi:
971                         f = 'html'
972                 elif latex and not html and not texi:
973                         f = 'latex'
974                 elif texi and not html and not latex:
975                         f = 'texi'
976                 else:
977                         error ("can't determine format, please specify")
978                 format = f
979
980         global paperguru
981         if paperguru == None:
982                 if format == 'html':
983                         g = HtmlPaper ()
984                 elif format == 'latex':
985                         g = LatexPaper ()
986                 elif format == 'texi':
987                         g = TexiPaper ()
988
989                 paperguru = g
990
991
992 def read_doc_file (filename):
993         '''Read the input file, find verbatim chunks and do \input and \include
994         '''
995         (str, path) = find_file (filename)
996         determine_format (str)
997
998         chunks = [('input', str)]
999
1000         # we have to check for verbatim before doing include,
1001         # because we don't want to include files that are mentioned
1002         # inside a verbatim environment
1003         chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1004
1005         chunks = chop_chunks (chunks, 'verb', make_verb)
1006         chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1007         #ugh fix input
1008         chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1009         chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1010         return chunks
1011
1012
1013 taken_file_names = {}
1014
1015 def unique_file_name (body):
1016         return 'lily-' + `abs (hash (body))`
1017
1018 def schedule_lilypond_block (chunk):
1019         '''Take the body and options from CHUNK, figure out how the
1020         real .ly should look, and what should be left MAIN_STR (meant
1021         for the main file).  The .ly is written, and scheduled in
1022         TODO.
1023
1024         Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1025
1026         TODO has format [basename, extension, extension, ... ]
1027         '''
1028         (type, body, opts) = chunk
1029         assert type == 'lilypond'
1030         file_body = compose_full_body (body, opts)
1031         ## Hmm, we should hash only lilypond source, and skip the
1032         ## %options are ...
1033         ## comment line
1034         basename = unique_file_name (file_body)
1035         for o in opts:
1036                 m = re.search ('filename="(.*?)"', o)
1037                 if m:
1038                         basename = m.group (1)
1039                         if not taken_file_names.has_key (basename):
1040                                 taken_file_names[basename] = 0
1041                         else:
1042                                 taken_file_names[basename] = taken_file_names[basename] + 1
1043                                 basename = basename + "-%i" % taken_file_names[basename]
1044         update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1045         needed_filetypes = ['tex']
1046
1047         if format == 'html' or g_make_html:
1048                 needed_filetypes.append ('eps')
1049                 needed_filetypes.append ('png')
1050         if 'eps' in opts and not ('eps' in needed_filetypes):
1051                 needed_filetypes.append ('eps')
1052
1053         pathbase = os.path.join (g_outdir, basename)
1054         def f (base, ext1, ext2):
1055                 a = os.path.isfile (base + ext2)
1056                 if (os.path.isfile (base + ext1) and
1057                     os.path.isfile (base + ext2) and
1058                                 os.stat (base+ext1)[stat.ST_MTIME] >
1059                                 os.stat (base+ext2)[stat.ST_MTIME]) or \
1060                                 not os.path.isfile (base + ext2):
1061                         return 1
1062         todo = []
1063         if 'tex' in needed_filetypes and f (pathbase, '.ly', '.tex'):
1064                 todo.append ('tex')
1065         if 'eps' in needed_filetypes and f (pathbase, '.tex', '.eps'):
1066                 todo.append ('eps')
1067         if 'png' in needed_filetypes and f (pathbase, '.eps', '.png'):
1068                 todo.append ('png')
1069         newbody = ''
1070
1071         if 'printfilename' in opts:
1072                 for o in opts:
1073                         m= re.match ("filename=(.*)", o)
1074                         if m:
1075                                 template = get_output ("output-filename")
1076                                 b =  basename + '.ly'
1077                                 human_base = os.path.basename (m.group (1))
1078                                                   
1079                                 ## todo: include path, but strip 
1080                                 ## first part of the path.
1081                                 newbody = newbody + template % (human_base, b,human_base)
1082                                 break
1083
1084
1085         if 'smallverbatim' in opts:
1086                 newbody = newbody + output_verbatim (body, 1)
1087         elif 'verbatim' in opts:
1088                 newbody = newbody + output_verbatim (body, 0)
1089
1090         for o in opts:
1091                 m = re.search ('intertext="(.*?)"', o)
1092                 if m:
1093                         newbody = newbody + "\n"
1094                         if format == 'texi':
1095                                 newbody = newbody + "@noindent\n"
1096                         elif format == 'latex':
1097                                 newbody = newbody + "\\noindent\n"
1098                         newbody = newbody + m.group (1) + "\n"
1099
1100         if 'noinline' in opts:
1101                 s = 'output-noinline'
1102         elif format == 'latex':
1103                 if 'eps' in opts:
1104                         s = 'output-eps'
1105                 else:
1106                         if 'quote' in opts:
1107                                 s = 'output-latex-quoted'
1108                         else:
1109                                 s = 'output-latex-noquote'
1110         elif format == 'texi':
1111                 if 'quote' in opts:
1112                         s = 'output-texi-quoted'
1113                 else:
1114                         s = 'output-texi-noquote'
1115         else: # format == 'html'
1116                 s = 'output-html'
1117         newbody = newbody + get_output (s) % {'fn': basename }
1118         return ('lilypond', newbody, opts, todo, basename)
1119
1120
1121
1122
1123 def process_lilypond_blocks (chunks):#ugh rename
1124         newchunks = []
1125         # Count sections/chapters.
1126         for c in chunks:
1127                 if c[0] == 'lilypond':
1128                         c = schedule_lilypond_block (c)
1129                 elif c[0] == 'numcols':
1130                         paperguru.m_num_cols = c[2]
1131                 elif c[0] == 'multicols':
1132                         paperguru.m_multicols = c[2]
1133                 newchunks.append (c)
1134         return newchunks
1135
1136 def process_ly2dvi_blocks (chunks):
1137         
1138         def process_ly2dvi_block (chunk):
1139                 """
1140
1141 Run ly2dvi script on filename specified in CHUNK.
1142 This is only supported for HTML output.
1143
1144 In HTML output it will leave a download menu with ps/pdf/midi etc.  in
1145 a separate HTML file, and a title + preview in the main html file,
1146 linking to the menu.
1147
1148                 """
1149                 (tag, name, opts) = chunk
1150                 assert format == 'html'
1151                 (content, original_name) = find_file (name)
1152
1153                 original_name = os.path.basename (original_name)
1154                 
1155                 base = unique_file_name (content)
1156                 outname = base + '.ly'
1157                 changed = update_file (content, outname)
1158
1159                 preview = base + ".png"
1160                 if changed or not os.path.isfile (preview):
1161                         
1162                         ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) ) 
1163
1164                         ly.make_page_images (base)
1165                         ly.system ('gzip -9 - < %s.ps > %s.ps.gz' %  (base, base))
1166                         
1167                 def size_str (fn):
1168                         b = os.stat(fn)[stat.ST_SIZE]
1169                         if b < 1024:
1170                                 return '%d bytes' % b
1171                         elif b < (2 << 20):
1172                                 return '%d kb' % (b >> 10)
1173                         else:
1174                                 return '%d mb' % (b >> 20)
1175
1176                 exts = {
1177                         'pdf' : "Print (PDF, %s)",
1178                         'ps.gz' : "Print (gzipped PostScript, %s)",
1179                         'png' : "View (PNG, %s)",
1180                         'midi' : "Listen (MIDI, %s)",
1181                         'ly' : "View source code (%s)", 
1182                         }
1183
1184                 menu = ''
1185                 page_files = glob.glob ('%s-page*.png' % base)
1186
1187                 for p in string.split (page_files, '\n'):
1188                         p = p.strip()
1189                         if os.path.isfile (p):
1190                                 sz = size_str (p)
1191                                 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1192                                 page = page % sz
1193                                 menu += '<li><a href="%s">%s</a>' % (p, page) 
1194
1195                 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1196                 for e in ext_order:
1197                         fn =   base +  '.' + e
1198                         print 'checking,' , fn
1199                         if not os.path.isfile (fn):
1200                                 continue
1201
1202                         entry = exts[e] % size_str (fn)
1203
1204                         ## TODO: do something like
1205                         ## this for texinfo/latex as well ?
1206                         
1207                         menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1208
1209
1210                 explanatory_para = """The pictures are 90dpi
1211 anti-aliased snapshots of the printed output, in PNG format. Both  PDF and PS
1212 use scalable fonts and should look OK at any resolution."""
1213                 
1214                 separate_menu =r'''
1215 <title>LilyPond example %s</title>
1216
1217 <h1>%s</h1>
1218 <p><img src="%s">
1219 <p>%s
1220 <p>
1221 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1222                 
1223                 open (base + '.html','w'). write (separate_menu)
1224
1225                 inline_menu = '<p/><a href="%s.html"><img src="%s"><p/></a>' % (base, original_name, preview)
1226
1227                 return ('ly2dvi', inline_menu)
1228
1229         newchunks = []
1230         for c in chunks:
1231                 if c[0] == 'ly2dvi':
1232                         c = process_ly2dvi_block (c)
1233                 newchunks.append (c)
1234
1235         return newchunks
1236
1237 def compile_all_files (chunks):
1238         global foutn
1239         eps = []
1240         tex = []
1241         png = []
1242
1243         for c in chunks:
1244                 if c[0] != 'lilypond':
1245                         continue
1246                 base  = c[4]
1247                 exts = c[3]
1248                 for e in exts:
1249                         if e == 'eps':
1250                                 eps.append (base)
1251                         elif e == 'tex':
1252                                 #ugh
1253                                 if base + '.ly' not in tex:
1254                                         tex.append (base + '.ly')
1255                         elif e == 'png' and g_do_pictures:
1256                                 png.append (base)
1257         d = os.getcwd ()
1258         if g_outdir:
1259                 os.chdir (g_outdir)
1260         if tex:
1261                 # fixme: be sys-independent.
1262                 def incl_opt (x):
1263                         if g_outdir and x[0] != '/' :
1264                                 x = os.path.join (g_here_dir, x)
1265                         return ' -I %s' % x
1266
1267                 incs = map (incl_opt, include_path)
1268                 lilyopts = string.join (incs)
1269                 if do_deps:
1270                         lilyopts += ' --dependencies'
1271                         if g_outdir:
1272                                 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1273                 lilyopts += ' --header=texidoc'
1274                 texfiles = string.join (tex)
1275                 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1276                                     texfiles))
1277                 ly.system (cmd, ignore_error = 0, progress_p = 1)
1278
1279                 #
1280                 # Ugh, fixing up dependencies for .tex generation
1281                 #
1282                 if do_deps:
1283                         depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1284                                                         x), tex)
1285
1286                         for i in depfiles:
1287                                 f =open (i)
1288                                 text=f.read ()
1289                                 f.close ()
1290                                 text=re.sub ('\n([^:\n]*):',
1291                                              '\n' + foutn + ':', text)
1292                                 f = open (i, 'w')
1293                                 f.write (text)
1294                                 f.close ()
1295
1296         def to_eps (file):
1297                 cmd = r"latex '\nonstopmode \input %s'" % file
1298                 # Ugh.  (La)TeX writes progress and error messages on stdout
1299                 # Redirect to stderr
1300                 cmd += ' 1>/dev/stderr'
1301                 ly.system (cmd)
1302                 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1303         map (to_eps, eps)
1304
1305         map (ly.make_preview, png)
1306         os.chdir (d)
1307
1308
1309 def update_file (body, name):
1310         '''
1311         write the body if it has changed. Return whether BODY has changed.
1312         '''
1313         same = 0
1314         try:
1315                 f = open (name)
1316                 fs = f.read (-1)
1317                 same = (fs == body)
1318         except:
1319                 pass
1320
1321         if not same:
1322                 f = open (name , 'w')
1323                 f.write (body)
1324                 f.close ()
1325
1326         return not same
1327
1328
1329 def write_deps (fn, target, chunks):
1330         global read_files
1331         sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1332         f = open (os.path.join (g_outdir, fn), 'w')
1333         f.write ('%s%s: ' % (g_dep_prefix, target))
1334         for d in read_files:
1335                 f.write ('%s ' %  d)
1336
1337
1338         ## There used to be code to write .tex dependencies, but
1339         ## that is silly: lilypond-book has its own dependency scheme
1340         ## to ensure that all lily-XXX.tex files are there
1341                 
1342
1343         f.write ('\n')
1344         f.close ()
1345         read_files = []
1346
1347 def check_texidoc (chunks):
1348         ## TODO: put file name in front of texidoc. 
1349         ##
1350         n = []
1351         for c in chunks:
1352                 if c[0] == 'lilypond':
1353                         (type, body, opts, todo, basename) = c;
1354                         pathbase = os.path.join (g_outdir, basename)
1355                         if os.path.isfile (pathbase + '.texidoc') \
1356                            and 'notexidoc' not in opts:
1357                                 body = '\n@include %s.texidoc\n' % basename + body
1358                                 c = (type, body, opts, todo, basename)
1359                 n.append (c)
1360         return n
1361
1362
1363 ## what's this? Docme --hwn
1364 ##
1365 def fix_epswidth (chunks):
1366         newchunks = []
1367         for c in chunks:
1368                 if c[0] != 'lilypond' or 'eps' not in c[2]:
1369                         newchunks.append (c)
1370                         continue
1371
1372                 mag = 1.0
1373                 for o in c[2]:
1374                         m  = re.match ('magnification=([0-9.]+)', o)
1375                         if m:
1376                                 mag = string.atof (m.group (1))
1377
1378                 def replace_eps_dim (match, lmag = mag):
1379                         filename = match.group (1)
1380                         dims = bounding_box_dimensions (filename)
1381
1382                         return '%fpt' % (dims[0] *lmag)
1383
1384                 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1385                 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1386
1387         return newchunks
1388
1389
1390 ##docme: why global?
1391 foutn=""
1392
1393 def do_file (input_filename):
1394         chunks = read_doc_file (input_filename)
1395         chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1396         chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1397         chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1398         chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1399         chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1400         chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1401         chunks = chop_chunks (chunks, 'numcols', do_columns)
1402         chunks = chop_chunks (chunks, 'multicols', do_multicols)
1403         #print "-" * 50
1404         #for c in chunks: print "c:", c;
1405         #sys.exit ()
1406         scan_preamble (chunks)
1407         chunks = process_lilypond_blocks (chunks)
1408         chunks = process_ly2dvi_blocks (chunks)
1409         
1410         # Do It.
1411         global g_run_lilypond
1412         if g_run_lilypond:
1413                 compile_all_files (chunks)
1414                 chunks = fix_epswidth (chunks)
1415
1416         global format
1417         if format == 'texi':
1418                 chunks = check_texidoc (chunks)
1419
1420         x = 0
1421         chunks = completize_preamble (chunks)
1422
1423         global foutn
1424
1425         if outname:
1426                 my_outname = outname
1427         elif input_filename == '-' or input_filename == "/dev/stdin":
1428                 my_outname = '-'
1429         else:
1430                 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1431         my_depname = my_outname + '.dep'
1432
1433         if my_outname == '-' or my_outname == '/dev/stdout':
1434                 fout = sys.stdout
1435                 foutn = "<stdout>"
1436                 global do_deps
1437                 do_deps = 0
1438         else:
1439                 foutn = os.path.join (g_outdir, my_outname)
1440                 sys.stderr.write ("Writing `%s'\n" % foutn)
1441                 fout = open (foutn, 'w')
1442         for c in chunks:
1443                 fout.write (c[1])
1444         fout.close ()
1445         # should chmod -w
1446
1447         if do_deps:
1448                 write_deps (my_depname, foutn, chunks)
1449
1450 outname = ''
1451 try:
1452         (sh, long) = ly.getopt_args (option_definitions)
1453         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1454         
1455 except getopt.error, msg:
1456         sys.stderr.write ('\n')
1457         ly.error (_ ("getopt says: `%s\'" % s))
1458         sys.stderr.write ('\n')
1459         ly.help ()
1460         ly.exit (2)
1461
1462 do_deps = 0
1463 for opt in options:
1464         o = opt[0]
1465         a = opt[1]
1466
1467         if o == '--include' or o == '-I':
1468                 include_path.append (a)
1469         elif o == '--version' or o == '-v':
1470                 ly.identify (sys.stdout)
1471                 sys.exit (0)
1472         elif o == '--verbose' or o == '-V':
1473                 verbose_p = 1
1474         elif o == '--format' or o == '-f':
1475                 format = a
1476                 if a == 'texi-html':
1477                         format = 'texi'
1478                         g_make_html = 1
1479         elif o == '--outname' or o == '-o':
1480                 if len (files) > 1:
1481                         #HACK
1482                         sys.stderr.write ("Lilypond-book is confused by --outname on multiple files")
1483                         sys.exit (1)
1484                 outname = a
1485         elif o == '--help' or o == '-h':
1486                 ly.help ()
1487                 sys.exit (0)
1488         elif o == '--no-lily' or o == '-n':
1489                 g_run_lilypond = 0
1490         elif o == '--preview-resolution':
1491                 preview_resolution = string.atoi (a)
1492         elif o == '--dependencies' or o == '-M':
1493                 do_deps = 1
1494         elif o == '--default-music-fontsize':
1495                 default_music_fontsize = string.atoi (a)
1496         elif o == '--default-lilypond-fontsize':
1497                 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1498                 default_music_fontsize = string.atoi (a)
1499         elif o == '--extra-options':
1500                 g_extra_opts = a
1501         elif o == '--force-music-fontsize':
1502                 g_force_music_fontsize = string.atoi (a)
1503         elif o == '--force-lilypond-fontsize':
1504                 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1505                 g_force_music_fontsize = string.atoi (a)
1506         elif o == '--dep-prefix':
1507                 g_dep_prefix = a
1508         elif o == '--no-pictures':
1509                 g_do_pictures = 0
1510         elif o == '--no-music':
1511                 g_do_music = 0
1512         elif o == '--outdir':
1513                 g_outdir = a
1514         elif o == '--warranty' or o == '-w':
1515                 #status = os.system ('lilypond -w')
1516                 if 1 or status:
1517                         ly.warranty ()
1518                 sys.exit (0)
1519
1520 ly.identify (sys.stderr)
1521
1522 if g_outdir:
1523         if os.path.isfile (g_outdir):
1524                 error ("outdir is a file: %s" % g_outdir)
1525         if not os.path.exists (g_outdir):
1526                 os.mkdir (g_outdir)
1527                 
1528 if not files:
1529         ly.help ()
1530         ly.error (_ ("no files specified on command line"))
1531         ly.exit (2)
1532
1533 ly.setup_environment ()
1534
1535 for input_filename in files:
1536         do_file (input_filename)
1537
1538
1539 #
1540 # Petr, ik zou willen dat ik iets zinvoller deed,
1541 # maar wat ik kan ik doen, het verandert toch niets?
1542 #   --hwn 20/aug/99