]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/lilypond-book.py
Add lilypond-book ly verbatim gettext quirk
[lilypond.git] / scripts / lilypond-book.py
index b8c765d3fb0b74799d2269f922a4bcbf2e3f43a4..b4bd319a690d3b35aa27b969a7d0b4edd2d0435a 100644 (file)
@@ -1,5 +1,20 @@
 #!@TARGET_PYTHON@
 
+# This file is part of LilyPond, the GNU music typesetter.
+#
+# LilyPond is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LilyPond is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+
 '''
 Example usage:
 
@@ -15,7 +30,7 @@ classic lilypond-book:
 TODO:
 
   *  this script is too complex. Modularize.
-  
+
   *  ly-options: intertext?
   *  --line-width?
   *  eps in latex / eps by lilypond -b ps?
@@ -29,7 +44,6 @@ TODO:
 '''
 
 import glob
-import md5
 import os
 import re
 import stat
@@ -51,6 +65,17 @@ ly.require_python_version ()
 program_version = '@TOPLEVEL_VERSION@'
 program_name = os.path.basename (sys.argv[0])
 
+# Check if program_version contains @ characters. This will be the case if
+# the .py file is called directly while building the lilypond documentation.
+# If so, try to check for the env var LILYPOND_VERSION, which is set by our
+# makefiles and use its value.
+at_re = re.compile (r'@')
+if at_re.match (program_version):
+    if os.environ.has_key('LILYPOND_VERSION'):
+        program_version = os.environ['LILYPOND_VERSION']
+    else:
+        program_version = "unknown"
+
 original_dir = os.getcwd ()
 backend = 'ps'
 
