]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/mudela-book.py
partial: 1.3.107.jcn
[lilypond.git] / scripts / mudela-book.py
index 359d68b326958e1b1e544ceac7bd6e5f931b6f7f..d37806d3a91145d281b7a7b12a53a7b6a9161030 100644 (file)
 #!@PYTHON@
+# vim: set noexpandtab:
+# TODO:
+# * Figure out clean set of options. Hmm, isn't it pretty ok now?
+# * add support for .lilyrc
 
-#  TODO: center option
 
-# log:
+# todo: dimension handling (all the x2y) is clumsy. 
 
-# 0.3:
-#   rewrite in Python.
+# This is was the idea for handling of comments:
+#      Multiline comments, @ignore .. @end ignore is scanned for
+#      in read_doc_file, and the chunks are marked as 'ignore', so
+#      mudela-book will not touch them any more. The content of the
+#      chunks are written to the output file. Also 'include' and 'input'
+#      regex has to check if they are commented out.
+#
+#      Then it is scanned for 'mudela', 'mudela-file' and 'mudela-block'.
+#      These three regex's has to check if they are on a commented line,
+#      % for latex, @c for texinfo.
+#
+#      Then lines that are commented out with % (latex) and @c (Texinfo)
+#      are put into chunks marked 'ignore'. This cannot be done before
+#      searching for the mudela-blocks because % is also the comment character
+#      for lilypond.
+#
+#      The the rest of the rexeces are searched for. They don't have to test
+#      if they are on a commented out line.
 
 import os
+import stat
 import string
-import regex
+import re
 import getopt
 import sys
-import regsub
+import __main__
+import operator
 
-outdir = 'out/'
-program_version = '0.3'
 
+program_version = '@TOPLEVEL_VERSION@'
+if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
+       program_version = '1.3.106'     
 
+include_path = [os.getcwd()]
 
 
-def file_exist_b(name):
-    try: 
-       f = open(name)
-    except IOError:
-       return 0
-    f.close ()
-    return 1
+# g_ is for global (?)
 
-class CompileStatus:
-       pass
+g_here_dir = os.getcwd ()
+g_dep_prefix = ''
+g_outdir = ''
+g_force_mudela_fontsize = 0
+g_read_lys = 0
+g_do_pictures = 1
+g_num_cols = 1
+format = ''
+g_run_lilypond = 1
+no_match = 'a\ba'
 
-def file_mtime (name):
-    return os.stat (name)[8] #mod time
+default_music_fontsize = 16
+default_text_fontsize = 12
 
-def compile (command, infile, outfile):
-       indate = file_mtime (infile)
-       try:
-               outdate = file_mtime (outfile)
-               recompile = indate > outdate
 
