2 # vim: set noexpandtab:
4 # * junk --outdir for --output
5 # * Figure out clean set of options.
7 # * EndLilyPondOutput is def'd as vfil. Causes large white gaps.
8 # * texinfo: add support for @pagesize
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
16 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
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.
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.
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
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.
47 # Handle bug in Python 1.6-2.1
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.
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
56 ## https://sourceforge.net/tracker/?func=detail&aid=604803&group_id=5470&atid=105470
59 if float (sys.version[0:3]) <= 2.1 or sys.version[0:5] == '2.2.1':
69 # Attempt to fix problems with limited stack size set by Python!
70 # Sets unlimited stack size. Note that the resource module only
71 # is available on UNIX.
74 resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
78 errorport = sys.stderr
85 gettext.bindtextdomain ('lilypond', localedir)
86 gettext.textdomain ('lilypond')
93 errorport.write (s + '\n')
96 program_version = '@TOPLEVEL_VERSION@'
97 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
98 program_version = '1.6.0'
100 # if set, LILYPONDPREFIX must take prevalence
101 # if datadir is not set, we're doing a build and LILYPONDPREFIX
102 datadir = '@local_lilypond_datadir@'
104 if os.environ.has_key ('LILYPONDPREFIX') :
105 datadir = os.environ['LILYPONDPREFIX']
107 datadir = '@local_lilypond_datadir@'
109 while datadir[-1] == os.sep:
110 datadir= datadir[:-1]
112 kpse = os.popen ('kpsexpand \$TEXMF').read()
113 kpse = re.sub('[ \t\n]+$','', kpse)
114 type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
117 #binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
119 # TODO: * prevent multiple addition.
120 # * clean TEXINPUTS, MFINPUTS, TFMFONTS,
121 # as these take prevalence over $TEXMF
122 # and thus may break tex run?
123 'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
124 'GS_FONTPATH' : type1_paths,
125 'GS_LIB' : datadir + '/ps',
128 # tex needs lots of memory, more than it gets by default on Debian
129 non_path_environment = {
130 'extra_mem_top' : '1000000',
131 'extra_mem_bottom' : '1000000',
132 'pool_size' : '250000',
135 def setup_environment ():
136 # $TEXMF is special, previous value is already taken care of
137 if os.environ.has_key ('TEXMF'):
138 del os.environ['TEXMF']
140 for key in environment.keys ():
141 val = environment[key]
142 if os.environ.has_key (key):
143 val = val + os.pathsep + os.environ[key]
144 os.environ[key] = val
146 for key in non_path_environment.keys ():
147 val = non_path_environment[key]
148 os.environ[key] = val
150 include_path = [os.getcwd()]
153 # g_ is for global (?)
155 g_here_dir = os.getcwd ()
158 g_force_music_fontsize = 0
167 default_music_fontsize = 16
168 default_text_fontsize = 12
173 self.m_document_preamble = []
177 def find_latex_dims(self):
179 fname = os.path.join(g_outdir, "lily-tmp.tex")
181 fname = "lily-tmp.tex"
185 error ("Error creating temporary file '%s'" % fname)
187 for s in self.m_document_preamble:
192 \typeout{\columnsep \the\columnsep}
193 \typeout{\textwidth \the\textwidth}
198 re_dim = re.compile(r"\\(\w+)\s+(\d+\.\d+)")
200 cmd = "latex '\\nonstopmode \input %s'" % fname
202 sys.stderr.write ("Invoking `%s' as pipe" % cmd)
204 status = quiet_system (cmd, "Latex for finding dimensions")
206 sys.stderr.write (_("Invoking LaTeX failed.") + '\n' )
207 sys.stderr.write (_("This is the error log:\n") + '\n')
209 lns = open ('lily-tmp.log').readlines()
213 sys.stderr.write (ln)
214 if re.match('^!', ln):
221 countdown = countdown -1
223 sys.stderr.write (" ... (further messages elided)...\n")
226 lns = open ('lily-tmp.log').readlines()
228 ln = string.strip(ln)
231 if m.groups()[0] in ('textwidth', 'columnsep'):
232 self.__dict__['m_%s' % m.groups()[0]] = float(m.groups()[1])
236 os.remove (os.path.splitext(fname)[0]+".aux")
237 os.remove (os.path.splitext(fname)[0]+".log")
241 if not self.__dict__.has_key ('m_textwidth'):
244 def get_linewidth(self):
245 if self.m_num_cols == 1:
248 w = (self.m_textwidth - self.m_columnsep)/2
249 if self.m_multicols > 1:
250 return (w - self.m_columnsep*(self.m_multicols-1)) \
257 self.m_papersize = 'letterpaper'
259 def get_linewidth(self):
260 return html_linewidths[self.m_papersize][self.m_fontsize]
264 self.m_papersize = 'letterpaper'
266 def get_linewidth(self):
267 return texi_linewidths[self.m_papersize][self.m_fontsize]
273 def em2pt(x, fontsize = 10):
274 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
275 def ex2pt(x, fontsize = 10):
276 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
281 dimension_conversion_dict ={
283 'cm': lambda x: mm2pt(10*x),
290 # Convert numeric values, with or without specific dimension, to floats.
292 def conv_dimen_to_float(value):
293 if type(value) == type(""):
294 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
297 num = string.atof(m.group (1))
298 conv = dimension_conversion_dict[m.group(2)]
302 elif re.match ("^[0-9.]+$",value):
308 'afourpaper': {12: mm2pt(160)},
309 'afourwide': {12: in2pt(6.5)},
310 'afourlatex': {12: mm2pt(150)},
311 'smallbook': {12: in2pt(5)},
312 'letterpaper': {12: in2pt(6)}}
315 'afourpaper': {12: mm2pt(160)},
316 'afourwide': {12: in2pt(6.5)},
317 'afourlatex': {12: mm2pt(150)},
318 'smallbook': {12: in2pt(5)},
319 'letterpaper': {12: in2pt(6)}}
321 option_definitions = [
322 ('EXT', 'f', 'format', 'use output format EXT (texi [default], latex, html)'),
323 ('DIM', '', 'default-music-fontsize', 'default fontsize for music. DIM is assumed to be in points'),
324 ('DIM', '', 'default-lilypond-fontsize', 'deprecated, use --default-music-fontsize'),
325 ('OPT', '', 'extra-options' , 'pass OPT quoted to the lilypond command line'),
326 ('DIM', '', 'force-music-fontsize', 'force fontsize for all inline lilypond. DIM is assumed be to in points'),
327 ('DIM', '', 'force-lilypond-fontsize', 'deprecated, use --force-music-fontsize'),
328 ('', 'h', 'help', 'this help'),
329 ('DIR', 'I', 'include', 'include path'),
330 ('', 'M', 'dependencies', 'write dependencies'),
331 ('PREF', '', 'dep-prefix', 'prepend PREF before each -M dependency'),
332 ('', 'n', 'no-lily', 'don\'t run lilypond'),
333 ('', '', 'no-pictures', "don\'t generate pictures"),
334 ('', '', 'no-music', "strip all lilypond blocks from output"),
335 ('', '', 'read-lys', "don't write ly files."),
336 ('FILE', 'o', 'outname', 'filename main output file'),
337 ('FILE', '', 'outdir', "where to place generated files"),
338 ('', 'V', 'verbose', 'verbose' ),
339 ('', 'v', 'version', 'print version information' ),
342 # format specific strings, ie. regex-es for input, and % strings for output
348 'output-lilypond': '''<lilypond%s>
351 'output-filename' : r'''
354 <pre>%s</pre></a>:''',
355 'output-lilypond-fragment': '''<lilypond%s>
356 \context Staff\context Voice{ %s }
358 'output-noinline': r'''
359 <!-- generated: %(fn)s.png !-->
363 # Verbatim text is always finished with \n. FIXME: For HTML,
364 # this newline should be removed.
365 'output-verbatim': r'''<pre>
367 # Verbatim text is always finished with \n. FIXME: For HTML,
368 # this newline should be removed.
369 'output-small-verbatim': r'''<font size=-1><pre>
371 ## Ugh we need to differentiate on origin:
372 ## lilypond-block origin wants an extra <p>, but
373 ## inline music doesn't.
374 ## possibly other center options?
376 <a href="%(fn)s.png">
377 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
384 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
391 'output-filename' : r'''\verb+%s+:\\
395 'output-lilypond': r'''\begin[%s]{lilypond}
399 # verbatim text is always finished with \n
400 'output-verbatim': r'''\begin{verbatim}
403 # verbatim text is always finished with \n
404 'output-small-verbatim': r'''{\small\begin{verbatim}
407 'output-default-post': "\\def\postLilypondExample{}\n",
408 'output-default-pre': "\\def\preLilypondExample{}\n",
409 'usepackage-graphics': '\\usepackage{graphics}\n',
410 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
411 'output-noinline': r'''
412 %% generated: %(fn)s.eps
414 'output-tex': '{\\preLilypondExample \\input %(fn)s.tex \\postLilypondExample\n}',
415 'pagebreak': r'\pagebreak',
421 'output-lilypond': '''@lilypond[%s]
425 'output-filename' : r'''@ifnothtml
432 'output-lilypond-fragment': '''@lilypond[%s]
433 \context Staff\context Voice{ %s }
435 'output-noinline': r'''
436 @c generated: %(fn)s.png
439 # verbatim text is always finished with \n
440 'output-small-verbatim': r'''@smallexample
443 # verbatim text is always finished with \n
444 'output-verbatim': r'''@example
447 # do some tweaking: @ is needed in some ps stuff.
448 # override EndLilyPondOutput, since @tex is done
449 # in a sandbox, you can't do \input lilyponddefs at the
450 # top of the document.
452 # ugh, the <p> below breaks inline images...
453 'output-texi-noquote': r'''@tex
457 \def\EndLilyPondOutput{}
462 <p><a href="%(fn)s.png">
463 <img border=0 src="%(fn)s.png" alt="[picture of music]">
467 'output-texi-quoted': r'''@quotation
471 \def\EndLilyPondOutput{}
476 <a href="%(fn)s.png">
477 <img border=0 src="%(fn)s.png" alt="[picture of music]">
486 def output_verbatim (body, small):
487 if __main__.format == 'html':
488 body = re.sub ('&', '&', body)
489 body = re.sub ('>', '>', body)
490 body = re.sub ('<', '<', body)
491 elif __main__.format == 'texi':
492 # clumsy workaround for python 2.2 pre bug.
493 body = re.sub ('@', '@@', body)
494 body = re.sub ('{', '@{', body)
495 body = re.sub ('}', '@}', body)
498 key = 'output-small-verbatim'
500 key = 'output-verbatim'
501 return get_output (key) % body
504 # Warning: This uses extended regular expressions. Treat with care.
508 # (?P<name>regex) -- assign result of REGEX to NAME
509 # *? -- match non-greedily.
510 # (?m) -- multiline regex: make ^ and $ match at each line
511 # (?s) -- make the dot match all characters including newline
517 'preamble-end': no_match,
518 'landscape': no_match,
519 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
520 'verb': r'''(?P<code><pre>.*?</pre>)''',
521 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
522 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
523 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
524 'option-sep' : '\s*',
525 'intertext': r',?\s*intertext=\".*?\"',
526 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
527 'singleline-comment': no_match,
529 'multicols': no_match,
533 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
534 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
535 'option-sep' : ',\s*',
536 'header': r"\n*\\documentclass\s*(\[.*?\])?",
537 'preamble-end': r'(?P<code>\\begin\s*{document})',
538 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
539 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
540 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
541 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
542 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
543 'def-post-re': r"\\def\\postLilypondExample",
544 'def-pre-re': r"\\def\\preLilypondExample",
545 'usepackage-graphics': r"\usepackage\s*{graphics}",
546 'intertext': r',?\s*intertext=\".*?\"',
547 'multiline-comment': no_match,
548 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
549 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
550 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
553 # why do we have distinction between @mbinclude and @include?
556 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
559 'preamble-end': no_match,
560 'landscape': no_match,
561 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
562 'verb': r'''(?P<code>@code{.*?})''',
563 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
564 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
565 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
566 'option-sep' : ',\s*',
567 'intertext': r',?\s*intertext=\".*?\"',
568 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
569 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
571 'multicols': no_match,
576 for r in re_dict.keys ():
579 for k in olddict.keys ():
581 newdict[k] = re.compile (olddict[k])
583 print 'invalid regexp: %s' % olddict[k]
585 # we'd like to catch and reraise a more detailed error, but
586 # alas, the exceptions changed across the 1.5/2.1 boundary.
601 def get_output (name):
602 return output_dict[format][name]
605 return re_dict[format][name]
607 def bounding_box_dimensions(fname):
609 fname = os.path.join(g_outdir, fname)
613 error ("Error opening `%s'" % fname)
615 s = re.search('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
618 gs = map (lambda x: string.atoi (x), s.groups ())
619 return (int (gs[2] - gs[0] + 0.5),
620 int (gs[3] - gs[1] + 0.5))
625 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
629 def compose_full_body (body, opts):
630 '''Construct the lilypond code to send to Lilypond.
631 Add stuff to BODY using OPTS as options.'''
632 music_size = default_music_fontsize
633 if g_force_music_fontsize:
634 music_size = g_force_music_fontsize
639 if not g_force_music_fontsize:
640 m = re.match ('([0-9]+)pt', o)
642 music_size = string.atoi(m.group (1))
644 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
646 f = float (m.group (1))
647 indent = 'indent = %f\\%s' % (f, m.group (2))
649 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
651 f = float (m.group (1))
652 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
654 if re.search ('\\\\score', body):
658 if 'fragment' in opts:
660 if 'nofragment' in opts:
663 if is_fragment and not 'multiline' in opts:
664 opts.append('singleline')
666 if 'singleline' in opts:
668 linewidth = 'linewidth = -1.0'
670 indent = 'indent = 0.0\mm'
672 l = __main__.paperguru.get_linewidth ()
673 linewidth = 'linewidth = %f\pt' % l
675 if 'noindent' in opts:
676 indent = 'indent = 0.0\mm'
682 \remove Time_signature_engraver
687 m= re.search ('relative(.*)', o)
691 v = string.atoi (m.group (1))
698 pitch = pitch + '\,' * v
700 pitch = pitch + '\'' * v
702 body = '\\relative %s { %s }' %(pitch, body)
714 optstring = string.join (opts, ' ')
715 optstring = re.sub ('\n', ' ', optstring)
717 %% Generated automatically by: lilypond-book.py
719 \include "paper%d.ly"
725 ''' % (optstring, music_size, linewidth, indent, notime) + body
727 # ughUGH not original options
730 def scan_html_preamble (chunks):
733 def scan_latex_preamble(chunks):
734 # First we want to scan the \documentclass line
735 # it should be the first non-comment line.
736 # The only thing we really need to know about the \documentclass line
737 # is if there are one or two columns to begin with.
740 if chunks[idx][0] == 'ignore':
743 m = get_re ('header').match(chunks[idx][1])
745 error ("Latex documents must start with a \documentclass command")
747 options = re.split (',[\n \t]*', m.group(1)[1:-1])
750 if 'twocolumn' in options:
751 paperguru.m_num_cols = 2
755 # Then we add everything before \begin{document} to
756 # paperguru.m_document_preamble so that we can later write this header
757 # to a temporary file in find_latex_dims() to find textwidth.
758 while idx < len(chunks) and chunks[idx][0] != 'preamble-end':
759 if chunks[idx] == 'ignore':
762 paperguru.m_document_preamble.append(chunks[idx][1])
765 if len(chunks) == idx:
766 error ("Didn't find end of preamble (\\begin{document})")
768 paperguru.find_latex_dims()
770 def scan_texi_preamble (chunks):
771 # this is not bulletproof..., it checks the first 10 chunks
772 for c in chunks[:10]:
774 for s in ('afourpaper', 'afourwide', 'letterpaper',
775 'afourlatex', 'smallbook'):
776 if string.find(c[1], "@%s" % s) != -1:
777 paperguru.m_papersize = s
780 def scan_preamble (chunks):
781 if __main__.format == 'html':
782 scan_html_preamble (chunks)
783 elif __main__.format == 'latex':
784 scan_latex_preamble (chunks)
785 elif __main__.format == 'texi':
786 scan_texi_preamble (chunks)
789 def completize_preamble (chunks):
790 if __main__.format != 'latex':
792 pre_b = post_b = graphics_b = None
794 if chunk[0] == 'preamble-end':
796 if chunk[0] == 'input':
797 m = get_re('def-pre-re').search(chunk[1])
800 if chunk[0] == 'input':
801 m = get_re('def-post-re').search(chunk[1])
805 if chunk[0] == 'input':
806 m = get_re('usepackage-graphics').search(chunk[1])
810 while x < len (chunks) and chunks[x][0] != 'preamble-end':
817 chunks.insert(x, ('input', get_output ('output-default-pre')))
819 chunks.insert(x, ('input', get_output ('output-default-post')))
821 chunks.insert(x, ('input', get_output ('usepackage-graphics')))
827 def find_file (name):
829 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
833 return (sys.stdin.read (), '<stdin>')
836 for a in include_path:
838 nm = os.path.join (a, name)
840 __main__.read_files.append (nm)
845 sys.stderr.write ("Reading `%s'\n" % nm)
846 return (f.read (), nm)
848 error ("File not found `%s'\n" % name)
851 def do_ignore(match_object):
852 return [('ignore', match_object.group('code'))]
853 def do_preamble_end(match_object):
854 return [('preamble-end', match_object.group('code'))]
856 def make_verbatim(match_object):
857 return [('verbatim', match_object.group('code'))]
859 def make_verb(match_object):
860 return [('verb', match_object.group('code'))]
862 def do_include_file(m):
864 return [('input', get_output ('pagebreak'))] \
865 + read_doc_file(m.group('filename')) \
866 + [('input', get_output ('pagebreak'))]
868 def do_input_file(m):
869 return read_doc_file(m.group('filename'))
871 def make_lilypond(m):
872 if m.group('options'):
873 options = m.group('options')
876 return [('input', get_output('output-lilypond-fragment') %
877 (options, m.group('code')))]
879 def make_lilypond_file(m):
882 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
883 into a @lilypond .. @end lilypond block.
887 if m.group('options'):
888 options = m.group('options')
891 (content, nm) = find_file(m.group('filename'))
892 options = "filename=%s," % nm + options
894 return [('input', get_output('output-lilypond') %
897 def make_lilypond_block(m):
901 if m.group('options'):
902 options = get_re('option-sep').split (m.group('options'))
905 options = filter(lambda s: s != '', options)
906 return [('lilypond', m.group('code'), options)]
909 if __main__.format != 'latex':
911 if m.group('num') == 'one':
912 return [('numcols', m.group('code'), 1)]
913 if m.group('num') == 'two':
914 return [('numcols', m.group('code'), 2)]
917 if __main__.format != 'latex':
919 if m.group('be') == 'begin':
920 return [('multicols', m.group('code'), int(m.group('num')))]
922 return [('multicols', m.group('code'), 1)]
925 def chop_chunks(chunks, re_name, func, use_match=0):
931 m = get_re (re_name).search (str)
933 newchunks.append (('input', str))
937 newchunks.append (('input', str[:m.start ('match')]))
939 newchunks.append (('input', str[:m.start (0)]))
940 #newchunks.extend(func(m))
941 # python 1.5 compatible:
942 newchunks = newchunks + func(m)
943 str = str [m.end(0):]
948 def determine_format (str):
949 if __main__.format == '':
951 html = re.search ('(?i)<[dh]tml', str[:200])
952 latex = re.search (r'''\\document''', str[:200])
953 texi = re.search ('@node|@setfilename', str[:200])
958 if html and not latex and not texi:
960 elif latex and not html and not texi:
962 elif texi and not html and not latex:
965 error ("can't determine format, please specify")
968 if __main__.paperguru == None:
969 if __main__.format == 'html':
971 elif __main__.format == 'latex':
973 elif __main__.format == 'texi':
976 __main__.paperguru = g
979 def read_doc_file (filename):
980 '''Read the input file, find verbatim chunks and do \input and \include
982 (str, path) = find_file(filename)
983 determine_format (str)
985 chunks = [('input', str)]
987 # we have to check for verbatim before doing include,
988 # because we don't want to include files that are mentioned
989 # inside a verbatim environment
990 chunks = chop_chunks(chunks, 'verbatim', make_verbatim)
992 chunks = chop_chunks(chunks, 'verb', make_verb)
993 chunks = chop_chunks(chunks, 'multiline-comment', do_ignore)
995 chunks = chop_chunks(chunks, 'include', do_include_file, 1)
996 chunks = chop_chunks(chunks, 'input', do_input_file, 1)
1000 taken_file_names = {}
1001 def schedule_lilypond_block (chunk):
1002 '''Take the body and options from CHUNK, figure out how the
1003 real .ly should look, and what should be left MAIN_STR (meant
1004 for the main file). The .ly is written, and scheduled in
1007 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1009 TODO has format [basename, extension, extension, ... ]
1011 (type, body, opts) = chunk
1012 assert type == 'lilypond'
1013 file_body = compose_full_body (body, opts)
1014 ## Hmm, we should hash only lilypond source, and skip the
1017 basename = 'lily-' + `abs(hash (file_body))`
1019 m = re.search ('filename="(.*?)"', o)
1021 basename = m.group (1)
1022 if not taken_file_names.has_key(basename):
1023 taken_file_names[basename] = 0
1025 taken_file_names[basename] = taken_file_names[basename] + 1
1026 basename = basename + "-%i" % taken_file_names[basename]
1028 update_file(file_body, os.path.join(g_outdir, basename) + '.ly')
1029 needed_filetypes = ['tex']
1031 if format == 'html' or format == 'texi':
1032 needed_filetypes.append ('eps')
1033 needed_filetypes.append ('png')
1034 if 'eps' in opts and not ('eps' in needed_filetypes):
1035 needed_filetypes.append('eps')
1036 pathbase = os.path.join (g_outdir, basename)
1037 def f (base, ext1, ext2):
1038 a = os.path.isfile(base + ext2)
1039 if (os.path.isfile(base + ext1) and
1040 os.path.isfile(base + ext2) and
1041 os.stat(base+ext1)[stat.ST_MTIME] >
1042 os.stat(base+ext2)[stat.ST_MTIME]) or \
1043 not os.path.isfile(base + ext2):
1046 if 'tex' in needed_filetypes and f(pathbase, '.ly', '.tex'):
1048 if 'eps' in needed_filetypes and f(pathbase, '.tex', '.eps'):
1050 if 'png' in needed_filetypes and f(pathbase, '.eps', '.png'):
1054 if 'printfilename' in opts:
1056 m= re.match ("filename=(.*)", o)
1058 newbody = newbody + get_output ("output-filename") % (m.group(1), basename + '.ly', m.group(1))
1062 if 'smallverbatim' in opts:
1063 newbody = newbody + output_verbatim (body, 1)
1064 elif 'verbatim' in opts:
1065 newbody = newbody + output_verbatim (body, 0)
1068 m = re.search ('intertext="(.*?)"', o)
1070 newbody = newbody + "\n"
1071 if format == 'texi':
1072 newbody = newbody + "@noindent\n"
1073 elif format == 'latex':
1074 newbody = newbody + "\\noindent\n"
1075 newbody = newbody + m.group (1) + "\n"
1077 if 'noinline' in opts:
1078 s = 'output-noinline'
1079 elif format == 'latex':
1084 elif format == 'texi':
1085 if 'noquote' in opts:
1086 s = 'output-texi-noquote'
1088 s = 'output-texi-quoted'
1089 else: # format == 'html'
1091 newbody = newbody + get_output (s) % {'fn': basename }
1092 return ('lilypond', newbody, opts, todo, basename)
1094 def process_lilypond_blocks(chunks):#ugh rename
1096 # Count sections/chapters.
1098 if c[0] == 'lilypond':
1099 c = schedule_lilypond_block (c)
1100 elif c[0] == 'numcols':
1101 paperguru.m_num_cols = c[2]
1102 elif c[0] == 'multicols':
1103 paperguru.m_multicols = c[2]
1104 newchunks.append (c)
1110 sys.stderr.write ("invoking `%s'\n" % cmd)
1111 st = os.system (cmd)
1113 error ('Error command exited with value %d\n' % st)
1116 def quiet_system (cmd, name):
1118 progress ( _("Running %s...") % name)
1119 cmd = cmd + ' 1> /dev/null 2> /dev/null'
1123 def get_bbox (filename):
1125 # gs bbox device is ugh, it always prints of stderr.
1126 system ('gs -sDEVICE=bbox -q -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1' % (filename, filename))
1128 box = open (filename + '.bbox').read()
1129 m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
1132 gr = map (string.atoi, m.groups ())
1136 def make_pixmap (name):
1137 bbox = get_bbox (name + '.eps')
1141 fo = open (name + '.trans.eps' , 'w')
1142 fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
1147 x = (2* margin + bbox[2] - bbox[0]) * res / 72.
1148 y = (2* margin + bbox[3] - bbox[1]) * res / 72.
1154 cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=%s -r%d -dNOPAUSE %s %s -c quit '''
1156 cmd = cmd % (x, y, name + '.png', res, name + '.trans.eps', name + '.eps')
1159 status = system (cmd)
1164 os.unlink (name + '.png')
1165 error ("Removing output file")
1167 def compile_all_files (chunks):
1174 if c[0] != 'lilypond':
1183 if base + '.ly' not in tex:
1184 tex.append (base + '.ly')
1185 elif e == 'png' and g_do_pictures:
1191 # fixme: be sys-independent.
1193 if g_outdir and x[0] != '/' :
1194 x = os.path.join (g_here_dir, x)
1197 incs = map (incl_opt, include_path)
1198 lilyopts = string.join (incs, ' ' )
1200 lilyopts = lilyopts + ' --dependencies '
1202 lilyopts = lilyopts + '--dep-prefix=' + g_outdir + '/'
1203 texfiles = string.join (tex, ' ')
1204 cmd = '%s --header=texidoc %s %s %s' \
1205 % (binary, lilyopts, g_extra_opts, texfiles)
1210 # Ugh, fixing up dependencies for .tex generation
1213 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep', x), tex)
1218 text=re.sub ('\n([^:\n]*):', '\n' + foutn + ':', text)
1224 cmd = r"echo $TEXMF; latex '\nonstopmode \input %s'" % e
1225 quiet_system (cmd, 'LaTeX')
1227 cmd = r"dvips -E -o %s %s" % (e + '.eps', e)
1228 quiet_system (cmd, 'dvips')
1236 def update_file (body, name):
1238 write the body if it has changed
1249 f = open (name , 'w')
1256 def getopt_args (opts):
1257 "Construct arguments (LONG, SHORT) for getopt from list of options."
1262 short = short + o[1]
1270 return (short, long)
1272 def option_help_str (o):
1273 "Transform one option description (4-tuple ) into neatly formatted string"
1291 return ' ' + sh + sep + long + arg
1293 def options_help_str (opts):
1294 "Convert a list of options into a neatly formatted string"
1300 s = option_help_str (o)
1301 strs.append ((s, o[3]))
1307 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1])
1311 sys.stdout.write('''Usage: lilypond-book [options] FILE\n
1312 Generate hybrid LaTeX input from latex + lilypond.\n
1315 sys.stdout.write (options_help_str (option_definitions))
1316 sys.stdout.write (r'''
1317 Warning: All output is written in the CURRENT directory.
1320 Report bugs to bug-lilypond@gnu.org.
1322 Written by Tom Cato Amundsen <tca@gnu.org> and
1323 Han-Wen Nienhuys <hanwen@cs.uu.nl>
1329 def write_deps (fn, target, chunks):
1331 sys.stderr.write('Writing `%s\'\n' % os.path.join(g_outdir, fn))
1332 f = open (os.path.join(g_outdir, fn), 'w')
1333 f.write ('%s%s: ' % (g_dep_prefix, target))
1334 for d in read_files:
1338 if c[0] == 'lilypond':
1339 (type, body, opts, todo, basename) = c;
1340 basenames.append (basename)
1343 d=g_outdir + '/' + d
1345 #if not os.isfile (d): # thinko?
1346 if not re.search ('/', d):
1347 d = g_dep_prefix + d
1348 f.write ('%s.tex ' % d)
1350 #if len (basenames):
1351 # for d in basenames:
1352 # f.write ('%s.ly ' % d)
1353 # f.write (' : %s' % target)
1358 def identify (stream):
1359 stream.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
1361 def print_version ():
1362 identify (sys.stdout)
1363 sys.stdout.write (r'''Copyright 1998--1999
1364 Distributed under terms of the GNU General Public License. It comes with
1369 def check_texidoc (chunks):
1372 if c[0] == 'lilypond':
1373 (type, body, opts, todo, basename) = c;
1374 pathbase = os.path.join (g_outdir, basename)
1375 if os.path.isfile (pathbase + '.texidoc'):
1376 body = '\n@include %s.texidoc\n' % basename + body
1377 c = (type, body, opts, todo, basename)
1382 ## what's this? Docme --hwn
1384 def fix_epswidth (chunks):
1387 if c[0] != 'lilypond' or 'eps' not in c[2]:
1388 newchunks.append (c)
1393 m = re.match ('magnification=([0-9.]+)', o)
1395 mag = string.atof (m.group (1))
1397 def replace_eps_dim (match, lmag = mag):
1398 filename = match.group (1)
1399 dims = bounding_box_dimensions (filename)
1401 return '%fpt' % (dims[0] *lmag)
1403 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1404 newchunks.append(('lilypond', body, c[2], c[3], c[4]))
1409 ##docme: why global?
1412 def do_file(input_filename):
1413 chunks = read_doc_file(input_filename)
1414 chunks = chop_chunks(chunks, 'lilypond', make_lilypond, 1)
1415 chunks = chop_chunks(chunks, 'lilypond-file', make_lilypond_file, 1)
1416 chunks = chop_chunks(chunks, 'lilypond-block', make_lilypond_block, 1)
1417 chunks = chop_chunks(chunks, 'singleline-comment', do_ignore, 1)
1418 chunks = chop_chunks(chunks, 'preamble-end', do_preamble_end)
1419 chunks = chop_chunks(chunks, 'numcols', do_columns)
1420 chunks = chop_chunks(chunks, 'multicols', do_multicols)
1422 #for c in chunks: print "c:", c;
1424 scan_preamble(chunks)
1425 chunks = process_lilypond_blocks(chunks)
1428 if __main__.g_run_lilypond:
1429 compile_all_files (chunks)
1430 chunks = fix_epswidth (chunks)
1432 if __main__.format == 'texi':
1433 chunks = check_texidoc (chunks)
1436 chunks = completize_preamble (chunks)
1441 my_outname = outname
1442 elif input_filename == '-' or input_filename == "/dev/stdin":
1445 my_outname = os.path.basename (os.path.splitext(input_filename)[0]) + '.' + format
1446 my_depname = my_outname + '.dep'
1448 if my_outname == '-' or my_outname == '/dev/stdout':
1451 __main__.do_deps = 0
1453 foutn = os.path.join (g_outdir, my_outname)
1454 sys.stderr.write ("Writing `%s'\n" % foutn)
1455 fout = open (foutn, 'w')
1462 write_deps (my_depname, foutn, chunks)
1466 (sh, long) = getopt_args (__main__.option_definitions)
1467 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1468 except getopt.error, msg:
1469 sys.stderr.write("error: %s" % msg)
1477 if o == '--include' or o == '-I':
1478 include_path.append (a)
1479 elif o == '--version' or o == '-v':
1482 elif o == '--verbose' or o == '-V':
1483 __main__.verbose_p = 1
1484 elif o == '--format' or o == '-f':
1486 elif o == '--outname' or o == '-o':
1489 sys.stderr.write("Lilypond-book is confused by --outname on multiple files")
1492 elif o == '--help' or o == '-h':
1494 elif o == '--no-lily' or o == '-n':
1495 __main__.g_run_lilypond = 0
1496 elif o == '--dependencies' or o == '-M':
1498 elif o == '--default-music-fontsize':
1499 default_music_fontsize = string.atoi (a)
1500 elif o == '--default-lilypond-fontsize':
1501 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1502 default_music_fontsize = string.atoi (a)
1503 elif o == '--extra-options':
1505 elif o == '--force-music-fontsize':
1506 g_force_music_fontsize = string.atoi(a)
1507 elif o == '--force-lilypond-fontsize':
1508 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1509 g_force_music_fontsize = string.atoi(a)
1510 elif o == '--dep-prefix':
1512 elif o == '--no-pictures':
1514 elif o == '--no-music':
1516 elif o == '--read-lys':
1518 elif o == '--outdir':
1521 identify (sys.stderr)
1523 if os.path.isfile(g_outdir):
1524 error ("outdir is a file: %s" % g_outdir)
1525 if not os.path.exists(g_outdir):
1527 setup_environment ()
1528 for input_filename in files:
1529 do_file(input_filename)
1532 # Petr, ik zou willen dat ik iets zinvoller deed,
1533 # maar wat ik kan ik doen, het verandert toch niets?