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': '\\\\input{?([^}\t \n}]*)',
116 'include': '\\\\include{([^}]+)}',
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""",
130 'texi': {'input': '@include[ \n\t]+([^\t \n]*)',
133 'preamble-end': no_match,
134 'verbatim': r"""(?s)@example(.*?)@end example$""",
135 'verb': r"""@code{(.*?)}""",
136 'mudela-file': '@mudelafile(\[[^\\]]+\])?{([^}]+)}',
137 'mudela' : '@mudela(\[.*?\])?{(.*?)}',
138 'mudela-block': r"""(?s)@mudela(\[.*?\])?(.*?)@end mudela""",
139 'interesting-cs': r"""[\\@](chapter|section)""",
145 for r in re_dict.keys ():
148 for k in olddict.keys ():
149 newdict[k] = re.compile (olddict[k])
163 def get_output (name):
164 return output_dict[format][name]
167 return re_dict[format][name]
170 def bounding_box_dimensions(fname):
174 error ("Error opening `%s'" % fname)
176 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
178 return (int(s.group(3))-int(s.group(1)),
179 int(s.group(4))-int(s.group(2)))
184 def find_file (name):
185 for a in include_path:
187 nm = os.path.join (a, name)
195 sys.stderr.write (str + "\n Exiting ... \n\n")
199 def compose_full_body (body, opts):
200 "Construct the text of an input file: add stuff to BODY using OPTS as options."
202 music_size = default_music_fontsize
203 latex_size = default_text_fontsize
207 m = re.search ('^(.*)paper$', o)
212 m = re.match ('([0-9]+)pt', o)
214 music_size = string.atoi(m.group (1))
216 m = re.match ('latexfontsize=([0-9]+)pt', o)
218 latex_size = string.atoi (m.group (1))
221 if 'twocolumn' in opts:
225 # urg: breaks on \include of full score
226 # Use nofly option if you want to \include full score.
227 if 'nofly' not in opts and not re.search ('\\\\score', body):
228 opts.append ('fragment')
230 if 'fragment' in opts and 'nosingleline' not in opts:
231 opts.append ('singleline')
233 if 'singleline' in opts:
236 l = latex_linewidths[cols][paper][latex_size]
239 if 'relative' in opts:
240 body = '\\relative c { %s }' % body
243 if 'fragment' in opts:
250 optstring = string.join (opts, ' ')
251 optstring = re.sub ('\n', ' ', optstring)
254 %% Generated by mudela-book.py; options are %s
255 \include "paper%d.ly"
256 \paper { linewidth = %f \pt; }
257 """ % (optstring, music_size, l) + body
261 def find_inclusion_chunks (regex, surround, str):
264 m = regex.search (str)
267 chunks.append (('input', str))
271 chunks.append (('input', str[: m.start (0)]))
272 chunks.append (('input', surround))
273 chunks = chunks + read_doc_file (m.group (1))
274 chunks.append (('input', surround))
276 str = str [m.end (0):]
279 def find_include_chunks (str):
280 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
282 def find_input_chunks (str):
283 return find_inclusion_chunks (get_re ('input'), '', str)
285 def read_doc_file (filename):
286 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
288 for ext in ['', '.tex', '.doc', '.tely']:
290 f = open(filename+ ext)
297 error ("File not found `%s'\n" % filename)
301 if __main__.format == '':
302 latex = re.search ('\\\\document', str[:200])
303 texinfo = re.search ('@node', str[:200])
304 if (texinfo and latex) or not (texinfo or latex):
305 error("error: can't determine format, please specify")
307 __main__.format = 'texi'
309 __main__.format = 'latex'
311 chunks = [('input', str)]
313 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
318 newchunks = newchunks + ch
327 def scan_preamble (str):
329 m = get_re ('header').search( str)
331 # should extract paper & fontsz.
332 if m and m.group (1):
333 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
335 def verbose_fontsize ( x):
337 #if o.match('[0-9]+pt'):
338 if re.match('[0-9]+pt', x):
339 return 'latexfontsize=' + x
343 options = map (verbose_fontsize, options)
348 def completize_preamble (str):
349 m = get_re ('preamble-end').search( str)
353 preamble = str [:m.start (0)]
354 str = str [m.start(0):]
356 if not get_re('def-post-re').search (preamble):
357 preamble = preamble + get_output('output-default-post')
358 if not get_re ('def-pre-re').search( preamble):
359 preamble = preamble + get_output ('output-default-pre')
362 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
364 preamble = preamble + '\\usepackage{graphics}\n'
366 return preamble + str
368 def find_verbatim_chunks (str):
369 """Chop STR into a list of tagged chunks, ie. tuples of form
370 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
375 m = get_re ('verbatim').search( str)
377 chunks.append( ('input', str))
380 chunks.append (('input', str[:m.start (0)]))
381 chunks.append (('verbatim', m.group (0)))
383 str = str [m.end(0):]
387 def find_verb_chunks (str):
391 m = get_re ("verb").search(str)
393 chunks.append (('input', str))
396 chunks.append (('input', str[:m.start (0)]))
397 chunks.append (('verbatim', m.group (0)))
398 str = str [m.end(0):]
404 def find_mudela_shorthand_chunks (str):
405 return [('input', find_mudela_shorthands(str))]
407 def find_mudela_shorthands (b):
408 def mudela_short (match):
409 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
410 opts = match.group (1)
412 opts = ',' + opts[1:-1]
415 return get_output ('output-mudela-fragment') % (opts, match.group (2))
417 def mudela_file (match):
418 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
419 d = [] #, d = retdeps
420 full_path = find_file (match.group (2))
422 error("error: can't find file `%s'\n" % match.group(2))
427 opts = match.group (1)
430 opts = re.split (',[ \n\t]*', opts)
434 if re.search ('.fly$', full_path):
436 elif re.search ('.sly$', full_path):
437 opts = opts + [ 'fly','fragment']
438 elif re.search ('.ly$', full_path):
439 opts .append ('nofly')
441 str_opts = string.join (opts, ',')
443 str = ("%% copied from file `%s'\n" % full_path) + str
444 return get_output ('output-mudela') % (str_opts, str)
446 b = get_re('mudela-file').sub (mudela_file, b)
447 b = get_re('mudela').sub (mudela_short, b)
450 def find_mudela_chunks (str):
451 """Find mudela blocks, while watching for verbatim. Returns
452 (STR,MUDS) with substituted for the blocks in STR,
453 and the blocks themselves MUDS"""
457 m = get_re ("mudela-block").search( str)
459 chunks.append (('input', str))
463 chunks.append (('input', str[:m.start (0)]))
470 optlist = get_re('comma-sep').split (opts)
473 chunks.append (('mudela', body, optlist))
475 str = str [m.end (0):]
481 def advance_counters (counter, opts, str):
482 """Advance chap/sect counters,
483 revise OPTS. Return the new counter tuple"""
485 (chapter, section, count) = counter
488 m = get_re ('interesting-cs').search(str)
494 done = done + str[:m.end (0)]
499 opts.append ('twocolumn')
500 elif g == 'onecolumn':
502 current_opts.remove ('twocolumn')
506 (chapter, section, count) = (chapter + 1, 0, 0)
508 (section, count) = (section + 1, 0)
511 return (chapter, section, count)
514 def schedule_mudela_block (base, chunk, extra_opts):
515 """Take the body and options from CHUNK, figure out how the
516 real .ly should look, and what should be left MAIN_STR (meant
517 for the main file). The .ly is written, and scheduled in
520 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
522 TODO has format [basename, extension, extension, ... ]
526 (type, body, opts) = chunk
527 assert type == 'mudela'
528 opts = opts + extra_opts
531 if 'verbatim' in opts:
532 newbody = output_verbatim (body)
534 file_body = compose_full_body (body, opts)
536 if __main__.use_hash:
537 basename = `hash (file_body)`
538 updated = update_file (file_body, basename + '.ly')
539 todo = [basename] # UGH.
541 if not os.path.isfile (basename + '.tex') or updated:
546 m = re.search ('intertext="(.*?)"', o)
548 newbody = newbody + m.group (1)
556 if 'eps' in opts and ('tex' in todo or
557 not os.path.isfile (basename + '.eps')):
560 if 'png' in opts and ('eps' in todo or
561 not os.path.isfile (basename + '.png')):
564 if format == 'latex':
566 newbody = newbody + get_output ('output-eps') % (basename, basename)
568 newbody = newbody + get_output ('output-tex') % basename
570 elif format == 'texi':
571 newbody = newbody + get_output ('output-all') % (basename, basename)
573 return ('mudela', newbody, opts, todo, base)
576 def find_eps_dims (match):
577 "Fill in dimensions of EPS files."
580 dims = bounding_box_dimensions (fn)
582 return '%ipt' % dims[0]
585 def print_chunks (ch):
587 print '-->%s\n%s' % (c[0], c[1])
589 print '==>%s' % list (c[2:])
593 def transform_input_file (in_filename, out_filename):
594 """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, deps):
805 sys.stdout.write('writing `%s\'\n' % fn)
809 target = target + '.latex'
810 f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
815 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
817 def print_version ():
819 sys.stdout.write (r"""Copyright 1998--1999
820 Distributed under terms of the GNU General Public License. It comes with
826 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
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 == '--outdir' or o == '-d':
856 elif o == '--help' or o == '-h':
858 elif o == '--no-lily' or o == '-n':
859 __main__.run_lilypond = 0
860 elif o == '--dependencies':
862 elif o == '--default-mudela-fontsize':
863 default_music_fontsize = string.atoi (a)
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, my_outname, deps)
888 sys.stderr.write ("--dependencies broken")
895 # Petr, ik zou willen dat ik iets zinvoller deed,
896 # maar wat ik kan ik doen, het verandert toch niets?