]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / scripts / lilypond-book.py
1 #!@TARGET_PYTHON@
2
3 '''
4 Example usage:
5
6 test:
7   lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
8
9 convert-ly on book:
10   lilypond-book --filter="convert-ly --no-version --from=1.6.11 -" BOOK
11
12 classic lilypond-book:
13   lilypond-book --process="lilypond" BOOK.tely
14
15 TODO:
16
17   *  this script is too complex. Modularize.
18   
19   *  ly-options: intertext?
20   *  --line-width?
21   *  eps in latex / eps by lilypond -b ps?
22   *  check latex parameters, twocolumn, multicolumn?
23   *  use --png --ps --pdf for making images?
24
25   *  Converting from lilypond-book source, substitute:
26    @mbinclude foo.itely -> @include foo.itely
27    \mbinput -> \input
28
29 '''
30
31 import stat
32 import string
33 import tempfile
34 import commands
35 import os
36 import sys
37 import re
38 import md5
39
40 ################
41 # RELOCATION
42 ################
43
44
45 for d in ['@lilypond_datadir@',
46           '@lilypond_libdir@']:
47     sys.path.insert (0, os.path.join (d, 'python'))
48
49 # dynamic relocation, for GUB binaries.
50 bindir = os.path.abspath (os.path.split (sys.argv[0])[0])
51
52 os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
53 for p in ['share', 'lib']:
54     datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p)
55     sys.path.insert (0, datadir)
56
57
58 import lilylib as ly
59 import fontextract
60 global _;_=ly._
61
62
63 # Lilylib globals.
64 program_version = '@TOPLEVEL_VERSION@'
65 program_name = os.path.basename (sys.argv[0])
66
67 original_dir = os.getcwd ()
68 backend = 'ps'
69
70 help_summary = (
71 _ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.")
72 + '\n\n'
73 + _ ("Examples:")
74 + '''
75  lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s
76  lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" %(BOOK)s
77  lilypond-book --process='lilypond -I include' %(BOOK)s
78 ''' % {'BOOK': _ ("BOOK")})
79
80 authors = ('Jan Nieuwenhuizen <janneke@gnu.org>',
81       'Han-Wen Nienhuys <hanwen@xs4all.nl>')
82
83 ################################################################
84 def exit (i):
85     if global_options.verbose:
86         raise _ ('Exiting (%d)...') % i
87     else:
88         sys.exit (i)
89
90 def identify ():
91     sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
92
93 def progress (s):
94     sys.stderr.write (s)
95
96 def warning (s):
97     sys.stderr.write (program_name + ": " + _ ("warning: %s") % s + '\n')
98
99 def error (s):
100     sys.stderr.write (program_name + ": " + _ ("error: %s") % s + '\n')
101
102 def ps_page_count (ps_name):
103     header = open (ps_name).read (1024)
104     m = re.search ('\n%%Pages: ([0-9]+)', header)
105     if m:
106         return string.atoi (m.group (1))
107     return 0
108
109 def warranty ():
110     identify ()
111     sys.stdout.write ('''
112 %s
113
114 %s
115
116 %s
117 %s
118 ''' % ( _ ('Copyright (c) %s by') % '2001--2006',
119     ' '.join (authors),
120    _ ("Distributed under terms of the GNU General Public License."),
121    _ ("It comes with NO WARRANTY.")))
122
123 def get_option_parser ():
124     p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'lilypond-book',
125                               version="@TOPLEVEL_VERSION@",
126                               description=help_summary)
127
128     p.add_option ('-F', '--filter', metavar=_ ("FILTER"),
129                   action="store",
130                   dest="filter_cmd",
131                   help=_ ("pipe snippets through FILTER [convert-ly -n -]"),
132                   default=None)
133     p.add_option ('-f', '--format',
134                   help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"),
135                   action='store')
136     
137     p.add_option ("-I", '--include', help=_ ("add DIR to include path"),
138                   metavar=_ ("DIR"),
139                   action='append', dest='include_path',
140                   default=[os.path.abspath (os.getcwd ())])
141
142     p.add_option ('--left-padding', 
143                   metavar=_("PAD"),
144                   dest="padding_mm",
145                   help="Pad left side of music to align music inspite of uneven bar numbers. (in mm)",
146                   type="float",
147                   default=3.0)
148     
149     p.add_option ("-o", '--output', help=_ ("write output to DIR"),
150                   metavar=_ ("DIR"),
151                   action='store', dest='output_name',
152                   default='')
153     
154     p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
155                   help = _ ("process ly_files using COMMAND FILE..."),
156                   action='store', 
157                   dest='process_cmd', default='lilypond -b eps')
158     p.add_option ('--pdf',
159                   action="store_true",
160                   dest="create_pdf",
161                   help=_ ("Create PDF files for use with PDFTeX"),
162                   default=False)
163     p.add_option ('', '--psfonts', action="store_true", dest="psfonts",
164                   help=_ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX
165 must use this with dvips -h INPUT.psfonts'''),
166                   default=None)
167     p.add_option ('-V', '--verbose', help=_ ("be verbose"),
168                   action="store_true",
169                   default=False,
170                   dest="verbose")
171     p.add_option ('-w', '--warranty',
172                   help=_ ("show warranty and copyright"),
173                   action='store_true')
174     p.add_option_group ('bugs',
175                         description=(_ ("Report bugs via")
176                                      + ''' http://post.gmane.org/post.php'''
177                                      '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
178     return p
179
180 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
181
182 # Only use installed binary when we are installed too.
183 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
184     lilypond_binary = 'lilypond'
185
186 global_options = None
187
188
189 default_ly_options = { 'alt': "[image of music]" }
190
191 #
192 # Is this pythonic?  Personally, I find this rather #define-nesque. --hwn
193 #
194 AFTER = 'after'
195 BEFORE = 'before'
196 DOCBOOK = 'docbook'
197 EXAMPLEINDENT = 'exampleindent'
198 FILTER = 'filter'
199 FRAGMENT = 'fragment'
200 HTML = 'html'
201 INDENT = 'indent'
202 LATEX = 'latex'
203 LAYOUT = 'layout'
204 LINE_WIDTH = 'line-width'
205 NOFRAGMENT = 'nofragment'
206 NOINDENT = 'noindent'
207 NOQUOTE = 'noquote'
208 NOTES = 'body'
209 NOTIME = 'notime'
210 OUTPUT = 'output'
211 OUTPUTIMAGE = 'outputimage'
212 PACKED = 'packed'
213 PAPER = 'paper'
214 PREAMBLE = 'preamble'
215 PRINTFILENAME = 'printfilename'
216 QUOTE = 'quote'
217 RAGGED_RIGHT = 'ragged-right'
218 RELATIVE = 'relative'
219 STAFFSIZE = 'staffsize'
220 TEXIDOC = 'texidoc'
221 TEXINFO = 'texinfo'
222 VERBATIM = 'verbatim'
223 FONTLOAD = 'fontload'
224 FILENAME = 'filename'
225 ALT = 'alt'
226
227
228 # NOTIME has no opposite so it isn't part of this dictionary.
229 # NOQUOTE is used internally only.
230 no_options = {
231     NOFRAGMENT: FRAGMENT,
232     NOINDENT: INDENT,
233 }
234
235
236 # Recognize special sequences in the input.
237 #
238 #   (?P<name>regex) -- Assign result of REGEX to NAME.
239 #   *? -- Match non-greedily.
240 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
241 #   (?s) -- Make the dot match all characters including newline.
242 #   (?x) -- Ignore whitespace in patterns.
243 no_match = 'a\ba'
244 snippet_res = {
245  ##
246     DOCBOOK: {
247         'include':
248          no_match,
249
250         'lilypond':
251          r'''(?smx)
252           (?P<match>
253           <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
254
255         'lilypond_block':
256          r'''(?smx)
257           (?P<match>
258           <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
259
260         'lilypond_file':
261          r'''(?smx)
262           (?P<match>
263           <(?P<inline>(inline)?)mediaobject>\s*<imageobject.*?>\s*<imagedata\s+fileref="(?P<filename>.*?\.ly)"\s*(role="(?P<options>.*?)")?\s*(/>|>\s*</imagedata>)\s*</imageobject>\s*</(inline)?mediaobject>)''',
264
265         'multiline_comment':
266          r'''(?smx)
267           (?P<match>
268           \s*(?!@c\s+)
269           (?P<code><!--\s.*?!-->)
270           \s)''',
271
272         'singleline_comment':
273          no_match,
274
275         'verb':
276          no_match,
277
278         'verbatim':
279         no_match,
280         
281     }, 
282     ##
283     HTML: {
284         'include':
285          no_match,
286
287         'lilypond':
288          r'''(?mx)
289           (?P<match>
290           <lilypond
291            (\s*(?P<options>.*?)\s*:)?\s*
292            (?P<code>.*?)
293           />)''',
294
295         'lilypond_block':
296          r'''(?msx)
297           (?P<match>
298           <lilypond
299            \s*(?P<options>.*?)\s*
300           >
301           (?P<code>.*?)
302           </lilypond>)''',
303
304         'lilypond_file':
305          r'''(?mx)
306           (?P<match>
307           <lilypondfile
308            \s*(?P<options>.*?)\s*
309           >
310           \s*(?P<filename>.*?)\s*
311           </lilypondfile>)''',
312
313         'multiline_comment':
314          r'''(?smx)
315           (?P<match>
316           \s*(?!@c\s+)
317           (?P<code><!--\s.*?!-->)
318           \s)''',
319
320         'singleline_comment':
321          no_match,
322
323         'verb':
324          r'''(?x)
325           (?P<match>
326            (?P<code><pre>.*?</pre>))''',
327
328         'verbatim':
329          r'''(?x)
330           (?s)
331           (?P<match>
332            (?P<code><pre>\s.*?</pre>\s))''',
333     },
334
335     ##
336     LATEX: {
337         'include':
338          r'''(?smx)
339           ^[^%\n]*?
340           (?P<match>
341           \\input\s*{
342            (?P<filename>\S+?)
343           })''',
344
345         'lilypond':
346          r'''(?smx)
347           ^[^%\n]*?
348           (?P<match>
349           \\lilypond\s*(
350           \[
351            \s*(?P<options>.*?)\s*
352           \])?\s*{
353            (?P<code>.*?)
354           })''',
355
356         'lilypond_block':
357          r'''(?smx)
358           ^[^%\n]*?
359           (?P<match>
360           \\begin\s*(
361           \[
362            \s*(?P<options>.*?)\s*
363           \])?\s*{lilypond}
364            (?P<code>.*?)
365           ^[^%\n]*?
366           \\end\s*{lilypond})''',
367
368         'lilypond_file':
369          r'''(?smx)
370           ^[^%\n]*?
371           (?P<match>
372           \\lilypondfile\s*(
373           \[
374            \s*(?P<options>.*?)\s*
375           \])?\s*\{
376            (?P<filename>\S+?)
377           })''',
378
379         'multiline_comment':
380          no_match,
381
382         'singleline_comment':
383          r'''(?mx)
384           ^.*?
385           (?P<match>
386            (?P<code>
387            %.*$\n+))''',
388
389         'verb':
390          r'''(?mx)
391           ^[^%\n]*?
392           (?P<match>
393            (?P<code>
394            \\verb(?P<del>.)
395             .*?
396            (?P=del)))''',
397
398         'verbatim':
399          r'''(?msx)
400           ^[^%\n]*?
401           (?P<match>
402            (?P<code>
403            \\begin\s*{verbatim}
404             .*?
405            \\end\s*{verbatim}))''',
406     },
407
408     ##
409     TEXINFO: {
410         'include':
411          r'''(?mx)
412           ^(?P<match>
413           @include\s+
414            (?P<filename>\S+))''',
415
416         'lilypond':
417          r'''(?smx)
418           ^[^\n]*?(?!@c\s+)[^\n]*?
419           (?P<match>
420           @lilypond\s*(
421           \[
422            \s*(?P<options>.*?)\s*
423           \])?\s*{
424            (?P<code>.*?)
425           })''',
426
427         'lilypond_block':
428          r'''(?msx)
429           ^(?P<match>
430           @lilypond\s*(
431           \[
432            \s*(?P<options>.*?)\s*
433           \])?\s+?
434           ^(?P<code>.*?)
435           ^@end\s+lilypond)\s''',
436
437         'lilypond_file':
438          r'''(?mx)
439           ^(?P<match>
440           @lilypondfile\s*(
441           \[
442            \s*(?P<options>.*?)\s*
443           \])?\s*{
444            (?P<filename>\S+)
445           })''',
446
447         'multiline_comment':
448          r'''(?smx)
449           ^(?P<match>
450            (?P<code>
451            @ignore\s
452             .*?
453            @end\s+ignore))\s''',
454
455         'singleline_comment':
456          r'''(?mx)
457           ^.*
458           (?P<match>
459            (?P<code>
460            @c([ \t][^\n]*|)\n))''',
461
462     # Don't do this: It interferes with @code{@{}.
463     #        'verb': r'''(?P<code>@code{.*?})''',
464
465         'verbatim':
466          r'''(?sx)
467           (?P<match>
468            (?P<code>
469            @example
470             \s.*?
471            @end\s+example\s))''',
472     },
473 }
474
475
476
477
478 format_res = {
479     DOCBOOK: {        
480         'intertext': r',?\s*intertext=\".*?\"',
481         'option_sep': '\s*',
482     }, 
483     HTML: {
484         'intertext': r',?\s*intertext=\".*?\"',
485         'option_sep': '\s*',
486     },
487
488     LATEX: {
489         'intertext': r',?\s*intertext=\".*?\"',
490         'option_sep': '\s*,\s*',
491     },
492
493     TEXINFO: {
494         'intertext': r',?\s*intertext=\".*?\"',
495         'option_sep': '\s*,\s*',
496     },
497 }
498
499 # Options without a pattern in ly_options.
500 simple_options = [
501     EXAMPLEINDENT,
502     FRAGMENT,
503     NOFRAGMENT,
504     NOINDENT,
505     PRINTFILENAME,
506     TEXIDOC,
507     VERBATIM,
508     FONTLOAD,
509     FILENAME,
510     ALT
511 ]
512
513 ly_options = {
514     ##
515     NOTES: {
516         RELATIVE: r'''\relative c%(relative_quotes)s''',
517     },
518
519     ##
520     PAPER: {
521         INDENT: r'''indent = %(indent)s''',
522
523         LINE_WIDTH: r'''line-width = %(line-width)s''',
524
525         QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
526
527         RAGGED_RIGHT: r'''ragged-right = ##t''',
528
529         PACKED: r'''packed = ##t''',
530     },
531
532     ##
533     LAYOUT: {
534         NOTIME: r'''
535  \context {
536   \Score
537   timing = ##f
538  }
539  \context {
540   \Staff
541   \remove Time_signature_engraver
542  }''',
543     },
544
545     ##
546     PREAMBLE: {
547         STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
548     },
549 }
550
551 output = {
552     ##
553     DOCBOOK: {                 
554         FILTER: r'''<mediaobject><textobject><programlisting language="lilypond" role="%(options)s">%(code)s</programlisting></textobject></mediaobject>''', 
555     
556         OUTPUT: r'''
557         <imageobject role="latex">
558                 <imagedata fileref="%(base)s.pdf" format="PDF"/>
559                 </imageobject>
560                 <imageobject role="html">
561                 <imagedata fileref="%(base)s.png" format="PNG"/></imageobject>''',
562     
563         VERBATIM: r'''<programlisting>%(verb)s</programlisting>''',
564     
565         PRINTFILENAME: '<textobject><simpara><ulink url="%(base)s.ly"><filename>%(filename)s</filename></ulink></simpara></textobject>'
566     },
567     ##
568     HTML: {
569         FILTER: r'''<lilypond %(options)s>
570 %(code)s
571 </lilypond>
572 ''',
573
574         AFTER: r'''
575  </a>
576 </p>''',
577
578         BEFORE: r'''<p>
579  <a href="%(base)s.ly">''',
580
581         OUTPUT: r'''
582   <img align="center" valign="center"
583     border="0" src="%(image)s" alt="%(alt)s">''',
584
585         PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
586
587         QUOTE: r'''<blockquote>
588 %(str)s
589 </blockquote>
590 ''',
591
592         VERBATIM: r'''<pre>
593 %(verb)s</pre>''',
594     },
595
596     ##
597     LATEX: {
598         OUTPUT: r'''{%%
599 \parindent 0pt%%
600 \ifx\preLilyPondExample \undefined%%
601  \relax%%
602 \else%%
603  \preLilyPondExample%%
604 \fi%%
605 \def\lilypondbook{}%%
606 \input %(base)s-systems.tex%%
607 \ifx\postLilyPondExample \undefined%%
608  \relax%%
609 \else%%
610  \postLilyPondExample%%
611 \fi%%
612 }''',
613
614         PRINTFILENAME: '''\\texttt{%(filename)s}
615     ''',
616
617         QUOTE: r'''\begin{quotation}%(str)s
618 \end{quotation}''',
619
620         VERBATIM: r'''\noindent
621 \begin{verbatim}%(verb)s\end{verbatim}''',
622
623         FILTER: r'''\begin{lilypond}[%(options)s]
624 %(code)s
625 \end{lilypond}''',
626     },
627
628     ##
629     TEXINFO: {
630         FILTER: r'''@lilypond[%(options)s]
631 %(code)s
632 @lilypond''',
633
634         OUTPUT: r'''
635 @iftex
636 @include %(base)s-systems.texi
637 @end iftex
638 ''',
639
640         OUTPUTIMAGE: r'''@noindent
641 @ifinfo
642 @image{%(base)s,,,%(alt)s,%(ext)s}
643 @end ifinfo
644 @html
645 <p>
646  <a href="%(base)s.ly">
647   <img align="center" valign="center"
648     border="0" src="%(image)s" alt="%(alt)s">
649  </a>
650 </p>
651 @end html
652 ''',
653
654         PRINTFILENAME: '''
655 @html
656 <a href="%(base)s.ly">
657 @end html
658 @file{%(filename)s}
659 @html
660 </a>
661 @end html
662     ''',
663
664         QUOTE: r'''@quotation
665 %(str)s@end quotation
666 ''',
667
668         NOQUOTE: r'''@format
669 %(str)s@end format
670 ''',
671
672         VERBATIM: r'''@exampleindent 0
673 @verbatim
674 %(verb)s@end verbatim
675 ''',
676     },
677 }
678
679 #
680 # Maintain line numbers.
681 #
682
683 ## TODO
684 if 0:
685     for f in [HTML, LATEX]:
686         for s in (QUOTE, VERBATIM):
687             output[f][s] = output[f][s].replace("\n"," ")
688
689
690 PREAMBLE_LY = '''%%%% Generated by %(program_name)s
691 %%%% Options: [%(option_string)s]
692 \\include "lilypond-book-preamble.ly"
693 %(preamble_string)s
694
695
696
697
698
699
700 %% ****************************************************************
701 %% Start cut-&-pastable-section 
702 %% ****************************************************************
703
704 \paper {
705   #(define dump-extents #t)
706   %(font_dump_setting)s
707   %(paper_string)s
708   force-assignment = #""
709   line-width = #(- line-width (* mm  %(padding_mm)f))
710 }
711
712 \layout {
713   %(layout_string)s
714 }
715 '''
716
717 FRAGMENT_LY = r'''
718 %(notes_string)s
719 {
720
721
722 %% ****************************************************************
723 %% ly snippet contents follows:
724 %% ****************************************************************
725 %(code)s
726
727
728 %% ****************************************************************
729 %% end ly snippet
730 %% ****************************************************************
731 }
732 '''
733
734 FULL_LY = '''
735
736
737 %% ****************************************************************
738 %% ly snippet:
739 %% ****************************************************************
740 %(code)s
741
742
743 %% ****************************************************************
744 %% end ly snippet
745 %% ****************************************************************
746 '''
747
748 texinfo_line_widths = {
749     '@afourpaper': '160\\mm',
750     '@afourwide': '6.5\\in',
751     '@afourlatex': '150\\mm',
752     '@smallbook': '5\\in',
753     '@letterpaper': '6\\in',
754 }
755
756 def classic_lilypond_book_compatibility (key, value):
757     if key == 'singleline' and value == None:
758         return (RAGGED_RIGHT, None)
759
760     m = re.search ('relative\s*([-0-9])', key)
761     if m:
762         return ('relative', m.group (1))
763
764     m = re.match ('([0-9]+)pt', key)
765     if m:
766         return ('staffsize', m.group (1))
767
768     if key == 'indent' or key == 'line-width':
769         m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value)
770         if m:
771             f = float (m.group (1))
772             return (key, '%f\\%s' % (f, m.group (2)))
773
774     return (None, None)
775
776 def find_file (name):
777     for i in global_options.include_path:
778         full = os.path.join (i, name)
779         if os.path.exists (full):
780             return full
781         
782     error (_ ("file not found: %s") % name + '\n')
783     exit (1)
784     return ''
785
786 def verbatim_html (s):
787     return re.sub ('>', '&gt;',
788            re.sub ('<', '&lt;',
789                re.sub ('&', '&amp;', s)))
790
791 def split_options (option_string):
792     if option_string:
793         if global_options.format == HTML:
794             options = re.findall('[\w\.-:]+(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|\S+))?',option_string)
795             for i in range(len(options)):
796                 options[i] = re.sub('^([^=]+=\s*)(?P<q>["\'])(.*)(?P=q)','\g<1>\g<3>',options[i])
797             return options
798         else:
799             return re.split (format_res[global_options.format]['option_sep'],
800                     option_string)
801     return []
802
803 def set_default_options (source):
804     global default_ly_options
805     if not default_ly_options.has_key (LINE_WIDTH):
806         if global_options.format == LATEX:
807             textwidth = get_latex_textwidth (source)
808             default_ly_options[LINE_WIDTH] = \
809              '''%.0f\\pt''' % textwidth
810         elif global_options.format == TEXINFO:
811             for (k, v) in texinfo_line_widths.items ():
812                 # FIXME: @layout is usually not in
813                 # chunk #0:
814                 #
815                 #  \input texinfo @c -*-texinfo-*-
816                 #
817                 # Bluntly search first K items of
818                 # source.
819                 # s = chunks[0].replacement_text ()
820                 if re.search (k, source[:1024]):
821                     default_ly_options[LINE_WIDTH] = v
822                     break
823
824 class Chunk:
825     def replacement_text (self):
826         return ''
827
828     def filter_text (self):
829         return self.replacement_text ()
830
831     def ly_is_outdated (self):
832         return 0
833
834     def png_is_outdated (self):
835         return 0
836
837     def is_plain (self):
838         return False
839     
840 class Substring (Chunk):
841     def __init__ (self, source, start, end, line_number):
842         self.source = source
843         self.start = start
844         self.end = end
845         self.line_number = line_number
846         self.override_text = None
847         
848     def is_plain (self):
849         return True
850
851     def replacement_text (self):
852         if self.override_text:
853             return self.override_text
854         else:
855             return self.source[self.start:self.end]
856
857 class Snippet (Chunk):
858     def __init__ (self, type, match, format, line_number):
859         self.type = type
860         self.match = match
861         self.hash = 0
862         self.option_dict = {}
863         self.format = format
864         self.line_number = line_number
865
866     def replacement_text (self):
867         return self.match.group ('match')
868
869     def substring (self, s):
870         return self.match.group (s)
871
872     def __repr__ (self):
873         return `self.__class__` + ' type = ' + self.type
874
875 class Include_snippet (Snippet):
876     def processed_filename (self):
877         f = self.substring ('filename')
878         return os.path.splitext (f)[0] + format2ext[global_options.format]
879
880     def replacement_text (self):
881         s = self.match.group ('match')
882         f = self.substring ('filename')
883
884         return re.sub (f, self.processed_filename (), s)
885
886 class Lilypond_snippet (Snippet):
887     def __init__ (self, type, match, format, line_number):
888         Snippet.__init__ (self, type, match, format, line_number)
889         os = match.group ('options')
890         self.do_options (os, self.type)
891
892     def ly (self):
893         contents = self.substring ('code')
894         return ('\\sourcefileline %d\n%s'
895                 % (self.line_number - 1, contents))
896
897     def full_ly (self):
898         s = self.ly ()
899         if s:
900             return self.compose_ly (s)
901         return ''
902
903     def do_options (self, option_string, type):
904         self.option_dict = {}
905
906         options = split_options (option_string)
907
908         for i in options:
909             if '=' in i:
910                 (key, value) = re.split ('\s*=\s*', i)
911                 self.option_dict[key] = value
912             else:
913                 if i in no_options.keys ():
914                     if no_options[i] in self.option_dict.keys ():
915                         del self.option_dict[no_options[i]]
916                 else:
917                     self.option_dict[i] = None
918
919         has_line_width = self.option_dict.has_key (LINE_WIDTH)
920         no_line_width_value = 0
921
922         # If LINE_WIDTH is used without parameter, set it to default.
923         if has_line_width and self.option_dict[LINE_WIDTH] == None:
924             no_line_width_value = 1
925             del self.option_dict[LINE_WIDTH]
926
927         for i in default_ly_options.keys ():
928             if i not in self.option_dict.keys ():
929                 self.option_dict[i] = default_ly_options[i]
930
931         if not has_line_width:
932             if type == 'lilypond' or FRAGMENT in self.option_dict.keys ():
933                 self.option_dict[RAGGED_RIGHT] = None
934
935             if type == 'lilypond':
936                 if LINE_WIDTH in self.option_dict.keys ():
937                     del self.option_dict[LINE_WIDTH]
938             else:
939                 if RAGGED_RIGHT in self.option_dict.keys ():
940                     if LINE_WIDTH in self.option_dict.keys ():
941                         del self.option_dict[LINE_WIDTH]
942
943             if QUOTE in self.option_dict.keys () or type == 'lilypond':
944                 if LINE_WIDTH in self.option_dict.keys ():
945                     del self.option_dict[LINE_WIDTH]
946
947         if not INDENT in self.option_dict.keys ():
948             self.option_dict[INDENT] = '0\\mm'
949
950         # The QUOTE pattern from ly_options only emits the `line-width'
951         # keyword.
952         if has_line_width and QUOTE in self.option_dict.keys ():
953             if no_line_width_value:
954                 del self.option_dict[LINE_WIDTH]
955             else:
956                 del self.option_dict[QUOTE]
957
958     def compose_ly (self, code):
959         if FRAGMENT in self.option_dict.keys ():
960             body = FRAGMENT_LY
961         else:
962             body = FULL_LY
963
964         # Defaults.
965         relative = 1
966         override = {}
967         # The original concept of the `exampleindent' option is broken.
968         # It is not possible to get a sane value for @exampleindent at all
969         # without processing the document itself.  Saying
970         #
971         #   @exampleindent 0
972         #   @example
973         #   ...
974         #   @end example
975         #   @exampleindent 5
976         #
977         # causes ugly results with the DVI backend of texinfo since the
978         # default value for @exampleindent isn't 5em but 0.4in (or a smaller
979         # value).  Executing the above code changes the environment
980         # indentation to an unknown value because we don't know the amount
981         # of 1em in advance since it is font-dependent.  Modifying
982         # @exampleindent in the middle of a document is simply not
983         # supported within texinfo.
984         #
985         # As a consequence, the only function of @exampleindent is now to
986         # specify the amount of indentation for the `quote' option.
987         #
988         # To set @exampleindent locally to zero, we use the @format
989         # environment for non-quoted snippets.
990         override[EXAMPLEINDENT] = r'0.4\in'
991         override[LINE_WIDTH] = texinfo_line_widths['@smallbook']
992         override.update (default_ly_options)
993
994         option_list = []
995         for (key, value) in self.option_dict.items ():
996             if value == None:
997                 option_list.append (key)
998             else:
999                 option_list.append (key + '=' + value)
1000         option_string = string.join (option_list, ',')
1001
1002         compose_dict = {}
1003         compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
1004         for a in compose_types:
1005             compose_dict[a] = []
1006
1007         for (key, value) in self.option_dict.items ():
1008             (c_key, c_value) = \
1009              classic_lilypond_book_compatibility (key, value)
1010             if c_key:
1011                 if c_value:
1012                     warning \
1013                      (_ ("deprecated ly-option used: %s=%s" \
1014                       % (key, value)))
1015                     warning \
1016                      (_ ("compatibility mode translation: %s=%s" \
1017                       % (c_key, c_value)))
1018                 else:
1019                     warning \
1020                      (_ ("deprecated ly-option used: %s" \
1021                       % key))
1022                     warning \
1023                      (_ ("compatibility mode translation: %s" \
1024                       % c_key))
1025
1026                 (key, value) = (c_key, c_value)
1027
1028             if value:
1029                 override[key] = value
1030             else:
1031                 if not override.has_key (key):
1032                     override[key] = None
1033
1034             found = 0
1035             for type in compose_types:
1036                 if ly_options[type].has_key (key):
1037                     compose_dict[type].append (ly_options[type][key])
1038                     found = 1
1039                     break
1040
1041             if not found and key not in simple_options:
1042                 warning (_ ("ignoring unknown ly option: %s") % key)
1043
1044         # URGS
1045         if RELATIVE in override.keys () and override[RELATIVE]:
1046             relative = int (override[RELATIVE])
1047
1048         relative_quotes = ''
1049
1050         # 1 = central C
1051         if relative < 0:
1052             relative_quotes += ',' * (- relative)
1053         elif relative > 0:
1054             relative_quotes += "'" * relative
1055
1056         paper_string = string.join (compose_dict[PAPER],
1057                       '\n  ') % override
1058         layout_string = string.join (compose_dict[LAYOUT],
1059                       '\n  ') % override
1060         notes_string = string.join (compose_dict[NOTES],
1061                       '\n  ') % vars ()
1062         preamble_string = string.join (compose_dict[PREAMBLE],
1063                        '\n  ') % override
1064         padding_mm = global_options.padding_mm
1065         font_dump_setting = ''
1066         if FONTLOAD in self.option_dict:
1067             font_dump_setting = '#(define-public force-eps-font-include #t)\n'
1068
1069         d = globals().copy()
1070         d.update (locals())
1071         return (PREAMBLE_LY + body) % d
1072
1073     def get_hash (self):
1074         if not self.hash:
1075             hash = md5.md5 (self.relevant_contents (self.full_ly ()))
1076
1077             ## let's not create too long names.
1078             self.hash = hash.hexdigest ()[:10]
1079             
1080         return self.hash
1081
1082     def basename (self):
1083         if FILENAME in self.option_dict:
1084             return self.option_dict[FILENAME]
1085         if global_options.use_hash:
1086             return 'lily-%s' % self.get_hash ()
1087         raise 'to be done'
1088
1089     def write_ly (self):
1090         outf = open (self.basename () + '.ly', 'w')
1091         outf.write (self.full_ly ())
1092         open (self.basename () + '.txt', 'w').write ('image of music')
1093
1094     def relevant_contents (self, ly):
1095         return re.sub (r'\\(version|sourcefileline)[^\n]*\n', '', ly)
1096              
1097     def ly_is_outdated (self):
1098         base = self.basename ()
1099         ly_file = base + '.ly'
1100         tex_file = base + '.tex'
1101         eps_file = base + '.eps'
1102         systems_file = base + '-systems.tex'
1103
1104         if (os.path.exists (ly_file)
1105             and os.path.exists (systems_file)
1106             and os.stat (systems_file)[stat.ST_SIZE]
1107             and re.match ('% eof', open (systems_file).readlines ()[-1])
1108             and (global_options.use_hash or FILENAME in self.option_dict)
1109             and (self.relevant_contents (self.full_ly ())
1110                  == self.relevant_contents (open (ly_file).read ()))):
1111             return None
1112
1113         return self
1114
1115     def png_is_outdated (self):
1116         base = self.basename ()
1117         # FIXME: refactor stupid OK stuff
1118         ok = not self.ly_is_outdated ()
1119         if global_options.format in (HTML, TEXINFO):
1120             ok = ok and os.path.exists (base + '.eps')
1121
1122             page_count = 0
1123             if ok:
1124                 page_count = ps_page_count (base + '.eps')
1125
1126             if page_count <= 1:
1127                 ok = ok and os.path.exists (base + '.png')
1128              
1129             elif page_count > 1:
1130                 for a in range (1, page_count + 1):
1131                         ok = ok and os.path.exists (base + '-page%d.png' % a)
1132                 
1133         return not ok
1134     
1135     def texstr_is_outdated (self):
1136         if backend == 'ps':
1137             return 0
1138
1139         # FIXME: refactor stupid OK stuff
1140         base = self.basename ()
1141         ok = self.ly_is_outdated ()
1142         ok = ok and (os.path.exists (base + '.texstr'))
1143         return not ok
1144
1145     def filter_text (self):
1146         code = self.substring ('code')
1147         s = run_filter (code)
1148         d = {
1149             'code': s,
1150             'options': self.match.group ('options')
1151         }
1152         # TODO
1153         return output[self.format][FILTER] % d
1154
1155     def replacement_text (self):
1156         func = Lilypond_snippet.__dict__['output_' + self.format]
1157         return func (self)
1158
1159     def get_images (self):
1160         base = self.basename ()
1161         # URGUGHUGHUGUGH
1162         single = '%(base)s.png' % vars ()
1163         multiple = '%(base)s-page1.png' % vars ()
1164         images = (single,)
1165         if os.path.exists (multiple) \
1166          and (not os.path.exists (single) \
1167             or (os.stat (multiple)[stat.ST_MTIME] \
1168               > os.stat (single)[stat.ST_MTIME])):
1169             count = ps_page_count ('%(base)s.eps' % vars ())
1170             images = ['%s-page%d.png' % (base, a) for a in range (1, count+1)]
1171             images = tuple (images)
1172         return images
1173
1174     def output_docbook (self):
1175         str = ''
1176         base = self.basename ()
1177         for image in self.get_images ():
1178             (base, ext) = os.path.splitext (image)
1179             str += output[DOCBOOK][OUTPUT] % vars ()
1180             str += self.output_print_filename (DOCBOOK)
1181             if (self.substring('inline') == 'inline'): 
1182                 str = '<inlinemediaobject>' + str + '</inlinemediaobject>'
1183             else:
1184                 str = '<mediaobject>' + str + '</mediaobject>'
1185         if VERBATIM in self.option_dict:
1186                 verb = verbatim_html (self.substring ('code'))
1187                 str = output[DOCBOOK][VERBATIM] % vars () + str
1188         return str
1189         
1190     def output_html (self):
1191         str = ''
1192         base = self.basename ()
1193         if global_options.format == HTML:
1194             str += self.output_print_filename (HTML)
1195             if VERBATIM in self.option_dict:
1196                 verb = verbatim_html (self.substring ('code'))
1197                 str += output[HTML][VERBATIM] % vars ()
1198             if QUOTE in self.option_dict:
1199                 str = output[HTML][QUOTE] % vars ()
1200
1201         str += output[HTML][BEFORE] % vars ()
1202         for image in self.get_images ():
1203             (base, ext) = os.path.splitext (image)
1204             alt = self.option_dict[ALT]
1205             str += output[HTML][OUTPUT] % vars ()
1206         str += output[HTML][AFTER] % vars ()
1207         return str
1208
1209     def output_info (self):
1210         str = ''
1211         for image in self.get_images ():
1212             (base, ext) = os.path.splitext (image)
1213
1214             # URG, makeinfo implicitly prepends dot to extension.
1215             # Specifying no extension is most robust.
1216             ext = ''
1217             alt = self.option_dict[ALT]
1218             str += output[TEXINFO][OUTPUTIMAGE] % vars ()
1219
1220         base = self.basename ()
1221         str += output[global_options.format][OUTPUT] % vars ()
1222         return str
1223
1224     def output_latex (self):
1225         str = ''
1226         base = self.basename ()
1227         if global_options.format == LATEX:
1228             str += self.output_print_filename (LATEX)
1229             if VERBATIM in self.option_dict:
1230                 verb = self.substring ('code')
1231                 str += (output[LATEX][VERBATIM] % vars ())
1232
1233         str += (output[LATEX][OUTPUT] % vars ())
1234
1235         ## todo: maintain breaks
1236         if 0:
1237             breaks = self.ly ().count ("\n")
1238             str += "".ljust (breaks, "\n").replace ("\n","%\n")
1239         
1240         if QUOTE in self.option_dict:
1241             str = output[LATEX][QUOTE] % vars ()
1242         return str
1243
1244     def output_print_filename (self, format):
1245         str = ''
1246         if PRINTFILENAME in self.option_dict:
1247             base = self.basename ()
1248             filename = self.substring ('filename')
1249             str = output[global_options.format][PRINTFILENAME] % vars ()
1250
1251         return str
1252
1253     def output_texinfo (self):
1254         str = ''
1255         if self.output_print_filename (TEXINFO):
1256             str += ('@html\n'
1257                 + self.output_print_filename (HTML)
1258                 + '\n@end html\n')
1259             str += ('@tex\n'
1260                 + self.output_print_filename (LATEX)
1261                 + '\n@end tex\n')
1262         base = self.basename ()
1263         if TEXIDOC in self.option_dict:
1264             texidoc = base + '.texidoc'
1265             if os.path.exists (texidoc):
1266                 str += '@include %(texidoc)s\n\n' % vars ()
1267
1268         if VERBATIM in self.option_dict:
1269             verb = self.substring ('code')
1270             str += (output[TEXINFO][VERBATIM] % vars ())
1271             if not QUOTE in self.option_dict:
1272                 str = output[TEXINFO][NOQUOTE] % vars ()
1273
1274         str += self.output_info ()
1275
1276 #                str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n')
1277 #                str += ('@tex\n' + self.output_latex () + '\n@end tex\n')
1278 #                str += ('@html\n' + self.output_html () + '\n@end html\n')
1279
1280         if QUOTE in self.option_dict:
1281             str = output[TEXINFO][QUOTE] % vars ()
1282
1283         # need par after image
1284         str += '\n'
1285
1286         return str
1287
1288 class Lilypond_file_snippet (Lilypond_snippet):
1289     def ly (self):
1290         name = self.substring ('filename')
1291         contents = open (find_file (name)).read ()
1292         return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
1293                 % (name, contents))
1294
1295 snippet_type_to_class = {
1296     'lilypond_file': Lilypond_file_snippet,
1297     'lilypond_block': Lilypond_snippet,
1298     'lilypond': Lilypond_snippet,
1299     'include': Include_snippet,
1300 }
1301
1302 def find_linestarts (s):
1303     nls = [0]
1304     start = 0
1305     end = len (s)
1306     while 1:
1307         i = s.find ('\n', start)
1308         if i < 0:
1309             break
1310
1311         i = i + 1
1312         nls.append (i)
1313         start = i
1314
1315     nls.append (len (s))
1316     return nls
1317
1318 def find_toplevel_snippets (s, types):
1319     res = {}
1320     for i in types:
1321         res[i] = ly.re.compile (snippet_res[global_options.format][i])
1322
1323     snippets = []
1324     index = 0
1325     found = dict ([(t, None) for t in types])
1326
1327     line_starts = find_linestarts (s)
1328     line_start_idx = 0
1329     # We want to search for multiple regexes, without searching
1330     # the string multiple times for one regex.
1331     # Hence, we use earlier results to limit the string portion
1332     # where we search.
1333     # Since every part of the string is traversed at most once for
1334     # every type of snippet, this is linear.
1335
1336     while 1:
1337         first = None
1338         endex = 1 << 30
1339         for type in types:
1340             if not found[type] or found[type][0] < index:
1341                 found[type] = None
1342                 
1343                 m = res[type].search (s[index:endex])
1344                 if not m:
1345                     continue
1346
1347                 cl = Snippet
1348                 if snippet_type_to_class.has_key (type):
1349                     cl = snippet_type_to_class[type]
1350
1351
1352                 start = index + m.start ('match')
1353                 line_number = line_start_idx
1354                 while (line_starts[line_number] < start):
1355                     line_number += 1
1356
1357                 line_number += 1
1358                 snip = cl (type, m, global_options.format, line_number)
1359
1360                 found[type] = (start, snip)
1361
1362             if found[type] \
1363              and (not first \
1364                 or found[type][0] < found[first][0]):
1365                 first = type
1366
1367                 # FIXME.
1368
1369                 # Limiting the search space is a cute
1370                 # idea, but this *requires* to search
1371                 # for possible containing blocks
1372                 # first, at least as long as we do not
1373                 # search for the start of blocks, but
1374                 # always/directly for the entire
1375                 # @block ... @end block.
1376
1377                 endex = found[first][0]
1378
1379         if not first:
1380             snippets.append (Substring (s, index, len (s), line_start_idx))
1381             break
1382
1383         while (start > line_starts[line_start_idx+1]):
1384             line_start_idx += 1
1385
1386         (start, snip) = found[first]
1387         snippets.append (Substring (s, index, start, line_start_idx + 1))
1388         snippets.append (snip)
1389         found[first] = None
1390         index = start + len (snip.match.group ('match'))
1391
1392     return snippets
1393
1394 def filter_pipe (input, cmd):
1395     if global_options.verbose:
1396         progress (_ ("Opening filter `%s'") % cmd)
1397
1398     (stdin, stdout, stderr) = os.popen3 (cmd)
1399     stdin.write (input)
1400     status = stdin.close ()
1401
1402     if not status:
1403         status = 0
1404         output = stdout.read ()
1405         status = stdout.close ()
1406         error = stderr.read ()
1407
1408     if not status:
1409         status = 0
1410     signal = 0x0f & status
1411     if status or (not output and error):
1412         exit_status = status >> 8
1413         error (_ ("`%s' failed (%d)") % (cmd, exit_status))
1414         error (_ ("The error log is as follows:"))
1415         sys.stderr.write (error)
1416         sys.stderr.write (stderr.read ())
1417         exit (status)
1418
1419     if global_options.verbose:
1420         progress ('\n')
1421
1422     return output
1423
1424 def run_filter (s):
1425     return filter_pipe (s, global_options.filter_cmd)
1426
1427 def is_derived_class (cl, baseclass):
1428     if cl == baseclass:
1429         return 1
1430     for b in cl.__bases__:
1431         if is_derived_class (b, baseclass):
1432             return 1
1433     return 0
1434
1435 def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets):
1436     ly_names = filter (lambda x: x,
1437                        map (Lilypond_snippet.basename, ly_snippets))
1438     texstr_names = filter (lambda x: x,
1439                            map (Lilypond_snippet.basename, texstr_snippets))
1440     
1441     png_names = filter (lambda x: x,
1442                         map (Lilypond_snippet.basename, png_snippets))
1443
1444     status = 0
1445     def my_system (cmd):
1446         status = ly.system (cmd,
1447                             be_verbose=global_options.verbose, 
1448                             progress_p=1)
1449
1450     if global_options.format in (HTML, TEXINFO) and '--formats' not in cmd:
1451         cmd += ' --formats=png '
1452     elif global_options.format in (DOCBOOK) and '--formats' not in cmd:
1453         cmd += ' --formats=png,pdf '
1454
1455         
1456     # UGH
1457     # the --process=CMD switch is a bad idea
1458     # it is too generic for lilypond-book.
1459     if texstr_names:
1460         my_system (string.join ([cmd, '--backend texstr',
1461                                  'snippet-map.ly'] + texstr_names))
1462         for l in texstr_names:
1463             my_system ('latex %s.texstr' % l)
1464
1465     if ly_names:
1466         open ('snippet-names', 'wb').write ('\n'.join (['snippet-map.ly']
1467                                                       + ly_names))
1468         
1469         my_system (string.join ([cmd, 'snippet-names']))
1470
1471
1472 LATEX_INSPECTION_DOCUMENT = r'''
1473 \nonstopmode
1474 %(preamble)s
1475 \begin{document}
1476 \typeout{textwidth=\the\textwidth}
1477 \typeout{columnsep=\the\columnsep}
1478 \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother
1479 \end{document}
1480 '''
1481
1482 # Do we need anything else besides `textwidth'?
1483 def get_latex_textwidth (source):
1484     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
1485     if m == None:
1486         warning (_ ("cannot find \\begin{document} in LaTeX document"))
1487         
1488         ## what's a sensible default?
1489         return 550.0
1490     
1491     preamble = source[:m.start (0)]
1492     latex_document = LATEX_INSPECTION_DOCUMENT % vars ()
1493     
1494     (handle, tmpfile) = tempfile.mkstemp('.tex')
1495     logfile = os.path.splitext (tmpfile)[0] + '.log'
1496     logfile = os.path.split (logfile)[1]
1497
1498     tmp_handle = os.fdopen (handle,'w')
1499     tmp_handle.write (latex_document)
1500     tmp_handle.close ()
1501     
1502     ly.system ('latex %s' % tmpfile, be_verbose=global_options.verbose)
1503     parameter_string = open (logfile).read()
1504     
1505     os.unlink (tmpfile)
1506     os.unlink (logfile)
1507
1508     columns = 0
1509     m = re.search ('columns=([0-9.]*)', parameter_string)
1510     if m:
1511         columns = int (m.group (1))
1512
1513     columnsep = 0
1514     m = re.search ('columnsep=([0-9.]*)pt', parameter_string)
1515     if m:
1516         columnsep = float (m.group (1))
1517
1518     textwidth = 0
1519     m = re.search ('textwidth=([0-9.]*)pt', parameter_string)
1520     if m:
1521         textwidth = float (m.group (1))
1522         if columns:
1523             textwidth = (textwidth - columnsep) / columns
1524
1525     return textwidth
1526
1527 def modify_preamble (chunk):
1528     str = chunk.replacement_text ()
1529     if (re.search (r"\\begin *{document}", str)
1530       and not re.search ("{graphic[sx]", str)):
1531         str = re.sub (r"\\begin{document}",
1532                r"\\usepackage{graphics}" + '\n'
1533                + r"\\begin{document}",
1534                str)
1535         chunk.override_text = str 
1536         
1537     
1538
1539 ext2format = {
1540     '.html': HTML,
1541     '.itely': TEXINFO,
1542     '.latex': LATEX,
1543     '.lytex': LATEX,
1544     '.tely': TEXINFO,
1545     '.tex': LATEX,
1546     '.texi': TEXINFO,
1547     '.texinfo': TEXINFO,
1548     '.xml': HTML,
1549     '.lyxml': DOCBOOK
1550 }
1551
1552 format2ext = {
1553     HTML: '.html',
1554     # TEXINFO: '.texinfo',
1555     TEXINFO: '.texi',
1556     LATEX: '.tex',
1557     DOCBOOK: '.xml'
1558 }
1559
1560 class Compile_error:
1561     pass
1562
1563 def write_file_map (lys, name):
1564     snippet_map = open ('snippet-map.ly', 'w')
1565     snippet_map.write ("""
1566 #(define version-seen #t)
1567 #(define output-empty-score-list #f)
1568 #(ly:add-file-name-alist '(
1569 """)
1570     for ly in lys:
1571         snippet_map.write ('("%s.ly" . "%s")\n'
1572                  % (ly.basename (),
1573                    name))
1574
1575     snippet_map.write ('))\n')
1576
1577 def do_process_cmd (chunks, input_name):
1578     all_lys = filter (lambda x: is_derived_class (x.__class__,
1579                            Lilypond_snippet),
1580                       chunks)
1581
1582     write_file_map (all_lys, input_name)
1583     ly_outdated = filter (lambda x: is_derived_class (x.__class__,
1584                                                       Lilypond_snippet)
1585                           and x.ly_is_outdated (), chunks)
1586     texstr_outdated = filter (lambda x: is_derived_class (x.__class__,
1587                                                           Lilypond_snippet)
1588                               and x.texstr_is_outdated (),
1589                               chunks)
1590     png_outdated = filter (lambda x: is_derived_class (x.__class__,
1591                                                         Lilypond_snippet)
1592                            and x.png_is_outdated (),
1593                            chunks)
1594
1595     outdated = png_outdated + texstr_outdated + ly_outdated
1596     
1597     progress (_ ("Writing snippets..."))
1598     map (Lilypond_snippet.write_ly, ly_outdated)
1599     progress ('\n')
1600
1601     if outdated:
1602         progress (_ ("Processing..."))
1603         progress ('\n')
1604         process_snippets (global_options.process_cmd, ly_outdated, texstr_outdated, png_outdated)
1605     else:
1606         progress (_ ("All snippets are up to date..."))
1607     progress ('\n')
1608
1609 def guess_format (input_filename):
1610     format = None
1611     e = os.path.splitext (input_filename)[1]
1612     if e in ext2format.keys ():
1613         # FIXME
1614         format = ext2format[e]
1615     else:
1616         error (_ ("cannot determine format for: %s" \
1617               % input_filename))
1618         exit (1)
1619     return format
1620
1621 def write_if_updated (file_name, lines):
1622     try:
1623         f = open (file_name)
1624         oldstr = f.read ()
1625         new_str = string.join (lines, '')
1626         if oldstr == new_str:
1627             progress (_ ("%s is up to date.") % file_name)
1628             progress ('\n')
1629             return
1630     except:
1631         pass
1632
1633     progress (_ ("Writing `%s'...") % file_name)
1634     open (file_name, 'w').writelines (lines)
1635     progress ('\n')
1636
1637 def note_input_file (name, inputs=[]):
1638     ## hack: inputs is mutable!
1639     inputs.append (name)
1640     return inputs
1641
1642 def samefile (f1, f2):
1643     try:
1644         return os.path.samefile (f1, f2)
1645     except AttributeError:                # Windoze
1646         f1 = re.sub ("//*", "/", f1)
1647         f2 = re.sub ("//*", "/", f2)
1648         return f1 == f2
1649
1650 def do_file (input_filename):
1651     # Ugh.
1652     if not input_filename or input_filename == '-':
1653         in_handle = sys.stdin
1654         input_fullname = '<stdin>'
1655     else:
1656         if os.path.exists (input_filename):
1657             input_fullname = input_filename
1658         elif global_options.format == LATEX and ly.search_exe_path ('kpsewhich'):
1659             input_fullname = os.popen ('kpsewhich ' + input_filename).read()[:-1]
1660         else:
1661             input_fullname = find_file (input_filename)
1662
1663         note_input_file (input_fullname)
1664         in_handle = open (input_fullname)
1665
1666     if input_filename == '-':
1667         input_base = 'stdin'
1668     else:
1669         input_base = os.path.basename \
1670                      (os.path.splitext (input_filename)[0])
1671
1672     # Only default to stdout when filtering.
1673     if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd):
1674         output_filename = '-'
1675         output_file = sys.stdout
1676     else:
1677         # don't complain when global_options.output_name is existing
1678         output_filename = input_base + format2ext[global_options.format]
1679         if global_options.output_name:
1680             if not os.path.isdir (global_options.output_name):
1681                 os.mkdir (global_options.output_name, 0777)
1682             os.chdir (global_options.output_name)
1683         else: 
1684             if (os.path.exists (input_filename) 
1685                 and os.path.exists (output_filename) 
1686                 and samefile (output_filename, input_fullname)):
1687              error (
1688              _ ("Output would overwrite input file; use --output."))
1689              exit (2)
1690
1691     try:
1692         progress (_ ("Reading %s...") % input_fullname)
1693         source = in_handle.read ()
1694         progress ('\n')
1695
1696         set_default_options (source)
1697
1698
1699         # FIXME: Containing blocks must be first, see
1700         #        find_toplevel_snippets.
1701         snippet_types = (
1702             'multiline_comment',
1703             'verbatim',
1704             'lilypond_block',
1705     #                'verb',
1706             'singleline_comment',
1707             'lilypond_file',
1708             'include',
1709             'lilypond',
1710         )
1711         progress (_ ("Dissecting..."))
1712         chunks = find_toplevel_snippets (source, snippet_types)
1713
1714         if global_options.format == LATEX:
1715             for c in chunks:
1716                 if (c.is_plain () and
1717                   re.search (r"\\begin *{document}", c.replacement_text())):
1718                     modify_preamble (c)
1719                     break
1720         progress ('\n')
1721
1722         if global_options.filter_cmd:
1723             write_if_updated (output_filename,
1724                      [c.filter_text () for c in chunks])
1725         elif global_options.process_cmd:
1726             do_process_cmd (chunks, input_fullname)
1727             progress (_ ("Compiling %s...") % output_filename)
1728             progress ('\n')
1729             write_if_updated (output_filename,
1730                      [s.replacement_text ()
1731                      for s in chunks])
1732         
1733         def process_include (snippet):
1734             os.chdir (original_dir)
1735             name = snippet.substring ('filename')
1736             progress (_ ("Processing include: %s") % name)
1737             progress ('\n')
1738             return do_file (name)
1739
1740         include_chunks = map (process_include,
1741                    filter (lambda x: is_derived_class (x.__class__,
1742                                      Include_snippet),
1743                        chunks))
1744
1745
1746         return chunks + reduce (lambda x,y: x + y, include_chunks, [])
1747         
1748     except Compile_error:
1749         os.chdir (original_dir)
1750         progress (_ ("Removing `%s'") % output_filename)
1751         progress ('\n')
1752         raise Compile_error
1753
1754 def do_options ():
1755
1756     global global_options
1757
1758     opt_parser = get_option_parser()
1759     (global_options, args) = opt_parser.parse_args ()
1760
1761     if global_options.format in ('texi-html', 'texi'):
1762         global_options.format = TEXINFO
1763     global_options.use_hash = True
1764
1765     global_options.include_path =  map (os.path.abspath, global_options.include_path)
1766     
1767     if global_options.warranty:
1768         warranty ()
1769         exit (0)
1770     if not args or len (args) > 1:
1771         opt_parser.print_help ()
1772         exit (2)
1773         
1774     return args
1775
1776 def psfonts_warning (options, basename):
1777     if options.format in (TEXINFO, LATEX):
1778         psfonts_file = os.path.join (options.output_name, basename + '.psfonts')
1779         output = os.path.join (options.output_name, basename +  '.dvi' )
1780
1781         if not options.create_pdf:
1782             if not options.psfonts:
1783                 warning (_ ("option --psfonts not used"))
1784                 warning (_ ("processing with dvips will have no fonts"))
1785             else:
1786                 progress ('\n')
1787                 progress (_ ("DVIPS usage:"))
1788                 progress ('\n')
1789                 progress ("    dvips -h %(psfonts_file)s %(output)s" % vars ())
1790                 progress ('\n')
1791
1792 def main ():
1793     # FIXME: 85 lines of `main' macramee??
1794     files = do_options ()
1795
1796     file = files[0]
1797
1798     basename = os.path.splitext (file)[0]
1799     basename = os.path.split (basename)[1]
1800     
1801     if not global_options.format:
1802         global_options.format = guess_format (files[0])
1803
1804     formats = 'ps'
1805     if global_options.format in (TEXINFO, HTML, DOCBOOK):
1806         formats += ',png'
1807
1808         
1809     if global_options.process_cmd == '':
1810         global_options.process_cmd = (lilypond_binary 
1811                                       + ' --formats=%s --backend eps ' % formats)
1812
1813     if global_options.process_cmd:
1814         global_options.process_cmd += string.join ([(' -I %s' % ly.mkarg (p))
1815                               for p in global_options.include_path])
1816
1817     if global_options.format in (TEXINFO, LATEX):
1818         ## prevent PDF from being switched on by default.
1819         global_options.process_cmd += ' --formats=eps '
1820         if global_options.create_pdf:
1821             global_options.process_cmd += "--pdf -dinclude-eps-fonts -dgs-load-fonts "
1822     
1823     if global_options.verbose:
1824         global_options.process_cmd += " --verbose "
1825
1826     if global_options.padding_mm:
1827         global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm
1828         
1829     global_options.process_cmd += " -dread-file-list "
1830
1831     identify ()
1832
1833     try:
1834         chunks = do_file (file)
1835         if global_options.psfonts:
1836             fontextract.verbose = global_options.verbose
1837             snippet_chunks = filter (lambda x: is_derived_class (x.__class__,
1838                                        Lilypond_snippet),
1839                         chunks)
1840
1841             psfonts_file = basename + '.psfonts' 
1842             if not global_options.verbose:
1843                 progress (_ ("Writing fonts to %s...") % psfonts_file)
1844             fontextract.extract_fonts (psfonts_file,
1845                          [x.basename() + '.eps'
1846                           for x in snippet_chunks])
1847             if not global_options.verbose:
1848                 progress ('\n')
1849             
1850     except Compile_error:
1851         exit (1)
1852
1853     psfonts_warning (global_options, basename)
1854
1855     inputs = note_input_file ('')
1856     inputs.pop ()
1857
1858     base_file_name = os.path.splitext (os.path.basename (file))[0]
1859     dep_file = os.path.join (global_options.output_name, base_file_name + '.dep')
1860     final_output_file = os.path.join (global_options.output_name,
1861                      base_file_name
1862                      + '.%s' % global_options.format)
1863     
1864     os.chdir (original_dir)
1865     open (dep_file, 'w').write ('%s: %s' % (final_output_file, ' '.join (inputs)))
1866
1867 if __name__ == '__main__':
1868     main ()