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