]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
091a1f6e13e710294cdcb5d55943ddfd95b9a477
[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-bin')
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-bin'
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 = '(( %s  >&2 ) >&- )' % cmd
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[singleline,%s]{lilypond}
386     \context Voice{
387       %s
388     }
389 \end{lilypond}''',
390                 'output-filename' : r'''\verb+%s+:\\
391 %% %s
392 %% %s
393 ''',
394
395                 # verbatim text is always finished with \n
396                 'output-verbatim': r'''\begin{verbatim}
397 %s\end{verbatim}
398 ''',
399                 # verbatim text is always finished with \n
400                 'output-small-verbatim': r'''{\small\begin{verbatim}
401 %s\end{verbatim}}
402 ''',
403                 'output-default-post': "\\def\postLilyPondExample{}\n",
404                 'output-default-pre': "\\def\preLilyPondExample{}\n",
405                 'usepackage-graphics': '\\usepackage{graphics}\n',
406                 'output-noinline': r'''
407 %% generated: %(fn)s.eps
408 ''',
409                 'output-latex-quoted': r'''{\preLilyPondExample
410 \input %(fn)s.tex
411 \postLilyPondExample}''',
412                 'output-latex-noquote': r'''{\parindent 0pt
413 \preLilyPondExample
414 \input %(fn)s.tex
415 \postLilyPondExample}''',
416                 'pagebreak': r'\pagebreak',
417                 },
418
419
420         'texi' : {
421
422
423                 'output-filename' : r'''
424 @ifnothtml
425 @file{%s}:@*
426 @end ifnothtml
427 @ifhtml
428 @uref{%s,@file{%s}}
429 @end ifhtml
430 ''',
431                 'output-lilypond-fragment': '''@lilypond[%s]
432 \context Staff\context Voice{ %s }
433 @end lilypond ''',
434                 'output-noinline': r'''
435 @c generated: %(fn)s.png
436 ''',
437                 'pagebreak': None,
438                 # verbatim text is always finished with \n
439                 'output-small-verbatim': r'''@smallexample
440 %s@end smallexample
441 ''',
442                 # verbatim text is always finished with \n
443                 'output-verbatim': r'''@example
444 %s@end example
445 ''',
446                 # do some tweaking: @ is needed in some ps stuff.
447                 #
448                 # ugh, the <p> below breaks inline images...
449                 'output-texi-noquote': r'''@tex
450 \catcode`\@=12
451 \parindent 0pt
452 \def\lilypondbook{}
453 \input %(fn)s.tex
454 \catcode`\@=0
455 @end tex
456 @html
457 <p>%(htmlimages)s
458 <p>
459 @end html
460 ''',
461                 'output-texi-quoted': r'''@quotation
462 @tex
463 \catcode`\@=12
464 \def\lilypondbook{}
465 \input %(fn)s.tex
466 \catcode`\@=0
467 @end tex
468 @html
469 <p>%(htmlimages)s
470 <p>
471 @end html
472 @end quotation
473 ''',
474                 }
475
476         }
477
478 def output_verbatim (body, small):
479         global format
480         if format == 'html':
481                 body = re.sub ('&', '&amp;', body)
482                 body = re.sub ('>', '&gt;', body)
483                 body = re.sub ('<', '&lt;', body)
484         elif format == 'texi':
485                 # clumsy workaround for python 2.2 pre bug.
486                 body = re.sub ('@', '@@', body)
487                 body = re.sub ('{', '@{', body)
488                 body = re.sub ('}', '@}', body)
489
490         if small:
491                 key = 'output-small-verbatim'
492         else:
493                 key = 'output-verbatim'
494         return get_output (key) % body
495
496
497 ################################################################
498 # Recognize special sequences in the input 
499
500
501 # Warning: This uses extended regular expressions.  Tread with care.
502 #
503 # legenda
504 #
505 # (?P<name>regex) -- assign result of REGEX to NAME
506 # *? -- match non-greedily.
507 # (?m) -- multiline regex: make ^ and $ match at each line
508 # (?s) -- make the dot match all characters including newline
509 re_dict = {
510         'html': {
511                 'include':  no_match,
512                 'input': no_match,
513                 'header': no_match,
514                 'preamble-end': no_match,
515                 'landscape': no_match,
516                 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
517                 'verb': r'''(?P<code><pre>.*?</pre>)''',
518                 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
519                 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
520                 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
521                 'option-sep' : '\s*',
522                 'intertext': r',?\s*intertext=\".*?\"',
523                 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
524                 'singleline-comment': no_match,
525                 'numcols': no_match,
526                 'multicols': no_match,
527                 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
528                 },
529
530         'latex': {
531                 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
532                 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
533                 'option-sep' : ',\s*',
534                 'header': r"\n*\\documentclass\s*(\[.*?\])?",
535                 'preamble-end': r'(?P<code>\\begin\s*{document})',
536                 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
537                 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
538                 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
539                 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
540                 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
541                 'def-post-re': r"\\def\\postLilyPondExample",
542                 'def-pre-re': r"\\def\\preLilyPondExample",
543                 'usepackage-graphics': r"\usepackage\s*{graphics}",
544                 'intertext': r',?\s*intertext=\".*?\"',
545                 'multiline-comment': no_match,
546                 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
547                 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
548                 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
549                 'ly2dvi': no_match,
550
551                 },
552
553         # why do we have distinction between @mbinclude and @include?
554
555         'texi': {
556                 'include':  '(?m)^[^%\n]*?(?P<match>@mbinclude\s+(?P<filename>\S*))',
557                 'input': no_match,
558                 'header': no_match,
559                 'preamble-end': no_match,
560                 'landscape': no_match,
561                 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
562                 'verb': r'''(?P<code>@code{.*?})''',
563                 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
564                 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
565                 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
566                 'option-sep' : ',\s*',
567                 'intertext': r',?\s*intertext=\".*?\"',
568                 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
569                 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
570                 'numcols': no_match,
571                 'multicols': no_match,
572                 'ly2dvi': no_match,
573                 }
574         }
575
576
577 for r in re_dict.keys ():
578         olddict = re_dict[r]
579         newdict = {}
580         for k in olddict.keys ():
581                 try:
582                         newdict[k] = re.compile (olddict[k])
583                 except:
584                         print 'invalid regexp: %s' % olddict[k]
585
586                         ## we'd like to catch and reraise a more
587                         ## detailed error, but alas, the exceptions
588                         ## changed across the 1.5/2.1 boundary.
589
590                         raise "Invalid re"
591         re_dict[r] = newdict
592
593
594 def uniq (list):
595         list.sort ()
596         s = list
597         list = []
598         for x in s:
599                 if x not in list:
600                         list.append (x)
601         return list
602
603
604 def get_output (name):
605         return  output_dict[format][name]
606
607 def get_re (name):
608         return  re_dict[format][name]
609
610 def bounding_box_dimensions (fname):
611         if g_outdir:
612                 fname = os.path.join (g_outdir, fname)
613         try:
614                 fd = open (fname)
615         except IOError:
616                 error ("Error opening `%s'" % fname)
617         str = fd.read ()
618         s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
619         if s:
620
621                 gs = map (lambda x: string.atoi (x), s.groups ())
622                 return (int (gs[2] - gs[0] + 0.5),
623                         int (gs[3] - gs[1] + 0.5))
624         else:
625                 return (0,0)
626
627 def error (str):
628         sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
629         raise 'Exiting.'
630
631
632 def compose_full_body (body, opts):
633         '''Construct the lilypond code to send to LilyPond.
634         Add stuff to BODY using OPTS as options.'''
635         music_size = default_music_fontsize
636         if g_force_music_fontsize:
637                 music_size = g_force_music_fontsize
638         indent = ''
639         linewidth = ''
640         notime = ''
641         for o in opts:
642                 if not g_force_music_fontsize:
643                         m = re.match ('([0-9]+)pt', o)
644                         if m:
645                                 music_size = string.atoi (m.group (1))
646
647                 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
648                 if m:
649                         f = float (m.group (1))
650                         indent = 'indent = %f\\%s' % (f, m.group (2))
651
652                 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
653                 if m:
654                         f = float (m.group (1))
655                         linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
656
657         if re.search ('\\\\score', body):
658                 is_fragment = 0
659         else:
660                 is_fragment = 1
661         if 'fragment' in opts:
662                 is_fragment = 1
663         if 'nofragment' in opts:
664                 is_fragment = 0
665
666         if is_fragment and not 'multiline' in opts:
667                 opts.append ('singleline')
668
669         if 'raggedright' in opts or  'singleline' in opts:
670                 if not linewidth:
671                         linewidth = 'raggedright = ##t'
672                 if not indent:
673                         indent = 'indent = 0.0\mm'
674         elif not linewidth:
675                 global paperguru
676                 l = paperguru.get_linewidth ()
677                 linewidth = 'linewidth = %f\pt' % l
678
679         if 'noindent' in opts:
680                 indent = 'indent = 0.0\mm'
681
682         if 'notime' in opts:
683                 notime = r'''
684 \translator {
685   \StaffContext
686   \remove Time_signature_engraver
687 }
688 '''
689
690         orig_name = ''
691         for o in opts:
692                 m= re.search ('relative(.*)', o)
693                 v = 0
694                 if m:
695                         try:
696                                 v = string.atoi (m.group (1))
697                         except ValueError:
698                                 pass
699
700                         v = v + 1
701                         pitch = 'c'
702                         if v < 0:
703                                 pitch = pitch + '\,' * v
704                         elif v > 0:
705                                 pitch = pitch + '\'' * v
706
707                         body = '\\relative %s { %s }' % (pitch, body)
708                 m =re.search ("filename=(.*)", o)
709                 if m:
710                         orig_name = m.group (1)
711                 
712         if is_fragment:
713                 body = r'''
714 \score {
715   \notes {
716 %s
717   }
718 }
719 ''' % body
720
721         opts = uniq (opts)
722         optstring = string.join (opts, ' ')
723         optstring = re.sub ('\n', ' ', optstring)
724         body = r'''
725 %% Generated automatically by: lilypond-book.py
726 %% options are %s
727 \include "paper%d.ly"
728 \paper  {
729   %s
730   %s
731   %s
732 }
733 ''' % (optstring, music_size, linewidth, indent, notime) + body
734
735         if orig_name:
736                 body = '\\renameinput \"%s\"\n%s' % (orig_name, body)
737         
738
739         # ughUGH not original options
740         return body
741
742 def scan_html_preamble (chunks):
743         return
744
745 def scan_latex_preamble (chunks):
746         # First we want to scan the \documentclass line
747         # it should be the first non-comment line.
748         # The only thing we really need to know about the \documentclass line
749         # is if there are one or two columns to begin with.
750         idx = 0
751         while 1:
752                 if chunks[idx][0] == 'ignore':
753                         idx = idx + 1
754                         continue
755                 m = get_re ('header').match (chunks[idx][1])
756                 if not m:
757                         error ("Latex documents must start with a \documentclass command")
758                 if m.group (1):
759                         options = re.split (',\s*', m.group (1)[1:-1])
760                 else:
761                         options = []
762                 if 'twocolumn' in options:
763                         paperguru.m_num_cols = 2
764                 break
765
766
767         # Then we add everything before \begin{document} to
768         # paperguru.m_document_preamble so that we can later write this header
769         # to a temporary file in find_latex_dims() to find textwidth.
770         while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
771                 if chunks[idx] == 'ignore':
772                         idx = idx + 1
773                         continue
774                 paperguru.m_document_preamble.append (chunks[idx][1])
775                 idx = idx + 1
776
777         if len (chunks) == idx:
778                 error ("Didn't find end of preamble (\\begin{document})")
779
780         paperguru.find_latex_dims ()
781
782 def scan_texi_preamble (chunks):
783         # this is not bulletproof..., it checks the first 10 chunks
784         for c in chunks[:10]:
785                 if c[0] == 'input':
786                         for s in ('afourpaper', 'afourwide', 'letterpaper',
787                                   'afourlatex', 'smallbook'):
788                                 if string.find (c[1], "@%s" % s) != -1:
789                                         paperguru.m_papersize = s
790
791
792 def scan_preamble (chunks):
793         global format
794         if format == 'html':
795                 scan_html_preamble (chunks)
796         elif format == 'latex':
797                 scan_latex_preamble (chunks)
798         elif format == 'texi':
799                 scan_texi_preamble (chunks)
800
801
802 def completize_preamble (chunks):
803         global format
804         if format != 'latex':
805                 return chunks
806         pre_b = post_b = graphics_b = None
807         for chunk in chunks:
808                 if chunk[0] == 'preamble-end':
809                         break
810                 if chunk[0] == 'input':
811                         m = get_re ('def-pre-re').search (chunk[1])
812                         if m:
813                                 pre_b = 1
814                 if chunk[0] == 'input':
815                         m = get_re ('def-post-re').search (chunk[1])
816                         if m:
817                                 post_b = 1
818
819                 if chunk[0] == 'input':
820                         m = get_re ('usepackage-graphics').search (chunk[1])
821                         if m:
822                                 graphics_b = 1
823         x = 0
824         while x < len (chunks) and   chunks[x][0] != 'preamble-end':
825                 x = x + 1
826
827         if x == len (chunks):
828                 return chunks
829
830         if not pre_b:
831                 chunks.insert (x, ('input', get_output ('output-default-pre')))
832         if not post_b:
833                 chunks.insert (x, ('input', get_output ('output-default-post')))
834         if not graphics_b:
835                 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
836
837         return chunks
838
839
840 read_files = []
841 def find_file (name):
842         '''
843         Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
844         '''
845
846         if name == '-':
847                 return (sys.stdin.read (), '<stdin>')
848         f = None
849         nm = ''
850         for a in include_path:
851                 try:
852                         nm = os.path.join (a, name)
853                         f = open (nm)
854                         global read_files
855                         read_files.append (nm)
856                         break
857                 except IOError:
858                         pass
859         if f:
860                 sys.stderr.write ("Reading `%s'\n" % nm)
861                 return (f.read (), nm)
862         else:
863                 error ("File not found `%s'\n" % name)
864                 return ('', '')
865
866 def do_ignore (match_object):
867         return [('ignore', match_object.group ('code'))]
868 def do_preamble_end (match_object):
869         return [('preamble-end', match_object.group ('code'))]
870
871 def make_verbatim (match_object):
872         return [('verbatim', match_object.group ('code'))]
873
874 def make_verb (match_object):
875         return [('verb', match_object.group ('code'))]
876
877 def do_include_file (m):
878         "m: MatchObject"
879         return [('input', get_output ('pagebreak'))] \
880              + read_doc_file (m.group ('filename')) \
881              + [('input', get_output ('pagebreak'))]
882
883 def do_input_file (m):
884         return read_doc_file (m.group ('filename'))
885
886 def make_lilypond (m):
887         if m.group ('options'):
888                 options = m.group ('options')
889         else:
890                 options = ''
891         return [('input', get_output ('output-lilypond-fragment') %
892                         (options, m.group ('code')))]
893
894 def make_lilypond_file (m):
895         '''
896
897         Find @lilypondfile{bla.ly} occurences and substitute bla.ly
898         into a @lilypond .. @end lilypond block.
899
900         '''
901
902         if m.group ('options'):
903                 options = get_re ('option-sep').split (m.group ('options'))
904         else:
905                 options = []
906         (content, nm) = find_file (m.group ('filename'))
907         options.append ("filename=%s" % nm)
908         (path, base) = os.path.split (nm)
909         
910         if path not in include_path:
911                 include_path.append (path)
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 + "-t%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 'quote' in opts:
1162                         s = 'output-latex-quoted'
1163                 else:
1164                         s = 'output-latex-noquote'
1165         elif format == 'texi':
1166                 if 'quote' in opts:
1167                         s = 'output-texi-quoted'
1168                 else:
1169                         s = 'output-texi-noquote'
1170         else: # format == 'html'
1171                 s = 'output-html'
1172
1173         def html_pages (basename):
1174                 pat = os.path.join (g_outdir, "%s-page*.png"%  basename)
1175                 
1176                 files =  glob.glob (pat)
1177                 
1178                 
1179                 template = '''<img align="center" valign="center"
1180                 border="0" src="%s" alt="[picture of music]">'''
1181
1182                 str = ''
1183                 if  files == []:
1184                         files = [basename+'.png' ]
1185                 else:
1186                         files = map (os.path.basename, files)
1187                         
1188                 for f in  files:
1189                         str += template % f
1190
1191                 str = '<a href="%s.ly">%s</a>' % (basename, str)
1192
1193                 return str
1194
1195         
1196         newbody = newbody + get_output (s) % {'fn': basename,
1197                                               'htmlimages': html_pages(basename)
1198                                               }
1199
1200         if filename_chunk:
1201                 return_chunks += [filename_chunk]
1202         
1203         return_chunks += [('lilypond', newbody, opts, todo, basename)]
1204         
1205         return return_chunks
1206
1207 def format_lilypond_output_bodies (chunks):
1208         newchunks = []
1209         for c in chunks:
1210
1211                 if c[0] == 'lilypond':
1212                         newchunks += format_lilypond_block (c)
1213                 else:
1214                         newchunks.append (c)
1215
1216         return newchunks
1217
1218
1219
1220 def process_lilypond_blocks (chunks):#ugh rename
1221         newchunks = []
1222         # Count sections/chapters.
1223         for c in chunks:
1224                 if c[0] == 'lilypond':
1225                         c = schedule_lilypond_block (c)
1226                 elif c[0] == 'numcols':
1227                         paperguru.m_num_cols = c[2]
1228                 elif c[0] == 'multicols':
1229                         paperguru.m_multicols = c[2]
1230                         
1231                 newchunks.append (c)
1232                 
1233         return newchunks
1234
1235 def process_ly2dvi_blocks (chunks):
1236         
1237         def process_ly2dvi_block (chunk):
1238                 """
1239
1240 Run ly2dvi script on filename specified in CHUNK.
1241 This is only supported for HTML output.
1242
1243 In HTML output it will leave a download menu with ps/pdf/midi etc.  in
1244 a separate HTML file, and a title + preview in the main html file,
1245 linking to the menu.
1246
1247                 """
1248                 (tag, name, opts) = chunk
1249                 assert format == 'html'
1250                 (content, original_name) = find_file (name)
1251
1252                 original_name = os.path.basename (original_name)
1253                 
1254                 base = unique_file_name (content)
1255                 outname = base + '.ly'
1256                 changed = update_file (content, outname)
1257
1258                 preview = base + ".preview.png"
1259                 preview_page = base + '-page1.png'
1260                 
1261                 if changed or not (os.path.isfile (preview) or
1262                                    os.path.isfile (preview_page)):
1263                         
1264                         ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) ) 
1265
1266                         ly.make_ps_images (base + '.ps')
1267                         ly.system ('gzip -9 - < %s.ps > %s.ps.gz' %  (base, base))
1268                         
1269                 def size_str (fn):
1270                         b = os.stat(fn)[stat.ST_SIZE]
1271                         if b < 1024:
1272                                 return '%d bytes' % b
1273                         elif b < (2 << 20):
1274                                 return '%d kb' % (b >> 10)
1275                         else:
1276                                 return '%d mb' % (b >> 20)
1277
1278                 exts = {
1279                         'pdf' : "Print (PDF, %s)",
1280                         'ps.gz' : "Print (gzipped PostScript, %s)",
1281                         'png' : "View (PNG, %s)",
1282                         'midi' : "Listen (MIDI, %s)",
1283                         'ly' : "View source code (%s)", 
1284                         }
1285
1286                 menu = ''
1287                 page_files = glob.glob ('%s-page*.png' % base)
1288
1289                 for p in page_files:
1290                         p = p.strip()
1291                         if os.path.isfile (p):
1292                                 sz = size_str (p)
1293                                 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1294                                 page = page % sz
1295                                 menu += '<li><a href="%s">%s</a>' % (p, page) 
1296
1297                 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1298                 for e in ext_order:
1299                         fn =   base +  '.' + e
1300                         print 'checking,' , fn
1301                         if not os.path.isfile (fn):
1302                                 continue
1303
1304                         entry = exts[e] % size_str (fn)
1305
1306                         ## TODO: do something like
1307                         ## this for texinfo/latex as well ?
1308                         
1309                         menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1310
1311
1312                 explanatory_para = """The pictures are 90dpi
1313 anti-aliased snapshots of the printed output, in PNG format. Both  PDF and PS
1314 use scalable fonts and should look OK at any resolution."""
1315                 
1316                 separate_menu =r'''
1317 <title>LilyPond example %s</title>
1318
1319 <h1>%s</h1>
1320 <p><img src="%s">
1321 <p>%s
1322 <p>
1323 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1324                 
1325                 open (base + '.html','w'). write (separate_menu)
1326
1327                 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1328
1329                 return ('ly2dvi', inline_menu)
1330
1331         newchunks = []
1332         for c in chunks:
1333                 if c[0] == 'ly2dvi':
1334                         c = process_ly2dvi_block (c)
1335                 newchunks.append (c)
1336
1337         return newchunks
1338
1339 def compile_all_files (chunks):
1340         global foutn
1341         eps = []
1342         tex = []
1343         png = []
1344
1345         for c in chunks:
1346                 if c[0] != 'lilypond':
1347                         continue
1348
1349                 base  = c[4]
1350                 exts = c[3]
1351                 for e in exts:
1352                         if e == 'eps':
1353                                 eps.append (base)
1354                         elif e == 'tex':
1355                                 #ugh
1356                                 if base + '.ly' not in tex:
1357                                         tex.append (base + '.ly')
1358                         elif e == 'png' and g_do_pictures:
1359                                 png.append (base)
1360         d = os.getcwd ()
1361         if g_outdir:
1362                 os.chdir (g_outdir)
1363         if tex:
1364                 # fixme: be sys-independent.
1365                 def incl_opt (x):
1366                         if g_outdir and x[0] != '/' :
1367                                 x = os.path.join (g_here_dir, x)
1368                         return ' -I %s' % x
1369
1370                 incs = map (incl_opt, include_path)
1371                 lilyopts = string.join (incs)
1372                 if do_deps:
1373                         lilyopts += ' --dependencies'
1374                         if g_outdir:
1375                                 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1376                 lilyopts += ' --header=texidoc'
1377                 texfiles = string.join (tex)
1378                 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1379                                     texfiles))
1380
1381                 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1382                 
1383                 ly.system (cmd, ignore_error = 0, progress_p = 1)
1384
1385                 #
1386                 # Ugh, fixing up dependencies for .tex generation
1387                 #
1388                 if do_deps:
1389                         depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1390                                                         x), tex)
1391
1392                         for i in depfiles:
1393                                 f =open (i)
1394                                 text=f.read ()
1395                                 f.close ()
1396                                 text=re.sub ('\n([^:\n]*):',
1397                                              '\n' + foutn + ':', text)
1398                                 f = open (i, 'w')
1399                                 f.write (text)
1400                                 f.close ()
1401
1402         def to_eps (file):
1403                 cmd = r"latex '\nonstopmode \input %s'" % file
1404                 # Ugh.  (La)TeX writes progress and error messages on stdout
1405                 # Redirect to stderr
1406                 cmd = '(( %s  >&2 ) >&- )' % cmd
1407                 
1408                 ly.system (cmd)
1409                 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1410         map (to_eps, eps)
1411
1412         map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1413         os.chdir (d)
1414
1415
1416 def update_file (body, name):
1417         '''
1418         write the body if it has changed. Return whether BODY has changed.
1419         '''
1420         same = 0
1421         try:
1422                 f = open (name)
1423                 fs = f.read (-1)
1424                 same = (fs == body)
1425         except:
1426                 pass
1427
1428         if not same:
1429                 f = open (name , 'w')
1430                 f.write (body)
1431                 f.close ()
1432
1433         return not same
1434
1435
1436 def write_deps (fn, target, chunks):
1437         global read_files
1438         sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1439         f = open (os.path.join (g_outdir, fn), 'w')
1440         f.write ('%s%s: ' % (g_dep_prefix, target))
1441         for d in read_files:
1442                 f.write ('%s ' %  d)
1443
1444
1445         ## There used to be code to write .tex dependencies, but
1446         ## that is silly: lilypond-book has its own dependency scheme
1447         ## to ensure that all lily-XXX.tex files are there
1448                 
1449
1450         f.write ('\n')
1451         f.close ()
1452         read_files = []
1453
1454 def check_texidoc (chunks):
1455         ## TODO: put file name in front of texidoc. 
1456         ##
1457         n = []
1458         for c in chunks:
1459                 if c[0] == 'lilypond':
1460                         (type, body, opts, todo, basename) = c;
1461                         pathbase = os.path.join (g_outdir, basename)
1462                         if os.path.isfile (pathbase + '.texidoc') \
1463                            and 'notexidoc' not in opts:
1464                                 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1465                 n.append (c)
1466         return n
1467
1468
1469 ## what's this? Docme --hwn
1470 ##
1471 def fix_epswidth (chunks):
1472         newchunks = []
1473         for c in chunks:
1474                 if c[0] != 'lilypond' or 'eps' not in c[2]:
1475                         newchunks.append (c)
1476                         continue
1477
1478                 mag = 1.0
1479                 for o in c[2]:
1480                         m  = re.match ('magnification=([0-9.]+)', o)
1481                         if m:
1482                                 mag = string.atof (m.group (1))
1483
1484                 def replace_eps_dim (match, lmag = mag):
1485                         filename = match.group (1)
1486                         dims = bounding_box_dimensions (filename)
1487
1488                         return '%fpt' % (dims[0] *lmag)
1489
1490                 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1491                 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1492
1493         return newchunks
1494
1495
1496 ##docme: why global?
1497 foutn=""
1498
1499 def do_file (input_filename):
1500         chunks = read_doc_file (input_filename)
1501         chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1502         chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1503         chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1504         chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1505         chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1506         chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1507         chunks = chop_chunks (chunks, 'numcols', do_columns)
1508         chunks = chop_chunks (chunks, 'multicols', do_multicols)
1509         
1510         scan_preamble (chunks)
1511         chunks = process_lilypond_blocks (chunks)
1512         chunks = process_ly2dvi_blocks (chunks)
1513         
1514         # Do It.
1515         global g_run_lilypond
1516         if g_run_lilypond:
1517                 compile_all_files (chunks)
1518                 chunks = fix_epswidth (chunks)
1519
1520
1521         chunks = format_lilypond_output_bodies (chunks)
1522         global format
1523         if format == 'texi':
1524                 chunks = check_texidoc (chunks)
1525
1526
1527         x = 0
1528         chunks = completize_preamble (chunks)
1529
1530         global foutn
1531
1532         if outname:
1533                 my_outname = outname
1534         elif input_filename == '-' or input_filename == "/dev/stdin":
1535                 my_outname = '-'
1536         else:
1537                 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1538         my_depname = my_outname + '.dep'
1539
1540         if my_outname == '-' or my_outname == '/dev/stdout':
1541                 fout = sys.stdout
1542                 foutn = "<stdout>"
1543                 global do_deps
1544                 do_deps = 0
1545         else:
1546                 foutn = os.path.join (g_outdir, my_outname)
1547                 sys.stderr.write ("Writing `%s'\n" % foutn)
1548                 fout = open (foutn, 'w')
1549         for c in chunks:
1550                 fout.write (c[1])
1551         fout.close ()
1552         # should chmod -w
1553
1554         if do_deps:
1555                 write_deps (my_depname, foutn, chunks)
1556
1557 outname = ''
1558 try:
1559         (sh, long) = ly.getopt_args (option_definitions)
1560         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1561         
1562 except getopt.error, msg:
1563         sys.stderr.write ('\n')
1564         ly.error (_ ("getopt says: `%s\'" % s))
1565         sys.stderr.write ('\n')
1566         ly.help ()
1567         ly.exit (2)
1568
1569 do_deps = 0
1570 for opt in options:
1571         o = opt[0]
1572         a = opt[1]
1573
1574         if o == '--include' or o == '-I':
1575                 include_path.append (a)
1576         elif o == '--version' or o == '-v':
1577                 ly.identify (sys.stdout)
1578                 sys.exit (0)
1579         elif o == '--verbose' or o == '-V':
1580                 verbose_p = 1
1581         elif o == '--format' or o == '-f':
1582                 format = a
1583                 if a == 'texi-html':
1584                         format = 'texi'
1585                         g_make_html = 1
1586         elif o == '--outname' or o == '-o':
1587                 if len (files) > 1:
1588                         #HACK
1589                         sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1590                         sys.exit (1)
1591                 outname = a
1592         elif o == '--help' or o == '-h':
1593                 ly.help ()
1594                 sys.exit (0)
1595         elif o == '--no-lily' or o == '-n':
1596                 g_run_lilypond = 0
1597         elif o == '--preview-resolution':
1598                 preview_resolution = string.atoi (a)
1599         elif o == '--dependencies' or o == '-M':
1600                 do_deps = 1
1601         elif o == '--default-music-fontsize':
1602                 default_music_fontsize = string.atoi (a)
1603         elif o == '--default-lilypond-fontsize':
1604                 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1605                 default_music_fontsize = string.atoi (a)
1606         elif o == '--extra-options':
1607                 g_extra_opts = a
1608         elif o == '--force-music-fontsize':
1609                 g_force_music_fontsize = string.atoi (a)
1610         elif o == '--force-lilypond-fontsize':
1611                 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1612                 g_force_music_fontsize = string.atoi (a)
1613         elif o == '--dep-prefix':
1614                 g_dep_prefix = a
1615         elif o == '--no-pictures':
1616                 g_do_pictures = 0
1617         elif o == '--no-music':
1618                 g_do_music = 0
1619         elif o == '--outdir':
1620                 g_outdir = a
1621         elif o == '--warranty' or o == '-w':
1622                 #status = os.system ('lilypond -w')
1623                 if 1 or status:
1624                         ly.warranty ()
1625                 sys.exit (0)
1626
1627 ly.identify (sys.stderr)
1628
1629 if g_outdir:
1630         if os.path.isfile (g_outdir):
1631                 error ("outdir is a file: %s" % g_outdir)
1632         if not os.path.exists (g_outdir):
1633                 os.mkdir (g_outdir)
1634                 
1635 if not files:
1636         ly.help ()
1637         ly.error (_ ("no files specified on command line"))
1638         ly.exit (2)
1639
1640 ly.setup_environment ()
1641
1642
1643 for input_filename in files:
1644         do_file (input_filename)
1645
1646
1647 #
1648 # Petr, ik zou willen dat ik iets zinvoller deed,
1649 # maar wat ik kan ik doen, het verandert toch niets?
1650 #   --hwn 20/aug/99