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