12 program_version = '@TOPLEVEL_VERSION@'
18 # TODO: Figure out clean set of options.
20 # BUG: does not handle \verb|\begin{verbatim}\end{verbatim}| correctly.
21 # Should make a joint RE for \verb and \begin, \end{verbatim}
24 default_music_fontsize = 16
25 default_text_fontsize = 12
28 # indices are no. of columns, papersize, fontsize
29 # Why can't this be calculated?
31 1: {'a4':{10: 345, 11: 360, 12: 390},
32 'a5':{10: 276, 11: 276, 12: 276},
33 'b5':{10: 345, 11: 356, 12: 356},
34 'letter':{10: 345, 11: 360, 12: 390},
35 'legal': {10: 345, 11: 360, 12: 390},
36 'executive':{10: 345, 11: 360, 12: 379}},
37 2: {'a4':{10: 167, 11: 175, 12: 190},
38 'a5':{10: 133, 11: 133, 12: 133},
39 'b5':{10: 167, 11: 173, 12: 173},
40 'letter':{10: 167, 11: 175, 12: 190},
41 'legal':{10: 167, 11: 175, 12: 190},
42 'executive':{10: 167, 11: 175, 12: 184}}}
45 option_definitions = [
46 ('DIM', '', 'default-mudela-fontsize', 'default fontsize for music. DIM is assumed to in points'),
47 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
48 ('', 'h', 'help', 'print help'),
49 ('DIR', 'I', 'include', 'include path'),
50 ('', '', 'init', 'mudela-book initfile'),
51 # ('DIM', '', 'force-mudela-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
52 ('', '', 'force-verbatim', 'make all mudela verbatim'),
53 ('', 'M', 'dependencies', 'write dependencies'),
54 ('', 'n', 'no-lily', 'don\'t run lilypond'),
55 ('', '', 'no-pictures', "don\'t generate pictures"),
56 ('FILE', 'o', 'outname', 'prefix for filenames'),
57 ('', 'v', 'version', 'print version information' ),
58 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency')
67 # format specific strings, ie. regex-es for input, and % strings for output
70 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
77 'output-mudela':r"""\begin[%s]{mudela}
80 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
81 'output-default-post': r"""\def\postMudelaExample{}""",
82 'output-default-pre': r"""\def\preMudelaExample{}""",
83 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
84 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
86 'texi' : {'output-mudela': """@mudela[%s]
90 'output-verbatim': r"""@example
95 # do some tweaking: @ is needed in some ps stuff.
96 # override EndLilyPondOutput, since @tex is done
97 # in a sandbox, you can't do \input lilyponddefs at the
98 # top of the document.
99 'output-all': r"""@tex
102 \def\EndLilyPondOutput{}
113 def output_verbatim (body):
114 if __main__.format == 'texi':
115 body = re.sub ('([@{}])', '@\\1', body)
116 return get_output ('output-verbatim') % body
119 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
120 'include': '\\\\mbinclude{([^}]+)}',
123 'header': r"""\\documentclass(\[.*?\])?""",
124 'preamble-end': '\\\\begin{document}',
125 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
126 'verb': r"""\\verb(.)(.*?)\1""",
127 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
128 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
129 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
130 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
131 'def-post-re': r"""\\def\\postMudelaExample""",
132 'def-pre-re': r"""\\def\\preMudelaExample""",
136 'input': '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
139 'preamble-end': no_match,
140 'verbatim': r"""(?s)@example(.*?)@end example$""",
141 'verb': r"""@code{(.*?)}""",
142 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
143 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
144 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
145 'interesting-cs': r"""[\\@](chapter|section)""",
151 for r in re_dict.keys ():
154 for k in olddict.keys ():
155 newdict[k] = re.compile (olddict[k])
169 def get_output (name):
170 return output_dict[format][name]
173 return re_dict[format][name]
176 def bounding_box_dimensions(fname):
180 error ("Error opening `%s'" % fname)
182 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
184 return (int(s.group(3))-int(s.group(1)),
185 int(s.group(4))-int(s.group(2)))
192 def find_file (name):
194 for a in include_path:
196 nm = os.path.join (a, name)
198 __main__.read_files.append (nm)
207 error ("File not found `%s'\n" % name)
211 sys.stderr.write (str + "\n Exiting ... \n\n")
215 def compose_full_body (body, opts):
216 "Construct the text of an input file: add stuff to BODY using OPTS as options."
218 music_size = default_music_fontsize
219 latex_size = default_text_fontsize
223 m = re.search ('^(.*)paper$', o)
228 m = re.match ('([0-9]+)pt', o)
230 music_size = string.atoi(m.group (1))
232 m = re.match ('latexfontsize=([0-9]+)pt', o)
234 latex_size = string.atoi (m.group (1))
238 if 'twocolumn' in opts:
242 # urg: breaks on \include of full score
243 # Use nofly option if you want to \include full score.
244 if 'nofly' not in opts and not re.search ('\\\\score', body):
245 opts.append ('fragment')
247 if 'fragment' in opts and 'nosingleline' not in opts:
248 opts.append ('singleline')
250 if 'singleline' in opts:
253 l = latex_linewidths[cols][paper][latex_size]
256 if 'relative' in opts:
257 body = '\\relative c { %s }' % body
260 if 'fragment' in opts:
267 optstring = string.join (opts, ' ')
268 optstring = re.sub ('\n', ' ', optstring)
271 %% Generated by mudela-book.py; options are %s
272 \include "paper%d.ly"
273 \paper { linewidth = %f \pt; }
274 """ % (optstring, music_size, l) + body
278 def find_inclusion_chunks (regex, surround, str):
281 m = regex.search (str)
284 chunks.append (('input', str))
288 chunks.append (('input', str[: m.start (0)]))
289 chunks.append (('input', surround))
290 chunks = chunks + read_doc_file (m.group (1))
292 chunks.append (('input', surround))
294 str = str [m.end (0):]
297 def find_include_chunks (str):
298 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
300 def find_input_chunks (str):
301 return find_inclusion_chunks (get_re ('input'), '', str)
303 def read_doc_file (filename):
304 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
306 str = find_file(filename)
308 if __main__.format == '':
309 latex = re.search ('\\\\document', str[:200])
310 texinfo = re.search ('@node', str[:200])
311 if (texinfo and latex) or not (texinfo or latex):
312 error("error: can't determine format, please specify")
314 __main__.format = 'texi'
316 __main__.format = 'latex'
318 chunks = [('input', str)]
320 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
324 newchunks = newchunks + func (c[1])
333 def scan_preamble (str):
335 m = get_re ('header').search( str)
337 # should extract paper & fontsz.
338 if m and m.group (1):
339 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
341 def verbose_fontsize ( x):
343 #if o.match('[0-9]+pt'):
344 if re.match('[0-9]+pt', x):
345 return 'latexfontsize=' + x
349 options = map (verbose_fontsize, options)
354 def completize_preamble (str):
355 m = get_re ('preamble-end').search( str)
359 preamble = str [:m.start (0)]
360 str = str [m.start(0):]
362 if not get_re('def-post-re').search (preamble):
363 preamble = preamble + get_output('output-default-post')
364 if not get_re ('def-pre-re').search( preamble):
365 preamble = preamble + get_output ('output-default-pre')
368 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
370 preamble = preamble + '\\usepackage{graphics}\n'
372 return preamble + str
374 def find_verbatim_chunks (str):
375 """Chop STR into a list of tagged chunks, ie. tuples of form
376 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
382 m = get_re ('verbatim').search( str)
384 chunks.append( ('input', str))
387 chunks.append (('input', str[:m.start (0)]))
388 chunks.append (('verbatim', m.group (0)))
390 str = str [m.end(0):]
394 def find_verb_chunks (str):
398 m = get_re ("verb").search(str)
400 chunks.append (('input', str))
403 chunks.append (('input', str[:m.start (0)]))
404 chunks.append (('verbatim', m.group (0)))
405 str = str [m.end(0):]
411 def find_mudela_shorthand_chunks (str):
412 return [('input', find_mudela_shorthands(str))]
414 def find_mudela_shorthands (b):
415 def mudela_short (match):
416 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
417 opts = match.group (1)
419 opts = ',' + opts[1:-1]
422 return get_output ('output-mudela-fragment') % (opts, match.group (2))
424 def mudela_file (match):
425 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
428 opts = match.group (1)
431 opts = re.split (',[ \n\t]*', opts)
435 if re.search ('.fly$', fn):
437 elif re.search ('.sly$', fn):
438 opts = opts + [ 'fly','fragment']
439 elif re.search ('.ly$', fn):
440 opts .append ('nofly')
442 str_opts = string.join (opts, ',')
444 str = ("%% copied from file `%s'\n" % fn) + str
445 return get_output ('output-mudela') % (str_opts, str)
447 b = get_re('mudela-file').sub (mudela_file, b)
448 b = get_re('mudela').sub (mudela_short, b)
451 def find_mudela_chunks (str):
452 """Find mudela blocks, while watching for verbatim. Returns
453 (STR,MUDS) with substituted for the blocks in STR,
454 and the blocks themselves MUDS"""
458 m = get_re ("mudela-block").search( str)
460 chunks.append (('input', str))
464 chunks.append (('input', str[:m.start (0)]))
471 optlist = get_re('comma-sep').split (opts)
474 chunks.append (('mudela', body, optlist))
476 str = str [m.end (0):]
482 def advance_counters (counter, opts, str):
483 """Advance chap/sect counters,
484 revise OPTS. Return the new counter tuple"""
486 (chapter, section, count) = counter
489 m = get_re ('interesting-cs').search(str)
495 done = done + str[:m.end (0)]
500 opts.append ('twocolumn')
501 elif g == 'onecolumn':
503 opts.remove ('twocolumn')
507 (chapter, section, count) = (chapter + 1, 0, 0)
509 (section, count) = (section + 1, 0)
512 return (chapter, section, count)
515 def schedule_mudela_block (base, chunk, extra_opts):
516 """Take the body and options from CHUNK, figure out how the
517 real .ly should look, and what should be left MAIN_STR (meant
518 for the main file). The .ly is written, and scheduled in
521 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
523 TODO has format [basename, extension, extension, ... ]
527 (type, body, opts) = chunk
528 assert type == 'mudela'
529 opts = opts + extra_opts
532 if 'verbatim' in opts:
533 newbody = output_verbatim (body)
535 file_body = compose_full_body (body, opts)
537 if __main__.use_hash:
538 basename = `abs(hash (file_body))`
539 updated = update_file (file_body, basename + '.ly')
540 todo = [basename] # UGH.
542 if not os.path.isfile (basename + '.tex') or updated:
547 m = re.search ('intertext="(.*?)"', o)
549 newbody = newbody + m.group (1)
557 if 'eps' in opts and ('tex' in todo or
558 not os.path.isfile (basename + '.eps')):
561 if 'png' in opts and ('eps' in todo or
562 not os.path.isfile (basename + '.png')):
565 if format == 'latex':
567 newbody = newbody + get_output ('output-eps') % (basename, basename)
569 newbody = newbody + get_output ('output-tex') % basename
571 elif format == 'texi':
572 newbody = newbody + get_output ('output-all') % (basename, basename)
574 return ('mudela', newbody, opts, todo, base)
577 def find_eps_dims (match):
578 "Fill in dimensions of EPS files."
581 dims = bounding_box_dimensions (fn)
583 return '%ipt' % dims[0]
586 def print_chunks (ch):
588 print '-->%s\n%s' % (c[0], c[1])
590 print '==>%s' % list (c[2:])
594 def transform_input_file (in_filename, out_filename):
595 """Read the input, and deliver a list of chunks
600 chunks = read_doc_file (in_filename)
602 #. Process \mudela and \mudelafile.
603 for func in [find_mudela_shorthand_chunks,
608 newchunks = newchunks + func (c[1])
615 opts = scan_preamble (chunks[0][1])
617 (chap,sect,count) = (0,0,0)
619 # Count sections/chapters.
622 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
623 elif c[0] == 'mudela':
624 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
626 c = schedule_mudela_block (base, c, opts)
634 if __main__.run_lilypond:
635 compile_all_files (chunks)
639 if c[0] == 'mudela' and 'eps' in c[2]:
640 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
641 newchunks.append (('mudela', body))
646 if chunks and chunks[0][0] == 'input':
647 chunks[0] = ('input', completize_preamble (chunks[0][1]))
652 sys.stderr.write ("invoking `%s'\n" % cmd)
655 error ('Error command exited with value %d\n' % st)
658 def compile_all_files (chunks):
673 tex.append (base + '.ly')
674 elif e == 'png' and do_pictures:
677 if __main__.use_hash:
678 hash_dict[c[4]] = c[3][0]
681 lilyopts = map (lambda x: '-I ' + x, include_path)
682 lilyopts = string.join (lilyopts, ' ' )
683 texfiles = string.join (tex, ' ')
684 system ('lilypond %s %s' % (lilyopts, texfiles))
687 cmd = r"""tex '\nonstopmode \input %s'; dvips -E -o %s %s""" % \
692 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
694 cmd = cmd % (g + '.eps', g + '.png')
697 if __main__.use_hash:
701 ks = hash_dict.keys ()
704 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
706 if name != last_name:
711 f.write ("%s:%s\n" % (i, hash_dict[i]))
714 def update_file (body, name):
724 f = open (name , 'w')
733 def getopt_args (opts):
734 "Construct arguments (LONG, SHORT) for getopt from list of options."
749 def option_help_str (o):
750 "Transform one option description (4-tuple ) into neatly formatted string"
768 return ' ' + sh + sep + long + arg
771 def options_help_str (opts):
772 "Convert a list of options into a neatly formatted string"
778 s = option_help_str (o)
779 strs.append ((s, o[3]))
785 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
789 sys.stdout.write("""Usage: mudela-book [options] FILE\n
790 Generate hybrid LaTeX input from Latex + mudela
793 sys.stdout.write (options_help_str (option_definitions))
794 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
798 Report bugs to bug-gnu-music@gnu.org.
800 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
801 Han-Wen Nienhuys <hanwen@cs.uu.nl>
807 def write_deps (fn, target):
808 sys.stdout.write('writing `%s\'\n' % fn)
812 f.write ('%s%s: ' % (dep_prefix, target))
813 for d in __main__.read_files:
817 __main__.read_files = []
820 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
822 def print_version ():
824 sys.stdout.write (r"""Copyright 1998--1999
825 Distributed under terms of the GNU General Public License. It comes with
832 (sh, long) = getopt_args (__main__.option_definitions)
833 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
834 except getopt.error, msg:
835 sys.stderr.write("error: %s" % msg)
843 if o == '--include' or o == '-I':
844 include_path.append (a)
845 elif o == '--version':
849 elif o == '--format' or o == '-o':
851 elif o == '--outname' or o == '-o':
854 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
857 elif o == '--help' or o == '-h':
859 elif o == '--no-lily' or o == '-n':
860 __main__.run_lilypond = 0
861 elif o == '--dependencies':
863 elif o == '--default-mudela-fontsize':
864 default_music_fontsize = string.atoi (a)
867 elif o == '--dep-prefix':
869 elif o == '--no-pictures':
874 for input_filename in files:
879 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
880 my_depname = my_outname + '.dep'
882 chunks = transform_input_file (input_filename, my_outname)
884 foutn = my_outname + '.' + format
885 sys.stderr.write ("Writing `%s'\n" % foutn)
886 fout = open (foutn, 'w')
892 write_deps (my_depname, foutn)
897 # Petr, ik zou willen dat ik iets zinvoller deed,
898 # maar wat ik kan ik doen, het verandert toch niets?