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' )
63 # format specific strings, ie. regex-es for input, and % strings for output
66 'output-mudela-fragment' : r"""\begin[eps,fragment%s]{mudela}
73 'output-mudela':r"""\begin[%s]{mudela}
76 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
77 'output-default-post': r"""\def\postMudelaExample{}""",
78 'output-default-pre': r"""\def\preMudelaExample{}""",
79 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%s.eps}}{\includegraphics{%s.eps}}',
80 'output-tex': '\\preMudelaExample \\input %s.tex \\postMudelaExample\n'
82 'texi' : {'output-mudela': """@mudela[%s]
86 'output-verbatim': r"""@example
91 # do some tweaking: @ is needed in some ps stuff.
92 # override EndLilyPondOutput, since @tex is done
93 # in a sandbox, you can't do \input lilyponddefs at the
94 # top of the document.
95 'output-all': r"""@tex
98 \def\EndLilyPondOutput{}
109 def output_verbatim (body):
110 if __main__.format == 'texi':
111 body = re.sub ('([@{}])', '@\\1', body)
112 return get_output ('output-verbatim') % body
115 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
116 'include': '\\\\mbinclude{([^}]+)}',
119 'header': r"""\\documentclass(\[.*?\])?""",
120 'preamble-end': '\\\\begin{document}',
121 'verbatim': r"""(?s)\\begin{verbatim}(.*?)\\end{verbatim}""",
122 'verb': r"""\\verb(.)(.*?)\1""",
123 'mudela-file': '\\\\mudelafile(\[[^\\]]+\])?{([^}]+)}',
124 'mudela' : '\\\\mudela(\[.*?\])?{(.*?)}',
125 'mudela-block': r"""(?s)\\begin(\[.*?\])?{mudela}(.*?)\\end{mudela}""",
126 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
127 'def-post-re': r"""\\def\\postMudelaExample""",
128 'def-pre-re': r"""\\def\\preMudelaExample""",
132 'input': '@mbinclude[ \n\t]+([^\t \n]*)',# disabled
135 'preamble-end': no_match,
136 'verbatim': r"""(?s)@example(.*?)@end example$""",
137 'verb': r"""@code{(.*?)}""",
138 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
139 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
140 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
141 'interesting-cs': r"""[\\@](chapter|section)""",
147 for r in re_dict.keys ():
150 for k in olddict.keys ():
151 newdict[k] = re.compile (olddict[k])
165 def get_output (name):
166 return output_dict[format][name]
169 return re_dict[format][name]
172 def bounding_box_dimensions(fname):
176 error ("Error opening `%s'" % fname)
178 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
180 return (int(s.group(3))-int(s.group(1)),
181 int(s.group(4))-int(s.group(2)))
186 def find_file (name):
187 for a in include_path:
189 nm = os.path.join (a, name)
197 sys.stderr.write (str + "\n Exiting ... \n\n")
201 def compose_full_body (body, opts):
202 "Construct the text of an input file: add stuff to BODY using OPTS as options."
204 music_size = default_music_fontsize
205 latex_size = default_text_fontsize
209 m = re.search ('^(.*)paper$', o)
214 m = re.match ('([0-9]+)pt', o)
216 music_size = string.atoi(m.group (1))
218 m = re.match ('latexfontsize=([0-9]+)pt', o)
220 latex_size = string.atoi (m.group (1))
224 if 'twocolumn' in opts:
228 # urg: breaks on \include of full score
229 # Use nofly option if you want to \include full score.
230 if 'nofly' not in opts and not re.search ('\\\\score', body):
231 opts.append ('fragment')
233 if 'fragment' in opts and 'nosingleline' not in opts:
234 opts.append ('singleline')
236 if 'singleline' in opts:
239 l = latex_linewidths[cols][paper][latex_size]
242 if 'relative' in opts:
243 body = '\\relative c { %s }' % body
246 if 'fragment' in opts:
253 optstring = string.join (opts, ' ')
254 optstring = re.sub ('\n', ' ', optstring)
257 %% Generated by mudela-book.py; options are %s
258 \include "paper%d.ly"
259 \paper { linewidth = %f \pt; }
260 """ % (optstring, music_size, l) + body
264 def find_inclusion_chunks (regex, surround, str):
267 m = regex.search (str)
270 chunks.append (('input', str))
274 chunks.append (('input', str[: m.start (0)]))
275 chunks.append (('input', surround))
276 chunks = chunks + read_doc_file (m.group (1))
277 chunks.append (('input', surround))
279 str = str [m.end (0):]
282 def find_include_chunks (str):
283 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
285 def find_input_chunks (str):
286 return find_inclusion_chunks (get_re ('input'), '', str)
288 def read_doc_file (filename):
289 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
291 for ext in ['', '.tex', '.doc', '.tely']:
293 f = open(filename+ ext)
300 error ("File not found `%s'\n" % filename)
304 if __main__.format == '':
305 latex = re.search ('\\\\document', str[:200])
306 texinfo = re.search ('@node', str[:200])
307 if (texinfo and latex) or not (texinfo or latex):
308 error("error: can't determine format, please specify")
310 __main__.format = 'texi'
312 __main__.format = 'latex'
314 chunks = [('input', str)]
316 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
321 newchunks = newchunks + ch
330 def scan_preamble (str):
332 m = get_re ('header').search( str)
334 # should extract paper & fontsz.
335 if m and m.group (1):
336 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
338 def verbose_fontsize ( x):
340 #if o.match('[0-9]+pt'):
341 if re.match('[0-9]+pt', x):
342 return 'latexfontsize=' + x
346 options = map (verbose_fontsize, options)
351 def completize_preamble (str):
352 m = get_re ('preamble-end').search( str)
356 preamble = str [:m.start (0)]
357 str = str [m.start(0):]
359 if not get_re('def-post-re').search (preamble):
360 preamble = preamble + get_output('output-default-post')
361 if not get_re ('def-pre-re').search( preamble):
362 preamble = preamble + get_output ('output-default-pre')
365 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
367 preamble = preamble + '\\usepackage{graphics}\n'
369 return preamble + str
371 def find_verbatim_chunks (str):
372 """Chop STR into a list of tagged chunks, ie. tuples of form
373 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
378 m = get_re ('verbatim').search( str)
380 chunks.append( ('input', str))
383 chunks.append (('input', str[:m.start (0)]))
384 chunks.append (('verbatim', m.group (0)))
386 str = str [m.end(0):]
390 def find_verb_chunks (str):
394 m = get_re ("verb").search(str)
396 chunks.append (('input', str))
399 chunks.append (('input', str[:m.start (0)]))
400 chunks.append (('verbatim', m.group (0)))
401 str = str [m.end(0):]
407 def find_mudela_shorthand_chunks (str):
408 return [('input', find_mudela_shorthands(str))]
410 def find_mudela_shorthands (b):
411 def mudela_short (match):
412 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
413 opts = match.group (1)
415 opts = ',' + opts[1:-1]
418 return get_output ('output-mudela-fragment') % (opts, match.group (2))
420 def mudela_file (match):
421 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
422 d = [] #, d = retdeps
423 full_path = find_file (match.group (2))
425 error("error: can't find file `%s'\n" % match.group(2))
430 opts = match.group (1)
433 opts = re.split (',[ \n\t]*', opts)
437 if re.search ('.fly$', full_path):
439 elif re.search ('.sly$', full_path):
440 opts = opts + [ 'fly','fragment']
441 elif re.search ('.ly$', full_path):
442 opts .append ('nofly')
444 str_opts = string.join (opts, ',')
446 str = ("%% copied from file `%s'\n" % full_path) + str
447 return get_output ('output-mudela') % (str_opts, str)
449 b = get_re('mudela-file').sub (mudela_file, b)
450 b = get_re('mudela').sub (mudela_short, b)
453 def find_mudela_chunks (str):
454 """Find mudela blocks, while watching for verbatim. Returns
455 (STR,MUDS) with substituted for the blocks in STR,
456 and the blocks themselves MUDS"""
460 m = get_re ("mudela-block").search( str)
462 chunks.append (('input', str))
466 chunks.append (('input', str[:m.start (0)]))
473 optlist = get_re('comma-sep').split (opts)
476 chunks.append (('mudela', body, optlist))
478 str = str [m.end (0):]
484 def advance_counters (counter, opts, str):
485 """Advance chap/sect counters,
486 revise OPTS. Return the new counter tuple"""
488 (chapter, section, count) = counter
491 m = get_re ('interesting-cs').search(str)
497 done = done + str[:m.end (0)]
502 opts.append ('twocolumn')
503 elif g == 'onecolumn':
505 opts.remove ('twocolumn')
509 (chapter, section, count) = (chapter + 1, 0, 0)
511 (section, count) = (section + 1, 0)
514 return (chapter, section, count)
517 def schedule_mudela_block (base, chunk, extra_opts):
518 """Take the body and options from CHUNK, figure out how the
519 real .ly should look, and what should be left MAIN_STR (meant
520 for the main file). The .ly is written, and scheduled in
523 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
525 TODO has format [basename, extension, extension, ... ]
529 (type, body, opts) = chunk
530 assert type == 'mudela'
531 opts = opts + extra_opts
534 if 'verbatim' in opts:
535 newbody = output_verbatim (body)
537 file_body = compose_full_body (body, opts)
539 if __main__.use_hash:
540 basename = `abs(hash (file_body))`
541 updated = update_file (file_body, basename + '.ly')
542 todo = [basename] # UGH.
544 if not os.path.isfile (basename + '.tex') or updated:
549 m = re.search ('intertext="(.*?)"', o)
551 newbody = newbody + m.group (1)
559 if 'eps' in opts and ('tex' in todo or
560 not os.path.isfile (basename + '.eps')):
563 if 'png' in opts and ('eps' in todo or
564 not os.path.isfile (basename + '.png')):
567 if format == 'latex':
569 newbody = newbody + get_output ('output-eps') % (basename, basename)
571 newbody = newbody + get_output ('output-tex') % basename
573 elif format == 'texi':
574 newbody = newbody + get_output ('output-all') % (basename, basename)
576 return ('mudela', newbody, opts, todo, base)
579 def find_eps_dims (match):
580 "Fill in dimensions of EPS files."
583 dims = bounding_box_dimensions (fn)
585 return '%ipt' % dims[0]
588 def print_chunks (ch):
590 print '-->%s\n%s' % (c[0], c[1])
592 print '==>%s' % list (c[2:])
596 def transform_input_file (in_filename, out_filename):
597 """Read the input, and deliver a list of chunks
601 chunks = read_doc_file (in_filename)
603 #. Process \mudela and \mudelafile.
604 for func in [find_mudela_shorthand_chunks,
609 newchunks = newchunks + func (c[1])
616 opts = scan_preamble (chunks[0][1])
618 (chap,sect,count) = (0,0,0)
620 # Count sections/chapters.
623 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
624 elif c[0] == 'mudela':
625 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
627 c = schedule_mudela_block (base, c, opts)
635 if __main__.run_lilypond:
636 compile_all_files (chunks)
640 if c[0] == 'mudela' and 'eps' in c[2]:
641 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
642 newchunks.append (('mudela', body))
647 if chunks and chunks[0][0] == 'input':
648 chunks[0] = ('input', completize_preamble (chunks[0][1]))
653 sys.stderr.write ("invoking `%s'\n" % cmd)
656 error ('Error command exited with value %d\n' % st)
659 def compile_all_files (chunks):
674 tex.append (base + '.ly')
678 if __main__.use_hash:
679 hash_dict[c[4]] = c[3][0]
682 lilyopts = map (lambda x: '-I ' + x, include_path)
683 lilyopts = string.join (lilyopts, ' ' )
684 texfiles = string.join (tex, ' ')
685 system ('lilypond %s %s' % (lilyopts, texfiles))
688 cmd = r"""tex %s; dvips -E -o %s %s""" % \
693 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
695 cmd = cmd % (g + '.eps', g + '.png')
698 if __main__.use_hash:
702 ks = hash_dict.keys ()
705 name = re.sub ("(.*)-[0-9]+\.[0-9]+\.[0-9]+", "\\1", i)
707 if name != last_name:
712 f.write ("%s:%s\n" % (i, hash_dict[i]))
715 def update_file (body, name):
725 f = open (name , 'w')
734 def getopt_args (opts):
735 "Construct arguments (LONG, SHORT) for getopt from list of options."
750 def option_help_str (o):
751 "Transform one option description (4-tuple ) into neatly formatted string"
769 return ' ' + sh + sep + long + arg
772 def options_help_str (opts):
773 "Convert a list of options into a neatly formatted string"
778 s = option_help_str (o)
779 strs.append ((s, o[3]))
785 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
789 sys.stdout.write("""Usage: mudela-book [options] FILE\n
790 Generate hybrid LaTeX input from Latex + mudela
793 sys.stdout.write (options_help_str (options))
794 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
798 Report bugs to bug-gnu-music@gnu.org.
800 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
801 Han-Wen Nienhuys <hanwen@cs.uu.nl>
807 def write_deps (fn, target, deps):
808 sys.stdout.write('writing `%s\'\n' % fn)
812 target = target + '.latex'
813 f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
818 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
820 def print_version ():
822 sys.stdout.write (r"""Copyright 1998--1999
823 Distributed under terms of the GNU General Public License. It comes with
829 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
832 (sh, long) = getopt_args (__main__.options)
833 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
834 except getopt.error, msg:
835 sys.stderr.write("error: %s" % msg)
843 if o == '--include' or o == '-I':
844 include_path.append (a)
845 elif o == '--version':
849 elif o == '--format' or o == '-o':
851 elif o == '--outname' or o == '-o':
854 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
857 elif o == '--outdir' or o == '-d':
859 elif o == '--help' or o == '-h':
861 elif o == '--no-lily' or o == '-n':
862 __main__.run_lilypond = 0
863 elif o == '--dependencies':
865 elif o == '--default-mudela-fontsize':
866 default_music_fontsize = string.atoi (a)
872 for input_filename in files:
877 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
878 my_depname = my_outname + '.dep'
880 chunks = transform_input_file (input_filename, my_outname)
882 foutn = my_outname + '.' + format
883 sys.stderr.write ("Writing `%s'\n" % foutn)
884 fout = open (foutn, 'w')
890 # write_deps (my_depname, my_outname, deps)
891 sys.stderr.write ("--dependencies broken")
898 # Petr, ik zou willen dat ik iets zinvoller deed,
899 # maar wat ik kan ik doen, het verandert toch niets?