]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/mudela-book.py
release: 1.3.0
[lilypond.git] / scripts / mudela-book.py
index 35c3b936443aa5535cfc7b5660959b6d70bc6d49..59bde3bbd7dbd24d3c0f065c3d4050fed0c7492d 100644 (file)
@@ -7,16 +7,20 @@ import getopt
 import sys
 import __main__
 
-outdir = 'out'
+
 initfile = ''
 program_version = '@TOPLEVEL_VERSION@'
 
 cwd = os.getcwd ()
 include_path = [cwd]
+dep_prefix = ''
 
-# TODO: use splitting iso. \mudelagraphic.
+# TODO: Figure out clean set of options.
 
+# BUG: does not handle \verb|\begin{verbatim}\end{verbatim}| correctly.
+# Should make a joint RE for \verb and \begin, \end{verbatim}
 #
+
 default_music_fontsize = 16
 default_text_fontsize = 12
 
@@ -49,11 +53,13 @@ options = [
   ('', 'M', 'dependencies', 'write dependencies'),
   ('', 'n', 'no-lily', 'don\'t run lilypond'),
   ('FILE', 'o', 'outname', 'prefix for filenames'),
-  ('', 'v', 'version', 'print version information' )
+  ('', 'v', 'version', 'print version information' ),
+  ('PREF', '',  'dep-prefix', 'prepend PREF before each -M dependency')
   ]
 
 format = ''
-no_lily = 0
+run_lilypond = 1
+use_hash = 1
 no_match = 'a\ba'
 
 # format specific strings, ie. regex-es for input, and % strings for output
@@ -66,7 +72,7 @@ output_dict= {
     }
   >
 \end{mudela}""", 
-               'output-mudela':r"""\begin%s{mudela}
+               'output-mudela':r"""\begin[%s]{mudela}
 %s
 \end{mudela}""",
                'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
@@ -83,8 +89,17 @@ output_dict= {
 %s
 @end example
 """,
+
+# do some tweaking: @ is needed in some ps stuff.
+# override EndLilyPondOutput, since @tex is done
+# in a sandbox, you can't do \input lilyponddefs at the
+# top of the document.
                  'output-all': r"""@tex
+\catcode`\@=12
+\input lilyponddefs
+\def\EndLilyPondOutput{}
 \input %s.tex
+\catcode`\@=0
 @end tex
 @html
 <img src=%s.png>
@@ -93,9 +108,14 @@ output_dict= {
                }
        }
 
