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