]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
f6358cc133ca77c64a59393cad5ca4146e4414ef
[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 \include "paper%d.ly"
730 \paper  {
731   %s
732   %s
733   %s
734 }
735 ''' % (optstring, music_size, linewidth, indent, notime) + body
736
737         if orig_name:
738                 body = '\\renameinput \"%s\"\n%s' % (orig_name, body)
739         
740
741         # ughUGH not original options
742         return body
743
744 def scan_html_preamble (chunks):
745         return
746
747 def scan_latex_preamble (chunks):
748         # First we want to scan the \documentclass line
749         # it should be the first non-comment line.
750         # The only thing we really need to know about the \documentclass line
751         # is if there are one or two columns to begin with.
752         idx = 0
753         while 1:
754                 if chunks[idx][0] == 'ignore':
755                         idx = idx + 1
756                         continue
757                 m = get_re ('header').match (chunks[idx][1])
758                 if not m:
759                         error ("Latex documents must start with a \documentclass command")
760                 if m.group (1):
761                         options = re.split (r',\s*', m.group (1)[1:-1])
762                 else:
763                         options = []
764                 if 'twocolumn' in options:
765                         paperguru.m_num_cols = 2
766                 break
767
768
769         # Then we add everything before \begin{document} to
770         # paperguru.m_document_preamble so that we can later write this header
771         # to a temporary file in find_latex_dims() to find textwidth.
772         while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
773                 if chunks[idx] == 'ignore':
774                         idx = idx + 1
775                         continue
776                 paperguru.m_document_preamble.append (chunks[idx][1])
777                 idx = idx + 1
778
779         if len (chunks) == idx:
780                 error ("Didn't find end of preamble (\\begin{document})")
781
782         paperguru.find_latex_dims ()
783
784 def scan_texi_preamble (chunks):
785         # this is not bulletproof..., it checks the first 10 chunks
786         for c in chunks[:10]:
787                 if c[0] == 'input':
788                         for s in ('afourpaper', 'afourwide', 'letterpaper',
789                                   'afourlatex', 'smallbook'):
790                                 if string.find (c[1], "@%s" % s) != -1:
791                                         paperguru.m_papersize = s
792
793
794 def scan_preamble (chunks):
795         global format
796         if format == 'html':
797                 scan_html_preamble (chunks)
798         elif format == 'latex':
799                 scan_latex_preamble (chunks)
800         elif format == 'texi':
801                 scan_texi_preamble (chunks)
802
803
804 def completize_preamble (chunks):
805         global format
806         if format != 'latex':
807                 return chunks
808         pre_b = post_b = graphics_b = None
809         for chunk in chunks:
810                 if chunk[0] == 'preamble-end':
811                         break
812                 if chunk[0] == 'input':
813                         m = get_re ('def-pre-re').search (chunk[1])
814                         if m:
815                                 pre_b = 1
816                 if chunk[0] == 'input':
817                         m = get_re ('def-post-re').search (chunk[1])
818                         if m:
819                                 post_b = 1
820
821                 if chunk[0] == 'input':
822                         m = get_re ('usepackage-graphics').search (chunk[1])
823                         if m:
824                                 graphics_b = 1
825         x = 0
826         while x < len (chunks) and   chunks[x][0] != 'preamble-end':
827                 x = x + 1
828
829         if x == len (chunks):
830                 return chunks
831
832         if not pre_b:
833                 chunks.insert (x, ('input', get_output ('output-default-pre')))
834         if not post_b:
835                 chunks.insert (x, ('input', get_output ('output-default-post')))
836         if not graphics_b:
837                 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
838
839         return chunks
840
841
842 read_files = []
843 def find_file (name):
844         '''
845         Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
846         '''
847
848         if name == '-':
849                 return (sys.stdin.read (), '<stdin>')
850         f = None
851         nm = ''
852         for a in include_path:
853                 try:
854                         nm = os.path.join (a, name)
855                         f = open (nm)
856                         global read_files
857                         read_files.append (nm)
858                         break
859                 except IOError:
860                         pass
861         if f:
862                 sys.stderr.write ("Reading `%s'\n" % nm)
863                 return (f.read (), nm)
864         else:
865                 error ("File not found `%s'\n" % name)
866                 return ('', '')
867
868 def do_ignore (match_object):
869         return [('ignore', match_object.group ('code'))]
870 def do_preamble_end (match_object):
871         return [('preamble-end', match_object.group ('code'))]
872
873 def make_verbatim (match_object):
874         return [('verbatim', match_object.group ('code'))]
875
876 def make_verb (match_object):
877         return [('verb', match_object.group ('code'))]
878
879 def do_include_file (m):
880         "m: MatchObject"
881         return [('input', get_output ('pagebreak'))] \
882              + read_doc_file (m.group ('filename')) \
883              + [('input', get_output ('pagebreak'))]
884
885 def do_input_file (m):
886         return read_doc_file (m.group ('filename'))
887
888 def make_lilypond (m):
889         if m.group ('options'):
890                 options = m.group ('options')
891         else:
892                 options = ''
893         return [('input', get_output ('output-lilypond-fragment') %
894                         (options, m.group ('code')))]
895
896 def make_lilypond_file (m):
897         '''
898
899         Find @lilypondfile{bla.ly} occurences and substitute bla.ly
900         into a @lilypond .. @end lilypond block.
901
902         '''
903
904         if m.group ('options'):
905                 options = get_re ('option-sep').split (m.group ('options'))
906         else:
907                 options = []
908         (content, nm) = find_file (m.group ('filename'))
909         options.append ("filename=%s" % nm)
910         (path, base) = os.path.split (nm)
911         
912         if path not in include_path:
913                 include_path.append (path)
914
915         return [('lilypond', content, options)]
916         
917
918 def make_ly2dvi_block (m):
919         '''
920
921         Find <ly2dvifile .. >
922         '''
923
924         return [('ly2dvi', m.group ('filename'), m.group ('options'))]
925
926
927 def make_lilypond_block (m):
928         if not g_do_music:
929                 return []
930
931         if m.group ('options'):
932                 options = get_re ('option-sep').split (m.group ('options'))
933         else:
934                 options = []
935         options = filter (lambda s: s != '', options)
936         return [('lilypond', m.group ('code'), options)]
937
938
939 def do_columns (m):
940         global format
941         if format != 'latex':
942                 return []
943         if m.group ('num') == 'one':
944                 return [('numcols', m.group ('code'), 1)]
945         if m.group ('num') == 'two':
946                 return [('numcols', m.group ('code'), 2)]
947
948 def do_multicols (m):
949         global format
950         if format != 'latex':
951                 return []
952         if m.group ('be') == 'begin':
953                 return [('multicols', m.group ('code'), int (m.group ('num')))]
954         else:
955                 return [('multicols', m.group ('code'), 1)]
956         return []
957
958 def chop_chunks (chunks, re_name, func, use_match=0):
959         newchunks = []
960         for c in chunks:
961                 if c[0] == 'input':
962                         str = c[1]
963                         while str:
964                                 m = get_re (re_name).search (str)
965                                 if m == None:
966                                         newchunks.append (('input', str))
967                                         str = ''
968                                 else:
969                                         if use_match:
970                                                 newchunks.append (('input', str[:m.start ('match')]))
971                                         else:
972                                                 newchunks.append (('input', str[:m.start (0)]))
973                                         #newchunks.extend (func (m))
974                                         # python 1.5 compatible:
975                                         newchunks = newchunks + func (m)
976                                         str = str [m.end (0):]
977                 else:
978                         newchunks.append (c)
979         return newchunks
980
981 def determine_format (str):
982         """
983
984         SIDE EFFECT! This sets FORMAT and PAPERGURU
985
986         """
987         
988         global format
989         if format == '':
990                 html = re.search ('(?i)<[dh]tml', str[:200])
991                 latex = re.search (r'''\\document''', str[:200])
992                 texi = re.search ('@node|@setfilename', str[:200])
993
994                 f = ''
995                 g = None
996
997                 if html and not latex and not texi:
998                         f = 'html'
999                 elif latex and not html and not texi:
1000                         f = 'latex'
1001                 elif texi and not html and not latex:
1002                         f = 'texi'
1003                 else:
1004                         error ("can't determine format, please specify")
1005                 format = f
1006
1007         global paperguru
1008         if paperguru == None:
1009                 if format == 'html':
1010                         g = HtmlPaper ()
1011                 elif format == 'latex':
1012                         g = LatexPaper ()
1013                 elif format == 'texi':
1014                         g = TexiPaper ()
1015
1016                 paperguru = g
1017
1018
1019 def read_doc_file (filename):
1020         '''Read the input file, find verbatim chunks and do \input and \include
1021         '''
1022         (str, path) = find_file (filename)
1023         determine_format (str)
1024
1025         chunks = [('input', str)]
1026
1027         # we have to check for verbatim before doing include,
1028         # because we don't want to include files that are mentioned
1029         # inside a verbatim environment
1030         chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1031
1032         chunks = chop_chunks (chunks, 'verb', make_verb)
1033         chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1034         #ugh fix input
1035         chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1036         chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1037         return chunks
1038
1039
1040 taken_file_names = {}
1041
1042 def unique_file_name (body):
1043         return 'lily-' + `abs (hash (body))`
1044
1045 def schedule_lilypond_block (chunk):
1046         '''Take the body and options from CHUNK, figure out how the
1047         real .ly should look.  The .ly is written, and scheduled in
1048         TODO.
1049
1050         Return: a single chunk.
1051
1052         The chunk pertaining to the lilypond output
1053         has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE), 
1054         where TODO has format [basename, extension, extension, ... ]
1055         '''
1056
1057         (type, body, opts) = chunk
1058         assert type == 'lilypond'
1059         file_body = compose_full_body (body, opts)
1060         ## Hmm, we should hash only lilypond source, and skip the
1061         ## %options are ...
1062         ## comment line
1063         basename = unique_file_name (file_body)
1064         for o in opts:
1065                 m = re.search ('filename="(.*?)"', o)
1066                 if m:
1067                         basename = m.group (1)
1068                         if not taken_file_names.has_key (basename):
1069                                 taken_file_names[basename] = 0
1070                         else:
1071                                 taken_file_names[basename] = taken_file_names[basename] + 1
1072                                 basename = basename + "-t%i" % taken_file_names[basename]
1073         update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1074         needed_filetypes = ['tex']
1075
1076         if format == 'html' or g_make_html:
1077                 needed_filetypes.append ('eps')
1078                 needed_filetypes.append ('png')
1079         if 'eps' in opts and not ('eps' in needed_filetypes):
1080                 needed_filetypes.append ('eps')
1081
1082         pathbase = os.path.join (g_outdir, basename)
1083         def must_rebuild (base, ext1, ext2):
1084                 
1085                 f2 = base + ext2
1086                 f1 = base + ext1
1087                 fp2 = base + '-page1' + ext2
1088
1089                 isfile2 = os.path.isfile (f2)
1090                 
1091                 if not isfile2 and os.path.isfile (fp2):
1092                         f2  = fp2
1093                         isfile2 = os.path.isfile (fp2)
1094                         
1095                 if (os.path.isfile (f2) and isfile2 and
1096                     os.stat (f1)[stat.ST_MTIME] >
1097                     os.stat (f2)[stat.ST_MTIME]) or \
1098                     not isfile2:
1099                 
1100                         return 1
1101                 
1102         todo = []
1103         if 'tex' in needed_filetypes and must_rebuild (pathbase, '.ly', '.tex'):
1104                 todo.append ('tex')
1105         if 'eps' in needed_filetypes and must_rebuild (pathbase, '.tex', '.eps'):
1106                 todo.append ('eps')
1107         if 'png' in needed_filetypes and must_rebuild (pathbase, '.eps', '.png'):
1108                 todo.append ('png')
1109
1110         return ('lilypond', body, opts, todo, basename)
1111
1112 def format_lilypond_block (chunk):
1113         """
1114         
1115         Figure out  what should be left MAIN_STR (meant
1116         for the main file) from a lilypond chunk: process
1117         verbatim, and other options. Return: multiple chunks.
1118
1119         
1120         """
1121
1122         
1123         return_chunks = []
1124
1125         (type, body, opts, todo, basename) = chunk
1126         assert type == 'lilypond'
1127
1128
1129         newbody = ''
1130         filename_chunk = None 
1131         if 'printfilename' in opts:
1132                 for o in opts:
1133                         m= re.match ("filename=(.*)", o)
1134                         if m:
1135                                 template = get_output ("output-filename")
1136                                 b =  basename + '.ly'
1137                                 human_base = os.path.basename (m.group (1))
1138                                                   
1139                                 ## todo: include path, but strip 
1140                                 ## first part of the path.
1141                                 filename_chunk = ('input',  template % (human_base, b,human_base))
1142                                 break
1143
1144
1145         if 'smallverbatim' in opts:
1146                 newbody += output_verbatim (body, 1)
1147         elif 'verbatim' in opts:
1148                 newbody += output_verbatim (body, 0)
1149
1150         for o in opts:
1151                 m = re.search ('intertext="(.*?)"', o)
1152                 if m:
1153                         newbody = newbody + "\n"
1154                         if format == 'texi':
1155                                 newbody = newbody + "@noindent\n"
1156                         elif format == 'latex':
1157                                 newbody = newbody + "\\noindent\n"
1158                         newbody = newbody + m.group (1) + "\n"
1159
1160         if 'noinline' in opts:
1161                 s = 'output-noinline'
1162         elif format == 'latex':
1163                 if 'quote' in opts:
1164                         s = 'output-latex-quoted'
1165                 elif 'eps' in opts:
1166                         s = 'output-eps'
1167                 else:
1168                         s = 'output-latex-noquote'
1169         elif format == 'texi':
1170                 if 'quote' in opts:
1171                         s = 'output-texi-quoted'
1172                 else:
1173                         s = 'output-texi-noquote'
1174         else: # format == 'html'
1175                 s = 'output-html'
1176
1177         def html_pages (basename):
1178                 pat = os.path.join (g_outdir, "%s-page*.png"%  basename)
1179                 
1180                 files =  glob.glob (pat)
1181                 
1182                 
1183                 template = '''<img align="center" valign="center"
1184                 border="0" src="%s" alt="[picture of music]">'''
1185
1186                 str = ''
1187                 if  files == []:
1188                         files = [basename+'.png' ]
1189                 else:
1190                         files = map (os.path.basename, files)
1191                         
1192                 for f in  files:
1193                         str += template % f
1194
1195                 str = '<a href="%s.ly">%s</a>' % (basename, str)
1196
1197                 return str
1198
1199         
1200         newbody = newbody + get_output (s) % {'fn': basename,
1201                                               'htmlimages': html_pages(basename)
1202                                               }
1203
1204         if filename_chunk:
1205                 return_chunks += [filename_chunk]
1206         
1207         return_chunks += [('lilypond', newbody, opts, todo, basename)]
1208         
1209         return return_chunks
1210
1211 def format_lilypond_output_bodies (chunks):
1212         newchunks = []
1213         for c in chunks:
1214
1215                 if c[0] == 'lilypond':
1216                         newchunks += format_lilypond_block (c)
1217                 else:
1218                         newchunks.append (c)
1219
1220         return newchunks
1221
1222
1223
1224 def process_lilypond_blocks (chunks):#ugh rename
1225         newchunks = []
1226         # Count sections/chapters.
1227         for c in chunks:
1228                 if c[0] == 'lilypond':
1229                         c = schedule_lilypond_block (c)
1230                 elif c[0] == 'numcols':
1231                         paperguru.m_num_cols = c[2]
1232                 elif c[0] == 'multicols':
1233                         paperguru.m_multicols = c[2]
1234                         
1235                 newchunks.append (c)
1236                 
1237         return newchunks
1238
1239 def process_ly2dvi_blocks (chunks):
1240         
1241         def process_ly2dvi_block (chunk):
1242                 """
1243
1244 Run ly2dvi script on filename specified in CHUNK.
1245 This is only supported for HTML output.
1246
1247 In HTML output it will leave a download menu with ps/pdf/midi etc.  in
1248 a separate HTML file, and a title + preview in the main html file,
1249 linking to the menu.
1250
1251                 """
1252                 (tag, name, opts) = chunk
1253                 assert format == 'html'
1254                 (content, original_name) = find_file (name)
1255
1256                 original_name = os.path.basename (original_name)
1257                 
1258                 base = unique_file_name (content)
1259                 outname = base + '.ly'
1260                 changed = update_file (content, outname)
1261
1262                 preview = base + ".preview.png"
1263                 preview_page = base + '-page1.png'
1264                 
1265                 if changed or not (os.path.isfile (preview) or
1266                                    os.path.isfile (preview_page)):
1267                         
1268                         ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) ) 
1269
1270                         ly.make_ps_images (base + '.ps')
1271                         ly.system ('gzip -9 - < %s.ps > %s.ps.gz' %  (base, base))
1272                         
1273                 def size_str (fn):
1274                         b = os.stat(fn)[stat.ST_SIZE]
1275                         if b < 1024:
1276                                 return '%d bytes' % b
1277                         elif b < (2 << 20):
1278                                 return '%d kb' % (b >> 10)
1279                         else:
1280                                 return '%d mb' % (b >> 20)
1281
1282                 exts = {
1283                         'pdf' : "Print (PDF, %s)",
1284                         'ps.gz' : "Print (gzipped PostScript, %s)",
1285                         'png' : "View (PNG, %s)",
1286                         'midi' : "Listen (MIDI, %s)",
1287                         'ly' : "View source code (%s)", 
1288                         }
1289
1290                 menu = ''
1291                 page_files = glob.glob ('%s-page*.png' % base)
1292
1293                 for p in page_files:
1294                         p = p.strip()
1295                         if os.path.isfile (p):
1296                                 sz = size_str (p)
1297                                 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1298                                 page = page % sz
1299                                 menu += '<li><a href="%s">%s</a>' % (p, page) 
1300
1301                 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1302                 for e in ext_order:
1303                         fn =   base +  '.' + e
1304                         print 'checking,' , fn
1305                         if not os.path.isfile (fn):
1306                                 continue
1307
1308                         entry = exts[e] % size_str (fn)
1309
1310                         ## TODO: do something like
1311                         ## this for texinfo/latex as well ?
1312                         
1313                         menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1314
1315
1316                 explanatory_para = """The pictures are 90dpi
1317 anti-aliased snapshots of the printed output, in PNG format. Both  PDF and PS
1318 use scalable fonts and should look OK at any resolution."""
1319                 
1320                 separate_menu =r'''
1321 <title>LilyPond example %s</title>
1322
1323 <h1>%s</h1>
1324 <p><img src="%s">
1325 <p>%s
1326 <p>
1327 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1328                 
1329                 open (base + '.html','w'). write (separate_menu)
1330
1331                 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1332
1333                 return ('ly2dvi', inline_menu)
1334
1335         newchunks = []
1336         for c in chunks:
1337                 if c[0] == 'ly2dvi':
1338                         c = process_ly2dvi_block (c)
1339                 newchunks.append (c)
1340
1341         return newchunks
1342
1343 def compile_all_files (chunks):
1344         global foutn
1345         eps = []
1346         tex = []
1347         png = []
1348
1349         for c in chunks:
1350                 if c[0] != 'lilypond':
1351                         continue
1352
1353                 base  = c[4]
1354                 exts = c[3]
1355                 for e in exts:
1356                         if e == 'eps':
1357                                 eps.append (base)
1358                         elif e == 'tex':
1359                                 #ugh
1360                                 if base + '.ly' not in tex:
1361                                         tex.append (base + '.ly')
1362                         elif e == 'png' and g_do_pictures:
1363                                 png.append (base)
1364         d = os.getcwd ()
1365         if g_outdir:
1366                 os.chdir (g_outdir)
1367         if tex:
1368                 # fixme: be sys-independent.
1369                 def incl_opt (x):
1370                         if g_outdir and x[0] != '/' :
1371                                 x = os.path.join (g_here_dir, x)
1372                         return ' -I %s' % x
1373
1374                 incs = map (incl_opt, include_path)
1375                 lilyopts = string.join (incs)
1376                 if do_deps:
1377                         lilyopts += ' --dependencies'
1378                         if g_outdir:
1379                                 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1380                 lilyopts += ' --header=texidoc'
1381                 texfiles = string.join (tex)
1382                 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1383                                     texfiles))
1384
1385                 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1386                 
1387                 ly.system (cmd, ignore_error = 0, progress_p = 1)
1388
1389                 #
1390                 # Ugh, fixing up dependencies for .tex generation
1391                 #
1392                 if do_deps:
1393                         depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1394                                                         x), tex)
1395
1396                         for i in depfiles:
1397                                 f =open (i)
1398                                 text=f.read ()
1399                                 f.close ()
1400                                 text=re.sub ('\n([^:\n]*):',
1401                                              '\n' + foutn + ':', text)
1402                                 f = open (i, 'w')
1403                                 f.write (text)
1404                                 f.close ()
1405
1406         def to_eps (file):
1407                 cmd = r"latex '\nonstopmode \input %s'" % file
1408                 # Ugh.  (La)TeX writes progress and error messages on stdout
1409                 # Redirect to stderr
1410                 cmd = '(( %s  >&2 ) >&- )' % cmd
1411                 
1412                 ly.system (cmd)
1413                 ly.system ("dvips -Ppdf -u+lilypond.map -E -o %s.eps %s" % (file, file))
1414         map (to_eps, eps)
1415
1416         map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1417         os.chdir (d)
1418
1419
1420 def update_file (body, name):
1421         '''
1422         write the body if it has changed. Return whether BODY has changed.
1423         '''
1424         same = 0
1425         try:
1426                 f = open (name)
1427                 fs = f.read (-1)
1428                 same = (fs == body)
1429         except:
1430                 pass
1431
1432         if not same:
1433                 f = open (name , 'w')
1434                 f.write (body)
1435                 f.close ()
1436
1437         return not same
1438
1439
1440 def write_deps (fn, target, chunks):
1441         global read_files
1442         sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1443         f = open (os.path.join (g_outdir, fn), 'w')
1444         f.write ('%s%s: ' % (g_dep_prefix, target))
1445         for d in read_files:
1446                 f.write ('%s ' %  d)
1447
1448
1449         ## There used to be code to write .tex dependencies, but
1450         ## that is silly: lilypond-book has its own dependency scheme
1451         ## to ensure that all lily-XXX.tex files are there
1452                 
1453
1454         f.write ('\n')
1455         f.close ()
1456         read_files = []
1457
1458 def check_texidoc (chunks):
1459         ## TODO: put file name in front of texidoc. 
1460         ##
1461         n = []
1462         for c in chunks:
1463                 if c[0] == 'lilypond':
1464                         (type, body, opts, todo, basename) = c;
1465                         pathbase = os.path.join (g_outdir, basename)
1466                         if os.path.isfile (pathbase + '.texidoc') \
1467                            and 'notexidoc' not in opts:
1468                                 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1469                 n.append (c)
1470         return n
1471
1472
1473 ## what's this? Docme --hwn
1474 ##
1475 def fix_epswidth (chunks):
1476         newchunks = []
1477         for c in chunks:
1478                 if c[0] != 'lilypond' or 'eps' not in c[2]:
1479                         newchunks.append (c)
1480                         continue
1481
1482                 mag = 1.0
1483                 for o in c[2]:
1484                         m  = re.match ('magnification=([0-9.]+)', o)
1485                         if m:
1486                                 mag = string.atof (m.group (1))
1487
1488                 def replace_eps_dim (match, lmag = mag):
1489                         filename = match.group (1)
1490                         dims = bounding_box_dimensions (filename)
1491
1492                         return '%fpt' % (dims[0] *lmag)
1493
1494                 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1495                 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1496
1497         return newchunks
1498
1499
1500 ##docme: why global?
1501 foutn=""
1502
1503 def do_file (input_filename):
1504         chunks = read_doc_file (input_filename)
1505         chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1506         chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1507         chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1508         chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1509         chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1510         chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1511         chunks = chop_chunks (chunks, 'numcols', do_columns)
1512         chunks = chop_chunks (chunks, 'multicols', do_multicols)
1513         
1514         scan_preamble (chunks)
1515         chunks = process_lilypond_blocks (chunks)
1516         chunks = process_ly2dvi_blocks (chunks)
1517         
1518         # Do It.
1519         global g_run_lilypond
1520         if g_run_lilypond:
1521                 compile_all_files (chunks)
1522                 chunks = fix_epswidth (chunks)
1523
1524
1525         chunks = format_lilypond_output_bodies (chunks)
1526         global format
1527         if format == 'texi':
1528                 chunks = check_texidoc (chunks)
1529
1530
1531         x = 0
1532         chunks = completize_preamble (chunks)
1533
1534         global foutn
1535
1536         if outname:
1537                 my_outname = outname
1538         elif input_filename == '-' or input_filename == "/dev/stdin":
1539                 my_outname = '-'
1540         else:
1541                 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1542         my_depname = my_outname + '.dep'
1543
1544         if my_outname == '-' or my_outname == '/dev/stdout':
1545                 fout = sys.stdout
1546                 foutn = "<stdout>"
1547                 global do_deps
1548                 do_deps = 0
1549         else:
1550                 foutn = os.path.join (g_outdir, my_outname)
1551                 sys.stderr.write ("Writing `%s'\n" % foutn)
1552                 fout = open (foutn, 'w')
1553         for c in chunks:
1554                 fout.write (c[1])
1555         fout.close ()
1556         # should chmod -w
1557
1558         if do_deps:
1559                 write_deps (my_depname, foutn, chunks)
1560
1561 outname = ''
1562 try:
1563         (sh, long) = ly.getopt_args (option_definitions)
1564         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1565         
1566 except getopt.error, msg:
1567         sys.stderr.write ('\n')
1568         ly.error (_ ("getopt says: `%s\'" % s))
1569         sys.stderr.write ('\n')
1570         ly.help ()
1571         ly.exit (2)
1572
1573 do_deps = 0
1574 for opt in options:
1575         o = opt[0]
1576         a = opt[1]
1577
1578         if o == '--include' or o == '-I':
1579                 include_path.append (a)
1580         elif o == '--version' or o == '-v':
1581                 ly.identify (sys.stdout)
1582                 sys.exit (0)
1583         elif o == '--verbose' or o == '-V':
1584                 verbose_p = 1
1585         elif o == '--format' or o == '-f':
1586                 format = a
1587                 if a == 'texi-html':
1588                         format = 'texi'
1589                         g_make_html = 1
1590         elif o == '--outname' or o == '-o':
1591                 if len (files) > 1:
1592                         #HACK
1593                         sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1594                         sys.exit (1)
1595                 outname = a
1596         elif o == '--help' or o == '-h':
1597                 ly.help ()
1598                 sys.exit (0)
1599         elif o == '--no-lily' or o == '-n':
1600                 g_run_lilypond = 0
1601         elif o == '--preview-resolution':
1602                 preview_resolution = string.atoi (a)
1603         elif o == '--dependencies' or o == '-M':
1604                 do_deps = 1
1605         elif o == '--default-music-fontsize':
1606                 default_music_fontsize = string.atoi (a)
1607         elif o == '--default-lilypond-fontsize':
1608                 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1609                 default_music_fontsize = string.atoi (a)
1610         elif o == '--extra-options':
1611                 g_extra_opts = a
1612         elif o == '--force-music-fontsize':
1613                 g_force_music_fontsize = string.atoi (a)
1614         elif o == '--force-lilypond-fontsize':
1615                 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1616                 g_force_music_fontsize = string.atoi (a)
1617         elif o == '--dep-prefix':
1618                 g_dep_prefix = a
1619         elif o == '--no-pictures':
1620                 g_do_pictures = 0
1621         elif o == '--no-music':
1622                 g_do_music = 0
1623         elif o == '--outdir':
1624                 g_outdir = a
1625         elif o == '--warranty' or o == '-w':
1626                 #status = os.system ('lilypond -w')
1627                 if 1 or status:
1628                         ly.warranty ()
1629                 sys.exit (0)
1630
1631 ly.identify (sys.stderr)
1632
1633 if g_outdir:
1634         if os.path.isfile (g_outdir):
1635                 error ("outdir is a file: %s" % g_outdir)
1636         if not os.path.exists (g_outdir):
1637                 os.mkdir (g_outdir)
1638                 
1639 if not files:
1640         ly.help ()
1641         ly.error (_ ("no files specified on command line"))
1642         ly.exit (2)
1643
1644 ly.setup_environment ()
1645
1646
1647 for input_filename in files:
1648         do_file (input_filename)
1649
1650
1651 #
1652 # Petr, ik zou willen dat ik iets zinvoller deed,
1653 # maar wat ik kan ik doen, het verandert toch niets?
1654 #   --hwn 20/aug/99