-       except os.error:
-               recompile = 1
+class LatexPaper:
+       def __init__(self):
+               self.m_paperdef =  {
+                       # the dimentions 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_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 name == 'body' or name == 'text':
+                       if type(value) == type(""):
+                               self._set_dimen('m_geo_textwidth', value)
+                       else:
+                               self._set_dimen('m_geo_textwidth', value[0])
+                       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._set_dimen('m_geo_x_marginparwidth', value)
+                       self.m_geo_includemp = 1
+               elif name == 'marginparsep':
+                       self._set_dimen('m_geo_x_marginparsep', value)
+                       self.m_geo_includemp = 1
+               elif name == 'scale':
+                       if type(value) == type(""):
+                               self.m_geo_width = self.get_paperwidth() * float(value)
+                       else:
+                               self.m_geo_width = self.get_paperwidth() * float(value[0])
+               elif name == 'hscale':
+                       self.m_geo_width = self.get_paperwidth() * float(value)
+               elif name == 'left' or name == 'lmargin':
+                       self._set_dimen('m_geo_lmargin', value)
+               elif name == 'right' or name == 'rmargin':
+                       self._set_dimen('m_geo_rmargin', value)
+               elif name == 'hdivide' or name == 'divide':
+                       if value[0] not in ('*', ''):
+                               self._set_dimen('m_geo_lmargin', value[0])
+                       if value[1] not in ('*', ''):
+                               self._set_dimen('m_geo_width', value[1])
+                       if value[2] not in ('*', ''):
+                               self._set_dimen('m_geo_rmargin', value[2])
+               elif name == 'hmargin':
+                       if type(value) == type(""):
+                               self._set_dimen('m_geo_lmargin', value)
+                               self._set_dimen('m_geo_rmargin', value)
+                       else:
+                               self._set_dimen('m_geo_lmargin', value[0])
+                               self._set_dimen('m_geo_rmargin', value[1])
+               elif name == 'margin':#ugh there is a bug about this option in
+                                       # the geometry documentation
+                       if type(value) == type(""):
+                               self._set_dimen('m_geo_lmargin', value)
+                               self._set_dimen('m_geo_rmargin', value)
+                       else:
+                               self._set_dimen('m_geo_lmargin', value[0])
+                               self._set_dimen('m_geo_rmargin', value[0])
+               elif name == 'total':
+                       if type(value) == type(""):
+                               self._set_dimen('m_geo_width', value)
+                       else:
+                               self._set_dimen('m_geo_width', value[0])
+               elif name == 'width' or name == 'totalwidth':
+                       self._set_dimen('m_geo_width', value)
+               elif name == 'paper' or name == 'papername':
+                       self.m_papersize = value
+               elif name[-5:] == 'paper':
+                       self.m_papersize = name
+               else:
+                       self._set_dimen('m_geo_'+name, value)
+       def _set_dimen(self, name, value):
+               if type(value) == type("") and value[-2:] == 'pt':
+                       self.__dict__[name] = float(value[:-2])
+               elif type(value) == type("") and value[-2:] == 'mm':
+                       self.__dict__[name] = mm2pt(float(value[:-2]))
+               elif type(value) == type("") and value[-2:] == 'cm':
+                       self.__dict__[name] = 10 * mm2pt(float(value[:-2]))
+               elif type(value) == type("") and value[-2:] == 'in':
+                       self.__dict__[name] = in2pt(float(value[:-2]))
+               else:
+                       self.__dict__[name] = value
+       def display(self):
+               print "LatexPaper:\n-----------"
+               for v in self.__dict__.keys():
+                       if v[:2] == 'm_':
+                               print v, self.__dict__[v]
+               print "-----------"
+       def get_linewidth(self):
+               w = self._calc_linewidth()
+               if self.m_num_cols == 2:
+                       return (w - 10) / 2
+               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]
+               if self.__body:#ugh test if this is necessary
+                       mp = 0
+               def tNone(a, b, c):
+                       return a == None, b == None, c == None
+               if not self.m_use_geometry:
+                       return latex_linewidths[self.m_papersize][self.m_fontsize]
+               else:
+                       if tNone(self.m_geo_lmargin, self.m_geo_width,
+                               self.m_geo_rmargin) == (1, 1, 1):
+                               if self.m_geo_textwidth:
+                                       return self.m_geo_textwidth
+                               w = self.get_paperwidth() * 0.8
+                               return w - mp
+                       elif tNone(self.m_geo_lmargin, self.m_geo_width,
+                                self.m_geo_rmargin) == (0, 1, 1):
+                                if self.m_geo_textwidth:
+                                       return self.m_geo_textwidth
+                                return self.f1(self.m_geo_lmargin, mp)
+                       elif tNone(self.m_geo_lmargin, self.m_geo_width,
+                                self.m_geo_rmargin) == (1, 1, 0):
+                                if self.m_geo_textwidth:
+                                       return self.m_geo_textwidth
+                                return self.f1(self.m_geo_rmargin, mp)
+                       elif tNone(self.m_geo_lmargin, self.m_geo_width,
+                               self.m_geo_rmargin) \
+                                       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 tNone(self.m_geo_lmargin, self.m_geo_width,
+                               self.m_geo_rmargin) 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 TexiPaper:
+       def __init__(self):
+               self.m_papersize = 'a4'
+               self.m_fontsize = 12
+       def get_linewidth(self):
+               return texi_linewidths[self.m_papersize][self.m_fontsize]
 
