]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/lilypond-book.py
* VERSION (MY_PATCH_LEVEL): make 1.7.0
[lilypond.git] / scripts / lilypond-book.py
index 3deb00b92b31c04d5e1079e9bc0fd1ea36019ac8..47db68f0b36f31bdb9e4caeb2ab69fc5bd877ce0 100644 (file)
 import os
 import stat
 import string
-
-
 import getopt
 import sys
 import __main__
-import operator
 
 # Handle bug in Python 1.6-2.1
 #
-# there are recursion limits for some patterns in Python 1.6 til 2.1. 
-# fix this by importing pre instead. Fix by Mats.
+# there are recursion limits for some patterns in Python 1.6 til 2.1.
+# fix this by importing the 1.5.2 implementation pre instead. Fix by Mats.
 
-# todo: should check Python version first.
-try:
-       import pre
-       re = pre
-       del pre
-except ImportError:
+
+## We would like to do this for python 2.2 as well, unfortunately
+## python 2.2 has another bug, see Sf.net bugtracker
+##
+## https://sourceforge.net/tracker/?func=detail&aid=604803&group_id=5470&atid=105470
+## 
+
+if float (sys.version[0:3]) <= 2.1:
+       try:
+               import pre
+               re = pre
+               del pre
+       except ImportError:
+               import re
+else:
        import re
 
 # Attempt to fix problems with limited stack size set by Python!
@@ -72,6 +78,8 @@ except:
 errorport = sys.stderr
 verbose_p = 0
 
+
+
 try:
        import gettext
        gettext.bindtextdomain ('lilypond', localedir)
@@ -87,29 +95,33 @@ def progress (s):
 
 program_version = '@TOPLEVEL_VERSION@'
 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
-       program_version = '1.5.53'
+       program_version = '1.6.0'
 
 # if set, LILYPONDPREFIX must take prevalence
 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
-datadir = '@datadir@'
+datadir = '@local_lilypond_datadir@'
 
 if os.environ.has_key ('LILYPONDPREFIX') :
        datadir = os.environ['LILYPONDPREFIX']
 else:
-       datadir = '@datadir@'
+       datadir = '@local_lilypond_datadir@'
 
 while datadir[-1] == os.sep:
        datadir= datadir[:-1]
 
-# Try to cater for bad installations of LilyPond, that have
-# broken TeX setup.  Just hope this doesn't hurt good TeX
-# setups.  Maybe we should check if kpsewhich can find
-# feta16.{afm,mf,tex,tfm}, and only set env upon failure.
+kpse = os.popen ('kpsexpand \$TEXMF').read()
+kpse = re.sub('[ \t\n]+$','', kpse)
+type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
+
+binary = 'lilypond'
+#binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp  --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
 environment = {
-       'MFINPUTS' : datadir + '/mf:',
-       'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
-       'TFMFONTS' : datadir + '/tfm:',
-       'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
+       # TODO: * prevent multiple addition.
+       #       * clean TEXINPUTS, MFINPUTS, TFMFONTS,
+       #         as these take prevalence over $TEXMF
+       #         and thus may break tex run?
+       'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
+       'GS_FONTPATH' : type1_paths,
        'GS_LIB' : datadir + '/ps',
 }
 
@@ -121,6 +133,10 @@ non_path_environment = {
 }
 
 def setup_environment ():
+       # $TEXMF is special, previous value is already taken care of
+       if os.environ.has_key ('TEXMF'):
+               del os.environ['TEXMF']
        for key in environment.keys ():
                val = environment[key]
                if os.environ.has_key (key):
@@ -139,10 +155,10 @@ g_extra_opts = ''
 g_here_dir = os.getcwd ()
 g_dep_prefix = ''
 g_outdir = ''
-g_force_lilypond_fontsize = 0
+g_force_music_fontsize = 0
 g_read_lys = 0
 g_do_pictures = 1
-g_num_cols = 1
+g_do_music = 1
 
 format = ''
 g_run_lilypond = 1
@@ -152,201 +168,89 @@ default_music_fontsize = 16
 default_text_fontsize = 12
 paperguru = None
 
-# this code is ugly. It should be cleaned
 class LatexPaper:
        def __init__(self):