@@ -97,12 +122,12 @@ def warranty ():
     ly.encoded_write (sys.stdout, '''
 %s
 
-%s
+  %s
 
 %s
 %s
-''' % ( _ ('Copyright (c) %s by') % '2001--2007',
-        ' '.join (authors),
+''' % ( _ ('Copyright (c) %s by') % '2001--2010',
+        '\n  '.join (authors),
         _ ("Distributed under terms of the GNU General Public License."),
         _ ("It comes with NO WARRANTY.")))
 
@@ -139,23 +164,41 @@ def get_option_parser ():
                   default='')
 
     p.add_option ('--latex-program',
-                  help=_ ("run executable PROG instead of latex"),
+                  help=_ ("run executable PROG instead of latex, or in\n\
+case --pdf option is set instead of pdflatex"),
                   metavar=_ ("PROG"),
                   action='store', dest='latex_program',
                   default='latex')
 
-    p.add_option ('--left-padding', 
+    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 ('--lily-output-dir',
+                  help=_ ("write lily-XXX files to DIR, link into --output dir"),
+                  metavar=_ ("DIR"),
+                  action='store', dest='lily_output_dir',
+                  default=None)
+
     p.add_option ("-o", '--output', help=_ ("write output to DIR"),
                   metavar=_ ("DIR"),
                   action='store', dest='output_dir',
                   default='')
-    
+
+    p.add_option ('--pdf',
+                  action="store_true",
+                  dest="create_pdf",
+                  help=_ ("create PDF files for use with PDFTeX"),
+                  default=False)
+
+    p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
+                  help = _ ("process ly_files using COMMAND FILE..."),
+                  action='store',
+                  dest='process_cmd', default='')
+
     p.add_option ('--skip-lily-check',
                   help=_ ("do not fail if no lilypond output is found"),
                   metavar=_ ("DIR"),
@@ -167,22 +210,10 @@ def get_option_parser ():
                   metavar=_ ("DIR"),
                   action='store_true', dest='skip_png_check',
                   default=False)
-    
-    p.add_option ('--lily-output-dir',
-                  help=_ ("write lily-XXX files to DIR, link into --output dir"),
-                  metavar=_ ("DIR"),
-                  action='store', dest='lily_output_dir',
-                  default=None)
-    
-    p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
-                  help = _ ("process ly_files using COMMAND FILE..."),
-                  action='store', 
-                  dest='process_cmd', default='')
 
-    p.add_option ('--pdf',
-                  action="store_true",
-                  dest="create_pdf",
-                  help=_ ("create PDF files for use with PDFTeX"),
+    p.add_option ('--use-source-file-names',
+                  help=_ ("write snippet output files with the same base name as their source file"),
+                  action='store_true', dest='use_source_file_names',
                   default=False)
 
     p.add_option ('-V', '--verbose', help=_ ("be verbose"),
@@ -200,8 +231,9 @@ def get_option_parser ():
                   action='store_true')
     p.add_option_group ('',
                         description=(
-        _ ("Report bugs via")
-        + ' http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs\n'))
+        _ ("Report bugs via %s")
+        % ' http://post.gmane.org/post.php'
+        '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
     return p
 
 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
@@ -241,6 +273,7 @@ LAYOUT = 'layout'
 LINE_WIDTH = 'line-width'
 LILYQUOTE = 'lilyquote'
 NOFRAGMENT = 'nofragment'
+NOGETTEXT = 'nogettext'
 NOINDENT = 'noindent'
 NOQUOTE = 'noquote'
 NORAGGED_RIGHT = 'noragged-right'
@@ -248,7 +281,6 @@ NOTES = 'body'
 NOTIME = 'notime'
 OUTPUT = 'output'
 OUTPUTIMAGE = 'outputimage'
-PACKED = 'packed'
 PAPER = 'paper'
 PREAMBLE = 'preamble'
 PRINTFILENAME = 'printfilename'
@@ -260,23 +292,32 @@ DOCTITLE = 'doctitle'
 TEXIDOC = 'texidoc'
 TEXINFO = 'texinfo'
 VERBATIM = 'verbatim'
-FONTLOAD = 'fontload'
+VERSION = 'lilypondversion'
 FILENAME = 'filename'
 ALT = 'alt'
 
 
-# NOTIME has no opposite so it isn't part of this dictionary.
+# NOTIME and NOGETTEXT have no opposite so they aren't part of this
+# dictionary.
 # NOQUOTE is used internally only.
 no_options = {
     NOFRAGMENT: FRAGMENT,
     NOINDENT: INDENT,
 }
 
+# Options that have no impact on processing by lilypond (or --process
+# argument)
+PROCESSING_INDEPENDENT_OPTIONS = (
+    ALT, NOGETTEXT, VERBATIM, ADDVERSION,
+    TEXIDOC, DOCTITLE, VERSION, PRINTFILENAME)
 
 # Recognize special sequences in the input.
 #
 #   (?P<name>regex) -- Assign result of REGEX to NAME.
 #   *? -- Match non-greedily.
+#   (?!...) -- Match if `...' doesn't match next (without consuming
+#              the string).
+#
 #   (?m) -- Multiline regex: Make ^ and $ match at each line.
 #   (?s) -- Make the dot match all characters including newline.
 #   (?x) -- Ignore whitespace in patterns.
@@ -290,17 +331,36 @@ snippet_res = {
         '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>)''',
+          <(?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>)''',
+          <(?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>)''',
+          <(?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)
@@ -316,9 +376,11 @@ snippet_res = {
          no_match,
 
         'verbatim':
-       no_match,
-       
-    }, 
+         no_match,
+
+        'lilypondversion':
+         no_match,
+    },
     ##
     HTML: {
         'include':
@@ -328,9 +390,9 @@ snippet_res = {
          r'''(?mx)
           (?P<match>
           <lilypond
-           (\s*(?P<options>.*?)\s*:)?\s*
+           (\s+(?P<options>.*?))?\s*:\s*
            (?P<code>.*?)
-          />)''',
+          \s*/>)''',
 
         'lilypond_block':
          r'''(?msx)
@@ -339,7 +401,7 @@ snippet_res = {
            \s*(?P<options>.*?)\s*
           >
           (?P<code>.*?)
-          </lilypond>)''',
+          </lilypond\s*>)''',
 
         'lilypond_file':
          r'''(?mx)
@@ -348,7 +410,7 @@ snippet_res = {
            \s*(?P<options>.*?)\s*
           >
           \s*(?P<filename>.*?)\s*
-          </lilypondfile>)''',
+          </lilypondfile\s*>)''',
 
         'multiline_comment':
          r'''(?smx)
@@ -370,6 +432,11 @@ snippet_res = {
           (?s)
           (?P<match>
            (?P<code><pre>\s.*?</pre>\s))''',
+
+        'lilypondversion':
+         r'''(?mx)
+          (?P<match>
+          <lilypondversion\s*/>)''',
     },
 
     ##
@@ -443,6 +510,12 @@ snippet_res = {
            \\begin\s*{verbatim}
             .*?
            \\end\s*{verbatim}))''',
+
+        'lilypondversion':
+         r'''(?smx)
+          (?P<match>
+          \\lilypondversion)[^a-zA-Z]''',
+
     },
 
     ##
@@ -509,17 +582,21 @@ snippet_res = {
            @example
             \s.*?
            @end\s+example\s))''',
-    },
-}
 
+        'lilypondversion':
+         r'''(?mx)
+         [^@](?P<match>
+          @lilypondversion)[^a-zA-Z]''',
 
+    },
+}
 
 
 format_res = {
-    DOCBOOK: {        
-       'intertext': r',?\s*intertext=\".*?\"',
+    DOCBOOK: {
+        'intertext': r',?\s*intertext=\".*?\"',
         'option_sep': '\s*',
-    }, 
+    },
     HTML: {
         'intertext': r',?\s*intertext=\".*?\"',
         'option_sep': '\s*',
@@ -536,18 +613,19 @@ format_res = {
     },
 }
 
+
 # Options without a pattern in ly_options.
 simple_options = [
     EXAMPLEINDENT,
     FRAGMENT,
     NOFRAGMENT,
+    NOGETTEXT,
     NOINDENT,
     PRINTFILENAME,
     DOCTITLE,
     TEXIDOC,
     LANG,
     VERBATIM,
-    FONTLOAD,
     FILENAME,
     ALT,
     ADDVERSION
@@ -572,8 +650,6 @@ ly_options = {
         RAGGED_RIGHT: r'''ragged-right = ##t''',
 
         NORAGGED_RIGHT: r'''ragged-right = ##f''',
