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 full_path = match.group (2)
425 str = find_file (full_path)
426 opts = match.group (1)
429 opts = re.split (',[ \n\t]*', opts)
433 if re.search ('.fly$', full_path):
435 elif re.search ('.sly$', full_path):
436 opts = opts + [ 'fly','fragment']
437 elif re.search ('.ly$', full_path):
438 opts .append ('nofly')
440 str_opts = string.join (opts, ',')
442 str = ("%% copied from file `%s'\n" % full_path) + str
443 return get_output ('output-mudela') % (str_opts, str)
445 b = get_re('mudela-file').sub (mudela_file, b)
446 b = get_re('mudela').sub (mudela_short, b)
449 def find_mudela_chunks (str):
450 """Find mudela blocks, while watching for verbatim. Returns
451 (STR,MUDS) with substituted for the blocks in STR,
452 and the blocks themselves MUDS"""
456 m = get_re ("mudela-block").search( str)
458 chunks.append (('input', str))
462 chunks.append (('input', str[:m.start (0)]))
469 optlist = get_re('comma-sep').split (opts)
472 chunks.append (('mudela', body, optlist))
474 str = str [m.end (0):]
480 def advance_counters (counter, opts, str):
481 """Advance chap/sect counters,
482 revise OPTS. Return the new counter tuple"""
484 (chapter, section, count) = counter
487 m = get_re ('interesting-cs').search(str)
493 done = done + str[:m.end (0)]
498 opts.append ('twocolumn')
499 elif g == 'onecolumn':
501 opts.remove ('twocolumn')
505 (chapter, section, count) = (chapter + 1, 0, 0)
507 (section, count) = (section + 1, 0)
510 return (chapter, section, count)
513 def schedule_mudela_block (base, chunk, extra_opts):
514 """Take the body and options from CHUNK, figure out how the
515 real .ly should look, and what should be left MAIN_STR (meant
516 for the main file). The .ly is written, and scheduled in
519 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
521 TODO has format [basename, extension, extension, ... ]
525 (type, body, opts) = chunk
526 assert type == 'mudela'
527 opts = opts + extra_opts
530 if 'verbatim' in opts:
531 newbody = output_verbatim (body)
533 file_body = compose_full_body (body, opts)
535 if __main__.use_hash:
536 basename = `abs(hash (file_body))`
537 updated = update_file (file_body, basename + '.ly')
538 todo = [basename] # UGH.
540 if not os.path.isfile (basename + '.tex') or updated:
545 m = re.search ('intertext="(.*?)"', o)
547 newbody = newbody + m.group (1)
555 if 'eps' in opts and ('tex' in todo or
556 not os.path.isfile (basename + '.eps')):
559 if 'png' in opts and ('eps' in todo or
560 not os.path.isfile (basename + '.png')):
563 if format == 'latex':
565 newbody = newbody + get_output ('output-eps') % (basename, basename)
567 newbody = newbody + get_output ('output-tex') % basename
569 elif format == 'texi':
570 newbody = newbody + get_output ('output-all') % (basename, basename)
572 return ('mudela', newbody, opts, todo, base)
575 def find_eps_dims (match):
576 "Fill in dimensions of EPS files."
579 dims = bounding_box_dimensions (fn)
581 return '%ipt' % dims[0]
584 def print_chunks (ch):
586 print '-->%s\n%s' % (c[0], c[1])
588 print '==>%s' % list (c[2:])
592 def transform_input_file (in_filename, out_filename):
593 """Read the input, and deliver a list of chunks
598 chunks = read_doc_file (in_filename)
600 #. Process \mudela and \mudelafile.
601 for func in [find_mudela_shorthand_chunks,
606 newchunks = newchunks + func (c[1])
613 opts = scan_preamble (chunks[0][1])
615 (chap,sect,count) = (0,0,0)
617 # Count sections/chapters.
620 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
621 elif c[0] == 'mudela':
622 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
624 c = schedule_mudela_block (base, c, opts)
632 if __main__.run_lilypond:
633 compile_all_files (chunks)
637 if c[0] == 'mudela' and 'eps' in c[2]:
638 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
639 newchunks.append (('mudela', body))
644 if chunks and chunks[0][0] == 'input':
645 chunks[0] = ('input', completize_preamble (chunks[0][1]))
650 sys.stderr.write ("invoking `%s'\n" % cmd)
653 error ('Error command exited with value %d\n' % st)
656 def compile_all_files (chunks):
671 tex.append (base + '.ly')
675 if __main__.use_hash:
676 hash_dict[c[4]] = c[3][0]
679 lilyopts = map (lambda x: '-I ' + x, include_path)
680 lilyopts = string.join (lilyopts, ' ' )
681 texfiles = string.join (tex, ' ')
682 system ('lilypond %s %s' % (lilyopts, texfiles))
685 cmd = r"""tex %s; dvips -E -o %s %s""" % \
690 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
692 cmd = cmd % (g + '.eps', g + '.png')
695 if __main__.use_hash:
699 ks = hash_dict.keys ()
702 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
704 if name != last_name:
709 f.write ("%s:%s\n" % (i, hash_dict[i]))
712 def update_file (body, name):
722 f = open (name , 'w')
731 def getopt_args (opts):
732 "Construct arguments (LONG, SHORT) for getopt from list of options."
747 def option_help_str (o):
748 "Transform one option description (4-tuple ) into neatly formatted string"
766 return ' ' + sh + sep + long + arg
769 def options_help_str (opts):
770 "Convert a list of options into a neatly formatted string"
775 s = option_help_str (o)
776 strs.append ((s, o[3]))
782 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
786 sys.stdout.write("""Usage: mudela-book [options] FILE\n
787 Generate hybrid LaTeX input from Latex + mudela
790 sys.stdout.write (options_help_str (options))
791 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
795 Report bugs to bug-gnu-music@gnu.org.
797 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
798 Han-Wen Nienhuys <hanwen@cs.uu.nl>
804 def write_deps (fn, target):
805 sys.stdout.write('writing `%s\'\n' % fn)
809 f.write ('%s%s: ' % (dep_prefix, target))
810 for d in __main__.read_files:
814 __main__.read_files = []
817 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
819 def print_version ():
821 sys.stdout.write (r"""Copyright 1998--1999
822 Distributed under terms of the GNU General Public License. It comes with
829 (sh, long) = getopt_args (__main__.options)
830 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
831 except getopt.error, msg:
832 sys.stderr.write("error: %s" % msg)
840 if o == '--include' or o == '-I':
841 include_path.append (a)
842 elif o == '--version':
846 elif o == '--format' or o == '-o':
848 elif o == '--outname' or o == '-o':
851 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
854 elif o == '--help' or o == '-h':
856 elif o == '--no-lily' or o == '-n':
857 __main__.run_lilypond = 0
858 elif o == '--dependencies':
860 elif o == '--default-mudela-fontsize':
861 default_music_fontsize = string.atoi (a)
864 elif o == '--dep-prefix':
869 for input_filename in files:
874 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
875 my_depname = my_outname + '.dep'
877 chunks = transform_input_file (input_filename, my_outname)
879 foutn = my_outname + '.' + format
880 sys.stderr.write ("Writing `%s'\n" % foutn)
881 fout = open (foutn, 'w')
887 write_deps (my_depname, foutn)
892 # Petr, ik zou willen dat ik iets zinvoller deed,
893 # maar wat ik kan ik doen, het verandert toch niets?