2 # vim: set noexpandtab:
3 # TODO: Figure out clean set of options.
4 # add support for .lilyrc
15 program_version = '@TOPLEVEL_VERSION@'
17 include_path = [os.getcwd()]
21 g_force_mudela_fontsize = 0
29 default_music_fontsize = 16
30 default_text_fontsize = 12
33 # indices are no. of columns, papersize, fontsize
34 # Why can't this be calculated?
36 1: {'a4':{10: 345, 11: 360, 12: 390},
37 'a5':{10: 276, 11: 276, 12: 276},
38 'b5':{10: 345, 11: 356, 12: 356},
39 'letter':{10: 345, 11: 360, 12: 390},
40 'legal': {10: 345, 11: 360, 12: 390},
41 'executive':{10: 345, 11: 360, 12: 379}},
42 2: {'a4':{10: 167, 11: 175, 12: 190},
43 'a5':{10: 133, 11: 133, 12: 133},
44 'b5':{10: 167, 11: 173, 12: 173},
45 'letter':{10: 167, 11: 175, 12: 190},
46 'legal':{10: 167, 11: 175, 12: 190},
47 'executive':{10: 167, 11: 175, 12: 184}}}
52 'smallbook': {12: 361},
53 'texidefault': {12: 433}}
56 def get_linewidth(cols, paper, fontsize):
57 if __main__.format == 'latex':
58 return latex_linewidths[cols][paper][fontsize]
59 elif __main__.format == 'texi':
60 return texi_linewidths[paper][fontsize]
63 option_definitions = [
64 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
65 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
66 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
67 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
68 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
69 ('DIR', 'I', 'include', 'include path'),
70 ('', 'M', 'dependencies', 'write dependencies'),
71 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
72 ('', 'n', 'no-lily', 'don\'t run lilypond'),
73 ('', '', 'no-pictures', "don\'t generate pictures"),
74 ('', '', 'read-lys', "don't write ly files."),
75 ('FILE', 'o', 'outname', 'filename main output file'),
76 ('FILE', '', 'outdir', "where to place generated files"),
77 ('', 'v', 'version', 'print version information' ),
78 ('', 'h', 'help', 'print help'),
81 # format specific strings, ie. regex-es for input, and % strings for output
84 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
91 'output-mudela':r"""\begin[%s]{mudela}
94 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
95 'output-default-post': r"""\def\postMudelaExample{}""",
96 'output-default-pre': r"""\def\preMudelaExample{}""",
97 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
98 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
99 'pagebreak': r'\pagebreak',
101 'texi' : {'output-mudela': """@mudela[%s]
105 'output-mudela-fragment': """@mudela[%s]
106 \context Staff\context Voice{ %s }
109 'output-verbatim': r"""@example
114 # do some tweaking: @ is needed in some ps stuff.
115 # override EndLilyPondOutput, since @tex is done
116 # in a sandbox, you can't do \input lilyponddefs at the
117 # top of the document.
119 # should also support fragment in
121 'output-all': r"""@tex
124 \def\EndLilyPondOutput{}
136 def output_verbatim (body):#ugh .format
137 if __main__.format == 'texi':
138 body = re.sub ('([@{}])', '@\\1', body)
139 return get_output ('output-verbatim') % body
141 def output_mbverbatim (body):#ugh .format
142 if __main__.format == 'texi':
143 body = re.sub ('([@{}])', '@\\1', body)
144 return get_output ('output-verbatim') % body
147 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
148 'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
150 'option-sep' : ', *',
151 'header': r"""\\documentclass(\[.*?\])?""",
152 'preamble-end': '\\\\begin{document}',
153 'verbatim': r"""(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})""",
154 'verb': r"""(?P<code>\\verb(?P<del>.).*?(?P=del))""",
155 'mudela-file': r'\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)}',
156 'mudela' : '(?m)\\\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
157 #'mudela-block': r"""(?m)^[^%]*?\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
158 'mudela-block': r"""(?s)\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
159 'def-post-re': r"""\\def\\postMudelaExample""",
160 'def-pre-re': r"""\\def\\preMudelaExample""",
161 'intertext': r',?\s*intertext=\".*?\"',
162 'ignore': r"(?m)(?P<code>%.*?^)",
163 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
167 'include': '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
170 'preamble-end': no_match,
171 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
172 'verb': r"""(?P<code>@code{.*?})""",
173 'mudela-file': '@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)}',
174 'mudela' : '@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
175 'mudela-block': r"""(?s)@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s""",
176 'option-sep' : ', *',
177 'intertext': r',?\s*intertext=\".*?\"',
178 'ignore': r"(?s)(?P<code>@ignore\s.*?@end ignore)\s",
184 for r in re_dict.keys ():
187 for k in olddict.keys ():
188 newdict[k] = re.compile (olddict[k])
202 def get_output (name):
203 return output_dict[format][name]
206 return re_dict[format][name]
208 def bounding_box_dimensions(fname):
212 error ("Error opening `%s'" % fname)
214 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
216 return (int(s.group(3))-int(s.group(1)),
217 int(s.group(4))-int(s.group(2)))
223 sys.stderr.write (str + "\n Exiting ... \n\n")
227 def compose_full_body (body, opts):
228 """Construct the mudela code to send to Lilypond.
229 Add stuff to BODY using OPTS as options."""
230 if __main__.format == 'texi':
231 paper = 'texidefault'
233 paper = 'letter' # yes, latex use letter as default, at least
235 music_size = default_music_fontsize
236 latex_size = default_text_fontsize
238 m = re.search ('^(.*)paper$', o)
242 if g_force_mudela_fontsize:
243 music_size = g_force_mudela_fontsize
245 m = re.match ('([0-9]+)pt', o)
247 music_size = string.atoi(m.group (1))
249 m = re.match ('latexfontsize=([0-9]+)pt', o)
251 latex_size = string.atoi (m.group (1))
253 if re.search ('\\\\score', body):
257 if 'fragment' in opts:
259 if 'nonfragment' in opts:
262 if is_fragment and not 'multiline' in opts:
263 opts.append('singleline')
264 if 'singleline' in opts:
267 l = get_linewidth(g_num_cols, paper, latex_size)
269 if 'relative' in opts:#ugh only when is_fragment
270 body = '\\relative c { %s }' % body
279 optstring = string.join (opts, ' ')
280 optstring = re.sub ('\n', ' ', optstring)
283 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
284 \include "paper%d.ly"
285 \paper { linewidth = %f \pt; }
286 """ % (optstring, music_size, l) + body
290 def scan_preamble (str):
292 if __main__.format == 'texi':
294 if string.find(str[:x], "@afourpaper") != -1:
295 options = ['a4paper']
296 elif string.find(str[:x], "@afourwide") != -1:
297 options = ['a4widepaper']
298 elif string.find(str[:x], "@smallbook") != -1:
299 options = ['smallbookpaper']
300 m = get_re ('header').search( str)
301 # should extract paper & fontsz.
302 if m and m.group (1):
303 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
305 def verbose_fontsize ( x):
306 if re.match('[0-9]+pt', x):
307 return 'latexfontsize=' + x
311 options = map (verbose_fontsize, options)
315 def completize_preamble (str):
316 m = get_re ('preamble-end').search( str)
320 preamble = str [:m.start (0)]
321 str = str [m.start(0):]
323 if not get_re('def-post-re').search (preamble):
324 preamble = preamble + get_output('output-default-post')
325 if not get_re ('def-pre-re').search( preamble):
326 preamble = preamble + get_output ('output-default-pre')
329 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
331 preamble = preamble + '\\usepackage{graphics}\n'
333 return preamble + str
337 def find_file (name):
339 for a in include_path:
341 nm = os.path.join (a, name)
343 __main__.read_files.append (nm)
350 error ("File not found `%s'\n" % name)
353 def do_ignore(match_object):
354 return [('ignore', match_object.group('code'))]
356 def make_verbatim(match_object):
357 return [('verbatim', match_object.group('code'))]
359 def make_verb(match_object):
360 return [('verb', match_object.group('code'))]
362 def do_include_file(m):
364 return [('input', get_output ('pagebreak'))] \
365 + read_doc_file(m.group('filename')) \
366 + [('input', get_output ('pagebreak'))]
368 def do_input_file(m):
369 return read_doc_file(m.group('filename'))
372 if m.group('options'):
373 options = m.group('options')
376 return [('input', get_output('output-mudela-fragment') %
377 (options, m.group('code')))]
379 def make_mudela_file(m):
380 if m.group('options'):
381 options = m.group('options')
384 return [('input', get_output('output-mudela') %
385 (options, find_file(m.group('filename'))))]
387 def make_mudela_block(m):
388 if m.group('options'):
389 options = get_re('option-sep').split (m.group('options'))
392 options = filter(lambda s: s != '', options)
393 if 'mbverbatim' in options:#ugh this is ugly and only for texi format
395 im = get_re('intertext').search(s)
397 s = s[:im.start()] + s[im.end():]
398 im = re.search('mbverbatim', s)
400 s = s[:im.start()] + s[im.end():]
401 if s[:9] == "@mudela[]":
402 s = "@mudela" + s[9:]
403 return [('mudela', m.group('code'), options, s)]
404 return [('mudela', m.group('code'), options)]
407 if __main__.format != 'latex':
409 if m.group('num') == 'one':
410 return [('numcols', m.group('code'), 1)]
411 if m.group('num') == 'two':
412 return [('numcols', m.group('code'), 2)]
414 def chop_chunks(chunks, re_name, func):
420 m = get_re (re_name).search (str)
422 newchunks.append (('input', str))
425 newchunks.append (('input', str[:m.start (0)]))
426 #newchunks.extend(func(m))
427 # python 1.5 compatible:
428 newchunks = newchunks + func(m)
429 str = str [m.end(0):]
434 def read_doc_file (filename):
435 """Read the input file, find verbatim chunks and do \input and \include
438 str = find_file(filename)
440 if __main__.format == '':
441 latex = re.search ('\\\\document', str[:200])
442 texinfo = re.search ('@node|@setfilename', str[:200])
443 if (texinfo and latex) or not (texinfo or latex):
444 error("error: can't determine format, please specify")
446 __main__.format = 'texi'
448 __main__.format = 'latex'
449 chunks = [('input', str)]
450 # we have to check for verbatim before doing include,
451 # because we don't want to include files that are mentioned
452 # inside a verbatim environment
453 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
454 chunks = chop_chunks(chunks, 'verb', make_verb)
456 chunks = chop_chunks(chunks, 'include', do_include_file)
457 chunks = chop_chunks(chunks, 'input', do_input_file)
461 taken_file_names = {}
462 def schedule_mudela_block (chunk, extra_opts):
463 """Take the body and options from CHUNK, figure out how the
464 real .ly should look, and what should be left MAIN_STR (meant
465 for the main file). The .ly is written, and scheduled in
468 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
470 TODO has format [basename, extension, extension, ... ]
474 (type, body, opts) = chunk
477 (type, body, opts, complete_body) = chunk
478 assert type == 'mudela'
479 opts = opts + extra_opts
480 file_body = compose_full_body (body, opts)
481 basename = `abs(hash (file_body))`
483 m = re.search ('filename="(.*?)"', o)
485 basename = m.group (1)
486 if not taken_file_names.has_key(basename):
487 taken_file_names[basename] = 0
489 taken_file_names[basename] = taken_file_names[basename] + 1
490 basename = basename + "-%i" % taken_file_names[basename]
491 # writes the file if necessary, returns true if it was written
493 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
494 needed_filetypes = ['tex']
497 needed_filetypes.append('eps')
498 needed_filetypes.append('png')
499 if 'eps' in opts and not ('eps' in needed_filetypes):
500 needed_filetypes.append('eps')
501 outname = os.path.join(g_outdir, basename)
502 if not os.path.isfile(outname + '.tex') \
503 or os.stat(outname+'.ly')[stat.ST_MTIME] > \
504 os.stat(outname+'.tex')[stat.ST_MTIME]:
505 todo = needed_filetypes
510 if 'verbatim' in opts:
511 newbody = output_verbatim (body)
512 elif 'mbverbatim' in opts:
513 newbody = output_mbverbatim (complete_body)
516 m = re.search ('intertext="(.*?)"', o)
518 newbody = newbody + m.group (1)
519 if format == 'latex':
524 else: # format == 'texi'
526 newbody = newbody + get_output(s) % {'fn': basename }
527 return ('mudela', newbody, opts, todo, basename)
529 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
531 # Count sections/chapters.
534 c = schedule_mudela_block (c, global_options)
535 elif c[0] == 'numcols':
536 __main__.g_num_cols = c[2]
541 def find_eps_dims (match):
542 "Fill in dimensions of EPS files."
545 dims = bounding_box_dimensions (fn)
547 return '%ipt' % dims[0]
551 sys.stderr.write ("invoking `%s'\n" % cmd)
554 error ('Error command exited with value %d\n' % st)
557 def compile_all_files (chunks):
571 tex.append (base + '.ly')
572 elif e == 'png' and g_do_pictures:
578 lilyopts = map (lambda x: '-I ' + x, include_path)
579 lilyopts = string.join (lilyopts, ' ' )
580 texfiles = string.join (tex, ' ')
581 system ('lilypond %s %s' % (lilyopts, texfiles))
583 system(r"tex '\nonstopmode \input %s'" % e)
584 system(r"dvips -E -o %s %s" % (e + '.eps', e))
586 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
587 cmd = cmd % (g + '.eps', g + '.png')
593 def update_file (body, name):
595 write the body if it has changed
606 f = open (name , 'w')
613 def getopt_args (opts):
614 "Construct arguments (LONG, SHORT) for getopt from list of options."
629 def option_help_str (o):
630 "Transform one option description (4-tuple ) into neatly formatted string"
648 return ' ' + sh + sep + long + arg
651 def options_help_str (opts):
652 "Convert a list of options into a neatly formatted string"
658 s = option_help_str (o)
659 strs.append ((s, o[3]))
665 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
669 sys.stdout.write("""Usage: mudela-book [options] FILE\n
670 Generate hybrid LaTeX input from Latex + mudela
673 sys.stdout.write (options_help_str (option_definitions))
674 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
678 Report bugs to bug-gnu-music@gnu.org.
680 Written by Tom Cato Amundsen <tca@gnu.org> and
681 Han-Wen Nienhuys <hanwen@cs.uu.nl>
687 def write_deps (fn, target):
688 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
689 f = open (os.path.join(g_outdir, fn), 'w')
690 f.write ('%s%s: ' % (g_dep_prefix, target))
691 for d in __main__.read_files:
695 __main__.read_files = []
698 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
700 def print_version ():
702 sys.stdout.write (r"""Copyright 1998--1999
703 Distributed under terms of the GNU General Public License. It comes with
707 def do_file(input_filename):
712 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
713 my_depname = my_outname + '.dep'
715 chunks = read_doc_file(input_filename)
716 chunks = chop_chunks(chunks, 'mudela', make_mudela)
717 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file)
718 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block)
719 #for c in chunks: print c, "\n"
720 chunks = chop_chunks(chunks, 'ignore', do_ignore)
721 chunks = chop_chunks(chunks, 'numcols', do_columns)
722 global_options = scan_preamble(chunks[0][1])
723 chunks = process_mudela_blocks(my_outname, chunks, global_options)
725 if __main__.g_run_lilypond:
726 compile_all_files (chunks)
730 if c[0] == 'mudela' and 'eps' in c[2]:
731 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
732 newchunks.append (('mudela', body))
737 if chunks and chunks[0][0] == 'input':
738 chunks[0] = ('input', completize_preamble (chunks[0][1]))
740 foutn = os.path.join(g_outdir, my_outname + '.' + format)
741 sys.stderr.write ("Writing `%s'\n" % foutn)
742 fout = open (foutn, 'w')
748 write_deps (my_depname, foutn)
753 (sh, long) = getopt_args (__main__.option_definitions)
754 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
755 except getopt.error, msg:
756 sys.stderr.write("error: %s" % msg)
764 if o == '--include' or o == '-I':
765 include_path.append (a)
766 elif o == '--version' or o == '-v':
769 elif o == '--format' or o == '-f':
771 elif o == '--outname' or o == '-o':
774 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
777 elif o == '--help' or o == '-h':
779 elif o == '--no-lily' or o == '-n':
780 __main__.g_run_lilypond = 0
781 elif o == '--dependencies' or o == '-M':
783 elif o == '--default-music-fontsize':
784 default_music_fontsize = string.atoi (a)
785 elif o == '--default-mudela-fontsize':
786 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
787 default_music_fontsize = string.atoi (a)
788 elif o == '--force-music-fontsize':
789 g_force_mudela_fontsize = string.atoi(a)
790 elif o == '--force-mudela-fontsize':
791 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
792 g_force_mudela_fontsize = string.atoi(a)
793 elif o == '--dep-prefix':
795 elif o == '--no-pictures':
797 elif o == '--read-lys':
799 elif o == '--outdir':
804 if os.path.isfile(g_outdir):
805 error ("outdir is a file: %s" % g_outdir)
806 if not os.path.exists(g_outdir):
808 for input_filename in files:
809 do_file(input_filename)
812 # Petr, ik zou willen dat ik iets zinvoller deed,
813 # maar wat ik kan ik doen, het verandert toch niets?