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