2 # vim: set noexpandtab:
4 # * junk --outdir for--output
5 # * Figure out clean set of options.
7 # * texinfo: add support for @pagesize
9 # todo: dimension handling (all the x2y) is clumsy. (tca: Thats
10 # because the values are taken directly from texinfo.tex,
11 # geometry.sty and article.cls. Give me a hint, and I'll
15 # TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
20 # This is a slightly hairy program. The general approach is as follows
21 # The input string is chopped up in chunks, i.e. , a list of tuples
23 # with the format (TAG_STR, MAIN_STR, OPTIONS, TODO, BASE)
25 # This list is build step by step: first ignore and verbatim commands are handled,
26 # delivering a list of chunks.
28 # then all chunks containing lilypnod commands are chopped up
30 # when all chunks have their final form, all bodies from lilypond blocks are
31 # extracted, and if applicable, written do disk and run through lilypond.
35 # This is was the idea for handling of comments:
38 # Multiline comments, @ignore .. @end ignore is scanned for
39 # in read_doc_file, and the chunks are marked as 'ignore', so
40 # lilypond-book will not touch them any more. The content of the
41 # chunks are written to the output file. Also 'include' and 'input'
42 # regex has to check if they are commented out.
45 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
46 # These three regex's has to check if they are on a commented line,
47 # % for latex, @c for texinfo.
49 # Then lines that are commented out with % (latex) and @c (Texinfo)
50 # are put into chunks marked 'ignore'. This cannot be done before
51 # searching for the lilypond-blocks because % is also the comment character
54 # The the rest of the rexeces are searched for. They don't have to test
55 # if they are on a commented out line.
62 ################################################################
63 # Users of python modules should include this snippet
64 # and customize variables below.
66 # We'll suffer this path init stuff as long as we don't install our
67 # python packages in <prefix>/lib/pythonx.y (and don't kludge around
68 # it as we do with teTeX on Red Hat Linux: set some environment var
69 # (PYTHONPATH) in profile)
71 # If set, LILYPONDPREFIX must take prevalence
72 # if datadir is not set, we're doing a build and LILYPONDPREFIX
73 import getopt, os, sys
74 datadir = '@local_lilypond_datadir@'
75 if not os.path.isdir (datadir):
76 datadir = '@lilypond_datadir@'
77 if os.environ.has_key ('LILYPONDPREFIX') :
78 datadir = os.environ['LILYPONDPREFIX']
79 while datadir[-1] == os.sep:
82 sys.path.insert (0, os.path.join (datadir, 'python'))
85 #if __name__ == '__main__':
92 program_name = 'lilypond-book'
95 original_dir = os.getcwd ()
96 #temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
100 preview_resolution = 90
103 ## ly2dvi: silly name?
104 ## do -P or -p by default?
105 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
106 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
107 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
108 'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
110 option_definitions = [
111 (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
112 (_ ("DIM"), '', 'default-music-fontsize', _ ("default fontsize for music. DIM is assumed to be in points")),
113 (_ ("DIM"), '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
114 (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
115 (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed be to in points")),
116 (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
117 ('', 'h', 'help', _ ("this help")),
118 (_ ("DIR"), 'I', 'include', _ ("include path")),
119 ('', 'M', 'dependencies', _ ("write dependencies")),
120 (_ ("PREF"), '', 'dep-prefix', _ ("prepend PREF before each -M dependency")),
121 ('', 'n', 'no-lily', _ ("don't run lilypond")),
122 ('', '', 'no-pictures', _ ("don't generate pictures")),
123 ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
124 ('', '', 'read-lys', _ ("don't write ly files.")),
125 (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
126 (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
127 (_ ('RES'), '', 'preview-resolution',
128 _ ("set the resolution of the preview to RES")),
129 ('', 'V', 'verbose', _ ("verbose")),
130 ('', 'v', 'version', _ ("print version information")),
131 ('', 'w', 'warranty', _ ("show warranty and copyright")),
134 # format specific strings, ie. regex-es for input, and % strings for output
138 include_path = [os.getcwd ()]
141 #lilypond_binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
143 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
145 # only use installed binary when we're installed too.
146 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
147 lilypond_binary = 'lilypond'
151 ly2dvi_binary = os.path.join ('@bindir@', 'ly2dvi')
153 # only use installed binary when we're installed too.
154 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
155 lilypond_binary = 'ly2dvi'
160 g_here_dir = os.getcwd ()
163 g_force_music_fontsize = 0
173 default_music_fontsize = 16
174 default_text_fontsize = 12
177 ################################################################
178 # Dimension handling for LaTeX.
182 self.m_document_preamble = []
186 def find_latex_dims (self):
188 fname = os.path.join (g_outdir, "lily-tmp.tex")
190 fname = "lily-tmp.tex"
192 f = open (fname, "w")
194 error ("Error creating temporary file '%s'" % fname)
196 for s in self.m_document_preamble:
201 \typeout{\columnsep \the\columnsep}
202 \typeout{\textwidth \the\textwidth}
207 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
209 cmd = "latex '\\nonstopmode \input %s'" % fname
210 # Ugh. (La)TeX writes progress and error messages on stdout
212 cmd += ' 1>/dev/stderr'
213 status = ly.system (cmd, ignore_error = 1)
214 signal = 0xf & status
215 exit_status = status >> 8
218 ly.error (_ ("LaTeX failed."))
219 ly.error (_ ("The error log is as follows:"))
223 lns = open ('lily-tmp.log').readlines ()
228 sys.stderr.write (ln)
229 if re.match ('^!', ln):
236 countdown = countdown -1
238 sys.stderr.write (" ... (further messages elided)...\n")
241 lns = open ('lily-tmp.log').readlines ()
243 ln = string.strip (ln)
244 m = re_dim.match (ln)
246 if m.groups ()[0] in ('textwidth', 'columnsep'):
247 self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
251 os.remove (os.path.splitext (fname)[0]+".aux")
252 os.remove (os.path.splitext (fname)[0]+".log")
256 if not self.__dict__.has_key ('m_textwidth'):
259 def get_linewidth (self):
260 if self.m_num_cols == 1:
263 w = (self.m_textwidth - self.m_columnsep)/2
264 if self.m_multicols > 1:
265 return (w - self.m_columnsep* (self.m_multicols-1)) \
272 self.m_papersize = 'letterpaper'
274 def get_linewidth (self):
275 return html_linewidths[self.m_papersize][self.m_fontsize]
279 self.m_papersize = 'letterpaper'
281 def get_linewidth (self):
282 return texi_linewidths[self.m_papersize][self.m_fontsize]
288 def em2pt (x, fontsize = 10):
289 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
290 def ex2pt (x, fontsize = 10):
291 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
296 dimension_conversion_dict ={
298 'cm': lambda x: mm2pt (10*x),
305 # Convert numeric values, with or without specific dimension, to floats.
307 def conv_dimen_to_float (value):
308 if type (value) == type (""):
309 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
312 num = string.atof (m.group (1))
313 conv = dimension_conversion_dict[m.group (2)]
317 elif re.match ("^[0-9.]+$",value):
318 value = float (value)
323 'afourpaper': {12: mm2pt (160)},
324 'afourwide': {12: in2pt (6.5)},
325 'afourlatex': {12: mm2pt (150)},
326 'smallbook': {12: in2pt (5)},
327 'letterpaper': {12: in2pt (6)}}
330 'afourpaper': {12: mm2pt (160)},
331 'afourwide': {12: in2pt (6.5)},
332 'afourlatex': {12: mm2pt (150)},
333 'smallbook': {12: in2pt (5)},
334 'letterpaper': {12: in2pt (6)}}
337 ################################################################
338 # How to output various structures.
344 'output-lilypond': '''<lilypond%s>
347 'output-filename' : r'''
350 <pre>%s</pre></a>:''',
351 'output-lilypond-fragment': '''<lilypond%s>
352 \context Staff\context Voice{ %s }
354 'output-noinline': r'''
355 <!-- generated: %(fn)s.png !-->
359 # Verbatim text is always finished with \n. FIXME: For HTML,
360 # this newline should be removed.
361 'output-verbatim': r'''<pre>
363 # Verbatim text is always finished with \n. FIXME: For HTML,
364 # this newline should be removed.
365 'output-small-verbatim': r'''<font size=-1><pre>
367 ## Ugh we need to differentiate on origin:
368 ## lilypond-block origin wants an extra <p>, but
369 ## inline music doesn't.
370 ## possibly other center options?
372 <a href="%(fn)s.png">
373 <img align="center" valign="center" border="0" src="%(fn)s.png" alt="[picture of music]"></a>
380 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
387 'output-filename' : r'''\verb+%s+:\\
391 'output-lilypond': r'''\begin[%s]{lilypond}
395 # verbatim text is always finished with \n
396 'output-verbatim': r'''\begin{verbatim}
399 # verbatim text is always finished with \n
400 'output-small-verbatim': r'''{\small\begin{verbatim}
403 'output-default-post': "\\def\postLilypondExample{}\n",
404 'output-default-pre': "\\def\preLilypondExample{}\n",
405 'usepackage-graphics': '\\usepackage{graphics}\n',
406 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
407 'output-noinline': r'''
408 %% generated: %(fn)s.eps
410 'output-latex-quoted': r'''{\preLilypondExample
412 \postLilypondExample}''',
413 'output-latex-noquote': r'''{\parindent 0pt
416 \postLilypondExample}''',
417 'pagebreak': r'\pagebreak',
423 'output-lilypond': '''@lilypond[%s]
427 'output-filename' : r'''@ifnothtml
434 'output-lilypond-fragment': '''@lilypond[%s]
435 \context Staff\context Voice{ %s }
437 'output-noinline': r'''
438 @c generated: %(fn)s.png
441 # verbatim text is always finished with \n
442 'output-small-verbatim': r'''@smallexample
445 # verbatim text is always finished with \n
446 'output-verbatim': r'''@example
449 # do some tweaking: @ is needed in some ps stuff.
451 # ugh, the <p> below breaks inline images...
452 'output-texi-noquote': r'''@tex
460 <p><a href="%(fn)s.png">
461 <img border=0 src="%(fn)s.png" alt="[picture of music]">
465 'output-texi-quoted': r'''@quotation
473 <a href="%(fn)s.png">
474 <img border=0 src="%(fn)s.png" alt="[picture of music]">
483 def output_verbatim (body, small):
486 body = re.sub ('&', '&', body)
487 body = re.sub ('>', '>', body)
488 body = re.sub ('<', '<', body)
489 elif format == 'texi':
490 # clumsy workaround for python 2.2 pre bug.
491 body = re.sub ('@', '@@', body)
492 body = re.sub ('{', '@{', body)
493 body = re.sub ('}', '@}', body)
496 key = 'output-small-verbatim'
498 key = 'output-verbatim'
499 return get_output (key) % body
502 ################################################################
503 # Recognize special sequences in the input
506 # Warning: This uses extended regular expressions. Tread with care.
510 # (?P<name>regex) -- assign result of REGEX to NAME
511 # *? -- match non-greedily.
512 # (?m) -- multiline regex: make ^ and $ match at each line
513 # (?s) -- make the dot match all characters including newline
519 'preamble-end': no_match,
520 'landscape': no_match,
521 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
522 'verb': r'''(?P<code><pre>.*?</pre>)''',
523 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
524 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
525 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
526 'option-sep' : '\s*',
527 'intertext': r',?\s*intertext=\".*?\"',
528 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
529 'singleline-comment': no_match,
531 'multicols': no_match,
532 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
536 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
537 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
538 'option-sep' : ',\s*',
539 'header': r"\n*\\documentclass\s*(\[.*?\])?",
540 'preamble-end': r'(?P<code>\\begin\s*{document})',
541 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
542 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
543 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
544 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
545 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
546 'def-post-re': r"\\def\\postLilypondExample",
547 'def-pre-re': r"\\def\\preLilypondExample",
548 'usepackage-graphics': r"\usepackage\s*{graphics}",
549 'intertext': r',?\s*intertext=\".*?\"',
550 'multiline-comment': no_match,
551 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
552 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
553 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
558 # why do we have distinction between @mbinclude and @include?
561 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
564 'preamble-end': no_match,
565 'landscape': no_match,
566 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
567 'verb': r'''(?P<code>@code{.*?})''',
568 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
569 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
570 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end +lilypond)\s''',
571 'option-sep' : ',\s*',
572 'intertext': r',?\s*intertext=\".*?\"',
573 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
574 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
576 'multicols': no_match,
582 for r in re_dict.keys ():
585 for k in olddict.keys ():
587 newdict[k] = re.compile (olddict[k])
589 print 'invalid regexp: %s' % olddict[k]
591 ## we'd like to catch and reraise a more
592 ## detailed error, but alas, the exceptions
593 ## changed across the 1.5/2.1 boundary.
609 def get_output (name):
610 return output_dict[format][name]
613 return re_dict[format][name]
615 def bounding_box_dimensions (fname):
617 fname = os.path.join (g_outdir, fname)
621 error ("Error opening `%s'" % fname)
623 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
626 gs = map (lambda x: string.atoi (x), s.groups ())
627 return (int (gs[2] - gs[0] + 0.5),
628 int (gs[3] - gs[1] + 0.5))
633 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
637 def compose_full_body (body, opts):
638 '''Construct the lilypond code to send to Lilypond.
639 Add stuff to BODY using OPTS as options.'''
640 music_size = default_music_fontsize
641 if g_force_music_fontsize:
642 music_size = g_force_music_fontsize
647 if not g_force_music_fontsize:
648 m = re.match ('([0-9]+)pt', o)
650 music_size = string.atoi (m.group (1))
652 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
654 f = float (m.group (1))
655 indent = 'indent = %f\\%s' % (f, m.group (2))
657 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
659 f = float (m.group (1))
660 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
662 if re.search ('\\\\score', body):
666 if 'fragment' in opts:
668 if 'nofragment' in opts:
671 if is_fragment and not 'multiline' in opts:
672 opts.append ('singleline')
674 if 'singleline' in opts:
676 linewidth = 'linewidth = -1.0'
678 indent = 'indent = 0.0\mm'
681 l = paperguru.get_linewidth ()
682 linewidth = 'linewidth = %f\pt' % l
684 if 'noindent' in opts:
685 indent = 'indent = 0.0\mm'
691 \remove Time_signature_engraver
696 m= re.search ('relative(.*)', o)
700 v = string.atoi (m.group (1))
707 pitch = pitch + '\,' * v
709 pitch = pitch + '\'' * v
711 body = '\\relative %s { %s }' % (pitch, body)
723 optstring = string.join (opts, ' ')
724 optstring = re.sub ('\n', ' ', optstring)
726 %% Generated automatically by: lilypond-book.py
728 \include "paper%d.ly"
734 ''' % (optstring, music_size, linewidth, indent, notime) + body
736 # ughUGH not original options
739 def scan_html_preamble (chunks):
742 def scan_latex_preamble (chunks):
743 # First we want to scan the \documentclass line
744 # it should be the first non-comment line.
745 # The only thing we really need to know about the \documentclass line
746 # is if there are one or two columns to begin with.
749 if chunks[idx][0] == 'ignore':
752 m = get_re ('header').match (chunks[idx][1])
754 error ("Latex documents must start with a \documentclass command")
756 options = re.split (',[\n \t]*', m.group (1)[1:-1])
759 if 'twocolumn' in options:
760 paperguru.m_num_cols = 2
764 # Then we add everything before \begin{document} to
765 # paperguru.m_document_preamble so that we can later write this header
766 # to a temporary file in find_latex_dims() to find textwidth.
767 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
768 if chunks[idx] == 'ignore':
771 paperguru.m_document_preamble.append (chunks[idx][1])
774 if len (chunks) == idx:
775 error ("Didn't find end of preamble (\\begin{document})")
777 paperguru.find_latex_dims ()
779 def scan_texi_preamble (chunks):
780 # this is not bulletproof..., it checks the first 10 chunks
781 for c in chunks[:10]:
783 for s in ('afourpaper', 'afourwide', 'letterpaper',
784 'afourlatex', 'smallbook'):
785 if string.find (c[1], "@%s" % s) != -1:
786 paperguru.m_papersize = s
789 def scan_preamble (chunks):
792 scan_html_preamble (chunks)
793 elif format == 'latex':
794 scan_latex_preamble (chunks)
795 elif format == 'texi':
796 scan_texi_preamble (chunks)
799 def completize_preamble (chunks):
801 if format != 'latex':
803 pre_b = post_b = graphics_b = None
805 if chunk[0] == 'preamble-end':
807 if chunk[0] == 'input':
808 m = get_re ('def-pre-re').search (chunk[1])
811 if chunk[0] == 'input':
812 m = get_re ('def-post-re').search (chunk[1])
816 if chunk[0] == 'input':
817 m = get_re ('usepackage-graphics').search (chunk[1])
821 while x < len (chunks) and chunks[x][0] != 'preamble-end':
824 if x == len (chunks):
828 chunks.insert (x, ('input', get_output ('output-default-pre')))
830 chunks.insert (x, ('input', get_output ('output-default-post')))
832 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
838 def find_file (name):
840 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
844 return (sys.stdin.read (), '<stdin>')
847 for a in include_path:
849 nm = os.path.join (a, name)
852 read_files.append (nm)
857 sys.stderr.write ("Reading `%s'\n" % nm)
858 return (f.read (), nm)
860 error ("File not found `%s'\n" % name)
863 def do_ignore (match_object):
864 return [('ignore', match_object.group ('code'))]
865 def do_preamble_end (match_object):
866 return [('preamble-end', match_object.group ('code'))]
868 def make_verbatim (match_object):
869 return [('verbatim', match_object.group ('code'))]
871 def make_verb (match_object):
872 return [('verb', match_object.group ('code'))]
874 def do_include_file (m):
876 return [('input', get_output ('pagebreak'))] \
877 + read_doc_file (m.group ('filename')) \
878 + [('input', get_output ('pagebreak'))]
880 def do_input_file (m):
881 return read_doc_file (m.group ('filename'))
883 def make_lilypond (m):
884 if m.group ('options'):
885 options = m.group ('options')
888 return [('input', get_output ('output-lilypond-fragment') %
889 (options, m.group ('code')))]
891 def make_lilypond_file (m):
894 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
895 into a @lilypond .. @end lilypond block.
899 if m.group ('options'):
900 options = m.group ('options')
903 (content, nm) = find_file (m.group ('filename'))
904 options = "filename=%s," % nm + options
906 return [('input', get_output ('output-lilypond') %
909 def make_ly2dvi_block (m):
912 Find <ly2dvifile .. >
915 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
918 def make_lilypond_block (m):
922 if m.group ('options'):
923 options = get_re ('option-sep').split (m.group ('options'))
926 options = filter (lambda s: s != '', options)
927 return [('lilypond', m.group ('code'), options)]
932 if format != 'latex':
934 if m.group ('num') == 'one':
935 return [('numcols', m.group ('code'), 1)]
936 if m.group ('num') == 'two':
937 return [('numcols', m.group ('code'), 2)]
939 def do_multicols (m):
941 if format != 'latex':
943 if m.group ('be') == 'begin':
944 return [('multicols', m.group ('code'), int (m.group ('num')))]
946 return [('multicols', m.group ('code'), 1)]
949 def chop_chunks (chunks, re_name, func, use_match=0):
955 m = get_re (re_name).search (str)
957 newchunks.append (('input', str))
961 newchunks.append (('input', str[:m.start ('match')]))
963 newchunks.append (('input', str[:m.start (0)]))
964 #newchunks.extend (func (m))
965 # python 1.5 compatible:
966 newchunks = newchunks + func (m)
967 str = str [m.end (0):]
972 def determine_format (str):
975 html = re.search ('(?i)<[dh]tml', str[:200])
976 latex = re.search (r'''\\document''', str[:200])
977 texi = re.search ('@node|@setfilename', str[:200])
982 if html and not latex and not texi:
984 elif latex and not html and not texi:
986 elif texi and not html and not latex:
989 error ("can't determine format, please specify")
993 if paperguru == None:
996 elif format == 'latex':
998 elif format == 'texi':
1004 def read_doc_file (filename):
1005 '''Read the input file, find verbatim chunks and do \input and \include
1007 (str, path) = find_file (filename)
1008 determine_format (str)
1010 chunks = [('input', str)]
1012 # we have to check for verbatim before doing include,
1013 # because we don't want to include files that are mentioned
1014 # inside a verbatim environment
1015 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1017 chunks = chop_chunks (chunks, 'verb', make_verb)
1018 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1020 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1021 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1025 taken_file_names = {}
1027 def unique_file_name (body):
1028 return 'lily-' + `abs (hash (body))`
1030 def schedule_lilypond_block (chunk):
1031 '''Take the body and options from CHUNK, figure out how the
1032 real .ly should look, and what should be left MAIN_STR (meant
1033 for the main file). The .ly is written, and scheduled in
1036 Return: a chunk (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE)
1038 TODO has format [basename, extension, extension, ... ]
1040 (type, body, opts) = chunk
1041 assert type == 'lilypond'
1042 file_body = compose_full_body (body, opts)
1043 ## Hmm, we should hash only lilypond source, and skip the
1046 basename = unique_file_name (file_body)
1048 m = re.search ('filename="(.*?)"', o)
1050 basename = m.group (1)
1051 if not taken_file_names.has_key (basename):
1052 taken_file_names[basename] = 0
1054 taken_file_names[basename] = taken_file_names[basename] + 1
1055 basename = basename + "-%i" % taken_file_names[basename]
1057 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1058 needed_filetypes = ['tex']
1060 if format == 'html' or g_make_html:
1061 needed_filetypes.append ('eps')
1062 needed_filetypes.append ('png')
1063 if 'eps' in opts and not ('eps' in needed_filetypes):
1064 needed_filetypes.append ('eps')
1066 pathbase = os.path.join (g_outdir, basename)
1067 def f (base, ext1, ext2):
1068 a = os.path.isfile (base + ext2)
1069 if (os.path.isfile (base + ext1) and
1070 os.path.isfile (base + ext2) and
1071 os.stat (base+ext1)[stat.ST_MTIME] >
1072 os.stat (base+ext2)[stat.ST_MTIME]) or \
1073 not os.path.isfile (base + ext2):
1076 if 'tex' in needed_filetypes and f (pathbase, '.ly', '.tex'):
1078 if 'eps' in needed_filetypes and f (pathbase, '.tex', '.eps'):
1080 if 'png' in needed_filetypes and f (pathbase, '.eps', '.png'):
1084 if 'printfilename' in opts:
1086 m= re.match ("filename=(.*)", o)
1088 newbody = newbody + get_output ("output-filename") % (m.group (1), basename + '.ly', m.group (1))
1092 if 'smallverbatim' in opts:
1093 newbody = newbody + output_verbatim (body, 1)
1094 elif 'verbatim' in opts:
1095 newbody = newbody + output_verbatim (body, 0)
1098 m = re.search ('intertext="(.*?)"', o)
1100 newbody = newbody + "\n"
1101 if format == 'texi':
1102 newbody = newbody + "@noindent\n"
1103 elif format == 'latex':
1104 newbody = newbody + "\\noindent\n"
1105 newbody = newbody + m.group (1) + "\n"
1107 if 'noinline' in opts:
1108 s = 'output-noinline'
1109 elif format == 'latex':
1113 if 'noquote' in opts:
1114 s = 'output-latex-noquote'
1116 s = 'output-latex-quoted'
1117 elif format == 'texi':
1118 if 'noquote' in opts:
1119 s = 'output-texi-noquote'
1121 s = 'output-texi-quoted'
1122 else: # format == 'html'
1124 newbody = newbody + get_output (s) % {'fn': basename }
1125 return ('lilypond', newbody, opts, todo, basename)
1130 def process_lilypond_blocks (chunks):#ugh rename
1132 # Count sections/chapters.
1134 if c[0] == 'lilypond':
1135 c = schedule_lilypond_block (c)
1136 elif c[0] == 'numcols':
1137 paperguru.m_num_cols = c[2]
1138 elif c[0] == 'multicols':
1139 paperguru.m_multicols = c[2]
1140 newchunks.append (c)
1143 def process_ly2dvi_blocks (chunks):
1145 def process_ly2dvi_block (chunk):
1148 Run ly2dvi script on filename specified in CHUNK.
1149 This is only supported for HTML output.
1151 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1152 a separate HTML file, and a title + preview in the main html file,
1153 linking to the menu.
1156 (tag, name, opts) = chunk
1157 assert format == 'html'
1158 (content, original_name) = find_file (name)
1160 original_name = os.path.basename (original_name)
1162 base = unique_file_name (content)
1163 outname = base + '.ly'
1164 changed = update_file (content, outname)
1166 preview = base + ".png"
1167 if changed or not os.path.isfile (preview):
1169 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1171 ly.make_page_images (base)
1172 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1175 b = os.stat(fn)[stat.ST_SIZE]
1177 return '%d bytes' % b
1179 return '%d kb' % (b >> 10)
1181 return '%d mb' % (b >> 20)
1184 'pdf' : "Print (PDF, %s)",
1185 'ps.gz' : "Print (gzipped PostScript, %s)",
1186 'png' : "View (PNG, %s)",
1187 'midi' : "Listen (MIDI, %s)",
1188 'ly' : "View source code (%s)",
1192 page_files = ly.read_pipe ('ls --color=no -1 %s-page*.png' % base)
1194 for p in string.split (page_files, '\n'):
1196 if os.path.isfile (p):
1198 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1200 menu += '<li><a href="%s">%s</a>' % (p, page)
1202 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1205 print 'checking,' , fn
1206 if not os.path.isfile (fn):
1209 entry = exts[e] % size_str (fn)
1211 ## TODO: do something like
1212 ## this for texinfo/latex as well ?
1214 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1217 explanatory_para = """The pictures are 90dpi
1218 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1219 use scalable fonts and should look OK at any resolution."""
1222 <title>LilyPond example %s</title>
1228 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1230 open (base + '.html','w'). write (separate_menu)
1232 inline_menu = '<p/><a href="%s.html"><img src="%s"><p/></a>' % (base, original_name, preview)
1234 return ('ly2dvi', inline_menu)
1238 if c[0] == 'ly2dvi':
1239 c = process_ly2dvi_block (c)
1240 newchunks.append (c)
1244 def compile_all_files (chunks):
1251 if c[0] != 'lilypond':
1260 if base + '.ly' not in tex:
1261 tex.append (base + '.ly')
1262 elif e == 'png' and g_do_pictures:
1268 # fixme: be sys-independent.
1270 if g_outdir and x[0] != '/' :
1271 x = os.path.join (g_here_dir, x)
1274 incs = map (incl_opt, include_path)
1275 lilyopts = string.join (incs)
1277 lilyopts += ' --dependencies'
1279 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1280 lilyopts += ' --header=texidoc'
1281 texfiles = string.join (tex)
1282 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1284 ly.system (cmd, ignore_error = 0, progress_p = 1)
1287 # Ugh, fixing up dependencies for .tex generation
1290 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1297 text=re.sub ('\n([^:\n]*):',
1298 '\n' + foutn + ':', text)
1304 cmd = r"latex '\nonstopmode \input %s'" % file
1305 # Ugh. (La)TeX writes progress and error messages on stdout
1306 # Redirect to stderr
1307 cmd += ' 1>/dev/stderr'
1309 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1312 map (ly.make_preview, png)
1316 def update_file (body, name):
1318 write the body if it has changed. Return whether BODY has changed.
1329 f = open (name , 'w')
1336 def write_deps (fn, target, chunks):
1338 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1339 f = open (os.path.join (g_outdir, fn), 'w')
1340 f.write ('%s%s: ' % (g_dep_prefix, target))
1341 for d in read_files:
1345 if c[0] == 'lilypond':
1346 (type, body, opts, todo, basename) = c;
1347 basenames.append (basename)
1350 d=g_outdir + '/' + d
1352 #if not os.isfile (d): # thinko?
1353 if not re.search ('/', d):
1354 d = g_dep_prefix + d
1355 f.write ('%s.tex ' % d)
1357 #if len (basenames):
1358 # for d in basenames:
1359 # f.write ('%s.ly ' % d)
1360 # f.write (' : %s' % target)
1365 def check_texidoc (chunks):
1368 if c[0] == 'lilypond':
1369 (type, body, opts, todo, basename) = c;
1370 pathbase = os.path.join (g_outdir, basename)
1371 if os.path.isfile (pathbase + '.texidoc') \
1372 and 'notexidoc' not in opts:
1373 body = '\n@include %s.texidoc\n' % basename + body
1374 c = (type, body, opts, todo, basename)
1379 ## what's this? Docme --hwn
1381 def fix_epswidth (chunks):
1384 if c[0] != 'lilypond' or 'eps' not in c[2]:
1385 newchunks.append (c)
1390 m = re.match ('magnification=([0-9.]+)', o)
1392 mag = string.atof (m.group (1))
1394 def replace_eps_dim (match, lmag = mag):
1395 filename = match.group (1)
1396 dims = bounding_box_dimensions (filename)
1398 return '%fpt' % (dims[0] *lmag)
1400 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1401 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1406 ##docme: why global?
1409 def do_file (input_filename):
1410 chunks = read_doc_file (input_filename)
1411 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1412 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1413 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1414 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1415 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1416 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1417 chunks = chop_chunks (chunks, 'numcols', do_columns)
1418 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1420 #for c in chunks: print "c:", c;
1422 scan_preamble (chunks)
1423 chunks = process_lilypond_blocks (chunks)
1424 chunks = process_ly2dvi_blocks (chunks)
1427 global g_run_lilypond
1429 compile_all_files (chunks)
1430 chunks = fix_epswidth (chunks)
1433 if format == 'texi':
1434 chunks = check_texidoc (chunks)
1437 chunks = completize_preamble (chunks)
1442 my_outname = outname
1443 elif input_filename == '-' or input_filename == "/dev/stdin":
1446 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1447 my_depname = my_outname + '.dep'
1449 if my_outname == '-' or my_outname == '/dev/stdout':
1455 foutn = os.path.join (g_outdir, my_outname)
1456 sys.stderr.write ("Writing `%s'\n" % foutn)
1457 fout = open (foutn, 'w')
1464 write_deps (my_depname, foutn, chunks)
1468 (sh, long) = ly.getopt_args (option_definitions)
1469 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1471 except getopt.error, msg:
1472 sys.stderr.write ('\n')
1473 ly.error (_ ("getopt says: `%s\'" % s))
1474 sys.stderr.write ('\n')
1483 if o == '--include' or o == '-I':
1484 include_path.append (a)
1485 elif o == '--version' or o == '-v':
1486 ly.identify (sys.stdout)
1488 elif o == '--verbose' or o == '-V':
1490 elif o == '--format' or o == '-f':
1492 if a == 'texi-html':
1495 elif o == '--outname' or o == '-o':
1498 sys.stderr.write ("Lilypond-book is confused by --outname on multiple files")
1501 elif o == '--help' or o == '-h':
1504 elif o == '--no-lily' or o == '-n':
1506 elif o == '--preview-resolution':
1507 preview_resolution = string.atoi (a)
1508 elif o == '--dependencies' or o == '-M':
1510 elif o == '--default-music-fontsize':
1511 default_music_fontsize = string.atoi (a)
1512 elif o == '--default-lilypond-fontsize':
1513 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1514 default_music_fontsize = string.atoi (a)
1515 elif o == '--extra-options':
1517 elif o == '--force-music-fontsize':
1518 g_force_music_fontsize = string.atoi (a)
1519 elif o == '--force-lilypond-fontsize':
1520 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1521 g_force_music_fontsize = string.atoi (a)
1522 elif o == '--dep-prefix':
1524 elif o == '--no-pictures':
1526 elif o == '--no-music':
1528 elif o == '--read-lys':
1530 elif o == '--outdir':
1532 elif o == '--warranty' or o == '-w':
1533 #status = os.system ('lilypond -w')
1538 ly.identify (sys.stderr)
1541 if os.path.isfile (g_outdir):
1542 error ("outdir is a file: %s" % g_outdir)
1543 if not os.path.exists (g_outdir):
1548 ly.error (_ ("no files specified on command line"))
1551 ly.setup_environment ()
1553 for input_filename in files:
1554 do_file (input_filename)
1558 # Petr, ik zou willen dat ik iets zinvoller deed,
1559 # maar wat ik kan ik doen, het verandert toch niets?