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