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"""[\\@](chapter|section)""",
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 'nofly' not in opts and not re.search ('\\\\score', body):
227 opts.append ('fragment')
229 if 'fragment' in opts and 'nosingleline' not in opts:
230 opts.append ('singleline')
232 if 'singleline' in opts:
235 l = latex_linewidths[cols][paper][latex_size]
238 if 'relative' in opts:
239 body = '\\relative c { %s }' % body
242 if 'fragment' in opts:
249 optstring = string.join (opts, ' ')
250 optstring = re.sub ('\n', ' ', optstring)
253 %% Generated by mudela-book.py; options are %s
254 \include "paper%d.ly"
255 \paper { linewidth = %f \pt; }
256 """ % (optstring, music_size, l) + body
260 def find_inclusion_chunks (regex, surround, str):
263 m = regex.search (str)
266 chunks.append (('input', str))
270 chunks.append (('input', str[: m.start (0)]))
271 chunks.append (('input', surround))
272 chunks = chunks + read_doc_file (m.group (1))
273 chunks.append (('input', surround))
275 str = str [m.end (0):]
278 def find_include_chunks (str):
279 return find_inclusion_chunks (get_re ('include'), '\\newpage', str)
281 def find_input_chunks (str):
282 return find_inclusion_chunks (get_re ('input'), '', str)
284 def read_doc_file (filename):
285 """Read the input file, substituting for \input, \include, \mudela{} and \mudelafile"""
287 for ext in ['', '.tex', '.doc', '.tely']:
289 f = open(filename+ ext)
296 error ("File not found `%s'\n" % filename)
300 if __main__.format == '':
301 latex = re.search ('\\\\document', str[:200])
302 texinfo = re.search ('@node', str[:200])
303 if (texinfo and latex) or not (texinfo or latex):
304 error("error: can't determine format, please specify")
306 __main__.format = 'texi'
308 __main__.format = 'latex'
310 chunks = [('input', str)]
312 for func in (find_verbatim_chunks, find_verb_chunks, find_include_chunks, find_input_chunks):
317 newchunks = newchunks + ch
326 def scan_preamble (str):
328 m = get_re ('header').search( str)
330 # should extract paper & fontsz.
331 if m and m.group (1):
332 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
334 def verbose_fontsize ( x):
336 #if o.match('[0-9]+pt'):
337 if re.match('[0-9]+pt', x):
338 return 'latexfontsize=' + x
342 options = map (verbose_fontsize, options)
347 def completize_preamble (str):
348 m = get_re ('preamble-end').search( str)
352 preamble = str [:m.start (0)]
353 str = str [m.start(0):]
355 if not get_re('def-post-re').search (preamble):
356 preamble = preamble + get_output('output-default-post')
357 if not get_re ('def-pre-re').search( preamble):
358 preamble = preamble + get_output ('output-default-pre')
361 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
363 preamble = preamble + '\\usepackage{graphics}\n'
365 return preamble + str
367 def find_verbatim_chunks (str):
368 """Chop STR into a list of tagged chunks, ie. tuples of form
369 (TYPE_STR, CONTENT_STR), where TYPE_STR is one of 'input' and 'verbatim'
374 m = get_re ('verbatim').search( str)
376 chunks.append( ('input', str))
379 chunks.append (('input', str[:m.start (0)]))
380 chunks.append (('verbatim', m.group (0)))
382 str = str [m.end(0):]
386 def find_verb_chunks (str):
390 m = get_re ("verb").search(str)
392 chunks.append (('input', str))
395 chunks.append (('input', str[:m.start (0)]))
396 chunks.append (('verbatim', m.group (0)))
397 str = str [m.end(0):]
403 def find_mudela_shorthand_chunks (str):
404 return [('input', find_mudela_shorthands(str))]
406 def find_mudela_shorthands (b):
407 def mudela_short (match):
408 "Find \mudela{}, and substitute appropriate \begin / \end blocks."
409 opts = match.group (1)
411 opts = ',' + opts[1:-1]
414 return get_output ('output-mudela-fragment') % (opts, match.group (2))
416 def mudela_file (match):
417 "Find \mudelafile, and substitute appropriate \begin / \end blocks."
418 d = [] #, d = retdeps
419 full_path = find_file (match.group (2))
421 error("error: can't find file `%s'\n" % match.group(2))
426 opts = match.group (1)
429 opts = re.split (',[ \n\t]*', opts)
433 if re.search ('.fly$', full_path):
435 elif re.search ('.sly$', full_path):
436 opts = opts + [ 'fly','fragment']
437 elif re.search ('.ly$', full_path):
438 opts .append ('nofly')
440 str_opts = string.join (opts, ',')
442 str = ("%% copied from file `%s'\n" % full_path) + str
443 return get_output ('output-mudela') % (str_opts, str)
445 b = get_re('mudela-file').sub (mudela_file, b)
446 b = get_re('mudela').sub (mudela_short, b)
449 def find_mudela_chunks (str):
450 """Find mudela blocks, while watching for verbatim. Returns
451 (STR,MUDS) with substituted for the blocks in STR,
452 and the blocks themselves MUDS"""
456 m = get_re ("mudela-block").search( str)
458 chunks.append (('input', str))
462 chunks.append (('input', str[:m.start (0)]))
469 optlist = get_re('comma-sep').split (opts)
472 chunks.append (('mudela', body, optlist))
474 str = str [m.end (0):]
480 def advance_counters (counter, opts, str):
481 """Advance chap/sect counters,
482 revise OPTS. Return the new counter tuple"""
484 (chapter, section, count) = counter
487 m = get_re ('interesting-cs').search(str)
493 done = done + str[:m.end (0)]
498 opts.append ('twocolumn')
499 elif g == 'onecolumn':
501 current_opts.remove ('twocolumn')
505 (chapter, section, count) = (chapter + 1, 0, 0)
507 (section, count) = (section + 1, 0)
510 return (chapter, section, count)
513 def schedule_mudela_block (base, chunk, extra_opts):
514 """Take the body and options from CHUNK, figure out how the
515 real .ly should look, and what should be left MAIN_STR (meant
516 for the main file). The .ly is written, and scheduled in
519 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO)
521 TODO has format [basename, extension, extension, ... ]
525 (type, body, opts) = chunk
526 assert type == 'mudela'
527 opts = opts + extra_opts
530 if 'verbatim' in opts:
531 newbody = output_verbatim (body)
533 file_body = compose_full_body (body, opts)
534 updated = update_file (file_body, base + '.ly')
537 if not os.path.isfile (base + '.tex') or updated:
542 m = re.search ('intertext="(.*?)"', o)
544 newbody = newbody + m.group (1)
552 if 'eps' in opts and ('tex' in todo or
553 not os.path.isfile (base + '.eps')):
556 if 'png' in opts and ('eps' in todo or
557 not os.path.isfile (base + '.png')):
560 if format == 'latex':
562 newbody = newbody + get_output ('output-eps') % (base, base)
564 newbody = newbody + get_output ('output-tex') % base
566 elif format == 'texi':
567 newbody = newbody + get_output ('output-all') % (base, base)
571 return ('mudela', newbody, opts, todo)
573 def find_eps_dims (match):
574 "Fill in dimensions of EPS files."
577 dims = bounding_box_dimensions (fn)
579 return '%ipt' % dims[0]
582 def print_chunks (ch):
584 print '-->%s\n%s' % (c[0], c[1])
586 print '==>%s' % list (c[2:])
590 def transform_input_file (in_filename, out_filename):
591 """Read the input, and deliver a list of chunks
595 chunks = read_doc_file (in_filename)
597 #. Process \mudela and \mudelafile.
598 for func in [find_mudela_shorthand_chunks,
603 newchunks = newchunks + func (c[1])
610 opts = scan_preamble (chunks[0][1])
612 (chap,sect,count) = (0,0,0)
614 # Count sections/chapters.
617 (chap,sect,count) = advance_counters((chap,sect,count), opts, c[1])
618 elif c[0] == 'mudela':
619 base = '%s-%d.%d.%d' % (out_filename, chap, sect, count)
621 c = schedule_mudela_block (base, c, opts)
629 if __main__.run_lilypond:
630 compile_all_files (chunks)
634 if c[0] == 'mudela' and 'eps' in c[2]:
635 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
636 newchunks.append (('mudela', body))
641 if chunks and chunks[0][0] == 'input':
642 chunks[0] = ('input', completize_preamble (chunks[0][1]))
647 sys.stderr.write ("invoking `%s'\n" % cmd)
650 error ('Error command exited with value %d\n' % st)
653 def compile_all_files (chunks):
667 tex.append (base + '.ly')
672 lilyopts = map (lambda x: '-I ' + x, include_path)
673 lilyopts = string.join (lilyopts, ' ' )
674 texfiles = string.join (tex, ' ')
675 system ('lilypond %s %s' % (lilyopts, texfiles))
678 cmd = r"""tex %s; dvips -E -o %s %s""" % \
683 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
685 cmd = cmd % (g + '.eps', g + '.png')
689 def update_file (body, name):
699 f = open (name , 'w')
708 def getopt_args (opts):
709 "Construct arguments (LONG, SHORT) for getopt from list of options."
724 def option_help_str (o):
725 "Transform one option description (4-tuple ) into neatly formatted string"
743 return ' ' + sh + sep + long + arg
746 def options_help_str (opts):
747 "Convert a list of options into a neatly formatted string"
752 s = option_help_str (o)
753 strs.append ((s, o[3]))
759 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
763 sys.stdout.write("""Usage: mudela-book [options] FILE\n
764 Generate hybrid LaTeX input from Latex + mudela
767 sys.stdout.write (options_help_str (options))
768 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
772 Report bugs to bug-gnu-music@gnu.org.
774 Written by Tom Cato Amundsen <tomcato@xoommail.com> and
775 Han-Wen Nienhuys <hanwen@cs.uu.nl>
781 def write_deps (fn, target, deps):
782 sys.stdout.write('writing `%s\'\n' % fn)
786 target = target + '.latex'
787 f.write ('%s: %s\n'% (target, string.join (deps, ' ')))
792 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
794 def print_version ():
796 sys.stdout.write (r"""Copyright 1998--1999
797 Distributed under terms of the GNU General Public License. It comes with
803 global outdir, initfile, defined_mudela_cmd, defined_mudela_cmd_re
806 (sh, long) = getopt_args (__main__.options)
807 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
808 except getopt.error, msg:
809 sys.stderr.write("error: %s" % msg)
817 if o == '--include' or o == '-I':
818 include_path.append (a)
819 elif o == '--version':
823 elif o == '--format' or o == '-o':
825 elif o == '--outname' or o == '-o':
828 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
831 elif o == '--outdir' or o == '-d':
833 elif o == '--help' or o == '-h':
835 elif o == '--no-lily' or o == '-n':
836 __main__.run_lilypond = 0
837 elif o == '--dependencies':
839 elif o == '--default-mudela-fontsize':
840 default_music_fontsize = string.atoi (a)
846 for input_filename in files:
851 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
852 my_depname = my_outname + '.dep'
854 chunks = transform_input_file (input_filename, my_outname)
856 foutn = my_outname + '.' + format
857 sys.stderr.write ("Writing `%s'\n" % foutn)
858 fout = open (foutn, 'w')
864 # write_deps (my_depname, my_outname, deps)
865 sys.stderr.write ("--dependencies broken")
872 # Petr, ik zou willen dat ik iets zinvoller deed,
873 # maar wat ik kan ik doen, het verandert toch niets?