2 # vim: set noexpandtab:
3 # TODO: Figure out clean set of options.
4 # add support for .lilyrc
16 program_version = '@TOPLEVEL_VERSION@'
17 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
18 program_version = '1.3.69-very-unstable'
20 include_path = [os.getcwd()]
24 g_force_mudela_fontsize = 0
32 default_music_fontsize = 16
33 default_text_fontsize = 12
36 # indices are no. of columns, papersize, fontsize
37 # Why can't this be calculated?
39 1: {'a4':{10: 345, 11: 360, 12: 390},
40 'a5':{10: 276, 11: 276, 12: 276},
41 'b5':{10: 345, 11: 356, 12: 356},
42 'letter':{10: 345, 11: 360, 12: 390},
43 'legal': {10: 345, 11: 360, 12: 390},
44 'executive':{10: 345, 11: 360, 12: 379}},
45 2: {'a4':{10: 167, 11: 175, 12: 190},
46 'a5':{10: 133, 11: 133, 12: 133},
47 'b5':{10: 167, 11: 173, 12: 173},
48 'letter':{10: 167, 11: 175, 12: 190},
49 'legal':{10: 167, 11: 175, 12: 190},
50 'executive':{10: 167, 11: 175, 12: 184}}}
55 'smallbook': {12: 361},
56 'texidefault': {12: 433}}
59 def get_linewidth(cols, paper, fontsize):
60 if __main__.format == 'latex':
61 return latex_linewidths[cols][paper][fontsize]
62 elif __main__.format == 'texi':
63 return texi_linewidths[paper][fontsize]
66 option_definitions = [
67 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
68 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
69 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
70 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
71 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
72 ('DIR', 'I', 'include', 'include path'),
73 ('', 'M', 'dependencies', 'write dependencies'),
74 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
75 ('', 'n', 'no-lily', 'don\'t run lilypond'),
76 ('', '', 'no-pictures', "don\'t generate pictures"),
77 ('', '', 'read-lys', "don't write ly files."),
78 ('FILE', 'o', 'outname', 'filename main output file'),
79 ('FILE', '', 'outdir', "where to place generated files"),
80 ('', 'v', 'version', 'print version information' ),
81 ('', 'h', 'help', 'print help'),
84 # format specific strings, ie. regex-es for input, and % strings for output
87 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
94 'output-mudela':r"""\begin[%s]{mudela}
97 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
98 'output-default-post': r"""\def\postMudelaExample{}""",
99 'output-default-pre': r"""\def\preMudelaExample{}""",
100 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
101 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
102 'pagebreak': r'\pagebreak',
104 'texi' : {'output-mudela': """@mudela[%s]
108 'output-mudela-fragment': """@mudela[%s]
109 \context Staff\context Voice{ %s }
112 'output-verbatim': r"""@example
117 # do some tweaking: @ is needed in some ps stuff.
118 # override EndLilyPondOutput, since @tex is done
119 # in a sandbox, you can't do \input lilyponddefs at the
120 # top of the document.
122 # should also support fragment in
124 'output-all': r"""@tex
127 \def\EndLilyPondOutput{}
139 def output_verbatim (body):#ugh .format
140 if __main__.format == 'texi':
141 body = re.sub ('([@{}])', '@\\1', body)
142 return get_output ('output-verbatim') % body
144 def output_mbverbatim (body):#ugh .format
145 if __main__.format == 'texi':
146 body = re.sub ('([@{}])', '@\\1', body)
147 return get_output ('output-verbatim') % body
150 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
151 'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
153 'option-sep' : ', *',
154 'header': r"""\\documentclass(\[.*?\])?""",
155 'preamble-end': '\\\\begin{document}',
156 'verbatim': r"""(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})""",
157 'verb': r"""(?P<code>\\verb(?P<del>.).*?(?P=del))""",
158 'mudela-file': r'\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)}',
159 'mudela' : '(?m)\\\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
160 #'mudela-block': r"""(?m)^[^%]*?\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
161 'mudela-block': r"""(?s)\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
162 'def-post-re': r"""\\def\\postMudelaExample""",
163 'def-pre-re': r"""\\def\\preMudelaExample""",
164 'intertext': r',?\s*intertext=\".*?\"',
165 'ignore': r"(?m)(?P<code>%.*?^)",
166 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
170 'include': '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
173 'preamble-end': no_match,
174 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
175 'verb': r"""(?P<code>@code{.*?})""",
176 'mudela-file': '@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)}',
177 'mudela' : '@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
178 'mudela-block': r"""(?s)@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s""",
179 'option-sep' : ', *',
180 'intertext': r',?\s*intertext=\".*?\"',
181 'ignore': r"(?s)(?P<code>@ignore\s.*?@end ignore)\s",
187 for r in re_dict.keys ():
190 for k in olddict.keys ():
191 newdict[k] = re.compile (olddict[k])
205 def get_output (name):
206 return output_dict[format][name]
209 return re_dict[format][name]
211 def bounding_box_dimensions(fname):
215 error ("Error opening `%s'" % fname)
217 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
219 return (int(s.group(3))-int(s.group(1)),
220 int(s.group(4))-int(s.group(2)))
226 sys.stderr.write (str + "\n Exiting ... \n\n")
230 def compose_full_body (body, opts):
231 """Construct the mudela code to send to Lilypond.
232 Add stuff to BODY using OPTS as options."""
233 if __main__.format == 'texi':
234 paper = 'texidefault'
236 paper = 'letter' # yes, latex use letter as default, at least
238 music_size = default_music_fontsize
239 latex_size = default_text_fontsize
241 m = re.search ('^(.*)paper$', o)
245 if g_force_mudela_fontsize:
246 music_size = g_force_mudela_fontsize
248 m = re.match ('([0-9]+)pt', o)
250 music_size = string.atoi(m.group (1))
252 m = re.match ('latexfontsize=([0-9]+)pt', o)
254 latex_size = string.atoi (m.group (1))
256 if re.search ('\\\\score', body):
260 if 'fragment' in opts:
262 if 'nonfragment' in opts:
265 if is_fragment and not 'multiline' in opts:
266 opts.append('singleline')
267 if 'singleline' in opts:
270 l = get_linewidth(g_num_cols, paper, latex_size)
272 if 'relative' in opts:#ugh only when is_fragment
273 body = '\\relative c { %s }' % body
282 optstring = string.join (opts, ' ')
283 optstring = re.sub ('\n', ' ', optstring)
286 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
287 \include "paper%d.ly"
288 \paper { linewidth = %f \pt; }
289 """ % (optstring, music_size, l) + body
293 def scan_preamble (str):
295 if __main__.format == 'texi':
297 if string.find(str[:x], "@afourpaper") != -1:
298 options = ['a4paper']
299 elif string.find(str[:x], "@afourwide") != -1:
300 options = ['a4widepaper']
301 elif string.find(str[:x], "@smallbook") != -1:
302 options = ['smallbookpaper']
303 m = get_re ('header').search( str)
304 # should extract paper & fontsz.
305 if m and m.group (1):
306 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
308 def verbose_fontsize ( x):
309 if re.match('[0-9]+pt', x):
310 return 'latexfontsize=' + x
314 options = map (verbose_fontsize, options)
318 def completize_preamble (str):
319 m = get_re ('preamble-end').search( str)
323 preamble = str [:m.start (0)]
324 str = str [m.start(0):]
326 if not get_re('def-post-re').search (preamble):
327 preamble = preamble + get_output('output-default-post')
328 if not get_re ('def-pre-re').search( preamble):
329 preamble = preamble + get_output ('output-default-pre')
332 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
334 preamble = preamble + '\\usepackage{graphics}\n'
336 return preamble + str
340 def find_file (name):
342 for a in include_path:
344 nm = os.path.join (a, name)
346 __main__.read_files.append (nm)
353 error ("File not found `%s'\n" % name)
356 def do_ignore(match_object):
357 return [('ignore', match_object.group('code'))]
359 def make_verbatim(match_object):
360 return [('verbatim', match_object.group('code'))]
362 def make_verb(match_object):
363 return [('verb', match_object.group('code'))]
365 def do_include_file(m):
367 return [('input', get_output ('pagebreak'))] \
368 + read_doc_file(m.group('filename')) \
369 + [('input', get_output ('pagebreak'))]
371 def do_input_file(m):
372 return read_doc_file(m.group('filename'))
375 if m.group('options'):
376 options = m.group('options')
379 return [('input', get_output('output-mudela-fragment') %
380 (options, m.group('code')))]
382 def make_mudela_file(m):
383 if m.group('options'):
384 options = m.group('options')
387 return [('input', get_output('output-mudela') %
388 (options, find_file(m.group('filename'))))]
390 def make_mudela_block(m):
391 if m.group('options'):
392 options = get_re('option-sep').split (m.group('options'))
395 options = filter(lambda s: s != '', options)
396 if 'mbverbatim' in options:#ugh this is ugly and only for texi format
398 im = get_re('intertext').search(s)
400 s = s[:im.start()] + s[im.end():]
401 im = re.search('mbverbatim', s)
403 s = s[:im.start()] + s[im.end():]
404 if s[:9] == "@mudela[]":
405 s = "@mudela" + s[9:]
406 return [('mudela', m.group('code'), options, s)]
407 return [('mudela', m.group('code'), options)]
410 if __main__.format != 'latex':
412 if m.group('num') == 'one':
413 return [('numcols', m.group('code'), 1)]
414 if m.group('num') == 'two':
415 return [('numcols', m.group('code'), 2)]
417 def chop_chunks(chunks, re_name, func):
423 m = get_re (re_name).search (str)
425 newchunks.append (('input', str))
428 newchunks.append (('input', str[:m.start (0)]))
429 #newchunks.extend(func(m))
430 # python 1.5 compatible:
431 newchunks = newchunks + func(m)
432 str = str [m.end(0):]
437 def read_doc_file (filename):
438 """Read the input file, find verbatim chunks and do \input and \include
441 str = find_file(filename)
443 if __main__.format == '':
444 latex = re.search ('\\\\document', str[:200])
445 texinfo = re.search ('@node|@setfilename', str[:200])
446 if (texinfo and latex) or not (texinfo or latex):
447 error("error: can't determine format, please specify")
449 __main__.format = 'texi'
451 __main__.format = 'latex'
452 chunks = [('input', str)]
453 # we have to check for verbatim before doing include,
454 # because we don't want to include files that are mentioned
455 # inside a verbatim environment
456 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
457 chunks = chop_chunks(chunks, 'verb', make_verb)
459 chunks = chop_chunks(chunks, 'include', do_include_file)
460 chunks = chop_chunks(chunks, 'input', do_input_file)
464 taken_file_names = {}
465 def schedule_mudela_block (chunk, extra_opts):
466 """Take the body and options from CHUNK, figure out how the
467 real .ly should look, and what should be left MAIN_STR (meant
468 for the main file). The .ly is written, and scheduled in
471 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
473 TODO has format [basename, extension, extension, ... ]
477 (type, body, opts) = chunk
480 (type, body, opts, complete_body) = chunk
481 assert type == 'mudela'
482 opts = opts + extra_opts
483 file_body = compose_full_body (body, opts)
484 basename = `abs(hash (file_body))`
486 m = re.search ('filename="(.*?)"', o)
488 basename = m.group (1)
489 if not taken_file_names.has_key(basename):
490 taken_file_names[basename] = 0
492 taken_file_names[basename] = taken_file_names[basename] + 1
493 basename = basename + "-%i" % taken_file_names[basename]
494 # writes the file if necessary, returns true if it was written
496 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
497 needed_filetypes = ['tex']
500 needed_filetypes.append('eps')
501 needed_filetypes.append('png')
502 if 'eps' in opts and not ('eps' in needed_filetypes):
503 needed_filetypes.append('eps')
504 outname = os.path.join(g_outdir, basename)
505 if not os.path.isfile(outname + '.tex') \
506 or os.stat(outname+'.ly')[stat.ST_MTIME] > \
507 os.stat(outname+'.tex')[stat.ST_MTIME]:
508 todo = needed_filetypes
513 if 'verbatim' in opts:
514 newbody = output_verbatim (body)
515 elif 'mbverbatim' in opts:
516 newbody = output_mbverbatim (complete_body)
519 m = re.search ('intertext="(.*?)"', o)
521 newbody = newbody + m.group (1)
522 if format == 'latex':
527 else: # format == 'texi'
529 newbody = newbody + get_output(s) % {'fn': basename }
530 return ('mudela', newbody, opts, todo, basename)
532 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
534 # Count sections/chapters.
537 c = schedule_mudela_block (c, global_options)
538 elif c[0] == 'numcols':
539 __main__.g_num_cols = c[2]
544 def find_eps_dims (match):
545 "Fill in dimensions of EPS files."
548 dims = bounding_box_dimensions (fn)
550 return '%ipt' % dims[0]
554 sys.stderr.write ("invoking `%s'\n" % cmd)
557 error ('Error command exited with value %d\n' % st)
560 def compile_all_files (chunks):
574 tex.append (base + '.ly')
575 elif e == 'png' and g_do_pictures:
581 lilyopts = map (lambda x: '-I ' + x, include_path)
582 lilyopts = string.join (lilyopts, ' ' )
583 texfiles = string.join (tex, ' ')
584 system ('lilypond %s %s' % (lilyopts, texfiles))
586 system(r"tex '\nonstopmode \input %s'" % e)
587 system(r"dvips -E -o %s %s" % (e + '.eps', e))
589 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
590 cmd = cmd % (g + '.eps', g + '.png')
596 def update_file (body, name):
598 write the body if it has changed
609 f = open (name , 'w')
616 def getopt_args (opts):
617 "Construct arguments (LONG, SHORT) for getopt from list of options."
632 def option_help_str (o):
633 "Transform one option description (4-tuple ) into neatly formatted string"
651 return ' ' + sh + sep + long + arg
654 def options_help_str (opts):
655 "Convert a list of options into a neatly formatted string"
661 s = option_help_str (o)
662 strs.append ((s, o[3]))
668 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
672 sys.stdout.write("""Usage: mudela-book [options] FILE\n
673 Generate hybrid LaTeX input from Latex + mudela
676 sys.stdout.write (options_help_str (option_definitions))
677 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
681 Report bugs to bug-gnu-music@gnu.org.
683 Written by Tom Cato Amundsen <tca@gnu.org> and
684 Han-Wen Nienhuys <hanwen@cs.uu.nl>
690 def write_deps (fn, target):
691 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
692 f = open (os.path.join(g_outdir, fn), 'w')
693 f.write ('%s%s: ' % (g_dep_prefix, target))
694 for d in __main__.read_files:
698 __main__.read_files = []
701 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
703 def print_version ():
705 sys.stdout.write (r"""Copyright 1998--1999
706 Distributed under terms of the GNU General Public License. It comes with
710 def do_file(input_filename):
715 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
716 my_depname = my_outname + '.dep'
718 chunks = read_doc_file(input_filename)
719 chunks = chop_chunks(chunks, 'mudela', make_mudela)
720 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file)
721 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block)
722 #for c in chunks: print c, "\n"
723 chunks = chop_chunks(chunks, 'ignore', do_ignore)
724 chunks = chop_chunks(chunks, 'numcols', do_columns)
725 global_options = scan_preamble(chunks[0][1])
726 chunks = process_mudela_blocks(my_outname, chunks, global_options)
728 if __main__.g_run_lilypond:
729 compile_all_files (chunks)
733 if c[0] == 'mudela' and 'eps' in c[2]:
734 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
735 newchunks.append (('mudela', body))
740 if chunks and chunks[0][0] == 'input':
741 chunks[0] = ('input', completize_preamble (chunks[0][1]))
743 foutn = os.path.join(g_outdir, my_outname + '.' + format)
744 sys.stderr.write ("Writing `%s'\n" % foutn)
745 fout = open (foutn, 'w')
751 write_deps (my_depname, foutn)
756 (sh, long) = getopt_args (__main__.option_definitions)
757 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
758 except getopt.error, msg:
759 sys.stderr.write("error: %s" % msg)
767 if o == '--include' or o == '-I':
768 include_path.append (a)
769 elif o == '--version' or o == '-v':
772 elif o == '--format' or o == '-f':
774 elif o == '--outname' or o == '-o':
777 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
780 elif o == '--help' or o == '-h':
782 elif o == '--no-lily' or o == '-n':
783 __main__.g_run_lilypond = 0
784 elif o == '--dependencies' or o == '-M':
786 elif o == '--default-music-fontsize':
787 default_music_fontsize = string.atoi (a)
788 elif o == '--default-mudela-fontsize':
789 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
790 default_music_fontsize = string.atoi (a)
791 elif o == '--force-music-fontsize':
792 g_force_mudela_fontsize = string.atoi(a)
793 elif o == '--force-mudela-fontsize':
794 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
795 g_force_mudela_fontsize = string.atoi(a)
796 elif o == '--dep-prefix':
798 elif o == '--no-pictures':
800 elif o == '--read-lys':
802 elif o == '--outdir':
807 if os.path.isfile(g_outdir):
808 error ("outdir is a file: %s" % g_outdir)
809 if not os.path.exists(g_outdir):
811 for input_filename in files:
812 do_file(input_filename)
815 # Petr, ik zou willen dat ik iets zinvoller deed,
816 # maar wat ik kan ik doen, het verandert toch niets?