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