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