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