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