-
-        PACKED: r'''packed = ##t''',
     },
 
     ##
@@ -597,19 +673,37 @@ 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>'
+    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>''',
+
+        VERSION: program_version,
+
+        PRINTFILENAME: r'''<textobject>
+  <simpara>
+    <ulink url="%(base)s.ly">
+      <filename>
+        %(filename)s
+      </filename>
+    </ulink>
+  </simpara>
+</textobject>'''
     },
     ##
     HTML: {
@@ -626,8 +720,10 @@ output = {
  <a href="%(base)s.ly">''',
 
         OUTPUT: r'''
-  <img align="middle" 
-    border="0" src="%(image)s" alt="%(alt)s">''',
+  <img align="middle"
+       border="0"
+       src="%(image)s"
+       alt="%(alt)s">''',
 
         PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
 
@@ -638,34 +734,38 @@ output = {
 
         VERBATIM: r'''<pre>
 %(verb)s</pre>''',
+
+        VERSION: program_version,
     },
 
     ##
     LATEX: {
         OUTPUT: r'''{%%
-\parindent 0pt%%
-\ifx\preLilyPondExample \undefined%%
- \relax%%
-\else%%
- \preLilyPondExample%%
-\fi%%
+\parindent 0pt
+\ifx\preLilyPondExample \undefined
+\else
+  \expandafter\preLilyPondExample
+\fi
 \def\lilypondbook{}%%
-\input %(base)s-systems.tex%%
-\ifx\postLilyPondExample \undefined%%
- \relax%%
-\else%%
- \postLilyPondExample%%
-\fi%%
+\input %(base)s-systems.tex
+\ifx\postLilyPondExample \undefined
+\else
+  \expandafter\postLilyPondExample
+\fi
 }''',
 
         PRINTFILENAME: '''\\texttt{%(filename)s}
-    ''',
+''',
 
-        QUOTE: r'''\begin{quotation}%(str)s
+        QUOTE: r'''\begin{quotation}
+%(str)s
 \end{quotation}''',
 
         VERBATIM: r'''\noindent
-\begin{verbatim}%(verb)s\end{verbatim}''',
+\begin{verbatim}%(verb)s\end{verbatim}
+''',
+
+        VERSION: program_version,
 
         FILTER: r'''\begin{lilypond}[%(options)s]
 %(code)s
@@ -692,7 +792,9 @@ output = {
 <p>
  <a href="%(base)s.ly">
   <img align="middle"
-    border="0" src="%(image)s" alt="%(alt)s">
+       border="0"
+       src="%(image)s"
+       alt="%(alt)s">
  </a>
 </p>
 @end html
@@ -721,6 +823,8 @@ output = {
 %(verb)s@end verbatim
 ''',
 
+        VERSION: program_version,
+
         ADDVERSION: r'''@example
 \version @w{"@version{}"}
 @end example
@@ -745,14 +849,12 @@ PREAMBLE_LY = '''%%%% Generated by %(program_name)s
 
 
 %% ****************************************************************
