]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
*** empty log message ***
[lilypond.git] / scripts / lilypond-book.py
1 #!@PYTHON@
2 # vim: set noexpandtab:
3 # TODO:
4 # * junk --outdir for --output 
5 # * Figure out clean set of options.
6 # * 
7 # * EndLilyPondOutput is def'd as vfil. Causes large white gaps.
8 # * texinfo: add support for @pagesize
9
10 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
11 #       because the values are taken directly from texinfo.tex,
12 #       geometry.sty and article.cls. Give me a hint, and I'll
13 #       fix it.)
14
15 #
16 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips. 
17
18
19 # This is was the idea for handling of comments:
20 #       Multiline comments, @ignore .. @end ignore is scanned for
21 #       in read_doc_file, and the chunks are marked as 'ignore', so
22 #       lilypond-book will not touch them any more. The content of the
23 #       chunks are written to the output file. Also 'include' and 'input'
24 #       regex has to check if they are commented out.
25 #
26 #       Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
27 #       These three regex's has to check if they are on a commented line,
28 #       % for latex, @c for texinfo.
29 #
30 #       Then lines that are commented out with % (latex) and @c (Texinfo)
31 #       are put into chunks marked 'ignore'. This cannot be done before
32 #       searching for the lilypond-blocks because % is also the comment character
33 #       for lilypond.
34 #
35 #       The the rest of the rexeces are searched for. They don't have to test
36 #       if they are on a commented out line.
37
38
39
40 import os
41 import stat
42 import string
43 import getopt
44 import sys
45 import __main__
46 import operator
47
48 # Handle bug in Python 1.6-2.1
49 #
50 # there are recursion limits for some patterns in Python 1.6 til 2.1. 
51 # fix this by importing the 1.5.2 implementation pre instead. Fix by Mats.
52
53 if float (sys.version[0:3]) < 2.2:
54         try:
55                 import pre
56                 re = pre
57                 del pre
58         except ImportError:
59                 import re
60 else:
61         import re
62
63 # Attempt to fix problems with limited stack size set by Python!
64 # Sets unlimited stack size. Note that the resource module only
65 # is available on UNIX.
66 try:
67        import resource
68        resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
69 except:
70        pass
71
72 errorport = sys.stderr
73 verbose_p = 0
74
75
76
77 try:
78         import gettext
79         gettext.bindtextdomain ('lilypond', localedir)
80         gettext.textdomain ('lilypond')
81         _ = gettext.gettext
82 except:
83         def _ (s):
84                 return s
85
86 def progress (s):
87         errorport.write (s + '\n')
88
89
90 program_version = '@TOPLEVEL_VERSION@'
91 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
92         program_version = '1.5.53'
93
94 # if set, LILYPONDPREFIX must take prevalence
95 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
96 datadir = '@datadir@'
97
98 if os.environ.has_key ('LILYPONDPREFIX') :
99         datadir = os.environ['LILYPONDPREFIX']
100 else:
101         datadir = '@datadir@'
102
103 while datadir[-1] == os.sep:
104         datadir= datadir[:-1]
105
106 # Try to cater for bad installations of LilyPond, that have
107 # broken TeX setup.  Just hope this doesn't hurt good TeX
108 # setups.  Maybe we should check if kpsewhich can find
109 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
110 environment = {
111         'MFINPUTS' : datadir + '/mf:',
112         'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
113         'TFMFONTS' : datadir + '/tfm:',
114         'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
115         'GS_LIB' : datadir + '/ps',
116 }
117
118 # tex needs lots of memory, more than it gets by default on Debian
119 non_path_environment = {
120         'extra_mem_top' : '1000000',
121         'extra_mem_bottom' : '1000000',
122         'pool_size' : '250000',
123 }
124
125 def setup_environment ():
126         for key in environment.keys ():
127                 val = environment[key]
128                 if os.environ.has_key (key):
129                         val = val + os.pathsep + os.environ[key]
130                 os.environ[key] = val
131
132         for key in non_path_environment.keys ():
133                 val = non_path_environment[key]
134                 os.environ[key] = val
135
136 include_path = [os.getcwd()]
137
138
139 # g_ is for global (?)
140 g_extra_opts = ''
141 g_here_dir = os.getcwd ()
142 g_dep_prefix = ''
143 g_outdir = ''
144 g_force_lilypond_fontsize = 0
145 g_read_lys = 0
146 g_do_pictures = 1
147 g_num_cols = 1
148 g_do_music = 1
149
150 format = ''
151 g_run_lilypond = 1
152 no_match = 'a\ba'
153
154 default_music_fontsize = 16
155 default_text_fontsize = 12
156 paperguru = None
157
158 # this code is ugly. It should be cleaned
159 class LatexPaper:
160         def __init__(self):
161                 self.m_paperdef =  {
162                         # the dimensions are from geometry.sty
163                         'a0paper': (mm2pt(841), mm2pt(1189)),
164                         'a1paper': (mm2pt(595), mm2pt(841)),
165                         'a2paper': (mm2pt(420), mm2pt(595)),
166                         'a3paper': (mm2pt(297), mm2pt(420)),
167                         'a4paper': (mm2pt(210), mm2pt(297)),
168                         'a5paper': (mm2pt(149), mm2pt(210)),
169                         'b0paper': (mm2pt(1000), mm2pt(1414)),
170                         'b1paper': (mm2pt(707), mm2pt(1000)),
171                         'b2paper': (mm2pt(500), mm2pt(707)),
172                         'b3paper': (mm2pt(353), mm2pt(500)),
173                         'b4paper': (mm2pt(250), mm2pt(353)),
174                         'b5paper': (mm2pt(176), mm2pt(250)),
175                         'letterpaper': (in2pt(8.5), in2pt(11)),
176                         'legalpaper': (in2pt(8.5), in2pt(14)),
177                         'executivepaper': (in2pt(7.25), in2pt(10.5))}
178                 self.m_use_geometry = None
179                 self.m_papersize = 'letterpaper'
180                 self.m_fontsize = 10
181                 self.m_num_cols = 1
182                 self.m_landscape = 0
183                 self.m_geo_landscape = 0
184                 self.m_geo_width = None
185                 self.m_geo_textwidth = None
186                 self.m_geo_lmargin = None
187                 self.m_geo_rmargin = None
188                 self.m_geo_includemp = None
189                 self.m_geo_marginparwidth = {10: 57, 11: 50, 12: 35}
190                 self.m_geo_marginparsep = {10: 11, 11: 10, 12: 10}
191                 self.m_geo_x_marginparwidth = None
192                 self.m_geo_x_marginparsep = None
193                 self.__body = None
194         def set_geo_option(self, name, value):
195
196                 if type(value) == type([]):
197                         value = map(conv_dimen_to_float, value)
198                 else:
199                         value = conv_dimen_to_float(value)
200
201                 if name == 'body' or name == 'text':
202                         if type(value) == type([]):
203                                 self.m_geo_textwidth =  value[0]
204                         else:
205                                 self.m_geo_textwidth =  value
206                         self.__body = 1
207                 elif name == 'portrait':
208                         self.m_geo_landscape = 0
209                 elif name == 'reversemp' or name == 'reversemarginpar':
210                         if self.m_geo_includemp == None:
211                                 self.m_geo_includemp = 1
212                 elif name == 'marginparwidth' or name == 'marginpar':
213                         self.m_geo_x_marginparwidth =  value
214                         self.m_geo_includemp = 1
215                 elif name == 'marginparsep':
216                         self.m_geo_x_marginparsep =  value
217                         self.m_geo_includemp = 1
218                 elif name == 'scale':
219                         if type(value) == type([]):
220                                 self.m_geo_width = self.get_paperwidth() * value[0]
221                         else:
222                                 self.m_geo_width = self.get_paperwidth() * value
223                 elif name == 'hscale':
224                         self.m_geo_width = self.get_paperwidth() * value
225                 elif name == 'left' or name == 'lmargin':
226                         self.m_geo_lmargin =  value
227                 elif name == 'right' or name == 'rmargin':
228                         self.m_geo_rmargin =  value
229                 elif name == 'hdivide' or name == 'divide':
230                         if value[0] not in ('*', ''):
231                                 self.m_geo_lmargin =  value[0]
232                         if value[1] not in ('*', ''):
233                                 self.m_geo_width =  value[1]
234                         if value[2] not in ('*', ''):
235                                 self.m_geo_rmargin =  value[2]
236                 elif name == 'hmargin':
237                         if type(value) == type([]):
238                                 self.m_geo_lmargin =  value[0]
239                                 self.m_geo_rmargin =  value[1]
240                         else:
241                                 self.m_geo_lmargin =  value
242                                 self.m_geo_rmargin =  value
243                 elif name == 'margin':#ugh there is a bug about this option in
244                                         # the geometry documentation
245                         if type(value) == type([]):
246                                 self.m_geo_lmargin =  value[0]
247                                 self.m_geo_rmargin =  value[0]
248                         else:
249                                 self.m_geo_lmargin =  value
250                                 self.m_geo_rmargin =  value
251                 elif name == 'total':
252                         if type(value) == type([]):
253                                 self.m_geo_width =  value[0]
254                         else:
255                                 self.m_geo_width =  value
256                 elif name == 'width' or name == 'totalwidth':
257                         self.m_geo_width =  value
258                 elif name == 'paper' or name == 'papername':
259                         self.m_papersize = value
260                 elif name[-5:] == 'paper':
261                         self.m_papersize = name
262                 else:
263                         pass 
264
265         def __setattr__(self, name, value):
266                 if type(value) == type("") and \
267                    dimension_conversion_dict.has_key (value[-2:]):
268                         f = dimension_conversion_dict[value[-2:]]
269                         self.__dict__[name] = f(float(value[:-2]))
270                 else:
271                         self.__dict__[name] = value
272                         
273         def __str__(self):
274                 s =  "LatexPaper:\n-----------"
275                 for v in self.__dict__.keys():
276                         if v[:2] == 'm_':
277                                 s = s +  str (v) + ' ' + str (self.__dict__[v])
278                 s = s +  "-----------"
279                 return s
280         
281         def get_linewidth(self):
282                 w = self._calc_linewidth()
283                 if self.m_num_cols == 2:
284                         return (w - 10) / 2
285                 else:
286                         return w
287         def get_paperwidth(self):
288                 #if self.m_use_geometry:
289                         return self.m_paperdef[self.m_papersize][self.m_landscape or self.m_geo_landscape]
290                 #return self.m_paperdef[self.m_papersize][self.m_landscape]
291         
292         def _calc_linewidth(self):
293                 # since geometry sometimes ignores 'includemp', this is
294                 # more complicated than it should be
295                 mp = 0
296                 if self.m_geo_includemp:
297                         if self.m_geo_x_marginparsep is not None:
298                                 mp = mp + self.m_geo_x_marginparsep
299                         else:
300                                 mp = mp + self.m_geo_marginparsep[self.m_fontsize]
301                         if self.m_geo_x_marginparwidth is not None:
302                                 mp = mp + self.m_geo_x_marginparwidth
303                         else:
304                                 mp = mp + self.m_geo_marginparwidth[self.m_fontsize]
305
306                 #ugh test if this is necessary                          
307                 if self.__body:
308                         mp = 0
309
310                 if not self.m_use_geometry:
311                         return latex_linewidths[self.m_papersize][self.m_fontsize]
312                 else:
313                         geo_opts = (self.m_geo_lmargin == None,
314                                     self.m_geo_width == None,
315                                     self.m_geo_rmargin == None)
316
317                         if geo_opts == (1, 1, 1):
318                                 if self.m_geo_textwidth:
319                                         return self.m_geo_textwidth
320                                 w = self.get_paperwidth() * 0.8
321                                 return w - mp
322                         elif geo_opts == (0, 1, 1):
323                                  if self.m_geo_textwidth:
324                                         return self.m_geo_textwidth
325                                  return self.f1(self.m_geo_lmargin, mp)
326                         elif geo_opts == (1, 1, 0):
327                                  if self.m_geo_textwidth:
328                                         return self.m_geo_textwidth
329                                  return self.f1(self.m_geo_rmargin, mp)
330                         elif geo_opts \
331                                         in ((0, 0, 1), (1, 0, 0), (1, 0, 1)):
332                                 if self.m_geo_textwidth:
333                                         return self.m_geo_textwidth
334                                 return self.m_geo_width - mp
335                         elif geo_opts in ((0, 1, 0), (0, 0, 0)):
336                                 w = self.get_paperwidth() \
337                                   - self.m_geo_lmargin - self.m_geo_rmargin - mp
338                                 if w < 0:
339                                         w = 0
340                                 return w
341                         raise "Never do this!"
342         def f1(self, m, mp):
343                 tmp = self.get_paperwidth() - m * 2 - mp
344                 if tmp < 0:
345                         tmp = 0
346                 return tmp
347         def f2(self):
348                 tmp = self.get_paperwidth() - self.m_geo_lmargin \
349                         - self.m_geo_rmargin
350                 if tmp < 0:
351                         return 0
352                 return tmp
353
354 class HtmlPaper:
355         def __init__(self):
356                 self.m_papersize = 'letterpaper'
357                 self.m_fontsize = 12
358         def get_linewidth(self):
359                 return html_linewidths[self.m_papersize][self.m_fontsize]
360
361 class TexiPaper:
362         def __init__(self):
363                 self.m_papersize = 'letterpaper'
364                 self.m_fontsize = 12
365         def get_linewidth(self):
366                 return texi_linewidths[self.m_papersize][self.m_fontsize]
367
368 def mm2pt(x):
369         return x * 2.8452756
370 def in2pt(x):
371         return x * 72.26999
372 def em2pt(x, fontsize = 10):
373         return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
374 def ex2pt(x, fontsize = 10):
375         return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
376
377 def pt2pt(x):
378         return x
379
380 dimension_conversion_dict ={
381         'mm': mm2pt,
382         'cm': lambda x: mm2pt(10*x),
383         'in': in2pt,
384         'em': em2pt,
385         'ex': ex2pt,
386         'pt': pt2pt
387         }
388
389 # Convert numeric values, with or without specific dimension, to floats.
390 # Keep other strings
391 def conv_dimen_to_float(value):
392         if type(value) == type(""):
393                 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
394                 if m:
395                         unit = m.group (2)
396                         num = string.atof(m.group (1))
397                         conv =  dimension_conversion_dict[m.group(2)]
398                         
399                         value = conv(num)
400                 
401                 elif re.match ("^[0-9.]+$",value):
402                         value = float(value)
403
404         return value
405                         
406         
407 # latex linewidths:
408 # indices are no. of columns, papersize,  fontsize
409 # Why can't this be calculated?
410 latex_linewidths = {
411         'a4paper':{10: 345, 11: 360, 12: 390},
412         'a4paper-landscape': {10: 598, 11: 596, 12:592},
413         'a5paper':{10: 276, 11: 276, 12: 276},
414         'b5paper':{10: 345, 11: 356, 12: 356},
415         'letterpaper':{10: 345, 11: 360, 12: 390},
416         'letterpaper-landscape':{10: 598, 11: 596, 12:596},
417         'legalpaper': {10: 345, 11: 360, 12: 390},
418         'executivepaper':{10: 345, 11: 360, 12: 379}}
419
420 texi_linewidths = {
421         'afourpaper': {12: mm2pt(160)},
422         'afourwide': {12: in2pt(6.5)},
423         'afourlatex': {12: mm2pt(150)},
424         'smallbook': {12: in2pt(5)},
425         'letterpaper': {12: in2pt(6)}}
426
427 html_linewidths = {
428         'afourpaper': {12: mm2pt(160)},
429         'afourwide': {12: in2pt(6.5)},
430         'afourlatex': {12: mm2pt(150)},
431         'smallbook': {12: in2pt(5)},
432         'letterpaper': {12: in2pt(6)}}
433
434 option_definitions = [
435         ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
436         ('DIM',  '', 'default-music-fontsize', 'default fontsize for music.  DIM is assumed to be in points'),
437         ('DIM',  '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
438         ('OPT', '', 'extra-options' , 'Pass OPT quoted to the lilypond command line'),
439         ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
440         ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
441         ('', 'h', 'help', 'this help'),
442         ('DIR', 'I', 'include', 'include path'),
443         ('', 'M', 'dependencies', 'write dependencies'),
444         ('PREF', '',  'dep-prefix', 'prepend PREF before each -M dependency'),
445         ('', 'n', 'no-lily', 'don\'t run lilypond'),
446         ('', '', 'no-pictures', "don\'t generate pictures"),
447         ('', '', 'no-music', "strip all lilypond blocks from output"),  
448         ('', '', 'read-lys', "don't write ly files."),
449         ('FILE', 'o', 'outname', 'filename main output file'),
450         ('FILE', '', 'outdir', "where to place generated files"),
451         ('', 'V', 'verbose', 'verbose' ),
452         ('', 'v', 'version', 'print version information' ),
453         ]
454
455 # format specific strings, ie. regex-es for input, and % strings for output
456 output_dict= {
457         'html' : {'output-lilypond': '''<lilypond%s>
458 %s
459 </lilypond>''',
460                 'output-filename' : r'''
461
462 <pre>%s</pre>:''',        
463                   'output-lilypond-fragment': '''<lilypond%s>
464 \context Staff\context Voice{ %s }
465 </lilypond>''',
466                   'output-noinline': r'''
467 <!-- generated: %(fn)s.png !-->
468 ''',
469                   ## maybe <hr> ?
470                   'pagebreak': None,
471                   'output-verbatim': r'''<pre>
472 %s
473 </pre>''',
474                   ## Ugh we need to differentiate on origin:
475                   ## lilypond-block origin wants an extra <p>, but
476                   ## inline music doesn't.
477                   ## possibly other center options?
478                   'output-all': r'''
479 <a href="%(fn)s.png">
480 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
481 ''',
482                   },
483         'latex': {
484                 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
485   \context Staff <
486     \context Voice{
487       %s
488     }
489   >
490 \end{lilypond}''',
491                 'output-filename' : r'''
492
493 \verb+%s+:''',
494                 'output-lilypond': r'''\begin[%s]{lilypond}
495 %s
496 \end{lilypond}
497 ''',
498                 'output-verbatim': r'''\begin{verbatim}%s\end{verbatim}%%
499 ''',
500                 'output-default-post': "\\def\postLilypondExample{}\n",
501                 'output-default-pre': "\\def\preLilypondExample{}\n",
502                 'usepackage-graphics': '\\usepackage{graphics}\n',
503                 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s.eps}}',
504                 'output-noinline': r'''
505 %% generated: %(fn)s.eps
506 ''',
507                 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
508                 'pagebreak': r'\pagebreak',
509                 },
510         
511         'texi' : {'output-lilypond': '''@lilypond[%s]
512 %s
513 @end lilypond 
514 ''',
515                 'output-filename' : r'''
516
517 @file{%s}:''',    
518                   'output-lilypond-fragment': '''@lilypond[%s]
519 \context Staff\context Voice{ %s }
520 @end lilypond ''',
521                   'output-noinline': r'''
522 @c generated: %(fn)s.png                  
523 ''',
524                   'pagebreak': None,
525                   'output-verbatim': r'''@example
526 %s
527 @end example
528 ''',
529
530 # do some tweaking: @ is needed in some ps stuff.
531 # override EndLilyPondOutput, since @tex is done
532 # in a sandbox, you can't do \input lilyponddefs at the
533 # top of the document.
534
535 # should also support fragment in
536
537 # ugh, the <p> below breaks inline images...
538                   
539                   'output-all': r'''
540 @tex
541 \catcode`\@=12
542 \input lilyponddefs
543 \def\EndLilyPondOutput{}
544 \input %(fn)s.tex
545 \catcode`\@=0
546 @end tex
547 @html
548 <p>
549 <a href="%(fn)s.png">
550 <img border=0 src="%(fn)s.png" alt="[picture of music]">
551 </a>
552 @end html
553 ''',
554                 }
555         
556         }
557
558 def output_verbatim (body):
559         if __main__.format == 'html':
560                 body = re.sub ('&', '&amp;', body)
561                 body = re.sub ('>', '&gt;', body)
562                 body = re.sub ('<', '&lt;', body)
563         elif __main__.format == 'texi':
564                 body = re.sub ('([@{}])', '@\\1', body)
565         return get_output ('output-verbatim') % body
566
567
568 #warning: this uses extended regular expressions. Tread with care.
569
570 # legenda
571
572 # (?P  -- name parameter
573 # *? -- match non-greedily.
574 # (?m)  -- ?  
575 re_dict = {
576         'html': {
577                  'include':  no_match,
578                  'input': no_match,
579                  'header': no_match,
580                  'preamble-end': no_match,
581                  'landscape': no_match,
582                  'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
583                  'verb': r'''(?P<code><pre>.*?</pre>)''',
584                  'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
585                  'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
586                  'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
587                   'option-sep' : '\s*',
588                   'intertext': r',?\s*intertext=\".*?\"',
589                   'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
590                   'singleline-comment': no_match,
591                   'numcols': no_match,
592                  },
593         
594         'latex': {'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
595                   'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
596                   'option-sep' : ',\s*',
597                   'header': r"\\documentclass\s*(\[.*?\])?",
598                   'geometry': r"^(?m)[^%\n]*?\\usepackage\s*(\[(?P<options>.*)\])?\s*{geometry}",
599                   'preamble-end': r'(?P<code>\\begin{document})',
600                   'verbatim': r"(?s)(?P<code>\\begin{verbatim}.*?\\end{verbatim})",
601                   'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
602                   'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
603                   'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
604                   'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
605                   'def-post-re': r"\\def\\postLilypondExample",
606                   'def-pre-re': r"\\def\\preLilypondExample",             
607                   'usepackage-graphics': r"\usepackage{graphics}",
608                   'intertext': r',?\s*intertext=\".*?\"',
609                   'multiline-comment': no_match,
610                   'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
611                   'numcols': r"(?P<code>\\(?P<num>one|two)column)",
612                   },
613
614
615         # why do we have distinction between @mbinclude and @include?
616
617         
618         'texi': {
619                  'include':  '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
620                  'input': no_match,
621                  'header': no_match,
622                  'preamble-end': no_match,
623                  'landscape': no_match,
624                  'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
625                  'verb': r'''(?P<code>@code{.*?})''',
626                  'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
627                  'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
628                  'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
629                  'option-sep' : ',\s*',
630                  'intertext': r',?\s*intertext=\".*?\"',
631                  'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
632                  'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
633                  'numcols': no_match,
634                  }
635         }
636
637
638 for r in re_dict.keys ():
639         olddict = re_dict[r]
640         newdict = {}
641         for k in olddict.keys ():
642                 try:
643                         newdict[k] = re.compile (olddict[k])
644                 except:
645                         print 'invalid regexp: %s' % olddict[k]
646
647                         # we'd like to catch and reraise a more detailed  error, but
648                         # alas, the exceptions changed across the 1.5/2.1 boundary.
649                         raise "Invalid re"
650         re_dict[r] = newdict
651
652         
653 def uniq (list):
654         list.sort ()
655         s = list
656         list = []
657         for x in s:
658                 if x not in list:
659                         list.append (x)
660         return list
661                 
662
663 def get_output (name):
664         return  output_dict[format][name]
665
666 def get_re (name):
667         return  re_dict[format][name]
668
669 def bounding_box_dimensions(fname):
670         if g_outdir:
671                 fname = os.path.join(g_outdir, fname)
672         try:
673                 fd = open(fname)
674         except IOError:
675                 error ("Error opening `%s'" % fname)
676         str = fd.read ()
677         s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
678         if s:
679                 
680                 gs = map (lambda x: string.atoi (x), s.groups ())
681                 return (int (gs[2] - gs[0] + 0.5),
682                         int (gs[3] - gs[1] + 0.5))
683         else:
684                 return (0,0)
685
686 def error (str):
687         sys.stderr.write (str + "\n  Exiting ... \n\n")
688         raise 'Exiting.'
689
690
691 def compose_full_body (body, opts):
692         '''Construct the lilypond code to send to Lilypond.
693         Add stuff to BODY using OPTS as options.'''
694         music_size = default_music_fontsize
695         latex_size = default_text_fontsize
696         indent = ''
697         linewidth = ''
698         for o in opts:
699                 if g_force_lilypond_fontsize:
700                         music_size = g_force_lilypond_fontsize
701                 else:
702                         m = re.match ('([0-9]+)pt', o)
703                         if m:
704                                 music_size = string.atoi(m.group (1))
705
706                 m = re.match ('latexfontsize=([0-9]+)pt', o)
707                 if m:
708                         latex_size = string.atoi (m.group (1))
709                         
710                 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
711                 if m:
712                         f = float (m.group (1))
713                         indent = 'indent = %f\\%s' % (f, m.group (2))
714                         
715                 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
716                 if m:
717                         f = float (m.group (1))
718                         linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
719
720         if re.search ('\\\\score', body):
721                 is_fragment = 0
722         else:
723                 is_fragment = 1
724         if 'fragment' in opts:
725                 is_fragment = 1
726         if 'nofragment' in opts:
727                 is_fragment = 0
728
729         if is_fragment and not 'multiline' in opts:
730                 opts.append('singleline')
731                 
732         if 'singleline' in opts:
733                 linewidth = 'linewidth = -1.0'
734         elif not linewidth:
735                 l = __main__.paperguru.get_linewidth ()
736                 linewidth = 'linewidth = %f\pt' % l
737
738         if 'noindent' in opts:
739                 indent = 'indent = 0.0\mm'
740
741         for o in opts:
742                 m= re.search ('relative(.*)', o)
743                 v = 0
744                 if m:
745                         try:
746                                 v = string.atoi (m.group (1))
747                         except ValueError:
748                                 pass
749
750                         v = v + 1
751                         pitch = 'c'
752                         if v < 0:
753                                 pitch = pitch + '\,' * v
754                         elif v > 0:
755                                 pitch = pitch + '\'' * v
756
757                         body = '\\relative %s { %s }' %(pitch, body)
758         
759         if is_fragment:
760                 body = r'''\score { 
761  \notes { %s }
762   \paper { }  
763 }''' % body
764
765         opts = uniq (opts)
766         optstring = string.join (opts, ' ')
767         optstring = re.sub ('\n', ' ', optstring)
768         body = r'''
769 %% Generated automatically by: lilypond-book.py
770 %% options are %s  
771 \include "paper%d.ly"
772 \paper  {
773   %s
774   %s
775
776 ''' % (optstring, music_size, linewidth, indent) + body
777
778         # ughUGH not original options
779         return body
780
781 def parse_options_string(s):
782         d = {}
783         r1 = re.compile("((\w+)={(.*?)})((,\s*)|$)")
784         r2 = re.compile("((\w+)=(.*?))((,\s*)|$)")
785         r3 = re.compile("(\w+?)((,\s*)|$)")
786         while s:
787                 m = r1.match(s)
788                 if m:
789                         s = s[m.end():]
790                         d[m.group(2)] = re.split(",\s*", m.group(3))
791                         continue
792                 m = r2.match(s)
793                 if m:
794                         s = s[m.end():]
795                         d[m.group(2)] = m.group(3)
796                         continue
797                 m = r3.match(s)
798                 if m:
799                         s = s[m.end():]
800                         d[m.group(1)] = 1
801                         continue
802                 
803                 error ("format of option string invalid (was `%')" % s)
804         return d
805
806 def scan_html_preamble (chunks):
807         return
808
809 def scan_latex_preamble(chunks):
810         # first we want to scan the \documentclass line
811         # it should be the first non-comment line
812         idx = 0
813         while 1:
814                 if chunks[idx][0] == 'ignore':
815                         idx = idx + 1
816                         continue
817                 m = get_re ('header').match(chunks[idx][1])
818                 if m <> None and m.group (1):
819                         options = re.split (',[\n \t]*', m.group(1)[1:-1])
820                 else:
821                         options = []
822                 for o in options:
823                         if o == 'landscape':
824                                 paperguru.m_landscape = 1
825                         m = re.match("(.*?)paper", o)
826                         if m:
827                                 paperguru.m_papersize = m.group()
828                         else:
829                                 m = re.match("(\d\d)pt", o)
830                                 if m:
831                                         paperguru.m_fontsize = int(m.group(1))
832                 break
833         
834         while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
835                 if chunks[idx] == 'ignore':
836                         idx = idx + 1
837                         continue
838                 m = get_re ('geometry').search(chunks[idx][1])
839                 if m:
840                         paperguru.m_use_geometry = 1
841                         o = parse_options_string(m.group('options'))
842                         for k in o.keys():
843                                 paperguru.set_geo_option(k, o[k])
844                 idx = idx + 1
845
846 def scan_texi_preamble (chunks):
847         # this is not bulletproof..., it checks the first 10 chunks
848         for c in chunks[:10]: 
849                 if c[0] == 'input':
850                         for s in ('afourpaper', 'afourwide', 'letterpaper',
851                                   'afourlatex', 'smallbook'):
852                                 if string.find(c[1], "@%s" % s) != -1:
853                                         paperguru.m_papersize = s
854
855
856 def scan_preamble (chunks):
857         if __main__.format == 'html':
858                 scan_html_preamble (chunks)
859         elif __main__.format == 'latex':
860                 scan_latex_preamble (chunks)
861         elif __main__.format == 'texi':
862                 scan_texi_preamble (chunks)
863                 
864
865 def completize_preamble (chunks):
866         if __main__.format != 'latex':
867                 return chunks
868         pre_b = post_b = graphics_b = None
869         for chunk in chunks:
870                 if chunk[0] == 'preamble-end':
871                         break
872                 if chunk[0] == 'input':
873                         m = get_re('def-pre-re').search(chunk[1])
874                         if m:
875                                 pre_b = 1
876                 if chunk[0] == 'input':
877                         m = get_re('def-post-re').search(chunk[1])
878                         if m:
879                                 post_b = 1
880                                 
881                 if chunk[0] == 'input':
882                         m = get_re('usepackage-graphics').search(chunk[1])
883                         if m:
884                                 graphics_b = 1
885         x = 0
886         while x < len (chunks) and   chunks[x][0] != 'preamble-end':
887                 x = x + 1
888
889         if x == len(chunks):
890                 return chunks
891         
892         if not pre_b:
893                 chunks.insert(x, ('input', get_output ('output-default-pre')))
894         if not post_b:
895                 chunks.insert(x, ('input', get_output ('output-default-post')))
896         if not graphics_b:
897                 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
898
899         return chunks
900
901
902 read_files = []
903 def find_file (name):
904         '''
905         Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
906         '''
907
908         if name == '-':
909                 return (sys.stdin.read (), '<stdin>')
910         f = None
911         nm = ''
912         for a in include_path:
913                 try:
914                         nm = os.path.join (a, name)
915                         f = open (nm)
916                         __main__.read_files.append (nm)
917                         break
918                 except IOError:
919                         pass
920         if f:
921                 sys.stderr.write ("Reading `%s'\n" % nm)
922                 return (f.read (), nm)
923         else:
924                 error ("File not found `%s'\n" % name)
925                 return ('', '')
926
927 def do_ignore(match_object):
928         return [('ignore', match_object.group('code'))]
929 def do_preamble_end(match_object):
930         return [('preamble-end', match_object.group('code'))]
931
932 def make_verbatim(match_object):
933         return [('verbatim', match_object.group('code'))]
934
935 def make_verb(match_object):
936         return [('verb', match_object.group('code'))]
937
938 def do_include_file(m):
939         "m: MatchObject"
940         return [('input', get_output ('pagebreak'))] \
941              + read_doc_file(m.group('filename')) \
942              + [('input', get_output ('pagebreak'))] 
943
944 def do_input_file(m):
945         return read_doc_file(m.group('filename'))
946
947 def make_lilypond(m):
948         if m.group('options'):
949                 options = m.group('options')
950         else:
951                 options = ''
952         return [('input', get_output('output-lilypond-fragment') % 
953                         (options, m.group('code')))]
954
955 def make_lilypond_file(m):
956         '''
957
958         Find @lilypondfile{bla.ly} occurences and substitute bla.ly
959         into a @lilypond .. @end lilypond block.
960         
961         '''
962         
963         if m.group('options'):
964                 options = m.group('options')
965         else:
966                 options = ''
967         (content, nm) = find_file(m.group('filename'))
968         options = "filename=%s," % nm + options
969
970         return [('input', get_output('output-lilypond') %
971                         (options, content))]
972
973 def make_lilypond_block(m):
974         if not g_do_music:
975                 return []
976         
977         if m.group('options'):
978                 options = get_re('option-sep').split (m.group('options'))
979         else:
980             options = []
981         options = filter(lambda s: s != '', options)
982         return [('lilypond', m.group('code'), options)]
983
984 def do_columns(m):
985         if __main__.format != 'latex':
986                 return []
987         if m.group('num') == 'one':
988                 return [('numcols', m.group('code'), 1)]
989         if m.group('num') == 'two':
990                 return [('numcols', m.group('code'), 2)]
991         
992 def chop_chunks(chunks, re_name, func, use_match=0):
993         newchunks = []
994         for c in chunks:
995                 if c[0] == 'input':
996                         str = c[1]
997                         while str:
998                                 m = get_re (re_name).search (str)
999                                 if m == None:
1000                                         newchunks.append (('input', str))
1001                                         str = ''
1002                                 else:
1003                                         if use_match:
1004                                                 newchunks.append (('input', str[:m.start ('match')]))
1005                                         else:
1006                                                 newchunks.append (('input', str[:m.start (0)]))
1007                                         #newchunks.extend(func(m))
1008                                         # python 1.5 compatible:
1009                                         newchunks = newchunks + func(m)
1010                                         str = str [m.end(0):]
1011                 else:
1012                         newchunks.append(c)
1013         return newchunks
1014
1015 def determine_format (str):
1016         if __main__.format == '':
1017                 
1018                 html = re.search ('(?i)<[dh]tml', str[:200])
1019                 latex = re.search (r'''\\document''', str[:200])
1020                 texi = re.search ('@node|@setfilename', str[:200])
1021
1022                 f = ''
1023                 g = None
1024                 
1025                 if html and not latex and not texi:
1026                         f = 'html'
1027                 elif latex and not html and not texi:
1028                         f = 'latex'
1029                 elif texi and not html and not latex:
1030                         f = 'texi'
1031                 else:
1032                         error ("can't determine format, please specify")
1033                 __main__.format = f
1034
1035         if __main__.paperguru == None:
1036                 if __main__.format == 'html':
1037                         g = HtmlPaper ()
1038                 elif __main__.format == 'latex':
1039                         g = LatexPaper ()
1040                 elif __main__.format == 'texi':
1041                         g = TexiPaper ()
1042                         
1043                 __main__.paperguru = g
1044
1045
1046 def read_doc_file (filename):
1047         '''Read the input file, find verbatim chunks and do \input and \include
1048         '''
1049         (str, path) = find_file(filename)
1050         determine_format (str)
1051         
1052         chunks = [('input', str)]
1053         
1054         # we have to check for verbatim before doing include,
1055         # because we don't want to include files that are mentioned
1056         # inside a verbatim environment
1057         chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
1058         chunks = chop_chunks(chunks, 'verb', make_verb)
1059         chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
1060         #ugh fix input
1061         chunks = chop_chunks(chunks, 'include', do_include_file, 1)
1062         chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1063         return chunks
1064
1065
1066 taken_file_names = {}
1067 def schedule_lilypond_block (chunk):
1068         '''Take the body and options from CHUNK, figure out how the
1069         real .ly should look, and what should be left MAIN_STR (meant
1070         for the main file).  The .ly is written, and scheduled in
1071         TODO.
1072
1073         Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1074
1075         TODO has format [basename, extension, extension, ... ]
1076         
1077         '''
1078         (type, body, opts) = chunk
1079         assert type == 'lilypond'
1080         file_body = compose_full_body (body, opts)
1081         ## Hmm, we should hash only lilypond source, and skip the
1082         ## %options are ...
1083         ## comment line
1084         basename = 'lily-' + `abs(hash (file_body))`
1085         for o in opts:
1086                 m = re.search ('filename="(.*?)"', o)
1087                 if m:
1088                         basename = m.group (1)
1089                         if not taken_file_names.has_key(basename):
1090                                 taken_file_names[basename] = 0
1091                         else:
1092                                 taken_file_names[basename] = taken_file_names[basename] + 1
1093                                 basename = basename + "-%i" % taken_file_names[basename]
1094         if not g_read_lys:
1095                 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1096         needed_filetypes = ['tex']
1097
1098         if format == 'html' or format == 'texi':
1099                 needed_filetypes.append ('eps')
1100                 needed_filetypes.append ('png')
1101         if 'eps' in opts and not ('eps' in needed_filetypes):
1102                 needed_filetypes.append('eps')
1103         pathbase = os.path.join (g_outdir, basename)
1104         def f (base, ext1, ext2):
1105                 a = os.path.isfile(base + ext2)
1106                 if (os.path.isfile(base + ext1) and
1107                     os.path.isfile(base + ext2) and
1108                                 os.stat(base+ext1)[stat.ST_MTIME] >
1109                                 os.stat(base+ext2)[stat.ST_MTIME]) or \
1110                                 not os.path.isfile(base + ext2):
1111                         return 1
1112         todo = []
1113         if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1114                 todo.append('tex')
1115         if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1116                 todo.append('eps')
1117         if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1118                 todo.append('png')
1119         newbody = ''
1120
1121         if 'printfilename' in opts:
1122                 for o in opts:
1123                         m= re.match ("filename=(.*)", o)
1124                         if m:
1125                                 newbody = newbody + get_output ("output-filename") % m.group(1)
1126                                 break
1127                 
1128         
1129         if 'verbatim' in opts:
1130                 newbody = output_verbatim (body)
1131
1132         for o in opts:
1133                 m = re.search ('intertext="(.*?)"', o)
1134                 if m:
1135                         newbody = newbody  + m.group (1) + "\n\n"
1136         
1137         if 'noinline' in opts:
1138                 s = 'output-noinline'
1139         elif format == 'latex':
1140                 if 'eps' in opts:
1141                         s = 'output-eps'
1142                 else:
1143                         s = 'output-tex'
1144         else: # format == 'html' or format == 'texi':
1145                 s = 'output-all'
1146         newbody = newbody + get_output (s) % {'fn': basename }
1147         return ('lilypond', newbody, opts, todo, basename)
1148
1149 def process_lilypond_blocks(chunks):#ugh rename
1150         newchunks = []
1151         # Count sections/chapters.
1152         for c in chunks:
1153                 if c[0] == 'lilypond':
1154                         c = schedule_lilypond_block (c)
1155                 elif c[0] == 'numcols':
1156                         paperguru.m_num_cols = c[2]
1157                 newchunks.append (c)
1158         return newchunks
1159
1160
1161
1162 def system (cmd):
1163         sys.stderr.write ("invoking `%s'\n" % cmd)
1164         st = os.system (cmd)
1165         if st:
1166                 error ('Error command exited with value %d\n' % st)
1167         return st
1168
1169 def quiet_system (cmd, name):
1170         if not verbose_p:
1171                 progress ( _("Running %s...") % name)
1172                 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1173
1174         return system (cmd)
1175
1176 def get_bbox (filename):
1177         system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
1178
1179         box = open (filename + '.bbox').read()
1180         m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1181         gr = []
1182         if m:
1183                 gr = map (string.atoi, m.groups ())
1184         
1185         return gr
1186
1187 def make_pixmap (name):
1188         bbox = get_bbox (name + '.eps')
1189         margin = 0
1190         fo = open (name + '.trans.eps' , 'w')
1191         fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1192         fo.close ()
1193         
1194         res = 90
1195
1196         x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1197         y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1198
1199         cmd = r'''gs -g%dx%d -sDEVICE=pnggray  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit  > %s'''
1200         
1201         cmd = cmd % (x, y, res, name + '.trans.eps', name + '.eps',name + '.png')
1202         quiet_system (cmd, 'gs')
1203
1204         try:
1205                 status = system (cmd)
1206         except:
1207                 os.unlink (name + '.png')
1208                 error ("Removing output file")
1209
1210 def compile_all_files (chunks):
1211         global foutn
1212         eps = []
1213         tex = []
1214         png = []
1215
1216         for c in chunks:
1217                 if c[0] <> 'lilypond':
1218                         continue
1219                 base  = c[4]
1220                 exts = c[3]
1221                 for e in exts:
1222                         if e == 'eps':
1223                                 eps.append (base)
1224                         elif e == 'tex':
1225                                 #ugh
1226                                 if base + '.ly' not in tex:
1227                                         tex.append (base + '.ly')
1228                         elif e == 'png' and g_do_pictures:
1229                                 png.append (base)
1230         d = os.getcwd()
1231         if g_outdir:
1232                 os.chdir(g_outdir)
1233         if tex:
1234                 # fixme: be sys-independent.
1235                 def incl_opt (x):
1236                         if g_outdir and x[0] <> '/' :
1237                                 x = os.path.join (g_here_dir, x)
1238                         return ' -I %s' % x
1239
1240                 incs = map (incl_opt, include_path)
1241                 lilyopts = string.join (incs, ' ' )
1242                 if do_deps:
1243                         lilyopts = lilyopts + ' --dependencies '
1244                         if g_outdir:
1245                                 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1246                 texfiles = string.join (tex, ' ')
1247                 cmd = 'lilypond --header=texidoc %s %s %s' \
1248                       % (lilyopts, g_extra_opts, texfiles)
1249                 system (cmd)
1250
1251                 #
1252                 # Ugh, fixing up dependencies for .tex generation
1253                 #
1254                 if do_deps:
1255                         depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1256                         for i in depfiles:
1257                                 f =open (i)
1258                                 text=f.read ()
1259                                 f.close ()
1260                                 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1261                                 f = open (i, 'w')
1262                                 f.write (text)
1263                                 f.close ()
1264
1265         for e in eps:
1266                 cmd = r"tex '\nonstopmode \input %s'" % e
1267                 quiet_system (cmd, 'TeX')
1268                 
1269                 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1270                 quiet_system (cmd, 'dvips')
1271                 
1272         for g in png:
1273                 make_pixmap (g)
1274                 
1275         os.chdir (d)
1276
1277
1278 def update_file (body, name):
1279         '''
1280         write the body if it has changed
1281         '''
1282         same = 0
1283         try:
1284                 f = open (name)
1285                 fs = f.read (-1)
1286                 same = (fs == body)
1287         except:
1288                 pass
1289
1290         if not same:
1291                 f = open (name , 'w')
1292                 f.write (body)
1293                 f.close ()
1294         
1295         return not same
1296
1297
1298 def getopt_args (opts):
1299         "Construct arguments (LONG, SHORT) for getopt from  list of options."
1300         short = ''
1301         long = []
1302         for o in opts:
1303                 if o[1]:
1304                         short = short + o[1]
1305                         if o[0]:
1306                                 short = short + ':'
1307                 if o[2]:
1308                         l = o[2]
1309                         if o[0]:
1310                                 l = l + '='
1311                         long.append (l)
1312         return (short, long)
1313
1314 def option_help_str (o):
1315         "Transform one option description (4-tuple ) into neatly formatted string"
1316         sh = '  '       
1317         if o[1]:
1318                 sh = '-%s' % o[1]
1319
1320         sep = ' '
1321         if o[1] and o[2]:
1322                 sep = ','
1323                 
1324         long = ''
1325         if o[2]:
1326                 long= '--%s' % o[2]
1327
1328         arg = ''
1329         if o[0]:
1330                 if o[2]:
1331                         arg = '='
1332                 arg = arg + o[0]
1333         return '  ' + sh + sep + long + arg
1334
1335
1336 def options_help_str (opts):
1337         "Convert a list of options into a neatly formatted string"
1338         w = 0
1339         strs =[]
1340         helps = []
1341
1342         for o in opts:
1343                 s = option_help_str (o)
1344                 strs.append ((s, o[3]))
1345                 if len (s) > w:
1346                         w = len (s)
1347
1348         str = ''
1349         for s in strs:
1350                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
1351         return str
1352
1353 def help():
1354         sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1355 Generate hybrid LaTeX input from Latex + lilypond
1356 Options:
1357 ''')
1358         sys.stdout.write (options_help_str (option_definitions))
1359         sys.stdout.write (r'''Warning all output is written in the CURRENT directory
1360
1361
1362
1363 Report bugs to bug-lilypond@gnu.org.
1364
1365 Written by Tom Cato Amundsen <tca@gnu.org> and
1366 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1367 ''')
1368
1369         sys.exit (0)
1370
1371
1372 def write_deps (fn, target, chunks):
1373         global read_files
1374         sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1375         f = open (os.path.join(g_outdir, fn), 'w')
1376         f.write ('%s%s: ' % (g_dep_prefix, target))
1377         for d in read_files:
1378                 f.write ('%s ' %  d)
1379         basenames=[]
1380         for c in chunks:
1381                 if c[0] == 'lilypond':
1382                         (type, body, opts, todo, basename) = c;
1383                         basenames.append (basename)
1384         for d in basenames:
1385                 if g_outdir:
1386                         d=g_outdir + '/' + d
1387                 if g_dep_prefix:
1388                         #if not os.isfile (d): # thinko?
1389                         if not re.search ('/', d):
1390                                 d = g_dep_prefix + d
1391                 f.write ('%s.tex ' %  d)
1392         f.write ('\n')
1393         #if len (basenames):
1394         #       for d in basenames:
1395         #               f.write ('%s.ly ' %  d)
1396         #       f.write (' : %s' % target)
1397         f.write ('\n')
1398         f.close ()
1399         read_files = []
1400
1401 def identify (stream):
1402         stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1403
1404 def print_version ():
1405         identify (sys.stdout)
1406         sys.stdout.write (r'''Copyright 1998--1999
1407 Distributed under terms of the GNU General Public License. It comes with
1408 NO WARRANTY.
1409 ''')
1410
1411
1412 def check_texidoc (chunks):
1413         n = []
1414         for c in chunks:
1415                 if c[0] == 'lilypond':
1416                         (type, body, opts, todo, basename) = c;
1417                         pathbase = os.path.join (g_outdir, basename)
1418                         if os.path.isfile (pathbase + '.texidoc'):
1419                                 body = '\n@include %s.texidoc\n' % basename + body
1420                                 c = (type, body, opts, todo, basename)
1421                 n.append (c)
1422         return n
1423
1424
1425 ## what's this? Docme --hwn
1426 ##
1427 def fix_epswidth (chunks):
1428         newchunks = []
1429         for c in chunks:
1430                 if c[0] <> 'lilypond' or 'eps' not in c[2]:
1431                         newchunks.append (c)
1432                         continue
1433
1434                 mag = 1.0
1435                 for o in c[2]:
1436                         m  = re.match ('magnification=([0-9.]+)', o)
1437                         if m:
1438                                 mag = string.atof (m.group (1))
1439
1440                 def replace_eps_dim (match, lmag = mag):
1441                         filename = match.group (1)
1442                         dims = bounding_box_dimensions (filename)
1443
1444                         return '%fpt' % (dims[0] *lmag)
1445         
1446                 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1447                 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1448                         
1449         return newchunks
1450
1451
1452 ##docme: why global?
1453 foutn=""
1454 def do_file(input_filename):
1455         global foutn
1456         file_settings = {}
1457         if outname:
1458                 my_outname = outname
1459         elif input_filename == '-' or input_filename == "/dev/stdin":
1460                 my_outname = '-'
1461         else:
1462                 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1463         my_depname = my_outname + '.dep'                
1464
1465         chunks = read_doc_file(input_filename)
1466         chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1467         chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1468         chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1469         chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1470         chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1471         chunks = chop_chunks(chunks, 'numcols', do_columns)
1472         #print "-" * 50
1473         #for c in chunks: print "c:", c;
1474         #sys.exit()
1475         scan_preamble(chunks)
1476         chunks = process_lilypond_blocks(chunks)
1477
1478         # Do It.
1479         if __main__.g_run_lilypond:
1480                 compile_all_files (chunks)
1481                 chunks = fix_epswidth (chunks)
1482
1483         if __main__.format == 'texi':
1484                 chunks = check_texidoc (chunks)
1485
1486         x = 0
1487         chunks = completize_preamble (chunks)
1488         if my_outname == '-' or my_outname == '/dev/stdout':
1489                 fout = sys.stdout
1490                 foutn = "<stdout>"
1491                 __main__.do_deps = 0
1492         else:
1493                 foutn = os.path.join (g_outdir, my_outname)
1494                 sys.stderr.write ("Writing `%s'\n" % foutn)
1495                 fout = open (foutn, 'w')
1496         for c in chunks:
1497                 fout.write (c[1])
1498         fout.close ()
1499         # should chmod -w
1500
1501         if do_deps:
1502                 write_deps (my_depname, foutn, chunks)
1503
1504
1505 outname = ''
1506 try:
1507         (sh, long) = getopt_args (__main__.option_definitions)
1508         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1509 except getopt.error, msg:
1510         sys.stderr.write("error: %s" % msg)
1511         sys.exit(1)
1512
1513 do_deps = 0
1514 for opt in options:     
1515         o = opt[0]
1516         a = opt[1]
1517
1518         if o == '--include' or o == '-I':
1519                 include_path.append (a)
1520         elif o == '--version' or o == '-v':
1521                 print_version ()
1522                 sys.exit  (0)
1523         elif o == '--verbose' or o == '-V':
1524                 __main__.verbose_p = 1
1525         elif o == '--format' or o == '-f':
1526                 __main__.format = a
1527         elif o == '--outname' or o == '-o':
1528                 if len(files) > 1:
1529                         #HACK
1530                         sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1531                         sys.exit(1)
1532                 outname = a
1533         elif o == '--help' or o == '-h':
1534                 help ()
1535         elif o == '--no-lily' or o == '-n':
1536                 __main__.g_run_lilypond = 0
1537         elif o == '--dependencies' or o == '-M':
1538                 do_deps = 1
1539         elif o == '--default-music-fontsize':
1540                 default_music_fontsize = string.atoi (a)
1541         elif o == '--default-lilypond-fontsize':
1542                 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1543                 default_music_fontsize = string.atoi (a)
1544         elif o == '--extra-options':
1545                 g_extra_opts = a
1546         elif o == '--force-music-fontsize':
1547                 g_force_lilypond_fontsize = string.atoi(a)
1548         elif o == '--force-lilypond-fontsize':
1549                 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1550                 g_force_lilypond_fontsize = string.atoi(a)
1551         elif o == '--dep-prefix':
1552                 g_dep_prefix = a
1553         elif o == '--no-pictures':
1554                 g_do_pictures = 0
1555         elif o == '--no-music':
1556                 g_do_music = 0
1557         elif o == '--read-lys':
1558                 g_read_lys = 1
1559         elif o == '--outdir':
1560                 g_outdir = a
1561
1562 identify (sys.stderr)
1563 if g_outdir:
1564         if os.path.isfile(g_outdir):
1565                 error ("outdir is a file: %s" % g_outdir)
1566         if not os.path.exists(g_outdir):
1567                 os.mkdir(g_outdir)
1568 setup_environment ()
1569 for input_filename in files:
1570         do_file(input_filename)
1571         
1572 #
1573 # Petr, ik zou willen dat ik iets zinvoller deed,
1574 # maar wat ik kan ik doen, het verandert toch niets?
1575 #   --hwn 20/aug/99