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