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