]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/lilypond-book.py
Merge branch 'lilypond/translation' of ssh://jomand@git.sv.gnu.org/srv/git/lilypond...
[lilypond.git] / scripts / lilypond-book.py
index 70ab6a935f7305bff14d708e2a987156e53e9dbd..7fe8210a9d6b312fb30074aba22db21e17c7e52e 100644 (file)
@@ -29,49 +29,17 @@ TODO:
 '''
 
 import stat
-import string
 import tempfile
 import commands
 import os
 import sys
 import re
+import md5
+import operator
 
-# Users of python modules should include this snippet
-# and customize variables below.
-
-# We'll suffer this path initialization stuff as long as we don't install
-# our python packages in <prefix>/lib/pythonX.Y
-
-# If set, LILYPONDPREFIX must take prevalence.
-# if datadir is not set, we're doing a build and LILYPONDPREFIX.
-
-################
-# RELOCATION
-################
-
-datadir = '@local_lilypond_datadir@'
-if not os.path.isdir (datadir):
-    datadir = '@lilypond_datadir@'
-
-sys.path.insert (0, os.path.join (datadir, 'python'))
-
-if os.environ.has_key ('LILYPONDPREFIX'):
-    datadir = os.environ['LILYPONDPREFIX']
-    while datadir[-1] == os.sep:
-        datadir = datadir[:-1]
-
-    if not os.path.exists (os.path.join (datadir, 'python/lilylib.py')):
-        datadir = os.path.join (datadir, 'share/lilypond/current/')
-sys.path.insert (0, os.path.join (datadir, 'python'))
-
-# dynamic relocation, for GUB binaries.
-bindir = os.path.split (sys.argv[0])[0]
-
-
-for prefix_component in ['share', 'lib']:
-    datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % prefix_component)
-    sys.path.insert (0, datadir)
-
+"""
+@relocate-preamble@
+"""
 
 import lilylib as ly
 import fontextract
@@ -85,106 +53,134 @@ program_name = os.path.basename (sys.argv[0])
 original_dir = os.getcwd ()
 backend = 'ps'
 
-help_summary = (
-'''Process LilyPond snippets in hybrid HTML, LaTeX, or texinfo document.
-
-Example usage:
-
- lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
- lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" BOOK
- lilypond-book --process='lilypond -I include' BOOK
-''')
+help_summary = (
+_ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.")
++ '\n\n'
++ _ ("Examples:")
++ '''
+ lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s
+ lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" %(BOOK)s
+ lilypond-book --process='lilypond -I include' %(BOOK)s
+''' % {'BOOK': _ ("BOOK")})
 
 authors = ('Jan Nieuwenhuizen <janneke@gnu.org>',
-      'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
+      'Han-Wen Nienhuys <hanwen@xs4all.nl>')
 
-    
 ################################################################
 def exit (i):
     if global_options.verbose:
-        raise _ ('Exiting (%d)...') % i
+        raise Exception (_ ('Exiting (%d)...') % i)
     else:
         sys.exit (i)
 
 def identify ():
-    sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
+    ly.encoded_write (sys.stdout, '%s (GNU LilyPond) %s\n' % (program_name, program_version))
 
-def progress (s):
-    sys.stderr.write (s)
+progress = ly.progress
 
 def warning (s):
-    sys.stderr.write (program_name + ": " + _ ("warning: %s") % s + '\n')
+    ly.stderr_write (program_name + ": " + _ ("warning: %s") % s + '\n')
 
 def error (s):
-    sys.stderr.write (program_name + ": " + _ ("error: %s") % s + '\n')
+    ly.stderr_write (program_name + ": " + _ ("error: %s") % s + '\n')
 
 def ps_page_count (ps_name):
     header = open (ps_name).read (1024)
     m = re.search ('\n%%Pages: ([0-9]+)', header)
     if m:
-        return string.atoi (m.group (1))
+        return int (m.group (1))
     return 0
 
 def warranty ():
     identify ()
-    sys.stdout.write ('''
+    ly.encoded_write (sys.stdout, '''
 %s
 
 %s
 
 %s
 %s
-''' % ( _('Copyright (c) %s by') % '2001--2006',
+''' % ( _ ('Copyright (c) %s by') % '2001--2007',
     ' '.join (authors),
-   _('Distributed under terms of the GNU General Public License.'),
-   _('It comes with NO WARRANTY.')))
-
+   _ ("Distributed under terms of the GNU General Public License."),
+   _ ("It comes with NO WARRANTY.")))
 
 def get_option_parser ():
-    p = ly.get_option_parser (usage='lilypond-book [OPTIONS] FILE',
-                              version="@TOPLEVEL_VERSION@",
-                              description=help_summary)
+    p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'lilypond-book',
+                              description=help_summary,
+                              add_help_option=False)
 
     p.add_option ('-F', '--filter', metavar=_ ("FILTER"),
                   action="store",
                   dest="filter_cmd",
                   help=_ ("pipe snippets through FILTER [convert-ly -n -]"),
                   default=None)
+
     p.add_option ('-f', '--format',
-                  help=_('''use output format FORMAT (texi [default], texi-html, latex, html)'''),
+                  help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"),
                   action='store')
-    p.add_option ("-I", '--include', help=_('add DIR to include path'),
-                  metavar="DIR",
+
+    p.add_option("-h", "--help",
+                 action="help",
+                 help=_ ("show this help and exit"))
+
+    p.add_option ("-I", '--include', help=_ ("add DIR to include path"),
+                  metavar=_ ("DIR"),
                   action='append', dest='include_path',
                   default=[os.path.abspath (os.getcwd ())])
+
+    p.add_option ('--info-images-dir',
+                  help=_ ("format Texinfo output so that Info will "
+                          "look for images of music in DIR"),
+                  metavar=_ ("DIR"),
+                  action='store', dest='info_images_dir',
+                  default='')
+
+    p.add_option ('--left-padding', 
+                  metavar=_ ("PAD"),
+                  dest="padding_mm",
+                  help=_ ("pad left side of music to align music inspite of uneven bar numbers (in mm)"),
+                  type="float",
+                  default=3.0)
     
-    p.add_option ("-o", '--output', help=_('write output to DIR'),
-                  metavar="DIR",
+    p.add_option ("-o", '--output', help=_ ("write output to DIR"),
+                  metavar=_ ("DIR"),
                   action='store', dest='output_name',
                   default='')
-    p.add_option ('-P', '--process', metavar=_("COMMAND"),
+    
+    p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
                   help = _ ("process ly_files using COMMAND FILE..."),
                   action='store', 
-                  dest='process_cmd', default='lilypond -b eps')
-    
+                  dest='process_cmd', default='lilypond -dbackend=eps')
+
+    p.add_option ('--pdf',
+                  action="store_true",
+                  dest="create_pdf",
+                  help=_ ("create PDF files for use with PDFTeX"),
+                  default=False)
+
     p.add_option ('', '--psfonts', action="store_true", dest="psfonts",
-                  help=_ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX'''
-                   '''must use this with dvips -h INPUT.psfonts'''),
+                  help=_ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX
+must use this with dvips -h INPUT.psfonts'''),
                   default=None)
-    p.add_option ('-V', '--verbose', help=_("be verbose"),
+
+    p.add_option ('-V', '--verbose', help=_ ("be verbose"),
                   action="store_true",
                   default=False,
                   dest="verbose")
-    
+
+    p.version = "@TOPLEVEL_VERSION@"
+    p.add_option("--version",
+                 action="version",
+                 help=_ ("show version number and exit"))
+
     p.add_option ('-w', '--warranty',
-                  help=_("show warranty and copyright"),
+                  help=_ ("show warranty and copyright"),
                   action='store_true')
-
-    
-    p.add_option_group  ('bugs',
-                         description='''Report bugs via http://post.gmane.org/post.php'''
-                         '''?group=gmane.comp.gnu.lilypond.bugs\n''')
-    
+    p.add_option_group (ly.display_encode (_ ('Bugs')),
+                        description=(_ ("Report bugs via")
+                                     + ''' http://post.gmane.org/post.php'''
+                                     '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
     return p
 
 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
@@ -201,8 +197,10 @@ default_ly_options = { 'alt': "[image of music]" }
 #
 # Is this pythonic?  Personally, I find this rather #define-nesque. --hwn
 #
+ADDVERSION = 'addversion'
 AFTER = 'after'
 BEFORE = 'before'
+DOCBOOK = 'docbook'
 EXAMPLEINDENT = 'exampleindent'
 FILTER = 'filter'
 FRAGMENT = 'fragment'
@@ -211,6 +209,7 @@ INDENT = 'indent'
 LATEX = 'latex'
 LAYOUT = 'layout'
 LINE_WIDTH = 'line-width'
+LILYQUOTE = 'lilyquote'
 NOFRAGMENT = 'nofragment'
 NOINDENT = 'noindent'
 NOQUOTE = 'noquote'
@@ -251,6 +250,43 @@ no_options = {
 #   (?x) -- Ignore whitespace in patterns.
 no_match = 'a\ba'
 snippet_res = {
+ ##
+    DOCBOOK: {
+        'include':
+         no_match,
+
+        'lilypond':
+         r'''(?smx)
+          (?P<match>
+          <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
+
+        'lilypond_block':
+         r'''(?smx)
+          (?P<match>
+          <(?P<inline>(inline)?)mediaobject>\s*<textobject.*?>\s*<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>(?P<code>.*?)</programlisting\s*>\s*</textobject\s*>\s*</(inline)?mediaobject>)''',
+
+        'lilypond_file':
+         r'''(?smx)
+          (?P<match>
+          <(?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>)''',
+
+        'multiline_comment':
+         r'''(?smx)
+          (?P<match>
+          \s*(?!@c\s+)
+          (?P<code><!--\s.*?!-->)
+          \s)''',
+
+        'singleline_comment':
+         no_match,
+
+        'verb':
+         no_match,
+
+        'verbatim':
+       no_match,
+       
+    }, 
     ##
     HTML: {
         'include':
@@ -448,6 +484,10 @@ snippet_res = {
 
 
 format_res = {
+    DOCBOOK: {        
+       'intertext': r',?\s*intertext=\".*?\"',
+        'option_sep': '\s*',
+    }, 
     HTML: {
         'intertext': r',?\s*intertext=\".*?\"',
         'option_sep': '\s*',
@@ -475,7 +515,8 @@ simple_options = [
     VERBATIM,
     FONTLOAD,
     FILENAME,
-    ALT
+    ALT,
+    ADDVERSION
 ]
 
 ly_options = {
@@ -492,6 +533,8 @@ ly_options = {
 
         QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
 
+        LILYQUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
+
         RAGGED_RIGHT: r'''ragged-right = ##t''',
 
         PACKED: r'''packed = ##t''',
@@ -517,6 +560,21 @@ ly_options = {
 }
 
 output = {
+    ##
+    DOCBOOK: {                 
+        FILTER: r'''<mediaobject><textobject><programlisting language="lilypond" role="%(options)s">%(code)s</programlisting></textobject></mediaobject>''', 
+    
+        OUTPUT: r'''
+        <imageobject role="latex">
+               <imagedata fileref="%(base)s.pdf" format="PDF"/>
+               </imageobject>
+               <imageobject role="html">
+               <imagedata fileref="%(base)s.png" format="PNG"/></imageobject>''',
+    
+        VERBATIM: r'''<programlisting>%(verb)s</programlisting>''',
+    
+        PRINTFILENAME: '<textobject><simpara><ulink url="%(base)s.ly"><filename>%(filename)s</filename></ulink></simpara></textobject>'
+    },
     ##
     HTML: {
         FILTER: r'''<lilypond %(options)s>
@@ -550,7 +608,6 @@ output = {
     LATEX: {
         OUTPUT: r'''{%%
 \parindent 0pt%%
-\catcode`\@=12%%
 \ifx\preLilyPondExample \undefined%%
  \relax%%
 \else%%
@@ -593,7 +650,7 @@ output = {
 
         OUTPUTIMAGE: r'''@noindent
 @ifinfo
-@image{%(base)s,,,%(alt)s,%(ext)s}
+@image{%(info_image_path)s,,,%(alt)s,%(ext)s}
 @end ifinfo
 @html
 <p>
@@ -624,9 +681,14 @@ output = {
 ''',
 
         VERBATIM: r'''@exampleindent 0
-@verbatim
+%(version)s@verbatim
 %(verb)s@end verbatim
 ''',
+
+        ADDVERSION: r'''@example
+\version @w{"@version{}"}
+@end example
+'''
     },
 }
 
@@ -643,32 +705,21 @@ if 0:
 
 PREAMBLE_LY = '''%%%% Generated by %(program_name)s
 %%%% Options: [%(option_string)s]
-
-#(set! toplevel-score-handler print-score-with-defaults)
-#(set! toplevel-music-handler
-  (lambda (p m)
-   (if (not (eq? (ly:music-property m \'void) #t))
-        (print-score-with-defaults
-        p (scorify-music m p)))))
-
-#(ly:set-option (quote no-point-and-click))
-#(define inside-lilypond-book #t)
-#(define version-seen? #t)
-%(preamble_string)s
-
-
-
-
+\\include "lilypond-book-preamble.ly"
 
 
 %% ****************************************************************
 %% Start cut-&-pastable-section 
 %% ****************************************************************
 
+%(preamble_string)s
+
 \paper {
   #(define dump-extents #t)
   %(font_dump_setting)s
   %(paper_string)s
+  force-assignment = #""
+  line-width = #(- line-width (* mm  %(padding_mm)f))
 }
 
 \layout {
@@ -735,14 +786,15 @@ def classic_lilypond_book_compatibility (key, value):
 
     return (None, None)
 
-def find_file (name):
+def find_file (name, raise_error=True):
     for i in global_options.include_path:
         full = os.path.join (i, name)
         if os.path.exists (full):
             return full
         
-    error (_ ("file not found: %s") % name + '\n')
-    exit (1)
+    if raise_error:
+        error (_ ("file not found: %s") % name + '\n')
+        exit (1)
     return ''
 
 def verbatim_html (s):
@@ -851,9 +903,14 @@ class Lilypond_snippet (Snippet):
         os = match.group ('options')
         self.do_options (os, self.type)
 
-    def ly (self):
+    def verb_ly (self):
         return self.substring ('code')
 
+    def ly (self):
+        contents = self.substring ('code')
+        return ('\\sourcefileline %d\n%s'
+                % (self.line_number - 1, contents))
+
     def full_ly (self):
         s = self.ly ()
         if s:
@@ -866,7 +923,7 @@ class Lilypond_snippet (Snippet):
         options = split_options (option_string)
 
         for i in options:
-            if string.find (i, '=') > 0:
+            if '=' in i:
                 (key, value) = re.split ('\s*=\s*', i)
                 self.option_dict[key] = value
             else:
@@ -957,7 +1014,7 @@ class Lilypond_snippet (Snippet):
                 option_list.append (key)
             else:
                 option_list.append (key + '=' + value)
-        option_string = string.join (option_list, ',')
+        option_string = ','.join (option_list)
 
         compose_dict = {}
         compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
@@ -1013,15 +1070,11 @@ class Lilypond_snippet (Snippet):
         elif relative > 0:
             relative_quotes += "'" * relative
 
-        paper_string = string.join (compose_dict[PAPER],
-                      '\n  ') % override
-        layout_string = string.join (compose_dict[LAYOUT],
-                      '\n  ') % override
-        notes_string = string.join (compose_dict[NOTES],
-                      '\n  ') % vars ()
-        preamble_string = string.join (compose_dict[PREAMBLE],
-                       '\n  ') % override
-        
+        paper_string = '\n  '.join (compose_dict[PAPER]) % override
+        layout_string = '\n  '.join (compose_dict[LAYOUT]) % override
+        notes_string = '\n  '.join (compose_dict[NOTES]) % vars ()
+        preamble_string = '\n  '.join (compose_dict[PREAMBLE]) % override
+        padding_mm = global_options.padding_mm
         font_dump_setting = ''
         if FONTLOAD in self.option_dict:
             font_dump_setting = '#(define-public force-eps-font-include #t)\n'
@@ -1030,71 +1083,69 @@ class Lilypond_snippet (Snippet):
         d.update (locals())
         return (PREAMBLE_LY + body) % d
 
-    # TODO: Use md5?
     def get_hash (self):
         if not self.hash:
-            self.hash = abs (hash (self.full_ly ()))
+            hash = md5.md5 (self.relevant_contents (self.full_ly ()))
+
+            ## let's not create too long names.
+            self.hash = hash.hexdigest ()[:10]
+            
         return self.hash
 
     def basename (self):
         if FILENAME in self.option_dict:
             return self.option_dict[FILENAME]
         if global_options.use_hash:
-            return 'lily-%d' % self.get_hash ()
+            return 'lily-%s' % self.get_hash ()
         raise 'to be done'
 
     def write_ly (self):
         outf = open (self.basename () + '.ly', 'w')
         outf.write (self.full_ly ())
-
         open (self.basename () + '.txt', 'w').write ('image of music')
 
+    def relevant_contents (self, ly):
+        return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', ly)
+             
     def ly_is_outdated (self):
         base = self.basename ()
-
-        tex_file = '%s.tex' % base
-        eps_file = '%s.eps' % base
-        system_file = '%s-systems.tex' % base
-        ly_file = '%s.ly' % base
-        ok = os.path.exists (ly_file) \
-          and os.path.exists (system_file)\
-          and os.stat (system_file)[stat.ST_SIZE] \
-          and re.match ('% eof', open (system_file).readlines ()[-1])
-        if ok and (not global_options.use_hash or FILENAME in self.option_dict):
-            ok = (self.full_ly () == open (ly_file).read ())
-        if ok:
-            # TODO: Do something smart with target formats
-            #       (ps, png) and m/ctimes.
+        ly_file = find_file (base + '.ly', raise_error=False)
+        tex_file = find_file (base + '.tex', raise_error=False)
+        systems_file = find_file (base + '-systems.tex', raise_error=False)
+
+        if (os.path.exists (ly_file)
+            and os.path.exists (systems_file)
+            and os.stat (systems_file)[stat.ST_SIZE]
+            and re.match ('% eof', open (systems_file).readlines ()[-1])
+            and (global_options.use_hash or FILENAME in self.option_dict)
+            and (self.relevant_contents (self.full_ly ())
+                 == self.relevant_contents (open (ly_file).read ()))):
             return None
+
         return self
 
     def png_is_outdated (self):
         base = self.basename ()
-        ok = not self.ly_is_outdated ()
-        if global_options.format in (HTML, TEXINFO):
-            ok = ok and os.path.exists (base + '.eps')
-
-            page_count = 0
-            if ok:
-                page_count = ps_page_count (base + '.eps')
-
-            if page_count <= 1:
-                ok = ok and os.path.exists (base + '.png')
-             
-            elif page_count > 1:
-                for a in range (1, page_count + 1):
-                        ok = ok and os.path.exists (base + '-page%d.png' % a)
-                
-        return not ok
+        eps_file = find_file (base + '.eps', raise_error=False)
+        png_file = find_file (base + '.png', raise_error=False)
+        if not self.ly_is_outdated () and global_options.format in (HTML, TEXINFO):
+            if os.path.exists (eps_file):
+                page_count = ps_page_count (eps_file)
+                if page_count <= 1:
+                    return not os.path.exists (png_file)
+                else:
+                    return not reduce (operator.or_,
+                                       [find_file (base + '-page%d.png' % a, raise_error=False)
+                                        for a in range (1, page_count + 1)])
+        return True
     
     def texstr_is_outdated (self):
         if backend == 'ps':
             return 0
 
         base = self.basename ()
-        ok = self.ly_is_outdated ()
-        ok = ok and (os.path.exists (base + '.texstr'))
-        return not ok
+        return not (self.ly_is_outdated ()
+                    and find_file (base + '.texstr', raise_error=False))
 
     def filter_text (self):
         code = self.substring ('code')
@@ -1125,13 +1176,29 @@ class Lilypond_snippet (Snippet):
             images = tuple (images)
         return images
 
+    def output_docbook (self):
+        str = ''
+        base = self.basename ()
+        for image in self.get_images ():
+            (base, ext) = os.path.splitext (image)
+            str += output[DOCBOOK][OUTPUT] % vars ()
+           str += self.output_print_filename (DOCBOOK)
+            if (self.substring('inline') == 'inline'): 
+                str = '<inlinemediaobject>' + str + '</inlinemediaobject>'
+            else:
+                str = '<mediaobject>' + str + '</mediaobject>'
+        if VERBATIM in self.option_dict:
+                verb = verbatim_html (self.verb_ly ())
+                str = output[DOCBOOK][VERBATIM] % vars () + str
+        return str
+       
     def output_html (self):
         str = ''
         base = self.basename ()
         if global_options.format == HTML:
             str += self.output_print_filename (HTML)
             if VERBATIM in self.option_dict:
-                verb = verbatim_html (self.substring ('code'))
+                verb = verbatim_html (self.verb_ly ())
                 str += output[HTML][VERBATIM] % vars ()
             if QUOTE in self.option_dict:
                 str = output[HTML][QUOTE] % vars ()
@@ -1153,6 +1220,7 @@ class Lilypond_snippet (Snippet):
             # Specifying no extension is most robust.
             ext = ''
             alt = self.option_dict[ALT]
+            info_image_path = os.path.join (global_options.info_images_dir, base)
             str += output[TEXINFO][OUTPUTIMAGE] % vars ()
 
         base = self.basename ()
@@ -1165,9 +1233,9 @@ class Lilypond_snippet (Snippet):
         if global_options.format == LATEX:
             str += self.output_print_filename (LATEX)
             if VERBATIM in self.option_dict:
-                verb = self.substring ('code')
+                verb = self.verb_ly ()
                 str += (output[LATEX][VERBATIM] % vars ())
-        
+
         str += (output[LATEX][OUTPUT] % vars ())
 
         ## todo: maintain breaks
@@ -1183,33 +1251,30 @@ class Lilypond_snippet (Snippet):
         str = ''
         if PRINTFILENAME in self.option_dict:
             base = self.basename ()
-            filename = self.substring ('filename')
-            str = output[global_options.format][PRINTFILENAME] % vars ()
+            filename = os.path.basename (self.substring ('filename'))
+            str = output[format][PRINTFILENAME] % vars ()
 
         return str
 
     def output_texinfo (self):
-        str = ''
-        if self.output_print_filename (TEXINFO):
-            str += ('@html\n'
-                + self.output_print_filename (HTML)
-                + '\n@end html\n')
-            str += ('@tex\n'
-                + self.output_print_filename (LATEX)
-                + '\n@end tex\n')
+        str = self.output_print_filename (TEXINFO)
         base = self.basename ()
         if TEXIDOC in self.option_dict:
             texidoc = base + '.texidoc'
             if os.path.exists (texidoc):
                 str += '@include %(texidoc)s\n\n' % vars ()
 
+        substr = ''
         if VERBATIM in self.option_dict:
-            verb = self.substring ('code')
-            str += (output[TEXINFO][VERBATIM] % vars ())
-            if not QUOTE in self.option_dict:
-                str = output[TEXINFO][NOQUOTE] % vars ()
-
-        str += self.output_info ()
+            version = ''
+            if ADDVERSION in self.option_dict:
+                version = output[TEXINFO][ADDVERSION]
+            verb = self.verb_ly ()
+            substr = output[TEXINFO][VERBATIM] % vars ()
+        substr += self.output_info ()
+        if LILYQUOTE in self.option_dict:
+            substr = output[TEXINFO][QUOTE] % {'str':substr}
+        str += substr
 
 #                str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n')
 #                str += ('@tex\n' + self.output_latex () + '\n@end tex\n')
@@ -1223,17 +1288,25 @@ class Lilypond_snippet (Snippet):
 
         return str
 
+re_begin_verbatim = re.compile (r'\s+%.*?begin verbatim.*\n*', re.M)
+re_end_verbatim = re.compile (r'\s+%.*?end verbatim.*$', re.M)
+
 class Lilypond_file_snippet (Lilypond_snippet):
+    def __init__ (self, type, match, format, line_number):
+        Lilypond_snippet.__init__ (self, type, match, format, line_number)
+        self.contents = open (find_file (self.substring ('filename'))).read ()
+
+    def verb_ly (self):
+        s = self.contents
+        s = re_begin_verbatim.split (s)[-1]
+        s = re_end_verbatim.split (s)[0]
+        return s
+
     def ly (self):
         name = self.substring ('filename')
-        contents = open (find_file (name)).read ()
+        return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
+                % (name, self.contents))
 
-        ## strip version string to make automated regtest comparisons
-        ## across versions easier.
-        contents = re.sub (r'\\version *"[^"]*"', '', contents)
-        
-        return ('\\sourcefilename \"%s\"\n%s'
-                % (name, contents))
 
 snippet_type_to_class = {
     'lilypond_file': Lilypond_file_snippet,
@@ -1265,7 +1338,7 @@ def find_toplevel_snippets (s, types):
 
     snippets = []
     index = 0
-    found = dict ((t, None) for t in types)
+    found = dict ([(t, None) for t in types])
 
     line_starts = find_linestarts (s)
     line_start_idx = 0
@@ -1355,8 +1428,8 @@ def filter_pipe (input, cmd):
         exit_status = status >> 8
         error (_ ("`%s' failed (%d)") % (cmd, exit_status))
         error (_ ("The error log is as follows:"))
-        sys.stderr.write (error)
-        sys.stderr.write (stderr.read ())
+        ly.stderr_write (error)
+        ly.stderr_write (stderr.read ())
         exit (status)
 
     if global_options.verbose:
@@ -1387,23 +1460,30 @@ def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets):
     status = 0
     def my_system (cmd):
         status = ly.system (cmd,
-                  be_verbose=global_options.verbose, 
-                  progress_p= 1)
+                            be_verbose=global_options.verbose, 
+                            progress_p=1)
 
-    if global_options.format in (HTML, TEXINFO):
-        cmd += ' --format png '
+    if global_options.format in (HTML, TEXINFO) and '--formats' not in cmd:
+        cmd += ' --formats=png '
+    elif global_options.format in (DOCBOOK) and '--formats' not in cmd:
+        cmd += ' --formats=png,pdf '
 
+        
     # UGH
     # the --process=CMD switch is a bad idea
     # it is too generic for lilypond-book.
     if texstr_names:
-        my_system (string.join ([cmd, '--backend texstr',
-                                 'snippet-map.ly'] + texstr_names))
+        my_system (' '.join ([cmd, '--backend texstr',
+                              'snippet-map.ly'] + texstr_names))
         for l in texstr_names:
             my_system ('latex %s.texstr' % l)
 
     if ly_names:
-        my_system (string.join ([cmd, 'snippet-map.ly'] + ly_names))
+        open ('snippet-names', 'wb').write ('\n'.join (['snippet-map.ly']
+                                                       + ly_names))
+        
+        my_system (' '.join ([cmd, 'snippet-names']))
+
 
 LATEX_INSPECTION_DOCUMENT = r'''
 \nonstopmode
@@ -1419,7 +1499,7 @@ LATEX_INSPECTION_DOCUMENT = r'''
 def get_latex_textwidth (source):
     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
     if m == None:
-        warning (_ ("Can't find \\begin{document} in LaTeX document"))
+        warning (_ ("cannot find \\begin{document} in LaTeX document"))
         
         ## what's a sensible default?
         return 550.0
@@ -1482,6 +1562,7 @@ ext2format = {
     '.texi': TEXINFO,
     '.texinfo': TEXINFO,
     '.xml': HTML,
+    '.lyxml': DOCBOOK
 }
 
 format2ext = {
@@ -1489,6 +1570,7 @@ format2ext = {
     # TEXINFO: '.texinfo',
     TEXINFO: '.texi',
     LATEX: '.tex',
+    DOCBOOK: '.xml'
 }
 
 class Compile_error:
@@ -1497,22 +1579,21 @@ class Compile_error:
 def write_file_map (lys, name):
     snippet_map = open ('snippet-map.ly', 'w')
     snippet_map.write ("""
-#(define version-seen? #t)
+#(define version-seen #t)
+#(define output-empty-score-list #f)
 #(ly:add-file-name-alist '(
 """)
     for ly in lys:
-        snippet_map.write ('("%s.ly" . "%s:%d (%s.ly)")\n'
+        snippet_map.write ('("%s.ly" . "%s")\n'
                  % (ly.basename (),
-                   name,
-                   ly.line_number,
-                   ly.basename ()))
+                   name))
 
     snippet_map.write ('))\n')
 
 def do_process_cmd (chunks, input_name):
     all_lys = filter (lambda x: is_derived_class (x.__class__,
                            Lilypond_snippet),
-             chunks)
+                      chunks)
 
     write_file_map (all_lys, input_name)
     ly_outdated = filter (lambda x: is_derived_class (x.__class__,
@@ -1548,7 +1629,7 @@ def guess_format (input_filename):
         # FIXME
         format = ext2format[e]
     else:
-        error (_ ("can't determine format for: %s" \
+        error (_ ("cannot determine format for: %s" \
               % input_filename))
         exit (1)
     return format
@@ -1557,10 +1638,15 @@ def write_if_updated (file_name, lines):
     try:
         f = open (file_name)
         oldstr = f.read ()
-        new_str = string.join (lines, '')
+        new_str = ''.join (lines)
         if oldstr == new_str:
             progress (_ ("%s is up to date.") % file_name)
             progress ('\n')
+
+            # this prevents make from always rerunning lilypond-book:
+            # .texi target must be touched in order to be up to date
+            if global_options.format == 'texinfo':
+                os.utime (file_name, None)
             return
     except:
         pass
@@ -1708,7 +1794,24 @@ def do_options ():
         
     return args
 
+def psfonts_warning (options, basename):
+    if options.format in (TEXINFO, LATEX):
+        psfonts_file = os.path.join (options.output_name, basename + '.psfonts')
+        output = os.path.join (options.output_name, basename +  '.dvi' )
+
+        if not options.create_pdf:
+            if not options.psfonts:
+                warning (_ ("option --psfonts not used"))
+                warning (_ ("processing with dvips will have no fonts"))
+            else:
+                progress ('\n')
+                progress (_ ("DVIPS usage:"))
+                progress ('\n')
+                progress ("    dvips -h %(psfonts_file)s %(output)s" % vars ())
+                progress ('\n')
+
 def main ():
+    # FIXME: 85 lines of `main' macramee??
     files = do_options ()
 
     file = files[0]
@@ -1720,16 +1823,32 @@ def main ():
         global_options.format = guess_format (files[0])
 
     formats = 'ps'
-    if global_options.format in (TEXINFO, HTML):
+    if global_options.format in (TEXINFO, HTML, DOCBOOK):
         formats += ',png'
+
+        
     if global_options.process_cmd == '':
-        global_options.process_cmd = lilypond_binary \
-               + ' --formats=%s --backend eps ' % formats
+        global_options.process_cmd = (lilypond_binary 
+                                      + ' --formats=%s --backend eps ' % formats)
 
     if global_options.process_cmd:
-        global_options.process_cmd += string.join ([(' -I %s' % commands.mkarg (p))
+        global_options.process_cmd += ' '.join ([(' -I %s' % ly.mkarg (p))
                               for p in global_options.include_path])
 
+    if global_options.format in (TEXINFO, LATEX):
+        ## prevent PDF from being switched on by default.
+        global_options.process_cmd += ' --formats=eps '
+        if global_options.create_pdf:
+            global_options.process_cmd += "--pdf -dinclude-eps-fonts -dgs-load-fonts "
+    
+    if global_options.verbose:
+        global_options.process_cmd += " --verbose "
+
+    if global_options.padding_mm:
+        global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm
+        
+    global_options.process_cmd += " -dread-file-list "
+
     identify ()
 
     try:
@@ -1752,19 +1871,7 @@ def main ():
     except Compile_error:
         exit (1)
 
-    if global_options.format in (TEXINFO, LATEX):
-        if not global_options.psfonts:
-            warning (_ ("option --psfonts not used"))
-            warning (_ ("processing with dvips will have no fonts"))
-
-        psfonts_file = os.path.join (global_options.output_name, basename + '.psfonts')
-        output = os.path.join (global_options.output_name, basename +  '.dvi' )
-        
-        progress ('\n')
-        progress (_ ("DVIPS usage:"))
-        progress ('\n')
-        progress ("    dvips -h %(psfonts_file)s %(output)s" % vars ())
-        progress ('\n')
+    psfonts_warning (global_options, basename)
 
     inputs = note_input_file ('')
     inputs.pop ()