2 # vim: set noexpandtab:
4 # * Figure out clean set of options.
5 # * add support for .lilyrc
6 # * %\def\preMudelaExample should be ignored by mudela-book because
8 # * if you run mudela-book once with --no-pictures, and then again
9 # without the option, then the pngs will not be created. You have
10 # to delete the generated .ly files and rerun mudela-book.
22 program_version = '@TOPLEVEL_VERSION@'
23 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
24 program_version = '1.3.85'
26 include_path = [os.getcwd()]
30 g_force_mudela_fontsize = 0
38 default_music_fontsize = 16
39 default_text_fontsize = 12
42 # indices are no. of columns, papersize, fontsize
43 # Why can't this be calculated?
45 1: { 'a4':{10: 345, 11: 360, 12: 390},
46 'a4-landscape': {10: 598, 11: 596, 12:592},
47 'a5':{10: 276, 11: 276, 12: 276},
48 'b5':{10: 345, 11: 356, 12: 356},
49 'letter':{10: 345, 11: 360, 12: 390},
50 'letter-landscape':{10: 598, 11: 596, 12:596},
51 'legal': {10: 345, 11: 360, 12: 390},
52 'executive':{10: 345, 11: 360, 12: 379}},
53 2: { 'a4':{10: 167, 11: 175, 12: 190},
54 'a4-landscape': {10: 291, 11: 291, 12: 291},
55 'a5':{10: 133, 11: 133, 12: 133},
56 'b5':{10: 167, 11: 173, 12: 173},
57 'letter':{10: 167, 11: 175, 12: 190},
58 'letter-landscape':{10: 270, 11: 267, 12: 269},
59 'legal':{10: 167, 11: 175, 12: 190},
60 'executive':{10: 167, 11: 175, 12: 184}}}
65 'smallbook': {12: 361},
66 'texidefault': {12: 433}}
69 def get_linewidth(cols, paper, fontsize):
70 if __main__.format == 'latex':
71 return latex_linewidths[cols][paper][fontsize]
72 elif __main__.format == 'texi':
73 return texi_linewidths[paper][fontsize]
76 option_definitions = [
77 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
78 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
79 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
80 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
81 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
82 ('DIR', 'I', 'include', 'include path'),
83 ('', 'M', 'dependencies', 'write dependencies'),
84 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
85 ('', 'n', 'no-lily', 'don\'t run lilypond'),
86 ('', '', 'no-pictures', "don\'t generate pictures"),
87 ('', '', 'read-lys', "don't write ly files."),
88 ('FILE', 'o', 'outname', 'filename main output file'),
89 ('FILE', '', 'outdir', "where to place generated files"),
90 ('', 'v', 'version', 'print version information' ),
91 ('', 'h', 'help', 'print help'),
94 # format specific strings, ie. regex-es for input, and % strings for output
97 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
104 'output-mudela':r"""\begin[%s]{mudela}
107 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
108 'output-default-post': r"""\def\postMudelaExample{}""",
109 'output-default-pre': r"""\def\preMudelaExample{}""",
110 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
111 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
112 'pagebreak': r'\pagebreak',
114 'texi' : {'output-mudela': """@mudela[%s]
118 'output-mudela-fragment': """@mudela[%s]
119 \context Staff\context Voice{ %s }
122 'output-verbatim': r"""@example
127 # do some tweaking: @ is needed in some ps stuff.
128 # override EndLilyPondOutput, since @tex is done
129 # in a sandbox, you can't do \input lilyponddefs at the
130 # top of the document.
132 # should also support fragment in
134 'output-all': r"""@tex
137 \def\EndLilyPondOutput{}
149 def output_verbatim (body):#ugh .format
150 if __main__.format == 'texi':
151 body = re.sub ('([@{}])', '@\\1', body)
152 return get_output ('output-verbatim') % body
154 def output_mbverbatim (body):#ugh .format
155 if __main__.format == 'texi':
156 body = re.sub ('([@{}])', '@\\1', body)
157 return get_output ('output-verbatim') % body
160 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
161 'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
163 'option-sep' : ', *',
164 'header': r"""\\documentclass(\[.*?\])?""",
165 # ^(?m)[^%]* is here so we can comment it out
166 'landscape': r"^(?m)[^%]*\\usepackage{landscape}",
167 'preamble-end': '\\\\begin{document}',
168 'verbatim': r"""(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})""",
169 'verb': r"""(?P<code>\\verb(?P<del>.).*?(?P=del))""",
170 'mudela-file': r'\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)}',
171 'mudela' : '(?m)^[^%]*?\\\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
172 #'mudela-block': r"""(?m)^[^%]*?\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
173 'mudela-block': r"""(?s)\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
174 'def-post-re': r"""\\def\\postMudelaExample""",
175 'def-pre-re': r"""\\def\\preMudelaExample""",
176 'intertext': r',?\s*intertext=\".*?\"',
177 #'ignore': r"(?m)(?P<code>%.*?^)",
178 'ignore': r"(?m)(?P<code>^%.*)$",
179 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
183 'include': '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
185 'landscape': no_match,
187 'preamble-end': no_match,
188 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
189 'verb': r"""(?P<code>@code{.*?})""",
190 'mudela-file': '@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)}',
191 'mudela' : '@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
192 'mudela-block': r"""(?s)@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s""",
193 'option-sep' : ', *',
194 'intertext': r',?\s*intertext=\".*?\"',
195 'ignore': r"(?s)(?P<code>@ignore\s.*?@end ignore)\s",
201 for r in re_dict.keys ():
204 for k in olddict.keys ():
205 newdict[k] = re.compile (olddict[k])
219 def get_output (name):
220 return output_dict[format][name]
223 return re_dict[format][name]
225 def bounding_box_dimensions(fname):
229 error ("Error opening `%s'" % fname)
231 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
233 return (int(s.group(3))-int(s.group(1)),
234 int(s.group(4))-int(s.group(2)))
240 sys.stderr.write (str + "\n Exiting ... \n\n")
244 def compose_full_body (body, opts):
245 """Construct the mudela code to send to Lilypond.
246 Add stuff to BODY using OPTS as options."""
247 if __main__.format == 'texi':
248 paper = 'texidefault'
250 paper = 'letter' # yes, latex use letter as default, at least
252 if 'landscape' in opts:
253 paper = paper + '-' + 'landscape'
254 music_size = default_music_fontsize
255 latex_size = default_text_fontsize
257 m = re.search ('^(.*)paper$', o)
260 if 'landscape' in opts:
261 paper = paper + '-' + 'landscape'
263 if g_force_mudela_fontsize:
264 music_size = g_force_mudela_fontsize
266 m = re.match ('([0-9]+)pt', o)
268 music_size = string.atoi(m.group (1))
270 m = re.match ('latexfontsize=([0-9]+)pt', o)
272 latex_size = string.atoi (m.group (1))
274 if re.search ('\\\\score', body):
278 if 'fragment' in opts:
280 if 'nonfragment' in opts:
283 if is_fragment and not 'multiline' in opts:
284 opts.append('singleline')
285 if 'singleline' in opts:
288 l = get_linewidth(g_num_cols, paper, latex_size)
290 if 'relative' in opts:#ugh only when is_fragment
291 body = '\\relative c { %s }' % body
300 optstring = string.join (opts, ' ')
301 optstring = re.sub ('\n', ' ', optstring)
304 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
305 \include "paper%d.ly"
306 \paper { linewidth = %f \pt; }
307 """ % (optstring, music_size, l) + body
311 def scan_preamble (str):
313 if __main__.format == 'texi':
315 if string.find(str[:x], "@afourpaper") != -1:
316 options = ['a4paper']
317 elif string.find(str[:x], "@afourwide") != -1:
318 options = ['a4widepaper']
319 elif string.find(str[:x], "@smallbook") != -1:
320 options = ['smallbookpaper']
321 m = get_re ('landscape').search(str)
323 options.append('landscape')
324 m = get_re ('header').search( str)
325 # should extract paper & fontsz.
326 if m and m.group (1):
327 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
329 def verbose_fontsize ( x):
330 if re.match('[0-9]+pt', x):
331 return 'latexfontsize=' + x
335 options = map (verbose_fontsize, options)
339 def completize_preamble (str):
340 m = get_re ('preamble-end').search( str)
344 preamble = str [:m.start (0)]
345 str = str [m.start(0):]
347 if not get_re('def-post-re').search (preamble):
348 preamble = preamble + get_output('output-default-post')
349 if not get_re ('def-pre-re').search( preamble):
350 preamble = preamble + get_output ('output-default-pre')
353 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
355 preamble = preamble + '\\usepackage{graphics}\n'
357 return preamble + str
361 def find_file (name):
363 for a in include_path:
365 nm = os.path.join (a, name)
367 __main__.read_files.append (nm)
374 error ("File not found `%s'\n" % name)
377 def do_ignore(match_object):
378 return [('ignore', match_object.group('code'))]
380 def make_verbatim(match_object):
381 return [('verbatim', match_object.group('code'))]
383 def make_verb(match_object):
384 return [('verb', match_object.group('code'))]
386 def do_include_file(m):
388 return [('input', get_output ('pagebreak'))] \
389 + read_doc_file(m.group('filename')) \
390 + [('input', get_output ('pagebreak'))]
392 def do_input_file(m):
393 return read_doc_file(m.group('filename'))
396 if m.group('options'):
397 options = m.group('options')
400 return [('input', get_output('output-mudela-fragment') %
401 (options, m.group('code')))]
403 def make_mudela_file(m):
404 if m.group('options'):
405 options = m.group('options')
408 return [('input', get_output('output-mudela') %
409 (options, find_file(m.group('filename'))))]
411 def make_mudela_block(m):
412 if m.group('options'):
413 options = get_re('option-sep').split (m.group('options'))
416 options = filter(lambda s: s != '', options)
417 if 'mbverbatim' in options:#ugh this is ugly and only for texi format
419 im = get_re('intertext').search(s)
421 s = s[:im.start()] + s[im.end():]
422 im = re.search('mbverbatim', s)
424 s = s[:im.start()] + s[im.end():]
425 if s[:9] == "@mudela[]":
426 s = "@mudela" + s[9:]
427 return [('mudela', m.group('code'), options, s)]
428 return [('mudela', m.group('code'), options)]
431 if __main__.format != 'latex':
433 if m.group('num') == 'one':
434 return [('numcols', m.group('code'), 1)]
435 if m.group('num') == 'two':
436 return [('numcols', m.group('code'), 2)]
438 def chop_chunks(chunks, re_name, func):
444 m = get_re (re_name).search (str)
446 newchunks.append (('input', str))
449 newchunks.append (('input', str[:m.start (0)]))
450 #newchunks.extend(func(m))
451 # python 1.5 compatible:
452 newchunks = newchunks + func(m)
453 str = str [m.end(0):]
458 def read_doc_file (filename):
459 """Read the input file, find verbatim chunks and do \input and \include
462 str = find_file(filename)
464 if __main__.format == '':
465 latex = re.search ('\\\\document', str[:200])
466 texinfo = re.search ('@node|@setfilename', str[:200])
467 if (texinfo and latex) or not (texinfo or latex):
468 error("error: can't determine format, please specify")
470 __main__.format = 'texi'
472 __main__.format = 'latex'
473 chunks = [('input', str)]
474 # we have to check for verbatim before doing include,
475 # because we don't want to include files that are mentioned
476 # inside a verbatim environment
477 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
478 chunks = chop_chunks(chunks, 'verb', make_verb)
480 chunks = chop_chunks(chunks, 'include', do_include_file)
481 chunks = chop_chunks(chunks, 'input', do_input_file)
485 taken_file_names = {}
486 def schedule_mudela_block (chunk, extra_opts):
487 """Take the body and options from CHUNK, figure out how the
488 real .ly should look, and what should be left MAIN_STR (meant
489 for the main file). The .ly is written, and scheduled in
492 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
494 TODO has format [basename, extension, extension, ... ]
497 #print "-schedule_mudela_block", extra_opts
499 (type, body, opts) = chunk
502 (type, body, opts, complete_body) = chunk
503 assert type == 'mudela'
504 opts = opts + extra_opts
505 file_body = compose_full_body (body, opts)
506 basename = `abs(hash (file_body))`
508 m = re.search ('filename="(.*?)"', o)
510 basename = m.group (1)
511 if not taken_file_names.has_key(basename):
512 taken_file_names[basename] = 0
514 taken_file_names[basename] = taken_file_names[basename] + 1
515 basename = basename + "-%i" % taken_file_names[basename]
516 # writes the file if necessary, returns true if it was written
518 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
519 needed_filetypes = ['tex']
522 needed_filetypes.append('eps')
523 needed_filetypes.append('png')
524 if 'eps' in opts and not ('eps' in needed_filetypes):
525 needed_filetypes.append('eps')
526 outname = os.path.join(g_outdir, basename)
527 if not os.path.isfile(outname + '.tex') \
528 or os.stat(outname+'.ly')[stat.ST_MTIME] > \
529 os.stat(outname+'.tex')[stat.ST_MTIME]:
530 todo = needed_filetypes
535 if 'verbatim' in opts:
536 newbody = output_verbatim (body)
537 elif 'mbverbatim' in opts:
538 newbody = output_mbverbatim (complete_body)
541 m = re.search ('intertext="(.*?)"', o)
543 newbody = newbody + m.group (1)
544 if format == 'latex':
549 else: # format == 'texi'
551 newbody = newbody + get_output(s) % {'fn': basename }
552 return ('mudela', newbody, opts, todo, basename)
554 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
556 # Count sections/chapters.
559 c = schedule_mudela_block (c, global_options)
560 elif c[0] == 'numcols':
561 __main__.g_num_cols = c[2]
566 def find_eps_dims (match):
567 "Fill in dimensions of EPS files."
570 dims = bounding_box_dimensions (fn)
572 return '%ipt' % dims[0]
576 sys.stderr.write ("invoking `%s'\n" % cmd)
579 error ('Error command exited with value %d\n' % st)
582 def compile_all_files (chunks):
597 if base + '.ly' not in tex:
598 tex.append (base + '.ly')
599 elif e == 'png' and g_do_pictures:
605 lilyopts = map (lambda x: '-I ' + x, include_path)
606 lilyopts = string.join (lilyopts, ' ' )
607 texfiles = string.join (tex, ' ')
608 system ('lilypond %s %s' % (lilyopts, texfiles))
610 system(r"tex '\nonstopmode \input %s'" % e)
611 system(r"dvips -E -o %s %s" % (e + '.eps', e))
613 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
614 cmd = cmd % (g + '.eps', g + '.png')
620 def update_file (body, name):
622 write the body if it has changed
633 f = open (name , 'w')
640 def getopt_args (opts):
641 "Construct arguments (LONG, SHORT) for getopt from list of options."
656 def option_help_str (o):
657 "Transform one option description (4-tuple ) into neatly formatted string"
675 return ' ' + sh + sep + long + arg
678 def options_help_str (opts):
679 "Convert a list of options into a neatly formatted string"
685 s = option_help_str (o)
686 strs.append ((s, o[3]))
692 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
696 sys.stdout.write("""Usage: mudela-book [options] FILE\n
697 Generate hybrid LaTeX input from Latex + mudela
700 sys.stdout.write (options_help_str (option_definitions))
701 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
705 Report bugs to bug-gnu-music@gnu.org.
707 Written by Tom Cato Amundsen <tca@gnu.org> and
708 Han-Wen Nienhuys <hanwen@cs.uu.nl>
714 def write_deps (fn, target):
715 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
716 f = open (os.path.join(g_outdir, fn), 'w')
717 f.write ('%s%s: ' % (g_dep_prefix, target))
718 for d in __main__.read_files:
722 __main__.read_files = []
725 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
727 def print_version ():
729 sys.stdout.write (r"""Copyright 1998--1999
730 Distributed under terms of the GNU General Public License. It comes with
734 def do_file(input_filename):
739 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
740 my_depname = my_outname + '.dep'
742 chunks = read_doc_file(input_filename)
743 chunks = chop_chunks(chunks, 'mudela', make_mudela)
744 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file)
745 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block)
746 chunks = chop_chunks(chunks, 'ignore', do_ignore)
747 #for c in chunks: print "c:", c
748 chunks = chop_chunks(chunks, 'numcols', do_columns)
749 global_options = scan_preamble(chunks[0][1])
750 chunks = process_mudela_blocks(my_outname, chunks, global_options)
752 if __main__.g_run_lilypond:
753 compile_all_files (chunks)
757 if c[0] == 'mudela' and 'eps' in c[2]:
758 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
759 newchunks.append (('mudela', body))
764 if chunks and chunks[0][0] == 'input':
765 chunks[0] = ('input', completize_preamble (chunks[0][1]))
767 foutn = os.path.join(g_outdir, my_outname + '.' + format)
768 sys.stderr.write ("Writing `%s'\n" % foutn)
769 fout = open (foutn, 'w')
775 write_deps (my_depname, foutn)
780 (sh, long) = getopt_args (__main__.option_definitions)
781 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
782 except getopt.error, msg:
783 sys.stderr.write("error: %s" % msg)
791 if o == '--include' or o == '-I':
792 include_path.append (a)
793 elif o == '--version' or o == '-v':
796 elif o == '--format' or o == '-f':
798 elif o == '--outname' or o == '-o':
801 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
804 elif o == '--help' or o == '-h':
806 elif o == '--no-lily' or o == '-n':
807 __main__.g_run_lilypond = 0
808 elif o == '--dependencies' or o == '-M':
810 elif o == '--default-music-fontsize':
811 default_music_fontsize = string.atoi (a)
812 elif o == '--default-mudela-fontsize':
813 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
814 default_music_fontsize = string.atoi (a)
815 elif o == '--force-music-fontsize':
816 g_force_mudela_fontsize = string.atoi(a)
817 elif o == '--force-mudela-fontsize':
818 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
819 g_force_mudela_fontsize = string.atoi(a)
820 elif o == '--dep-prefix':
822 elif o == '--no-pictures':
824 elif o == '--read-lys':
826 elif o == '--outdir':
831 if os.path.isfile(g_outdir):
832 error ("outdir is a file: %s" % g_outdir)
833 if not os.path.exists(g_outdir):
835 for input_filename in files:
836 do_file(input_filename)
839 # Petr, ik zou willen dat ik iets zinvoller deed,
840 # maar wat ik kan ik doen, het verandert toch niets?