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