2 # vim: set noexpandtab:
6 # support bruk av convert-mudela
9 # 11pt, 13pt, 16pt, 20pt, 26pt
12 # fragment (used when a comment containg \score confuses mudela-book)
13 # nonfragment (probably not needed)
20 # command line options
21 # --defalt-mudela-fontsize
22 # --force-mudela-fontsize
24 # --force-verbatim make all mudela verbatim. Maybe not that useful
29 # TODO: Figure out clean set of options.
31 # BUG: does not handle \verb|\begin{verbatim}\end{verbatim}| correctly.
32 # Should make a joint RE for \verb and \begin, \end{verbatim}
34 # TODO: add an option to read the .ly files from a previous run and dump
35 # the .tex file, so you can do
37 # * mudela-book file.tex
38 # * convert-mudela *.ly
39 # * mudela-book --read-lys *.ly
52 program_version = '1.3.69-very-unstable'
54 include_path = [os.getcwd()]
58 g_force_mudela_fontsize = 0
67 default_music_fontsize = 16
68 default_text_fontsize = 12
71 # indices are no. of columns, papersize, fontsize
72 # Why can't this be calculated?
74 1: {'a4':{10: 345, 11: 360, 12: 390},
75 'a5':{10: 276, 11: 276, 12: 276},
76 'b5':{10: 345, 11: 356, 12: 356},
77 'letter':{10: 345, 11: 360, 12: 390},
78 'legal': {10: 345, 11: 360, 12: 390},
79 'executive':{10: 345, 11: 360, 12: 379}},
80 2: {'a4':{10: 167, 11: 175, 12: 190},
81 'a5':{10: 133, 11: 133, 12: 133},
82 'b5':{10: 167, 11: 173, 12: 173},
83 'letter':{10: 167, 11: 175, 12: 190},
84 'legal':{10: 167, 11: 175, 12: 190},
85 'executive':{10: 167, 11: 175, 12: 184}}}
90 'smallbook': {12: 361},
91 'texidefault': {12: 433}}
94 def get_linewidth(cols, paper, fontsize):
95 if __main__.format == 'latex':
96 return latex_linewidths[cols][paper][fontsize]
97 elif __main__.format == 'texi':
98 return texi_linewidths[paper][fontsize]
101 option_definitions = [
102 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
103 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to in points'),
104 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
105 ('', 'h', 'help', 'print help'),
106 ('DIR', 'I', 'include', 'include path'),
107 ('', '', 'init', 'mudela-book initfile'),
108 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
109 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
110 ('', '', 'force-verbatim', 'make all mudela verbatim'),
111 ('', 'M', 'dependencies', 'write dependencies'),
112 ('', 'n', 'no-lily', 'don\'t run lilypond'),
113 ('', '', 'no-pictures', "don\'t generate pictures"),
114 ('', '', 'read-lys', "don't write ly files."),
115 ('FILE', 'o', 'outname', 'prefix for filenames'),
116 ('', 'v', 'version', 'print version information' ),
117 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
118 ('FILE', '', 'outdir', "where to place generated files"),
121 # format specific strings, ie. regex-es for input, and % strings for output
124 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
131 'output-mudela':r"""\begin[%s]{mudela}
134 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
135 'output-default-post': r"""\def\postMudelaExample{}""",
136 'output-default-pre': r"""\def\preMudelaExample{}""",
137 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
138 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
139 'pagebreak': r'\pagebreak',
141 'texi' : {'output-mudela': """@mudela[%s]
145 'output-mudela-fragment': """@mudela[%s]
146 \context Staff\context Voice{ %s }
149 'output-verbatim': r"""@example
154 # do some tweaking: @ is needed in some ps stuff.
155 # override EndLilyPondOutput, since @tex is done
156 # in a sandbox, you can't do \input lilyponddefs at the
157 # top of the document.
159 # should also support fragment in
161 'output-all': r"""@tex
164 \def\EndLilyPondOutput{}
176 def output_verbatim (body):#ugh .format
177 if __main__.format == 'texi':
178 body = re.sub ('([@{}])', '@\\1', body)
179 return get_output ('output-verbatim') % body
181 def output_mbverbatim (body):#ugh .format
182 if __main__.format == 'texi':
183 body = re.sub ('([@{}])', '@\\1', body)
184 return get_output ('output-verbatim') % body
187 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
188 'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
190 'option-sep' : ', *',
191 'header': r"""\\documentclass(\[.*?\])?""",
192 'preamble-end': '\\\\begin{document}',
193 'verbatim': r"""(?s)\\begin{verbatim}(?P<code>.*?)\\end{verbatim}""",
194 'verb': r"""\\verb(.)(?P<code>.*?)\1""",
195 'mudela-file': r'\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)}',
196 'mudela' : '\\\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
197 'mudela-block': r"""(?s)\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
198 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
199 'def-post-re': r"""\\def\\postMudelaExample""",
200 'def-pre-re': r"""\\def\\preMudelaExample""",
201 'intertext': r',?\s*intertext=\".*?\"',
203 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
207 'include': '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
210 'preamble-end': no_match,
211 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
212 'verb': r"""@code{(?P<code>.*?)}""",
213 'mudela-file': '@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)}',
214 'mudela' : '@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
215 'mudela-block': r"""(?s)@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s""",
216 'interesting-cs': r"""[\\@](chapter|section)""",
217 'option-sep' : ', *',
218 'intertext': r',?\s*intertext=\".*?\"',
219 'ignore': r"(?s)@ignore\s(.*?)@end ignore\s",
225 for r in re_dict.keys ():
228 for k in olddict.keys ():
229 newdict[k] = re.compile (olddict[k])
243 def get_output (name):
244 return output_dict[format][name]
247 return re_dict[format][name]
249 def bounding_box_dimensions(fname):
253 error ("Error opening `%s'" % fname)
255 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
257 return (int(s.group(3))-int(s.group(1)),
258 int(s.group(4))-int(s.group(2)))
264 sys.stderr.write (str + "\n Exiting ... \n\n")
268 def compose_full_body (body, opts):
269 """Construct the mudela code to send to Lilypond.
270 Add stuff to BODY using OPTS as options."""
271 if __main__.format == 'texi':
272 paper = 'texidefault'
274 paper = 'letter' # yes, latex use letter as default, at least
276 music_size = default_music_fontsize
277 latex_size = default_text_fontsize
279 m = re.search ('^(.*)paper$', o)
283 if g_force_mudela_fontsize:
284 music_size = g_force_mudela_fontsize
286 m = re.match ('([0-9]+)pt', o)
288 music_size = string.atoi(m.group (1))
290 m = re.match ('latexfontsize=([0-9]+)pt', o)
292 latex_size = string.atoi (m.group (1))
294 if re.search ('\\\\score', body):
298 if 'fragment' in opts:
300 if 'nonfragment' in opts:
303 if is_fragment and not 'multiline' in opts:
304 opts.append('singleline')
305 if 'singleline' in opts:
308 l = get_linewidth(g_num_cols, paper, latex_size)
310 if 'relative' in opts:#ugh only when is_fragment
311 body = '\\relative c { %s }' % body
320 optstring = string.join (opts, ' ')
321 optstring = re.sub ('\n', ' ', optstring)
324 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
325 \include "paper%d.ly"
326 \paper { linewidth = %f \pt; }
327 """ % (optstring, music_size, l) + body
331 def scan_preamble (str):
333 if __main__.format == 'texi':
335 if string.find(str[:x], "@afourpaper") != -1:
336 options = ['a4paper']
337 elif string.find(str[:x], "@afourwide") != -1:
338 options = ['a4widepaper']
339 elif string.find(str[:x], "@smallbook") != -1:
340 options = ['smallbookpaper']
341 m = get_re ('header').search( str)
342 # should extract paper & fontsz.
343 if m and m.group (1):
344 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
346 def verbose_fontsize ( x):
347 if re.match('[0-9]+pt', x):
348 return 'latexfontsize=' + x
352 options = map (verbose_fontsize, options)
356 def completize_preamble (str):
357 m = get_re ('preamble-end').search( str)
361 preamble = str [:m.start (0)]
362 str = str [m.start(0):]
364 if not get_re('def-post-re').search (preamble):
365 preamble = preamble + get_output('output-default-post')
366 if not get_re ('def-pre-re').search( preamble):
367 preamble = preamble + get_output ('output-default-pre')
370 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
372 preamble = preamble + '\\usepackage{graphics}\n'
374 return preamble + str
378 def find_file (name):
380 for a in include_path:
382 nm = os.path.join (a, name)
384 __main__.read_files.append (nm)
391 error ("File not found `%s'\n" % name)
394 def do_ignore(match_object):
397 def make_verbatim(match_object):
398 return [('verbatim', match_object.group('code'))]
400 def make_verb(match_object):
401 return [('verb', match_object.group('code'))]
403 def do_include_file(m):
405 return [('input', get_output ('pagebreak'))] \
406 + read_doc_file(m.group('filename')) \
407 + [('input', get_output ('pagebreak'))]
409 def do_input_file(m):
410 return read_doc_file(m.group('filename'))
413 if m.group('options'):
414 options = m.group('options')
417 return [('input', get_output('output-mudela-fragment') %
418 (options, m.group('code')))]
420 def make_mudela_file(m):
421 if m.group('options'):
422 options = m.group('options')
425 return [('input', get_output('output-mudela') %
426 (options, find_file(m.group('filename'))))]
428 def make_mudela_block(m):
429 if m.group('options'):
430 options = get_re('option-sep').split (m.group('options'))
433 options = filter(lambda s: s != '', options)
434 if 'mbverbatim' in options:#ugh this is ugly and only for texi format
436 im = get_re('intertext').search(s)
438 s = s[:im.start()] + s[im.end():]
439 im = re.search('mbverbatim', s)
441 s = s[:im.start()] + s[im.end():]
442 if s[:9] == "@mudela[]":
443 s = "@mudela" + s[9:]
444 return [('mudela', m.group('code'), options, s)]
445 return [('mudela', m.group('code'), options)]
448 if __main__.format != 'latex':
450 if m.group('num') == 'one':
451 return [('numcols', m.group('code'), 1)]
452 if m.group('num') == 'two':
453 return [('numcols', m.group('code'), 2)]
455 def chop_chunks(chunks, re_name, func):
461 m = get_re (re_name).search (str)
463 newchunks.append (('input', str))
466 newchunks.append (('input', str[:m.start (0)]))
467 newchunks.extend(func(m))
468 str = str [m.end(0):]
473 def read_doc_file (filename):
474 """Read the input file, find verbatim chunks and do \input and \include
477 str = find_file(filename)
479 if __main__.format == '':
480 latex = re.search ('\\\\document', str[:200])
481 texinfo = re.search ('@node|@setfilename', str[:200])
482 if (texinfo and latex) or not (texinfo or latex):
483 error("error: can't determine format, please specify")
485 __main__.format = 'texi'
487 __main__.format = 'latex'
488 chunks = [('input', str)]
489 # we have to check for verbatim before doing include,
490 # because we don't want to include files that are mentioned
491 # inside a verbatim environment
492 chunks = chop_chunks(chunks, 'ignore', do_ignore)
493 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
494 chunks = chop_chunks(chunks, 'verb', make_verb)
496 chunks = chop_chunks(chunks, 'include', do_include_file)
497 chunks = chop_chunks(chunks, 'input', do_input_file)
501 def advance_counters (counter, str):
502 """Advance chap/sect counters,
503 Return the new counter tuple
505 (chapter, section, count) = counter
507 m = get_re ('interesting-cs').search(str)
512 if g == 'chapter':#ugh use dict
513 (chapter, section, count) = (chapter + 1, 0, 0)
515 (section, count) = (section + 1, 0)
516 return (chapter, section, count)
518 taken_file_names = []
519 def schedule_mudela_block (basename, chunk, extra_opts):
520 """Take the body and options from CHUNK, figure out how the
521 real .ly should look, and what should be left MAIN_STR (meant
522 for the main file). The .ly is written, and scheduled in
525 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
527 TODO has format [basename, extension, extension, ... ]
531 (type, body, opts) = chunk
534 (type, body, opts, complete_body) = chunk
535 assert type == 'mudela'
536 opts = opts + extra_opts
537 file_body = compose_full_body (body, opts)
538 if __main__.g_use_hash:
539 basename = `abs(hash (file_body))`
541 m = re.search ('filename="(.*?)"', o)
543 basename = m.group (1)#ugh add check if more than
544 #one file has the same name
545 assert basename not in taken_file_names
546 taken_file_names.append(basename)
547 # writes the file if necessary, returns true if it was written
549 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
550 needed_filetypes = ['tex']
553 needed_filetypes.append('eps')
554 needed_filetypes.append('png')
555 if 'eps' in opts and not ('eps' in needed_filetypes):
556 needed_filetypes.append('eps')
557 outname = os.path.join(g_outdir, basename)
558 if not os.path.isfile(outname + '.tex') \
559 or os.stat(outname+'.ly')[stat.ST_MTIME] > \
560 os.stat(outname+'.tex')[stat.ST_MTIME]:
561 todo = needed_filetypes
566 if 'verbatim' in opts:
567 newbody = output_verbatim (body)
568 elif 'mbverbatim' in opts:
569 newbody = output_mbverbatim (complete_body)
572 m = re.search ('intertext="(.*?)"', o)
574 newbody = newbody + m.group (1)
575 if format == 'latex':
580 else: # format == 'texi'
582 newbody = newbody + get_output(s) % {'fn': basename }
583 return ('mudela', newbody, opts, todo, basename)
585 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
586 (chap,sect,count) = (0,0,0)
588 # Count sections/chapters.
591 (chap,sect,count) = advance_counters((chap,sect,count), c[1])
592 elif c[0] == 'mudela':
593 base = '%s-%d.%d.%d' % (outname, chap, sect, count)
595 c = schedule_mudela_block (base, c, global_options)
596 elif c[0] == 'numcols':
597 __main__.g_num_cols = c[2]
602 def find_eps_dims (match):
603 "Fill in dimensions of EPS files."
606 dims = bounding_box_dimensions (fn)
608 return '%ipt' % dims[0]
612 sys.stderr.write ("invoking `%s'\n" % cmd)
615 error ('Error command exited with value %d\n' % st)
618 def compile_all_files (chunks):
632 tex.append (base + '.ly')
633 elif e == 'png' and g_do_pictures:
639 lilyopts = map (lambda x: '-I ' + x, include_path)
640 lilyopts = string.join (lilyopts, ' ' )
641 texfiles = string.join (tex, ' ')
642 system ('lilypond %s %s' % (lilyopts, texfiles))
644 system(r"tex '\nonstopmode \input %s'" % e)
645 system(r"dvips -E -o %s %s" % (e + '.eps', e))
647 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
648 cmd = cmd % (g + '.eps', g + '.png')
654 def update_file (body, name):
656 write the body if it has changed
667 f = open (name , 'w')
674 def getopt_args (opts):
675 "Construct arguments (LONG, SHORT) for getopt from list of options."
690 def option_help_str (o):
691 "Transform one option description (4-tuple ) into neatly formatted string"
709 return ' ' + sh + sep + long + arg
712 def options_help_str (opts):
713 "Convert a list of options into a neatly formatted string"
719 s = option_help_str (o)
720 strs.append ((s, o[3]))
726 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
730 sys.stdout.write("""Usage: mudela-book [options] FILE\n
731 Generate hybrid LaTeX input from Latex + mudela
734 sys.stdout.write (options_help_str (option_definitions))
735 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
739 Report bugs to bug-gnu-music@gnu.org.
741 Written by Tom Cato Amundsen <tca@gnu.org> and
742 Han-Wen Nienhuys <hanwen@cs.uu.nl>
748 def write_deps (fn, target):
749 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
750 f = open (os.path.join(g_outdir, fn), 'w')
751 f.write ('%s%s: ' % (g_dep_prefix, target))
752 for d in __main__.read_files:
756 __main__.read_files = []
759 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
761 def print_version ():
763 sys.stdout.write (r"""Copyright 1998--1999
764 Distributed under terms of the GNU General Public License. It comes with
768 def do_file(input_filename):
773 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
774 my_depname = my_outname + '.dep'
776 chunks = read_doc_file(input_filename)
777 chunks = chop_chunks(chunks, 'mudela', make_mudela)
778 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file)
779 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block)
780 chunks = chop_chunks(chunks, 'numcols', do_columns)
781 #for c in chunks: print c, "\n"
782 global_options = scan_preamble(chunks[0][1])
783 chunks = process_mudela_blocks(my_outname, chunks, global_options)
785 if __main__.g_run_lilypond:
786 compile_all_files (chunks)
790 if c[0] == 'mudela' and 'eps' in c[2]:
791 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
792 newchunks.append (('mudela', body))
797 if chunks and chunks[0][0] == 'input':
798 chunks[0] = ('input', completize_preamble (chunks[0][1]))
800 foutn = os.path.join(g_outdir, my_outname + '.' + format)
801 sys.stderr.write ("Writing `%s'\n" % foutn)
802 fout = open (foutn, 'w')
804 #if c[1] is not None:
809 write_deps (my_depname, foutn)
814 (sh, long) = getopt_args (__main__.option_definitions)
815 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
816 except getopt.error, msg:
817 sys.stderr.write("error: %s" % msg)
825 if o == '--include' or o == '-I':
826 include_path.append (a)
827 elif o == '--version':
831 elif o == '--format' or o == '-o':
833 elif o == '--outname' or o == '-o':
836 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
839 elif o == '--help' or o == '-h':
841 elif o == '--no-lily' or o == '-n':
842 __main__.g_run_lilypond = 0
843 elif o == '--dependencies':
845 elif o == '--default-music-fontsize':
846 default_music_fontsize = string.atoi (a)
847 elif o == '--default-mudela-fontsize':
848 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
849 default_music_fontsize = string.atoi (a)
850 elif o == '--force-music-fontsize':
851 g_force_mudela_fontsize = string.atoi(a)
852 elif o == '--force-mudela-fontsize':
853 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
854 g_force_mudela_fontsize = string.atoi(a)
858 elif o == '--dep-prefix':
860 elif o == '--no-pictures':
862 elif o == '--read-lys':
864 elif o == '--outdir':
869 if os.path.isfile(g_outdir):
870 error ("outdir is a file: %s" % g_outdir)
871 if not os.path.exists(g_outdir):
873 for input_filename in files:
874 do_file(input_filename)
881 # Petr, ik zou willen dat ik iets zinvoller deed,
882 # maar wat ik kan ik doen, het verandert toch niets?