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