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