]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
99ddb8f5c31f964d1dd9bc9d896d7ac91c1648f1
[lilypond.git] / scripts / lilypond-book.py
1 #!@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     *  ly-options: intertext?
17     *  --linewidth?
18     *  eps in latex / eps by lilypond -b ps?
19     *  check latex parameters, twocolumn, multicolumn?
20     *  use --png --ps --pdf for making images?
21
22     *  Converting from lilypond-book source, substitute:
23        @mbinclude foo.itely -> @include foo.itely
24        \mbinput -> \input
25
26 '''
27
28 import __main__
29 import glob
30 import stat
31 import string
32
33 # Users of python modules should include this snippet
34 # and customize variables below.
35
36 # We'll suffer this path initialization stuff as long as we don't install
37 # our python packages in <prefix>/lib/pythonX.Y (and don't kludge around
38 # it as we do with teTeX on Red Hat Linux: set some environment variables
39 # (PYTHONPATH) in `etc/profile').
40
41 # If set, LILYPONDPREFIX must take prevalence.
42 # if datadir is not set, we're doing a build and LILYPONDPREFIX.
43 import getopt, os, sys
44 datadir = '@local_lilypond_datadir@'
45 if not os.path.isdir (datadir):
46         datadir = '@lilypond_datadir@'
47 if os.environ.has_key ('LILYPONDPREFIX'):
48         datadir = os.environ['LILYPONDPREFIX']
49         while datadir[-1] == os.sep:
50                 datadir= datadir[:-1]
51
52 sys.path.insert (0, os.path.join (datadir, 'python'))
53
54 # Customize these.
55 #if __name__ == '__main__':
56
57 import lilylib as ly
58 global _;_=ly._
59 global re;re = ly.re
60
61 # Lilylib globals.
62 program_version = '@TOPLEVEL_VERSION@'
63 program_name = sys.argv[0]
64 verbose_p = 0
65 pseudo_filter_p = 0
66 original_dir = os.getcwd ()
67 backend = 'ps'
68
69 help_summary = _ (
70 '''Process LilyPond snippets in hybrid HTML, LaTeX, or texinfo document.
71 Example usage:
72
73    lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
74    lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" BOOK
75    lilypond-book --process='lilypond -I include' BOOK
76 ''')
77
78 copyright = ('Jan Nieuwenhuizen <janneke@gnu.org>',
79              'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
80
81 option_definitions = [
82         (_ ("EXT"), 'f', 'format',
83           _ ('''use output format EXT (texi [default], texi-html,
84                 latex, html)''')),
85         (_ ("FILTER"), 'F', 'filter',
86           _ ("pipe snippets through FILTER [convert-ly -n -]")),
87         ('', 'h', 'help',
88           _ ("print this help")),
89         (_ ("DIR"), 'I', 'include',
90           _ ("add DIR to include path")),
91         (_ ("DIR"), 'o', 'output',
92           _ ("write output to DIR")),
93         (_ ("COMMAND"), 'P', 'process',
94           _ ("process ly_files using COMMAND FILE...")),
95         ('', 'V', 'verbose',
96           _ ("be verbose")),
97         ('', 'v', 'version',
98           _ ("print version information")),
99         ('', 'w', 'warranty',
100           _ ("show warranty and copyright")),
101 ]
102
103 include_path = [ly.abspath (os.getcwd ())]
104 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
105
106 # Only use installed binary when we are installed too.
107 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
108         lilypond_binary = 'lilypond'
109
110 use_hash_p = 1
111 format = 0
112 output_name = 0
113 latex_filter_cmd = 'latex "\\nonstopmode \input /dev/stdin"'
114 filter_cmd = 0
115 process_cmd = ''
116 default_ly_options = {}
117
118 #
119 # Is this pythonic?  Personally, I find this rather #define-nesque. --hwn
120 #
121 AFTER = 'after'
122 BEFORE = 'before'
123 EXAMPLEINDENT = 'exampleindent'
124 FILTER = 'filter'
125 FRAGMENT = 'fragment'
126 HTML = 'html'
127 INDENT = 'indent'
128 LATEX = 'latex'
129 LAYOUT = 'layout'
130 LINEWIDTH = 'linewidth'
131 NOFRAGMENT = 'nofragment'
132 NOINDENT = 'noindent'
133 NOQUOTE = 'noquote'
134 NOTES = 'body'
135 NOTIME = 'notime'
136 OUTPUT = 'output'
137 OUTPUTIMAGE = 'outputimage'
138 PAPER = 'paper'
139 PREAMBLE = 'preamble'
140 PRINTFILENAME = 'printfilename'
141 QUOTE = 'quote'
142 RAGGEDRIGHT = 'raggedright'
143 RELATIVE = 'relative'
144 STAFFSIZE = 'staffsize'
145 TEXIDOC = 'texidoc'
146 TEXINFO = 'texinfo'
147 VERBATIM = 'verbatim'
148
149 # NOTIME has no opposite so it isn't part of this dictionary.
150 # NOQUOTE is used internally only.
151 no_options = {
152         NOFRAGMENT: FRAGMENT,
153         NOINDENT: INDENT,
154 }
155
156 # Recognize special sequences in the input.
157 #
158 #   (?P<name>regex) -- Assign result of REGEX to NAME.
159 #   *? -- Match non-greedily.
160 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
161 #   (?s) -- Make the dot match all characters including newline.
162 #   (?x) -- Ignore whitespace in patterns.
163 no_match = 'a\ba'
164 snippet_res = {
165         ##
166         HTML: {
167                 'include':
168                   no_match,
169
170                 'lilypond':
171                   r'''(?mx)
172                     (?P<match>
173                     <lilypond
174                       (\s*(?P<options>.*?)\s*:)?\s*
175                       (?P<code>.*?)
176                     />)''',
177
178                 'lilypond_block':
179                   r'''(?msx)
180                     (?P<match>
181                     <lilypond
182                       \s*(?P<options>.*?)\s*
183                     >
184                     (?P<code>.*?)
185                     </lilypond>)''',
186
187                 'lilypond_file':
188                   r'''(?mx)
189                     (?P<match>
190                     <lilypondfile
191                       \s*(?P<options>.*?)\s*
192                     >
193                     \s*(?P<filename>.*?)\s*
194                     </lilypondfile>)''',
195
196                 'multiline_comment':
197                   r'''(?smx)
198                     (?P<match>
199                     \s*(?!@c\s+)
200                     (?P<code><!--\s.*?!-->)
201                     \s)''',
202
203                 'singleline_comment':
204                   no_match,
205
206                 'verb':
207                   r'''(?x)
208                     (?P<match>
209                       (?P<code><pre>.*?</pre>))''',
210
211                 'verbatim':
212                   r'''(?x)
213                     (?s)
214                     (?P<match>
215                       (?P<code><pre>\s.*?</pre>\s))''',
216         },
217
218         ##
219         LATEX: {
220                 'include':
221                   r'''(?smx)
222                     ^[^%\n]*?
223                     (?P<match>
224                     \\input\s*{
225                       (?P<filename>\S+?)
226                     })''',
227
228                 'lilypond':
229                   r'''(?smx)
230                     ^[^%\n]*?
231                     (?P<match>
232                     \\lilypond\s*(
233                     \[
234                       \s*(?P<options>.*?)\s*
235                     \])?\s*{
236                       (?P<code>.*?)
237                     })''',
238
239                 'lilypond_block':
240                   r'''(?smx)
241                     ^[^%\n]*?
242                     (?P<match>
243                     \\begin\s*(
244                     \[
245                       \s*(?P<options>.*?)\s*
246                     \])?\s*{lilypond}
247                       (?P<code>.*?)
248                     ^[^%\n]*?
249                     \\end\s*{lilypond})''',
250
251                 'lilypond_file':
252                   r'''(?smx)
253                     ^[^%\n]*?
254                     (?P<match>
255                     \\lilypondfile\s*(
256                     \[
257                       \s*(?P<options>.*?)\s*
258                     \])?\s*\{
259                       (?P<filename>\S+?)
260                     })''',
261
262                 'multiline_comment':
263                   no_match,
264
265                 'singleline_comment':
266                   r'''(?mx)
267                     ^.*?
268                     (?P<match>
269                       (?P<code>
270                       %.*$\n+))''',
271
272                 'verb':
273                   r'''(?mx)
274                     ^[^%\n]*?
275                     (?P<match>
276                       (?P<code>
277                       \\verb(?P<del>.)
278                         .*?
279                       (?P=del)))''',
280
281                 'verbatim':
282                   r'''(?msx)
283                     ^[^%\n]*?
284                     (?P<match>
285                       (?P<code>
286                       \\begin\s*{verbatim}
287                         .*?
288                       \\end\s*{verbatim}))''',
289         },
290
291         ##
292         TEXINFO: {
293                 'include':
294                   r'''(?mx)
295                     ^(?P<match>
296                     @include\s+
297                       (?P<filename>\S+))''',
298
299                 'lilypond':
300                   r'''(?smx)
301                     ^[^\n]*?(?!@c\s+)[^\n]*?
302                     (?P<match>
303                     @lilypond\s*(
304                     \[
305                       \s*(?P<options>.*?)\s*
306                     \])?\s*{
307                       (?P<code>.*?)
308                     })''',
309
310                 'lilypond_block':
311                   r'''(?msx)
312                     ^(?P<match>
313                     @lilypond\s*(
314                     \[
315                       \s*(?P<options>.*?)\s*
316                     \])?\s+?
317                     ^(?P<code>.*?)
318                     ^@end\s+lilypond)\s''',
319
320                 'lilypond_file':
321                   r'''(?mx)
322                     ^(?P<match>
323                     @lilypondfile\s*(
324                     \[
325                       \s*(?P<options>.*?)\s*
326                     \])?\s*{
327                       (?P<filename>\S+)
328                     })''',
329
330                 'multiline_comment':
331                   r'''(?smx)
332                     ^(?P<match>
333                       (?P<code>
334                       @ignore\s
335                         .*?
336                       @end\s+ignore))\s''',
337
338                 'singleline_comment':
339                   r'''(?mx)
340                     ^.*
341                     (?P<match>
342                       (?P<code>
343                       @c([ \t][^\n]*|)\n))''',
344
345         # Don't do this: It interferes with @code{@{}.
346         #       'verb': r'''(?P<code>@code{.*?})''',
347
348                 'verbatim':
349                   r'''(?sx)
350                     (?P<match>
351                       (?P<code>
352                       @example
353                         \s.*?
354                       @end\s+example\s))''',
355         },
356 }
357
358 format_res = {
359         HTML: {
360                 'intertext': r',?\s*intertext=\".*?\"',
361                 'option_sep': '\s*',
362         },
363
364         LATEX: {
365                 'intertext': r',?\s*intertext=\".*?\"',
366                 'option_sep': '\s*,\s*',
367         },
368
369         TEXINFO: {
370                 'intertext': r',?\s*intertext=\".*?\"',
371                 'option_sep': '\s*,\s*',
372         },
373 }
374
375 # Options without a pattern in ly_options.
376 simple_options = [
377         EXAMPLEINDENT,
378         FRAGMENT,
379         NOFRAGMENT,
380         NOINDENT,
381         PRINTFILENAME,
382         TEXIDOC,
383         VERBATIM
384 ]
385
386 ly_options = {
387         ##
388         NOTES: {
389                 RELATIVE: r'''\relative c%(relative_quotes)s''',
390         },
391
392         ##
393         PAPER: {
394                 INDENT: r'''indent = %(indent)s''',
395
396                 LINEWIDTH: r'''linewidth = %(linewidth)s''',
397
398                 QUOTE: r'''linewidth = %(linewidth)s - 2.0 * %(exampleindent)s''',
399
400                 RAGGEDRIGHT: r'''raggedright = ##t''',
401         },
402
403         ##
404         LAYOUT: {
405                 NOTIME: r'''\context {
406     \Staff
407     \remove Time_signature_engraver
408   }''',
409         },
410
411         ##
412         PREAMBLE: {
413                 STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
414         },
415 }
416
417 output = {
418         ##
419         HTML: {
420                 FILTER: r'''<lilypond %(options)s>
421 %(code)s
422 </lilypond>
423 ''',
424
425                 AFTER: r'''
426   </a>
427 </p>''',
428
429                 BEFORE: r'''<p>
430   <a href="%(base)s.ly">''',
431
432                 OUTPUT: r'''
433     <img align="center" valign="center"
434          border="0" src="%(image)s" alt="[image of music]">''',
435
436                 PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
437
438                 QUOTE: r'''<blockquote>
439 %(str)s
440 </blockquote>
441 ''',
442
443                 VERBATIM: r'''<pre>
444 %(verb)s</pre>''',
445         },
446
447         ##
448         LATEX: {
449                 OUTPUT: r'''{%%
450 \parindent 0pt
451 \catcode`\@=12
452 \ifx\preLilyPondExample \undefined
453   \relax
454 \else
455   \preLilyPondExample
456 \fi
457 \def\lilypondbook{}%%
458 \input %(base)s-systems.tex
459 \ifx\postLilyPondExample \undefined
460   \relax
461 \else
462   \postLilyPondExample
463 \fi
464 }''',
465
466                 PRINTFILENAME: '''\\texttt{%(filename)s}
467         ''',
468
469                 QUOTE: r'''\begin{quotation}
470 %(str)s
471 \end{quotation}
472 ''',
473
474                 VERBATIM: r'''\noindent
475 \begin{verbatim}
476 %(verb)s\end{verbatim}
477 ''',
478
479                 FILTER: r'''\begin{lilypond}[%(options)s]
480 %(code)s
481 \end{lilypond}''',
482         },
483
484         ##
485         TEXINFO: {
486                 FILTER: r'''@lilypond[%(options)s]
487 %(code)s
488 @lilypond''',
489
490                 OUTPUT: r'''
491 @iftex
492 @include %(base)s-systems.texi
493 @end iftex''',
494
495                 OUTPUTIMAGE: r'''@noindent
496 @ifnottex
497 @image{%(base)s,,,[image of music],%(ext)s}
498 @end ifnottex
499 ''',
500
501                 PRINTFILENAME: '''@file{%(filename)s}
502         ''',
503
504                 QUOTE: r'''@quotation
505 %(str)s@end quotation
506 ''',
507
508                 NOQUOTE: r'''@format
509 %(str)s@end format
510 ''',
511
512                 VERBATIM: r'''@exampleindent 0
513 @example
514 %(verb)s@end example
515 ''',
516         },
517 }
518
519 PREAMBLE_LY = r'''%%%% Generated by %(program_name)s
520 %%%% Options: [%(option_string)s]
521
522 #(set! toplevel-score-handler ly:parser-print-score)
523 #(set! toplevel-music-handler (lambda (p m)
524                                (ly:parser-print-score
525                                 p (ly:music-scorify m p))))
526
527 #(define version-seen? #t)
528 %(preamble_string)s
529
530 \paper {
531   #(define dump-extents #t)
532   %(paper_string)s
533 }
534
535 \layout {
536   %(layout_string)s
537 }
538 '''
539
540 FRAGMENT_LY = r'''
541 %(notes_string)s
542 {
543 %% ly snippet contents follows:
544 %(code)s
545 %% end ly snippet
546 }
547 '''
548
549 FULL_LY = '''
550 %% ly snippet:
551 %(code)s
552 %% end ly snippet
553 '''
554
555 texinfo_linewidths = {
556         '@afourpaper': '160\\mm',
557         '@afourwide': '6.5\\in',
558         '@afourlatex': '150\\mm',
559         '@smallbook': '5\\in',
560         '@letterpaper': '6\\in',
561 }
562
563 def classic_lilypond_book_compatibility (key, value):
564         if key == 'singleline' and value == None:
565                 return (RAGGEDRIGHT, None)
566
567         m = re.search ('relative\s*([-0-9])', key)
568         if m:
569                 return ('relative', m.group (1))
570
571         m = re.match ('([0-9]+)pt', key)
572         if m:
573                 return ('staffsize', m.group (1))
574
575         if key == 'indent' or key == 'linewidth':
576                 m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value)
577                 if m:
578                         f = float (m.group (1))
579                         return (key, '%f\\%s' % (f, m.group (2)))
580
581         return (None, None)
582
583 def compose_ly (code, options, type):
584         option_dict = {}
585
586         for i in options:
587                 if string.find (i, '=') > 0:
588                         (key, value) = re.split ('\s*=\s*', i)
589                         option_dict[key] = value
590                 else:
591                         if i in no_options.keys ():
592                                 if no_options[i] in option_dict.keys ():
593                                         del option_dict[no_options[i]]
594                         else:
595                                 option_dict[i] = None
596
597         has_linewidth = option_dict.has_key (LINEWIDTH)
598         no_linewidth_value = 0
599
600         # If LINEWIDTH is used without parameter, set it to default.
601         if has_linewidth and option_dict[LINEWIDTH] == None:
602                 no_linewidth_value = 1
603                 del option_dict[LINEWIDTH]
604
605         for i in default_ly_options.keys ():
606                 if i not in option_dict.keys ():
607                         option_dict[i] = default_ly_options[i]
608
609         if not has_linewidth:
610                 if type == 'lilypond' or FRAGMENT in option_dict.keys ():
611                         option_dict[RAGGEDRIGHT] = None
612
613                 if type == 'lilypond':
614                         if LINEWIDTH in option_dict.keys ():
615                                 del option_dict[LINEWIDTH]
616                 else:
617                         if RAGGEDRIGHT in option_dict.keys ():
618                                 if LINEWIDTH in option_dict.keys ():
619                                         del option_dict[LINEWIDTH]
620
621                 if QUOTE in option_dict.keys () or type == 'lilypond':
622                         if LINEWIDTH in option_dict.keys ():
623                                 del option_dict[LINEWIDTH]
624
625         if not INDENT in option_dict.keys ():
626                 option_dict[INDENT] = '0\\mm'
627
628         # The QUOTE pattern from ly_options only emits the `linewidth'
629         # keyword.
630         if has_linewidth and QUOTE in option_dict.keys ():
631                 if no_linewidth_value:
632                         del option_dict[LINEWIDTH]
633                 else:
634                         del option_dict[QUOTE]
635
636         if FRAGMENT in option_dict.keys ():
637                 body = FRAGMENT_LY
638         else:
639                 body = FULL_LY
640
641         # Defaults.
642         relative = 1
643         override = {}
644         # The original concept of the `exampleindent' option is broken.
645         # It is not possible to get a sane value for @exampleindent at all
646         # without processing the document itself.  Saying
647         #
648         #   @exampleindent 0
649         #   @example
650         #   ...
651         #   @end example
652         #   @exampleindent 5
653         #
654         # causes ugly results with the DVI backend of texinfo since the
655         # default value for @exampleindent isn't 5em but 0.4in (or a smaller
656         # value).  Executing the above code changes the environment
657         # indentation to an unknown value because we don't know the amount
658         # of 1em in advance since it is font-dependent.  Modifying
659         # @exampleindent in the middle of a document is simply not
660         # supported within texinfo.
661         #
662         # As a consequence, the only function of @exampleindent is now to
663         # specify the amount of indentation for the `quote' option.
664         #
665         # To set @exampleindent locally to zero, we use the @format
666         # environment for non-quoted snippets.
667         override[EXAMPLEINDENT] = r'0.4\in'
668         override[LINEWIDTH] = texinfo_linewidths['@smallbook']
669         override.update (default_ly_options)
670
671         option_list = []
672         for (key, value) in option_dict.items ():
673                 if value == None:
674                         option_list.append (key)
675                 else:
676                         option_list.append (key + '=' + value)
677         option_string = string.join (option_list, ',')
678
679         compose_dict = {}
680         compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
681         for a in compose_types:
682                 compose_dict[a] = []
683
684         for (key, value) in option_dict.items():
685                 (c_key, c_value) = \
686                   classic_lilypond_book_compatibility (key, value)
687                 if c_key:
688                         if c_value:
689                                 ly.warning \
690                                   (_ ("deprecated ly-option used: %s=%s" \
691                                     % (key, value)))
692                                 ly.warning \
693                                   (_ ("compatibility mode translation: %s=%s" \
694                                     % (c_key, c_value)))
695                         else:
696                                 ly.warning \
697                                   (_ ("deprecated ly-option used: %s" \
698                                     % key))
699                                 ly.warning \
700                                   (_ ("compatibility mode translation: %s" \
701                                     % c_key))
702
703                         (key, value) = (c_key, c_value)
704
705                 if value:
706                         override[key] = value
707                 else:
708                         if not override.has_key (key):
709                                 override[key] = None
710
711                 found = 0
712                 for type in compose_types:
713                         if ly_options[type].has_key (key):
714                                 compose_dict[type].append (ly_options[type][key])
715                                 found = 1
716                                 break
717
718                 if not found and key not in simple_options:
719                         ly.warning (_ ("ignoring unknown ly option: %s") % i)
720
721         # URGS
722         if RELATIVE in override.keys () and override[RELATIVE]:
723                 relative = string.atoi (override[RELATIVE])
724
725         relative_quotes = ''
726
727         # 1 = central C
728         if relative < 0:
729                 relative_quotes += ',' * (- relative)
730         elif relative > 0:
731                 relative_quotes += "'" * relative
732
733         program_name = __main__.program_name
734
735         paper_string = \
736           string.join (compose_dict[PAPER], '\n  ') % override
737         layout_string = \
738           string.join (compose_dict[LAYOUT], '\n  ') % override
739         notes_string = \
740           string.join (compose_dict[NOTES], '\n  ') % vars ()
741         preamble_string = \
742           string.join (compose_dict[PREAMBLE], '\n  ') % override
743
744         return (PREAMBLE_LY + body) % vars ()
745
746
747 def find_file (name):
748         for i in include_path:
749                 full = os.path.join (i, name)
750                 if os.path.exists (full):
751                         return full
752         ly.error (_ ("file not found: %s") % name + '\n')
753         ly.exit (1)
754         return ''
755
756 def verbatim_html (s):
757         return re.sub ('>', '&gt;',
758                        re.sub ('<', '&lt;',
759                                re.sub ('&', '&amp;', s)))
760
761 def verbatim_texinfo (s):
762         return re.sub ('{', '@{',
763                        re.sub ('}', '@}',
764                                re.sub ('@', '@@', s)))
765
766 def split_options (option_string):
767         return re.split (format_res[format]['option_sep'], option_string)
768
769 class Chunk:
770         def replacement_text (self):
771                 return ''
772
773         def filter_text (self):
774                 return self.replacement_text ()
775
776         def ly_is_outdated (self):
777                 return 0
778
779         def png_is_outdated (self):
780                 return 0
781
782 class Substring (Chunk):
783         def __init__ (self, source, start, end):
784                 self.source = source
785                 self.start = start
786                 self.end = end
787
788         def replacement_text (self):
789                 return self.source[self.start:self.end]
790
791 class Snippet (Chunk):
792         def __init__ (self, type, match, format):
793                 self.type = type
794                 self.match = match
795                 self.hash = 0
796                 self.options = []
797                 self.format = format
798
799         def replacement_text (self):
800                 return self.match.group ('match')
801
802         def substring (self, s):
803                 return self.match.group (s)
804
805         def __repr__ (self):
806                 return `self.__class__` + ' type = ' + self.type
807
808 class Include_snippet (Snippet):
809         def processed_filename (self):
810                 f = self.substring ('filename')
811                 return os.path.splitext (f)[0] + format2ext[format]
812
813         def replacement_text (self):
814                 s = self.match.group ('match')
815                 f = self.substring ('filename')
816
817                 return re.sub (f, self.processed_filename (), s)
818
819 class Lilypond_snippet (Snippet):
820         def __init__ (self, type, match, format):
821                 Snippet.__init__ (self, type, match, format)
822                 os = match.group ('options')
823                 if os:
824                         self.options = split_options (os)
825
826         def ly (self):
827                 return self.substring ('code')
828
829         def full_ly (self):
830                 s = self.ly ()
831                 if s:
832                         return compose_ly (s, self.options, self.type)
833                 return ''
834
835         # TODO: Use md5?
836         def get_hash (self):
837                 if not self.hash:
838                         self.hash = abs (hash (self.full_ly ()))
839                 return self.hash
840
841         def basename (self):
842                 if use_hash_p:
843                         return 'lily-%d' % self.get_hash ()
844                 raise 'to be done'
845
846         def write_ly (self):
847                 outf = open (self.basename () + '.ly', 'w')
848                 outf.write (self.full_ly ())
849
850                 open (self.basename () + '.txt', 'w').write ('image of music')
851
852         def ly_is_outdated (self):
853                 base = self.basename ()
854
855                 tex_file = '%s.tex' % base
856                 eps_file = '%s.eps' % base
857                 system_file = '%s-systems.tex' % base
858                 ly_file = '%s.ly' % base
859                 ok = os.path.exists (ly_file) \
860                      and os.path.exists (system_file)\
861                      and os.stat (system_file)[stat.ST_SIZE] \
862                      and re.match ('% eof', open (system_file).readlines ()[-1])
863                 if ok and (use_hash_p \
864                            or self.ly () == open (ly_file).read ()):
865                         # TODO: Do something smart with target formats
866                         #       (ps, png) and m/ctimes.
867                         return None
868                 return self
869
870         def png_is_outdated (self):
871                 base = self.basename ()
872                 ok = self.ly_is_outdated ()
873                 if format == HTML or format == TEXINFO:
874                         ok = ok and (os.path.exists (base + '.png')
875                                      or glob.glob (base + '-page*.png'))
876                 return not ok
877         def texstr_is_outdated (self):
878                 if backend == 'ps':
879                         return 0
880                 
881                 base = self.basename ()
882                 ok = self.ly_is_outdated ()
883                 ok = ok and (os.path.exists (base + '.texstr'))
884                 return not ok
885
886         def filter_text (self):
887                 code = self.substring ('code')
888                 s = run_filter (code)
889                 d = {
890                         'code': s,
891                         'options': self.match.group ('options')
892                 }
893                 # TODO
894                 return output[self.format][FILTER] % d
895
896         def replacement_text (self):
897                 func = Lilypond_snippet.__dict__['output_' + self.format]
898                 return func (self)
899
900         def get_images (self):
901                 base = self.basename ()
902                 # URGUGHUGHUGUGH
903                 single = '%(base)s.png' % vars ()
904                 multiple = '%(base)s-page1.png' % vars ()
905                 images = (single,)
906                 if os.path.exists (multiple) \
907                    and (not os.path.exists (single) \
908                         or (os.stat (multiple)[stat.ST_MTIME] \
909                             > os.stat (single)[stat.ST_MTIME])):
910                         images = glob.glob ('%(base)s-page*.png' % vars ())
911                 return images
912
913         def output_html (self):
914                 str = ''
915                 base = self.basename ()
916                 if format == HTML:
917                         str += self.output_print_filename (HTML)
918                         if VERBATIM in self.options:
919                                 verb = verbatim_html (self.substring ('code'))
920                                 str += write (output[HTML][VERBATIM] % vars ())
921                         if QUOTE in self.options:
922                                 str = output[HTML][QUOTE] % vars ()
923
924                 str += output[HTML][BEFORE] % vars ()
925                 for image in self.get_images ():
926                         (base, ext) = os.path.splitext (image)
927                         str += output[HTML][OUTPUT] % vars ()
928                 str += output[HTML][AFTER] % vars ()
929                 return str
930
931         def output_info (self):
932                 str = ''
933                 for image in self.get_images ():
934                         (base, ext) = os.path.splitext (image)
935
936                         # URG, makeinfo implicitly prepends dot to extension.
937                         # Specifying no extension is most robust.
938                         ext = ''
939                         str += output[TEXINFO][OUTPUTIMAGE] % vars ()
940
941                 base = self.basename()
942                 str += output[format][OUTPUT] % vars()
943                 return str
944
945         def output_latex (self):
946                 str = ''
947                 base = self.basename ()
948                 if format == LATEX:
949                         str += self.output_print_filename (LATEX)
950                         if VERBATIM in self.options:
951                                 verb = self.substring ('code')
952                                 str += (output[LATEX][VERBATIM] % vars ())
953                         if QUOTE in self.options:
954                                 str = output[LATEX][QUOTE] % vars ()
955
956                 str += (output[LATEX][OUTPUT] % vars ())
957                 return str
958
959         def output_print_filename (self, format):
960                 str = ''
961                 if PRINTFILENAME in self.options:
962                         base = self.basename ()
963                         filename = self.substring ('filename')
964                         str = output[format][PRINTFILENAME] % vars ()
965
966                 return str
967
968         def output_texinfo (self):
969                 str = ''
970                 if self.output_print_filename (TEXINFO):
971                         str += ('@html\n'
972                                 + self.output_print_filename (HTML)
973                                 + '\n@end html\n')
974                         str += ('@tex\n'
975                                 + self.output_print_filename (LATEX)
976                                 + '\n@end tex\n')
977                 base = self.basename ()
978                 if TEXIDOC in self.options:
979                         texidoc = base + '.texidoc'
980                         if os.path.exists (texidoc):
981                                 str += '@include %(texidoc)s\n\n' % vars ()
982
983                 if VERBATIM in self.options:
984                         verb = verbatim_texinfo (self.substring ('code'))
985                         str += (output[TEXINFO][VERBATIM] % vars ())
986                         if not QUOTE in self.options:
987                                 str = output[TEXINFO][NOQUOTE] % vars()
988
989                 str += self.output_info ()
990                 
991 #               str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n')
992 #               str += ('@tex\n' + self.output_latex () + '\n@end tex\n')
993 #               str += ('@html\n' + self.output_html () + '\n@end html\n')
994
995                 if QUOTE in self.options:
996                         str = output[TEXINFO][QUOTE] % vars ()
997
998                 # need par after image
999                 str += '\n'
1000
1001                 return str
1002
1003 class Lilypond_file_snippet (Lilypond_snippet):
1004         def ly (self):
1005                 name = self.substring ('filename')
1006                 return '\\renameinput \"%s\"\n%s' \
1007                          % (name, open (find_file (name)).read ())
1008
1009 snippet_type_to_class = {
1010         'lilypond_file': Lilypond_file_snippet,
1011         'lilypond_block': Lilypond_snippet,
1012         'lilypond': Lilypond_snippet,
1013         'include': Include_snippet,
1014 }
1015
1016 def find_toplevel_snippets (s, types):
1017         res = {}
1018         for i in types:
1019                 res[i] = ly.re.compile (snippet_res[format][i])
1020
1021         snippets = []
1022         index = 0
1023         ## found = dict (map (lambda x: (x, None),
1024         ##                    types))
1025         ## urg python2.1
1026         found = {}
1027         map (lambda x, f = found: f.setdefault (x, None),
1028              types)
1029
1030         # We want to search for multiple regexes, without searching
1031         # the string multiple times for one regex.
1032         # Hence, we use earlier results to limit the string portion
1033         # where we search.
1034         # Since every part of the string is traversed at most once for
1035         # every type of snippet, this is linear.
1036
1037         while 1:
1038                 first = None
1039                 endex = 1 << 30
1040                 for type in types:
1041                         if not found[type] or found[type][0] < index:
1042                                 found[type] = None
1043                                 m = res[type].search (s[index:endex])
1044                                 if not m:
1045                                         continue
1046
1047                                 cl = Snippet
1048                                 if snippet_type_to_class.has_key (type):
1049                                         cl = snippet_type_to_class[type]
1050                                 snip = cl (type, m, format)
1051                                 start = index + m.start ('match')
1052                                 found[type] = (start, snip)
1053
1054                         if found[type] \
1055                            and (not first \
1056                                 or found[type][0] < found[first][0]):
1057                                 first = type
1058
1059                                 # FIXME.
1060
1061                                 # Limiting the search space is a cute
1062                                 # idea, but this *requires* to search
1063                                 # for possible containing blocks
1064                                 # first, at least as long as we do not
1065                                 # search for the start of blocks, but
1066                                 # always/directly for the entire
1067                                 # @block ... @end block.
1068
1069                                 endex = found[first][0]
1070
1071                 if not first:
1072                         snippets.append (Substring (s, index, len (s)))
1073                         break
1074
1075                 (start, snip) = found[first]
1076                 snippets.append (Substring (s, index, start))
1077                 snippets.append (snip)
1078                 found[first] = None
1079                 index = start + len (snip.match.group ('match'))
1080
1081         return snippets
1082
1083 def filter_pipe (input, cmd):
1084         if verbose_p:
1085                 ly.progress (_ ("Opening filter `%s'") % cmd)
1086
1087         (stdin, stdout, stderr) = os.popen3 (cmd)
1088         stdin.write (input)
1089         status = stdin.close ()
1090
1091         if not status:
1092                 status = 0
1093                 output = stdout.read ()
1094                 status = stdout.close ()
1095                 error = stderr.read ()
1096
1097         if not status:
1098                 status = 0
1099         signal = 0x0f & status
1100         if status or (not output and error):
1101                 exit_status = status >> 8
1102                 ly.error (_ ("`%s' failed (%d)") % (cmd, exit_status))
1103                 ly.error (_ ("The error log is as follows:"))
1104                 sys.stderr.write (error)
1105                 sys.stderr.write (stderr.read ())
1106                 ly.exit (status)
1107
1108         if verbose_p:
1109                 ly.progress ('\n')
1110
1111         return output
1112
1113 def run_filter (s):
1114         return filter_pipe (s, filter_cmd)
1115
1116 def is_derived_class (cl, baseclass):
1117         if cl == baseclass:
1118                 return 1
1119         for b in cl.__bases__:
1120                 if is_derived_class (b, baseclass):
1121                         return 1
1122         return 0
1123
1124 def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets):
1125         ly_names = filter (lambda x: x,
1126                            map (Lilypond_snippet.basename, ly_snippets))
1127         texstr_names = filter (lambda x: x,
1128                            map (Lilypond_snippet.basename, texstr_snippets))
1129         png_names = filter (lambda x: x,
1130                             map (Lilypond_snippet.basename, png_snippets))
1131
1132         status = 0
1133         def my_system (cmd):
1134                 status = ly.system (cmd,
1135                                     ignore_error = 1, progress_p = 1)
1136
1137                 if status:
1138                         ly.error ('Process %s exited unsuccessfully.' % cmd)
1139                         raise Compile_error
1140
1141         # UGH
1142         # the --process=CMD switch is a bad idea
1143         # it is too generic for lilypond-book.
1144         if texstr_names and re.search ('^[0-9A-Za-z/]*lilypond', cmd):
1145
1146                 my_system (string.join ([cmd + ' --backend texstr ' ] + texstr_names))
1147                 for l in texstr_names:
1148                         my_system ('latex %s.texstr' % l)
1149
1150         if ly_names:
1151                 my_system (string.join ([cmd] + ly_names))
1152
1153 LATEX_DOCUMENT = r'''
1154 %(preamble)s
1155 \begin{document}
1156 \typeout{textwidth=\the\textwidth}
1157 \typeout{columnsep=\the\columnsep}
1158 \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother
1159 \end{document}
1160 '''
1161
1162 # Do we need anything else besides `textwidth'?
1163 def get_latex_textwidth (source):
1164         m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
1165         preamble = source[:m.start (0)]
1166         latex_document = LATEX_DOCUMENT % vars ()
1167         parameter_string = filter_pipe (latex_document, latex_filter_cmd)
1168
1169         columns = 0
1170         m = re.search ('columns=([0-9.]*)', parameter_string)
1171         if m:
1172                 columns = string.atoi (m.group (1))
1173
1174         columnsep = 0
1175         m = re.search ('columnsep=([0-9.]*)pt', parameter_string)
1176         if m:
1177                 columnsep = string.atof (m.group (1))
1178
1179         textwidth = 0
1180         m = re.search ('textwidth=([0-9.]*)pt', parameter_string)
1181         if m:
1182                 textwidth = string.atof (m.group (1))
1183                 if columns:
1184                         textwidth = (textwidth - columnsep) / columns
1185
1186         return textwidth
1187
1188 ext2format = {
1189         '.html': HTML,
1190         '.itely': TEXINFO,
1191         '.latex': LATEX,
1192         '.lytex': LATEX,
1193         '.tely': TEXINFO,
1194         '.tex': LATEX,
1195         '.texi': TEXINFO,
1196         '.texinfo': TEXINFO,
1197         '.xml': HTML,
1198 }
1199
1200 format2ext = {
1201         HTML: '.html',
1202         # TEXINFO: '.texinfo',
1203         TEXINFO: '.texi',
1204         LATEX: '.tex',
1205 }
1206
1207 class Compile_error:
1208         pass
1209
1210 def do_process_cmd (chunks):
1211         ly_outdated = \
1212           filter (lambda x: is_derived_class (x.__class__,
1213                                               Lilypond_snippet)
1214                             and x.ly_is_outdated (),
1215                   chunks)
1216         texstr_outdated = \
1217           filter (lambda x: is_derived_class (x.__class__,
1218                                               Lilypond_snippet)
1219                             and x.texstr_is_outdated (),
1220                   chunks)
1221         png_outdated = \
1222           filter (lambda x: is_derived_class (x.__class__,
1223                                               Lilypond_snippet)
1224                             and x.png_is_outdated (),
1225                   chunks)
1226
1227         ly.progress (_ ("Writing snippets..."))
1228         map (Lilypond_snippet.write_ly, ly_outdated)
1229         ly.progress ('\n')
1230
1231         if ly_outdated:
1232                 ly.progress (_ ("Processing...\n"))
1233                 process_snippets (process_cmd, ly_outdated, texstr_outdated, png_outdated)
1234         else:
1235                 ly.progress (_ ("All snippets are up to date..."))
1236         ly.progress ('\n')
1237
1238 def guess_format (input_filename):
1239         format = None
1240         e = os.path.splitext (input_filename)[1]
1241         if e in ext2format.keys ():
1242                 # FIXME
1243                 format = ext2format[e]
1244         else:
1245                 ly.error (_ ("cannot determine format for: %s" \
1246                              % input_filename))
1247                 ly.exit (1)
1248         return format
1249         
1250 def do_file (input_filename):
1251         # Ugh.
1252         if not input_filename or input_filename == '-':
1253                 in_handle = sys.stdin
1254                 input_fullname = '<stdin>'
1255         else:
1256                 if os.path.exists (input_filename):
1257                         input_fullname = input_filename
1258                 elif format == LATEX:
1259                         # urg python interface to libkpathsea?
1260                         input_fullname = ly.read_pipe ('kpsewhich '
1261                                                        + input_filename)[:-1]
1262                 else:
1263                         input_fullname = find_file (input_filename)
1264                 in_handle = open (input_fullname)
1265
1266         if input_filename == '-':
1267                 input_base = 'stdin'
1268         else:
1269                 input_base = os.path.basename \
1270                              (os.path.splitext (input_filename)[0])
1271
1272         # Only default to stdout when filtering.
1273         if output_name == '-' or (not output_name and filter_cmd):
1274                 output_filename = '-'
1275                 output_file = sys.stdout
1276         else:
1277                 if not output_name:
1278                         output_filename = input_base + format2ext[format]
1279                 else:
1280                         if not os.path.isdir (output_name):
1281                                 os.mkdir (output_name, 0777)
1282                         output_filename = (output_name
1283                                            + '/' + input_base
1284                                            + format2ext[format])
1285
1286                 if os.path.exists (input_filename) \
1287                    and os.path.exists (output_filename) \
1288                    and os.path.samefile (output_filename, input_fullname):
1289                         ly.error (
1290                           _ ("Output would overwrite input file; use --output."))
1291                         ly.exit (2)
1292
1293                 output_file = open (output_filename, 'w')
1294                 if output_name:
1295                         os.chdir (output_name)
1296         try:
1297                 ly.progress (_ ("Reading %s...") % input_fullname)
1298                 source = in_handle.read ()
1299                 ly.progress ('\n')
1300
1301                 # FIXME: Containing blocks must be first, see
1302                 #        find_toplevel_snippets.
1303                 snippet_types = (
1304                         'multiline_comment',
1305                         'verbatim',
1306                         'lilypond_block',
1307         #               'verb',
1308                         'singleline_comment',
1309                         'lilypond_file',
1310                         'include',
1311                         'lilypond',
1312                 )
1313                 ly.progress (_ ("Dissecting..."))
1314                 chunks = find_toplevel_snippets (source, snippet_types)
1315                 ly.progress ('\n')
1316
1317                 global default_ly_options
1318                 textwidth = 0
1319                 if not default_ly_options.has_key (LINEWIDTH):
1320                         if format == LATEX:
1321                                 textwidth = get_latex_textwidth (source)
1322                                 default_ly_options[LINEWIDTH] = \
1323                                   '''%.0f\\pt''' % textwidth
1324                         elif format == TEXINFO:
1325                                 for (k, v) in texinfo_linewidths.items ():
1326                                         # FIXME: @layout is usually not in
1327                                         # chunk #0:
1328                                         #
1329                                         #  \input texinfo @c -*-texinfo-*-
1330                                         #
1331                                         # Bluntly search first K items of
1332                                         # source.
1333                                         # s = chunks[0].replacement_text ()
1334                                         if re.search (k, source[:1024]):
1335                                                 default_ly_options[LINEWIDTH] = v
1336                                                 break
1337
1338                 if filter_cmd:
1339                         output_file.writelines ([c.filter_text () \
1340                                                  for c in chunks])
1341
1342                 elif process_cmd:
1343                         do_process_cmd (chunks)
1344                         ly.progress (_ ("Compiling %s...") % output_filename)
1345                         output_file.writelines ([s.replacement_text () \
1346                                                  for s in chunks])
1347                         ly.progress ('\n')
1348
1349                 def process_include (snippet):
1350                         os.chdir (original_dir)
1351                         name = snippet.substring ('filename')
1352                         ly.progress (_ ("Processing include: %s") % name)
1353                         ly.progress ('\n')
1354                         do_file (name)
1355
1356                 map (process_include,
1357                      filter (lambda x: is_derived_class (x.__class__,
1358                                                          Include_snippet),
1359                              chunks))
1360         except Compile_error:
1361                 os.chdir (original_dir)
1362                 ly.progress (_ ("Removing `%s'") % output_filename)
1363                 ly.progress ('\n')
1364
1365                 os.unlink (output_filename)
1366                 raise Compile_error
1367
1368 def do_options ():
1369         global format, output_name
1370         global filter_cmd, process_cmd, verbose_p
1371
1372         (sh, long) = ly.getopt_args (option_definitions)
1373         try:
1374                 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1375         except getopt.error, s:
1376                 sys.stderr.write ('\n')
1377                 ly.error (_ ("getopt says: `%s'" % s))
1378                 sys.stderr.write ('\n')
1379                 ly.help ()
1380                 ly.exit (2)
1381
1382         for opt in options:
1383                 o = opt[0]
1384                 a = opt[1]
1385
1386                 if 0:
1387                         pass
1388                 elif o == '--filter' or o == '-F':
1389                         filter_cmd = a
1390                         process_cmd = 0
1391                 elif o == '--format' or o == '-f':
1392                         format = a
1393                         if a == 'texi-html' or a == 'texi':
1394                                 format = TEXINFO
1395                 elif o == '--tex-backend ':
1396                         backend = 'tex'
1397                 elif o == '--help' or o == '-h':
1398                         ly.help ()
1399                         sys.exit (0)
1400                 elif o == '--include' or o == '-I':
1401                         include_path.append (os.path.join (original_dir,
1402                                                            ly.abspath (a)))
1403                 elif o == '--output' or o == '-o':
1404                         output_name = a
1405                 elif o == '--outdir':
1406                         output_name = a
1407                 elif o == '--process' or o == '-P':
1408                         process_cmd = a
1409                         filter_cmd = 0
1410                 elif o == '--version' or o == '-v':
1411                         ly.identify (sys.stdout)
1412                         sys.exit (0)
1413                 elif o == '--verbose' or o == '-V':
1414                         verbose_p = 1
1415                 elif o == '--warranty' or o == '-w':
1416                         if 1 or status:
1417                                 ly.warranty ()
1418                         sys.exit (0)
1419         return files
1420
1421 def main ():
1422         files = do_options ()
1423         if not files:
1424                 ly.warning ("Need to have command line option")
1425                 ly.exit (2)
1426
1427         file = files[0]
1428         global process_cmd, format
1429         format = guess_format (files[0])
1430
1431         formats = "ps"
1432         if format == TEXINFO:
1433                 formats += ',png' 
1434         if process_cmd == '':
1435                 process_cmd = lilypond_binary + ' --formats=%s --backend eps ' % formats
1436
1437         if process_cmd:
1438                 process_cmd += string.join ([(' -I %s' % p)
1439                                              for p in include_path])
1440
1441         ly.identify (sys.stderr)
1442         ly.setup_environment ()
1443
1444         try:
1445                 do_file (file)
1446         except Compile_error:
1447                 ly.exit (1)
1448
1449
1450 if __name__ == '__main__':
1451         main ()