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
54 program_version = '@TOPLEVEL_VERSION@'
55 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
56 program_version = '1.3.69-very-unstable'
58 include_path = [os.getcwd()]
62 g_force_mudela_fontsize = 0
71 default_music_fontsize = 16
72 default_text_fontsize = 12
75 # indices are no. of columns, papersize, fontsize
76 # Why can't this be calculated?
78 1: {'a4':{10: 345, 11: 360, 12: 390},
79 'a5':{10: 276, 11: 276, 12: 276},
80 'b5':{10: 345, 11: 356, 12: 356},
81 'letter':{10: 345, 11: 360, 12: 390},
82 'legal': {10: 345, 11: 360, 12: 390},
83 'executive':{10: 345, 11: 360, 12: 379}},
84 2: {'a4':{10: 167, 11: 175, 12: 190},
85 'a5':{10: 133, 11: 133, 12: 133},
86 'b5':{10: 167, 11: 173, 12: 173},
87 'letter':{10: 167, 11: 175, 12: 190},
88 'legal':{10: 167, 11: 175, 12: 190},
89 'executive':{10: 167, 11: 175, 12: 184}}}
94 'smallbook': {12: 361},
95 'texidefault': {12: 433}}
98 def get_linewidth(cols, paper, fontsize):
99 if __main__.format == 'latex':
100 return latex_linewidths[cols][paper][fontsize]
101 elif __main__.format == 'texi':
102 return texi_linewidths[paper][fontsize]
105 option_definitions = [
106 ('EXT', 'f', 'format', 'set format. EXT is one of texi and latex.'),
107 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to in points'),
108 ('DIM', '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
109 ('', 'h', 'help', 'print help'),
110 ('DIR', 'I', 'include', 'include path'),
111 ('', '', 'init', 'mudela-book initfile'),
112 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed to in points'),
113 ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
114 ('', '', 'force-verbatim', 'make all mudela verbatim'),
115 ('', 'M', 'dependencies', 'write dependencies'),
116 ('', 'n', 'no-lily', 'don\'t run lilypond'),
117 ('', '', 'no-pictures', "don\'t generate pictures"),
118 ('', '', 'read-lys', "don't write ly files."),
119 ('FILE', 'o', 'outname', 'prefix for filenames'),
120 ('', 'v', 'version', 'print version information' ),
121 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
122 ('FILE', '', 'outdir', "where to place generated files"),
125 # format specific strings, ie. regex-es for input, and % strings for output
128 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
135 'output-mudela':r"""\begin[%s]{mudela}
138 'output-verbatim': r"""\begin{verbatim}%s\end{verbatim}""",
139 'output-default-post': r"""\def\postMudelaExample{}""",
140 'output-default-pre': r"""\def\preMudelaExample{}""",
141 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
142 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
143 'pagebreak': r'\pagebreak',
145 'texi' : {'output-mudela': """@mudela[%s]
149 'output-mudela-fragment': """@mudela[%s]
150 \context Staff\context Voice{ %s }
153 'output-verbatim': r"""@example
158 # do some tweaking: @ is needed in some ps stuff.
159 # override EndLilyPondOutput, since @tex is done
160 # in a sandbox, you can't do \input lilyponddefs at the
161 # top of the document.
163 # should also support fragment in
165 'output-all': r"""@tex
168 \def\EndLilyPondOutput{}
180 def output_verbatim (body):#ugh .format
181 if __main__.format == 'texi':
182 body = re.sub ('([@{}])', '@\\1', body)
183 return get_output ('output-verbatim') % body
185 def output_mbverbatim (body):#ugh .format
186 if __main__.format == 'texi':
187 body = re.sub ('([@{}])', '@\\1', body)
188 return get_output ('output-verbatim') % body
191 'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
192 'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
194 'option-sep' : ', *',
195 'header': r"""\\documentclass(\[.*?\])?""",
196 'preamble-end': '\\\\begin{document}',
197 'verbatim': r"""(?s)\\begin{verbatim}(?P<code>.*?)\\end{verbatim}""",
198 'verb': r"""\\verb(.)(?P<code>.*?)\1""",
199 'mudela-file': r'\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)}',
200 'mudela' : '\\\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
201 'mudela-block': r"""(?s)\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela}""",
202 'interesting-cs': '\\\\(chapter|section|twocolumn|onecolumn)',
203 'def-post-re': r"""\\def\\postMudelaExample""",
204 'def-pre-re': r"""\\def\\preMudelaExample""",
205 'intertext': r',?\s*intertext=\".*?\"',
207 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
211 'include': '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
214 'preamble-end': no_match,
215 'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
216 'verb': r"""@code{(?P<code>.*?)}""",
217 'mudela-file': '@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)}',
218 'mudela' : '@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)}',
219 'mudela-block': r"""(?s)@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s""",
220 'interesting-cs': r"""[\\@](chapter|section)""",
221 'option-sep' : ', *',
222 'intertext': r',?\s*intertext=\".*?\"',
223 'ignore': r"(?s)@ignore\s(.*?)@end ignore\s",
229 for r in re_dict.keys ():
232 for k in olddict.keys ():
233 newdict[k] = re.compile (olddict[k])
247 def get_output (name):
248 return output_dict[format][name]
251 return re_dict[format][name]
253 def bounding_box_dimensions(fname):
257 error ("Error opening `%s'" % fname)
259 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
261 return (int(s.group(3))-int(s.group(1)),
262 int(s.group(4))-int(s.group(2)))
268 sys.stderr.write (str + "\n Exiting ... \n\n")
272 def compose_full_body (body, opts):
273 """Construct the mudela code to send to Lilypond.
274 Add stuff to BODY using OPTS as options."""
275 if __main__.format == 'texi':
276 paper = 'texidefault'
278 paper = 'letter' # yes, latex use letter as default, at least
280 music_size = default_music_fontsize
281 latex_size = default_text_fontsize
283 m = re.search ('^(.*)paper$', o)
287 if g_force_mudela_fontsize:
288 music_size = g_force_mudela_fontsize
290 m = re.match ('([0-9]+)pt', o)
292 music_size = string.atoi(m.group (1))
294 m = re.match ('latexfontsize=([0-9]+)pt', o)
296 latex_size = string.atoi (m.group (1))
298 if re.search ('\\\\score', body):
302 if 'fragment' in opts:
304 if 'nonfragment' in opts:
307 if is_fragment and not 'multiline' in opts:
308 opts.append('singleline')
309 if 'singleline' in opts:
312 l = get_linewidth(g_num_cols, paper, latex_size)
314 if 'relative' in opts:#ugh only when is_fragment
315 body = '\\relative c { %s }' % body
324 optstring = string.join (opts, ' ')
325 optstring = re.sub ('\n', ' ', optstring)
328 %% Generated by mudela-book.py; options are %s %%ughUGH not original options
329 \include "paper%d.ly"
330 \paper { linewidth = %f \pt; }
331 """ % (optstring, music_size, l) + body
335 def scan_preamble (str):
337 if __main__.format == 'texi':
339 if string.find(str[:x], "@afourpaper") != -1:
340 options = ['a4paper']
341 elif string.find(str[:x], "@afourwide") != -1:
342 options = ['a4widepaper']
343 elif string.find(str[:x], "@smallbook") != -1:
344 options = ['smallbookpaper']
345 m = get_re ('header').search( str)
346 # should extract paper & fontsz.
347 if m and m.group (1):
348 options = options + re.split (',[\n \t]*', m.group(1)[1:-1])
350 def verbose_fontsize ( x):
351 if re.match('[0-9]+pt', x):
352 return 'latexfontsize=' + x
356 options = map (verbose_fontsize, options)
360 def completize_preamble (str):
361 m = get_re ('preamble-end').search( str)
365 preamble = str [:m.start (0)]
366 str = str [m.start(0):]
368 if not get_re('def-post-re').search (preamble):
369 preamble = preamble + get_output('output-default-post')
370 if not get_re ('def-pre-re').search( preamble):
371 preamble = preamble + get_output ('output-default-pre')
374 #if re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
376 preamble = preamble + '\\usepackage{graphics}\n'
378 return preamble + str
382 def find_file (name):
384 for a in include_path:
386 nm = os.path.join (a, name)
388 __main__.read_files.append (nm)
395 error ("File not found `%s'\n" % name)
398 def do_ignore(match_object):
401 def make_verbatim(match_object):
402 return [('verbatim', match_object.group('code'))]
404 def make_verb(match_object):
405 return [('verb', match_object.group('code'))]
407 def do_include_file(m):
409 return [('input', get_output ('pagebreak'))] \
410 + read_doc_file(m.group('filename')) \
411 + [('input', get_output ('pagebreak'))]
413 def do_input_file(m):
414 return read_doc_file(m.group('filename'))
417 if m.group('options'):
418 options = m.group('options')
421 return [('input', get_output('output-mudela-fragment') %
422 (options, m.group('code')))]
424 def make_mudela_file(m):
425 if m.group('options'):
426 options = m.group('options')
429 return [('input', get_output('output-mudela') %
430 (options, find_file(m.group('filename'))))]
432 def make_mudela_block(m):
433 if m.group('options'):
434 options = get_re('option-sep').split (m.group('options'))
437 options = filter(lambda s: s != '', options)
438 if 'mbverbatim' in options:#ugh this is ugly and only for texi format
440 im = get_re('intertext').search(s)
442 s = s[:im.start()] + s[im.end():]
443 im = re.search('mbverbatim', s)
445 s = s[:im.start()] + s[im.end():]
446 if s[:9] == "@mudela[]":
447 s = "@mudela" + s[9:]
448 return [('mudela', m.group('code'), options, s)]
449 return [('mudela', m.group('code'), options)]
452 if __main__.format != 'latex':
454 if m.group('num') == 'one':
455 return [('numcols', m.group('code'), 1)]
456 if m.group('num') == 'two':
457 return [('numcols', m.group('code'), 2)]
459 def chop_chunks(chunks, re_name, func):
465 m = get_re (re_name).search (str)
467 newchunks.append (('input', str))
470 newchunks.append (('input', str[:m.start (0)]))
471 newchunks.extend(func(m))
472 str = str [m.end(0):]
477 def read_doc_file (filename):
478 """Read the input file, find verbatim chunks and do \input and \include
481 str = find_file(filename)
483 if __main__.format == '':
484 latex = re.search ('\\\\document', str[:200])
485 texinfo = re.search ('@node|@setfilename', str[:200])
486 if (texinfo and latex) or not (texinfo or latex):
487 error("error: can't determine format, please specify")
489 __main__.format = 'texi'
491 __main__.format = 'latex'
492 chunks = [('input', str)]
493 # we have to check for verbatim before doing include,
494 # because we don't want to include files that are mentioned
495 # inside a verbatim environment
496 chunks = chop_chunks(chunks, 'ignore', do_ignore)
497 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
498 chunks = chop_chunks(chunks, 'verb', make_verb)
500 chunks = chop_chunks(chunks, 'include', do_include_file)
501 chunks = chop_chunks(chunks, 'input', do_input_file)
505 def advance_counters (counter, str):
506 """Advance chap/sect counters,
507 Return the new counter tuple
509 (chapter, section, count) = counter
511 m = get_re ('interesting-cs').search(str)
516 if g == 'chapter':#ugh use dict
517 (chapter, section, count) = (chapter + 1, 0, 0)
519 (section, count) = (section + 1, 0)
520 return (chapter, section, count)
522 taken_file_names = []
523 def schedule_mudela_block (basename, chunk, extra_opts):
524 """Take the body and options from CHUNK, figure out how the
525 real .ly should look, and what should be left MAIN_STR (meant
526 for the main file). The .ly is written, and scheduled in
529 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
531 TODO has format [basename, extension, extension, ... ]
535 (type, body, opts) = chunk
538 (type, body, opts, complete_body) = chunk
539 assert type == 'mudela'
540 opts = opts + extra_opts
541 file_body = compose_full_body (body, opts)
542 if __main__.g_use_hash:
543 basename = `abs(hash (file_body))`
545 m = re.search ('filename="(.*?)"', o)
547 basename = m.group (1)#ugh add check if more than
548 #one file has the same name
549 assert basename not in taken_file_names
550 taken_file_names.append(basename)
551 # writes the file if necessary, returns true if it was written
553 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
554 needed_filetypes = ['tex']
557 needed_filetypes.append('eps')
558 needed_filetypes.append('png')
559 if 'eps' in opts and not ('eps' in needed_filetypes):
560 needed_filetypes.append('eps')
561 outname = os.path.join(g_outdir, basename)
562 if not os.path.isfile(outname + '.tex') \
563 or os.stat(outname+'.ly')[stat.ST_MTIME] > \
564 os.stat(outname+'.tex')[stat.ST_MTIME]:
565 todo = needed_filetypes
570 if 'verbatim' in opts:
571 newbody = output_verbatim (body)
572 elif 'mbverbatim' in opts:
573 newbody = output_mbverbatim (complete_body)
576 m = re.search ('intertext="(.*?)"', o)
578 newbody = newbody + m.group (1)
579 if format == 'latex':
584 else: # format == 'texi'
586 newbody = newbody + get_output(s) % {'fn': basename }
587 return ('mudela', newbody, opts, todo, basename)
589 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
590 (chap,sect,count) = (0,0,0)
592 # Count sections/chapters.
595 (chap,sect,count) = advance_counters((chap,sect,count), c[1])
596 elif c[0] == 'mudela':
597 base = '%s-%d.%d.%d' % (outname, chap, sect, count)
599 c = schedule_mudela_block (base, c, global_options)
600 elif c[0] == 'numcols':
601 __main__.g_num_cols = c[2]
606 def find_eps_dims (match):
607 "Fill in dimensions of EPS files."
610 dims = bounding_box_dimensions (fn)
612 return '%ipt' % dims[0]
616 sys.stderr.write ("invoking `%s'\n" % cmd)
619 error ('Error command exited with value %d\n' % st)
622 def compile_all_files (chunks):
636 tex.append (base + '.ly')
637 elif e == 'png' and g_do_pictures:
643 lilyopts = map (lambda x: '-I ' + x, include_path)
644 lilyopts = string.join (lilyopts, ' ' )
645 texfiles = string.join (tex, ' ')
646 system ('lilypond %s %s' % (lilyopts, texfiles))
648 system(r"tex '\nonstopmode \input %s'" % e)
649 system(r"dvips -E -o %s %s" % (e + '.eps', e))
651 cmd = r"""gs -sDEVICE=pgm -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
652 cmd = cmd % (g + '.eps', g + '.png')
658 def update_file (body, name):
660 write the body if it has changed
671 f = open (name , 'w')
678 def getopt_args (opts):
679 "Construct arguments (LONG, SHORT) for getopt from list of options."
694 def option_help_str (o):
695 "Transform one option description (4-tuple ) into neatly formatted string"
713 return ' ' + sh + sep + long + arg
716 def options_help_str (opts):
717 "Convert a list of options into a neatly formatted string"
723 s = option_help_str (o)
724 strs.append ((s, o[3]))
730 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
734 sys.stdout.write("""Usage: mudela-book [options] FILE\n
735 Generate hybrid LaTeX input from Latex + mudela
738 sys.stdout.write (options_help_str (option_definitions))
739 sys.stdout.write (r"""Warning all output is written in the CURRENT directory
743 Report bugs to bug-gnu-music@gnu.org.
745 Written by Tom Cato Amundsen <tca@gnu.org> and
746 Han-Wen Nienhuys <hanwen@cs.uu.nl>
752 def write_deps (fn, target):
753 sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
754 f = open (os.path.join(g_outdir, fn), 'w')
755 f.write ('%s%s: ' % (g_dep_prefix, target))
756 for d in __main__.read_files:
760 __main__.read_files = []
763 sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
765 def print_version ():
767 sys.stdout.write (r"""Copyright 1998--1999
768 Distributed under terms of the GNU General Public License. It comes with
772 def do_file(input_filename):
777 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
778 my_depname = my_outname + '.dep'
780 chunks = read_doc_file(input_filename)
781 chunks = chop_chunks(chunks, 'mudela', make_mudela)
782 chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file)
783 chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block)
784 chunks = chop_chunks(chunks, 'numcols', do_columns)
785 #for c in chunks: print c, "\n"
786 global_options = scan_preamble(chunks[0][1])
787 chunks = process_mudela_blocks(my_outname, chunks, global_options)
789 if __main__.g_run_lilypond:
790 compile_all_files (chunks)
794 if c[0] == 'mudela' and 'eps' in c[2]:
795 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
796 newchunks.append (('mudela', body))
801 if chunks and chunks[0][0] == 'input':
802 chunks[0] = ('input', completize_preamble (chunks[0][1]))
804 foutn = os.path.join(g_outdir, my_outname + '.' + format)
805 sys.stderr.write ("Writing `%s'\n" % foutn)
806 fout = open (foutn, 'w')
808 #if c[1] is not None:
813 write_deps (my_depname, foutn)
818 (sh, long) = getopt_args (__main__.option_definitions)
819 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
820 except getopt.error, msg:
821 sys.stderr.write("error: %s" % msg)
829 if o == '--include' or o == '-I':
830 include_path.append (a)
831 elif o == '--version':
835 elif o == '--format' or o == '-o':
837 elif o == '--outname' or o == '-o':
840 sys.stderr.write("Mudela-book is confused by --outname on multiple files")
843 elif o == '--help' or o == '-h':
845 elif o == '--no-lily' or o == '-n':
846 __main__.g_run_lilypond = 0
847 elif o == '--dependencies':
849 elif o == '--default-music-fontsize':
850 default_music_fontsize = string.atoi (a)
851 elif o == '--default-mudela-fontsize':
852 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
853 default_music_fontsize = string.atoi (a)
854 elif o == '--force-music-fontsize':
855 g_force_mudela_fontsize = string.atoi(a)
856 elif o == '--force-mudela-fontsize':
857 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
858 g_force_mudela_fontsize = string.atoi(a)
862 elif o == '--dep-prefix':
864 elif o == '--no-pictures':
866 elif o == '--read-lys':
868 elif o == '--outdir':
873 if os.path.isfile(g_outdir):
874 error ("outdir is a file: %s" % g_outdir)
875 if not os.path.exists(g_outdir):
877 for input_filename in files:
878 do_file(input_filename)
885 # Petr, ik zou willen dat ik iets zinvoller deed,
886 # maar wat ik kan ik doen, het verandert toch niets?