]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/lilypond-book.py
Merge branch 'jneeman' of git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond into jneeman
[lilypond.git] / scripts / lilypond-book.py
index 2faaa9c3bfdbd042911dd55eac047d40ad771a75..6699ba2dcb8b5a269536238c32f4331b6c5f92cb 100644 (file)
@@ -1,4 +1,4 @@
-#!@PYTHON@
+#!@TARGET_PYTHON@
 
 '''
 Example usage:
@@ -35,40 +35,23 @@ import commands
 import os
 import sys
 import re
-
-# 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.
+import md5
 
 ################
 # 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]
-        
-    datadir = os.path.join (datadir, "share/lilypond/current/")
-sys.path.insert (0, os.path.join (datadir, 'python'))
+for d in ['@lilypond_datadir@',
+          '@lilypond_libdir@']:
+    sys.path.insert (0, os.path.join (d, 'python'))
 
 # dynamic relocation, for GUB binaries.
-bindir = os.path.split (sys.argv[0])[0]
-
+bindir = os.path.abspath (os.path.split (sys.argv[0])[0])
 
-for prefix_component in ['share', 'lib']:
-    datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % prefix_component)
+os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
+for p in ['share', 'lib']:
+    datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p)
     sys.path.insert (0, datadir)
 
 
@@ -85,7 +68,7 @@ original_dir = os.getcwd ()
 backend = 'ps'
 
 help_summary = _ (
-'''Process LilyPond snippets in hybrid HTML, LaTeX, or texinfo document.
+'''Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.
 
 Example usage:
 
@@ -141,47 +124,55 @@ def warranty ():
 
 def get_option_parser ():
     p = ly.get_option_parser (usage='lilypond-book [OPTIONS] FILE',
-                 version="@TOPLEVEL_VERSION@",
-                 description=help_summary)
+                              version="@TOPLEVEL_VERSION@",
+                              description=help_summary)
 
     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)'''),
-           action='store')
+                  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, docbook)'''),
+                  action='store')
+    
     p.add_option ("-I", '--include', help=_('add DIR to include path'),
-           metavar="DIR",
-           action='append', dest='include_path',
-           default=[os.path.abspath (os.getcwd ())])
+                  metavar="DIR",
+                  action='append', dest='include_path',
+                  default=[os.path.abspath (os.getcwd ())])
     
     p.add_option ("-o", '--output', help=_('write output to DIR'),
-           metavar="DIR",
-           action='store', dest='output_name',
-           default='')
+                  metavar="DIR",
+                  action='store', dest='output_name',
+                  default='')
     p.add_option ('-P', '--process', metavar=_("COMMAND"),
-           help = _ ("process ly_files using COMMAND FILE..."),
-           action='store', 
-           dest='process_cmd', default='lilypond -b eps')
+                  help = _ ("process ly_files using COMMAND FILE..."),
+                  action='store', 
+                  dest='process_cmd', default='lilypond -b 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'''),
-           default=None)
+                  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"),
-           action="store_true",
-           default=False,
-           dest="verbose")
-           
+                  action="store_true",
+                  default=False,
+                  dest="verbose")
+    
     p.add_option ('-w', '--warranty',
-           help=_("show warranty and copyright"),
-           action='store_true')
+                  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''')
+                         description='''Report bugs via http://post.gmane.org/post.php'''
+                         '''?group=gmane.comp.gnu.lilypond.bugs\n''')
     
     return p
 
@@ -201,6 +192,7 @@ default_ly_options = { 'alt': "[image of music]" }
 #
 AFTER = 'after'
 BEFORE = 'before'
+DOCBOOK = 'docbook'
 EXAMPLEINDENT = 'exampleindent'
 FILTER = 'filter'
 FRAGMENT = 'fragment'
