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