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