12 program_version = '@TOPLEVEL_VERSION@'
17 # TODO: Figure out clean set of options.
19 # BUG: does not handle \verb|\begin{verbatim}\end{verbatim}| correctly.
20 # Should make a joint RE for \verb and \begin, \end{verbatim}
23 default_music_fontsize = 16
24 default_text_fontsize = 12
27 # indices are no. of columns, papersize, fontsize
28 # Why can't this be calculated?
30 1: {'a4':{10: 345, 11: 360, 12: 390},
31 'a5':{10: 276, 11: 276, 12: 276},
32 'b5':{10: 345, 11: 356, 12: 356},
33 'letter':{10: 345, 11: 360, 12: 390},
34 'legal': {10: 345, 11: 360, 12: 390},
35 'executive':{10: 345, 11: 360, 12: 379}},
36 2: {'a4':{10: 167, 11: 175, 12: 190},
37 'a5':{10: 133, 11: 133, 12: 133},
38 'b5':{10: 167, 11: 173, 12: 173},
39 'letter':{10: 167, 11: 175, 12: 190},
40 'legal':{10: 167, 11: 175, 12: 190},
41 'executive':{10: 167, 11: 175, 12: 184}}}
45 ('DIM', '', 'default-mudela-fontsize', 'default fontsize for music. DIM is assumed to in points'),
46 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
47 ('', 'h', 'help', 'print help'),
48 ('DIR', 'I', 'include', 'include path'),
49 ('', '', 'init', 'mudela-book initfile'),
50 # ('DIM', '', 'force-mudela-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
51 ('', '', 'force-verbatim', 'make all mudela verbatim'),
52 ('', 'M', 'dependencies', 'write dependencies'),
53 ('', 'n', 'no-lily', 'don\'t run lilypond'),
54 ('FILE', 'o', 'outname', 'prefix for filenames'),
55 ('', 'v', 'version', 'print version information' )
62 # format specific strings, ie. regex-es for input, and % strings for output
65 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
72 'output-mudela':r"""\begin%s{mudela}
75 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
76 'output-default-post': r"""\def\postMudelaExample{}""",
77 'output-default-pre': r"""\def\preMudelaExample{}""",
78 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
79 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
81 'texi' : {'output-mudela': """@mudela[%s]
85 'output-verbatim': r"""@example
90 # do some tweaking: @ is needed in some ps stuff.
91 # override EndLilyPondOutput, since @tex is done
92 # in a sandbox, you can't do \input lilyponddefs at the
93 # top of the document.
94 'output-all': r"""@tex
97 \def\EndLilyPondOutput{}
108 def output_verbatim (body):
109 if __main__.format == 'texi':
110 body = re.sub ('([@{}])', '@\\1', body)
111 return get_output ('output-verbatim') % body
114 'latex': {'input': '\\\\input{?([^}\t \n}]*)',
115 'include': '\\\\include{([^}]+)}',
118 'header': r"""\\documentclass(\[.*?\])?""",
119 'preamble-end': '\\\\begin{document}',
120 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
121 'verb': r"""\\verb(.)(.*?)\1""",
122 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
123 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
124 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
125 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
126 'def-post-re': r"""\\def\\postMudelaExample""",
127 'def-pre-re': r"""\\def\\preMudelaExample""",
129 'texi': {'input': '@include[ \n\t]+([^\t \n]*)',
132 'preamble-end': no_match,
133 'verbatim': r"""(?s)@example(.*?)@end example$""",
134 'verb': r"""@code{(.*?)}""",
135 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
136 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
137 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
138 'interesting-cs': r"""[\\@](node|mudelagraphic)""",
144 for r in re_dict.keys ():
147 for k in olddict.keys ():
148 newdict[k] = re.compile (olddict[k])
162 def get_output (name):
163 return output_dict[format][name]
166 return re_dict[format][name]
169 def bounding_box_dimensions(fname):
173 error ("Error opening `%s'" % fname)
175 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
177 return (int(s.group(3))-int(s.group(1)),
178 int(s.group(4))-int(s.group(2)))
183 def find_file (name):
184 for a in include_path:
186 nm = os.path.join (a, name)
194 sys.stderr.write (str + "\n Exiting ... \n\n")
198 def compose_full_body (body, opts):
199 "Construct the text of an input file: add stuff to BODY using OPTS as options."
201 music_size = default_music_fontsize
202 latex_size = default_text_fontsize
206 m = re.search ('^(.*)paper$', o)
211 m = re.match ('([0-9]+)pt', o)
213 music_size = string.atoi(m.group (1))
215 m = re.match ('latexfontsize=([0-9]+)pt', o)
217 latex_size = string.atoi (m.group (1))
220 if 'twocolumn' in opts:
224 # urg: breaks on \include of full score
225 # Use nofly option if you want to \include full score.
226 if not 'nofly' in opts and not re.search ('\\\\score', body):
229 if 'fragment' in opts or 'singleline' in opts:
232 l = latex_linewidths[cols][paper][latex_size]
244 optstring = string.join (opts, ' ')
245 optstring = re.sub ('\n', ' ', optstring)
248 %% Generated by mudela-book.py; options are %s
249 \include "paper%d.ly"
250 \paper { linewidth = %f \pt; }
251 """ % (optstring, music_size, l) + body
255 def find_inclusion_chunks (regex, surround, str):
258 m = regex.search (str)
261 chunks.append (('input', str))
265 chunks.append (('input', str[: m.start (0)]))
266 chunks.append (('input', surround))
267 chunks = chunks + read_doc_file (m.group (1))
268 chunks.append (('input', surround))
270 str = str [m.end (0):]
273 def find_include_chunks (str):
274 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
276 def find_input_chunks (str):
277 return find_inclusion_chunks (get_re ('input'), '', str)
279 def read_doc_file (filename):
280 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
282 for ext in ['', '.tex', '.doc', '.tely']:
284 f = open(filename+ ext)
291 error ("File not found `%s'\n" % filename)
295 if __main__.format == '':
296 latex = re.search ('\\\\document', str[:200])
297 texinfo = re.search ('@node', str[:200])
298 if (texinfo and latex) or not (texinfo or latex):
299 error("error: can't determine format, please specify")
301 __main__.format = 'texi'
303 __main__.format = 'latex'
305 chunks = [('input', str)]
307 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
312 newchunks = newchunks + ch
321 def scan_preamble (str):
323 m = get_re ('header').search( str)
325 # should extract paper & fontsz.
326 if m and m.group (1):
327 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
329 def verbose_fontsize ( x):
331 #if o.match('[0-9]+pt'):
332 if re.match('[0-9]+pt', x):
333 return 'latexfontsize=' + x
337 options = map (verbose_fontsize, options)
342 def completize_preamble (str):
343 m = get_re ('preamble-end').search( str)
347 preamble = str [:m.start (0)]
348 str = str [m.start(0):]
350 if not get_re('def-post-re').search (preamble):
351 preamble = preamble + get_output('output-default-post')
352 if not get_re ('def-pre-re').search( preamble):
353 preamble = preamble + get_output ('output-default-pre')
356 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
358 preamble = preamble + '\\usepackage{graphics}\n'
360 return preamble + str
362 def find_verbatim_chunks (str):
363 """Chop STR into a list of tagged chunks, ie. tuples of form
364 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
369 m = get_re ('verbatim').search( str)
371 chunks.append( ('input', str))
374 chunks.append (('input', str[:m.start (0)]))
375 chunks.append (('verbatim', m.group (0)))
377 str = str [m.end(0):]
381 def find_verb_chunks (str):
385 m = get_re ("verb").search(str)
387 chunks.append (('input', str))
390 chunks.append (('input', str[:m.start (0)]))
391 chunks.append (('verbatim', m.group (0)))
392 str = str [m.end(0):]
398 def find_mudela_shorthand_chunks (str):
399 return [('input', find_mudela_shorthands(str))]
401 def find_mudela_shorthands (b):
402 def mudela_short (match):
403 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
404 opts = match.group (1)
406 opts = ',' + opts[1:-1]
409 return get_output ('output-mudela-fragment') % (opts, match.group (2))
411 def mudela_file (match):
412 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
413 d = [] #, d = retdeps
414 full_path = find_file (match.group (2))
416 error("error: can't find file `%s'\n" % match.group(2))
421 opts = match.group (1)
423 opts = re.split (',[ \n\t]*', opts[1:-1])
427 if re.search ('.fly$', full_path):
429 elif re.search ('.sly$', full_path):
430 opts = opts + [ 'fly','fragment']
431 elif re.search ('.ly$', full_path):
432 opts .append ('nofly')
434 str_opts = string.join (opts, ',')
435 if str_opts: str_opts = '[' + str_opts + ']'
438 str = ("%% copied from file `%s'\n" % full_path) + str
439 return get_output ('output-mudela') % (str_opts, str)
441 b = get_re('mudela-file').sub (mudela_file, b)
442 b = get_re('mudela').sub (mudela_short, b)
445 def find_mudela_chunks (str):
446 """Find mudela blocks, while watching for verbatim. Returns
447 (STR,MUDS) with \mudelagraphic substituted for the blocks in STR,
448 and the blocks themselves MUDS"""
452 m = get_re ("mudela-block").search( str)
454 chunks.append (('input', str))
458 chunks.append (('input', str[:m.start (0)]))
465 optlist = get_re('comma-sep').split (opts)
468 chunks.append (('mudela', body, optlist))
470 str = str [m.end (0):]
476 def advance_counters (counter, opts, str):
477 """Advance chap/sect counters,
478 revise OPTS. Return the new counter tuple"""
480 (chapter, section, count) = counter
483 m = get_re ('interesting-cs').search(str)
489 done = done + str[:m.end (0)]
494 opts.append ('twocolumn')
495 elif g == 'onecolumn':
497 current_opts.remove ('twocolumn')
501 (chapter, section, count) = (chapter + 1, 0, 0)
502 elif g == 'section' or g == 'node':
503 (section, count) = (section + 1, 0)
506 return (chapter, section, count)
509 def schedule_mudela_block (base, chunk, extra_opts):
510 """Take the body and options from CHUNK, figure out how the
511 real .ly should look, and what should be left MAIN_STR (meant
512 for the main file). The .ly is written, and scheduled in
515 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO)
517 TODO has format [basename, extension, extension, ... ]
521 (type, body, opts) = chunk
522 assert type == 'mudela'
523 opts = opts + extra_opts
526 if 'verbatim' in opts:
527 newbody = output_verbatim (body)
529 file_body = compose_full_body (body, opts)
530 updated = update_file (file_body, base + '.ly')
533 if not os.path.isfile (base + '.tex') or updated:
538 m = re.search ('intertext="(.*?)"', o)
540 newbody = newbody + m.group (1)
548 if 'eps' in opts and ('tex' in todo or
549 not os.path.isfile (base + '.eps')):
552 if 'png' in opts and ('eps' in todo or
553 not os.path.isfile (base + '.png')):
556 if format == 'latex':
558 newbody = newbody + get_output ('output-eps') % (base, base)
560 newbody = newbody + get_output ('output-tex') % base
562 elif format == 'texi':
563 newbody = newbody + get_output ('output-all') % (base, base)
567 return ('mudela', newbody, opts, todo)
569 def find_eps_dims (match):
570 "Fill in dimensions of EPS files."
573 dims = bounding_box_dimensions (fn)
575 return '%ipt' % dims[0]
578 def print_chunks (ch):
580 print '-->%s\n%s' % (c[0], c[1])
582 print '==>%s' % list (c[2:])
586 def transform_input_file (in_filename, out_filename):
587 """Read the input, and deliver a list of chunks
591 chunks = read_doc_file (in_filename)
593 #. Process \mudela and \mudelafile.
594 for func in [find_mudela_shorthand_chunks,
599 newchunks = newchunks + func (c[1])
606 opts = scan_preamble (chunks[0][1])
608 (chap,sect,count) = (0,0,0)
610 # Count sections/chapters.
613 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
614 elif c[0] == 'mudela':
615 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
617 c = schedule_mudela_block (base, c, opts)
625 if __main__.run_lilypond:
626 compile_all_files (chunks)
630 if c[0] == 'mudela' and 'eps' in c[2]:
631 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
632 newchunks.append (('mudela', body))
637 if chunks and chunks[0][0] == 'input':
638 chunks[0] = ('input', completize_preamble (chunks[0][1]))
643 sys.stderr.write ("invoking `%s'\n" % cmd)
646 error ('Error command exited with value %d\n' % st)
649 def compile_all_files (chunks):
663 tex.append (base + '.ly')
668 lilyopts = map (lambda x: '-I ' + x, include_path)
669 lilyopts = string.join (lilyopts, ' ' )
670 texfiles = string.join (tex, ' ')
671 system ('lilypond %s %s' % (lilyopts, texfiles))
674 cmd = r"""tex %s; dvips -E -o %s %s""" % \
679 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
681 cmd = cmd % (g + '.eps', g + '.png')
685 def update_file (body, name):
695 f = open (name , 'w')
704 def getopt_args (opts):
705 "Construct arguments (LONG, SHORT) for getopt from list of options."
720 def option_help_str (o):
721 "Transform one option description (4-tuple ) into neatly formatted string"
739 return ' ' + sh + sep + long + arg
742 def options_help_str (opts):
743 "Convert a list of options into a neatly formatted string"
748 s = option_help_str (o)
749 strs.append ((s, o[3]))
755 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
759 sys.stdout.write("""Usage: mudela-book [options] FILE\n
760 Generate hybrid LaTeX input from Latex + mudela
763 sys.stdout.write (options_help_str (options))
764 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
768 Report bugs to bug-gnu-music@gnu.org.
770 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
771 Han-Wen Nienhuys <hanwen@cs.uu.nl>
777 def write_deps (fn, target, deps):
778 sys.stdout.write('writing `%s\'\n' % fn)
782 target = target + '.latex'
783 f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
788 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
790 def print_version ():
792 sys.stdout.write (r"""Copyright 1998--1999
793 Distributed under terms of the GNU General Public License. It comes with
799 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
802 (sh, long) = getopt_args (__main__.options)
803 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
804 except getopt.error, msg:
805 sys.stderr.write("error: %s" % msg)
813 if o == '--include' or o == '-I':
814 include_path.append (a)
815 elif o == '--version':
819 elif o == '--format' or o == '-o':
821 elif o == '--outname' or o == '-o':
824 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
827 elif o == '--outdir' or o == '-d':
829 elif o == '--help' or o == '-h':
831 elif o == '--no-lily' or o == '-n':
832 __main__.run_lilypond = 0
833 elif o == '--dependencies':
835 elif o == '--default-mudela-fontsize':
836 default_music_fontsize = string.atoi (a)
842 for input_filename in files:
847 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
848 my_depname = my_outname + '.dep'
850 chunks = transform_input_file (input_filename, my_outname)
852 foutn = my_outname + '.' + format
853 sys.stderr.write ("Writing `%s'\n" % foutn)
854 fout = open (foutn, 'w')
860 # write_deps (my_depname, my_outname, deps)
861 sys.stderr.write ("--dependencies broken")
868 # Petr, ik zou willen dat ik iets zinvoller deed,
869 # maar wat ik kan ik doen, het verandert toch niets?