]> 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 @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--2006',
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 -b 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 NOFRAGMENT = 'nofragment'
192 NOINDENT = 'noindent'
193 NOQUOTE = 'noquote'
194 NOTES = 'body'
195 NOTIME = 'notime'
196 OUTPUT = 'output'
197 OUTPUTIMAGE = 'outputimage'
198 PACKED = 'packed'
199 PAPER = 'paper'
200 PREAMBLE = 'preamble'
201 PRINTFILENAME = 'printfilename'
202 QUOTE = 'quote'
203 RAGGED_RIGHT = 'ragged-right'
204 RELATIVE = 'relative'
205 STAFFSIZE = 'staffsize'
206 TEXIDOC = 'texidoc'
207 TEXINFO = 'texinfo'
208 VERBATIM = 'verbatim'
209 FONTLOAD = 'fontload'
210 FILENAME = 'filename'
211 ALT = 'alt'
212
213
214 # NOTIME has no opposite so it isn't part of this dictionary.
215 # NOQUOTE is used internally only.
216 no_options = {
217     NOFRAGMENT: FRAGMENT,
218     NOINDENT: INDENT,
219 }
220
221
222 # Recognize special sequences in the input.
223 #
224 #   (?P<name>regex) -- Assign result of REGEX to NAME.
225 #   *? -- Match non-greedily.
226 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
227 #   (?s) -- Make the dot match all characters including newline.
228 #   (?x) -- Ignore whitespace in patterns.
229 no_match = 'a\ba'
230 snippet_res = {
231  ##
232     DOCBOOK: {
233         'include':
234          no_match,
235
236         'lilypond':
237          r'''(?smx)
238           (?P<match>
239           <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
240
241         'lilypond_block':
242          r'''(?smx)
243           (?P<match>
244           <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
245
246         'lilypond_file':
247          r'''(?smx)
248           (?P<match>
249           <(?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>)''',
250
251         'multiline_comment':
252          r'''(?smx)
253           (?P<match>
254           \s*(?!@c\s+)
255           (?P<code><!--\s.*?!-->)
256           \s)''',
257
258         'singleline_comment':
259          no_match,
260
261         'verb':
262          no_match,
263
264         'verbatim':
265         no_match,
266         
267     }, 
268     ##
269     HTML: {
270         'include':
271          no_match,
272
273         'lilypond':
274          r'''(?mx)
275           (?P<match>
276           <lilypond
277            (\s*(?P<options>.*?)\s*:)?\s*
278            (?P<code>.*?)
279           />)''',
280
281         'lilypond_block':
282          r'''(?msx)
283           (?P<match>
284           <lilypond
285            \s*(?P<options>.*?)\s*
286           >
287           (?P<code>.*?)
288           </lilypond>)''',
289
290         'lilypond_file':
291          r'''(?mx)
292           (?P<match>
293           <lilypondfile
294            \s*(?P<options>.*?)\s*
295           >
296           \s*(?P<filename>.*?)\s*
297           </lilypondfile>)''',
298
299         'multiline_comment':
300          r'''(?smx)
301           (?P<match>
302           \s*(?!@c\s+)
303           (?P<code><!--\s.*?!-->)
304           \s)''',
305
306         'singleline_comment':
307          no_match,
308
309         'verb':
310          r'''(?x)
311           (?P<match>
312            (?P<code><pre>.*?</pre>))''',
313
314         'verbatim':
315          r'''(?x)
316           (?s)
317           (?P<match>
318            (?P<code><pre>\s.*?</pre>\s))''',
319     },
320
321     ##
322     LATEX: {
323         'include':
324          r'''(?smx)
325           ^[^%\n]*?
326           (?P<match>
327           \\input\s*{
328            (?P<filename>\S+?)
329           })''',
330
331         'lilypond':
332          r'''(?smx)
333           ^[^%\n]*?
334           (?P<match>
335           \\lilypond\s*(
336           \[
337            \s*(?P<options>.*?)\s*
338           \])?\s*{
339            (?P<code>.*?)
340           })''',
341
342         'lilypond_block':
343          r'''(?smx)
344           ^[^%\n]*?
345           (?P<match>
346           \\begin\s*(
347           \[
348            \s*(?P<options>.*?)\s*
349           \])?\s*{lilypond}
350            (?P<code>.*?)
351           ^[^%\n]*?
352           \\end\s*{lilypond})''',
353
354         'lilypond_file':
355          r'''(?smx)
356           ^[^%\n]*?
357           (?P<match>
358           \\lilypondfile\s*(
359           \[
360            \s*(?P<options>.*?)\s*
361           \])?\s*\{
362            (?P<filename>\S+?)
363           })''',
364
365         'multiline_comment':
366          no_match,
367
368         'singleline_comment':
369          r'''(?mx)
370           ^.*?
371           (?P<match>
372            (?P<code>
373            %.*$\n+))''',
374
375         'verb':
376          r'''(?mx)
377           ^[^%\n]*?
378           (?P<match>
379            (?P<code>
380            \\verb(?P<del>.)
381             .*?
382            (?P=del)))''',
383
384         'verbatim':
385          r'''(?msx)
386           ^[^%\n]*?
387           (?P<match>
388            (?P<code>
389            \\begin\s*{verbatim}
390             .*?
391            \\end\s*{verbatim}))''',
392     },
393
394     ##
395     TEXINFO: {
396         'include':
397          r'''(?mx)
398           ^(?P<match>
399           @include\s+
400            (?P<filename>\S+))''',
401
402         'lilypond':
403          r'''(?smx)
404           ^[^\n]*?(?!@c\s+)[^\n]*?
405           (?P<match>
406           @lilypond\s*(
407           \[
408            \s*(?P<options>.*?)\s*
409           \])?\s*{
410            (?P<code>.*?)
411           })''',
412
413         'lilypond_block':
414          r'''(?msx)
415           ^(?P<match>
416           @lilypond\s*(
417           \[
418            \s*(?P<options>.*?)\s*
419           \])?\s+?
420           ^(?P<code>.*?)
421           ^@end\s+lilypond)\s''',
422
423         'lilypond_file':
424          r'''(?mx)
425           ^(?P<match>
426           @lilypondfile\s*(
427           \[
428            \s*(?P<options>.*?)\s*
429           \])?\s*{
430            (?P<filename>\S+)
431           })''',
432
433         'multiline_comment':
434          r'''(?smx)
435           ^(?P<match>
436            (?P<code>
437            @ignore\s
438             .*?
439            @end\s+ignore))\s''',
440
441         'singleline_comment':
442          r'''(?mx)
443           ^.*
444           (?P<match>
445            (?P<code>
446            @c([ \t][^\n]*|)\n))''',
447
448     # Don't do this: It interferes with @code{@{}.
449     #        'verb': r'''(?P<code>@code{.*?})''',
450
451         'verbatim':
452          r'''(?sx)
453           (?P<match>
454            (?P<code>
455            @example
456             \s.*?
457            @end\s+example\s))''',
458     },
459 }
460
461
462
463
464 format_res = {
465     DOCBOOK: {        
466         'intertext': r',?\s*intertext=\".*?\"',
467         'option_sep': '\s*',
468     }, 
469     HTML: {
470         'intertext': r',?\s*intertext=\".*?\"',
471         'option_sep': '\s*',
472     },
473
474     LATEX: {
475         'intertext': r',?\s*intertext=\".*?\"',
476         'option_sep': '\s*,\s*',
477     },
478
479     TEXINFO: {
480         'intertext': r',?\s*intertext=\".*?\"',
481         'option_sep': '\s*,\s*',
482     },
483 }
484
485 # Options without a pattern in ly_options.
486 simple_options = [
487     EXAMPLEINDENT,
488     FRAGMENT,
489     NOFRAGMENT,
490     NOINDENT,
491     PRINTFILENAME,
492     TEXIDOC,
493     VERBATIM,
494     FONTLOAD,
495     FILENAME,
496     ALT
497 ]
498
499 ly_options = {
500     ##
501     NOTES: {
502         RELATIVE: r'''\relative c%(relative_quotes)s''',
503     },
504
505     ##
506     PAPER: {
507         INDENT: r'''indent = %(indent)s''',
508
509         LINE_WIDTH: r'''line-width = %(line-width)s''',
510
511         QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
512
513         RAGGED_RIGHT: r'''ragged-right = ##t''',
514
515         PACKED: r'''packed = ##t''',
516     },
517
518     ##
519     LAYOUT: {
520         NOTIME: r'''
521  \context {
522   \Score
523   timing = ##f
524  }
525  \context {
526   \Staff
527   \remove Time_signature_engraver
528  }''',
529     },
530
531     ##
532     PREAMBLE: {
533         STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
534     },
535 }
536
537 output = {
538     ##
539     DOCBOOK: {                 
540         FILTER: r'''<mediaobject><textobject><programlisting language="lilypond" role="%(options)s">%(code)s</programlisting></textobject></mediaobject>''', 
541     
542         OUTPUT: r'''
543         <imageobject role="latex">
544                 <imagedata fileref="%(base)s.pdf" format="PDF"/>
545                 </imageobject>
546                 <imageobject role="html">
547                 <imagedata fileref="%(base)s.png" format="PNG"/></imageobject>''',
548     
549         VERBATIM: r'''<programlisting>%(verb)s</programlisting>''',
550     
551         PRINTFILENAME: '<textobject><simpara><ulink url="%(base)s.ly"><filename>%(filename)s</filename></ulink></simpara></textobject>'
552     },
553     ##
554     HTML: {
555         FILTER: r'''<lilypond %(options)s>
556 %(code)s
557 </lilypond>
558 ''',
559
560         AFTER: r'''
561  </a>
562 </p>''',
563
564         BEFORE: r'''<p>
565  <a href="%(base)s.ly">''',
566
567         OUTPUT: r'''
568   <img align="center" valign="center"
569     border="0" src="%(image)s" alt="%(alt)s">''',
570
571         PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
572
573         QUOTE: r'''<blockquote>
574 %(str)s
575 </blockquote>
576 ''',
577
578         VERBATIM: r'''<pre>
579 %(verb)s</pre>''',
580     },
581
582     ##
583     LATEX: {
584         OUTPUT: r'''{%%
585 \parindent 0pt%%
586 \ifx\preLilyPondExample \undefined%%
587  \relax%%
588 \else%%
589  \preLilyPondExample%%
590 \fi%%
591 \def\lilypondbook{}%%
592 \input %(base)s-systems.tex%%
593 \ifx\postLilyPondExample \undefined%%
594  \relax%%
595 \else%%
596  \postLilyPondExample%%
597 \fi%%
598 }''',
599
600         PRINTFILENAME: '''\\texttt{%(filename)s}
601     ''',
602
603         QUOTE: r'''\begin{quotation}%(str)s
604 \end{quotation}''',
605
606         VERBATIM: r'''\noindent
607 \begin{verbatim}%(verb)s\end{verbatim}''',
608
609         FILTER: r'''\begin{lilypond}[%(options)s]
610 %(code)s
611 \end{lilypond}''',
612     },
613
614     ##
615     TEXINFO: {
616         FILTER: r'''@lilypond[%(options)s]
617 %(code)s
618 @lilypond''',
619
620         OUTPUT: r'''
621 @iftex
622 @include %(base)s-systems.texi
623 @end iftex
624 ''',
625
626         OUTPUTIMAGE: r'''@noindent
627 @ifinfo
628 @image{%(base)s,,,%(alt)s,%(ext)s}
629 @end ifinfo
630 @html
631 <p>
632  <a href="%(base)s.ly">
633   <img align="center" valign="center"
634     border="0" src="%(image)s" alt="%(alt)s">
635  </a>
636 </p>
637 @end html
638 ''',
639
640         PRINTFILENAME: '''
641 @html
642 <a href="%(base)s.ly">
643 @end html
644 @file{%(filename)s}
645 @html
646 </a>
647 @end html
648     ''',
649
650         QUOTE: r'''@quotation
651 %(str)s@end quotation
652 ''',
653
654         NOQUOTE: r'''@format
655 %(str)s@end format
656 ''',
657
658         VERBATIM: r'''@exampleindent 0
659 @verbatim
660 %(verb)s@end verbatim
661 ''',
662     },
663 }
664
665 #
666 # Maintain line numbers.
667 #
668
669 ## TODO
670 if 0:
671     for f in [HTML, LATEX]:
672         for s in (QUOTE, VERBATIM):
673             output[f][s] = output[f][s].replace("\n"," ")
674
675
676 PREAMBLE_LY = '''%%%% Generated by %(program_name)s
677 %%%% Options: [%(option_string)s]
678 \\include "lilypond-book-preamble.ly"
679 %(preamble_string)s
680
681
682
683
684
685
686 %% ****************************************************************
687 %% Start cut-&-pastable-section 
688 %% ****************************************************************
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)[^\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         str += self.output_info ()
1261
1262 #                str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n')
1263 #                str += ('@tex\n' + self.output_latex () + '\n@end tex\n')
1264 #                str += ('@html\n' + self.output_html () + '\n@end html\n')
1265
1266         if QUOTE in self.option_dict:
1267             str = output[TEXINFO][QUOTE] % vars ()
1268
1269         # need par after image
1270         str += '\n'
1271
1272         return str
1273
1274 class Lilypond_file_snippet (Lilypond_snippet):
1275     def ly (self):
1276         name = self.substring ('filename')
1277         contents = open (find_file (name)).read ()
1278         return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
1279                 % (name, contents))
1280
1281 snippet_type_to_class = {
1282     'lilypond_file': Lilypond_file_snippet,
1283     'lilypond_block': Lilypond_snippet,
1284     'lilypond': Lilypond_snippet,
1285     'include': Include_snippet,
1286 }
1287
1288 def find_linestarts (s):
1289     nls = [0]
1290     start = 0
1291     end = len (s)
1292     while 1:
1293         i = s.find ('\n', start)
1294         if i < 0:
1295             break
1296
1297         i = i + 1
1298         nls.append (i)
1299         start = i
1300
1301     nls.append (len (s))
1302     return nls
1303
1304 def find_toplevel_snippets (s, types):
1305     res = {}
1306     for i in types:
1307         res[i] = ly.re.compile (snippet_res[global_options.format][i])
1308
1309     snippets = []
1310     index = 0
1311     found = dict ([(t, None) for t in types])
1312
1313     line_starts = find_linestarts (s)
1314     line_start_idx = 0
1315     # We want to search for multiple regexes, without searching
1316     # the string multiple times for one regex.
1317     # Hence, we use earlier results to limit the string portion
1318     # where we search.
1319     # Since every part of the string is traversed at most once for
1320     # every type of snippet, this is linear.
1321
1322     while 1:
1323         first = None
1324         endex = 1 << 30
1325         for type in types:
1326             if not found[type] or found[type][0] < index:
1327                 found[type] = None
1328                 
1329                 m = res[type].search (s[index:endex])
1330                 if not m:
1331                     continue
1332
1333                 cl = Snippet
1334                 if snippet_type_to_class.has_key (type):
1335                     cl = snippet_type_to_class[type]
1336
1337
1338                 start = index + m.start ('match')
1339                 line_number = line_start_idx
1340                 while (line_starts[line_number] < start):
1341                     line_number += 1
1342
1343                 line_number += 1
1344                 snip = cl (type, m, global_options.format, line_number)
1345
1346                 found[type] = (start, snip)
1347
1348             if found[type] \
1349              and (not first \
1350                 or found[type][0] < found[first][0]):
1351                 first = type
1352
1353                 # FIXME.
1354
1355                 # Limiting the search space is a cute
1356                 # idea, but this *requires* to search
1357                 # for possible containing blocks
1358                 # first, at least as long as we do not
1359                 # search for the start of blocks, but
1360                 # always/directly for the entire
1361                 # @block ... @end block.
1362
1363                 endex = found[first][0]
1364
1365         if not first:
1366             snippets.append (Substring (s, index, len (s), line_start_idx))
1367             break
1368
1369         while (start > line_starts[line_start_idx+1]):
1370             line_start_idx += 1
1371
1372         (start, snip) = found[first]
1373         snippets.append (Substring (s, index, start, line_start_idx + 1))
1374         snippets.append (snip)
1375         found[first] = None
1376         index = start + len (snip.match.group ('match'))
1377
1378     return snippets
1379
1380 def filter_pipe (input, cmd):
1381     if global_options.verbose:
1382         progress (_ ("Opening filter `%s'") % cmd)
1383
1384     (stdin, stdout, stderr) = os.popen3 (cmd)
1385     stdin.write (input)
1386     status = stdin.close ()
1387
1388     if not status:
1389         status = 0
1390         output = stdout.read ()
1391         status = stdout.close ()
1392         error = stderr.read ()
1393
1394     if not status:
1395         status = 0
1396     signal = 0x0f & status
1397     if status or (not output and error):
1398         exit_status = status >> 8
1399         error (_ ("`%s' failed (%d)") % (cmd, exit_status))
1400         error (_ ("The error log is as follows:"))
1401         sys.stderr.write (error)
1402         sys.stderr.write (stderr.read ())
1403         exit (status)
1404
1405     if global_options.verbose:
1406         progress ('\n')
1407
1408     return output
1409
1410 def run_filter (s):
1411     return filter_pipe (s, global_options.filter_cmd)
1412
1413 def is_derived_class (cl, baseclass):
1414     if cl == baseclass:
1415         return 1
1416     for b in cl.__bases__:
1417         if is_derived_class (b, baseclass):
1418             return 1
1419     return 0
1420
1421 def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets):
1422     ly_names = filter (lambda x: x,
1423                        map (Lilypond_snippet.basename, ly_snippets))
1424     texstr_names = filter (lambda x: x,
1425                            map (Lilypond_snippet.basename, texstr_snippets))
1426     
1427     png_names = filter (lambda x: x,
1428                         map (Lilypond_snippet.basename, png_snippets))
1429
1430     status = 0
1431     def my_system (cmd):
1432         status = ly.system (cmd,
1433                             be_verbose=global_options.verbose, 
1434                             progress_p=1)
1435
1436     if global_options.format in (HTML, TEXINFO) and '--formats' not in cmd:
1437         cmd += ' --formats=png '
1438     elif global_options.format in (DOCBOOK) and '--formats' not in cmd:
1439         cmd += ' --formats=png,pdf '
1440
1441         
1442     # UGH
1443     # the --process=CMD switch is a bad idea
1444     # it is too generic for lilypond-book.
1445     if texstr_names:
1446         my_system (string.join ([cmd, '--backend texstr',
1447                                  'snippet-map.ly'] + texstr_names))
1448         for l in texstr_names:
1449             my_system ('latex %s.texstr' % l)
1450
1451     if ly_names:
1452         open ('snippet-names', 'wb').write ('\n'.join (['snippet-map.ly']
1453                                                       + ly_names))
1454         
1455         my_system (string.join ([cmd, 'snippet-names']))
1456
1457
1458 LATEX_INSPECTION_DOCUMENT = r'''
1459 \nonstopmode
1460 %(preamble)s
1461 \begin{document}
1462 \typeout{textwidth=\the\textwidth}
1463 \typeout{columnsep=\the\columnsep}
1464 \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother
1465 \end{document}
1466 '''
1467
1468 # Do we need anything else besides `textwidth'?
1469 def get_latex_textwidth (source):
1470     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
1471     if m == None:
1472         warning (_ ("cannot find \\begin{document} in LaTeX document"))
1473         
1474         ## what's a sensible default?
1475         return 550.0
1476     
1477     preamble = source[:m.start (0)]
1478     latex_document = LATEX_INSPECTION_DOCUMENT % vars ()
1479     
1480     (handle, tmpfile) = tempfile.mkstemp('.tex')
1481     logfile = os.path.splitext (tmpfile)[0] + '.log'
1482     logfile = os.path.split (logfile)[1]
1483
1484     tmp_handle = os.fdopen (handle,'w')
1485     tmp_handle.write (latex_document)
1486     tmp_handle.close ()
1487     
1488     ly.system ('latex %s' % tmpfile, be_verbose=global_options.verbose)
1489     parameter_string = open (logfile).read()
1490     
1491     os.unlink (tmpfile)
1492     os.unlink (logfile)
1493
1494     columns = 0
1495     m = re.search ('columns=([0-9.]*)', parameter_string)
1496     if m:
1497         columns = int (m.group (1))
1498
1499     columnsep = 0
1500     m = re.search ('columnsep=([0-9.]*)pt', parameter_string)
1501     if m:
1502         columnsep = float (m.group (1))
1503
1504     textwidth = 0
1505     m = re.search ('textwidth=([0-9.]*)pt', parameter_string)
1506     if m:
1507         textwidth = float (m.group (1))
1508         if columns:
1509             textwidth = (textwidth - columnsep) / columns
1510
1511     return textwidth
1512
1513 def modify_preamble (chunk):
1514     str = chunk.replacement_text ()
1515     if (re.search (r"\\begin *{document}", str)
1516       and not re.search ("{graphic[sx]", str)):
1517         str = re.sub (r"\\begin{document}",
1518                r"\\usepackage{graphics}" + '\n'
1519                + r"\\begin{document}",
1520                str)
1521         chunk.override_text = str 
1522         
1523     
1524
1525 ext2format = {
1526     '.html': HTML,
1527     '.itely': TEXINFO,
1528     '.latex': LATEX,
1529     '.lytex': LATEX,
1530     '.tely': TEXINFO,
1531     '.tex': LATEX,
1532     '.texi': TEXINFO,
1533     '.texinfo': TEXINFO,
1534     '.xml': HTML,
1535     '.lyxml': DOCBOOK
1536 }
1537
1538 format2ext = {
1539     HTML: '.html',
1540     # TEXINFO: '.texinfo',
1541     TEXINFO: '.texi',
1542     LATEX: '.tex',
1543     DOCBOOK: '.xml'
1544 }
1545
1546 class Compile_error:
1547     pass
1548
1549 def write_file_map (lys, name):
1550     snippet_map = open ('snippet-map.ly', 'w')
1551     snippet_map.write ("""
1552 #(define version-seen #t)
1553 #(define output-empty-score-list #f)
1554 #(ly:add-file-name-alist '(
1555 """)
1556     for ly in lys:
1557         snippet_map.write ('("%s.ly" . "%s")\n'
1558                  % (ly.basename (),
1559                    name))
1560
1561     snippet_map.write ('))\n')
1562
1563 def do_process_cmd (chunks, input_name):
1564     all_lys = filter (lambda x: is_derived_class (x.__class__,
1565                            Lilypond_snippet),
1566                       chunks)
1567
1568     write_file_map (all_lys, input_name)
1569     ly_outdated = filter (lambda x: is_derived_class (x.__class__,
1570                                                       Lilypond_snippet)
1571                           and x.ly_is_outdated (), chunks)
1572     texstr_outdated = filter (lambda x: is_derived_class (x.__class__,
1573                                                           Lilypond_snippet)
1574                               and x.texstr_is_outdated (),
1575                               chunks)
1576     png_outdated = filter (lambda x: is_derived_class (x.__class__,
1577                                                         Lilypond_snippet)
1578                            and x.png_is_outdated (),
1579                            chunks)
1580
1581     outdated = png_outdated + texstr_outdated + ly_outdated
1582     
1583     progress (_ ("Writing snippets..."))
1584     map (Lilypond_snippet.write_ly, ly_outdated)
1585     progress ('\n')
1586
1587     if outdated:
1588         progress (_ ("Processing..."))
1589         progress ('\n')
1590         process_snippets (global_options.process_cmd, ly_outdated, texstr_outdated, png_outdated)
1591     else:
1592         progress (_ ("All snippets are up to date..."))
1593     progress ('\n')
1594
1595 def guess_format (input_filename):
1596     format = None
1597     e = os.path.splitext (input_filename)[1]
1598     if e in ext2format.keys ():
1599         # FIXME
1600         format = ext2format[e]
1601     else:
1602         error (_ ("cannot determine format for: %s" \
1603               % input_filename))
1604         exit (1)
1605     return format
1606
1607 def write_if_updated (file_name, lines):
1608     try:
1609         f = open (file_name)
1610         oldstr = f.read ()
1611         new_str = string.join (lines, '')
1612         if oldstr == new_str:
1613             progress (_ ("%s is up to date.") % file_name)
1614             progress ('\n')
1615             return
1616     except:
1617         pass
1618
1619     progress (_ ("Writing `%s'...") % file_name)
1620     open (file_name, 'w').writelines (lines)
1621     progress ('\n')
1622
1623 def note_input_file (name, inputs=[]):
1624     ## hack: inputs is mutable!
1625     inputs.append (name)
1626     return inputs
1627
1628 def samefile (f1, f2):
1629     try:
1630         return os.path.samefile (f1, f2)
1631     except AttributeError:                # Windoze
1632         f1 = re.sub ("//*", "/", f1)
1633         f2 = re.sub ("//*", "/", f2)
1634         return f1 == f2
1635
1636 def do_file (input_filename):
1637     # Ugh.
1638     if not input_filename or input_filename == '-':
1639         in_handle = sys.stdin
1640         input_fullname = '<stdin>'
1641     else:
1642         if os.path.exists (input_filename):
1643             input_fullname = input_filename
1644         elif global_options.format == LATEX and ly.search_exe_path ('kpsewhich'):
1645             input_fullname = os.popen ('kpsewhich ' + input_filename).read()[:-1]
1646         else:
1647             input_fullname = find_file (input_filename)
1648
1649         note_input_file (input_fullname)
1650         in_handle = open (input_fullname)
1651
1652     if input_filename == '-':
1653         input_base = 'stdin'
1654     else:
1655         input_base = os.path.basename \
1656                      (os.path.splitext (input_filename)[0])
1657
1658     # Only default to stdout when filtering.
1659     if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd):
1660         output_filename = '-'
1661         output_file = sys.stdout
1662     else:
1663         # don't complain when global_options.output_name is existing
1664         output_filename = input_base + format2ext[global_options.format]
1665         if global_options.output_name:
1666             if not os.path.isdir (global_options.output_name):
1667                 os.mkdir (global_options.output_name, 0777)
1668             os.chdir (global_options.output_name)
1669         else: 
1670             if (os.path.exists (input_filename) 
1671                 and os.path.exists (output_filename) 
1672                 and samefile (output_filename, input_fullname)):
1673              error (
1674              _ ("Output would overwrite input file; use --output."))
1675              exit (2)
1676
1677     try:
1678         progress (_ ("Reading %s...") % input_fullname)
1679         source = in_handle.read ()
1680         progress ('\n')
1681
1682         set_default_options (source)
1683
1684
1685         # FIXME: Containing blocks must be first, see
1686         #        find_toplevel_snippets.
1687         snippet_types = (
1688             'multiline_comment',
1689             'verbatim',
1690             'lilypond_block',
1691     #                'verb',
1692             'singleline_comment',
1693             'lilypond_file',
1694             'include',
1695             'lilypond',
1696         )
1697         progress (_ ("Dissecting..."))
1698         chunks = find_toplevel_snippets (source, snippet_types)
1699
1700         if global_options.format == LATEX:
1701             for c in chunks:
1702                 if (c.is_plain () and
1703                   re.search (r"\\begin *{document}", c.replacement_text())):
1704                     modify_preamble (c)
1705                     break
1706         progress ('\n')
1707
1708         if global_options.filter_cmd:
1709             write_if_updated (output_filename,
1710                      [c.filter_text () for c in chunks])
1711         elif global_options.process_cmd:
1712             do_process_cmd (chunks, input_fullname)
1713             progress (_ ("Compiling %s...") % output_filename)
1714             progress ('\n')
1715             write_if_updated (output_filename,
1716                      [s.replacement_text ()
1717                      for s in chunks])
1718         
1719         def process_include (snippet):
1720             os.chdir (original_dir)
1721             name = snippet.substring ('filename')
1722             progress (_ ("Processing include: %s") % name)
1723             progress ('\n')
1724             return do_file (name)
1725
1726         include_chunks = map (process_include,
1727                    filter (lambda x: is_derived_class (x.__class__,
1728                                      Include_snippet),
1729                        chunks))
1730
1731
1732         return chunks + reduce (lambda x,y: x + y, include_chunks, [])
1733         
1734     except Compile_error:
1735         os.chdir (original_dir)
1736         progress (_ ("Removing `%s'") % output_filename)
1737         progress ('\n')
1738         raise Compile_error
1739
1740 def do_options ():
1741
1742     global global_options
1743
1744     opt_parser = get_option_parser()
1745     (global_options, args) = opt_parser.parse_args ()
1746
1747     if global_options.format in ('texi-html', 'texi'):
1748         global_options.format = TEXINFO
1749     global_options.use_hash = True
1750
1751     global_options.include_path =  map (os.path.abspath, global_options.include_path)
1752     
1753     if global_options.warranty:
1754         warranty ()
1755         exit (0)
1756     if not args or len (args) > 1:
1757         opt_parser.print_help ()
1758         exit (2)
1759         
1760     return args
1761
1762 def psfonts_warning (options, basename):
1763     if options.format in (TEXINFO, LATEX):
1764         psfonts_file = os.path.join (options.output_name, basename + '.psfonts')
1765         output = os.path.join (options.output_name, basename +  '.dvi' )
1766
1767         if not options.create_pdf:
1768             if not options.psfonts:
1769                 warning (_ ("option --psfonts not used"))
1770                 warning (_ ("processing with dvips will have no fonts"))
1771             else:
1772                 progress ('\n')
1773                 progress (_ ("DVIPS usage:"))
1774                 progress ('\n')
1775                 progress ("    dvips -h %(psfonts_file)s %(output)s" % vars ())
1776                 progress ('\n')
1777
1778 def main ():
1779     # FIXME: 85 lines of `main' macramee??
1780     files = do_options ()
1781
1782     file = files[0]
1783
1784     basename = os.path.splitext (file)[0]
1785     basename = os.path.split (basename)[1]
1786     
1787     if not global_options.format:
1788         global_options.format = guess_format (files[0])
1789
1790     formats = 'ps'
1791     if global_options.format in (TEXINFO, HTML, DOCBOOK):
1792         formats += ',png'
1793
1794         
1795     if global_options.process_cmd == '':
1796         global_options.process_cmd = (lilypond_binary 
1797                                       + ' --formats=%s --backend eps ' % formats)
1798
1799     if global_options.process_cmd:
1800         global_options.process_cmd += string.join ([(' -I %s' % ly.mkarg (p))
1801                               for p in global_options.include_path])
1802
1803     if global_options.format in (TEXINFO, LATEX):
1804         ## prevent PDF from being switched on by default.
1805         global_options.process_cmd += ' --formats=eps '
1806         if global_options.create_pdf:
1807             global_options.process_cmd += "--pdf -dinclude-eps-fonts -dgs-load-fonts "
1808     
1809     if global_options.verbose:
1810         global_options.process_cmd += " --verbose "
1811
1812     if global_options.padding_mm:
1813         global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm
1814         
1815     global_options.process_cmd += " -dread-file-list "
1816
1817     identify ()
1818
1819     try:
1820         chunks = do_file (file)
1821         if global_options.psfonts:
1822             fontextract.verbose = global_options.verbose
1823             snippet_chunks = filter (lambda x: is_derived_class (x.__class__,
1824                                        Lilypond_snippet),
1825                         chunks)
1826
1827             psfonts_file = basename + '.psfonts' 
1828             if not global_options.verbose:
1829                 progress (_ ("Writing fonts to %s...") % psfonts_file)
1830             fontextract.extract_fonts (psfonts_file,
1831                          [x.basename() + '.eps'
1832                           for x in snippet_chunks])
1833             if not global_options.verbose:
1834                 progress ('\n')
1835             
1836     except Compile_error:
1837         exit (1)
1838
1839     psfonts_warning (global_options, basename)
1840
1841     inputs = note_input_file ('')
1842     inputs.pop ()
1843
1844     base_file_name = os.path.splitext (os.path.basename (file))[0]
1845     dep_file = os.path.join (global_options.output_name, base_file_name + '.dep')
1846     final_output_file = os.path.join (global_options.output_name,
1847                      base_file_name
1848                      + '.%s' % global_options.format)
1849     
1850     os.chdir (original_dir)
1851     open (dep_file, 'w').write ('%s: %s' % (final_output_file, ' '.join (inputs)))
1852
1853 if __name__ == '__main__':
1854     main ()