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