-       if recompile:
-               sys.stderr.write ('invoking `%s\'\n' % command)
-               
-               status = os.system (command)
-               if status:
-                       raise CompileStatus
+def mm2pt(x):
+       return x * 2.8452756
+def in2pt(x):
+       return x * 72.26999
+def em2pt(x, fontsize):
+       return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
+def ex2pt(x, fontsize):
+       return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
+       
+# 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 = {
+       'a4': {12: 455},
+       'a4wide': {12: 470},
+       'smallbook': {12: 361},
+       'texidefault': {12: 433}}
+
+option_definitions = [
+  ('EXT', 'f', 'format', 'set format.  EXT is one of texi and latex.'),
+  ('DIM',  '', 'default-music-fontsize', 'default fontsize for music.  DIM is assumed to be in points'),
+  ('DIM',  '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
+  ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
+  ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
+  ('DIR', 'I', 'include', 'include path'),
+  ('', 'M', 'dependencies', 'write dependencies'),
+  ('PREF', '',  'dep-prefix', 'prepend PREF before each -M dependency'),
+  ('', 'n', 'no-lily', 'don\'t run lilypond'),
+  ('', '', 'no-pictures', "don\'t generate pictures"),
+  ('', '', 'read-lys', "don't write ly files."),
+  ('FILE', 'o', 'outname', 'filename main output file'),
+  ('FILE', '', 'outdir', "where to place generated files"),
+  ('', 'v', 'version', 'print version information' ),
+  ('', 'h', 'help', 'print help'),
+  ]
+
+# format specific strings, ie. regex-es for input, and % strings for output
+output_dict= {
+       'latex': {
+               'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
+  \context Staff <
+    \context Voice{
+      %s
+    }
+  >
+\end{mudela}""", 
+               'output-mudela':r"""\begin[%s]{mudela}
+%s
+\end{mudela}""",
+               'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
+               'output-default-post': "\\def\postMudelaExample{}\n",
+               'output-default-pre': "\\def\preMudelaExample{}\n",
+               'usepackage-graphics': '\\usepackage{graphics}\n',
+               'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
+               'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
+               'pagebreak': r'\pagebreak',
+               },
+       'texi' : {'output-mudela': """@mudela[%s]
+%s
+@end mudela 
+""",
+                 'output-mudela-fragment': """@mudela[%s]
+\context Staff\context Voice{ %s }
+@end mudela """,
+                 'pagebreak': None,
+                 'output-verbatim': r"""@example
+%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.
+
+# should also support fragment in
+                 
+                 'output-all': r"""@tex
+\catcode`\@=12
+\input lilyponddefs
+\def\EndLilyPondOutput{}
+\input %(fn)s.tex
+\catcode`\@=0
+@end tex
+@html
+<p>
+<img src=%(fn)s.png>
+@end html
+""",
+               }
+       }
+
+def output_verbatim (body):
+       if __main__.format == 'texi':
+               body = re.sub ('([@{}])', '@\\1', body)
+       return get_output ('output-verbatim') % body
+
+
+re_dict = {
+       'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
+                 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
+                 'option-sep' : ', *',
+                 '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})",
+                 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
+                 'mudela-file': r'(?m)^[^%\n]*?(?P<match>\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
+                 'mudela' : r'(?m)^[^%\n]*?(?P<match>\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
+                 'mudela-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela})",
+                 'def-post-re': r"\\def\\postMudelaExample",
+                 'def-pre-re': r"\\def\\preMudelaExample",               
+                 'usepackage-graphics': r"\usepackage{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)",
+                 },
+       
+       'texi': {
+                'include':  '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
+                'input': no_match,
+                'header': no_match,
+                'preamble-end': no_match,
+                'landscape': no_match,
+                'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
+                'verb': r"""(?P<code>@code{.*?})""",
+                'mudela-file': '(?m)^(?!@c)(?P<match>@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
+                'mudela' : '(?m)^(?!@c)(?P<match>@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
+                'mudela-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s))""",
+                 'option-sep' : ', *',
+                 '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,
+                }
+       }
 
-class Mudela_output:
-       def __init__ (self):
-               self.basename = ''
-               self.fragment = 0
-               self.size = 16
-               
-       def open (self, basename):
-               self.basename = basename
-               self.temp_file = "%s/%s" %(outdir, 'mudela-temp.ly')
-               self.file = open (self.temp_file, 'w')
-               self.file.write ('\\include \"paper%d.ly\"\n' % self.size)
-               if self.size == 16:
-                       s = 'sixteen'
-               else:
-                       s = 'twenty'
 
-               self.file.write ('default_paper = \\paper { \\paper_%s\n linewidth = -15.\\cm; }\n' % s)
+for r in re_dict.keys ():
+       olddict = re_dict[r]
+       newdict = {}
+       for k in olddict.keys ():
+               newdict[k] = re.compile (olddict[k])
+       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
                
-               if self.fragment:
-                       self.file.write ('\\score { \\melodic { ')
 
-       def write (self,s):
-               self.file.write (s)
+def get_output (name):
+       return  output_dict[format][name]
 
-       def close (self):
-               if self.fragment:
-                       self.file.write (
-                               '}\n \\paper { linewidth = -1.0\\cm;\n' +
-                               'castingalgorithm = \\Wordwrap; } }\n')
+def get_re (name):
+       return  re_dict[format][name]
+
+def bounding_box_dimensions(fname):
+       try:
+               fd = open(fname)
+       except IOError:
+               error ("Error opening `%s'" % fname)
+       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)), 
+                       int(s.group(4))-int(s.group(2)))
+       else:
+               return (0,0)
 
 
-               self.file.close ()
+def error (str):
+       sys.stderr.write (str + "\n  Exiting ... \n\n")
+       raise 'Exiting.'
 