-               self.m_paperdef =  {
-                       # the dimensions are from geometry.sty
-                       'a0paper': (mm2pt(841), mm2pt(1189)),
-                       'a1paper': (mm2pt(595), mm2pt(841)),
-                       'a2paper': (mm2pt(420), mm2pt(595)),
-                       'a3paper': (mm2pt(297), mm2pt(420)),
-                       'a4paper': (mm2pt(210), mm2pt(297)),
-                       'a5paper': (mm2pt(149), mm2pt(210)),
-                       'b0paper': (mm2pt(1000), mm2pt(1414)),
-                       'b1paper': (mm2pt(707), mm2pt(1000)),
-                       'b2paper': (mm2pt(500), mm2pt(707)),
-                       'b3paper': (mm2pt(353), mm2pt(500)),
-                       'b4paper': (mm2pt(250), mm2pt(353)),
-                       'b5paper': (mm2pt(176), mm2pt(250)),
-                       'letterpaper': (in2pt(8.5), in2pt(11)),
-                       'legalpaper': (in2pt(8.5), in2pt(14)),
-                       'executivepaper': (in2pt(7.25), in2pt(10.5))}
-               self.m_use_geometry = None
-               self.m_papersize = 'letterpaper'
-               self.m_fontsize = 10
+               self.m_document_preamble = []
                self.m_num_cols = 1
-               self.m_landscape = 0
-               self.m_geo_landscape = 0
-               self.m_geo_width = None
-               self.m_geo_textwidth = None
-               self.m_geo_lmargin = None
-               self.m_geo_rmargin = None
-               self.m_geo_includemp = None
-               self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
-               self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
-               self.m_geo_x_marginparwidth = None
-               self.m_geo_x_marginparsep = None
-               self.__body = None
-       def set_geo_option(self, name, value):
-
-               if type(value) == type([]):
-                       value = map(conv_dimen_to_float, value)
+               self.m_multicols = 1
+               
+       def find_latex_dims(self):
+               if g_outdir:
+                       fname = os.path.join(g_outdir, "lily-tmp.tex")
                else:
-                       value = conv_dimen_to_float(value)
+                       fname = "lily-tmp.tex"
+               try:
+                       f = open(fname, "w")
+               except IOError:
+                       error ("Error creating temporary file '%s'" % fname)
+
+               for s in self.m_document_preamble:
+                       f.write(s)
+               f.write(r"""
+\begin{document}
+\typeout{---}
+\typeout{\columnsep \the\columnsep}
+\typeout{\textwidth \the\textwidth}
+\typeout{---}
+\end{document}
+               """)
+               f.close()
+               re_dim = re.compile(r"\\(\w+)\s+(\d+\.\d+)")
+
+               cmd = "latex '\\nonstopmode \input %s'" % fname
+               if verbose_p:
+                       sys.stderr.write ("Invoking `%s' as pipe" % cmd)
+               try:
+                       status = quiet_system (cmd, "Latex for finding dimensions")
+               except:
+                       sys.stderr.write (_("Invoking LaTeX failed.") + '\n' )
+                       sys.stderr.write (_("This is the error log:\n") + '\n')
 
-               if name == 'body' or name == 'text':
-                       if type(value) == type([]):
-                               self.m_geo_textwidth =  value[0]
-                       else:
-                               self.m_geo_textwidth =  value
-                       self.__body = 1
-               elif name == 'portrait':
-                       self.m_geo_landscape = 0
-               elif name == 'reversemp' or name == 'reversemarginpar':
-                       if self.m_geo_includemp == None:
-                               self.m_geo_includemp = 1
-               elif name == 'marginparwidth' or name == 'marginpar':
-                       self.m_geo_x_marginparwidth =  value
-                       self.m_geo_includemp = 1
-               elif name == 'marginparsep':
-                       self.m_geo_x_marginparsep =  value
-                       self.m_geo_includemp = 1
-               elif name == 'scale':
-                       if type(value) == type([]):
-                               self.m_geo_width = self.get_paperwidth() * value[0]
-                       else:
-                               self.m_geo_width = self.get_paperwidth() * value
-               elif name == 'hscale':
-                       self.m_geo_width = self.get_paperwidth() * value
-               elif name == 'left' or name == 'lmargin':
-                       self.m_geo_lmargin =  value
-               elif name == 'right' or name == 'rmargin':
-                       self.m_geo_rmargin =  value
-               elif name == 'hdivide' or name == 'divide':
-                       if value[0] not in ('*', ''):
-                               self.m_geo_lmargin =  value[0]
-                       if value[1] not in ('*', ''):
-                               self.m_geo_width =  value[1]
-                       if value[2] not in ('*', ''):
-                               self.m_geo_rmargin =  value[2]
-               elif name == 'hmargin':
-                       if type(value) == type([]):
-                               self.m_geo_lmargin =  value[0]
-                               self.m_geo_rmargin =  value[1]
-                       else:
-                               self.m_geo_lmargin =  value
-                               self.m_geo_rmargin =  value
-               elif name == 'margin':#ugh there is a bug about this option in
-                                       # the geometry documentation
-                       if type(value) == type([]):
-                               self.m_geo_lmargin =  value[0]
-                               self.m_geo_rmargin =  value[0]
-                       else:
-                               self.m_geo_lmargin =  value
-                               self.m_geo_rmargin =  value
-               elif name == 'total':
-                       if type(value) == type([]):
-                               self.m_geo_width =  value[0]
-                       else:
-                               self.m_geo_width =  value
-               elif name == 'width' or name == 'totalwidth':
-                       self.m_geo_width =  value
-               elif name == 'paper' or name == 'papername':
-                       self.m_papersize = value
-               elif name[-5:] == 'paper':
-                       self.m_papersize = name
-               else:
-                               pass 
+                       lns = open ('lily-tmp.log').readlines()
 
