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