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