-               inf=self.basename + '.ly'
-               outf = self.basename + '.tex'           
-               if not file_exist_b (inf):
-                       status = 1
+
+def compose_full_body (body, opts):
+       """Construct the mudela code to send to Lilypond.
+       Add stuff to BODY using OPTS as options."""
+       music_size = default_music_fontsize
+       latex_size = default_text_fontsize
+       for o in opts:
+               if g_force_mudela_fontsize:
+                       music_size = g_force_mudela_fontsize
                else:
-#                      print 'invoking %s' %('diff %s %s' % (self.temp_file, inf))
-                       status = os.system ('diff -q %s %s' % (self.temp_file, inf))
-#                      print 'status %d' % status
+                       m = re.match ('([0-9]+)pt', o)
+                       if m:
+                               music_size = string.atoi(m.group (1))
 
-               if status:
-                       os.rename (self.temp_file, inf)
+               m = re.match ('latexfontsize=([0-9]+)pt', o)
+               if m:
+                       latex_size = string.atoi (m.group (1))
 
-               compile ('lilypond  -o %s %s'%  (self.basename, inf), inf, outf)
-#                      os.rename (self.basename + '.tex', outdir  +)
-               
+       if re.search ('\\\\score', body):
+               is_fragment = 0
+       else:
+               is_fragment = 1
+       if 'fragment' in opts:
+               is_fragment = 1
+       if 'nonfragment' in opts:
+               is_fragment = 0
+
+       if is_fragment and not 'multiline' in opts:
+               opts.append('singleline')
+       if 'singleline' in opts:
+               l = -1.0;
+       else:
+               l = paperguru.get_linewidth()
        
+       if 'relative' in opts:#ugh only when is_fragment
+               body = '\\relative c { %s }' % body
+       
+       if is_fragment:
+               body = r"""\score { 
+ \notes { %s }
+  \paper { }  
+}""" % body
 
-class Tex_output:
-       def __init__ (self, name):
-               self.output_fn = '%s/%s' % (outdir, name)
-               self.mudela = 0
-               self.file = open (self.output_fn , 'w')
-               self.verbatim = 0               
-       def open_mudela (self, basename):
-               self.mudela_basename =basename
-               if self.verbatim:
-                       self.file.write ('\\begin{verbatim}\n')
-               self.mudela = 1
-
-       def write (self, s):
-               self.file.write (s)
-                       
-       def write_mudela (self, s):
-               if self.verbatim:
-                       self.file.write (s)
+       opts = uniq (opts)
+       optstring = string.join (opts, ' ')
+       optstring = re.sub ('\n', ' ', optstring)
+       body = r"""
+%% Generated by mudela-book.py; options are %s  %%ughUGH not original options
+\include "paper%d.ly"
+\paper  { linewidth = %f \pt; } 
+""" % (optstring, music_size, l) + body
+       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
+               print "trøbbel:%s:" % s
+       return d
+
+def scan_latex_preamble(chunks):
+       # first we want to scan the \documentclass line
+       # it should be the first non-comment line
+       idx = 0
+       while 1:
+               if chunks[idx][0] == 'ignore':
+                       idx = idx + 1
+                       continue
+               m = get_re ('header').match(chunks[idx][1])
+               options = re.split (',[\n \t]*', m.group(1)[1:-1])
+               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))
                        
-       def close_mudela (self):
-               if self.verbatim:
-                       self.file.write ('\\end{verbatim}\n')
-                       self.verbatim = 0
+               break
+       while 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])
+               idx = idx + 1
+
+def scan_texi_preamble (chunks):
+       # this is not bulletproof..., it checks the first 10 chunks
+       idx = 0
+       while 1:
+               if chunks[idx][0] == 'input':
+                       if string.find(chunks[idx][1], "@afourpaper") != -1:
+                               paperguru.m_papersize = 'a4'
+                       elif string.find(chunks[idx][1], "@afourwide") != -1:
+                               paperguru.m_papersize = 'a4wide'
+                       elif string.find(chunks[idx][1], "@smallbook") != -1:
+                               paperguru.m_papersize = 'smallbook'
+               idx = idx + 1
+               if idx == 10 or idx == len(chunks):
+                       break
+
+def scan_preamble (chunks):
+       if __main__.format == 'texi':
+               scan_texi_preamble(chunks)
+       else:
+               assert __main__.format == 'latex'
+               scan_latex_preamble(chunks)
                
-               self.file.write (
-                   '\\preMudelaExample\\input %s\n\postMudelaExample' %(self.mudela_basename))
-               self.mudela = 0
 
