X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmudela-book.py;h=c60924d43498712dafdab9014699494091f3aae1;hb=03311c038c913d96eb7ed0a896e2c7d8f4815e17;hp=98fb69f81ea44b09d479f1904b0f080f90a787fc;hpb=c64a05795e51367c17589a9e7d60bad426305159;p=lilypond.git diff --git a/scripts/mudela-book.py b/scripts/mudela-book.py index 98fb69f81e..c60924d434 100644 --- a/scripts/mudela-book.py +++ b/scripts/mudela-book.py @@ -1,645 +1,817 @@ #!@PYTHON@ -# All non-english comments are NOT in swedish, they are norwegian! -# TODO: -# * center option (??) -# * mudela-book should treat \begin{verbatim} that contains inline mudela -# correctly. -# * make mudela-book understand usepackage{geometry} -# * check that linewidth set in \paper is not wider than actual linewidth? -# * the following fails because mudelabook doesn't care that the -# last } after \end{mudela} finishes the marginpar: -# \marginpar{ -# \begin{mudela} -# c d e f g -# \end{mudela}} -# * force-verbatim is maybe not that useful since latex fails with footnotes, -# marginpars and others -# log: -# 0.3: -# rewrite in Python. -# 0.4: -# much rewritten by new author. I think the work has been split more -# logical between different classes. -# 0.4.1: -# - cleanup -# - speedup, do all mudela parsing at the same time to avoid multiple -# guile startup that takes some seconds on my computer -# 0.4.2: -# - fixed default latex fontsize, it should be 10pt not 11pt -# - verbatim option no longer visible -# - mudela-book produces .dvi output -# - change to use castingalgorithm = \Gourlay instead of \Wordwrap. It gives -# better result on small linewidths. -# - mudela-book-doc.doc rewritten -# 0.5: -# - junked floating and fragment options, added eps option -# - mudela-book will scan the mudela code to find out if it has to add -# paper-definition and \score{\notes{...}} -# - possible to define commands that look like this: \mudela{ c d e } +# vim: set noexpandtab: +# TODO: Figure out clean set of options. +# add support for .lilyrc import os +import stat import string import re import getopt import sys +import __main__ + + + +program_version = '@TOPLEVEL_VERSION@' +if program_version == '@' + 'TOPLEVEL_VERSION' + '@': + program_version = '1.3.69-very-unstable' + +include_path = [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' + +default_music_fontsize = 16 +default_text_fontsize = 12 + +# latex linewidths: +# indices are no. of columns, papersize, fontsize +# Why can't this be calculated? +latex_linewidths = { + 1: {'a4':{10: 345, 11: 360, 12: 390}, + 'a5':{10: 276, 11: 276, 12: 276}, + 'b5':{10: 345, 11: 356, 12: 356}, + 'letter':{10: 345, 11: 360, 12: 390}, + 'legal': {10: 345, 11: 360, 12: 390}, + 'executive':{10: 345, 11: 360, 12: 379}}, + 2: {'a4':{10: 167, 11: 175, 12: 190}, + 'a5':{10: 133, 11: 133, 12: 133}, + 'b5':{10: 167, 11: 173, 12: 173}, + 'letter':{10: 167, 11: 175, 12: 190}, + 'legal':{10: 167, 11: 175, 12: 190}, + 'executive':{10: 167, 11: 175, 12: 184}}} + +texi_linewidths = { + 'a4': {12: 455}, + 'a4wide': {12: 470}, + 'smallbook': {12: 361}, + 'texidefault': {12: 433}} + + +def get_linewidth(cols, paper, fontsize): + if __main__.format == 'latex': + return latex_linewidths[cols][paper][fontsize] + elif __main__.format == 'texi': + return texi_linewidths[paper][fontsize] + raise "never here" + +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': r"""\begin{verbatim}%s\end{verbatim}""", + 'output-default-post': r"""\def\postMudelaExample{}""", + 'output-default-pre': r"""\def\preMudelaExample{}""", + '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 +

