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.
100 # should also support fragment in
102 'output-all': r"""@tex
105 \def\EndLilyPondOutput{}
117 def output_verbatim (body):
118 if __main__.format == 'texi':
119 body = re.sub ('([@{}])', '@\\1', body)
120 return get_output ('output-verbatim') % body
123 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
124 'include': '\\\\mbinclude{([^}]+)}',
127 'header': r"""\\documentclass(\[.*?\])?""",
128 'preamble-end': '\\\\begin{document}',
129 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
130 'verb': r"""\\verb(.)(.*?)\1""",
131 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
132 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
133 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
134 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
135 'def-post-re': r"""\\def\\postMudelaExample""",
136 'def-pre-re': r"""\\def\\preMudelaExample""",
140 'input': '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
143 'preamble-end': no_match,
144 'verbatim': r"""(?s)@example(.*?)@end example$""",
145 'verb': r"""@code{(.*?)}""",
146 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
147 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
148 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
149 'interesting-cs': r"""[\\@](chapter|section)""",
155 for r in re_dict.keys ():
158 for k in olddict.keys ():
159 newdict[k] = re.compile (olddict[k])
173 def get_output (name):
174 return output_dict[format][name]
177 return re_dict[format][name]
180 def bounding_box_dimensions(fname):
184 error ("Error opening `%s'" % fname)
186 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
188 return (int(s.group(3))-int(s.group(1)),
189 int(s.group(4))-int(s.group(2)))
196 def find_file (name):
198 for a in include_path:
200 nm = os.path.join (a, name)
202 __main__.read_files.append (nm)
211 error ("File not found `%s'\n" % name)
215 sys.stderr.write (str + "\n Exiting ... \n\n")
219 def compose_full_body (body, opts):
220 "Construct the text of an input file: add stuff to BODY using OPTS as options."
222 music_size = default_music_fontsize
223 latex_size = default_text_fontsize
227 m = re.search ('^(.*)paper$', o)
232 m = re.match ('([0-9]+)pt', o)
234 music_size = string.atoi(m.group (1))
236 m = re.match ('latexfontsize=([0-9]+)pt', o)
238 latex_size = string.atoi (m.group (1))
242 if 'twocolumn' in opts:
246 # urg: breaks on \include of full score
247 # Use nofly option if you want to \include full score.
248 if 'nofly' not in opts and not re.search ('\\\\score', body):
249 opts.append ('fragment')
251 if 'fragment' in opts and 'nosingleline' not in opts:
252 opts.append ('singleline')
254 if 'singleline' in opts:
257 l = latex_linewidths[cols][paper][latex_size]
260 if 'relative' in opts:
261 body = '\\relative c { %s }' % body
264 if 'fragment' in opts:
271 optstring = string.join (opts, ' ')
272 optstring = re.sub ('\n', ' ', optstring)
275 %% Generated by mudela-book.py; options are %s
276 \include "paper%d.ly"
277 \paper { linewidth = %f \pt; }
278 """ % (optstring, music_size, l) + body
282 def find_inclusion_chunks (regex, surround, str):
285 m = regex.search (str)
288 chunks.append (('input', str))
292 chunks.append (('input', str[: m.start (0)]))
293 chunks.append (('input', surround))
294 chunks = chunks + read_doc_file (m.group (1))
296 chunks.append (('input', surround))
298 str = str [m.end (0):]
301 def find_include_chunks (str):
302 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
304 def find_input_chunks (str):
305 return find_inclusion_chunks (get_re ('input'), '', str)
307 def read_doc_file (filename):
308 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
310 str = find_file(filename)
312 if __main__.format == '':
313 latex = re.search ('\\\\document', str[:200])
314 texinfo = re.search ('@node', str[:200])
315 if (texinfo and latex) or not (texinfo or latex):
316 error("error: can't determine format, please specify")
318 __main__.format = 'texi'
320 __main__.format = 'latex'
322 chunks = [('input', str)]
324 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
328 newchunks = newchunks + func (c[1])
337 def scan_preamble (str):
339 m = get_re ('header').search( str)
341 # should extract paper & fontsz.
342 if m and m.group (1):
343 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
345 def verbose_fontsize ( x):
347 #if o.match('[0-9]+pt'):
348 if re.match('[0-9]+pt', x):
349 return 'latexfontsize=' + x
353 options = map (verbose_fontsize, options)
358 def completize_preamble (str):
359 m = get_re ('preamble-end').search( str)
363 preamble = str [:m.start (0)]
364 str = str [m.start(0):]
366 if not get_re('def-post-re').search (preamble):
367 preamble = preamble + get_output('output-default-post')
368 if not get_re ('def-pre-re').search( preamble):
369 preamble = preamble + get_output ('output-default-pre')
372 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
374 preamble = preamble + '\\usepackage{graphics}\n'
376 return preamble + str
378 def find_verbatim_chunks (str):
379 """Chop STR into a list of tagged chunks, ie. tuples of form
380 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
386 m = get_re ('verbatim').search( str)
388 chunks.append( ('input', str))
391 chunks.append (('input', str[:m.start (0)]))
392 chunks.append (('verbatim', m.group (0)))
394 str = str [m.end(0):]
398 def find_verb_chunks (str):
402 m = get_re ("verb").search(str)
404 chunks.append (('input', str))
407 chunks.append (('input', str[:m.start (0)]))
408 chunks.append (('verbatim', m.group (0)))
409 str = str [m.end(0):]
415 def find_mudela_shorthand_chunks (str):
416 return [('input', find_mudela_shorthands(str))]
418 def find_mudela_shorthands (b):
419 def mudela_short (match):
420 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
421 opts = match.group (1)
423 opts = ',' + opts[1:-1]
426 return get_output ('output-mudela-fragment') % (opts, match.group (2))
428 def mudela_file (match):
429 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
432 opts = match.group (1)
435 opts = re.split (',[ \n\t]*', opts)
439 if re.search ('.fly$', fn):
441 elif re.search ('.sly$', fn):
442 opts = opts + [ 'fly','fragment']
443 elif re.search ('.ly$', fn):
444 opts .append ('nofly')
446 str_opts = string.join (opts, ',')
448 str = ("%% copied from file `%s'\n" % fn) + str
449 return get_output ('output-mudela') % (str_opts, str)
451 b = get_re('mudela-file').sub (mudela_file, b)
452 b = get_re('mudela').sub (mudela_short, b)
455 def find_mudela_chunks (str):
456 """Find mudela blocks, while watching for verbatim. Returns
457 (STR,MUDS) with substituted for the blocks in STR,
458 and the blocks themselves MUDS"""
462 m = get_re ("mudela-block").search( str)
464 chunks.append (('input', str))
468 chunks.append (('input', str[:m.start (0)]))
475 optlist = get_re('comma-sep').split (opts)
478 chunks.append (('mudela', body, optlist))
480 str = str [m.end (0):]
486 def advance_counters (counter, opts, str):
487 """Advance chap/sect counters,
488 revise OPTS. Return the new counter tuple"""
490 (chapter, section, count) = counter
493 m = get_re ('interesting-cs').search(str)
499 done = done + str[:m.end (0)]
504 opts.append ('twocolumn')
505 elif g == 'onecolumn':
507 opts.remove ('twocolumn')
511 (chapter, section, count) = (chapter + 1, 0, 0)
513 (section, count) = (section + 1, 0)
516 return (chapter, section, count)
519 def schedule_mudela_block (base, chunk, extra_opts):
520 """Take the body and options from CHUNK, figure out how the
521 real .ly should look, and what should be left MAIN_STR (meant
522 for the main file). The .ly is written, and scheduled in
525 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
527 TODO has format [basename, extension, extension, ... ]
531 (type, body, opts) = chunk
532 assert type == 'mudela'
533 opts = opts + extra_opts
536 if 'verbatim' in opts:
537 newbody = output_verbatim (body)
539 file_body = compose_full_body (body, opts)
541 if __main__.use_hash:
542 basename = `abs(hash (file_body))`
543 updated = update_file (file_body, basename + '.ly')
544 todo = [basename] # UGH.
546 if not os.path.isfile (basename + '.tex') or updated:
551 m = re.search ('intertext="(.*?)"', o)
553 newbody = newbody + m.group (1)
561 if 'eps' in opts and ('tex' in todo or
562 not os.path.isfile (basename + '.eps')):
565 if 'png' in opts and ('eps' in todo or
566 not os.path.isfile (basename + '.png')):
569 if format == 'latex':
571 newbody = newbody + get_output ('output-eps') % (basename, basename)
573 newbody = newbody + get_output ('output-tex') % basename
575 elif format == 'texi':
576 newbody = newbody + get_output ('output-all') % (basename, basename)
578 return ('mudela', newbody, opts, todo, base)
581 def find_eps_dims (match):
582 "Fill in dimensions of EPS files."
585 dims = bounding_box_dimensions (fn)
587 return '%ipt' % dims[0]
590 def print_chunks (ch):
592 print '-->%s\n%s' % (c[0], c[1])
594 print '==>%s' % list (c[2:])
598 def transform_input_file (in_filename, out_filename):
599 """Read the input, and deliver a list of chunks
604 chunks = read_doc_file (in_filename)
606 #. Process \mudela and \mudelafile.
607 for func in [find_mudela_shorthand_chunks,
612 newchunks = newchunks + func (c[1])
619 opts = scan_preamble (chunks[0][1])
621 (chap,sect,count) = (0,0,0)
623 # Count sections/chapters.
626 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
627 elif c[0] == 'mudela':
628 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
630 c = schedule_mudela_block (base, c, opts)
638 if __main__.run_lilypond:
639 compile_all_files (chunks)
643 if c[0] == 'mudela' and 'eps' in c[2]:
644 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
645 newchunks.append (('mudela', body))
650 if chunks and chunks[0][0] == 'input':
651 chunks[0] = ('input', completize_preamble (chunks[0][1]))
656 sys.stderr.write ("invoking `%s'\n" % cmd)
659 error ('Error command exited with value %d\n' % st)
662 def compile_all_files (chunks):
677 tex.append (base + '.ly')
678 elif e == 'png' and do_pictures:
681 if __main__.use_hash:
682 hash_dict[c[4]] = c[3][0]
685 lilyopts = map (lambda x: '-I ' + x, include_path)
686 lilyopts = string.join (lilyopts, ' ' )
687 texfiles = string.join (tex, ' ')
688 system ('lilypond %s %s' % (lilyopts, texfiles))
691 cmd = r"""tex '\nonstopmode \input %s'; dvips -E -o %s %s""" % \
696 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
698 cmd = cmd % (g + '.eps', g + '.png')
701 if __main__.use_hash:
705 ks = hash_dict.keys ()
708 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
710 if name != last_name:
715 f.write ("%s:%s\n" % (i, hash_dict[i]))
718 def update_file (body, name):
728 f = open (name , 'w')
737 def getopt_args (opts):
738 "Construct arguments (LONG, SHORT) for getopt from list of options."
753 def option_help_str (o):
754 "Transform one option description (4-tuple ) into neatly formatted string"
772 return ' ' + sh + sep + long + arg
775 def options_help_str (opts):
776 "Convert a list of options into a neatly formatted string"
782 s = option_help_str (o)
783 strs.append ((s, o[3]))
789 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
793 sys.stdout.write("""Usage: mudela-book [options] FILE\n
794 Generate hybrid LaTeX input from Latex + mudela
797 sys.stdout.write (options_help_str (option_definitions))
798 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
802 Report bugs to bug-gnu-music@gnu.org.
804 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
805 Han-Wen Nienhuys <hanwen@cs.uu.nl>
811 def write_deps (fn, target):
812 sys.stdout.write('writing `%s\'\n' % fn)
816 f.write ('%s%s: ' % (dep_prefix, target))
817 for d in __main__.read_files:
821 __main__.read_files = []
824 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
826 def print_version ():
828 sys.stdout.write (r"""Copyright 1998--1999
829 Distributed under terms of the GNU General Public License. It comes with
836 (sh, long) = getopt_args (__main__.option_definitions)
837 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
838 except getopt.error, msg:
839 sys.stderr.write("error: %s" % msg)
847 if o == '--include' or o == '-I':
848 include_path.append (a)
849 elif o == '--version':
853 elif o == '--format' or o == '-o':
855 elif o == '--outname' or o == '-o':
858 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
861 elif o == '--help' or o == '-h':
863 elif o == '--no-lily' or o == '-n':
864 __main__.run_lilypond = 0
865 elif o == '--dependencies':
867 elif o == '--default-mudela-fontsize':
868 default_music_fontsize = string.atoi (a)
871 elif o == '--dep-prefix':
873 elif o == '--no-pictures':
878 for input_filename in files:
883 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
884 my_depname = my_outname + '.dep'
886 chunks = transform_input_file (input_filename, my_outname)
888 foutn = my_outname + '.' + format
889 sys.stderr.write ("Writing `%s'\n" % foutn)
890 fout = open (foutn, 'w')
896 write_deps (my_depname, foutn)
901 # Petr, ik zou willen dat ik iets zinvoller deed,
902 # maar wat ik kan ik doen, het verandert toch niets?