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}
23 # TODO: add an option to read the .ly files from a previous run and dump
24 # the .tex file, so you can do
26 # * mudela-book file.tex
27 # * convert-mudela *.ly
28 # * mudela-book --read-lys *.ly
31 default_music_fontsize = 16
32 default_text_fontsize = 12
35 # indices are no. of columns, papersize, fontsize
36 # Why can't this be calculated?
38 1: {'a4':{10: 345, 11: 360, 12: 390},
39 'a5':{10: 276, 11: 276, 12: 276},
40 'b5':{10: 345, 11: 356, 12: 356},
41 'letter':{10: 345, 11: 360, 12: 390},
42 'legal': {10: 345, 11: 360, 12: 390},
43 'executive':{10: 345, 11: 360, 12: 379}},
44 2: {'a4':{10: 167, 11: 175, 12: 190},
45 'a5':{10: 133, 11: 133, 12: 133},
46 'b5':{10: 167, 11: 173, 12: 173},
47 'letter':{10: 167, 11: 175, 12: 190},
48 'legal':{10: 167, 11: 175, 12: 190},
49 'executive':{10: 167, 11: 175, 12: 184}}}
52 option_definitions = [
53 ('DIM', '', 'default-mudela-fontsize', 'default fontsize for music. DIM is assumed to in points'),
54 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
55 ('', 'h', 'help', 'print help'),
56 ('DIR', 'I', 'include', 'include path'),
57 ('', '', 'init', 'mudela-book initfile'),
58 # ('DIM', '', 'force-mudela-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
59 ('', '', 'force-verbatim', 'make all mudela verbatim'),
60 ('', 'M', 'dependencies', 'write dependencies'),
61 ('', 'n', 'no-lily', 'don\'t run lilypond'),
62 ('', '', 'no-pictures', "don\'t generate pictures"),
63 ('FILE', 'o', 'outname', 'prefix for filenames'),
64 ('', 'v', 'version', 'print version information' ),
65 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency')
74 # format specific strings, ie. regex-es for input, and % strings for output
77 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
84 'output-mudela':r"""\begin[%s]{mudela}
87 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
88 'output-default-post': r"""\def\postMudelaExample{}""",
89 'output-default-pre': r"""\def\preMudelaExample{}""",
90 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
91 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
93 'texi' : {'output-mudela': """@mudela[%s]
97 'output-verbatim': r"""@example
102 # do some tweaking: @ is needed in some ps stuff.
103 # override EndLilyPondOutput, since @tex is done
104 # in a sandbox, you can't do \input lilyponddefs at the
105 # top of the document.
107 # should also support fragment in
109 'output-all': r"""@tex
112 \def\EndLilyPondOutput{}
124 def output_verbatim (body):
125 if __main__.format == 'texi':
126 body = re.sub ('([@{}])', '@\\1', body)
127 return get_output ('output-verbatim') % body
130 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
131 'include': '\\\\mbinclude{([^}]+)}',
134 'header': r"""\\documentclass(\[.*?\])?""",
135 'preamble-end': '\\\\begin{document}',
136 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
137 'verb': r"""\\verb(.)(.*?)\1""",
138 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
139 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
140 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
141 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
142 'def-post-re': r"""\\def\\postMudelaExample""",
143 'def-pre-re': r"""\\def\\preMudelaExample""",
147 'input': '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
150 'preamble-end': no_match,
151 'verbatim': r"""(?s)@example(.*?)@end example$""",
152 'verb': r"""@code{(.*?)}""",
153 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
154 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
155 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
156 'interesting-cs': r"""[\\@](chapter|section)""",
162 for r in re_dict.keys ():
165 for k in olddict.keys ():
166 newdict[k] = re.compile (olddict[k])
180 def get_output (name):
181 return output_dict[format][name]
184 return re_dict[format][name]
187 def bounding_box_dimensions(fname):
191 error ("Error opening `%s'" % fname)
193 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
195 return (int(s.group(3))-int(s.group(1)),
196 int(s.group(4))-int(s.group(2)))
203 def find_file (name):
205 for a in include_path:
207 nm = os.path.join (a, name)
209 __main__.read_files.append (nm)
218 error ("File not found `%s'\n" % name)
222 sys.stderr.write (str + "\n Exiting ... \n\n")
226 def compose_full_body (body, opts):
227 "Construct the text of an input file: add stuff to BODY using OPTS as options."
229 music_size = default_music_fontsize
230 latex_size = default_text_fontsize
234 m = re.search ('^(.*)paper$', o)
239 m = re.match ('([0-9]+)pt', o)
241 music_size = string.atoi(m.group (1))
243 m = re.match ('latexfontsize=([0-9]+)pt', o)
245 latex_size = string.atoi (m.group (1))
249 if 'twocolumn' in opts:
253 # urg: breaks on \include of full score
254 # Use nofly option if you want to \include full score.
255 if 'nofly' not in opts and not re.search ('\\\\score', body):
256 opts.append ('fragment')
258 if 'fragment' in opts and 'nosingleline' not in opts:
259 opts.append ('singleline')
261 if 'singleline' in opts:
264 l = latex_linewidths[cols][paper][latex_size]
267 if 'relative' in opts:
268 body = '\\relative c { %s }' % body
271 if 'fragment' in opts:
278 optstring = string.join (opts, ' ')
279 optstring = re.sub ('\n', ' ', optstring)
282 %% Generated by mudela-book.py; options are %s
283 \include "paper%d.ly"
284 \paper { linewidth = %f \pt; }
285 """ % (optstring, music_size, l) + body
289 def find_inclusion_chunks (regex, surround, str):
292 m = regex.search (str)
295 chunks.append (('input', str))
299 chunks.append (('input', str[: m.start (0)]))
300 chunks.append (('input', surround))
301 chunks = chunks + read_doc_file (m.group (1))
303 chunks.append (('input', surround))
305 str = str [m.end (0):]
308 def find_include_chunks (str):
309 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
311 def find_input_chunks (str):
312 return find_inclusion_chunks (get_re ('input'), '', str)
314 def read_doc_file (filename):
315 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
317 str = find_file(filename)
319 if __main__.format == '':
320 latex = re.search ('\\\\document', str[:200])
321 texinfo = re.search ('@node', str[:200])
322 if (texinfo and latex) or not (texinfo or latex):
323 error("error: can't determine format, please specify")
325 __main__.format = 'texi'
327 __main__.format = 'latex'
329 chunks = [('input', str)]
331 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
335 newchunks = newchunks + func (c[1])
344 def scan_preamble (str):
346 m = get_re ('header').search( str)
348 # should extract paper & fontsz.
349 if m and m.group (1):
350 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
352 def verbose_fontsize ( x):
354 #if o.match('[0-9]+pt'):
355 if re.match('[0-9]+pt', x):
356 return 'latexfontsize=' + x
360 options = map (verbose_fontsize, options)
365 def completize_preamble (str):
366 m = get_re ('preamble-end').search( str)
370 preamble = str [:m.start (0)]
371 str = str [m.start(0):]
373 if not get_re('def-post-re').search (preamble):
374 preamble = preamble + get_output('output-default-post')
375 if not get_re ('def-pre-re').search( preamble):
376 preamble = preamble + get_output ('output-default-pre')
379 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
381 preamble = preamble + '\\usepackage{graphics}\n'
383 return preamble + str
385 def find_verbatim_chunks (str):
386 """Chop STR into a list of tagged chunks, ie. tuples of form
387 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
393 m = get_re ('verbatim').search( str)
395 chunks.append( ('input', str))
398 chunks.append (('input', str[:m.start (0)]))
399 chunks.append (('verbatim', m.group (0)))
401 str = str [m.end(0):]
405 def find_verb_chunks (str):
409 m = get_re ("verb").search(str)
411 chunks.append (('input', str))
414 chunks.append (('input', str[:m.start (0)]))
415 chunks.append (('verbatim', m.group (0)))
416 str = str [m.end(0):]
422 def find_mudela_shorthand_chunks (str):
423 return [('input', find_mudela_shorthands(str))]
425 def find_mudela_shorthands (b):
426 def mudela_short (match):
427 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
428 opts = match.group (1)
430 opts = ',' + opts[1:-1]
433 return get_output ('output-mudela-fragment') % (opts, match.group (2))
435 def mudela_file (match):
436 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
439 opts = match.group (1)
442 opts = re.split (',[ \n\t]*', opts)
446 if re.search ('.fly$', fn):
448 elif re.search ('.sly$', fn):
449 opts = opts + [ 'fly','fragment']
450 elif re.search ('.ly$', fn):
451 opts .append ('nofly')
453 str_opts = string.join (opts, ',')
455 str = ("%% copied from file `%s'\n" % fn) + str
456 return get_output ('output-mudela') % (str_opts, str)
458 b = get_re('mudela-file').sub (mudela_file, b)
459 b = get_re('mudela').sub (mudela_short, b)
462 def find_mudela_chunks (str):
463 """Find mudela blocks, while watching for verbatim. Returns
464 (STR,MUDS) with substituted for the blocks in STR,
465 and the blocks themselves MUDS"""
469 m = get_re ("mudela-block").search( str)
471 chunks.append (('input', str))
475 chunks.append (('input', str[:m.start (0)]))
482 optlist = get_re('comma-sep').split (opts)
485 chunks.append (('mudela', body, optlist))
487 str = str [m.end (0):]
493 def advance_counters (counter, opts, str):
494 """Advance chap/sect counters,
495 revise OPTS. Return the new counter tuple"""
497 (chapter, section, count) = counter
500 m = get_re ('interesting-cs').search(str)
506 done = done + str[:m.end (0)]
511 opts.append ('twocolumn')
512 elif g == 'onecolumn':
514 opts.remove ('twocolumn')
518 (chapter, section, count) = (chapter + 1, 0, 0)
520 (section, count) = (section + 1, 0)
523 return (chapter, section, count)
526 def schedule_mudela_block (base, chunk, extra_opts):
527 """Take the body and options from CHUNK, figure out how the
528 real .ly should look, and what should be left MAIN_STR (meant
529 for the main file). The .ly is written, and scheduled in
532 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
534 TODO has format [basename, extension, extension, ... ]
538 (type, body, opts) = chunk
539 assert type == 'mudela'
540 opts = opts + extra_opts
543 if 'verbatim' in opts:
544 newbody = output_verbatim (body)
546 file_body = compose_full_body (body, opts)
548 if __main__.use_hash:
549 basename = `abs(hash (file_body))`
550 updated = update_file (file_body, basename + '.ly')
551 todo = [basename] # UGH.
553 if not os.path.isfile (basename + '.tex') or updated:
558 m = re.search ('intertext="(.*?)"', o)
560 newbody = newbody + m.group (1)
568 if 'eps' in opts and ('tex' in todo or
569 not os.path.isfile (basename + '.eps')):
572 if 'png' in opts and ('eps' in todo or
573 not os.path.isfile (basename + '.png')):
576 if format == 'latex':
578 newbody = newbody + get_output ('output-eps') % (basename, basename)
580 newbody = newbody + get_output ('output-tex') % basename
582 elif format == 'texi':
583 newbody = newbody + get_output ('output-all') % (basename, basename)
585 return ('mudela', newbody, opts, todo, base)
588 def find_eps_dims (match):
589 "Fill in dimensions of EPS files."
592 dims = bounding_box_dimensions (fn)
594 return '%ipt' % dims[0]
597 def print_chunks (ch):
599 print '-->%s\n%s' % (c[0], c[1])
601 print '==>%s' % list (c[2:])
605 def transform_input_file (in_filename, out_filename):
606 """Read the input, and deliver a list of chunks
611 chunks = read_doc_file (in_filename)
613 #. Process \mudela and \mudelafile.
614 for func in [find_mudela_shorthand_chunks,
619 newchunks = newchunks + func (c[1])
626 opts = scan_preamble (chunks[0][1])
628 (chap,sect,count) = (0,0,0)
630 # Count sections/chapters.
633 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
634 elif c[0] == 'mudela':
635 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
637 c = schedule_mudela_block (base, c, opts)
645 if __main__.run_lilypond:
646 compile_all_files (chunks)
650 if c[0] == 'mudela' and 'eps' in c[2]:
651 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
652 newchunks.append (('mudela', body))
657 if chunks and chunks[0][0] == 'input':
658 chunks[0] = ('input', completize_preamble (chunks[0][1]))
663 sys.stderr.write ("invoking `%s'\n" % cmd)
666 error ('Error command exited with value %d\n' % st)
669 def compile_all_files (chunks):
684 tex.append (base + '.ly')
685 elif e == 'png' and do_pictures:
688 if __main__.use_hash:
689 hash_dict[c[4]] = c[3][0]
692 lilyopts = map (lambda x: '-I ' + x, include_path)
693 lilyopts = string.join (lilyopts, ' ' )
694 texfiles = string.join (tex, ' ')
695 system ('lilypond %s %s' % (lilyopts, texfiles))
698 cmd = r"""tex '\nonstopmode \input %s'; dvips -E -o %s %s""" % \
703 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
705 cmd = cmd % (g + '.eps', g + '.png')
708 if __main__.use_hash:
712 ks = hash_dict.keys ()
715 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
717 if name != last_name:
722 f.write ("%s:%s\n" % (i, hash_dict[i]))
725 def update_file (body, name):
735 f = open (name , 'w')
744 def getopt_args (opts):
745 "Construct arguments (LONG, SHORT) for getopt from list of options."
760 def option_help_str (o):
761 "Transform one option description (4-tuple ) into neatly formatted string"
779 return ' ' + sh + sep + long + arg
782 def options_help_str (opts):
783 "Convert a list of options into a neatly formatted string"
789 s = option_help_str (o)
790 strs.append ((s, o[3]))
796 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
800 sys.stdout.write("""Usage: mudela-book [options] FILE\n
801 Generate hybrid LaTeX input from Latex + mudela
804 sys.stdout.write (options_help_str (option_definitions))
805 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
809 Report bugs to bug-gnu-music@gnu.org.
811 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
812 Han-Wen Nienhuys <hanwen@cs.uu.nl>
818 def write_deps (fn, target):
819 sys.stdout.write('writing `%s\'\n' % fn)
823 f.write ('%s%s: ' % (dep_prefix, target))
824 for d in __main__.read_files:
828 __main__.read_files = []
831 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
833 def print_version ():
835 sys.stdout.write (r"""Copyright 1998--1999
836 Distributed under terms of the GNU General Public License. It comes with
843 (sh, long) = getopt_args (__main__.option_definitions)
844 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
845 except getopt.error, msg:
846 sys.stderr.write("error: %s" % msg)
854 if o == '--include' or o == '-I':
855 include_path.append (a)
856 elif o == '--version':
860 elif o == '--format' or o == '-o':
862 elif o == '--outname' or o == '-o':
865 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
868 elif o == '--help' or o == '-h':
870 elif o == '--no-lily' or o == '-n':
871 __main__.run_lilypond = 0
872 elif o == '--dependencies':
874 elif o == '--default-mudela-fontsize':
875 default_music_fontsize = string.atoi (a)
878 elif o == '--dep-prefix':
880 elif o == '--no-pictures':
885 for input_filename in files:
890 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
891 my_depname = my_outname + '.dep'
893 chunks = transform_input_file (input_filename, my_outname)
895 foutn = my_outname + '.' + format
896 sys.stderr.write ("Writing `%s'\n" % foutn)
897 fout = open (foutn, 'w')
903 write_deps (my_depname, foutn)
908 # Petr, ik zou willen dat ik iets zinvoller deed,
909 # maar wat ik kan ik doen, het verandert toch niets?