+def completize_preamble (chunks):
+       if __main__.format == 'texi':
+               return chunks
+       pre_b = post_b = graphics_b = None
+       for chunk in chunks:
+               if chunk[0] == 'preamble-end':
+                       break
+               if chunk[0] == 'input':
+                       m = get_re('def-pre-re').search(chunk[1])
+                       if m:
+                               pre_b = 1
+               if chunk[0] == 'input':
+                       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':
+               x = x + 1
+       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
 
 
+read_files = []
+def find_file (name):
+       f = None
+       for a in include_path:
+               try:
+                       nm = os.path.join (a, name)
+                       f = open (nm)
+                       __main__.read_files.append (nm)
+                       break
+               except IOError:
+                       pass
+       if f:
+               return f.read ()
+       else:
+               error ("File not found `%s'\n" % name)
+               return ''
 
+def do_ignore(match_object):
+       return [('ignore', match_object.group('code'))]
+def do_preamble_end(match_object):
+       return [('preamble-end', match_object.group('code'))]
 
-begin_mudela_re = regex.compile ('^ *\\\\begin{mudela}')
-begin_mudela_opts_re = regex.compile ('^ *\\\\begin{mudela}\[\(.*\)\]')
-end_mudela_re = regex.compile ('^ *\\\\end{mudela}')
-section_re = regex.compile ('\\\\section')
-chapter_re = regex.compile ('\\\\chapter')
-input_re = regex.compile ('^\\\\input[ \t\n]+\\(.*\\)$')
+def make_verbatim(match_object):
+       return [('verbatim', match_object.group('code'))]
 
-class Tex_input:
-       def __init__ (self,name):
-               if regex.search ('\\.[^/\\\\]+',name) == -1:
-                       name = name + '.tex'
-               print 'opening %s' % name
-               self.filename = name
-               self.infile = open (name)
-               
-       def get_lines (self):
-               lines = self.infile.readlines ()
-               (retlines, retdeps) = ([],[self.filename])
-               for line in lines:
-                       if input_re.search (line) <> -1:
-                               t = Tex_input (input_re.group (1))
-                               ls =t.get_lines ()
-                               retlines = retlines + ls[0]
-                               retdeps = retdeps + ls[1]
+def make_verb(match_object):
+       return [('verb', match_object.group('code'))]
+
+def do_include_file(m):
+       "m: MatchObject"
+       return [('input', get_output ('pagebreak'))] \
+            + read_doc_file(m.group('filename')) \
+            + [('input', get_output ('pagebreak'))] 
+
+def do_input_file(m):
+       return read_doc_file(m.group('filename'))
+
+def make_mudela(m):
+       if m.group('options'):
+               options = m.group('options')
+       else:
+               options = ''
+       return [('input', get_output('output-mudela-fragment') % 
+                       (options, m.group('code')))]
+
+def make_mudela_file(m):
+       if m.group('options'):
+               options = m.group('options')
+       else:
+               options = ''
+       return [('input', get_output('output-mudela') %
+                       (options, find_file(m.group('filename'))))]
+
+def make_mudela_block(m):
+       if m.group('options'):
+               options = get_re('option-sep').split (m.group('options'))
+       else:
+           options = []
+       options = filter(lambda s: s != '', options)
+       return [('mudela', m.group('code'), options)]
+
+def do_columns(m):
+       if __main__.format != 'latex':
+               return []
+       if m.group('num') == 'one':
+               return [('numcols', m.group('code'), 1)]
+       if m.group('num') == 'two':
+               return [('numcols', m.group('code'), 2)]
+       
+def chop_chunks(chunks, re_name, func, use_match=0):
+    newchunks = []
+    for c in chunks:
+        if c[0] == 'input':
+            str = c[1]
+            while str:
+                m = get_re (re_name).search (str)
+                if m == None:
+                    newchunks.append (('input', str))
+                    str = ''
+                else:
+                   if use_match:
+                        newchunks.append (('input', str[:m.start ('match')]))
+                   else:
+                        newchunks.append (('input', str[:m.start (0)]))
+                    #newchunks.extend(func(m))
+                   # python 1.5 compatible:
+                   newchunks = newchunks + func(m)
+                    str = str [m.end(0):]
+        else:
+            newchunks.append(c)
+    return newchunks
+
+def read_doc_file (filename):
+       """Read the input file, find verbatim chunks and do \input and \include
+       """
+       str = ''
+       str = find_file(filename)
+
+       if __main__.format == '':
+               latex =  re.search ('\\\\document', str[:200])
+               texinfo =  re.search ('@node|@setfilename', str[:200])
+               if (texinfo and latex) or not (texinfo or latex):
+                       error("error: can't determine format, please specify")
+               if texinfo:
+                       __main__.format = 'texi'
+               else:
+                       __main__.format = 'latex'
+       if __main__.format == 'texi':
+               __main__.paperguru = TexiPaper()
+       else:
+               __main__.paperguru = LatexPaper()
+       chunks = [('input', str)]
+       # we have to check for verbatim before doing include,
+       # 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
+       chunks = chop_chunks(chunks, 'include', do_include_file, 1)
+       chunks = chop_chunks(chunks, 'input', do_input_file, 1)
+       return chunks
+
+
+taken_file_names = {}
+def schedule_mudela_block (chunk):
+       """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, BASE)
+
+       TODO has format [basename, extension, extension, ... ]
+       
+       """
+       (type, body, opts) = chunk
+       assert type == 'mudela'
+       file_body = compose_full_body (body, opts)
+       basename = `abs(hash (file_body))`
+       for o in opts:
+               m = re.search ('filename="(.*?)"', o)
+               if m:
+                       basename = m.group (1)
+                       if not taken_file_names.has_key(basename):
+                           taken_file_names[basename] = 0
                        else:
