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