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