]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mudela-book.py
release: 1.3.77
[lilypond.git] / scripts / mudela-book.py
1 #!@PYTHON@
2 # vim: set noexpandtab:
3 import time
4 t1 = time.clock()
5
6 # support bruk av convert-mudela
7 #
8 # option:
9 # 11pt, 13pt, 16pt, 20pt, 26pt
10 # singleline
11 # multiline
12 # fragment  (used when a comment containg \score confuses mudela-book)
13 # nonfragment (probably not needed)
14 # verbatim
15
16 # latex only options:
17 # eps
18
19
20 # command line options
21 # --defalt-mudela-fontsize
22 # --force-mudela-fontsize
23 # --outname
24 # --force-verbatim make all mudela verbatim. Maybe not that useful
25 # --dependencies
26 # --dep-prefix
27 # --no-pictures
28 # --no-lily
29 # TODO: Figure out clean set of options.
30
31 # BUG: does not handle \verb|\begin{verbatim}\end{verbatim}| correctly.
32 # Should make a joint RE for \verb and \begin, \end{verbatim}
33
34 # TODO: add an option to read the .ly files from a previous run and dump
35 # the .tex file, so you can do
36 #
37 # * mudela-book file.tex
38 # * convert-mudela *.ly
39 # * mudela-book --read-lys *.ly
40 #
41
42 import os
43 import stat
44 import string
45 import re
46 import getopt
47 import sys
48 import __main__
49
50
51 initfile = ''
52 program_version = '1.3.69-very-unstable'
53
54 include_path = [os.getcwd()]
55
56 g_dep_prefix = ''
57 g_outdir = ''
58 g_force_mudela_fontsize = 0
59 g_read_lys = 0
60 g_do_pictures = 1
61 g_num_cols = 1
62 format = ''
63 g_run_lilypond = 1
64 g_use_hash = 1
65 no_match = 'a\ba'
66
67 default_music_fontsize = 16
68 default_text_fontsize = 12
69
70 # latex linewidths:
71 # indices are no. of columns, papersize,  fontsize
72 # Why can't this be calculated?
73 latex_linewidths = {
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}}}
86
87 texi_linewidths = {
88         'a4': {12: 455},
89         'a4wide': {12: 470},
90         'smallbook': {12: 361},
91         'texidefault': {12: 433}}
92
93
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]
99         raise "never here"
100
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"),
119   ]
120
121 # format specific strings, ie. regex-es for input, and % strings for output
122 output_dict= {
123         'latex': {
124                 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
125   \context Staff <
126     \context Voice{
127       %s
128     }
129   >
130 \end{mudela}""", 
131                 'output-mudela':r"""\begin[%s]{mudela}
132 %s
133 \end{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',
140                 },
141         'texi' : {'output-mudela': """@mudela[%s]
142 %s
143 @end mudela 
144 """,
145                   'output-mudela-fragment': """@mudela[%s]
146 \context Staff\context Voice{ %s }
147 @end mudela """,
148                   'pagebreak': None,
149                   'output-verbatim': r"""@example
150 %s
151 @end example
152 """,
153
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.
158
159 # should also support fragment in
160                   
161                   'output-all': r"""@tex
162 \catcode`\@=12
163 \input lilyponddefs
164 \def\EndLilyPondOutput{}
165 \input %(fn)s.tex
166 \catcode`\@=0
167 @end tex
168 @html
169 <p>
170 <img src=%(fn)s.png>
171 @end html
172 """,
173                 }
174         }
175
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
180
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
185
186 re_dict = {
187         'latex': {'input': '\\\\mbinput{?([^}\t \n}]*)',
188                   'include': '\\\\mbinclude{(?P<filename>[^}]+)}',
189                  
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=\".*?\"',
202                   'ignore': no_match,
203                   'numcols': r"(?P<code>\\(?P<num>one|two)column)",
204                   },
205         
206         'texi': {
207                  'include':  '@mbinclude[ \n\t]+(?P<filename>[^\t \n]*)',
208                  'input': no_match,
209                  'header': no_match,
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",
220                   'numcols': no_match,
221                  }
222         }
223
224
225 for r in re_dict.keys ():
226         olddict = re_dict[r]
227         newdict = {}
228         for k in olddict.keys ():
229                 newdict[k] = re.compile (olddict[k])
230         re_dict[r] = newdict
231
232         
233 def uniq (list):
234         list.sort ()
235         s = list
236         list = []
237         for x in s:
238                 if x not in list:
239                         list.append (x)
240         return list
241                 
242
243 def get_output (name):
244         return  output_dict[format][name]
245
246 def get_re (name):
247         return  re_dict[format][name]
248
249 def bounding_box_dimensions(fname):
250         try:
251                 fd = open(fname)
252         except IOError:
253                 error ("Error opening `%s'" % fname)
254         str = fd.read ()
255         s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
256         if s:
257                 return (int(s.group(3))-int(s.group(1)), 
258                         int(s.group(4))-int(s.group(2)))
259         else:
260                 return (0,0)
261
262
263 def error (str):
264         sys.stderr.write (str + "\n  Exiting ... \n\n")
265         raise 'Exiting.'
266
267
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'
273         else:
274                 paper = 'letter' # yes, latex use letter as default, at least
275                                  # my tetex distro
276         music_size = default_music_fontsize
277         latex_size = default_text_fontsize
278         for o in opts:
279                 m = re.search ('^(.*)paper$', o)
280                 if m:
281                         paper = m.group (1)
282                 
283                 if g_force_mudela_fontsize:
284                         music_size = g_force_mudela_fontsize
285                 else:
286                         m = re.match ('([0-9]+)pt', o)
287                         if m:
288                                 music_size = string.atoi(m.group (1))
289
290                 m = re.match ('latexfontsize=([0-9]+)pt', o)
291                 if m:
292                         latex_size = string.atoi (m.group (1))
293
294         if re.search ('\\\\score', body):
295                 is_fragment = 0
296         else:
297                 is_fragment = 1
298         if 'fragment' in opts:
299                 is_fragment = 1
300         if 'nonfragment' in opts:
301                 is_fragment = 0
302
303         if is_fragment and not 'multiline' in opts:
304                 opts.append('singleline')
305         if 'singleline' in opts:
306                 l = -1.0;
307         else:
308                 l = get_linewidth(g_num_cols, paper, latex_size)
309         
310         if 'relative' in opts:#ugh only when is_fragment
311                 body = '\\relative c { %s }' % body
312         
313         if is_fragment:
314                 body = r"""\score { 
315  \notes { %s }
316   \paper { }  
317 }""" % body
318
319         opts = uniq (opts)
320         optstring = string.join (opts, ' ')
321         optstring = re.sub ('\n', ' ', optstring)
322         
323         body = r"""
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
328         return body
329
330
331 def scan_preamble (str):
332         options = []
333         if __main__.format == 'texi':
334                 x = 250
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])
345
346         def verbose_fontsize ( x):
347                 if re.match('[0-9]+pt', x):
348                         return 'latexfontsize=' + x
349                 else:
350                         return x 
351                         
352         options = map (verbose_fontsize, options)
353         return options
354
355
356 def completize_preamble (str):
357         m = get_re ('preamble-end').search( str)
358         if not m:
359                 return str
360         
361         preamble = str [:m.start (0)]
362         str = str [m.start(0):]
363         
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')
368
369         # UGH ! BUG!
370         #if  re.search ('\\\\includegraphics', str) and not re.search ('usepackage{graphics}',str):
371
372         preamble = preamble + '\\usepackage{graphics}\n'
373
374         return preamble + str
375
376
377 read_files = []
378 def find_file (name):
379         f = None
380         for a in include_path:
381                 try:
382                         nm = os.path.join (a, name)
383                         f = open (nm)
384                         __main__.read_files.append (nm)
385                         break
386                 except IOError:
387                         pass
388         if f:
389                 return f.read ()
390         else:
391                 error ("File not found `%s'\n" % name)
392                 return ''
393
394 def do_ignore(match_object):
395         return []
396
397 def make_verbatim(match_object):
398         return [('verbatim', match_object.group('code'))]
399
400 def make_verb(match_object):
401         return [('verb', match_object.group('code'))]
402
403 def do_include_file(m):
404         "m: MatchObject"
405         return [('input', get_output ('pagebreak'))] \
406              + read_doc_file(m.group('filename')) \
407              + [('input', get_output ('pagebreak'))] 
408
409 def do_input_file(m):
410         return read_doc_file(m.group('filename'))
411
412 def make_mudela(m):
413         if m.group('options'):
414                 options = m.group('options')
415         else:
416                 options = ''
417         return [('input', get_output('output-mudela-fragment') % 
418                         (options, m.group('code')))]
419
420 def make_mudela_file(m):
421         if m.group('options'):
422                 options = m.group('options')
423         else:
424                 options = ''
425         return [('input', get_output('output-mudela') %
426                         (options, find_file(m.group('filename'))))]
427
428 def make_mudela_block(m):
429         if m.group('options'):
430                 options = get_re('option-sep').split (m.group('options'))
431         else:
432             options = []
433         options = filter(lambda s: s != '', options)
434         if 'mbverbatim' in options:#ugh this is ugly and only for texi format
435                 s  = m.group()
436                 im = get_re('intertext').search(s)
437                 if im:
438                         s = s[:im.start()] + s[im.end():]
439                 im = re.search('mbverbatim', s)
440                 if im:
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)]
446
447 def do_columns(m):
448         if __main__.format != 'latex':
449                 return []
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)]
454         
455 def chop_chunks(chunks, re_name, func):
456     newchunks = []
457     for c in chunks:
458         if c[0] == 'input':
459             str = c[1]
460             while str:
461                 m = get_re (re_name).search (str)
462                 if m == None:
463                     newchunks.append (('input', str))
464                     str = ''
465                 else:
466                     newchunks.append (('input', str[:m.start (0)]))
467                     newchunks.extend(func(m))
468                     str = str [m.end(0):]
469         else:
470             newchunks.append(c)
471     return newchunks
472
473 def read_doc_file (filename):
474         """Read the input file, find verbatim chunks and do \input and \include
475         """
476         str = ''
477         str = find_file(filename)
478
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")
484                 if texinfo:
485                         __main__.format = 'texi'
486                 else:
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)
495         #ugh fix input
496         chunks = chop_chunks(chunks, 'include', do_include_file)
497         chunks = chop_chunks(chunks, 'input', do_input_file)
498         return chunks
499
500
501 def advance_counters (counter, str):
502         """Advance chap/sect counters,
503         Return the new counter tuple
504         """
505         (chapter, section, count) = counter
506         while str:
507                 m = get_re ('interesting-cs').search(str)
508                 if not m:
509                         break
510                 str = str[m.end(0):]
511                 g = m.group (1)
512                 if g == 'chapter':#ugh use dict
513                         (chapter, section, count)  = (chapter + 1, 0, 0)
514                 elif g == 'section':
515                         (section, count)  = (section + 1, 0)
516         return (chapter, section, count)
517
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
523         TODO.
524
525         Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
526
527         TODO has format [basename, extension, extension, ... ]
528         
529         """
530         if len(chunk) == 3:
531                 (type, body, opts) = chunk
532                 complete_body = None
533         else:# mbverbatim
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))`
540         for o in opts:
541                 m = re.search ('filename="(.*?)"', o)
542                 if m:
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
548         if not g_read_lys:
549                 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
550         needed_filetypes = ['tex']
551
552         if format  == 'texi':
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
562         else:
563                 todo = []
564                 
565         newbody = ''
566         if 'verbatim' in opts:
567                 newbody = output_verbatim (body)
568         elif 'mbverbatim' in opts:
569                 newbody = output_mbverbatim (complete_body)
570
571         for o in opts:
572                 m = re.search ('intertext="(.*?)"', o)
573                 if m:
574                         newbody = newbody  + m.group (1)
575         if format == 'latex':
576                 if 'eps' in opts:
577                         s = 'output-eps'
578                 else:
579                         s = 'output-tex'
580         else: # format == 'texi'
581                 s = 'output-all'
582         newbody = newbody + get_output(s) % {'fn': basename }
583         return ('mudela', newbody, opts, todo, basename)
584
585 def process_mudela_blocks(outname, chunks, global_options):#ugh rename
586         (chap,sect,count) = (0,0,0)
587         newchunks = []
588         # Count sections/chapters.
589         for c in chunks:
590                 if c[0] == 'input':
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)
594                         count = count + 1
595                         c = schedule_mudela_block (base, c, global_options)
596                 elif c[0] == 'numcols':
597                         __main__.g_num_cols = c[2]
598                 newchunks.append (c)
599         return newchunks
600
601
602 def find_eps_dims (match):
603         "Fill in dimensions of EPS files."
604         
605         fn =match.group (1)
606         dims = bounding_box_dimensions (fn)
607
608         return '%ipt' % dims[0]
609
610
611 def system (cmd):
612         sys.stderr.write ("invoking `%s'\n" % cmd)
613         st = os.system (cmd)
614         if st:
615                 error ('Error command exited with value %d\n' % st)
616         return st
617
618 def compile_all_files (chunks):
619         eps = []
620         tex = []
621         png = []
622
623         for c in chunks:
624                 if c[0] <> 'mudela':
625                         continue
626                 base  = c[4]
627                 exts = c[3]
628                 for e in exts:
629                         if e == 'eps':
630                                 eps.append (base)
631                         elif e == 'tex':
632                                 tex.append (base + '.ly')
633                         elif e == 'png' and g_do_pictures:
634                                 png.append (base)
635         d = os.getcwd()
636         if g_outdir:
637                 os.chdir(g_outdir)
638         if tex:
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))
643         for e in eps:
644                 system(r"tex '\nonstopmode \input %s'" % e)
645                 system(r"dvips -E -o %s %s" % (e + '.eps', e))
646         for g in png:
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')
649                 system (cmd)
650         if g_outdir:
651                 os.chdir(d)
652
653
654 def update_file (body, name):
655         """
656         write the body if it has changed
657         """
658         same = 0
659         try:
660                 f = open (name)
661                 fs = f.read (-1)
662                 same = (fs == body)
663         except:
664                 pass
665
666         if not same:
667                 f = open (name , 'w')
668                 f.write (body)
669                 f.close ()
670         
671         return not same
672
673
674 def getopt_args (opts):
675         "Construct arguments (LONG, SHORT) for getopt from  list of options."
676         short = ''
677         long = []
678         for o in opts:
679                 if o[1]:
680                         short = short + o[1]
681                         if o[0]:
682                                 short = short + ':'
683                 if o[2]:
684                         l = o[2]
685                         if o[0]:
686                                 l = l + '='
687                         long.append (l)
688         return (short, long)
689
690 def option_help_str (o):
691         "Transform one option description (4-tuple ) into neatly formatted string"
692         sh = '  '       
693         if o[1]:
694                 sh = '-%s' % o[1]
695
696         sep = ' '
697         if o[1] and o[2]:
698                 sep = ','
699                 
700         long = ''
701         if o[2]:
702                 long= '--%s' % o[2]
703
704         arg = ''
705         if o[0]:
706                 if o[2]:
707                         arg = '='
708                 arg = arg + o[0]
709         return '  ' + sh + sep + long + arg
710
711
712 def options_help_str (opts):
713         "Convert a list of options into a neatly formatted string"
714         w = 0
715         strs =[]
716         helps = []
717
718         for o in opts:
719                 s = option_help_str (o)
720                 strs.append ((s, o[3]))
721                 if len (s) > w:
722                         w = len (s)
723
724         str = ''
725         for s in strs:
726                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
727         return str
728
729 def help():
730         sys.stdout.write("""Usage: mudela-book [options] FILE\n
731 Generate hybrid LaTeX input from Latex + mudela
732 Options:
733 """)
734         sys.stdout.write (options_help_str (option_definitions))
735         sys.stdout.write (r"""Warning all output is written in the CURRENT directory
736
737
738
739 Report bugs to bug-gnu-music@gnu.org.
740
741 Written by Tom Cato Amundsen <tca@gnu.org> and
742 Han-Wen Nienhuys <hanwen@cs.uu.nl>
743 """)
744
745         sys.exit (0)
746
747
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:
753                 f.write ('%s ' %  d)
754         f.write ('\n')
755         f.close ()
756         __main__.read_files = []
757
758 def identify():
759         sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
760
761 def print_version ():
762         identify()
763         sys.stdout.write (r"""Copyright 1998--1999
764 Distributed under terms of the GNU General Public License. It comes with
765 NO WARRANTY.
766 """)
767
768 def do_file(input_filename):
769         file_settings = {}
770         if outname:
771                 my_outname = outname
772         else:
773                 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
774         my_depname = my_outname + '.dep'                
775
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)
784         # Do It.
785         if __main__.g_run_lilypond:
786                 compile_all_files (chunks)
787                 newchunks = []
788                 # finishing touch.
789                 for c in 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))
793                         else:
794                                 newchunks.append (c)
795                 chunks = newchunks
796
797         if chunks and chunks[0][0] == 'input':
798                 chunks[0] = ('input', completize_preamble (chunks[0][1]))
799
800         foutn = os.path.join(g_outdir, my_outname + '.' + format)
801         sys.stderr.write ("Writing `%s'\n" % foutn)
802         fout = open (foutn, 'w')
803         for c in chunks:
804                 #if c[1] is not None:
805                         fout.write (c[1])
806         fout.close ()
807
808         if do_deps:
809                 write_deps (my_depname, foutn)
810
811
812 outname = ''
813 try:
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)
818         sys.exit(1)
819
820 do_deps = 0
821 for opt in options:     
822         o = opt[0]
823         a = opt[1]
824
825         if o == '--include' or o == '-I':
826                 include_path.append (a)
827         elif o == '--version':
828                 print_version ()
829                 sys.exit  (0)
830
831         elif o == '--format' or o == '-o':
832                 __main__.format = a
833         elif o == '--outname' or o == '-o':
834                 if len(files) > 1:
835                         #HACK
836                         sys.stderr.write("Mudela-book is confused by --outname on multiple files")
837                         sys.exit(1)
838                 outname = a
839         elif o == '--help' or o == '-h':
840                 help ()
841         elif o == '--no-lily' or o == '-n':
842                 __main__.g_run_lilypond = 0
843         elif o == '--dependencies':
844                 do_deps = 1
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)
855
856         elif o == '--init':
857                 initfile =  a
858         elif o == '--dep-prefix':
859                 g_dep_prefix = a
860         elif o == '--no-pictures':
861                 g_do_pictures = 0
862         elif o == '--read-lys':
863                 g_read_lys = 1
864         elif o == '--outdir':
865                 g_outdir = a
866
867 identify()
868 if g_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):
872                 os.mkdir(g_outdir)
873 for input_filename in files:
874         do_file(input_filename)
875         
876
877
878 t2 = time.clock()
879 print "Time:", t2-t1
880 #
881 # Petr, ik zou willen dat ik iets zinvoller deed,
882 # maar wat ik kan ik doen, het verandert toch niets?
883 #   --hwn 20/aug/99