-                               retlines.append (line)
+                           taken_file_names[basename] = taken_file_names[basename] + 1
+                           basename = basename + "-%i" % taken_file_names[basename]
+       if not g_read_lys:
+               update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
+       needed_filetypes = ['tex']
 
-               return (retlines, retdeps)
+       if format  == 'texi':
+               needed_filetypes.append('eps')
+               needed_filetypes.append('png')
+       if 'eps' in opts and not ('eps' in needed_filetypes):
+               needed_filetypes.append('eps')
+       outname = os.path.join(g_outdir, basename)
+       def f(base, ext1, ext2):
+               a = os.path.isfile(base + ext2)
+               if (os.path.isfile(base + ext1) and
+                   os.path.isfile(base + ext2) and
+                               os.stat(base+ext1)[stat.ST_MTIME] >
+                               os.stat(base+ext2)[stat.ST_MTIME]) or \
+                               not os.path.isfile(base + ext2):
+                       return 1
+       todo = []
+       if 'tex' in needed_filetypes and f(outname, '.ly', '.tex'):
+               todo.append('tex')
+       if 'eps' in needed_filetypes and f(outname, '.tex', '.eps'):
+               todo.append('eps')
+       if 'png' in needed_filetypes and f(outname, '.eps', '.png'):
+               todo.append('png')
+       newbody = ''
+       if 'verbatim' in opts:
+               newbody = output_verbatim (body)
 
-class Main_tex_input(Tex_input):
-       def __init__ (self, name, outname):
-               
-               Tex_input.__init__ (self, name) # ugh
-
-               self.outname = outname
-               self.chapter = 0
-               self.section = 0
-               self.fine_count =0
-               self.mudtex = Tex_output (self.outname)
-               self.mudela = None
-               self.deps = []
-       def set_sections (self, l):
-               if section_re.search (l) <> -1:
-                       self.section = self.section + 1
-               if chapter_re.search (l) <> -1:
-                       self.section = 0
-                       self.chapter = self.chapter + 1
+       for o in opts:
+               m = re.search ('intertext="(.*?)"', o)
+               if m:
+                       newbody = newbody  + m.group (1) + "\n\n"
+       if format == 'latex':
+               if 'eps' in opts:
+                       s = 'output-eps'
+               else:
+                       s = 'output-tex'
+       else: # format == 'texi'
+               s = 'output-all'
+       newbody = newbody + get_output(s) % {'fn': basename }
+       return ('mudela', newbody, opts, todo, basename)
 
