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