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