+ +@end html +""", + } + } + +def output_verbatim (body):#ugh .format + if __main__.format == 'texi': + body = re.sub ('([@{}])', '@\\1', body) + return get_output ('output-verbatim') % body + +def output_mbverbatim (body):#ugh .format + if __main__.format == 'texi': + body = re.sub ('([@{}])', '@\\1', body) + return get_output ('output-verbatim') % body + +re_dict = { + 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)', + 'include': '\\\\mbinclude{(?P[^}]+)}', + + 'option-sep' : ', *', + 'header': r"""\\documentclass(\[.*?\])?""", + 'preamble-end': '\\\\begin{document}', + 'verbatim': r"""(?s)(?P\\begin{verbatim}.*?\\end{verbatim})""", + 'verb': r"""(?P\\verb(?P.).*?(?P=del))""", + 'mudela-file': r'\\mudelafile(\[(?P.*?)\])?\{(?P.+)}', + 'mudela' : '(?m)\\\\mudela(\[(?P.*?)\])?{(?P.*?)}', + #'mudela-block': r"""(?m)^[^%]*?\\begin(\[(?P.*?)\])?{mudela}(?P.*?)\\end{mudela}""", + 'mudela-block': r"""(?s)\\begin(\[(?P.*?)\])?{mudela}(?P.*?)\\end{mudela}""", + 'def-post-re': r"""\\def\\postMudelaExample""", + 'def-pre-re': r"""\\def\\preMudelaExample""", + 'intertext': r',?\s*intertext=\".*?\"', + 'ignore': r"(?m)(?P%.*?^)", + 'numcols': r"(?P\\(?Pone|two)column)", + }, + + 'texi': { + 'include': '@mbinclude[ \n\t]+(?P[^\t \n]*)', + 'input': no_match, + 'header': no_match, + 'preamble-end': no_match, + 'verbatim': r"""(?s)(?P@example\s.*?@end example\s)""", + 'verb': r"""(?P@code{.*?})""", + 'mudela-file': '@mudelafile(\[(?P.*?)\])?{(?P[^}]+)}', + 'mudela' : '@mudela(\[(?P.*?)\])?{(?P.*?)}', + 'mudela-block': r"""(?s)@mudela(\[(?P.*?)\])?\s(?P.*?)@end mudela\s""", + 'option-sep' : ', *', + 'intertext': r',?\s*intertext=\".*?\"', + 'ignore': r"(?s)(?P@ignore\s.*?@end ignore)\s", + 'numcols': no_match, + } + } + + +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 -outdir = 'out' -initfile = '' -program_version = '0.5' - -out_files = [] - -fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen', - 20:'twenty', 26:'twentysix'} -fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26} - -begin_mudela_re = re.compile ('^ *\\\\begin{mudela}') -extract_papersize_re = re.compile('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}') -extract_fontsize_re = re.compile('[ ,\[]*([0-9]*)pt') -begin_mudela_opts_re = re.compile('\[[^\]]*\]') -end_mudela_re = re.compile ('^ *\\\\end{mudela}') -section_re = re.compile ('\\\\section') -chapter_re = re.compile ('\\\\chapter') -input_re = re.compile ('^\\\\input{([^}]*)') -include_re = re.compile ('^\\\\include{([^}]*)') -begin_document_re = re.compile ('^ *\\\\begin{document}') -documentclass_re = re.compile('\\\\documentclass') -twocolumn_re = re.compile('\\\\twocolumn') -onecolumn_re = re.compile('\\\\onecolumn') -preMudelaExample_re = re.compile('\\\\def\\\\preMudelaExample') -postMudelaExample_re = re.compile('\\\\def\\\\postMudelaExample') -boundingBox_re = re.compile('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)') - -def file_exist_b(name): - try: - f = open(name) - except IOError: - return 0 - f.close () - return 1 - -def ps_dimention(fname): - fd = open(fname) - lines = fd.readlines() - for line in lines: - s = boundingBox_re.search(line) - if s: - break - return (int(s.groups()[2])-int(s.groups()[0]), - int(s.groups()[3])-int(s.groups()[1])) - - -class CompileStatus: - pass -class SomethingIsSeriouslyBroken: - pass - -def file_mtime (name): - return os.stat (name)[8] #mod time - -def need_recompile_b(infile, outfile): - indate = file_mtime (infile) - try: - outdate = file_mtime (outfile) - return indate > outdate - except os.error: - return 1 + +def uniq (list): + list.sort () + s = list + list = [] + for x in s: + if x not in list: + list.append (x) + return list + -# -# executes os.system(command) if infile is newer than -# outfile or outfile don't exist -# -def compile (command, workingdir, infile, outfile): - "Test if infile is newer than outfile. If so, cd to workingdir" - "and execute command" - indate = file_mtime (workingdir+infile) - try: - outdate = file_mtime (workingdir+outfile) - recompile = indate > outdate - - except os.error: - recompile = 1 - - if recompile: - sys.stderr.write ('invoking `%s\'\n' % command) - if workingdir == '': - status = os.system (command) - else: - status = os.system ('cd %s; %s' %(workingdir, command)) - if status: - raise CompileStatus - -class Properties: - # - # init - # - def __init__(self): - self.__linewidth = { - 1: {'a4':{10: 345, 11: 360, 12: 390}, - 'a5':{10: 276, 11: 276, 12: 276}, - 'b5':{10: 345, 11: 356, 12: 356}, - 'letter':{10: 345, 11: 360, 12: 390}, - 'legal': {10: 345, 11: 360, 12: 390}, - 'executive':{10: 345, 11: 360, 12: 379}}, - 2: {'a4':{10: 167, 11: 175, 12: 190}, - 'a5':{10: 133, 11: 133, 12: 133}, - 'b5':{10: 167, 11: 173, 12: 173}, - 'letter':{10: 167, 11: 175, 12: 190}, - 'legal':{10: 167, 11: 175, 12: 190}, - 'executive':{10: 167, 11: 175, 12: 184}}} - # >0 --> force all mudela to this pt size - self.force_mudela_fontsize = 0 - self.force_verbatim_b = 0 - self.__data = { - 'mudela-fontsize' : {'init': 16}, - 'papersize' : {'init': 'a4'}, - 'num-column' : {'init': 1}, - 'tex-fontsize' : {'init': 10} - } - def clear_for_new_file(self): - for var in self.__data.keys(): - self.__data[var] = {'init': self.__data[var]['init']} - def clear_for_new_block(self): - for var in self.__data.keys(): - if self.__data[var].has_key('block'): - del self.__data[var]['block'] - def __get_variable(self, var): - if self.__data[var].has_key('block'): - return self.__data[var]['block'] - elif self.__data[var].has_key('file'): - return self.__data[var]['file'] - else: - return self.__data[var]['init'] - def setPapersize(self, size, requester): - self.__data['papersize'][requester] = size - def getPapersize(self): - return self.__get_variable('papersize') - def setMudelaFontsize(self, size, requester): - self.__data['mudela-fontsize'][requester] = size - def getMudelaFontsize(self): - if self.force_mudela_fontsize: - return self.force_mudela_fontsize - return self.__get_variable('mudela-fontsize') - def setTexFontsize(self, size, requester): - self.__data['tex-fontsize'][requester] = size - def getTexFontsize(self): - return self.__get_variable('tex-fontsize') - def setNumColumn(self, num, requester): - self.__data['num-column'][requester] = num - def getNumColumn(self): - return self.__get_variable('num-column') - def getLineWidth(self): - return self.__linewidth[self.getNumColumn()][self.getPapersize()][self.getTexFontsize()] - - -class Mudela_output: - def __init__ (self, basename): - self.basename = basename - self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly') - self.file = open (self.temp_filename, 'w') - self.__lines = [] - # 'tex' or 'eps' - self.graphic_type = 'tex' - self.code_type = 'unknown' - self.code_type_override = None - def write (self, line): - # match only if there is nothing but whitespace before \begin HACK - if re.search('^\s*\\\\begin{mudela}', line): - self.scan_begin_statement(line) - else: - if self.code_type == 'unknown': - if re.search('^\s*\\\\score', line) or \ - re.search('^\s*\\\\paper', line) or \ - re.search('^\s*\\\\header', line) or \ - re.search('^\s*[A-Za-z]*\s*=', line): - self.code_type = 'ly' - self.__lines.append(line) - def scan_begin_statement(self, line): - r = begin_mudela_opts_re.search(line) - if r: - o = r.group()[1:-1] - optlist = re.compile('[\s,]*').split(o) - else: - optlist = [] - if 'fragment' in optlist: - self.code_type_override = 'fly' - if 'nonfragment' in optlist: - self.code_type_override = 'ly' - if 'eps' in optlist: - self.graphic_type = 'eps' - for pt in fontsize_pt2i.keys(): - if pt in optlist: - Props.setMudelaFontsize(fontsize_pt2i[pt], 'block') - def write_red_tape(self): - self.file.write ('\\include \"paper%d.ly\"\n' \ - % Props.getMudelaFontsize()) - - s = fontsize_i2a[Props.getMudelaFontsize()] - if self.code_type == 'fly': - linewidth_str = 'linewidth = -1.\cm;' - else: - linewidth_str = 'linewidth = %i.\\pt;' % Props.getLineWidth() - self.file.write("\\paper {" - + "\\paper_%s " % s - + linewidth_str - + "castingalgorithm = \Gourlay; \n}") - #+ "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}") - if self.code_type == 'fly': - self.file.write('\\score{\n\\notes{') - def close (self): - if self.code_type == 'unknown': - self.code_type = 'fly' - if self.code_type_override: - self.code_type = self.code_type_override - print "override:", self.code_type_override - self.write_red_tape() - for l in self.__lines: - self.file.write(l) - if self.code_type == 'fly': - self.file.write('}}') - - self.file.close() - - inf = outdir + self.basename + '.ly' - outf = outdir + self.basename + '.tex' - if not os.path.isfile(inf): - status = 1 - else: - status = os.system ('diff -q %s %s' % (self.temp_filename, inf)) - if status: - os.rename (self.temp_filename, inf) - if need_recompile_b(inf, outf): - out_files.append((self.graphic_type, inf)) - def insert_me_string(self): - "Returns a string that can be used directly in latex." - if self.graphic_type == 'tex': - return ['tex', self.basename] - elif self.graphic_type == 'eps': - return ['eps', self.basename] - else: - raise SomethingIsSeriouslyBroken - -class Tex_output: - def __init__ (self, name): - self.output_fn = '%s/%s' % (outdir, name) - self.__lines = [] - def open_verbatim (self, line): - self.__lines.append('\\begin{verbatim}\n') - s = re.sub('[\s,]*verbatim[\s]*', '', line) - s = re.sub('\[\]', '', s) - self.__lines.append(s); - def close_verbatim (self): - self.__lines.append('\\end{verbatim}\n') - def write (self, s): - self.__lines.append(s) - def create_graphics(self): - s = '' - g_vec = [] - for line in self.__lines: - if type(line)==type([]): - g_vec.append(line) - for g in g_vec: - if need_recompile_b(outdir+g[1]+'.ly', outdir+g[1]+'.tex'): - s = s + ' ' + g[1]+'.ly' - if s != '': - e = os.system('cd %s; lilypond %s' %(outdir, s)) - if e: - print "error: lilypond exited with value", e - sys.exit(e) - for g in g_vec: - if g[0] == 'eps': - compile('tex %s' % g[1]+'.tex', outdir, g[1]+'.tex', g[1]+'.dvi') - compile('dvips -E -o %s %s' %(g[1]+'.eps', g[1]+'.dvi'), outdir, - g[1]+'.dvi', g[1]+'.eps') - def write_outfile(self): - file = open(self.output_fn+'.latex', 'w') - file.write('% Created by mudela-book\n') - for line in self.__lines: - if type(line)==type([]): - if line[0] == 'tex': - file.write('\\preMudelaExample\\input %s\n\postMudelaExample '\ - # TeX applies the prefix of the main source automatically. - % (line[1]+'.tex')) -# % (outdir+line[1]+'.tex')) - if line[0] == 'eps': - ps_dim = ps_dimention(outdir+line[1]+'.eps') - file.write('\\parbox{%ipt}{\includegraphics{%s}}\n' \ - % (ps_dim[0], line[1]+'.eps')) -# % (ps_dim[0], outdir+line[1]+'.eps')) - else: - file.write(line) - file.close() - -class Tex_input: - def __init__ (self, filename): - for fn in [filename, filename+'.tex', filename+'.doc']: - try: - self.infile = open (fn) - self.filename = fn - return - except: - continue - raise IOError - def get_lines (self): - lines = self.infile.readlines () - (retlines, retdeps) = ([],[self.filename]) - for line in lines: - r_inp = input_re.search (line) - r_inc = include_re.search (line) - - # Filename rules for \input : - # input: no .tex ext - # 1. will search for file with exact that name (tex-input.my will be found) - # 2. will search for file with .tex ext, (tex-input.my - # will find tex-input.my.tex) - # input: with .tex ext - # 1. will find exact match - - # Filename rules for \include : - # 1. will only find files with name given to \include + .tex ext - if r_inp: - try: - t = Tex_input (r_inp.groups()[0]) - ls = t.get_lines () - retlines = retlines + ls[0] - retdeps = retdeps + ls[1] - except: - print "warning: can't find %s, let's hope latex will" \ - % r_inp.groups()[0] - retlines.append (line) - elif r_inc: - try: - t = Tex_input (r_inc.groups()[0]+'.tex') - ls =t.get_lines () - ls[0].insert(0, '\\newpage\n') - ls[0].append('\\newpage\n') - retlines = retlines + ls[0] - retdeps = retdeps + ls[1] - except: - print "warning: can't find %s, let's hope latex will" \ - % r_inc.groups()[0] - retlines.append (line) - else: - r_mud = defined_mudela_cmd_re.search(line) - if r_mud: - ss = "\\\\verb(?P[^a-zA-Z])\s*\\\\%s\s*(?P=xx)" \ - % re.escape(r_mud.group()[1:]) - if re.search(ss, line): - retlines.append(line) - continue - while 1: - opts = r_mud.groups()[2] - if opts == None: - opts = '' - else: - opts = ', '+opts - (start, rest) = string.split(line, r_mud.group(), 1) - retlines.append(start)#+'\n') - v = string.split(defined_mudela_cmd[r_mud.groups()[0]], '\n') - for l in v[1:-1]: - l = re.sub(r'\\fontoptions', opts, l) - l = re.sub(r'\\maininput', r_mud.groups()[3], l) - retlines.append(l) - r_mud = defined_mudela_cmd_re.search(rest) - if not r_mud: - retlines.append(rest) - break; - line = rest +def get_output (name): + return output_dict[format][name] + +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) + + +def error (str): + sys.stderr.write (str + "\n Exiting ... \n\n") + raise 'Exiting.' + + +def compose_full_body (body, opts): + """Construct the mudela code to send to Lilypond. + Add stuff to BODY using OPTS as options.""" + if __main__.format == 'texi': + paper = 'texidefault' + else: + paper = 'letter' # yes, latex use letter as default, at least + # my tetex distro + music_size = default_music_fontsize + latex_size = default_text_fontsize + for o in opts: + m = re.search ('^(.*)paper$', o) + if m: + paper = m.group (1) + + if g_force_mudela_fontsize: + music_size = g_force_mudela_fontsize + else: + 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)) + + 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 = get_linewidth(g_num_cols, paper, latex_size) + + if 'relative' in opts:#ugh only when is_fragment + body = '\\relative c { %s }' % body + + if is_fragment: + body = r"""\score { + \notes { %s } + \paper { } +}""" % body + + 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 scan_preamble (str): + options = [] + if __main__.format == 'texi': + x = 250 + if string.find(str[:x], "@afourpaper") != -1: + options = ['a4paper'] + elif string.find(str[:x], "@afourwide") != -1: + options = ['a4widepaper'] + elif string.find(str[:x], "@smallbook") != -1: + options = ['smallbookpaper'] + m = get_re ('header').search( str) + # should extract paper & fontsz. + if m and m.group (1): + options = options + re.split (',[\n \t]*', m.group(1)[1:-1]) + + def verbose_fontsize ( x): + if re.match('[0-9]+pt', x): + return 'latexfontsize=' + x + else: + return x + + options = map (verbose_fontsize, options) + return options + + +def completize_preamble (str): + m = get_re ('preamble-end').search( str) + if not m: + return str + + preamble = str [:m.start (0)] + str = str [m.start(0):] + + if not get_re('def-post-re').search (preamble): + preamble = preamble + get_output('output-default-post') + if not get_re ('def-pre-re').search( preamble): + preamble = preamble + get_output ('output-default-pre') + + # UGH ! BUG! + #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str): + + preamble = preamble + '\\usepackage{graphics}\n' + + return preamble + str + + +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 make_verbatim(match_object): + return [('verbatim', match_object.group('code'))] + +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) + if 'mbverbatim' in options:#ugh this is ugly and only for texi format + s = m.group() + im = get_re('intertext').search(s) + if im: + s = s[:im.start()] + s[im.end():] + im = re.search('mbverbatim', s) + if im: + s = s[:im.start()] + s[im.end():] + if s[:9] == "@mudela[]": + s = "@mudela" + s[9:] + return [('mudela', m.group('code'), options, s)] + 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): + 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: - retlines.append (line) + 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: - return (retlines, retdeps) - - -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 = [] - self.verbatim = 0 - # set to 'mudela' when we are processing mudela code, - # both verbatim and graphic-to-be - self.mode = 'latex' - def set_sections (self, l): - if section_re.search (l): - self.section = self.section + 1 - if chapter_re.search (l): - self.section = 0 - self.chapter = self.chapter + 1 - - def gen_basename (self): - return '%s-%d.%d.%d' % (self.outname, self.chapter, - self.section, self.fine_count) - def extract_papersize_from_documentclass(self, line): - pre = extract_papersize_re.search(line) - if not pre: - return None - return pre.groups()[0] - def extract_fontsize_from_documentclass(self, line): - r = extract_fontsize_re.search(line) - if r: - return int(r.groups()[0]) - def do_it(self): - preMudelaDef = postMudelaDef = 0 - (lines, self.deps) = self.get_lines () - for line in lines: - if documentclass_re.search (line): - p = self.extract_papersize_from_documentclass (line) - if p: - Props.setPapersize(p, 'file') - f = self.extract_fontsize_from_documentclass (line) - if f: - Props.setTexFontsize (f, 'file') - elif twocolumn_re.search (line): - Props.setNumColumn (2, 'file') - elif onecolumn_re.search (line): - Props.setNumColumn (1, 'file') - elif preMudelaExample_re.search (line): - preMudelaDef = 1 - elif postMudelaExample_re.search (line): - postMudelaDef = 1 - elif begin_document_re.search (line): - if not preMudelaDef: - self.mudtex.write ('\\def\\preMudelaExample{}\n') - if not postMudelaDef: - self.mudtex.write ('\\def\\postMudelaExample{}\n') - elif begin_mudela_re.search (line): - Props.clear_for_new_block() - if __debug__: - if self.mode == 'mudela': - raise AssertionError - self.mode = 'mudela' - r = begin_mudela_opts_re.search (line) - if r: - o = r.group()[1:][:-1] - optlist = re.compile('[ ,]*').split(o) - else: - optlist = [] - if ('verbatim' in optlist) or (Props.force_verbatim_b): - self.verbatim = 1 - self.mudtex.open_verbatim (line) - continue - else: - self.verbatim = 0 - self.mudela = Mudela_output (self.gen_basename ()) - elif end_mudela_re.search (line): - if __debug__: - if self.mode != 'mudela': - raise AssertionError - if self.mudela: - self.mudela.close () - self.mudtex.write (self.mudela.insert_me_string()) - del self.mudela - self.mudela = None - self.fine_count = self.fine_count + 1 - else: - self.mudtex.write (line) - self.mudtex.close_verbatim () - self.mode = 'latex' - continue - - if self.mode == 'mudela' and not self.verbatim: - self.mudela.write (line) - else: - self.mudtex.write (line) - self.set_sections(line) - self.mudtex.create_graphics() - self.mudtex.write_outfile() - del self.mudtex + 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' + 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) + #ugh fix input + chunks = chop_chunks(chunks, 'include', do_include_file) + chunks = chop_chunks(chunks, 'input', do_input_file) + return chunks + + +taken_file_names = {} +def schedule_mudela_block (chunk, extra_opts): + """Take the body and options from CHUNK, figure out how the + real .ly should look, and what should be left MAIN_STR (meant + for the main file). The .ly is written, and scheduled in + TODO. + + Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE) + + TODO has format [basename, extension, extension, ... ] + + """ + if len(chunk) == 3: + (type, body, opts) = chunk + complete_body = None + else:# mbverbatim + (type, body, opts, complete_body) = chunk + assert type == 'mudela' + opts = opts + extra_opts + 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: + taken_file_names[basename] = taken_file_names[basename] + 1 + basename = basename + "-%i" % taken_file_names[basename] + # writes the file if necessary, returns true if it was written + if not g_read_lys: + update_file(file_body, os.path.join(g_outdir, basename) + '.ly') + needed_filetypes = ['tex'] + + 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) + if not os.path.isfile(outname + '.tex') \ + or os.stat(outname+'.ly')[stat.ST_MTIME] > \ + os.stat(outname+'.tex')[stat.ST_MTIME]: + todo = needed_filetypes + else: + todo = [] + newbody = '' + if 'verbatim' in opts: + newbody = output_verbatim (body) + elif 'mbverbatim' in opts: + newbody = output_mbverbatim (complete_body) + + for o in opts: + m = re.search ('intertext="(.*?)"', o) + if m: + newbody = newbody + m.group (1) + 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 process_mudela_blocks(outname, chunks, global_options):#ugh rename + newchunks = [] + # Count sections/chapters. + for c in chunks: + if c[0] == 'mudela': + c = schedule_mudela_block (c, global_options) + elif c[0] == 'numcols': + __main__.g_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) + + 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': + 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: + lilyopts = map (lambda x: '-I ' + x, include_path) + lilyopts = string.join (lilyopts, ' ' ) + 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 + + 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 + sys.stdout.write("""Usage: mudela-book [options] FILE\n Generate hybrid LaTeX input from Latex + mudela -Options:\n - -h, --help print this help - -d, --outdir=DIR directory to put generated files - -o, --outname=FILE prefix for filenames - --default-mudela-fontsize=??pt default fontsize for music - --force-mudela-fontsize=??pt force fontsize for all inline mudela - --force-verbatim make all mudela verbatim\n - --dependencies write dependencies - --init mudela-book initfile - """ - ) - 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))) +Options: +""") + sys.stdout.write (options_help_str (option_definitions)) + sys.stdout.write (r"""Warning all output is written in the CURRENT directory + + + +Report bugs to bug-gnu-music@gnu.org. + +Written by Tom Cato Amundsen and +Han-Wen Nienhuys +""") + + sys.exit (0) + + +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.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version)) - -def main(): - global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re - outname = '' - try: - (options, files) = getopt.getopt( - sys.argv[1:], 'hd:o:', ['outdir=', 'outname=', - 'default-mudela-fontsize=', - 'force-mudela-fontsize=', - 'help', 'dependencies', - 'force-verbatim', 'init=']) - except getopt.error, msg: - print "error:", msg - sys.exit(1) - - do_deps = 0 - for opt in options: + sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version) + +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. +""") + +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' + + chunks = read_doc_file(input_filename) + chunks = chop_chunks(chunks, 'mudela', make_mudela) + chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file) + chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block) + #for c in chunks: print c, "\n" + chunks = chop_chunks(chunks, 'ignore', do_ignore) + chunks = chop_chunks(chunks, 'numcols', do_columns) + global_options = scan_preamble(chunks[0][1]) + chunks = process_mudela_blocks(my_outname, chunks, global_options) + # 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 + + if chunks and chunks[0][0] == 'input': + chunks[0] = ('input', completize_preamble (chunks[0][1])) + + 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, 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 == '--outname' or o == '-o': - if len(files) > 1: - #HACK - print "Mudela-book is confused by --outname on multiple files" - sys.exit(1) - outname = a - if o == '--outdir' or o == '-d': - outdir = a - if o == '--help' or o == '-h': - help () - if o == '--dependencies': - do_deps = 1 - if o == '--default-mudela-fontsize': - if not fontsize_pt2i.has_key(a): - print "Error: illegal fontsize:", a - print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt" - sys.exit() - Props.setMudelaFontsize(fontsize_pt2i[a], 'init') - if o == '--force-mudela-fontsize': - if not fontsize_pt2i.has_key(a): - print "Error: illegal fontsize:", a - print " accepted fontsizes are: 11pt, 13pt, 16pt, 20pt, 26pt" - sys.exit() - Props.force_mudela_fontsize = fontsize_pt2i[a] - if o == '--force-verbatim': - Props.force_verbatim_b = 1 - if o == '--init': - initfile = a - if outdir[-1:] != '/': - outdir = outdir + '/' - - std_init_filename = '' - for p in string.split(os.environ['LILYINCLUDE'], ':'): - try: - std_init_filename = p+os.sep+'mudela-book-defs.py' - break - except: - continue - defined_mudela_cmd_re = {} - try: - f = open(std_init_filename) - s = f.read() - f.close() - defined_mudela_cmd = eval(s) # UGH - except IOError, w: - sys.stderr.write("%s (`%s')\n" % (w[1], std_init_filename)) -# sys.exit(1) - - - if initfile != '': - f = open(initfile) - s = f.read() - f.close() - d = eval(s) - for i in d.keys(): - defined_mudela_cmd[i] = d[i] - del d - - c = defined_mudela_cmd.keys()[0] - for x in defined_mudela_cmd.keys()[1:]: - c = c + '|'+x - defined_mudela_cmd_re = re.compile("\\\\(%s)(\[(\d*pt)\])*{([^}]*)}" %c) - - if not os.path.isdir(outdir): - os.system('mkdir %s' % outdir) - - for input_filename in files: - Props.clear_for_new_file() - 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 (input_filename, my_outname) - inp.do_it () -# os.system('latex %s/%s.latex' % (outdir, my_outname)) - if do_deps: - write_deps (my_depname, my_outname, inp.deps) + + 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() -Props = Properties() -main() +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