]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
* scm/translation-functions.scm (format-bass-figure): inspect stream-event.
[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 \catcode`\@=12%%
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 string.find (i, '=') > 0:
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         my_system (string.join ([cmd, 'snippet-map.ly'] + ly_names))
1386
1387 LATEX_INSPECTION_DOCUMENT = r'''
1388 \nonstopmode
1389 %(preamble)s
1390 \begin{document}
1391 \typeout{textwidth=\the\textwidth}
1392 \typeout{columnsep=\the\columnsep}
1393 \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother
1394 \end{document}
1395 '''
1396
1397 # Do we need anything else besides `textwidth'?
1398 def get_latex_textwidth (source):
1399     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
1400     if m == None:
1401         warning (_ ("Can't find \\begin{document} in LaTeX document"))
1402         
1403         ## what's a sensible default?
1404         return 550.0
1405     
1406     preamble = source[:m.start (0)]
1407     latex_document = LATEX_INSPECTION_DOCUMENT % vars ()
1408     
1409     (handle, tmpfile) = tempfile.mkstemp('.tex')
1410     logfile = os.path.splitext (tmpfile)[0] + '.log'
1411     logfile = os.path.split (logfile)[1]
1412
1413     tmp_handle = os.fdopen (handle,'w')
1414     tmp_handle.write (latex_document)
1415     tmp_handle.close ()
1416     
1417     ly.system ('latex %s' % tmpfile, be_verbose=global_options.verbose)
1418     parameter_string = open (logfile).read()
1419     
1420     os.unlink (tmpfile)
1421     os.unlink (logfile)
1422
1423     columns = 0
1424     m = re.search ('columns=([0-9.]*)', parameter_string)
1425     if m:
1426         columns = int (m.group (1))
1427
1428     columnsep = 0
1429     m = re.search ('columnsep=([0-9.]*)pt', parameter_string)
1430     if m:
1431         columnsep = float (m.group (1))
1432
1433     textwidth = 0
1434     m = re.search ('textwidth=([0-9.]*)pt', parameter_string)
1435     if m:
1436         textwidth = float (m.group (1))
1437         if columns:
1438             textwidth = (textwidth - columnsep) / columns
1439
1440     return textwidth
1441
1442 def modify_preamble (chunk):
1443     str = chunk.replacement_text ()
1444     if (re.search (r"\\begin *{document}", str)
1445       and not re.search ("{graphic[sx]", str)):
1446         str = re.sub (r"\\begin{document}",
1447                r"\\usepackage{graphics}" + '\n'
1448                + r"\\begin{document}",
1449                str)
1450         chunk.override_text = str 
1451         
1452     
1453
1454 ext2format = {
1455     '.html': HTML,
1456     '.itely': TEXINFO,
1457     '.latex': LATEX,
1458     '.lytex': LATEX,
1459     '.tely': TEXINFO,
1460     '.tex': LATEX,
1461     '.texi': TEXINFO,
1462     '.texinfo': TEXINFO,
1463     '.xml': HTML,
1464 }
1465
1466 format2ext = {
1467     HTML: '.html',
1468     # TEXINFO: '.texinfo',
1469     TEXINFO: '.texi',
1470     LATEX: '.tex',
1471 }
1472
1473 class Compile_error:
1474     pass
1475
1476 def write_file_map (lys, name):
1477     snippet_map = open ('snippet-map.ly', 'w')
1478     snippet_map.write ("""
1479 #(define version-seen #t)
1480 #(ly:add-file-name-alist '(
1481 """)
1482     for ly in lys:
1483         snippet_map.write ('("%s.ly" . "%s")\n'
1484                  % (ly.basename (),
1485                    name))
1486
1487     snippet_map.write ('))\n')
1488
1489 def do_process_cmd (chunks, input_name):
1490     all_lys = filter (lambda x: is_derived_class (x.__class__,
1491                            Lilypond_snippet),
1492              chunks)
1493
1494     write_file_map (all_lys, input_name)
1495     ly_outdated = filter (lambda x: is_derived_class (x.__class__,
1496                                                       Lilypond_snippet)
1497                           and x.ly_is_outdated (), chunks)
1498     texstr_outdated = filter (lambda x: is_derived_class (x.__class__,
1499                                                           Lilypond_snippet)
1500                               and x.texstr_is_outdated (),
1501                               chunks)
1502     png_outdated = filter (lambda x: is_derived_class (x.__class__,
1503                                                         Lilypond_snippet)
1504                            and x.png_is_outdated (),
1505                            chunks)
1506
1507     outdated = png_outdated + texstr_outdated + ly_outdated
1508     
1509     progress (_ ("Writing snippets..."))
1510     map (Lilypond_snippet.write_ly, ly_outdated)
1511     progress ('\n')
1512
1513     if outdated:
1514         progress (_ ("Processing..."))
1515         progress ('\n')
1516         process_snippets (global_options.process_cmd, ly_outdated, texstr_outdated, png_outdated)
1517     else:
1518         progress (_ ("All snippets are up to date..."))
1519     progress ('\n')
1520
1521 def guess_format (input_filename):
1522     format = None
1523     e = os.path.splitext (input_filename)[1]
1524     if e in ext2format.keys ():
1525         # FIXME
1526         format = ext2format[e]
1527     else:
1528         error (_ ("can't determine format for: %s" \
1529               % input_filename))
1530         exit (1)
1531     return format
1532
1533 def write_if_updated (file_name, lines):
1534     try:
1535         f = open (file_name)
1536         oldstr = f.read ()
1537         new_str = string.join (lines, '')
1538         if oldstr == new_str:
1539             progress (_ ("%s is up to date.") % file_name)
1540             progress ('\n')
1541             return
1542     except:
1543         pass
1544
1545     progress (_ ("Writing `%s'...") % file_name)
1546     open (file_name, 'w').writelines (lines)
1547     progress ('\n')
1548
1549 def note_input_file (name, inputs=[]):
1550     ## hack: inputs is mutable!
1551     inputs.append (name)
1552     return inputs
1553
1554 def samefile (f1, f2):
1555     try:
1556         return os.path.samefile (f1, f2)
1557     except AttributeError:                # Windoze
1558         f1 = re.sub ("//*", "/", f1)
1559         f2 = re.sub ("//*", "/", f2)
1560         return f1 == f2
1561
1562 def do_file (input_filename):
1563     # Ugh.
1564     if not input_filename or input_filename == '-':
1565         in_handle = sys.stdin
1566         input_fullname = '<stdin>'
1567     else:
1568         if os.path.exists (input_filename):
1569             input_fullname = input_filename
1570         elif global_options.format == LATEX and ly.search_exe_path ('kpsewhich'):
1571             input_fullname = os.popen ('kpsewhich ' + input_filename).read()[:-1]
1572         else:
1573             input_fullname = find_file (input_filename)
1574
1575         note_input_file (input_fullname)
1576         in_handle = open (input_fullname)
1577
1578     if input_filename == '-':
1579         input_base = 'stdin'
1580     else:
1581         input_base = os.path.basename \
1582                      (os.path.splitext (input_filename)[0])
1583
1584     # Only default to stdout when filtering.
1585     if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd):
1586         output_filename = '-'
1587         output_file = sys.stdout
1588     else:
1589         # don't complain when global_options.output_name is existing
1590         output_filename = input_base + format2ext[global_options.format]
1591         if global_options.output_name:
1592             if not os.path.isdir (global_options.output_name):
1593                 os.mkdir (global_options.output_name, 0777)
1594             os.chdir (global_options.output_name)
1595         else: 
1596             if (os.path.exists (input_filename) 
1597                 and os.path.exists (output_filename) 
1598                 and samefile (output_filename, input_fullname)):
1599              error (
1600              _ ("Output would overwrite input file; use --output."))
1601              exit (2)
1602
1603     try:
1604         progress (_ ("Reading %s...") % input_fullname)
1605         source = in_handle.read ()
1606         progress ('\n')
1607
1608         set_default_options (source)
1609
1610
1611         # FIXME: Containing blocks must be first, see
1612         #        find_toplevel_snippets.
1613         snippet_types = (
1614             'multiline_comment',
1615             'verbatim',
1616             'lilypond_block',
1617     #                'verb',
1618             'singleline_comment',
1619             'lilypond_file',
1620             'include',
1621             'lilypond',
1622         )
1623         progress (_ ("Dissecting..."))
1624         chunks = find_toplevel_snippets (source, snippet_types)
1625
1626         if global_options.format == LATEX:
1627             for c in chunks:
1628                 if (c.is_plain () and
1629                   re.search (r"\\begin *{document}", c.replacement_text())):
1630                     modify_preamble (c)
1631                     break
1632         progress ('\n')
1633
1634         if global_options.filter_cmd:
1635             write_if_updated (output_filename,
1636                      [c.filter_text () for c in chunks])
1637         elif global_options.process_cmd:
1638             do_process_cmd (chunks, input_fullname)
1639             progress (_ ("Compiling %s...") % output_filename)
1640             progress ('\n')
1641             write_if_updated (output_filename,
1642                      [s.replacement_text ()
1643                      for s in chunks])
1644         
1645         def process_include (snippet):
1646             os.chdir (original_dir)
1647             name = snippet.substring ('filename')
1648             progress (_ ("Processing include: %s") % name)
1649             progress ('\n')
1650             return do_file (name)
1651
1652         include_chunks = map (process_include,
1653                    filter (lambda x: is_derived_class (x.__class__,
1654                                      Include_snippet),
1655                        chunks))
1656
1657
1658         return chunks + reduce (lambda x,y: x + y, include_chunks, [])
1659         
1660     except Compile_error:
1661         os.chdir (original_dir)
1662         progress (_ ("Removing `%s'") % output_filename)
1663         progress ('\n')
1664         raise Compile_error
1665
1666 def do_options ():
1667
1668     global global_options
1669
1670     opt_parser = get_option_parser()
1671     (global_options, args) = opt_parser.parse_args ()
1672
1673     if global_options.format in ('texi-html', 'texi'):
1674         global_options.format = TEXINFO
1675     global_options.use_hash = True
1676
1677     global_options.include_path =  map (os.path.abspath, global_options.include_path)
1678     
1679     if global_options.warranty:
1680         warranty ()
1681         exit (0)
1682     if not args or len (args) > 1:
1683         opt_parser.print_help ()
1684         exit (2)
1685         
1686     return args
1687
1688 def main ():
1689     files = do_options ()
1690
1691     file = files[0]
1692
1693     basename = os.path.splitext (file)[0]
1694     basename = os.path.split (basename)[1]
1695     
1696     if not global_options.format:
1697         global_options.format = guess_format (files[0])
1698
1699     formats = 'ps'
1700     if global_options.format in (TEXINFO, HTML):
1701         formats += ',png'
1702
1703         
1704     if global_options.process_cmd == '':
1705         global_options.process_cmd = (lilypond_binary 
1706                                       + ' --formats=%s --backend eps ' % formats)
1707
1708     if global_options.process_cmd:
1709         global_options.process_cmd += string.join ([(' -I %s' % commands.mkarg (p))
1710                               for p in global_options.include_path])
1711
1712     if (global_options.format in (TEXINFO, LATEX)
1713         and global_options.create_pdf):
1714         global_options.process_cmd += "--pdf  -deps-font-include -dgs-font-load "
1715
1716     if global_options.verbose:
1717         global_options.process_cmd += " --verbose "
1718     identify ()
1719
1720     try:
1721         chunks = do_file (file)
1722         if global_options.psfonts:
1723             fontextract.verbose = global_options.verbose
1724             snippet_chunks = filter (lambda x: is_derived_class (x.__class__,
1725                                        Lilypond_snippet),
1726                         chunks)
1727
1728             psfonts_file = basename + '.psfonts' 
1729             if not global_options.verbose:
1730                 progress (_ ("Writing fonts to %s...") % psfonts_file)
1731             fontextract.extract_fonts (psfonts_file,
1732                          [x.basename() + '.eps'
1733                           for x in snippet_chunks])
1734             if not global_options.verbose:
1735                 progress ('\n')
1736             
1737     except Compile_error:
1738         exit (1)
1739
1740     if global_options.format in (TEXINFO, LATEX):
1741         psfonts_file = os.path.join (global_options.output_name, basename + '.psfonts')
1742         output = os.path.join (global_options.output_name, basename +  '.dvi' )
1743         
1744         if not global_options.psfonts and not global_options.create_pdf:
1745             warning (_ ("option --psfonts not used"))
1746             warning (_ ("processing with dvips will have no fonts"))
1747         else:
1748             progress ('\n')
1749             progress (_ ("DVIPS usage:"))
1750             progress ('\n')
1751             progress ("    dvips -h %(psfonts_file)s %(output)s" % vars ())
1752             progress ('\n')
1753
1754     inputs = note_input_file ('')
1755     inputs.pop ()
1756
1757     base_file_name = os.path.splitext (os.path.basename (file))[0]
1758     dep_file = os.path.join (global_options.output_name, base_file_name + '.dep')
1759     final_output_file = os.path.join (global_options.output_name,
1760                      base_file_name
1761                      + '.%s' % global_options.format)
1762     
1763     os.chdir (original_dir)
1764     open (dep_file, 'w').write ('%s: %s' % (final_output_file, ' '.join (inputs)))
1765
1766 if __name__ == '__main__':
1767     main ()