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}}}
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 ('FILE', 'o', 'outname', 'prefix for filenames'),
56 ('', 'v', 'version', 'print version information' ),
57 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency')
65 # format specific strings, ie. regex-es for input, and % strings for output
68 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
75 'output-mudela':r"""\begin[%s]{mudela}
78 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
79 'output-default-post': r"""\def\postMudelaExample{}""",
80 'output-default-pre': r"""\def\preMudelaExample{}""",
81 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
82 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
84 'texi' : {'output-mudela': """@mudela[%s]
88 'output-verbatim': r"""@example
93 # do some tweaking: @ is needed in some ps stuff.
94 # override EndLilyPondOutput, since @tex is done
95 # in a sandbox, you can't do \input lilyponddefs at the
96 # top of the document.
97 'output-all': r"""@tex
100 \def\EndLilyPondOutput{}
111 def output_verbatim (body):
112 if __main__.format == 'texi':
113 body = re.sub ('([@{}])', '@\\1', body)
114 return get_output ('output-verbatim') % body
117 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
118 'include': '\\\\mbinclude{([^}]+)}',
121 'header': r"""\\documentclass(\[.*?\])?""",
122 'preamble-end': '\\\\begin{document}',
123 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
124 'verb': r"""\\verb(.)(.*?)\1""",
125 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
126 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
127 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
128 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
129 'def-post-re': r"""\\def\\postMudelaExample""",
130 'def-pre-re': r"""\\def\\preMudelaExample""",
134 'input': '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
137 'preamble-end': no_match,
138 'verbatim': r"""(?s)@example(.*?)@end example$""",
139 'verb': r"""@code{(.*?)}""",
140 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
141 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
142 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
143 'interesting-cs': r"""[\\@](chapter|section)""",
149 for r in re_dict.keys ():
152 for k in olddict.keys ():
153 newdict[k] = re.compile (olddict[k])
167 def get_output (name):
168 return output_dict[format][name]
171 return re_dict[format][name]
174 def bounding_box_dimensions(fname):
178 error ("Error opening `%s'" % fname)
180 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
182 return (int(s.group(3))-int(s.group(1)),
183 int(s.group(4))-int(s.group(2)))
190 def find_file (name):
192 for a in include_path:
194 nm = os.path.join (a, name)
196 __main__.read_files.append (nm)
205 error ("File not found `%s'\n" % name)
209 sys.stderr.write (str + "\n Exiting ... \n\n")
213 def compose_full_body (body, opts):
214 "Construct the text of an input file: add stuff to BODY using OPTS as options."
216 music_size = default_music_fontsize
217 latex_size = default_text_fontsize
221 m = re.search ('^(.*)paper$', o)
226 m = re.match ('([0-9]+)pt', o)
228 music_size = string.atoi(m.group (1))
230 m = re.match ('latexfontsize=([0-9]+)pt', o)
232 latex_size = string.atoi (m.group (1))
236 if 'twocolumn' in opts:
240 # urg: breaks on \include of full score
241 # Use nofly option if you want to \include full score.
242 if 'nofly' not in opts and not re.search ('\\\\score', body):
243 opts.append ('fragment')
245 if 'fragment' in opts and 'nosingleline' not in opts:
246 opts.append ('singleline')
248 if 'singleline' in opts:
251 l = latex_linewidths[cols][paper][latex_size]
254 if 'relative' in opts:
255 body = '\\relative c { %s }' % body
258 if 'fragment' in opts:
265 optstring = string.join (opts, ' ')
266 optstring = re.sub ('\n', ' ', optstring)
269 %% Generated by mudela-book.py; options are %s
270 \include "paper%d.ly"
271 \paper { linewidth = %f \pt; }
272 """ % (optstring, music_size, l) + body
276 def find_inclusion_chunks (regex, surround, str):
279 m = regex.search (str)
282 chunks.append (('input', str))
286 chunks.append (('input', str[: m.start (0)]))
287 chunks.append (('input', surround))
288 chunks = chunks + read_doc_file (m.group (1))
290 chunks.append (('input', surround))
292 str = str [m.end (0):]
295 def find_include_chunks (str):
296 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
298 def find_input_chunks (str):
299 return find_inclusion_chunks (get_re ('input'), '', str)
301 def read_doc_file (filename):
302 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
304 str = find_file(filename)
306 if __main__.format == '':
307 latex = re.search ('\\\\document', str[:200])
308 texinfo = re.search ('@node', str[:200])
309 if (texinfo and latex) or not (texinfo or latex):
310 error("error: can't determine format, please specify")
312 __main__.format = 'texi'
314 __main__.format = 'latex'
316 chunks = [('input', str)]
318 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
322 newchunks = newchunks + func (c[1])
331 def scan_preamble (str):
333 m = get_re ('header').search( str)
335 # should extract paper & fontsz.
336 if m and m.group (1):
337 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
339 def verbose_fontsize ( x):
341 #if o.match('[0-9]+pt'):
342 if re.match('[0-9]+pt', x):
343 return 'latexfontsize=' + x
347 options = map (verbose_fontsize, options)
352 def completize_preamble (str):
353 m = get_re ('preamble-end').search( str)
357 preamble = str [:m.start (0)]
358 str = str [m.start(0):]
360 if not get_re('def-post-re').search (preamble):
361 preamble = preamble + get_output('output-default-post')
362 if not get_re ('def-pre-re').search( preamble):
363 preamble = preamble + get_output ('output-default-pre')
366 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
368 preamble = preamble + '\\usepackage{graphics}\n'
370 return preamble + str
372 def find_verbatim_chunks (str):
373 """Chop STR into a list of tagged chunks, ie. tuples of form
374 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
380 m = get_re ('verbatim').search( str)
382 chunks.append( ('input', str))
385 chunks.append (('input', str[:m.start (0)]))
386 chunks.append (('verbatim', m.group (0)))
388 str = str [m.end(0):]
392 def find_verb_chunks (str):
396 m = get_re ("verb").search(str)
398 chunks.append (('input', str))
401 chunks.append (('input', str[:m.start (0)]))
402 chunks.append (('verbatim', m.group (0)))
403 str = str [m.end(0):]
409 def find_mudela_shorthand_chunks (str):
410 return [('input', find_mudela_shorthands(str))]
412 def find_mudela_shorthands (b):
413 def mudela_short (match):
414 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
415 opts = match.group (1)
417 opts = ',' + opts[1:-1]
420 return get_output ('output-mudela-fragment') % (opts, match.group (2))
422 def mudela_file (match):
423 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
424 str = find_file (match.group (2))
425 opts = match.group (1)
428 opts = re.split (',[ \n\t]*', opts)
432 if re.search ('.fly$', full_path):
434 elif re.search ('.sly$', full_path):
435 opts = opts + [ 'fly','fragment']
436 elif re.search ('.ly$', full_path):
437 opts .append ('nofly')
439 str_opts = string.join (opts, ',')
441 str = ("%% copied from file `%s'\n" % full_path) + str
442 return get_output ('output-mudela') % (str_opts, str)
444 b = get_re('mudela-file').sub (mudela_file, b)
445 b = get_re('mudela').sub (mudela_short, b)
448 def find_mudela_chunks (str):
449 """Find mudela blocks, while watching for verbatim. Returns
450 (STR,MUDS) with substituted for the blocks in STR,
451 and the blocks themselves MUDS"""
455 m = get_re ("mudela-block").search( str)
457 chunks.append (('input', str))
461 chunks.append (('input', str[:m.start (0)]))
468 optlist = get_re('comma-sep').split (opts)
471 chunks.append (('mudela', body, optlist))
473 str = str [m.end (0):]
479 def advance_counters (counter, opts, str):
480 """Advance chap/sect counters,
481 revise OPTS. Return the new counter tuple"""
483 (chapter, section, count) = counter
486 m = get_re ('interesting-cs').search(str)
492 done = done + str[:m.end (0)]
497 opts.append ('twocolumn')
498 elif g == 'onecolumn':
500 opts.remove ('twocolumn')
504 (chapter, section, count) = (chapter + 1, 0, 0)
506 (section, count) = (section + 1, 0)
509 return (chapter, section, count)
512 def schedule_mudela_block (base, chunk, extra_opts):
513 """Take the body and options from CHUNK, figure out how the
514 real .ly should look, and what should be left MAIN_STR (meant
515 for the main file). The .ly is written, and scheduled in
518 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
520 TODO has format [basename, extension, extension, ... ]
524 (type, body, opts) = chunk
525 assert type == 'mudela'
526 opts = opts + extra_opts
529 if 'verbatim' in opts:
530 newbody = output_verbatim (body)
532 file_body = compose_full_body (body, opts)
534 if __main__.use_hash:
535 basename = `abs(hash (file_body))`
536 updated = update_file (file_body, basename + '.ly')
537 todo = [basename] # UGH.
539 if not os.path.isfile (basename + '.tex') or updated:
544 m = re.search ('intertext="(.*?)"', o)
546 newbody = newbody + m.group (1)
554 if 'eps' in opts and ('tex' in todo or
555 not os.path.isfile (basename + '.eps')):
558 if 'png' in opts and ('eps' in todo or
559 not os.path.isfile (basename + '.png')):
562 if format == 'latex':
564 newbody = newbody + get_output ('output-eps') % (basename, basename)
566 newbody = newbody + get_output ('output-tex') % basename
568 elif format == 'texi':
569 newbody = newbody + get_output ('output-all') % (basename, basename)
571 return ('mudela', newbody, opts, todo, base)
574 def find_eps_dims (match):
575 "Fill in dimensions of EPS files."
578 dims = bounding_box_dimensions (fn)
580 return '%ipt' % dims[0]
583 def print_chunks (ch):
585 print '-->%s\n%s' % (c[0], c[1])
587 print '==>%s' % list (c[2:])
591 def transform_input_file (in_filename, out_filename):
592 """Read the input, and deliver a list of chunks
597 chunks = read_doc_file (in_filename)
599 #. Process \mudela and \mudelafile.
600 for func in [find_mudela_shorthand_chunks,
605 newchunks = newchunks + func (c[1])
612 opts = scan_preamble (chunks[0][1])
614 (chap,sect,count) = (0,0,0)
616 # Count sections/chapters.
619 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
620 elif c[0] == 'mudela':
621 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
623 c = schedule_mudela_block (base, c, opts)
631 if __main__.run_lilypond:
632 compile_all_files (chunks)
636 if c[0] == 'mudela' and 'eps' in c[2]:
637 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
638 newchunks.append (('mudela', body))
643 if chunks and chunks[0][0] == 'input':
644 chunks[0] = ('input', completize_preamble (chunks[0][1]))
649 sys.stderr.write ("invoking `%s'\n" % cmd)
652 error ('Error command exited with value %d\n' % st)
655 def compile_all_files (chunks):
670 tex.append (base + '.ly')
674 if __main__.use_hash:
675 hash_dict[c[4]] = c[3][0]
678 lilyopts = map (lambda x: '-I ' + x, include_path)
679 lilyopts = string.join (lilyopts, ' ' )
680 texfiles = string.join (tex, ' ')
681 system ('lilypond %s %s' % (lilyopts, texfiles))
684 cmd = r"""tex %s; dvips -E -o %s %s""" % \
689 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
691 cmd = cmd % (g + '.eps', g + '.png')
694 if __main__.use_hash:
698 ks = hash_dict.keys ()
701 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
703 if name != last_name:
708 f.write ("%s:%s\n" % (i, hash_dict[i]))
711 def update_file (body, name):
721 f = open (name , 'w')
730 def getopt_args (opts):
731 "Construct arguments (LONG, SHORT) for getopt from list of options."
746 def option_help_str (o):
747 "Transform one option description (4-tuple ) into neatly formatted string"
765 return ' ' + sh + sep + long + arg
768 def options_help_str (opts):
769 "Convert a list of options into a neatly formatted string"
774 s = option_help_str (o)
775 strs.append ((s, o[3]))
781 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
785 sys.stdout.write("""Usage: mudela-book [options] FILE\n
786 Generate hybrid LaTeX input from Latex + mudela
789 sys.stdout.write (options_help_str (options))
790 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
794 Report bugs to bug-gnu-music@gnu.org.
796 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
797 Han-Wen Nienhuys <hanwen@cs.uu.nl>
803 def write_deps (fn, target):
804 sys.stdout.write('writing `%s\'\n' % fn)
808 f.write ('%s%s: ' % (dep_prefix, target))
809 for d in __main__.read_files:
813 __main__.read_files = []
816 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
818 def print_version ():
820 sys.stdout.write (r"""Copyright 1998--1999
821 Distributed under terms of the GNU General Public License. It comes with
828 (sh, long) = getopt_args (__main__.options)
829 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
830 except getopt.error, msg:
831 sys.stderr.write("error: %s" % msg)
839 if o == '--include' or o == '-I':
840 include_path.append (a)
841 elif o == '--version':
845 elif o == '--format' or o == '-o':
847 elif o == '--outname' or o == '-o':
850 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
853 elif o == '--help' or o == '-h':
855 elif o == '--no-lily' or o == '-n':
856 __main__.run_lilypond = 0
857 elif o == '--dependencies':
859 elif o == '--default-mudela-fontsize':
860 default_music_fontsize = string.atoi (a)
863 elif o == '--dep-prefix':
868 for input_filename in files:
873 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
874 my_depname = my_outname + '.dep'
876 chunks = transform_input_file (input_filename, my_outname)
878 foutn = my_outname + '.' + format
879 sys.stderr.write ("Writing `%s'\n" % foutn)
880 fout = open (foutn, 'w')
886 write_deps (my_depname, foutn)
891 # Petr, ik zou willen dat ik iets zinvoller deed,
892 # maar wat ik kan ik doen, het verandert toch niets?