]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
5e6ff2b32a0870e2b2f202e9cad63e950cc8461d
[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="middle" 
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="middle"
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             # output file must be touched in order to be up to date
1648             os.utime (file_name, None)
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 ()