12 program_version = '@TOPLEVEL_VERSION@'
17 # TODO: use splitting iso. \mudelagraphic.
20 default_music_fontsize = 16
21 default_text_fontsize = 12
24 # indices are no. of columns, papersize, fontsize
25 # Why can't this be calculated?
27 1: {'a4':{10: 345, 11: 360, 12: 390},
28 'a5':{10: 276, 11: 276, 12: 276},
29 'b5':{10: 345, 11: 356, 12: 356},
30 'letter':{10: 345, 11: 360, 12: 390},
31 'legal': {10: 345, 11: 360, 12: 390},
32 'executive':{10: 345, 11: 360, 12: 379}},
33 2: {'a4':{10: 167, 11: 175, 12: 190},
34 'a5':{10: 133, 11: 133, 12: 133},
35 'b5':{10: 167, 11: 173, 12: 173},
36 'letter':{10: 167, 11: 175, 12: 190},
37 'legal':{10: 167, 11: 175, 12: 190},
38 'executive':{10: 167, 11: 175, 12: 184}}}
42 ('DIM', '', 'default-mudela-fontsize', 'default fontsize for music. DIM is assumed to in points'),
43 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
44 ('', 'h', 'help', 'print help'),
45 ('DIR', 'I', 'include', 'include path'),
46 ('', '', 'init', 'mudela-book initfile'),
47 # ('DIM', '', 'force-mudela-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
48 ('', '', 'force-verbatim', 'make all mudela verbatim'),
49 ('', 'M', 'dependencies', 'write dependencies'),
50 ('', 'n', 'no-lily', 'don\'t run lilypond'),
51 ('FILE', 'o', 'outname', 'prefix for filenames'),
52 ('', 'v', 'version', 'print version information' )
59 # format specific strings, ie. regex-es for input, and % strings for output
62 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
69 'output-mudela':r"""\begin%s{mudela}
72 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
73 'output-default-post': r"""\def\postMudelaExample{}""",
74 'output-default-pre': r"""\def\preMudelaExample{}""",
75 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
76 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
78 'texi' : {'output-mudela': """@mudela[%s]
82 'output-verbatim': r"""@example
86 'output-all': r"""@tex
97 'latex': {'input': '\\\\input{?([^}\t \n}]*)',
98 'include': '\\\\include{([^}]+)}',
101 'header': r"""\\documentclass(\[.*?\])?""",
102 'preamble-end': '\\\\begin{document}',
103 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
104 'verb': r"""\\verb(.)(.*?)\1""",
105 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
106 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
107 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
108 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
109 'def-post-re': r"""\\def\\postMudelaExample""",
110 'def-pre-re': r"""\\def\\preMudelaExample""",
112 'texi': {'input': '@include[ \n\t]+([^\t \n]*)',
115 'preamble-end': no_match,
116 'verbatim': r"""(?s)@example(.*?)@end example$""",
117 'verb': r"""@code{(.*?)}""",
118 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
119 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
120 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
121 'interesting-cs': r"""[\\@](node|mudelagraphic)""",
127 for r in re_dict.keys ():
130 for k in olddict.keys ():
131 newdict[k] = re.compile (olddict[k])
135 def get_output (name):
136 return output_dict[format][name]
139 return re_dict[format][name]
142 def bounding_box_dimensions(fname):
146 error ("Error opening `%s'" % fname)
148 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
150 return (int(s.group(3))-int(s.group(1)),
151 int(s.group(4))-int(s.group(2)))
156 def find_file (name):
157 for a in include_path:
159 nm = os.path.join (a, name)
167 sys.stderr.write (str + "\n Exiting ... \n\n")
171 def compose_full_body (body, opts):
172 "Construct the text of an input file: add stuff to BODY using OPTS as options."
174 music_size = default_music_fontsize
175 latex_size = default_text_fontsize
179 m = re.search ('^(.*)paper$', o)
184 m = re.match ('([0-9]+)pt', o)
186 music_size = string.atoi(m.group (1))
188 m = re.match ('latexfontsize=([0-9]+)pt', o)
190 latex_size = string.atoi (m.group (1))
193 if 'twocolumn' in opts:
196 if 'fragment' or 'singleline' in opts:
199 l = latex_linewidths[cols][paper][latex_size]
201 # urg: breaks on \include of full score
202 # Use nofly option if you want to \include full score.
203 if not 'nofly' in opts and not re.search ('\\\\score', body):
216 %% Generated by mudela-book.py
217 \include "paper%d.ly"
218 \paper { linewidth = %f \pt; }
219 """ % (music_size, l) + body
223 def inclusion_func (match, surround):
224 insert = match.group (0)
226 (insert, d) = read_doc_file (match.group(1))
228 insert = surround + insert + surround
230 sys.stderr.write("warning: can't find %s, let's hope latex will\n" % m.group(1))
232 return (insert, deps)
234 def find_inclusion_chunks (regex, surround, str):
237 m = regex.search (str)
240 chunks.append (('input', str))
244 chunks.append (('input', str[: m.start (0)]))
245 chunks.append (('input', surround))
246 chunks = chunks + read_doc_file (m.group (1))
247 chunks.append (('input', surround))
249 str = str [m.end (0):]
252 def find_include_chunks (str):
253 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
255 def find_input_chunks (str):
256 return find_inclusion_chunks (get_re ('input'), '', str)
258 def read_doc_file (filename):
259 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
261 for fn in [filename, filename+'.tex', filename+'.doc']:
274 if __main__.format == '':
275 latex = re.search ('\\\\document', str[:200])
276 texinfo = re.search ('@node', str[:200])
277 if (texinfo and latex) or not (texinfo or latex):
278 error("error: can't determine format, please specify")
280 __main__.format = 'texi'
282 __main__.format = 'latex'
284 chunks = find_verbatim_chunks (str)
287 for func in (find_include_chunks, find_input_chunks):
291 newchunks = newchunks + ch
300 def scan_preamble (str):
302 m = get_re ('header').search( str)
304 # should extract paper & fontsz.
305 if m and m.group (1):
306 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
308 def verbose_fontsize ( x):
310 #if o.match('[0-9]+pt'):
311 if re.match('[0-9]+pt', x):
312 return 'latexfontsize=' + x
316 options = map (verbose_fontsize, options)
321 def completize_preamble (str):
322 m = get_re ('preamble-end').search( str)
326 preamble = str [:m.start (0)]
327 str = str [m.start(0):]
329 if not get_re('def-post-re').search (preamble):
330 preamble = preamble + get_output('output-default-post')
331 if not get_re ('def-pre-re').search( preamble):
332 preamble = preamble + get_output ('output-default-pre')
335 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
337 preamble = preamble + '\\usepackage{graphics}\n'
339 return preamble + str
341 def find_verbatim_chunks (str):
342 """Chop STR into a list of tagged chunks, ie. tuples of form
343 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
350 m = get_re ('verbatim').search( str)
351 m2 = get_re ("verb").search( str)
353 if m == None and m2 == None:
354 chunks.append (('input', str))
361 if m2 and m2.start (0) < m.start (0):
364 chunks.append (('input', str[:m.start (0)]))
365 chunks.append (('verbatim', m.group (0)))
367 str = str [m.end(0):]
371 def find_mudela_shorthand_chunks (str):
372 return [('input', find_mudela_shorthands(str))]
374 def find_mudela_shorthands (b):
375 def mudela_short (match):
376 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
377 opts = match.group (1)
379 opts = ',' + opts[1:-1]
382 return get_output ('output-mudela-fragment') % (opts, match.group (2))
384 def mudela_file (match):
385 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
386 d = [] #, d = retdeps
387 full_path = find_file (match.group (2))
389 error("error: can't find file `%s'\n" % match.group(2))
394 opts = match.group (1)
396 opts = re.split (',[ \n\t]*', opts[1:-1])
400 if re.search ('.fly$', full_path):
402 elif re.search ('.sly$', full_path):
403 opts = opts + [ 'fly','fragment']
404 elif re.search ('.ly$', full_path):
405 opts .append ('nofly')
407 str_opts = string.join (opts, ',')
408 if str_opts: str_opts = '[' + str_opts + ']'
411 str = ("%% copied from file `%s'\n" % full_path) + str
412 return get_output ('output-mudela') % (str_opts, str)
414 b = get_re('mudela-file').sub (mudela_file, b)
415 b = get_re('mudela').sub (mudela_short, b)
418 def find_mudela_chunks (str):
419 """Find mudela blocks, while watching for verbatim. Returns
420 (STR,MUDS) with \mudelagraphic substituted for the blocks in STR,
421 and the blocks themselves MUDS"""
425 m = get_re ("mudela-block").search( str)
427 chunks.append (('input', str))
431 chunks.append (('input', str[:m.start (0)]))
438 optlist = get_re('comma-sep').split (opts)
441 chunks.append (('mudela', body, optlist))
443 str = str [m.end (0):]
449 def advance_counters (counter, opts, str):
450 """Advance chap/sect counters,
451 revise OPTS. Return the new counter tuple"""
453 (chapter, section, count) = counter
456 m = get_re ('interesting-cs').search(str)
462 done = done + str[:m.end (0)]
467 opts.append ('twocolumn')
468 elif g == 'onecolumn':
470 current_opts.remove ('twocolumn')
474 (chapter, section, count) = (chapter + 1, 0, 0)
475 elif g == 'section' or g == 'node':
476 (section, count) = (section + 1, 0)
479 return (chapter, section, count)
482 def schedule_mudela_block (base, chunk, extra_opts):
484 """Take the body and options from CHUNK, figure out how the
485 real .ly should look, and what should be left MAIN_STR (meant
486 for the main file). The .ly is written, and scheduled in
489 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO)
491 TODO has format [basename, extension, extension, ... ]
495 (type, body, opts) = chunk
496 assert type == 'mudela'
497 opts = opts + extra_opts
500 if 'verbatim' in opts:
501 verbatim_mangle = body
502 if __main__.format == 'texi':
503 verbatim_mangle = re.sub ('([{}])', '@\\1', body)
504 newbody = get_output ('output-verbatim') % verbatim_mangle
506 file_body = compose_full_body (body, opts)
507 updated = update_file (file_body, base + '.ly')
510 if not os.path.isfile (base + '.tex') or updated:
515 m = re.search ('intertext="(.*?)"', o)
517 newbody = newbody + m.group (1)
525 if 'eps' in opts and ('tex' in todo or
526 not os.path.isfile (base + '.eps')):
529 if 'png' in opts and ('eps' in todo or
530 not os.path.isfile (base + '.png')):
533 if format == 'latex':
535 newbody = newbody + get_output ('output-eps') % (base, base)
537 newbody = newbody + get_output ('output-tex') % base
539 elif format == 'texi':
540 newbody = newbody + get_output ('output-all') % (base, base)
544 return ('mudela', newbody, opts, todo)
546 def find_eps_dims (match):
547 "Fill in dimensions of EPS files."
550 dims = bounding_box_dimensions (fn)
552 return '%ipt' % dims[0]
555 def print_chunks (ch):
557 print '-->%s\n%s' % (c[0], c[1])
559 print '==>%s' % list (c[2:])
563 def transform_input_file (in_filename, out_filename):
564 """Read the input, and deliver a list of chunks
568 chunks = read_doc_file (in_filename)
570 #. Process \mudela and \mudelafile.
571 for func in [find_mudela_shorthand_chunks,
576 newchunks = newchunks + func (c[1])
583 opts = scan_preamble (chunks[0][1])
585 (chap,sect,count) = (0,0,0)
587 # Count sections/chapters.
590 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
591 elif c[0] == 'mudela':
592 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
594 c = schedule_mudela_block (base, c, opts)
602 if __main__.run_lilypond:
603 compile_all_files (chunks)
607 if c[0] == 'mudela' and 'eps' in c[2]:
608 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
609 newchunks.append (('mudela', body))
615 if chunks and chunks[0][0] == 'input':
616 chunks[0] = ('input', completize_preamble (chunks[0][1]))
621 sys.stderr.write ("invoking `%s'\n" % cmd)
624 sys.stderr.write ('Error command exited with value %d\n' % st)
627 def compile_all_files (chunks):
641 tex.append (base + '.ly')
646 lilyopts = map (lambda x: '-I ' + x, include_path)
647 lilyopts = string.join (lilyopts, ' ' )
648 texfiles = string.join (tex, ' ')
649 system ('lilypond %s %s' % (lilyopts, texfiles))
652 cmd = r"""tex %s; dvips -E -o %s %s""" % \
657 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
659 cmd = cmd % (g + '.eps', g + '.png')
663 def update_file (body, name):
673 f = open (name , 'w')
682 def getopt_args (opts):
683 "Construct arguments (LONG, SHORT) for getopt from list of options."
698 def option_help_str (o):
699 "Transform one option description (4-tuple ) into neatly formatted string"
717 return ' ' + sh + sep + long + arg
720 def options_help_str (opts):
721 "Convert a list of options into a neatly formatted string"
726 s = option_help_str (o)
727 strs.append ((s, o[3]))
733 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
737 sys.stdout.write("""Usage: mudela-book [options] FILE\n
738 Generate hybrid LaTeX input from Latex + mudela
741 sys.stdout.write (options_help_str (options))
742 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
746 Report bugs to bug-gnu-music@gnu.org.
748 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
749 Han-Wen Nienhuys <hanwen@cs.uu.nl>
755 def write_deps (fn, target, deps):
756 sys.stdout.write('writing `%s\'\n' % fn)
760 target = target + '.latex'
761 f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
766 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
768 def print_version ():
770 sys.stdout.write (r"""Copyright 1998--1999
771 Distributed under terms of the GNU General Public License. It comes with
777 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
780 (sh, long) = getopt_args (__main__.options)
781 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
782 except getopt.error, msg:
783 sys.stderr.write("error: %s" % msg)
791 if o == '--include' or o == '-I':
792 include_path.append (a)
793 elif o == '--version':
797 elif o == '--format' or o == '-o':
799 elif o == '--outname' or o == '-o':
802 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
805 elif o == '--outdir' or o == '-d':
807 elif o == '--help' or o == '-h':
809 elif o == '--no-lily' or o == '-n':
810 __main__.run_lilypond = 0
811 elif o == '--dependencies':
813 elif o == '--default-mudela-fontsize':
814 default_music_fontsize = string.atoi (a)
820 for input_filename in files:
825 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
826 my_depname = my_outname + '.dep'
828 chunks = transform_input_file (input_filename, my_outname)
830 foutn = my_outname + '.' + format
831 sys.stderr.write ("Writing `%s'\n" % foutn)
832 fout = open (foutn, 'w')
838 # write_deps (my_depname, my_outname, deps)
839 sys.stderr.write ("--dependencies broken")
846 # Petr, ik zou willen dat ik iets zinvoller deed,
847 # maar wat ik kan ik doen, het verandert toch niets?