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