-                       
-       def gulp_mudela (self):
+def process_mudela_blocks(outname, chunks):#ugh rename
+       newchunks = []
+       # Count sections/chapters.
+       for c in chunks:
+               if c[0] == 'mudela':
+                       c = schedule_mudela_block (c)
+               elif c[0] == 'numcols':
+                       paperguru.m_num_cols = c[2]
+               newchunks.append (c)
+       return newchunks
+
+
+def find_eps_dims (match):
+       "Fill in dimensions of EPS files."
+       
+       fn =match.group (1)
+       dims = bounding_box_dimensions (fn)
+       if g_outdir:
+               fn = os.path.join(g_outdir, fn)
+       
+       return '%ipt' % dims[0]
+
+
+def system (cmd):
+       sys.stderr.write ("invoking `%s'\n" % cmd)
+       st = os.system (cmd)
+       if st:
+               error ('Error command exited with value %d\n' % st)
+       return st
+
+def compile_all_files (chunks):
+       eps = []
+       tex = []
+       png = []
+
+       for c in chunks:
+               if c[0] <> 'mudela':
+                       continue
+               base  = c[4]
+               exts = c[3]
+               for e in exts:
+                       if e == 'eps':
+                               eps.append (base)
+                       elif e == 'tex':
+                               #ugh
+                               if base + '.ly' not in tex:
+                                       tex.append (base + '.ly')
+                       elif e == 'png' and g_do_pictures:
+                               png.append (base)
+       d = os.getcwd()
+       if g_outdir:
+               os.chdir(g_outdir)
+       if tex:
+               # fixme: be sys-independent.
+               def incl_opt (x):
+                       if g_outdir and x[0] <> '/' :
+                               x = os.path.join (g_here_dir, x)
+                       return ' -I %s' % x
+
+               incs =  map (incl_opt, include_path)
+               lilyopts = string.join (incs, ' ' )
+               texfiles = string.join (tex, ' ')
+               system ('lilypond %s %s' % (lilyopts, texfiles))
+       for e in eps:
+               system(r"tex '\nonstopmode \input %s'" % e)
+               system(r"dvips -E -o %s %s" % (e + '.eps', e))
+       for g in png:
+               cmd = r"""gs -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
+               cmd = cmd % (g + '.eps', g + '.png')
+               system (cmd)
+       if g_outdir:
+               os.chdir(d)
+
+
+def update_file (body, name):
+       """
+       write the body if it has changed
+       """
+       same = 0
+       try:
+               f = open (name)
+               fs = f.read (-1)
+               same = (fs == body)
+       except:
                pass
 
-       def gen_basename (self):
-               return '%s/%s-%d.%d.%d' % (outdir, self.outname,self.chapter,self.section,self.fine_count)
-
-       def do_it(self):
-               (lines, self.deps) = self.get_lines ()
-               for line in lines:
-                       if begin_mudela_re.search (line) <> -1:
-                               if begin_mudela_opts_re.search (line) <> -1:
-                                       opts = begin_mudela_opts_re.group (1)
-                               else:
-                                       opts = ''
-                               optlist = string.split (opts, ',')
-                               self.mudela = Mudela_output ()
-                               if 'fragment' in optlist:
-                                       self.mudela.fragment = 1
-                               if '16pt' in optlist:
-                                       self.mudela.size = 16
-                               if '20pt' in optlist:
-                                       self.mudela.size = 20
-                               
-                               if 'verbatim' in optlist:
-                                       self.mudtex.verbatim = 1
-
-                               b = self.gen_basename ()
-                               self.mudtex.open_mudela (b)
-                               self.mudela.open (b)
-
-
-                               continue
-                       elif end_mudela_re.search (line) <> -1:
-                               self.mudela.close ()
-                               self.mudtex.close_mudela ()
-                               self.mudela = None
-                               self.fine_count = self.fine_count + 1
-                               continue
-                           
-                       if self.mudela:
-                               self.mudela.write (line)
-                               self.mudtex.write_mudela (line)
-                       else:
-                               self.mudtex.write (line)
-                       self.set_sections(line)
-               del self.mudtex
+       if not same:
+               f = open (name , 'w')
+               f.write (body)
+               f.close ()
+       
+       return not same
+
+
+def getopt_args (opts):
+       "Construct arguments (LONG, SHORT) for getopt from  list of options."
+       short = ''
+       long = []
+       for o in opts:
+               if o[1]:
+                       short = short + o[1]
+                       if o[0]:
+                               short = short + ':'
+               if o[2]:
+                       l = o[2]
+                       if o[0]:
+                               l = l + '='
+                       long.append (l)
+       return (short, long)
+
+def option_help_str (o):
+       "Transform one option description (4-tuple ) into neatly formatted string"
+       sh = '  '       
+       if o[1]:
+               sh = '-%s' % o[1]
+
+       sep = ' '
+       if o[1] and o[2]:
+               sep = ','
                
+       long = ''
+       if o[2]:
+               long= '--%s' % o[2]
+
+       arg = ''
+       if o[0]:
+               if o[2]:
+                       arg = '='
+               arg = arg + o[0]
+       return '  ' + sh + sep + long + arg
+
+
+def options_help_str (opts):
+       "Convert a list of options into a neatly formatted string"
+       w = 0
+       strs =[]
+       helps = []
+
+       for o in opts:
+               s = option_help_str (o)
+               strs.append ((s, o[3]))
+               if len (s) > w:
+                       w = len (s)
+
+       str = ''
+       for s in strs:
+               str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
+       return str
 
 def help():
-    sys.stdout.write("Usage: mudela-book [options] FILE\n"
-                + "Generate hybrid LaTeX input from Latex + mudela"
-                + "Options:\n"
-                + "  -h, --help             print this help\n"
-                + "  -d, --outdir=DIR       prefix directory\n" 
-                + "  -o, --outname=FILE     prefix for filenames\n"
-                    )
-    sys.exit (0)
+       sys.stdout.write("""Usage: mudela-book [options] FILE\n
+Generate hybrid LaTeX input from Latex + mudela
+Options:
+""")
+       sys.stdout.write (options_help_str (option_definitions))
+       sys.stdout.write (r"""Warning all output is written in the CURRENT directory
 
 
 
-sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
+Report bugs to bug-gnu-music@gnu.org.
 
-outname = ''
-(options, files) = getopt.getopt(
-       sys.argv[1:], 'hd:o:', [ 'outdir=', 'outname=', 'help', 'dependencies'])
+Written by Tom Cato Amundsen <tca@gnu.org> and
+Han-Wen Nienhuys <hanwen@cs.uu.nl>
+""")
 
-do_deps = 0
-for opt in options:
-       o = opt[0]
-       a = opt[1]
-       if o == '--outname' or o == '-o':
-               outname = a
-       if o == '--outdir' or o == '-d':
-               outdir = a
-       if o == '--help' or o == '-h':
-               help ()
-       if o == '--dependencies':
-               do_deps = 1
+       sys.exit (0)
 
-def write_deps (fn, out,  deps):
-       out_fn = outdir + '/' + fn
-       print '\`writing \`%s\'\n\'' % out_fn
-       
-       f = open (out_fn, 'w')
-       f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
-                             reduce (lambda x,y: x + ' '+ y, deps)))
+
+def write_deps (fn, target):
+       sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
+       f = open (os.path.join(g_outdir, fn), 'w')
+       f.write ('%s%s: ' % (g_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)
 
-for f in files:
-       my_outname = outname
-       if not my_outname:
-               my_outname = regsub.sub ('\\(.*\\)\\.doc', '\\1', f)
+def print_version ():
+       identify()
+       sys.stdout.write (r"""Copyright 1998--1999
+Distributed under terms of the GNU General Public License. It comes with
+NO WARRANTY.
+""")
 
-       my_depname = my_outname + '.dep'
+def do_file(input_filename):
+       file_settings = {}
+       if outname:
+               my_outname = outname
+       else:
+               my_outname = os.path.basename(os.path.splitext(input_filename)[0])
+       my_depname = my_outname + '.dep'                
 
-       inp = Main_tex_input (f, my_outname)
-       inp.do_it ()
+       chunks = read_doc_file(input_filename)
+       chunks = chop_chunks(chunks, 'mudela', make_mudela, 1)
+       chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file, 1)
+       chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block, 1)
+       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)
+       #print "-" * 50
+       #for c in chunks: print "c:", c;
+       #sys.exit()
+       scan_preamble(chunks)
+       chunks = process_mudela_blocks(my_outname, chunks)
+       # Do It.
+       if __main__.g_run_lilypond:
+               compile_all_files (chunks)
+               newchunks = []
+               # finishing touch.
+               for c in chunks:
+                       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)
+               chunks = newchunks
+       x = 0
+       chunks = completize_preamble (chunks)
+       foutn = os.path.join(g_outdir, my_outname + '.' + format)
+       sys.stderr.write ("Writing `%s'\n" % foutn)
+       fout = open (foutn, 'w')
+       for c in chunks:
+               fout.write (c[1])
+       fout.close ()
 
        if do_deps:
-               write_deps (my_depname, my_outname, inp.deps)
+               write_deps (my_depname, foutn)
+
+
+outname = ''
+try:
+       (sh, long) = getopt_args (__main__.option_definitions)
+       (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' or o == '-v':
+               print_version ()
+               sys.exit  (0)
+       elif o == '--format' or o == '-f':
+               __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__.g_run_lilypond = 0
+       elif o == '--dependencies' or o == '-M':
+               do_deps = 1
+       elif o == '--default-music-fontsize':
+               default_music_fontsize = string.atoi (a)
+       elif o == '--default-mudela-fontsize':
+               print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
+               default_music_fontsize = string.atoi (a)
+       elif o == '--force-music-fontsize':
+               g_force_mudela_fontsize = string.atoi(a)
+       elif o == '--force-mudela-fontsize':
+               print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
+               g_force_mudela_fontsize = string.atoi(a)
+       elif o == '--dep-prefix':
+               g_dep_prefix = a
+       elif o == '--no-pictures':
+               g_do_pictures = 0
+       elif o == '--read-lys':
+               g_read_lys = 1
+       elif o == '--outdir':
+               g_outdir = a
 
+identify()
+if g_outdir:
+       if os.path.isfile(g_outdir):
+               error ("outdir is a file: %s" % g_outdir)
+       if not os.path.exists(g_outdir):
+               os.mkdir(g_outdir)
+for input_filename in files:
+       do_file(input_filename)
+       
+#
+# Petr, ik zou willen dat ik iets zinvoller deed,
+# maar wat ik kan ik doen, het verandert toch niets?
+#   --hwn 20/aug/99