-%% Start cut-&-pastable-section 
+%% 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))
@@ -827,7 +929,7 @@ def find_file (name, raise_error=True):
         full = os.path.join (i, name)
         if os.path.exists (full):
             return full
-        
+
     if raise_error:
         error (_ ("file not found: %s") % name + '\n')
         exit (1)
@@ -855,15 +957,16 @@ def verb_ly_gettext (s):
         return s
 
     s = ly_comment_re.sub (lambda m: ly_comment_gettext (t, m), s)
-    
-    for v in ly_var_def_re.findall (s):
-        s = re.sub (r"(?m)(^|[' \\#])%s([^a-zA-Z])" % v,
-                    "\\1" + t (v) + "\\2",
-                    s)
-    for id in ly_context_id_re.findall (s):
-        s = re.sub (r'(\s+|")%s(\s+|")' % id,
-                    "\\1" + t (id) + "\\2",
-                    s)
+
+    if langdefs.LANGDICT[document_language].enable_ly_identifier_l10n:
+        for v in ly_var_def_re.findall (s):
+            s = re.sub (r"(?m)(?<!\\clef)(^|[' \\#])%s([^a-zA-Z])" % v,
+                        "\\1" + t (v) + "\\2",
+                        s)
+        for id in ly_context_id_re.findall (s):
+            s = re.sub (r'(\s+|")%s(\s+|")' % id,
+                        "\\1" + t (id) + "\\2",
+                        s)
     return s
 
 texinfo_lang_re = re.compile ('(?m)^@documentlanguage (.*?)( |$)')
@@ -901,7 +1004,7 @@ class Chunk:
 
     def is_plain (self):
         return False
-    
+
 class Substring (Chunk):
     """A string that does not require extra memory."""
     def __init__ (self, source, start, end, line_number):
@@ -910,7 +1013,7 @@ class Substring (Chunk):
         self.end = end
         self.line_number = line_number
         self.override_text = None
-        
+
     def is_plain (self):
         return True
 
@@ -956,7 +1059,12 @@ class LilypondSnippet (Snippet):
         self.do_options (os, self.type)
 
     def verb_ly (self):
-        return verb_ly_gettext (self.substring ('code'))
+        verb_text = self.substring ('code')
+        if not NOGETTEXT in self.option_dict:
+            verb_text = verb_ly_gettext (verb_text)
+        if not verb_text.endswith ('\n'):
+            verb_text += '\n'
+        return verb_text
 
     def ly (self):
         contents = self.substring ('code')
@@ -1035,13 +1143,28 @@ class LilypondSnippet (Snippet):
         if not INDENT in self.option_dict:
             self.option_dict[INDENT] = '0\\mm'
 
-        # The QUOTE pattern from ly_options only emits the `line-width'
-        # keyword.
-        if has_line_width and QUOTE in self.option_dict:
-            if no_line_width_value:
-                del self.option_dict[LINE_WIDTH]
-            else:
-                del self.option_dict[QUOTE]
+        # Set a default line-width if there is none. We need this, because
+        # lilypond-book has set left-padding by default and therefore does
+        # #(define line-width (- line-width (* 3 mm)))
+        # TODO: Junk this ugly hack if the code gets rewritten to concatenate
+        # all settings before writing them in the \paper block.
+        if not LINE_WIDTH in self.option_dict:
+            if not QUOTE in self.option_dict:
+                if not LILYQUOTE in self.option_dict:
+                    self.option_dict[LINE_WIDTH] = "#(- paper-width \
+left-margin-default right-margin-default)"
+
+    def get_option_list (self):
+        if not 'option_list' in self.__dict__:
+            option_list = []
+            for (key, value) in self.option_dict.items ():
+                if value == None:
+                    option_list.append (key)
+                else:
+                    option_list.append (key + '=' + value)
+            option_list.sort ()
+            self.option_list = option_list
+        return self.option_list
 
     def compose_ly (self, code):
         if FRAGMENT in self.option_dict:
@@ -1080,19 +1203,22 @@ class LilypondSnippet (Snippet):
         override.update (default_ly_options)
 
         option_list = []
-        for (key, value) in self.option_dict.items ():
-            if value == None:
-                option_list.append (key)
+        for option in self.get_option_list ():
+            for name in PROCESSING_INDEPENDENT_OPTIONS:
+                if option.startswith (name):
+                    break
             else:
-                option_list.append (key + '=' + value)
+                option_list.append (option)
         option_string = ','.join (option_list)
-
         compose_dict = {}
         compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
         for a in compose_types:
             compose_dict[a] = []
 
