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