@@ -249,6 +241,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':
@@ -446,6 +475,10 @@ snippet_res = {
 
 
 format_res = {
+    DOCBOOK: {        
+       'intertext': r',?\s*intertext=\".*?\"',
+        'option_sep': '\s*',
+    }, 
     HTML: {
         'intertext': r',?\s*intertext=\".*?\"',
         'option_sep': '\s*',
@@ -515,6 +548,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>
@@ -548,7 +596,6 @@ output = {
     LATEX: {
         OUTPUT: r'''{%%
 \parindent 0pt%%
-\catcode`\@=12%%
 \ifx\preLilyPondExample \undefined%%
  \relax%%
 \else%%
@@ -641,17 +688,7 @@ 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)
+\\include "lilypond-book-preamble.ly"
 %(preamble_string)s
 
 
@@ -664,13 +701,13 @@ PREAMBLE_LY = '''%%%% Generated by %(program_name)s
 %% ****************************************************************
 
 \paper {
- #(define dump-extents #t)
- %(font_dump_setting)s
- %(paper_string)s
 #(define dump-extents #t)
 %(font_dump_setting)s
 %(paper_string)s
 }
 
 \layout {
- %(layout_string)s
 %(layout_string)s
 }
 '''
 
@@ -850,7 +887,9 @@ class Lilypond_snippet (Snippet):
         self.do_options (os, self.type)
 
     def ly (self):
-        return self.substring ('code')
+        contents = self.substring ('code')
+        return ('\\sourcefileline %d\n%s'
+                % (self.line_number - 1, contents))
 
     def full_ly (self):
         s = self.ly ()
@@ -864,7 +903,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:
@@ -1028,56 +1067,63 @@ 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)[^\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 = base + '.ly'
+        tex_file = base + '.tex'
+        eps_file = base + '.eps'
+        systems_file = base + '-systems.tex'
+
+        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
+        if global_options.verbose:
+            print 'OUT OF DATE: ', ly_file
         return self
 
     def png_is_outdated (self):
         base = self.basename ()
-        ok = self.ly_is_outdated ()
+        # FIXME: refactor stupid OK stuff
+        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:
+
+            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)
@@ -1088,6 +1134,7 @@ class Lilypond_snippet (Snippet):
         if backend == 'ps':
             return 0
 
+        # FIXME: refactor stupid OK stuff
         base = self.basename ()
         ok = self.ly_is_outdated ()
         ok = ok and (os.path.exists (base + '.texstr'))
@@ -1122,6 +1169,22 @@ 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.substring ('code'))
+                str = output[DOCBOOK][VERBATIM] % vars () + str
+        return str
+       
     def output_html (self):
         str = ''
         base = self.basename ()
@@ -1164,7 +1227,7 @@ class Lilypond_snippet (Snippet):
             if VERBATIM in self.option_dict:
                 verb = self.substring ('code')
                 str += (output[LATEX][VERBATIM] % vars ())
-        
+
         str += (output[LATEX][OUTPUT] % vars ())
 
         ## todo: maintain breaks
@@ -1223,8 +1286,9 @@ class Lilypond_snippet (Snippet):
 class Lilypond_file_snippet (Lilypond_snippet):
     def ly (self):
         name = self.substring ('filename')
-        return '\\sourcefilename \"%s\"\n%s' \
-            % (name, open (find_file (name)).read ())
+        contents = open (find_file (name)).read ()
+        return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
+                % (name, contents))
 
 snippet_type_to_class = {
     'lilypond_file': Lilypond_file_snippet,
@@ -1256,12 +1320,7 @@ def find_toplevel_snippets (s, types):
 
     snippets = []
     index = 0
-    ## found = dict (map (lambda x: (x, None),
-    ##                      types))
-    ## urg python2.1
-    found = {}
-    map (lambda x, f = found: f.setdefault (x, None),
-      types)
+    found = dict ([(t, None) for t in types])
 
     line_starts = find_linestarts (s)
     line_start_idx = 0
@@ -1373,29 +1432,38 @@ def is_derived_class (cl, baseclass):
 
 def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets):
     ly_names = filter (lambda x: x,
-             map (Lilypond_snippet.basename, ly_snippets))
+                       map (Lilypond_snippet.basename, ly_snippets))
     texstr_names = filter (lambda x: x,
-             map (Lilypond_snippet.basename, texstr_snippets))
+                           map (Lilypond_snippet.basename, texstr_snippets))
+    
     png_names = filter (lambda x: x,
-              map (Lilypond_snippet.basename, png_snippets))
+                        map (Lilypond_snippet.basename, 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 += ' --formats=png '
+    if global_options.format in (DOCBOOK):
+        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))
+                                 '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 (string.join ([cmd, 'snippet-names']))
+
 
 LATEX_INSPECTION_DOCUMENT = r'''
 \nonstopmode
@@ -1474,6 +1542,7 @@ ext2format = {
     '.texi': TEXINFO,
     '.texinfo': TEXINFO,
     '.xml': HTML,
+    '.lyxml': DOCBOOK
 }
 
 format2ext = {
@@ -1481,6 +1550,7 @@ format2ext = {
     # TEXINFO: '.texinfo',
     TEXINFO: '.texi',
     LATEX: '.tex',
+    DOCBOOK: '.xml'
 }
 
 class Compile_error:
@@ -1489,15 +1559,13 @@ 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)
 #(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')
 
@@ -1507,27 +1575,25 @@ def do_process_cmd (chunks, input_name):
              chunks)
 
     write_file_map (all_lys, input_name)
-    ly_outdated = \
-     filter (lambda x: is_derived_class (x.__class__,
-                       Lilypond_snippet)
-              and x.ly_is_outdated (),
-         chunks)
-    texstr_outdated = \
-     filter (lambda x: is_derived_class (x.__class__,
-                       Lilypond_snippet)
-              and x.texstr_is_outdated (),
-         chunks)
-    png_outdated = \
-     filter (lambda x: is_derived_class (x.__class__,
-                       Lilypond_snippet)
-              and x.png_is_outdated (),
-         chunks)
-
+    ly_outdated = filter (lambda x: is_derived_class (x.__class__,
+                                                      Lilypond_snippet)
+                          and x.ly_is_outdated (), chunks)
+    texstr_outdated = filter (lambda x: is_derived_class (x.__class__,
+                                                          Lilypond_snippet)
+                              and x.texstr_is_outdated (),
+                              chunks)
+    png_outdated = filter (lambda x: is_derived_class (x.__class__,
+                                                        Lilypond_snippet)
+                           and x.png_is_outdated (),
+                           chunks)
+
+    outdated = png_outdated + texstr_outdated + ly_outdated
+    
     progress (_ ("Writing snippets..."))
     map (Lilypond_snippet.write_ly, ly_outdated)
     progress ('\n')
 
-    if ly_outdated:
+    if outdated:
         progress (_ ("Processing..."))
         progress ('\n')
         process_snippets (global_options.process_cmd, ly_outdated, texstr_outdated, png_outdated)
@@ -1596,7 +1662,7 @@ def do_file (input_filename):
         input_base = 'stdin'
     else:
         input_base = os.path.basename \
-              (os.path.splitext (input_filename)[0])
+                     (os.path.splitext (input_filename)[0])
 
     # Only default to stdout when filtering.
     if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd):
@@ -1610,9 +1676,9 @@ def do_file (input_filename):
                 os.mkdir (global_options.output_name, 0777)
             os.chdir (global_options.output_name)
         else: 
-            if os.path.exists (input_filename) \
-             and os.path.exists (output_filename) \
-             and samefile (output_filename, input_fullname):
+            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."))
              exit (2)
@@ -1703,6 +1769,7 @@ def do_options ():
     return args
 
 def main ():
+    # FIXME: 85 lines of `main' macramee??
     files = do_options ()
 
     file = files[0]
@@ -1714,16 +1781,33 @@ 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 += string.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.format in (TEXINFO, LATEX)
+        and global_options.create_pdf):
+        global_options.process_cmd += "--pdf  -dinclude-eps-fonts -dgs-load-fonts "
+
+        
+    
+    if global_options.verbose:
+        global_options.process_cmd += " --verbose "
+
+    global_options.process_cmd += " -dread-file-list -deps-box-padding=-3 "
+
     identify ()
 
     try:
@@ -1747,18 +1831,18 @@ def main ():
         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')
+        if not global_options.psfonts and not global_options.create_pdf:
+            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')
 
     inputs = note_input_file ('')
     inputs.pop ()