-       def __setattr__(self, name, value):
-               if type(value) == type("") and \
-                  dimension_conversion_dict.has_key (value[-2:]):
-                       f = dimension_conversion_dict[value[-2:]]
-                       self.__dict__[name] = f(float(value[:-2]))
-               else:
-                       self.__dict__[name] = value
+                       countdown = -3
+                       for ln in lns:
+                               sys.stderr.write (ln)
+                               if re.match('^!', ln):
+                                       countdown = 3
+
+                               if countdown == 0:
+                                       break
+                               
+                               if countdown > 0:
+                                       countdown = countdown -1
+
+                       sys.stderr.write ("  ... (further messages elided)...\n")
+                       sys.exit (1)
                        
-       def __str__(self):
-               s =  "LatexPaper:\n-----------"
-               for v in self.__dict__.keys():
-                       if v[:2] == 'm_':
-                               s = s +  str (v) + ' ' + str (self.__dict__[v])
-               s = s +  "-----------"
-               return s
-       
+               lns = open ('lily-tmp.log').readlines()
+               for ln in lns:
+                       ln = string.strip(ln)
+                       m = re_dim.match(ln)
+                       if m:
+                               if m.groups()[0] in ('textwidth', 'columnsep'):
+                                       self.__dict__['m_%s' % m.groups()[0]] = float(m.groups()[1])
+                                       
+               try:
+                       os.remove (fname)
+                       os.remove (os.path.splitext(fname)[0]+".aux")
+                       os.remove (os.path.splitext(fname)[0]+".log")
+               except:
+                       pass
+
+               if not self.__dict__.has_key ('m_textwidth'):
+                       raise 'foo!'
+               
        def get_linewidth(self):
-               w = self._calc_linewidth()
-               if self.m_num_cols == 2:
-                       return (w - 10) / 2
+               if self.m_num_cols == 1:
+                       w = self.m_textwidth
                else:
-                       return w
-       def get_paperwidth(self):
-               #if self.m_use_geometry:
-                       return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
-               #return self.m_paperdef[self.m_papersize][self.m_landscape]
-       
-       def _calc_linewidth(self):
-               # since geometry sometimes ignores 'includemp', this is
-               # more complicated than it should be
-               mp = 0
-               if self.m_geo_includemp:
-                       if self.m_geo_x_marginparsep is not None:
-                               mp = mp + self.m_geo_x_marginparsep
-                       else:
-                               mp = mp + self.m_geo_marginparsep[self.m_fontsize]
-                       if self.m_geo_x_marginparwidth is not None:
-                               mp = mp + self.m_geo_x_marginparwidth
-                       else:
-                               mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
+                       w = (self.m_textwidth - self.m_columnsep)/2
+               if self.m_multicols > 1:
+                       return (w - self.m_columnsep*(self.m_multicols-1)) \
+                          / self.m_multicols
+               return w
 
-               #ugh test if this is necessary                          
-               if self.__body:
-                       mp = 0
-
-               if not self.m_use_geometry:
-                       return latex_linewidths[self.m_papersize][self.m_fontsize]
-               else:
-                       geo_opts = (self.m_geo_lmargin == None,
-                                   self.m_geo_width == None,
-                                   self.m_geo_rmargin == None)
-
-                       if geo_opts == (1, 1, 1):
-                               if self.m_geo_textwidth:
-                                       return self.m_geo_textwidth
-                               w = self.get_paperwidth() * 0.8
-                               return w - mp
-                       elif geo_opts == (0, 1, 1):
-                                if self.m_geo_textwidth:
-                                       return self.m_geo_textwidth
-                                return self.f1(self.m_geo_lmargin, mp)
-                       elif geo_opts == (1, 1, 0):
-                                if self.m_geo_textwidth:
-                                       return self.m_geo_textwidth
-                                return self.f1(self.m_geo_rmargin, mp)
-                       elif geo_opts \
-                                       in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
-                               if self.m_geo_textwidth:
-                                       return self.m_geo_textwidth
-                               return self.m_geo_width - mp
-                       elif geo_opts in ((0, 1, 0), (0, 0, 0)):
-                               w = self.get_paperwidth() \
-                                 - self.m_geo_lmargin - self.m_geo_rmargin - mp
-                               if w < 0:
-                                       w = 0
-                               return w
-                       raise "Never do this!"
-       def f1(self, m, mp):
-               tmp = self.get_paperwidth() - m * 2 - mp
-               if tmp < 0:
-                       tmp = 0
-               return tmp
-       def f2(self):
-               tmp = self.get_paperwidth() - self.m_geo_lmargin \
-                       - self.m_geo_rmargin
-               if tmp < 0:
-                       return 0
-               return tmp
 
 class HtmlPaper:
        def __init__(self):