+def output_verbatim (body):
+       if __main__.format == 'texi':
+               body = re.sub ('([@{}])', '@\\1', body)
+       return get_output ('output-verbatim') % body
+
 re_dict = {
-       'latex': {'input': '\\\\input{?([^}\t \n}]*)',
-                 'include': '\\\\include{([^}]+)}',
+       'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
+                 'include': '\\\\mbinclude{([^}]+)}',
                 
                  'comma-sep' : ', *',
                  'header': r"""\\documentclass(\[.*?\])?""",
@@ -109,7 +129,9 @@ re_dict = {
                  'def-post-re': r"""\\def\\postMudelaExample""",
                  'def-pre-re': r"""\\def\\preMudelaExample""",           
                  },
-       'texi': {'input': '@include[ \n\t]+([^\t \n]*)',
+       
+       'texi': {
+                'input':  '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
                 'include': no_match,
                 'header': no_match,
                 'preamble-end': no_match,
@@ -118,7 +140,7 @@ re_dict = {
                 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
                 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
                 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
-                'interesting-cs': r"""[\\@](node|mudelagraphic)""",
+                'interesting-cs': r"""[\\@](chapter|section)""",
                  'comma-sep' : ', *',           
                 }
        }
@@ -132,6 +154,16 @@ for r in re_dict.keys ():
        re_dict[r] = newdict
 
        
+def uniq (list):
+       list.sort ()
+       s = list
+       list = []
+       for x in s:
+               if x not in list:
+                       list.append (x)
+       return list
+               
+
 def get_output (name):
        return  output_dict[format][name]
 
@@ -144,7 +176,7 @@ def bounding_box_dimensions(fname):
                fd = open(fname)
        except IOError:
                error ("Error opening `%s'" % fname)
-       str = fd.read (-1)
+       str = fd.read ()
        s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
        if s:
                return (int(s.group(3))-int(s.group(1)), 
@@ -153,15 +185,25 @@ def bounding_box_dimensions(fname):
                return (0,0)
 
 
+
+read_files = []
 def find_file (name):
+       f = None
        for a in include_path:
                try:
                        nm = os.path.join (a, name)
                        f = open (nm)
-                       return nm
+                       __main__.read_files.append (nm)
+                       break
                except IOError:
                        pass
-       return ''
+
+
+       if f:
+               return f.read ()
+       else:
+               error ("File not found `%s'\n" % name)
+               return ''
 
 def error (str):
        sys.stderr.write (str + "\n  Exiting ... \n\n")
@@ -190,46 +232,47 @@ def compose_full_body (body, opts):
                        latex_size = string.atoi (m.group (1))
 
 
+
        if 'twocolumn' in opts:
                cols = 2
                
-       if 'fragment' or 'singleline' in opts:
+
+       # urg: breaks on \include of full score
+       # Use nofly option if you want to \include full score.
+       if 'nofly'  not in opts and not re.search ('\\\\score', body):
+               opts.append ('fragment')
+
+       if 'fragment' in opts and 'nosingleline' not in opts:
+               opts.append ('singleline')
+
+       if 'singleline' in opts:
                l = -1.0;
        else:
                l = latex_linewidths[cols][paper][latex_size]
 
-       # urg: breaks on \include of full score
-       # if not 'nofly' in opts and not re.search ('\\\\score', body):
-       #       opts.append ('fly')
 
-       if 'fly' in opts:
+       if 'relative' in opts:
+               body = '\\relative c { %s }' % body
+
+       
+       if 'fragment' in opts:
                body = r"""\score { 
-  \notes\relative c {
-    %s
-  }
+  \notes { %s }
   \paper { }  
 }""" % body
 
-               
+        opts = uniq (opts)
+        optstring = string.join (opts, ' ')
+       optstring = re.sub ('\n', ' ', optstring)
+       
        body = r"""
-%% Generated by mudela-book.py
+%% Generated by mudela-book.py; options are %s
 \include "paper%d.ly"
 \paper  { linewidth = %f \pt; } 
-""" % (music_size, l) + body
+""" % (optstring, music_size, l) + body
 
        return body
 
-def inclusion_func (match, surround):
-       insert = match.group (0)
-       try:
-               (insert, d) = read_doc_file (match.group(1))
-               deps = deps + d
-               insert = surround + insert + surround
-       except:
-               sys.stderr.write("warning: can't find %s, let's hope latex will\n" % m.group(1))
-
-       return (insert, deps)
-
 def find_inclusion_chunks (regex, surround, str):
        chunks = []
        while str:
@@ -242,7 +285,8 @@ def find_inclusion_chunks (regex, surround, str):
 
                chunks.append (('input', str[: m.start (0)]))
                chunks.append (('input', surround))
-               chunks = chunks + read_doc_file (m.group (1))
+               chunks = chunks +  read_doc_file (m.group (1))
+
                chunks.append (('input', surround))
 
                str = str [m.end (0):]
@@ -257,18 +301,7 @@ def find_input_chunks (str):
 def read_doc_file (filename):
        """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
        str = ''
-       for fn in [filename, filename+'.tex', filename+'.doc']:
-               try:
-                       f = open(fn)
-                       str = f.read (-1)
-               except:
-                       pass
-               
-
-       if not str:
-               raise IOError
-
-       retdeps =  [filename]
+       str = find_file(filename)
 
        if __main__.format == '':
                latex =  re.search ('\\\\document', str[:200])
@@ -280,17 +313,16 @@ def read_doc_file (filename):
                else:
                        __main__.format = 'latex'
                        
-       chunks = find_verbatim_chunks (str)
-       newchunks = []
+       chunks = [('input', str)]
 
-       for func in (find_include_chunks, find_input_chunks):
+       for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
+               newchunks = []
                for c in chunks:
                        if c[0] == 'input':
-                               ch = func (c[1])
-                               newchunks = newchunks + ch
+                               newchunks = newchunks + func (c[1])
                        else:
                                newchunks.append (c)
-
+               chunks = newchunks
        
        return chunks
 
@@ -340,31 +372,38 @@ def completize_preamble (str):
 def find_verbatim_chunks (str):
        """Chop STR into a list of tagged chunks, ie. tuples of form
        (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
+
        """
 
-       
        chunks = []
-       
        while str:
                m = get_re ('verbatim').search( str)
-               m2 = get_re ("verb").search( str)
+               if m == None:
+                       chunks.append( ('input', str))
+                       str = ''
+               else:
+                       chunks.append (('input', str[:m.start (0)]))
+                       chunks.append (('verbatim', m.group (0)))
+               
+                       str = str [m.end(0):]
+               
+       return chunks
 
-               if  m == None and m2 == None:
+def find_verb_chunks (str):
+
+       chunks = []
+       while str:
+               m = get_re ("verb").search(str)
+               if  m == None:
                        chunks.append (('input', str))
                        str = ''
-                       break
-
-               if m == None:
-                       m = m2
+               else:
+                       chunks.append (('input', str[:m.start (0)]))
+                       chunks.append (('verbatim', m.group (0)))
+                       str = str [m.end(0):]
 
-               if m2 and m2.start (0) < m.start (0):
-                       m = m2
+       return chunks
                        
-               chunks.append (('input', str[:m.start (0)]))
-               chunks.append (('verbatim', m.group (0)))
-               
-               str = str [m.end(0):]
-       return chunks         
 
 
 def find_mudela_shorthand_chunks (str):
@@ -382,43 +421,36 @@ def find_mudela_shorthands (b):
 
        def mudela_file (match):
                "Find \mudelafile, and substitute appropriate \begin / \end blocks."
-               d = [] #, d = retdeps
-               full_path = find_file (match.group (2))
-               if not full_path:
-                       error("error: can't find file `%s'\n" % match.group(2))
-
-               d.append (full_path)
-               f = open (full_path)
-               str = f.read (-1)
+               fn = match.group (2)
+               str = find_file (fn)
                opts = match.group (1)
                if opts:
-                       opts = re.split (',[ \n\t]*', opts[1:-1])
+                       opts = opts[1:-1]
+                       opts = re.split (',[ \n\t]*', opts)
                else:
                        opts = []
 
-               if re.search ('.fly$', full_path):
+               if re.search ('.fly$', fn):
                        opts.append ('fly')
-               elif re.search ('.sly$', full_path):
+               elif re.search ('.sly$', fn):
                        opts = opts + [ 'fly','fragment']
-               elif re.search ('.ly$', full_path):
+               elif re.search ('.ly$', fn):
                        opts .append ('nofly')
                        
                str_opts = string.join (opts, ',')
-               if str_opts: str_opts = '[' + str_opts + ']'
-
 
-               str = "%% copied from %s" % full_path + str 
+               str = ("%% copied from file `%s'\n" % fn) + str 
                return get_output ('output-mudela') % (str_opts, str)
-
+  
        b = get_re('mudela-file').sub (mudela_file, b)
        b = get_re('mudela').sub (mudela_short, b)
        return b
        
 def find_mudela_chunks (str):
        """Find mudela blocks, while watching for verbatim. Returns
-       (STR,MUDS) with \mudelagraphic substituted for the blocks in STR,
+       (STR,MUDS) with  substituted for the blocks in STR,
        and the blocks themselves MUDS"""
-
+  
        chunks = []
        while str:
                m = get_re ("mudela-block").search( str)
@@ -430,28 +462,28 @@ def find_mudela_chunks (str):
                chunks.append (('input', str[:m.start (0)]))
                
                opts = m.group (1)
-               if opts:
-                       opts = opts[1:-1]
-               else:
-                       opts = ''
+               if opts:
+                       opts = opts[1:-1]
+               else:
+                       opts = ''
                optlist = get_re('comma-sep').split (opts)
                
                body = m.group (2)
                chunks.append (('mudela', body, optlist))
-
+  
                str = str [m.end (0):]
-
+  
        return chunks
-
-
-
+  
+  
+  
 def advance_counters (counter, opts, str):
        """Advance chap/sect counters,
        revise OPTS. Return the new counter tuple"""
        
        (chapter, section, count) = counter
-       done = ''
-       while str:
+       done = ''
+       while str:
                m = get_re ('interesting-cs').search(str)
                if not m:
                        done = done + str
@@ -466,12 +498,12 @@ def advance_counters (counter, opts, str):
                        opts.append ('twocolumn')
                elif g  == 'onecolumn':
                        try:
-                               current_opts.remove ('twocolumn')
+                               opts.remove ('twocolumn')
                        except IndexError:
                                pass
                elif g == 'chapter':
                        (chapter, section, count)  = (chapter + 1, 0, 0)
-               elif g == 'section' or g == 'node':
+               elif g == 'section':
                        (section, count)  = (section + 1, 0)
                        
 
@@ -479,13 +511,12 @@ def advance_counters (counter, opts, str):
 
 
 def schedule_mudela_block (base, chunk, extra_opts):
-
        """Take the body and options from CHUNK, figure out how the
        real .ly should look, and what should be left MAIN_STR (meant
        for the main file).  The .ly is written, and scheduled in
        TODO.
 
-       Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO)
+       Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
 
        TODO has format [basename, extension, extension, ... ]
        
@@ -497,17 +528,16 @@ def schedule_mudela_block (base, chunk, extra_opts):
        
        newbody = ''
        if 'verbatim' in opts:
-               verbatim_mangle = body
-               if __main__.format == 'texi':
-                       verbatim_mangle = re.sub ('([{}])', '@\\1', body)
-               newbody = get_output ('output-verbatim') % verbatim_mangle
+               newbody = output_verbatim (body)
 
        file_body = compose_full_body (body, opts)
-       updated = update_file (file_body, base + '.ly')
+       basename = base
+       if __main__.use_hash:
+               basename = `abs(hash (file_body))`
+       updated = update_file (file_body, basename + '.ly')
+       todo = [basename]                       # UGH.
 
-       todo = [base]                   # UGH.
-       
-       if not os.path.isfile (base + '.tex') or updated:
+       if not os.path.isfile (basename + '.tex') or updated:
                todo.append ('tex')
                updated = 1
 
@@ -523,28 +553,28 @@ def schedule_mudela_block (base, chunk, extra_opts):
                opts.append ('eps')
 
        if 'eps' in opts and ('tex' in todo or
-                             not os.path.isfile (base + '.eps')):
+                             not os.path.isfile (basename + '.eps')):
                todo.append ('eps')
 
        if 'png' in opts and ('eps' in todo or
-                             not os.path.isfile (base + '.png')):
+                             not os.path.isfile (basename + '.png')):
                todo.append ('png')
 
        if format == 'latex':
                if 'eps' in opts :
-                       newbody = newbody + get_output ('output-eps') %  (base, base)
+                       newbody = newbody + get_output ('output-eps') %  (basename, basename)
                else:
-                       newbody = newbody + get_output ('output-tex') % base
+                       newbody = newbody + get_output ('output-tex') % basename
 
        elif format == 'texi':
-               newbody = newbody + get_output ('output-all') % (base, base) 
+               newbody = newbody + get_output ('output-all') % (basename, basename) 
 
+       return ('mudela', newbody, opts, todo, base)
 
-       
-       return ('mudela', newbody, opts, todo)
 
 def find_eps_dims (match):
        "Fill in dimensions of EPS files."
+       
        fn =match.group (1)
        dims = bounding_box_dimensions (fn)
 
@@ -562,6 +592,7 @@ def print_chunks (ch):
 def transform_input_file (in_filename, out_filename):
        """Read the input, and deliver a list of chunks
        ready for writing.
+
        """
 
        chunks = read_doc_file (in_filename)
@@ -597,35 +628,36 @@ def transform_input_file (in_filename, out_filename):
        chunks = newchunks
        newchunks = []
 
-
        # Do It.
-       if not __main__.no_lily:
+       if __main__.run_lilypond:
                compile_all_files (chunks)
                
                # finishing touch.
                for c in chunks:
-                       if c[0] == 'mudela' and 'eps' in c[2]: 
+                       if c[0] == 'mudela' and 'eps' in c[2]:
                                body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
                                newchunks.append (('mudela', body))
                        else:
                                newchunks.append (c)
-
-               if chunks and chunks[0][0] == 'input':
-                       chunks[0] = ('input', completize_preamble (chunks[0][1]))
+               chunks = newchunks
                
+       if chunks and chunks[0][0] == 'input':
+               chunks[0] = ('input', completize_preamble (chunks[0][1]))
+
        return chunks
 
 def system (cmd):
        sys.stderr.write ("invoking `%s'\n" % cmd)
        st = os.system (cmd)
        if st:
-               sys.stderr.write ('Error command exited with value %d\n' % st)
+               error ('Error command exited with value %d\n' % st)
        return st
 
 def compile_all_files (chunks):
        eps = []
        tex = []
        png = []
+       hash_dict = {}
 
        for c in chunks:
                if c[0] <> 'mudela':
@@ -640,6 +672,9 @@ def compile_all_files (chunks):
                        elif e == 'png':
                                png.append (base)
 
+               if __main__.use_hash:
+                       hash_dict[c[4]] = c[3][0]
+
        if tex:
                lilyopts = map (lambda x:  '-I ' + x, include_path)
                lilyopts = string.join (lilyopts, ' ' )
@@ -647,7 +682,7 @@ def compile_all_files (chunks):
                system ('lilypond %s %s' % (lilyopts, texfiles))
 
        for e in eps:
-               cmd = r"""tex %s; dvips -E -o %s %s""" % \
+               cmd = r"""tex '\nonstopmode \input %s'; dvips -E -o %s %s""" % \
                      (e, e + '.eps', e)
                system (cmd)
 
@@ -657,6 +692,22 @@ def compile_all_files (chunks):
                cmd = cmd % (g + '.eps', g + '.png')
                system (cmd)
 
+       if __main__.use_hash:
+               name = ''
+               last_name = ''
+               f = 0
+               ks = hash_dict.keys ()
+               ks.sort ()
+               for i in ks:
+                       name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
+                       name = name + '.mix'
+                       if name != last_name:
+                               if last_name:
+                                       f.close ()
+                               f = open (name, 'w')
+                               last_name = name
+                       f.write ("%s:%s\n" % (i, hash_dict[i]))
+
        
 def update_file (body, name):
        same = 0
@@ -750,16 +801,18 @@ Han-Wen Nienhuys <hanwen@cs.uu.nl>
        sys.exit (0)
 
 
-def write_deps (fn, target,  deps):
+def write_deps (fn, target):
        sys.stdout.write('writing `%s\'\n' % fn)
 
        f = open (fn, 'w')
        
-       target = target + '.latex'
-       f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
+       f.write ('%s%s: ' % (dep_prefix, target))
+       for d in __main__.read_files:
+               f.write ('%s ' %  d)
+       f.write ('\n')
        f.close ()
+       __main__.read_files = []
 
-               
 def identify():
        sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
 
@@ -771,72 +824,67 @@ NO WARRANTY.
 """)
 
 
-def main():
-       global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
-       outname = ''
-       try:
-               (sh, long) = getopt_args (__main__.options)
-               (options, files) = getopt.getopt(sys.argv[1:], sh, long)
-       except getopt.error, msg:
-               sys.stderr.write("error: %s" % msg)
-               sys.exit(1)
-
-       do_deps = 0
-       for opt in options:     
-               o = opt[0]
-               a = opt[1]
-               
-               if o == '--include' or o == '-I':
-                       include_path.append (a)
-               elif o == '--version':
-                       print_version ()
-                       sys.exit  (0)
-
-               elif o == '--format' or o == '-o':
-                       __main__.format = a
-               elif o == '--outname' or o == '-o':
-                       if len(files) > 1:
-                               #HACK
-                               sys.stderr.write("Mudela-book is confused by --outname on multiple files")
-                               sys.exit(1)
-                       outname = a
-               elif o == '--outdir' or o == '-d':
-                       outdir = a
-               elif o == '--help' or o == '-h':
-                       help ()
-               elif o == '--no-lily' or o == '-n':
-                       __main__.no_lily = 1
-               elif o == '--dependencies':
-                       do_deps = 1
-               elif o == '--default-mudela-fontsize':
-                       default_music_fontsize = string.atoi (a)
-               elif o == '--init':
-                       initfile =  a
-       
-       identify()
-
-       for input_filename in files:
-               file_settings = {}
-               if outname:
-                       my_outname = outname
-               else:
-                       my_outname = os.path.basename(os.path.splitext(input_filename)[0])
-               my_depname = my_outname + '.dep'                
+outname = ''
+try:
+       (sh, long) = getopt_args (__main__.options)
+       (options, files) = getopt.getopt(sys.argv[1:], sh, long)
+except getopt.error, msg:
+       sys.stderr.write("error: %s" % msg)
+       sys.exit(1)
+
+do_deps = 0
+for opt in options:    
+       o = opt[0]
+       a = opt[1]
+
+       if o == '--include' or o == '-I':
+               include_path.append (a)
+       elif o == '--version':
+               print_version ()
+               sys.exit  (0)
+
+       elif o == '--format' or o == '-o':
+               __main__.format = a
+       elif o == '--outname' or o == '-o':
+               if len(files) > 1:
+                       #HACK
+                       sys.stderr.write("Mudela-book is confused by --outname on multiple files")
+                       sys.exit(1)
+               outname = a
+       elif o == '--help' or o == '-h':
+               help ()
+       elif o == '--no-lily' or o == '-n':
+               __main__.run_lilypond = 0
+       elif o == '--dependencies':
+               do_deps = 1
+       elif o == '--default-mudela-fontsize':
+               default_music_fontsize = string.atoi (a)
+       elif o == '--init':
+               initfile =  a
+       elif o == '--dep-prefix':
+               dep_prefix = a
+
+identify()
+
+for input_filename in files:
+       file_settings = {}
+       if outname:
+               my_outname = outname
+       else:
+               my_outname = os.path.basename(os.path.splitext(input_filename)[0])
+       my_depname = my_outname + '.dep'                
 
-               chunks = transform_input_file (input_filename, my_outname)
-               
-               foutn = my_outname + '.' + format
-               sys.stderr.write ("Writing `%s'\n" % foutn)
-               fout = open (foutn, 'w')
-               for c in chunks:
-                       fout.write (c[1])
-               fout.close ()
+       chunks = transform_input_file (input_filename, my_outname)
 
-               if do_deps:
-                       # write_deps (my_depname, my_outname, deps)
-                       sys.stderr.write ("--dependencies broken")
+       foutn = my_outname + '.' + format
+       sys.stderr.write ("Writing `%s'\n" % foutn)
+       fout = open (foutn, 'w')
+       for c in chunks:
+               fout.write (c[1])
+       fout.close ()
 
-main()
+       if do_deps:
+               write_deps (my_depname, foutn)