-        for (key, value) in self.option_dict.items ():
+        option_names = self.option_dict.keys ()
+        option_names.sort ()
+        for key in option_names:
+            value = self.option_dict[key]
             (c_key, c_value) = classic_lilypond_book_compatibility (key, value)
             if c_key:
                 if c_value:
@@ -1141,9 +1267,6 @@ class LilypondSnippet (Snippet):
         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'
 
         d = globals().copy()
         d.update (locals())
@@ -1151,27 +1274,52 @@ class LilypondSnippet (Snippet):
 
     def get_checksum (self):
         if not self.checksum:
-            hash = md5.md5 (self.relevant_contents (self.full_ly ()))
+            # Work-around for md5 module deprecation warning in python 2.5+:
+            try: 
+                from hashlib import md5
+            except ImportError:
+                from md5 import md5
+
+            # We only want to calculate the hash based on the snippet
+            # code plus fragment options relevant to processing by
+            # lilypond, not the snippet + preamble
+            hash = md5 (self.relevant_contents (self.ly ()))
+            for option in self.get_option_list ():
+                for name in PROCESSING_INDEPENDENT_OPTIONS:
+                    if option.startswith (name):
+                        break
+                else:
+                    hash.update (option)
 
             ## let's not create too long names.
             self.checksum = hash.hexdigest ()[:10]
-            
+
         return self.checksum
 
     def basename (self):
         cs = self.get_checksum ()
-        name = '%s/lily-%s' % (cs[:2], cs[2:10])
+        name = '%s/lily-%s' % (cs[:2], cs[2:])
         return name
 
+    final_basename = basename
+
     def write_ly (self):
         base = self.basename ()
         path = os.path.join (global_options.lily_output_dir, base)
         directory = os.path.split(path)[0]
         if not os.path.isdir (directory):
             os.makedirs (directory)
-        out = file (path + '.ly', 'w')
-        out.write (self.full_ly ())
-        file (path + '.txt', 'w').write ('image of music')
+        filename = path + '.ly'
+        if os.path.exists (filename):
+            diff_against_existing = filter_pipe (self.full_ly (), 'diff -u %s -' % filename)
+            if diff_against_existing:
+                warning ("%s: duplicate filename but different contents of orginal file,\n\
+printing diff against existing file." % filename)
+                ly.stderr_write (diff_against_existing)
+        else:
+            out = file (filename, 'w')
+            out.write (self.full_ly ())
+            file (path + '.txt', 'w').write ('image of music')
 
     def relevant_contents (self, ly):
         return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', ly)
@@ -1182,19 +1330,31 @@ class LilypondSnippet (Snippet):
             print '\nMissing', missing
             raise CompileError(self.basename())
         for name in existing:
+            if (global_options.use_source_file_names
+                and isinstance (self, LilypondFileSnippet)):
+                base, ext = os.path.splitext (name)
+                components = base.split ('-')
+                # ugh, assume filenames with prefix with one dash (lily-xxxx)
+                if len (components) > 2:
+                    base_suffix = '-' + components[-1]
+                else:
+                    base_suffix = ''
+                final_name = self.final_basename () + base_suffix + ext
+            else:
+                final_name = name
             try:
-                os.unlink (os.path.join (destination, name))
+                os.unlink (os.path.join (destination, final_name))
             except OSError:
                 pass
 
             src = os.path.join (output_dir, name)
-            dst = os.path.join (destination, name)
+            dst = os.path.join (destination, final_name)
             dst_path = os.path.split(dst)[0]
             if not os.path.isdir (dst_path):
                 os.makedirs (dst_path)
             os.link (src, dst)
 
-        
+
     def all_output_files (self, output_dir, output_dir_files):
         """Return all files generated in lily_output_dir, a set.
 
@@ -1207,7 +1367,7 @@ class LilypondSnippet (Snippet):
         def consider_file (name):
             if name in output_dir_files:
                 result.add (name)
-             
+
         def require_file (name):
             if name in output_dir_files:
                 result.add (name)
@@ -1262,14 +1422,14 @@ class LilypondSnippet (Snippet):
             # markups do not output a signature.
             if 'ddump-signature' in global_options.process_cmd:
                 consider_file (systemfile + '.signature')
-             
-       
+
+
         return (result, missing)
-    
+
     def is_outdated (self, output_dir, current_files):
         found, missing = self.all_output_files (output_dir, current_files)
         return missing
-    
+
     def filter_text (self):
         """Run snippet bodies through a command (say: convert-ly).
 