@@ -399,20 +303,6 @@ def conv_dimen_to_float(value):
                        value = float(value)
 
        return value
-                       
-       
-# latex linewidths:
-# indices are no. of columns, papersize,  fontsize
-# Why can't this be calculated?
-latex_linewidths = {
-       'a4paper':{10: 345, 11: 360, 12: 390},
-       'a4paper-landscape': {10: 598, 11: 596, 12:592},
-       'a5paper':{10: 276, 11: 276, 12: 276},
-       'b5paper':{10: 345, 11: 356, 12: 356},
-       'letterpaper':{10: 345, 11: 360, 12: 390},
-       'letterpaper-landscape':{10: 598, 11: 596, 12:596},
-       'legalpaper': {10: 345, 11: 360, 12: 390},
-       'executivepaper':{10: 345, 11: 360, 12: 379}}
 
 texi_linewidths = {
        'afourpaper': {12: mm2pt(160)},
@@ -441,6 +331,7 @@ option_definitions = [
        ('PREF', '',  'dep-prefix', 'prepend PREF before each -M dependency'),
        ('', 'n', 'no-lily', 'don\'t run lilypond'),
        ('', '', 'no-pictures', "don\'t generate pictures"),
+       ('', '', 'no-music', "strip all lilypond blocks from output"),  
        ('', '', 'read-lys', "don't write ly files."),
        ('FILE', 'o', 'outname', 'filename main output file'),
        ('FILE', '', 'outdir', "where to place generated files"),
@@ -454,8 +345,9 @@ output_dict= {
 %s
 </lilypond>''',
                'output-filename' : r'''
-
-<pre>%s</pre>:''',       
+<!-- %s >
+<a href="%s">
+<pre>%s</pre></a>:''',   
                  'output-lilypond-fragment': '''<lilypond%s>
 \context Staff\context Voice{ %s }
 </lilypond>''',
@@ -467,6 +359,10 @@ output_dict= {
                  'output-verbatim': r'''<pre>
 %s
 </pre>''',
+                 'output-small-verbatim': r'''<font size=-1><pre>
+%s
+</pre></font>''',
+
                  ## Ugh we need to differentiate on origin:
                  ## lilypond-block origin wants an extra <p>, but
                  ## inline music doesn't.
@@ -485,18 +381,21 @@ output_dict= {
   >
 \end{lilypond}''',
                'output-filename' : r'''
-
-\verb+%s+:''',
+\verb+%s+:
+%% %s
+%% %s
+''',
                'output-lilypond': r'''\begin[%s]{lilypond}
 %s
 \end{lilypond}
 ''',
                'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
 ''',
+               'output-small-verbatim': r'''{\small\begin{verbatim}%s\end{verbatim}}%%''',
                'output-default-post': "\\def\postLilypondExample{}\n",
                'output-default-pre': "\\def\preLilypondExample{}\n",
                'usepackage-graphics': '\\usepackage{graphics}\n',
-               'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
+               'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
                'output-noinline': r'''
 %% generated: %(fn)s.eps
 ''',
@@ -509,8 +408,13 @@ output_dict= {
 @end lilypond 
 ''',
                'output-filename' : r'''
-
-@file{%s}:''',   
+@ifnothtml
+@file{%s}:
+@end ifnothtml
+@ifhtml
+@uref{%s,@file{%s}}
+@end ifhtml
+''',     
                  'output-lilypond-fragment': '''@lilypond[%s]
 \context Staff\context Voice{ %s }
 @end lilypond ''',
@@ -518,6 +422,10 @@ output_dict= {
 @c generated: %(fn)s.png                 
 ''',
                  'pagebreak': None,
+                 'output-small-verbatim': r'''@smallexample
+%s
+@end smallexample
+''',
                  'output-verbatim': r'''@example
 %s
 @end example
@@ -544,28 +452,39 @@ output_dict= {
 <p>
 <a href="%(fn)s.png">
 <img border=0 src="%(fn)s.png" alt="[picture of music]">
-</a>
+</a><p>
 @end html
 ''',
                }
        
        }
 
-def output_verbatim (body):
+def output_verbatim (body, small):
        if __main__.format == 'html':
                body = re.sub ('&', '&amp;', body)
                body = re.sub ('>', '&gt;', body)
                body = re.sub ('<', '&lt;', body)
        elif __main__.format == 'texi':
-               body = re.sub ('([@{}])', '@\\1', body)
-       return get_output ('output-verbatim') % body
+               
+               # clumsy workaround for python 2.2 pre bug.
+               body = re.sub ('@', '@@', body)
+               body = re.sub ('{', '@{', body)
+               body = re.sub ('}', '@}', body)
+
+       if small:
+               key = 'output-small-verbatim'
+       else:
+               key = 'output-verbatim'
+       return get_output (key) % body
 
 
 #warning: this uses extended regular expressions. Tread with care.
 
-# legenda (?P  name parameter
-# *? match non-greedily.
+# legenda
 
+# (?P  -- name parameter
+# *? -- match non-greedily.
+# (?m)  -- ?  
 re_dict = {
        'html': {
                 'include':  no_match,
@@ -575,34 +494,35 @@ re_dict = {
                 'landscape': no_match,
                 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
                 'verb': r'''(?P<code><pre>.*?</pre>)''',
-                'lilypond-file': '(?m)(?P<match><lilypondfile(?P<options>[^>]*)?>\s*(?P<filename>.*?)\s*</lilypondfile>)',
+                'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
                 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
-                'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]*)?>(?P<code>.*?)</lilypond>)''',
+                'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
                  'option-sep' : '\s*',
                  'intertext': r',?\s*intertext=\".*?\"',
                  'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
                  'singleline-comment': no_match,
                  'numcols': no_match,
+                 'multicols': no_match,
                 },
        
        'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
                  'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
                  'option-sep' : ',\s*',
-                 'header': r"\\documentclass\s*(\[.*?\])?",
-                 'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
-                 'preamble-end': r'(?P<code>\\begin{document})',
-                 'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
+                 'header': r"\n*\\documentclass\s*(\[.*?\])?",
+                 'preamble-end': r'(?P<code>\\begin\s*{document})',
+                 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
                  'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
                  'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
                  'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
                  'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
                  'def-post-re': r"\\def\\postLilypondExample",
-                 'def-pre-re': r"\\def\\preLilypondExample",             
-                 'usepackage-graphics': r"\usepackage{graphics}",
+                 'def-pre-re': r"\\def\\preLilypondExample",
+                 'usepackage-graphics': r"\usepackage\s*{graphics}",
                  'intertext': r',?\s*intertext=\".*?\"',
                  'multiline-comment': no_match,
                  'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
                  'numcols': r"(?P<code>\\(?P<num>one|two)column)",
+                 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
                  },
 
 
@@ -619,12 +539,13 @@ re_dict = {
                 'verb': r'''(?P<code>@code{.*?})''',
                 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
                 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
-                'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
+                'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
                 'option-sep' : ',\s*',
                 'intertext': r',?\s*intertext=\".*?\"',
                 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
                 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
                 'numcols': no_match,
+                'multicols': no_match,
                 }
        }
 
@@ -678,7 +599,7 @@ def bounding_box_dimensions(fname):
                return (0,0)
 
 def error (str):
-       sys.stderr.write (str + "\n  Exiting ... \n\n")
+       sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
        raise 'Exiting.'
 
 
@@ -686,21 +607,16 @@ def compose_full_body (body, opts):
        '''Construct the lilypond code to send to Lilypond.
        Add stuff to BODY using OPTS as options.'''
        music_size = default_music_fontsize
-       latex_size = default_text_fontsize
+       if g_force_music_fontsize:
+               music_size = g_force_music_fontsize
        indent = ''
        linewidth = ''
        for o in opts:
-               if g_force_lilypond_fontsize:
-                       music_size = g_force_lilypond_fontsize
-               else:
+               if not g_force_music_fontsize:
                        m = re.match ('([0-9]+)pt', o)
                        if m:
                                music_size = string.atoi(m.group (1))
 
-               m = re.match ('latexfontsize=([0-9]+)pt', o)
-               if m:
-                       latex_size = string.atoi (m.group (1))
-                       
                m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
                if m:
                        f = float (m.group (1))
@@ -772,74 +688,49 @@ def compose_full_body (body, opts):
        # ughUGH not original options
        return body
 
-def parse_options_string(s):
-       d = {}
-       r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
-       r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
-       r3 = re.compile("(\w+?)((,\s*)|$)")
-       while s:
-               m = r1.match(s)
-               if m:
-                       s = s[m.end():]
-                       d[m.group(2)] = re.split(",\s*", m.group(3))
-                       continue
-               m = r2.match(s)
-               if m:
-                       s = s[m.end():]
-                       d[m.group(2)] = m.group(3)
-                       continue
-               m = r3.match(s)
-               if m:
-                       s = s[m.end():]
-                       d[m.group(1)] = 1
-                       continue
-               
-               error ("format of option string invalid (was `%')" % s)
-       return d
-
 def scan_html_preamble (chunks):
        return
 
 def scan_latex_preamble(chunks):
-       # first we want to scan the \documentclass line
-       # it should be the first non-comment line
+       # First we want to scan the \documentclass line
+       # it should be the first non-comment line.
+       # The only thing we really need to know about the \documentclass line
+       # is if there are one or two columns to begin with.
        idx = 0
        while 1:
                if chunks[idx][0] == 'ignore':
                        idx = idx + 1
                        continue
                m = get_re ('header').match(chunks[idx][1])
-               if m <> None and m.group (1):
+               if not m:
+                       error ("Latex documents must start with a \documentclass command")
+               if m.group (1):
                        options = re.split (',[\n \t]*', m.group(1)[1:-1])
                else:
                        options = []
-               for o in options:
-                       if o == 'landscape':
-                               paperguru.m_landscape = 1
-                       m = re.match("(.*?)paper", o)
-                       if m:
-                               paperguru.m_papersize = m.group()
-                       else:
-                               m = re.match("(\d\d)pt", o)
-                               if m:
-                                       paperguru.m_fontsize = int(m.group(1))
+               if 'twocolumn' in options:
+                       paperguru.m_num_cols = 2
                break
-       
-       while chunks[idx][0] != 'preamble-end':
+
+
+       # Then we add everythin before \begin{document} to
+       # paperguru.m_document_preamble so that we can later write this header
+       # to a temporary file in find_latex_dims() to find textwidth.
+       while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
                if chunks[idx] == 'ignore':
                        idx = idx + 1
                        continue
-               m = get_re ('geometry').search(chunks[idx][1])
-               if m:
-                       paperguru.m_use_geometry = 1
-                       o = parse_options_string(m.group('options'))
-                       for k in o.keys():
-                               paperguru.set_geo_option(k, o[k])
+               paperguru.m_document_preamble.append(chunks[idx][1])
                idx = idx + 1
 
+       if len(chunks) == idx:
+               error ("Didn't find end of preamble (\\begin{document})")
+               
+       paperguru.find_latex_dims()
+
 def scan_texi_preamble (chunks):
        # this is not bulletproof..., it checks the first 10 chunks
-       for c in chunks[:10]: 
+       for c in chunks[:10]:
                if c[0] == 'input':
                        for s in ('afourpaper', 'afourwide', 'letterpaper',
                                  'afourlatex', 'smallbook'):
@@ -871,19 +762,25 @@ def completize_preamble (chunks):
                        m = get_re('def-post-re').search(chunk[1])
                        if m:
                                post_b = 1
+                               
                if chunk[0] == 'input':
                        m = get_re('usepackage-graphics').search(chunk[1])
                        if m:
                                graphics_b = 1
        x = 0
-       while chunks[x][0] != 'preamble-end':
+       while x < len (chunks) and   chunks[x][0] != 'preamble-end':
                x = x + 1
+
+       if x == len(chunks):
+               return chunks
+       
        if not pre_b:
                chunks.insert(x, ('input', get_output ('output-default-pre')))
        if not post_b:
                chunks.insert(x, ('input', get_output ('output-default-post')))
        if not graphics_b:
                chunks.insert(x, ('input', get_output ('usepackage-graphics')))
+
        return chunks
 
 
@@ -959,6 +856,9 @@ def make_lilypond_file(m):
                        (options, content))]
 
 def make_lilypond_block(m):
+       if not g_do_music:
+               return []
+       
        if m.group('options'):
                options = get_re('option-sep').split (m.group('options'))
        else:
@@ -973,7 +873,16 @@ def do_columns(m):
                return [('numcols', m.group('code'), 1)]
        if m.group('num') == 'two':
                return [('numcols', m.group('code'), 2)]
-       
+
+def do_multicols(m):
+       if __main__.format != 'latex':
+               return []
+       if m.group('be') == 'begin':
+               return [('multicols', m.group('code'), int(m.group('num')))]
+       else:
+               return [('multicols', m.group('code'), 1)]
+       return []
+
 def chop_chunks(chunks, re_name, func, use_match=0):
        newchunks = []
        for c in chunks:
@@ -1001,7 +910,7 @@ def determine_format (str):
        if __main__.format == '':
                
                html = re.search ('(?i)<[dh]tml', str[:200])
-               latex = re.search ('''\\document''', str[:200])
+               latex = re.search (r'''\\document''', str[:200])
                texi = re.search ('@node|@setfilename', str[:200])
 
                f = ''
@@ -1040,6 +949,8 @@ def read_doc_file (filename):
        # because we don't want to include files that are mentioned
        # inside a verbatim environment
        chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
+
+
        chunks = chop_chunks(chunks, 'verb', make_verb)
        chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
        #ugh fix input
@@ -1107,12 +1018,14 @@ def schedule_lilypond_block (chunk):
                for o in opts:
                        m= re.match ("filename=(.*)", o)
                        if m:
-                               newbody = newbody + get_output ("output-filename") % m.group(1)
+                               newbody = newbody + get_output ("output-filename") % (m.group(1), basename + '.ly', m.group(1))
                                break
                
-       
-       if 'verbatim' in opts:
-               newbody = output_verbatim (body)
+
+       if 'smallverbatim' in opts:
+               newbody = newbody + output_verbatim (body, 1)
+       elif 'verbatim' in opts:
+               newbody = newbody + output_verbatim (body, 0)
 
        for o in opts:
                m = re.search ('intertext="(.*?)"', o)
@@ -1127,11 +1040,11 @@ def schedule_lilypond_block (chunk):
                else:
                        s = 'output-tex'
        else: # format == 'html' or format == 'texi':
-               s = 'output-all'
+               s = 'output-all'
        newbody = newbody + get_output (s) % {'fn': basename }
        return ('lilypond', newbody, opts, todo, basename)
 
-def process_lilypond_blocks(outname, chunks):#ugh rename
+def process_lilypond_blocks(chunks):#ugh rename
        newchunks = []
        # Count sections/chapters.
        for c in chunks:
@@ -1139,6 +1052,8 @@ def process_lilypond_blocks(outname, chunks):#ugh rename
                        c = schedule_lilypond_block (c)
                elif c[0] == 'numcols':
                        paperguru.m_num_cols = c[2]
+               elif c[0] == 'multicols':
+                       paperguru.m_multicols = c[2]
                newchunks.append (c)
        return newchunks
 
@@ -1151,9 +1066,17 @@ def system (cmd):
                error ('Error command exited with value %d\n' % st)
        return st
 
+def quiet_system (cmd, name):
+       if not verbose_p:
+               progress ( _("Running %s...") % name)
+               cmd = cmd + ' 1> /dev/null 2> /dev/null'
+
+       return system (cmd)
 
 def get_bbox (filename):
-       system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
+
+       # gs bbox device is ugh, it always prints of stderr.
+       system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1' % (filename, filename))
 
        box = open (filename + '.bbox').read()
        m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
@@ -1175,16 +1098,16 @@ def make_pixmap (name):
        x = (2* margin + bbox[2] - bbox[0]) * res / 72.
        y = (2* margin + bbox[3] - bbox[1]) * res / 72.
 
-       cmd = r'''gs -g%dx%d -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
+       cmd = r'''gs -g%dx%d -sDEVICE=pnggray  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=%s -r%d -dNOPAUSE %s %s -c quit '''
        
-       cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
-       if not verbose_p:
-               progress ( _("Running %s...") % 'gs')
-               cmd = cmd + ' 1> /dev/null 2> /dev/null'
-               
+       cmd = cmd % (x, y, name + '.png', res, name + '.trans.eps', name + '.eps')
+       status = 0
        try:
                status = system (cmd)
        except:
+               status = -1
+
+       if status:
                os.unlink (name + '.png')
                error ("Removing output file")
 
@@ -1195,7 +1118,7 @@ def compile_all_files (chunks):
        png = []
 
        for c in chunks:
-               if c[0] <> 'lilypond':
+               if c[0] != 'lilypond':
                        continue
                base  = c[4]
                exts = c[3]
@@ -1214,7 +1137,7 @@ def compile_all_files (chunks):
        if tex:
                # fixme: be sys-independent.
                def incl_opt (x):
-                       if g_outdir and x[0] <> '/' :
+                       if g_outdir and x[0] != '/' :
                                x = os.path.join (g_here_dir, x)
                        return ' -I %s' % x
 
@@ -1225,11 +1148,9 @@ def compile_all_files (chunks):
                        if g_outdir:
                                lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
                texfiles = string.join (tex, ' ')
-               cmd = 'lilypond --header=texidoc %s %s %s' \
-                     % (lilyopts, g_extra_opts, texfiles)
-               if not verbose_p:
-                       progress ( _("Running %s...") % 'LilyPond')
-                       cmd = cmd + ' 1> /dev/null 2> /dev/null'
+               cmd = '%s --header=texidoc %s %s %s' \
+                     % (binary, lilyopts, g_extra_opts, texfiles)
+
                system (cmd)
 
                #
@@ -1247,17 +1168,11 @@ def compile_all_files (chunks):
                                f.close ()
 
        for e in eps:
-               cmd = r"tex '\nonstopmode \input %s'" % e
-               if not verbose_p:
-                       progress ( _("Running %s...") % 'TeX')
-                       cmd = cmd + ' 1> /dev/null 2> /dev/null'
-               system (cmd)
+               cmd = r"echo $TEXMF; tex '\nonstopmode \input %s'" % e
+               quiet_system (cmd, 'TeX')
                
                cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
-               if not verbose_p:
-                       progress ( _("Running %s...") % 'dvips')
-                       cmd = cmd + ' 1> /dev/null 2> /dev/null'
-               system (cmd) 
+               quiet_system (cmd, 'dvips')
                
        for g in png:
                make_pixmap (g)
@@ -1417,7 +1332,7 @@ def check_texidoc (chunks):
 def fix_epswidth (chunks):
        newchunks = []
        for c in chunks:
-               if c[0] <> 'lilypond' or 'eps' not in c[2]:
+               if c[0] != 'lilypond' or 'eps' not in c[2]:
                        newchunks.append (c)
                        continue
 
@@ -1439,17 +1354,9 @@ def fix_epswidth (chunks):
        return newchunks
 
 
+##docme: why global?
 foutn=""
 def do_file(input_filename):
-       global foutn
-       file_settings = {}
-       if outname:
-               my_outname = outname
-       elif input_filename == '-' or input_filename == "/dev/stdin":
-               my_outname = '-'
-       else:
-               my_outname = os.path.basename (os.path.splitext(input_filename)[0])
-       my_depname = my_outname + '.dep'                
 
        chunks = read_doc_file(input_filename)
        chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
@@ -1458,11 +1365,12 @@ def do_file(input_filename):
        chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
        chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
        chunks = chop_chunks(chunks, 'numcols', do_columns)
+       chunks = chop_chunks(chunks, 'multicols', do_multicols)
        #print "-" * 50
        #for c in chunks: print "c:", c;
        #sys.exit()
        scan_preamble(chunks)
-       chunks = process_lilypond_blocks(my_outname, chunks)
+       chunks = process_lilypond_blocks(chunks)
 
        # Do It.
        if __main__.g_run_lilypond:
@@ -1474,14 +1382,24 @@ def do_file(input_filename):
 
        x = 0
        chunks = completize_preamble (chunks)
+
+
+       global foutn
+
+       if outname:
+               my_outname = outname
+       elif input_filename == '-' or input_filename == "/dev/stdin":
+               my_outname = '-'
+       else:
+               my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
+       my_depname = my_outname + '.dep'                
+       
        if my_outname == '-' or my_outname == '/dev/stdout':
                fout = sys.stdout
                foutn = "<stdout>"
                __main__.do_deps = 0
        else:
-               ## ugh, ugh.
-               foutn = os.path.join (g_outdir, my_outname + '.' + format)
-               ## foutn = os.path.join (g_outdir, my_outname)
+               foutn = os.path.join (g_outdir, my_outname)
                sys.stderr.write ("Writing `%s'\n" % foutn)
                fout = open (foutn, 'w')
        for c in chunks:
@@ -1492,7 +1410,6 @@ def do_file(input_filename):
        if do_deps:
                write_deps (my_depname, foutn, chunks)
 
-
 outname = ''
 try:
        (sh, long) = getopt_args (__main__.option_definitions)
@@ -1535,14 +1452,16 @@ for opt in options:
        elif o == '--extra-options':
                g_extra_opts = a
        elif o == '--force-music-fontsize':
-               g_force_lilypond_fontsize = string.atoi(a)
+               g_force_music_fontsize = string.atoi(a)
        elif o == '--force-lilypond-fontsize':
                print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
-               g_force_lilypond_fontsize = string.atoi(a)
+               g_force_music_fontsize = string.atoi(a)
        elif o == '--dep-prefix':
                g_dep_prefix = a
        elif o == '--no-pictures':
                g_do_pictures = 0
+       elif o == '--no-music':
+               g_do_music = 0
        elif o == '--read-lys':
                g_read_lys = 1
        elif o == '--outdir':