]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
316694e9096d752ea564da159de496fffe4a673b
[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                 print 'hai', str
1180                 
1181                 return str
1182
1183         
1184         newbody = newbody + get_output (s) % {'fn': basename,
1185                                               'htmlimages': html_pages(basename)
1186                                               }
1187
1188         if filename_chunk:
1189                 return_chunks += [filename_chunk]
1190         
1191         return_chunks += [('lilypond', newbody, opts, todo, basename)]
1192         
1193         return return_chunks
1194
1195 def format_lilypond_output_bodies (chunks):
1196         newchunks = []
1197         for c in chunks:
1198
1199                 if c[0] == 'lilypond':
1200                         newchunks += format_lilypond_block (c)
1201                 else:
1202                         newchunks.append (c)
1203
1204         return newchunks
1205
1206
1207
1208 def process_lilypond_blocks (chunks):#ugh rename
1209         newchunks = []
1210         # Count sections/chapters.
1211         for c in chunks:
1212                 if c[0] == 'lilypond':
1213                         c = schedule_lilypond_block (c)
1214                 elif c[0] == 'numcols':
1215                         paperguru.m_num_cols = c[2]
1216                 elif c[0] == 'multicols':
1217                         paperguru.m_multicols = c[2]
1218                         
1219                 newchunks.append (c)
1220                 
1221         return newchunks
1222
1223 def process_ly2dvi_blocks (chunks):
1224         
1225         def process_ly2dvi_block (chunk):
1226                 """
1227
1228 Run ly2dvi script on filename specified in CHUNK.
1229 This is only supported for HTML output.
1230
1231 In HTML output it will leave a download menu with ps/pdf/midi etc.  in
1232 a separate HTML file, and a title + preview in the main html file,
1233 linking to the menu.
1234
1235                 """
1236                 (tag, name, opts) = chunk
1237                 assert format == 'html'
1238                 (content, original_name) = find_file (name)
1239
1240                 original_name = os.path.basename (original_name)
1241                 
1242                 base = unique_file_name (content)
1243                 outname = base + '.ly'
1244                 changed = update_file (content, outname)
1245
1246                 preview = base + ".png"
1247                 preview_page = base + '-page1.png'
1248                 
1249                 if changed or not (os.path.isfile (preview) or
1250                                    os.path.isfile (preview_page)):
1251                         
1252                         ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) ) 
1253
1254                         ly.make_ps_images (base)
1255                         ly.system ('gzip -9 - < %s.ps > %s.ps.gz' %  (base, base))
1256                         
1257                 def size_str (fn):
1258                         b = os.stat(fn)[stat.ST_SIZE]
1259                         if b < 1024:
1260                                 return '%d bytes' % b
1261                         elif b < (2 << 20):
1262                                 return '%d kb' % (b >> 10)
1263                         else:
1264                                 return '%d mb' % (b >> 20)
1265
1266                 exts = {
1267                         'pdf' : "Print (PDF, %s)",
1268                         'ps.gz' : "Print (gzipped PostScript, %s)",
1269                         'png' : "View (PNG, %s)",
1270                         'midi' : "Listen (MIDI, %s)",
1271                         'ly' : "View source code (%s)", 
1272                         }
1273
1274                 menu = ''
1275                 page_files = glob.glob ('%s-page*.png' % base)
1276
1277                 for p in string.split (page_files, '\n'):
1278                         p = p.strip()
1279                         if os.path.isfile (p):
1280                                 sz = size_str (p)
1281                                 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1282                                 page = page % sz
1283                                 menu += '<li><a href="%s">%s</a>' % (p, page) 
1284
1285                 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1286                 for e in ext_order:
1287                         fn =   base +  '.' + e
1288                         print 'checking,' , fn
1289                         if not os.path.isfile (fn):
1290                                 continue
1291
1292                         entry = exts[e] % size_str (fn)
1293
1294                         ## TODO: do something like
1295                         ## this for texinfo/latex as well ?
1296                         
1297                         menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1298
1299
1300                 explanatory_para = """The pictures are 90dpi
1301 anti-aliased snapshots of the printed output, in PNG format. Both  PDF and PS
1302 use scalable fonts and should look OK at any resolution."""
1303                 
1304                 separate_menu =r'''
1305 <title>LilyPond example %s</title>
1306
1307 <h1>%s</h1>
1308 <p><img src="%s">
1309 <p>%s
1310 <p>
1311 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1312                 
1313                 open (base + '.html','w'). write (separate_menu)
1314
1315                 inline_menu = '<p/><a href="%s.html"><img src="%s"><p/></a>' % (base, original_name, preview)
1316
1317                 return ('ly2dvi', inline_menu)
1318
1319         newchunks = []
1320         for c in chunks:
1321                 if c[0] == 'ly2dvi':
1322                         c = process_ly2dvi_block (c)
1323                 newchunks.append (c)
1324
1325         return newchunks
1326
1327 def compile_all_files (chunks):
1328         global foutn
1329         eps = []
1330         tex = []
1331         png = []
1332
1333         for c in chunks:
1334                 if c[0] != 'lilypond':
1335                         continue
1336
1337                 base  = c[4]
1338                 exts = c[3]
1339                 for e in exts:
1340                         if e == 'eps':
1341                                 eps.append (base)
1342                         elif e == 'tex':
1343                                 #ugh
1344                                 if base + '.ly' not in tex:
1345                                         tex.append (base + '.ly')
1346                         elif e == 'png' and g_do_pictures:
1347                                 png.append (base)
1348         d = os.getcwd ()
1349         if g_outdir:
1350                 os.chdir (g_outdir)
1351         if tex:
1352                 # fixme: be sys-independent.
1353                 def incl_opt (x):
1354                         if g_outdir and x[0] != '/' :
1355                                 x = os.path.join (g_here_dir, x)
1356                         return ' -I %s' % x
1357
1358                 incs = map (incl_opt, include_path)
1359                 lilyopts = string.join (incs)
1360                 if do_deps:
1361                         lilyopts += ' --dependencies'
1362                         if g_outdir:
1363                                 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1364                 lilyopts += ' --header=texidoc'
1365                 texfiles = string.join (tex)
1366                 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1367                                     texfiles))
1368                 ly.system (cmd, ignore_error = 0, progress_p = 1)
1369
1370                 #
1371                 # Ugh, fixing up dependencies for .tex generation
1372                 #
1373                 if do_deps:
1374                         depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1375                                                         x), tex)
1376
1377                         for i in depfiles:
1378                                 f =open (i)
1379                                 text=f.read ()
1380                                 f.close ()
1381                                 text=re.sub ('\n([^:\n]*):',
1382                                              '\n' + foutn + ':', text)
1383                                 f = open (i, 'w')
1384                                 f.write (text)
1385                                 f.close ()
1386
1387         def to_eps (file):
1388                 cmd = r"latex '\nonstopmode \input %s'" % file
1389                 # Ugh.  (La)TeX writes progress and error messages on stdout
1390                 # Redirect to stderr
1391                 cmd += ' 1>/dev/stderr'
1392                 ly.system (cmd)
1393                 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1394         map (to_eps, eps)
1395
1396         map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1397         os.chdir (d)
1398
1399
1400 def update_file (body, name):
1401         '''
1402         write the body if it has changed. Return whether BODY has changed.
1403         '''
1404         same = 0
1405         try:
1406                 f = open (name)
1407                 fs = f.read (-1)
1408                 same = (fs == body)
1409         except:
1410                 pass
1411
1412         if not same:
1413                 f = open (name , 'w')
1414                 f.write (body)
1415                 f.close ()
1416
1417         return not same
1418
1419
1420 def write_deps (fn, target, chunks):
1421         global read_files
1422         sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1423         f = open (os.path.join (g_outdir, fn), 'w')
1424         f.write ('%s%s: ' % (g_dep_prefix, target))
1425         for d in read_files:
1426                 f.write ('%s ' %  d)
1427
1428
1429         ## There used to be code to write .tex dependencies, but
1430         ## that is silly: lilypond-book has its own dependency scheme
1431         ## to ensure that all lily-XXX.tex files are there
1432                 
1433
1434         f.write ('\n')
1435         f.close ()
1436         read_files = []
1437
1438 def check_texidoc (chunks):
1439         ## TODO: put file name in front of texidoc. 
1440         ##
1441         n = []
1442         for c in chunks:
1443                 if c[0] == 'lilypond':
1444                         (type, body, opts, todo, basename) = c;
1445                         pathbase = os.path.join (g_outdir, basename)
1446                         if os.path.isfile (pathbase + '.texidoc') \
1447                            and 'notexidoc' not in opts:
1448                                 n.append( ('input', '\n@include %s.texidoc\n' % basename))
1449                 n.append (c)
1450         return n
1451
1452
1453 ## what's this? Docme --hwn
1454 ##
1455 def fix_epswidth (chunks):
1456         newchunks = []
1457         for c in chunks:
1458                 if c[0] != 'lilypond' or 'eps' not in c[2]:
1459                         newchunks.append (c)
1460                         continue
1461
1462                 mag = 1.0
1463                 for o in c[2]:
1464                         m  = re.match ('magnification=([0-9.]+)', o)
1465                         if m:
1466                                 mag = string.atof (m.group (1))
1467
1468                 def replace_eps_dim (match, lmag = mag):
1469                         filename = match.group (1)
1470                         dims = bounding_box_dimensions (filename)
1471
1472                         return '%fpt' % (dims[0] *lmag)
1473
1474                 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1475                 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1476
1477         return newchunks
1478
1479
1480 ##docme: why global?
1481 foutn=""
1482
1483 def do_file (input_filename):
1484         chunks = read_doc_file (input_filename)
1485         chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1486         chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1487         chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1488         chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1489         chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1490         chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1491         chunks = chop_chunks (chunks, 'numcols', do_columns)
1492         chunks = chop_chunks (chunks, 'multicols', do_multicols)
1493         
1494         scan_preamble (chunks)
1495         chunks = process_lilypond_blocks (chunks)
1496         chunks = process_ly2dvi_blocks (chunks)
1497         
1498         # Do It.
1499         global g_run_lilypond
1500         if g_run_lilypond:
1501                 compile_all_files (chunks)
1502                 chunks = fix_epswidth (chunks)
1503
1504
1505         chunks = format_lilypond_output_bodies (chunks)
1506         global format
1507         if format == 'texi':
1508                 chunks = check_texidoc (chunks)
1509
1510
1511         x = 0
1512         chunks = completize_preamble (chunks)
1513
1514         global foutn
1515
1516         if outname:
1517                 my_outname = outname
1518         elif input_filename == '-' or input_filename == "/dev/stdin":
1519                 my_outname = '-'
1520         else:
1521                 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1522         my_depname = my_outname + '.dep'
1523
1524         if my_outname == '-' or my_outname == '/dev/stdout':
1525                 fout = sys.stdout
1526                 foutn = "<stdout>"
1527                 global do_deps
1528                 do_deps = 0
1529         else:
1530                 foutn = os.path.join (g_outdir, my_outname)
1531                 sys.stderr.write ("Writing `%s'\n" % foutn)
1532                 fout = open (foutn, 'w')
1533         for c in chunks:
1534                 fout.write (c[1])
1535         fout.close ()
1536         # should chmod -w
1537
1538         if do_deps:
1539                 write_deps (my_depname, foutn, chunks)
1540
1541 outname = ''
1542 try:
1543         (sh, long) = ly.getopt_args (option_definitions)
1544         (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1545         
1546 except getopt.error, msg:
1547         sys.stderr.write ('\n')
1548         ly.error (_ ("getopt says: `%s\'" % s))
1549         sys.stderr.write ('\n')
1550         ly.help ()
1551         ly.exit (2)
1552
1553 do_deps = 0
1554 for opt in options:
1555         o = opt[0]
1556         a = opt[1]
1557
1558         if o == '--include' or o == '-I':
1559                 include_path.append (a)
1560         elif o == '--version' or o == '-v':
1561                 ly.identify (sys.stdout)
1562                 sys.exit (0)
1563         elif o == '--verbose' or o == '-V':
1564                 verbose_p = 1
1565         elif o == '--format' or o == '-f':
1566                 format = a
1567                 if a == 'texi-html':
1568                         format = 'texi'
1569                         g_make_html = 1
1570         elif o == '--outname' or o == '-o':
1571                 if len (files) > 1:
1572                         #HACK
1573                         sys.stderr.write ("Lilypond-book is confused by --outname on multiple files")
1574                         sys.exit (1)
1575                 outname = a
1576         elif o == '--help' or o == '-h':
1577                 ly.help ()
1578                 sys.exit (0)
1579         elif o == '--no-lily' or o == '-n':
1580                 g_run_lilypond = 0
1581         elif o == '--preview-resolution':
1582                 preview_resolution = string.atoi (a)
1583         elif o == '--dependencies' or o == '-M':
1584                 do_deps = 1
1585         elif o == '--default-music-fontsize':
1586                 default_music_fontsize = string.atoi (a)
1587         elif o == '--default-lilypond-fontsize':
1588                 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1589                 default_music_fontsize = string.atoi (a)
1590         elif o == '--extra-options':
1591                 g_extra_opts = a
1592         elif o == '--force-music-fontsize':
1593                 g_force_music_fontsize = string.atoi (a)
1594         elif o == '--force-lilypond-fontsize':
1595                 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1596                 g_force_music_fontsize = string.atoi (a)
1597         elif o == '--dep-prefix':
1598                 g_dep_prefix = a
1599         elif o == '--no-pictures':
1600                 g_do_pictures = 0
1601         elif o == '--no-music':
1602                 g_do_music = 0
1603         elif o == '--outdir':
1604                 g_outdir = a
1605         elif o == '--warranty' or o == '-w':
1606                 #status = os.system ('lilypond -w')
1607                 if 1 or status:
1608                         ly.warranty ()
1609                 sys.exit (0)
1610
1611 ly.identify (sys.stderr)
1612
1613 if g_outdir:
1614         if os.path.isfile (g_outdir):
1615                 error ("outdir is a file: %s" % g_outdir)
1616         if not os.path.exists (g_outdir):
1617                 os.mkdir (g_outdir)
1618                 
1619 if not files:
1620         ly.help ()
1621         ly.error (_ ("no files specified on command line"))
1622         ly.exit (2)
1623
1624 ly.setup_environment ()
1625
1626
1627 for input_filename in files:
1628         do_file (input_filename)
1629
1630
1631 #
1632 # Petr, ik zou willen dat ik iets zinvoller deed,
1633 # maar wat ik kan ik doen, het verandert toch niets?
1634 #   --hwn 20/aug/99