@@ -1289,29 +1449,29 @@ class LilypondSnippet (Snippet):
         return func (self)
 
     def get_images (self):
-        base = self.basename ()
+        base = self.final_basename ()
 
         single = '%(base)s.png' % vars ()
         multiple = '%(base)s-page1.png' % vars ()
         images = (single,)
-        if (os.path.exists (multiple) 
+        if (os.path.exists (multiple)
             and (not os.path.exists (single)
                  or (os.stat (multiple)[stat.ST_MTIME]
                      > os.stat (single)[stat.ST_MTIME]))):
             count = ps_page_count ('%(base)s.eps' % vars ())
             images = ['%s-page%d.png' % (base, page) for page in range (1, count+1)]
             images = tuple (images)
-            
+
         return images
 
     def output_docbook (self):
         str = ''
-        base = self.basename ()
+        base = self.final_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 += self.output_print_filename (DOCBOOK)
+            if (self.substring('inline') == 'inline'):
                 str = '<inlinemediaobject>' + str + '</inlinemediaobject>'
             else:
                 str = '<mediaobject>' + str + '</mediaobject>'
@@ -1319,10 +1479,10 @@ class LilypondSnippet (Snippet):
                 verb = verbatim_html (self.verb_ly ())
                 str = output[DOCBOOK][VERBATIM] % vars () + str
         return str
-       
+
     def output_html (self):
         str = ''
-        base = self.basename ()
+        base = self.final_basename ()
         if self.format == HTML:
             str += self.output_print_filename (HTML)
             if VERBATIM in self.option_dict:
@@ -1351,13 +1511,13 @@ class LilypondSnippet (Snippet):
             info_image_path = os.path.join (global_options.info_images_dir, base)
             str += output[TEXINFO][OUTPUTIMAGE] % vars ()
 
-        base = self.basename ()
+        base = self.final_basename ()
         str += output[self.format][OUTPUT] % vars ()
         return str
 
     def output_latex (self):
         str = ''
-        base = self.basename ()
+        base = self.final_basename ()
         if self.format == LATEX:
             str += self.output_print_filename (LATEX)
             if VERBATIM in self.option_dict:
@@ -1370,7 +1530,7 @@ class LilypondSnippet (Snippet):
         if 0:
             breaks = self.ly ().count ("\n")
             str += "".ljust (breaks, "\n").replace ("\n","%\n")
-        
+
         if QUOTE in self.option_dict:
             str = output[LATEX][QUOTE] % vars ()
         return str
@@ -1378,7 +1538,7 @@ class LilypondSnippet (Snippet):
     def output_print_filename (self, format):
         str = ''
         if PRINTFILENAME in self.option_dict:
-            base = self.basename ()
+            base = self.final_basename ()
             filename = os.path.basename (self.substring ('filename'))
             str = output[format][PRINTFILENAME] % vars ()
 
@@ -1386,14 +1546,14 @@ class LilypondSnippet (Snippet):
 
     def output_texinfo (self):
         str = self.output_print_filename (TEXINFO)
-        base = self.basename ()
+        base = self.final_basename ()
         if DOCTITLE in self.option_dict:
             doctitle = base + '.doctitle'
             translated_doctitle = doctitle + document_language
             if os.path.exists (translated_doctitle):
-                str += '@lydoctitle %s\n' % open (translated_doctitle).read ()
+                str += '@lydoctitle %s\n\n' % open (translated_doctitle).read ()
             elif os.path.exists (doctitle):
-                str += '@lydoctitle %s\n' % open (doctitle).read ()
+                str += '@lydoctitle %s\n\n' % open (doctitle).read ()
         if TEXIDOC in self.option_dict:
             texidoc = base + '.texidoc'
             translated_texidoc = texidoc + document_language
@@ -1438,19 +1598,40 @@ class LilypondFileSnippet (LilypondSnippet):
         s = self.contents
         s = re_begin_verbatim.split (s)[-1]
         s = re_end_verbatim.split (s)[0]
-        return verb_ly_gettext (s)
+        if not NOGETTEXT in self.option_dict:
+            s = verb_ly_gettext (s)
+        if not s.endswith ('\n'):
+            s += '\n'
+        return s
 
     def ly (self):
         name = self.substring ('filename')
         return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
                 % (name, self.contents))
 
