]> git.donarmstrong.com Git - lilypond.git/blob - scripts/mudela-book.py
patch::: 1.3.96.jcn9
[lilypond.git] / scripts / mudela-book.py
1 #!@PYTHON@
2 # vim: set noexpandtab:
3 # TODO:
4 # * Figure out clean set of options. Hmm, isn't it pretty ok now?
5 # * add support for .lilyrc
6
7
8 # This is was the idea for handling of comments:
9 #       Multiline comments, @ignore .. @end ignore is scanned for
10 #       in read_doc_file, and the chunks are marked as 'ignore', so
11 #       mudela-book will not touch them any more. The content of the
12 #       chunks are written to the output file. Also 'include' and 'input'
13 #       regex has to check if they are commented out.
14 #
15 #       Then it is scanned for 'mudela', 'mudela-file' and 'mudela-block'.
16 #       These three regex's has to check if they are on a commented line,
17 #       % for latex, @c for texinfo.
18 #
19 #       Then lines that are commented out with % (latex) and @c (Texinfo)
20 #       are put into chunks marked 'ignore'. This cannot be done before
21 #       searching for the mudela-blocks because % is also the comment character
22 #       for lilypond.
23 #
24 #       The the rest of the rexeces are searched for. They don't have to test
25 #       if they are on a commented out line.
26
27 import os
28 import stat
29 import string
30 import re
31 import getopt
32 import sys
33 import __main__
34 import operator
35
36
37 program_version = '@TOPLEVEL_VERSION@'
38 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
39         program_version = '1.3.85'      
40
41 include_path = [os.getcwd()]
42
43 g_dep_prefix = ''
44 g_outdir = ''
45 g_force_mudela_fontsize = 0
46 g_read_lys = 0
47 g_do_pictures = 1
48 g_num_cols = 1
49 format = ''
50 g_run_lilypond = 1
51 no_match = 'a\ba'
52
53 default_music_fontsize = 16
54 default_text_fontsize = 12
55
56
57 class LatexPaper:
58         def __init__(self):
59                 self.m_paperdef =  {
60                         # the dimentions are from geometry.sty
61                         'a0paper': (mm2pt(841), mm2pt(1189)),
62                         'a1paper': (mm2pt(595), mm2pt(841)),
63                         'a2paper': (mm2pt(420), mm2pt(595)),
64                         'a3paper': (mm2pt(297), mm2pt(420)),
65                         'a4paper': (mm2pt(210), mm2pt(297)),
66                         'a5paper': (mm2pt(149), mm2pt(210)),
67                         'b0paper': (mm2pt(1000), mm2pt(1414)),
68                         'b1paper': (mm2pt(707), mm2pt(1000)),
69                         'b2paper': (mm2pt(500), mm2pt(707)),
70                         'b3paper': (mm2pt(353), mm2pt(500)),
71                         'b4paper': (mm2pt(250), mm2pt(353)),
72                         'b5paper': (mm2pt(176), mm2pt(250)),
73                         'letterpaper': (in2pt(8.5), in2pt(11)),
74                         'legalpaper': (in2pt(8.5), in2pt(14)),
75                         'executivepaper': (in2pt(7.25), in2pt(10.5))}
76                 self.m_use_geometry = None
77                 self.m_papersize = 'letterpaper'
78                 self.m_fontsize = 10
79                 self.m_num_cols = 1
80                 self.m_landscape = 0
81                 self.m_geo_landscape = 0
82                 self.m_geo_width = None
83                 self.m_geo_textwidth = None
84                 self.m_geo_lmargin = None
85                 self.m_geo_rmargin = None
86                 self.m_geo_includemp = None
87                 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
88                 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
89                 self.m_geo_x_marginparwidth = None
90                 self.m_geo_x_marginparsep = None
91                 self.__body = None
92         def set_geo_option(self, name, value):
93                 if name == 'body' or name == 'text':
94                         if type(value) == type(""):
95                                 self._set_dimen('m_geo_textwidth', value)
96                         else:
97                                 self._set_dimen('m_geo_textwidth', value[0])
98                         self.__body = 1
99                 elif name == 'portrait':
100                         self.m_geo_landscape = 0
101                 elif name == 'reversemp' or name == 'reversemarginpar':
102                         if self.m_geo_includemp == None:
103                                 self.m_geo_includemp = 1
104                 elif name == 'marginparwidth' or name == 'marginpar':
105                         self._set_dimen('m_geo_x_marginparwidth', value)
106                         self.m_geo_includemp = 1
107                 elif name == 'marginparsep':
108                         self._set_dimen('m_geo_x_marginparsep', value)
109                         self.m_geo_includemp = 1
110                 elif name == 'scale':
111                         if type(value) == type(""):
112                                 self.m_geo_width = self.get_paperwidth() * float(value)
113                         else:
114                                 self.m_geo_width = self.get_paperwidth() * float(value[0])
115                 elif name == 'hscale':
116                         self.m_geo_width = self.get_paperwidth() * float(value)
117                 elif name == 'left' or name == 'lmargin':
118                         self._set_dimen('m_geo_lmargin', value)
119                 elif name == 'right' or name == 'rmargin':
120                         self._set_dimen('m_geo_rmargin', value)
121                 elif name == 'hdivide' or name == 'divide':
122                         if value[0] not in ('*', ''):
123                                 self._set_dimen('m_geo_lmargin', value[0])
124                         if value[1] not in ('*', ''):
125                                 self._set_dimen('m_geo_width', value[1])
126                         if value[2] not in ('*', ''):
127                                 self._set_dimen('m_geo_rmargin', value[2])
128                 elif name == 'hmargin':
129                         if type(value) == type(""):
130                                 self._set_dimen('m_geo_lmargin', value)
131                                 self._set_dimen('m_geo_rmargin', value)
132                         else:
133                                 self._set_dimen('m_geo_lmargin', value[0])
134                                 self._set_dimen('m_geo_rmargin', value[1])
135                 elif name == 'margin':#ugh there is a bug about this option in
136                                         # the geometry documentation
137                         if type(value) == type(""):
138                                 self._set_dimen('m_geo_lmargin', value)
139                                 self._set_dimen('m_geo_rmargin', value)
140                         else:
141                                 self._set_dimen('m_geo_lmargin', value[0])
142                                 self._set_dimen('m_geo_rmargin', value[0])
143                 elif name == 'total':
144                         if type(value) == type(""):
145                                 self._set_dimen('m_geo_width', value)
146                         else:
147                                 self._set_dimen('m_geo_width', value[0])
148                 elif name == 'width' or name == 'totalwidth':
149                         self._set_dimen('m_geo_width', value)
150                 elif name == 'paper' or name == 'papername':
151                         self.m_papersize = value
152                 elif name[-5:] == 'paper':
153                         self.m_papersize = name
154                 else:
155                         self._set_dimen('m_geo_'+name, value)
156         def _set_dimen(self, name, value):
157                 if type(value) == type("") and value[-2:] == 'pt':
158                         self.__dict__[name] = float(value[:-2])
159                 elif type(value) == type("") and value[-2:] == 'mm':
160                         self.__dict__[name] = mm2pt(float(value[:-2]))
161                 elif type(value) == type("") and value[-2:] == 'cm':
162                         self.__dict__[name] = 10 * mm2pt(float(value[:-2]))
163                 elif type(value) == type("") and value[-2:] == 'in':
164                         self.__dict__[name] = in2pt(float(value[:-2]))
165                 else:
166                         self.__dict__[name] = value
167         def display(self):
168                 print "LatexPaper:\n-----------"
169                 for v in self.__dict__.keys():
170                         if v[:2] == 'm_':
171                                 print v, self.__dict__[v]
172                 print "-----------"
173         def get_linewidth(self):
174                 w = self._calc_linewidth()
175                 if self.m_num_cols == 2:
176                         return (w - 10) / 2
177                 else:
178                         return w
179         def get_paperwidth(self):
180                 #if self.m_use_geometry:
181                         return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
182                 #return self.m_paperdef[self.m_papersize][self.m_landscape]
183         
184         def _calc_linewidth(self):
185                 # since geometry sometimes ignores 'includemp', this is
186                 # more complicated than it should be
187                 mp = 0
188                 if self.m_geo_includemp:
189                         if self.m_geo_x_marginparsep is not None:
190                                 mp = mp + self.m_geo_x_marginparsep
191                         else:
192                                 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
193                         if self.m_geo_x_marginparwidth is not None:
194                                 mp = mp + self.m_geo_x_marginparwidth
195                         else:
196                                 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
197                 if self.__body:#ugh test if this is necessary
198                         mp = 0
199                 def tNone(a, b, c):
200                         return a == None, b == None, c == None
201                 if not self.m_use_geometry:
202                         return latex_linewidths[self.m_papersize][self.m_fontsize]
203                 else:
204                         if tNone(self.m_geo_lmargin, self.m_geo_width,
205                                 self.m_geo_rmargin) == (1, 1, 1):
206                                 if self.m_geo_textwidth:
207                                         return self.m_geo_textwidth
208                                 w = self.get_paperwidth() * 0.8
209                                 return w - mp
210                         elif tNone(self.m_geo_lmargin, self.m_geo_width,
211                                  self.m_geo_rmargin) == (0, 1, 1):
212                                  if self.m_geo_textwidth:
213                                         return self.m_geo_textwidth
214                                  return self.f1(self.m_geo_lmargin, mp)
215                         elif tNone(self.m_geo_lmargin, self.m_geo_width,
216                                  self.m_geo_rmargin) == (1, 1, 0):
217                                  if self.m_geo_textwidth:
218                                         return self.m_geo_textwidth
219                                  return self.f1(self.m_geo_rmargin, mp)
220                         elif tNone(self.m_geo_lmargin, self.m_geo_width,
221                                 self.m_geo_rmargin) \
222                                         in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
223                                 if self.m_geo_textwidth:
224                                         return self.m_geo_textwidth
225                                 return self.m_geo_width - mp
226                         elif tNone(self.m_geo_lmargin, self.m_geo_width,
227                                 self.m_geo_rmargin) in ((0, 1, 0), (0, 0, 0)):
228                                 w = self.get_paperwidth() - self.m_geo_lmargin - self.m_geo_rmargin - mp
229                                 if w < 0:
230                                         w = 0
231                                 return w
232                         raise "Never do this!"
233         def f1(self, m, mp):
234                 tmp = self.get_paperwidth() - m * 2 - mp
235                 if tmp < 0:
236                         tmp = 0
237                 return tmp
238         def f2(self):
239                 tmp = self.get_paperwidth() - self.m_geo_lmargin \
240                         - self.m_geo_rmargin
241                 if tmp < 0:
242                         return 0
243                 return tmp
244
245 class TexiPaper:
246         def __init__(self):
247                 self.m_papersize = 'a4'
248                 self.m_fontsize = 12
249         def get_linewidth(self):
250                 return texi_linewidths[self.m_papersize][self.m_fontsize]
251
252 def mm2pt(x):
253         return x * 2.8452756
254 def in2pt(x):
255         return x * 72.26999
256 def em2pt(x, fontsize):
257         return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
258 def ex2pt(x, fontsize):
259         return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
260         
261 # latex linewidths:
262 # indices are no. of columns, papersize,  fontsize
263 # Why can't this be calculated?
264 latex_linewidths = {
265         'a4paper':{10: 345, 11: 360, 12: 390},
266         'a4paper-landscape': {10: 598, 11: 596, 12:592},
267         'a5paper':{10: 276, 11: 276, 12: 276},
268         'b5paper':{10: 345, 11: 356, 12: 356},
269         'letterpaper':{10: 345, 11: 360, 12: 390},
270         'letterpaper-landscape':{10: 598, 11: 596, 12:596},
271         'legalpaper': {10: 345, 11: 360, 12: 390},
272         'executivepaper':{10: 345, 11: 360, 12: 379}}
273
274 texi_linewidths = {
275         'a4': {12: 455},
276         'a4wide': {12: 470},
277         'smallbook': {12: 361},
278         'texidefault': {12: 433}}
279
280 option_definitions = [
281   ('EXT', 'f', 'format', 'set format.  EXT is one of texi and latex.'),
282   ('DIM',  '', 'default-music-fontsize', 'default fontsize for music.  DIM is assumed to be in points'),
283   ('DIM',  '', 'default-mudela-fontsize', 'deprecated, use --default-music-fontsize'),
284   ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline mudela. DIM is assumed be to in points'),
285   ('DIM', '', 'force-mudela-fontsize', 'deprecated, use --force-music-fontsize'),
286   ('DIR', 'I', 'include', 'include path'),
287   ('', 'M', 'dependencies', 'write dependencies'),
288   ('PREF', '',  'dep-prefix', 'prepend PREF before each -M dependency'),
289   ('', 'n', 'no-lily', 'don\'t run lilypond'),
290   ('', '', 'no-pictures', "don\'t generate pictures"),
291   ('', '', 'read-lys', "don't write ly files."),
292   ('FILE', 'o', 'outname', 'filename main output file'),
293   ('FILE', '', 'outdir', "where to place generated files"),
294   ('', 'v', 'version', 'print version information' ),
295   ('', 'h', 'help', 'print help'),
296   ]
297
298 # format specific strings, ie. regex-es for input, and % strings for output
299 output_dict= {
300         'latex': {
301                 'output-mudela-fragment' : r"""\begin[eps,singleline,%s]{mudela}
302   \context Staff <
303     \context Voice{
304       %s
305     }
306   >
307 \end{mudela}""", 
308                 'output-mudela':r"""\begin[%s]{mudela}
309 %s
310 \end{mudela}""",
311                 'output-verbatim': "\\begin{verbatim}%s\\end{verbatim}",
312                 'output-default-post': "\\def\postMudelaExample{}\n",
313                 'output-default-pre': "\\def\preMudelaExample{}\n",
314                 'usepackage-graphics': '\\usepackage{graphics}\n',
315                 'output-eps': '\\noindent\\parbox{\\mudelaepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
316                 'output-tex': '\\preMudelaExample \\input %(fn)s.tex \\postMudelaExample\n',
317                 'pagebreak': r'\pagebreak',
318                 },
319         'texi' : {'output-mudela': """@mudela[%s]
320 %s
321 @end mudela 
322 """,
323                   'output-mudela-fragment': """@mudela[%s]
324 \context Staff\context Voice{ %s }
325 @end mudela """,
326                   'pagebreak': None,
327                   'output-verbatim': r"""@example
328 %s
329 @end example
330 """,
331
332 # do some tweaking: @ is needed in some ps stuff.
333 # override EndLilyPondOutput, since @tex is done
334 # in a sandbox, you can't do \input lilyponddefs at the
335 # top of the document.
336
337 # should also support fragment in
338                   
339                   'output-all': r"""@tex
340 \catcode`\@=12
341 \input lilyponddefs
342 \def\EndLilyPondOutput{}
343 \input %(fn)s.tex
344 \catcode`\@=0
345 @end tex
346 @html
347 <p>
348 <img src=%(fn)s.png>
349 @end html
350 """,
351                 }
352         }
353
354 def output_verbatim (body):
355         if __main__.format == 'texi':
356                 body = re.sub ('([@{}])', '@\\1', body)
357         return get_output ('output-verbatim') % body
358
359
360 re_dict = {
361         'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
362                   'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
363                   'option-sep' : ', *',
364                   'header': r"\\documentclass\s*(\[.*?\])?",
365                   'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
366                   'preamble-end': r'(?P<code>\\begin{document})',
367                   'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
368                   'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
369                   'mudela-file': r'(?m)^[^%\n]*?(?P<match>\\mudelafile(\[(?P<options>.*?)\])?\{(?P<filename>.+)})',
370                   'mudela' : r'(?m)^[^%\n]*?(?P<match>\\mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
371                   'mudela-block': r"(?sm)^[^%\n]*?(?P<match>\\begin(\[(?P<options>.*?)\])?{mudela}(?P<code>.*?)\\end{mudela})",
372                   'def-post-re': r"\\def\\postMudelaExample",
373                   'def-pre-re': r"\\def\\preMudelaExample",               
374                   'usepackage-graphics': r"\usepackage{graphics}",
375                   'intertext': r',?\s*intertext=\".*?\"',
376                   'multiline-comment': no_match,
377                   'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
378                   'numcols': r"(?P<code>\\(?P<num>one|two)column)",
379                   },
380         
381         'texi': {
382                  'include':  '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
383                  'input': no_match,
384                  'header': no_match,
385                  'preamble-end': no_match,
386                  'landscape': no_match,
387                  'verbatim': r"""(?s)(?P<code>@example\s.*?@end example\s)""",
388                  'verb': r"""(?P<code>@code{.*?})""",
389                  'mudela-file': '(?m)^(?!@c)(?P<match>@mudelafile(\[(?P<options>.*?)\])?{(?P<filename>[^}]+)})',
390                  'mudela' : '(?m)^(?!@c)(?P<match>@mudela(\[(?P<options>.*?)\])?{(?P<code>.*?)})',
391                  'mudela-block': r"""(?m)^(?!@c)(?P<match>(?s)(?P<match>@mudela(\[(?P<options>.*?)\])?\s(?P<code>.*?)@end mudela\s))""",
392                   'option-sep' : ', *',
393                   'intertext': r',?\s*intertext=\".*?\"',
394                   'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
395                   'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
396                   'numcols': no_match,
397                  }
398         }
399
400
401 for r in re_dict.keys ():
402         olddict = re_dict[r]
403         newdict = {}
404         for k in olddict.keys ():
405                 newdict[k] = re.compile (olddict[k])
406         re_dict[r] = newdict
407
408         
409 def uniq (list):
410         list.sort ()
411         s = list
412         list = []
413         for x in s:
414                 if x not in list:
415                         list.append (x)
416         return list
417                 
418
419 def get_output (name):
420         return  output_dict[format][name]
421
422 def get_re (name):
423         return  re_dict[format][name]
424
425 def bounding_box_dimensions(fname):
426         try:
427                 fd = open(fname)
428         except IOError:
429                 error ("Error opening `%s'" % fname)
430         str = fd.read ()
431         s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
432         if s:
433                 return (int(s.group(3))-int(s.group(1)), 
434                         int(s.group(4))-int(s.group(2)))
435         else:
436                 return (0,0)
437
438
439 def error (str):
440         sys.stderr.write (str + "\n  Exiting ... \n\n")
441         raise 'Exiting.'
442
443
444 def compose_full_body (body, opts):
445         """Construct the mudela code to send to Lilypond.
446         Add stuff to BODY using OPTS as options."""
447         music_size = default_music_fontsize
448         latex_size = default_text_fontsize
449         for o in opts:
450                 if g_force_mudela_fontsize:
451                         music_size = g_force_mudela_fontsize
452                 else:
453                         m = re.match ('([0-9]+)pt', o)
454                         if m:
455                                 music_size = string.atoi(m.group (1))
456
457                 m = re.match ('latexfontsize=([0-9]+)pt', o)
458                 if m:
459                         latex_size = string.atoi (m.group (1))
460
461         if re.search ('\\\\score', body):
462                 is_fragment = 0
463         else:
464                 is_fragment = 1
465         if 'fragment' in opts:
466                 is_fragment = 1
467         if 'nonfragment' in opts:
468                 is_fragment = 0
469
470         if is_fragment and not 'multiline' in opts:
471                 opts.append('singleline')
472         if 'singleline' in opts:
473                 l = -1.0;
474         else:
475                 l = paperguru.get_linewidth()
476         
477         if 'relative' in opts:#ugh only when is_fragment
478                 body = '\\relative c { %s }' % body
479         
480         if is_fragment:
481                 body = r"""\score { 
482  \notes { %s }
483   \paper { }  
484 }""" % body
485
486         opts = uniq (opts)
487         optstring = string.join (opts, ' ')
488         optstring = re.sub ('\n', ' ', optstring)
489         body = r"""
490 %% Generated by mudela-book.py; options are %s  %%ughUGH not original options
491 \include "paper%d.ly"
492 \paper  { linewidth = %f \pt; } 
493 """ % (optstring, music_size, l) + body
494         return body
495
496 def parse_options_string(s):
497         d = {}
498         r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
499         r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
500         r3 = re.compile("(\w+?)((,\s*)|$)")
501         while s:
502                 m = r1.match(s)
503                 if m:
504                         s = s[m.end():]
505                         d[m.group(2)] = re.split(",\s*", m.group(3))
506                         continue
507                 m = r2.match(s)
508                 if m:
509                         s = s[m.end():]
510                         d[m.group(2)] = m.group(3)
511                         continue
512                 m = r3.match(s)
513                 if m:
514                         s = s[m.end():]
515                         d[m.group(1)] = 1
516                         continue
517                 print "trøbbel:%s:" % s
518         return d
519
520 def scan_latex_preamble(chunks):
521         # first we want to scan the \documentclass line
522         # it should be the first non-comment line
523         idx = 0
524         while 1:
525                 if chunks[idx][0] == 'ignore':
526                         idx = idx + 1
527                         continue
528                 m = get_re ('header').match(chunks[idx][1])
529                 options = re.split (',[\n \t]*', m.group(1)[1:-1])
530                 for o in options:
531                         if o == 'landscape':
532                                 paperguru.m_landscape = 1
533                         m = re.match("(.*?)paper", o)
534                         if m:
535                                 paperguru.m_papersize = m.group()
536                         else:
537                                 m = re.match("(\d\d)pt", o)
538                                 if m:
539                                         paperguru.m_fontsize = int(m.group(1))
540                         
541                 break
542         while chunks[idx][0] != 'preamble-end':
543                 if chunks[idx] == 'ignore':
544                         idx = idx + 1
545                         continue
546                 m = get_re ('geometry').search(chunks[idx][1])
547                 if m:
548                         paperguru.m_use_geometry = 1
549                         o = parse_options_string(m.group('options'))
550                         for k in o.keys():
551                                 paperguru.set_geo_option(k, o[k])
552                 idx = idx + 1
553
554 def scan_texi_preamble (chunks):
555         # this is not bulletproof..., it checks the first 10 chunks
556         idx = 0
557         while 1:
558                 if chunks[idx][0] == 'input':
559                         if string.find(chunks[idx][1], "@afourpaper") != -1:
560                                 paperguru.m_papersize = 'a4'
561                         elif string.find(chunks[idx][1], "@afourwide") != -1:
562                                 paperguru.m_papersize = 'a4wide'
563                         elif string.find(chunks[idx][1], "@smallbook") != -1:
564                                 paperguru.m_papersize = 'smallbook'
565                 idx = idx + 1
566                 if idx == 10 or idx == len(chunks):
567                         break
568
569 def scan_preamble (chunks):
570         if __main__.format == 'texi':
571                 scan_texi_preamble(chunks)
572         else:
573                 assert __main__.format == 'latex'
574                 scan_latex_preamble(chunks)
575                 
576
577 def completize_preamble (chunks):
578         if __main__.format == 'texi':
579                 return chunks
580         pre_b = post_b = graphics_b = None
581         for chunk in chunks:
582                 if chunk[0] == 'preamble-end':
583                         break
584                 if chunk[0] == 'input':
585                         m = get_re('def-pre-re').search(chunk[1])
586                         if m:
587                                 pre_b = 1
588                 if chunk[0] == 'input':
589                         m = get_re('def-post-re').search(chunk[1])
590                         if m:
591                                 post_b = 1
592                 if chunk[0] == 'input':
593                         m = get_re('usepackage-graphics').search(chunk[1])
594                         if m:
595                                 graphics_b = 1
596         x = 0
597         while chunks[x][0] != 'preamble-end':
598                 x = x + 1
599         if not pre_b:
600                 chunks.insert(x, ('input', get_output ('output-default-pre')))
601         if not post_b:
602                 chunks.insert(x, ('input', get_output ('output-default-post')))
603         if not graphics_b:
604                 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
605         return chunks
606
607
608 read_files = []
609 def find_file (name):
610         f = None
611         for a in include_path:
612                 try:
613                         nm = os.path.join (a, name)
614                         f = open (nm)
615                         __main__.read_files.append (nm)
616                         break
617                 except IOError:
618                         pass
619         if f:
620                 return f.read ()
621         else:
622                 error ("File not found `%s'\n" % name)
623                 return ''
624
625 def do_ignore(match_object):
626         return [('ignore', match_object.group('code'))]
627 def do_preamble_end(match_object):
628         return [('preamble-end', match_object.group('code'))]
629
630 def make_verbatim(match_object):
631         return [('verbatim', match_object.group('code'))]
632
633 def make_verb(match_object):
634         return [('verb', match_object.group('code'))]
635
636 def do_include_file(m):
637         "m: MatchObject"
638         return [('input', get_output ('pagebreak'))] \
639              + read_doc_file(m.group('filename')) \
640              + [('input', get_output ('pagebreak'))] 
641
642 def do_input_file(m):
643         return read_doc_file(m.group('filename'))
644
645 def make_mudela(m):
646         if m.group('options'):
647                 options = m.group('options')
648         else:
649                 options = ''
650         return [('input', get_output('output-mudela-fragment') % 
651                         (options, m.group('code')))]
652
653 def make_mudela_file(m):
654         if m.group('options'):
655                 options = m.group('options')
656         else:
657                 options = ''
658         return [('input', get_output('output-mudela') %
659                         (options, find_file(m.group('filename'))))]
660
661 def make_mudela_block(m):
662         if m.group('options'):
663                 options = get_re('option-sep').split (m.group('options'))
664         else:
665             options = []
666         options = filter(lambda s: s != '', options)
667         return [('mudela', m.group('code'), options)]
668
669 def do_columns(m):
670         if __main__.format != 'latex':
671                 return []
672         if m.group('num') == 'one':
673                 return [('numcols', m.group('code'), 1)]
674         if m.group('num') == 'two':
675                 return [('numcols', m.group('code'), 2)]
676         
677 def chop_chunks(chunks, re_name, func, use_match=0):
678     newchunks = []
679     for c in chunks:
680         if c[0] == 'input':
681             str = c[1]
682             while str:
683                 m = get_re (re_name).search (str)
684                 if m == None:
685                     newchunks.append (('input', str))
686                     str = ''
687                 else:
688                     if use_match:
689                         newchunks.append (('input', str[:m.start ('match')]))
690                     else:
691                         newchunks.append (('input', str[:m.start (0)]))
692                     #newchunks.extend(func(m))
693                     # python 1.5 compatible:
694                     newchunks = newchunks + func(m)
695                     str = str [m.end(0):]
696         else:
697             newchunks.append(c)
698     return newchunks
699
700 def read_doc_file (filename):
701         """Read the input file, find verbatim chunks and do \input and \include
702         """
703         str = ''
704         str = find_file(filename)
705
706         if __main__.format == '':
707                 latex =  re.search ('\\\\document', str[:200])
708                 texinfo =  re.search ('@node|@setfilename', str[:200])
709                 if (texinfo and latex) or not (texinfo or latex):
710                         error("error: can't determine format, please specify")
711                 if texinfo:
712                         __main__.format = 'texi'
713                 else:
714                         __main__.format = 'latex'
715         if __main__.format == 'texi':
716                 __main__.paperguru = TexiPaper()
717         else:
718                 __main__.paperguru = LatexPaper()
719         chunks = [('input', str)]
720         # we have to check for verbatim before doing include,
721         # because we don't want to include files that are mentioned
722         # inside a verbatim environment
723         chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
724         chunks = chop_chunks(chunks, 'verb', make_verb)
725         chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
726         #ugh fix input
727         chunks = chop_chunks(chunks, 'include', do_include_file, 1)
728         chunks = chop_chunks(chunks, 'input', do_input_file, 1)
729         return chunks
730
731
732 taken_file_names = {}
733 def schedule_mudela_block (chunk):
734         """Take the body and options from CHUNK, figure out how the
735         real .ly should look, and what should be left MAIN_STR (meant
736         for the main file).  The .ly is written, and scheduled in
737         TODO.
738
739         Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
740
741         TODO has format [basename, extension, extension, ... ]
742         
743         """
744         (type, body, opts) = chunk
745         assert type == 'mudela'
746         file_body = compose_full_body (body, opts)
747         basename = `abs(hash (file_body))`
748         for o in opts:
749                 m = re.search ('filename="(.*?)"', o)
750                 if m:
751                         basename = m.group (1)
752                         if not taken_file_names.has_key(basename):
753                             taken_file_names[basename] = 0
754                         else:
755                             taken_file_names[basename] = taken_file_names[basename] + 1
756                             basename = basename + "-%i" % taken_file_names[basename]
757         if not g_read_lys:
758                 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
759         needed_filetypes = ['tex']
760
761         if format  == 'texi':
762                 needed_filetypes.append('eps')
763                 needed_filetypes.append('png')
764         if 'eps' in opts and not ('eps' in needed_filetypes):
765                 needed_filetypes.append('eps')
766         outname = os.path.join(g_outdir, basename)
767         def f(base, ext1, ext2):
768                 a = os.path.isfile(base + ext2)
769                 if (os.path.isfile(base + ext1) and
770                     os.path.isfile(base + ext2) and
771                                 os.stat(base+ext1)[stat.ST_MTIME] >
772                                 os.stat(base+ext2)[stat.ST_MTIME]) or \
773                                 not os.path.isfile(base + ext2):
774                         return 1
775         todo = []
776         if 'tex' in needed_filetypes and f(outname, '.ly', '.tex'):
777                 todo.append('tex')
778         if 'eps' in needed_filetypes and f(outname, '.tex', '.eps'):
779                 todo.append('eps')
780         if 'png' in needed_filetypes and f(outname, '.eps', '.png'):
781                 todo.append('png')
782         newbody = ''
783         if 'verbatim' in opts:
784                 newbody = output_verbatim (body)
785
786         for o in opts:
787                 m = re.search ('intertext="(.*?)"', o)
788                 if m:
789                         newbody = newbody  + m.group (1) + "\n\n"
790         if format == 'latex':
791                 if 'eps' in opts:
792                         s = 'output-eps'
793                 else:
794                         s = 'output-tex'
795         else: # format == 'texi'
796                 s = 'output-all'
797         newbody = newbody + get_output(s) % {'fn': basename }
798         return ('mudela', newbody, opts, todo, basename)
799
800 def process_mudela_blocks(outname, chunks):#ugh rename
801         newchunks = []
802         # Count sections/chapters.
803         for c in chunks:
804                 if c[0] == 'mudela':
805                         c = schedule_mudela_block (c)
806                 elif c[0] == 'numcols':
807                         paperguru.m_num_cols = c[2]
808                 newchunks.append (c)
809         return newchunks
810
811
812 def find_eps_dims (match):
813         "Fill in dimensions of EPS files."
814         
815         fn =match.group (1)
816         dims = bounding_box_dimensions (fn)
817
818         return '%ipt' % dims[0]
819
820
821 def system (cmd):
822         sys.stderr.write ("invoking `%s'\n" % cmd)
823         st = os.system (cmd)
824         if st:
825                 error ('Error command exited with value %d\n' % st)
826         return st
827
828 def compile_all_files (chunks):
829         eps = []
830         tex = []
831         png = []
832
833         for c in chunks:
834                 if c[0] <> 'mudela':
835                         continue
836                 base  = c[4]
837                 exts = c[3]
838                 for e in exts:
839                         if e == 'eps':
840                                 eps.append (base)
841                         elif e == 'tex':
842                                 #ugh
843                                 if base + '.ly' not in tex:
844                                         tex.append (base + '.ly')
845                         elif e == 'png' and g_do_pictures:
846                                 png.append (base)
847         d = os.getcwd()
848         if g_outdir:
849                 os.chdir(g_outdir)
850         if tex:
851                 lilyopts = map (lambda x:  '-I ' + x, include_path)
852                 lilyopts = string.join (lilyopts, ' ' )
853                 texfiles = string.join (tex, ' ')
854                 system ('lilypond %s %s' % (lilyopts, texfiles))
855         for e in eps:
856                 system(r"tex '\nonstopmode \input %s'" % e)
857                 system(r"dvips -E -o %s %s" % (e + '.eps', e))
858         for g in png:
859                 cmd = r"""gs -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r90 -dNOPAUSE %s -c quit | pnmcrop | pnmtopng > %s"""
860                 cmd = cmd % (g + '.eps', g + '.png')
861                 system (cmd)
862         if g_outdir:
863                 os.chdir(d)
864
865
866 def update_file (body, name):
867         """
868         write the body if it has changed
869         """
870         same = 0
871         try:
872                 f = open (name)
873                 fs = f.read (-1)
874                 same = (fs == body)
875         except:
876                 pass
877
878         if not same:
879                 f = open (name , 'w')
880                 f.write (body)
881                 f.close ()
882         
883         return not same
884
885
886 def getopt_args (opts):
887         "Construct arguments (LONG, SHORT) for getopt from  list of options."
888         short = ''
889         long = []
890         for o in opts:
891                 if o[1]:
892                         short = short + o[1]
893                         if o[0]:
894                                 short = short + ':'
895                 if o[2]:
896                         l = o[2]
897                         if o[0]:
898                                 l = l + '='
899                         long.append (l)
900         return (short, long)
901
902 def option_help_str (o):
903         "Transform one option description (4-tuple ) into neatly formatted string"
904         sh = '  '       
905         if o[1]:
906                 sh = '-%s' % o[1]
907
908         sep = ' '
909         if o[1] and o[2]:
910                 sep = ','
911                 
912         long = ''
913         if o[2]:
914                 long= '--%s' % o[2]
915
916         arg = ''
917         if o[0]:
918                 if o[2]:
919                         arg = '='
920                 arg = arg + o[0]
921         return '  ' + sh + sep + long + arg
922
923
924 def options_help_str (opts):
925         "Convert a list of options into a neatly formatted string"
926         w = 0
927         strs =[]
928         helps = []
929
930         for o in opts:
931                 s = option_help_str (o)
932                 strs.append ((s, o[3]))
933                 if len (s) > w:
934                         w = len (s)
935
936         str = ''
937         for s in strs:
938                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
939         return str
940
941 def help():
942         sys.stdout.write("""Usage: mudela-book [options] FILE\n
943 Generate hybrid LaTeX input from Latex + mudela
944 Options:
945 """)
946         sys.stdout.write (options_help_str (option_definitions))
947         sys.stdout.write (r"""Warning all output is written in the CURRENT directory
948
949
950
951 Report bugs to bug-gnu-music@gnu.org.
952
953 Written by Tom Cato Amundsen <tca@gnu.org> and
954 Han-Wen Nienhuys <hanwen@cs.uu.nl>
955 """)
956
957         sys.exit (0)
958
959
960 def write_deps (fn, target):
961         sys.stdout.write('writing `%s\'\n' % os.path.join(g_outdir, fn))
962         f = open (os.path.join(g_outdir, fn), 'w')
963         f.write ('%s%s: ' % (g_dep_prefix, target))
964         for d in __main__.read_files:
965                 f.write ('%s ' %  d)
966         f.write ('\n')
967         f.close ()
968         __main__.read_files = []
969
970 def identify():
971         sys.stdout.write ('mudela-book (GNU LilyPond) %s\n' % program_version)
972
973 def print_version ():
974         identify()
975         sys.stdout.write (r"""Copyright 1998--1999
976 Distributed under terms of the GNU General Public License. It comes with
977 NO WARRANTY.
978 """)
979
980 def do_file(input_filename):
981         file_settings = {}
982         if outname:
983                 my_outname = outname
984         else:
985                 my_outname = os.path.basename(os.path.splitext(input_filename)[0])
986         my_depname = my_outname + '.dep'                
987
988         chunks = read_doc_file(input_filename)
989         chunks = chop_chunks(chunks, 'mudela', make_mudela, 1)
990         chunks = chop_chunks(chunks, 'mudela-file', make_mudela_file, 1)
991         chunks = chop_chunks(chunks, 'mudela-block', make_mudela_block, 1)
992         chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
993         chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
994         chunks = chop_chunks(chunks, 'numcols', do_columns)
995         #print "-" * 50
996         #for c in chunks: print "c:", c;
997         #sys.exit()
998         scan_preamble(chunks)
999         chunks = process_mudela_blocks(my_outname, chunks)
1000         # Do It.
1001         if __main__.g_run_lilypond:
1002                 compile_all_files (chunks)
1003                 newchunks = []
1004                 # finishing touch.
1005                 for c in chunks:
1006                         if c[0] == 'mudela' and 'eps' in c[2]:
1007                                 body = re.sub (r"""\\mudelaepswidth{(.*?)}""", find_eps_dims, c[1])
1008                                 newchunks.append (('mudela', body))
1009                         else:
1010                                 newchunks.append (c)
1011                 chunks = newchunks
1012         x = 0
1013         chunks = completize_preamble (chunks)
1014         foutn = os.path.join(g_outdir, my_outname + '.' + format)
1015         sys.stderr.write ("Writing `%s'\n" % foutn)
1016         fout = open (foutn, 'w')
1017         for c in chunks:
1018                 fout.write (c[1])
1019         fout.close ()
1020
1021         if do_deps:
1022                 write_deps (my_depname, foutn)
1023
1024
1025 outname = ''
1026 try:
1027         (sh, long) = getopt_args (__main__.option_definitions)
1028         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1029 except getopt.error, msg:
1030         sys.stderr.write("error: %s" % msg)
1031         sys.exit(1)
1032
1033 do_deps = 0
1034 for opt in options:     
1035         o = opt[0]
1036         a = opt[1]
1037
1038         if o == '--include' or o == '-I':
1039                 include_path.append (a)
1040         elif o == '--version' or o == '-v':
1041                 print_version ()
1042                 sys.exit  (0)
1043         elif o == '--format' or o == '-f':
1044                 __main__.format = a
1045         elif o == '--outname' or o == '-o':
1046                 if len(files) > 1:
1047                         #HACK
1048                         sys.stderr.write("Mudela-book is confused by --outname on multiple files")
1049                         sys.exit(1)
1050                 outname = a
1051         elif o == '--help' or o == '-h':
1052                 help ()
1053         elif o == '--no-lily' or o == '-n':
1054                 __main__.g_run_lilypond = 0
1055         elif o == '--dependencies' or o == '-M':
1056                 do_deps = 1
1057         elif o == '--default-music-fontsize':
1058                 default_music_fontsize = string.atoi (a)
1059         elif o == '--default-mudela-fontsize':
1060                 print "--default-mudela-fontsize is deprecated, use --default-music-fontsize"
1061                 default_music_fontsize = string.atoi (a)
1062         elif o == '--force-music-fontsize':
1063                 g_force_mudela_fontsize = string.atoi(a)
1064         elif o == '--force-mudela-fontsize':
1065                 print "--force-mudela-fontsize is deprecated, use --default-mudela-fontsize"
1066                 g_force_mudela_fontsize = string.atoi(a)
1067         elif o == '--dep-prefix':
1068                 g_dep_prefix = a
1069         elif o == '--no-pictures':
1070                 g_do_pictures = 0
1071         elif o == '--read-lys':
1072                 g_read_lys = 1
1073         elif o == '--outdir':
1074                 g_outdir = a
1075
1076 identify()
1077 if g_outdir:
1078         if os.path.isfile(g_outdir):
1079                 error ("outdir is a file: %s" % g_outdir)
1080         if not os.path.exists(g_outdir):
1081                 os.mkdir(g_outdir)
1082 for input_filename in files:
1083         do_file(input_filename)
1084         
1085 #
1086 # Petr, ik zou willen dat ik iets zinvoller deed,
1087 # maar wat ik kan ik doen, het verandert toch niets?
1088 #   --hwn 20/aug/99