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