+    def final_basename (self):
+        if global_options.use_source_file_names:
+            base = os.path.splitext (os.path.basename (self.substring ('filename')))[0]
+            return base
+        else:
+            return self.basename ()
+
+
+class LilyPondVersionString (Snippet):
+    """A string that does not require extra memory."""
+    def __init__ (self, type, match, format, line_number):
+        Snippet.__init__ (self, type, match, format, line_number)
+
+    def replacement_text (self):
+        return output[self.format][self.type]
+
 
 snippet_type_to_class = {
     'lilypond_file': LilypondFileSnippet,
     'lilypond_block': LilypondSnippet,
     'lilypond': LilypondSnippet,
     'include': IncludeSnippet,
+    'lilypondversion': LilyPondVersionString,
 }
 
 def find_linestarts (s):
@@ -1493,7 +1674,7 @@ def find_toplevel_snippets (input_string, format, types):
         for type in types:
             if not found[type] or found[type][0] < index:
                 found[type] = None
-                
+
                 m = res[type].search (input_string[index:endex])
                 if not m:
                     continue
@@ -1512,8 +1693,8 @@ def find_toplevel_snippets (input_string, format, types):
 
                 found[type] = (start, snip)
 
-            if (found[type] 
-                and (not first 
+            if (found[type]
+                and (not first
                      or found[type][0] < found[first][0])):
                 first = type
 
@@ -1546,7 +1727,7 @@ def find_toplevel_snippets (input_string, format, types):
 
 def filter_pipe (input, cmd):
     """Pass input through cmd, and return the result."""
-    
+
     if global_options.verbose:
         progress (_ ("Opening filter `%s'") % cmd)
 
@@ -1581,13 +1762,13 @@ def system_in_directory (cmd, directory):
 
     Because of win32 compatibility, we can't simply use subprocess.
     """
-    
+
     current = os.getcwd()
     os.chdir (directory)
-    ly.system(cmd, be_verbose=global_options.verbose, 
+    ly.system(cmd, be_verbose=global_options.verbose,
               progress_p=1)
     os.chdir (current)
-    
+
 
 def process_snippets (cmd, snippets,
                       format, lily_output_dir):
@@ -1595,23 +1776,23 @@ def process_snippets (cmd, snippets,
 
     if not snippets:
         return
-    
+
     if format in (HTML, TEXINFO) and '--formats' not in cmd:
         cmd += ' --formats=png '
     elif format in (DOCBOOK) and '--formats' not in cmd:
         cmd += ' --formats=png,pdf '
 
     checksum = snippet_list_checksum (snippets)
-    contents = '\n'.join (['snippet-map-%d.ly' % checksum] 
-                          + [snip.basename() + '.ly' for snip in snippets])
+    contents = '\n'.join (['snippet-map-%d.ly' % checksum]
+                          + list (set ([snip.basename() + '.ly' for snip in snippets])))
     name = os.path.join (lily_output_dir,
                          'snippet-names-%d.ly' % checksum)
     file (name, 'wb').write (contents)
 
     system_in_directory (' '.join ([cmd, ly.mkarg (name)]),
                          lily_output_dir)
-            
-        
+
+
 ###
 # Retrieve dimensions from LaTeX
 LATEX_INSPECTION_DOCUMENT = r'''
@@ -1629,13 +1810,13 @@ def get_latex_textwidth (source):
     m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
     if m == None:
         warning (_ ("cannot find \\begin{document} in LaTeX document"))
-        
+
         ## what's a sensible default?
         return 550.0
-    
+
     preamble = source[:m.start (0)]
     latex_document = LATEX_INSPECTION_DOCUMENT % vars ()
-    
+
     (handle, tmpfile) = tempfile.mkstemp('.tex')
     logfile = os.path.splitext (tmpfile)[0] + '.log'
     logfile = os.path.split (logfile)[1]
@@ -1643,26 +1824,26 @@ def get_latex_textwidth (source):
     tmp_handle = os.fdopen (handle,'w')
     tmp_handle.write (latex_document)
     tmp_handle.close ()
-    
+
     ly.system ('%s %s' % (global_options.latex_program, tmpfile),
                be_verbose=global_options.verbose)
     parameter_string = file (logfile).read()
-    
+
     os.unlink (tmpfile)
     os.unlink (logfile)
 
     columns = 0
-    m = re.search ('columns=([0-9.]*)', parameter_string)
+    m = re.search ('columns=([0-9.]+)', parameter_string)
     if m:
         columns = int (m.group (1))
 
     columnsep = 0
-    m = re.search ('columnsep=([0-9.]*)pt', parameter_string)
+    m = re.search ('columnsep=([0-9.]+)pt', parameter_string)
     if m:
         columnsep = float (m.group (1))
 
     textwidth = 0
-    m = re.search ('textwidth=([0-9.]*)pt', parameter_string)
+    m = re.search ('textwidth=([0-9.]+)pt', parameter_string)
     if m:
         textwidth = float (m.group (1))
         if columns:
@@ -1678,7 +1859,7 @@ def modify_preamble (chunk):
                r"\\usepackage{graphics}" + '\n'
                + r"\\begin{document}",
                str)
-        chunk.override_text = str 
+        chunk.override_text = str
 
 
 format2ext = {
@@ -1726,8 +1907,8 @@ def do_process_cmd (chunks, input_name, options):
 
     output_files = split_output_files (options.lily_output_dir)
     outdated = [c for c in snippets if c.is_outdated (options.lily_output_dir, output_files)]
-    
-    write_file_map (outdated, input_name)    
+
+    write_file_map (outdated, input_name)
     progress (_ ("Writing snippets..."))
     for snippet in outdated:
         snippet.write_ly()
@@ -1846,15 +2027,15 @@ def do_file (input_filename, included=False):
         global_options.output_dir = os.getcwd()
     else:
         global_options.output_dir = os.path.abspath(global_options.output_dir)
-        
+
         if not os.path.isdir (global_options.output_dir):
             os.mkdir (global_options.output_dir, 0777)
         os.chdir (global_options.output_dir)
 
     output_filename = os.path.join(global_options.output_dir,
                                    input_base + format2ext[global_options.format])
-    if (os.path.exists (input_filename) 
-        and os.path.exists (output_filename) 
+    if (os.path.exists (input_filename)
+        and os.path.exists (output_filename)
         and samefile (output_filename, input_fullname)):
      error (
      _ ("Output would overwrite input file; use --output."))
@@ -1879,6 +2060,7 @@ def do_file (input_filename, included=False):
             'lilypond_file',
             'include',
             'lilypond',
+            'lilypondversion',
         )
         progress (_ ("Dissecting..."))
         chunks = find_toplevel_snippets (source, global_options.format, snippet_types)
@@ -1901,7 +2083,7 @@ def do_file (input_filename, included=False):
             write_if_updated (output_filename,
                      [s.replacement_text ()
                      for s in chunks])
-        
+
         def process_include (snippet):
             os.chdir (original_dir)
             name = snippet.substring ('filename')
@@ -1914,7 +2096,7 @@ def do_file (input_filename, included=False):
                                       chunks))
 
         return chunks + reduce (lambda x, y: x + y, include_chunks, [])
-        
+
     except CompileError:
         os.chdir (original_dir)
         progress (_ ("Removing `%s'") % output_filename)
@@ -1930,14 +2112,14 @@ def do_options ():
         global_options.format = TEXINFO
 
     global_options.include_path =  map (os.path.abspath, global_options.include_path)
-    
+
     if global_options.warranty:
         warranty ()
         exit (0)
     if not args or len (args) > 1:
         opt_parser.print_help ()
         exit (2)
-        
+
     return args
 
 def main ():
@@ -1946,7 +2128,7 @@ def main ():
 
     basename = os.path.splitext (files[0])[0]
     basename = os.path.split (basename)[1]
-    
+
     if not global_options.format:
         global_options.format = guess_format (files[0])
 
@@ -1955,7 +2137,7 @@ def main ():
         formats += ',png'
 
     if global_options.process_cmd == '':
-        global_options.process_cmd = (lilypond_binary 
+        global_options.process_cmd = (lilypond_binary
                                       + ' --formats=%s -dbackend=eps ' % formats)
 
     if global_options.process_cmd:
@@ -1972,13 +2154,15 @@ def main ():
         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.latex_program == 'latex':
+                global_options.latex_program = 'pdflatex'
+
     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 -dno-strip-output-dir"
 
     if global_options.lily_output_dir:
@@ -1987,7 +2171,7 @@ def main ():
             os.makedirs (global_options.lily_output_dir)
     else:
         global_options.lily_output_dir = os.path.abspath(global_options.output_dir)
-        
+
 
     identify ()
     try:
@@ -2003,7 +2187,7 @@ def main ():
     final_output_file = os.path.join (global_options.output_dir,
                      base_file_name
                      + '.%s' % global_options.format)
-    
+
     os.chdir (original_dir)
     file (dep_file, 'w').write ('%s: %s'
                                 % (final_output_file, ' '.join (inputs)))