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