2 # vim: set noexpandtab:
7 * junk --outdir for--output
8 * Figure out clean set of options.
10 * texinfo: add support for @pagesize
12 todo: dimension handling (all the x2y) is clumsy. (tca: Thats
13 because the values are taken directly from texinfo.tex,
14 geometry.sty and article.cls. Give me a hint, and I'll
18 TODO: magnification support should also work for texinfo -> html: eg. add as option to dvips.
22 This is a slightly hairy program. The general approach is as follows
23 The input string is chopped up in chunks, i.e. , a list of tuples
25 with the format (TAG_STR, MAIN_STR, OPTIONS, TODO, BASE)
27 This list is built step by step: first ignore and verbatim commands
28 are handled, delivering a list of chunks.
30 then all chunks containing lilypond commands are chopped up
32 when all chunks have their final form, all bodies from lilypond blocks are
33 extracted, and if applicable, written do disk and run through lilypond.
51 # This is was the idea for handling of comments:
52 # Multiline comments, @ignore .. @end ignore is scanned for
53 # in read_doc_file, and the chunks are marked as 'ignore', so
54 # lilypond-book will not touch them any more. The content of the
55 # chunks are written to the output file. Also 'include' and 'input'
56 # regex has to check if they are commented out.
59 # Then it is scanned for 'lilypond', 'lilypond-file' and 'lilypond-block'.
60 # These three regex's has to check if they are on a commented line,
61 # % for latex, @c for texinfo.
63 # Then lines that are commented out with % (latex) and @c (Texinfo)
64 # are put into chunks marked 'ignore'. This cannot be done before
65 # searching for the lilypond-blocks because % is also the comment character
68 # The the rest of the rexeces are searched for. They don't have to test
69 # if they are on a commented out line.
76 ################################################################
77 # Users of python modules should include this snippet
78 # and customize variables below.
80 # We'll suffer this path init stuff as long as we don't install our
81 # python packages in <prefix>/lib/pythonx.y (and don't kludge around
82 # it as we do with teTeX on Red Hat Linux: set some environment var
83 # (PYTHONPATH) in profile)
85 # If set, LILYPONDPREFIX must take prevalence
86 # if datadir is not set, we're doing a build and LILYPONDPREFIX
87 import getopt, os, sys
88 datadir = '@local_lilypond_datadir@'
89 if not os.path.isdir (datadir):
90 datadir = '@lilypond_datadir@'
91 if os.environ.has_key ('LILYPONDPREFIX') :
92 datadir = os.environ['LILYPONDPREFIX']
93 while datadir[-1] == os.sep:
96 sys.path.insert (0, os.path.join (datadir, 'python'))
99 #if __name__ == '__main__':
106 program_version = '@TOPLEVEL_VERSION@'
107 program_name = 'lilypond-book'
110 original_dir = os.getcwd ()
113 preview_resolution = 90
116 ## ly2dvi: silly name?
117 ## do -P or -p by default?
118 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
119 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
120 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
121 'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
123 option_definitions = [
124 (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
125 (_ ("DIM"), '', 'default-music-fontsize', _ ("default fontsize for music. DIM is assumed to be in points")),
126 (_ ("DIM"), '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
127 (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
128 (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed to be in points")),
129 (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
130 ('', 'h', 'help', _ ("print this help")),
131 (_ ("DIR"), 'I', 'include', _ ("include path")),
132 ('', 'M', 'dependencies', _ ("write dependencies")),
133 (_ ("PREF"), '', 'dep-prefix', _ ("prepend PREF before each -M dependency")),
134 ('', 'n', 'no-lily', _ ("don't run lilypond")),
135 ('', '', 'no-pictures', _ ("don't generate pictures")),
136 ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
137 (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
138 (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
139 (_ ('RES'), '', 'preview-resolution',
140 _ ("set the resolution of the preview to RES")),
141 ('', 'V', 'verbose', _ ("be verbose")),
142 ('', 'v', 'version', _ ("print version information")),
143 ('', 'w', 'warranty', _ ("show warranty and copyright")),
146 # format specific strings, ie. regex-es for input, and % strings for output
150 include_path = [os.getcwd ()]
152 #lilypond_binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
154 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
156 # only use installed binary when we're installed too.
157 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
158 lilypond_binary = 'lilypond'
162 ly2dvi_binary = os.path.join ('@bindir@', 'ly2dvi')
164 # only use installed binary when we're installed too.
165 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
166 ly2dvi_binary = 'ly2dvi'
171 g_here_dir = os.getcwd ()
174 g_force_music_fontsize = 0
183 default_music_fontsize = 16
184 default_text_fontsize = 12
187 ################################################################
188 # Dimension handling for LaTeX.
192 self.m_document_preamble = []
196 def find_latex_dims (self):
198 fname = os.path.join (g_outdir, "lily-tmp.tex")
200 fname = "lily-tmp.tex"
202 f = open (fname, "w")
204 error ("Error creating temporary file '%s'" % fname)
206 for s in self.m_document_preamble:
211 \typeout{\columnsep \the\columnsep}
212 \typeout{\textwidth \the\textwidth}
217 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
219 cmd = "latex '\\nonstopmode \input %s'" % fname
220 # Ugh. (La)TeX writes progress and error messages on stdout
222 cmd += ' 1>/dev/stderr'
223 status = ly.system (cmd, ignore_error = 1)
224 signal = 0xf & status
225 exit_status = status >> 8
228 ly.error (_ ("LaTeX failed."))
229 ly.error (_ ("The error log is as follows:"))
233 lns = open ('lily-tmp.log').readlines ()
238 sys.stderr.write (ln)
239 if re.match ('^!', ln):
246 countdown = countdown -1
248 sys.stderr.write (" ... (further messages elided)...\n")
251 lns = open ('lily-tmp.log').readlines ()
253 ln = string.strip (ln)
254 m = re_dim.match (ln)
256 if m.groups ()[0] in ('textwidth', 'columnsep'):
257 self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
261 os.remove (os.path.splitext (fname)[0]+".aux")
262 os.remove (os.path.splitext (fname)[0]+".log")
266 if not self.__dict__.has_key ('m_textwidth'):
269 def get_linewidth (self):
270 if self.m_num_cols == 1:
273 w = (self.m_textwidth - self.m_columnsep)/2
274 if self.m_multicols > 1:
275 return (w - self.m_columnsep* (self.m_multicols-1)) \
282 self.m_papersize = 'letterpaper'
284 def get_linewidth (self):
285 return html_linewidths[self.m_papersize][self.m_fontsize]
289 self.m_papersize = 'letterpaper'
291 def get_linewidth (self):
292 return texi_linewidths[self.m_papersize][self.m_fontsize]
298 def em2pt (x, fontsize = 10):
299 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
300 def ex2pt (x, fontsize = 10):
301 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
306 dimension_conversion_dict ={
308 'cm': lambda x: mm2pt (10*x),
315 # Convert numeric values, with or without specific dimension, to floats.
317 def conv_dimen_to_float (value):
318 if type (value) == type (""):
319 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
322 num = string.atof (m.group (1))
323 conv = dimension_conversion_dict[m.group (2)]
327 elif re.match ("^[0-9.]+$",value):
328 value = float (value)
333 'afourpaper': {12: mm2pt (160)},
334 'afourwide': {12: in2pt (6.5)},
335 'afourlatex': {12: mm2pt (150)},
336 'smallbook': {12: in2pt (5)},
337 'letterpaper': {12: in2pt (6)}}
340 'afourpaper': {12: mm2pt (160)},
341 'afourwide': {12: in2pt (6.5)},
342 'afourlatex': {12: mm2pt (150)},
343 'smallbook': {12: in2pt (5)},
344 'letterpaper': {12: in2pt (6)}}
347 ################################################################
348 # How to output various structures.
354 'output-filename' : r'''
357 <pre>%s</pre></a>:''',
358 'output-lilypond-fragment': '''<lilypond%s>
359 \context Staff\context Voice{ %s }
361 'output-noinline': r'''
362 <!-- generated: %(fn)s.png !-->
366 # Verbatim text is always finished with \n. FIXME: For HTML,
367 # this newline should be removed.
368 'output-verbatim': r'''<pre>
370 # Verbatim text is always finished with \n. FIXME: For HTML,
371 # this newline should be removed.
372 'output-small-verbatim': r'''<font size=-1><pre>
374 ## Ugh we need to differentiate on origin:
375 ## lilypond-block origin wants an extra <p>, but
376 ## inline music doesn't.
377 ## possibly other center options?
385 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
392 'output-filename' : r'''\verb+%s+:\\
397 # verbatim text is always finished with \n
398 'output-verbatim': r'''\begin{verbatim}
401 # verbatim text is always finished with \n
402 'output-small-verbatim': r'''{\small\begin{verbatim}
405 'output-default-post': "\\def\postLilyPondExample{}\n",
406 'output-default-pre': "\\def\preLilyPondExample{}\n",
407 'usepackage-graphics': '\\usepackage{graphics}\n',
408 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
409 'output-noinline': r'''
410 %% generated: %(fn)s.eps
412 'output-latex-quoted': r'''{\preLilyPondExample
414 \postLilyPondExample}''',
415 'output-latex-noquote': r'''{\parindent 0pt
418 \postLilyPondExample}''',
419 'pagebreak': r'\pagebreak',
426 'output-filename' : r'''
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
464 'output-texi-quoted': r'''@quotation
481 def output_verbatim (body, small):
484 body = re.sub ('&', '&', body)
485 body = re.sub ('>', '>', body)
486 body = re.sub ('<', '<', body)
487 elif format == 'texi':
488 # clumsy workaround for python 2.2 pre bug.
489 body = re.sub ('@', '@@', body)
490 body = re.sub ('{', '@{', body)
491 body = re.sub ('}', '@}', body)
494 key = 'output-small-verbatim'
496 key = 'output-verbatim'
497 return get_output (key) % body
500 ################################################################
501 # Recognize special sequences in the input
504 # Warning: This uses extended regular expressions. Tread 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,
530 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
534 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
535 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
536 'option-sep' : ',\s*',
537 'header': r"\n*\\documentclass\s*(\[.*?\])?",
538 'preamble-end': r'(?P<code>\\begin\s*{document})',
539 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
540 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
541 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
542 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
543 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
544 'def-post-re': r"\\def\\postLilyPondExample",
545 'def-pre-re': r"\\def\\preLilyPondExample",
546 'usepackage-graphics': r"\usepackage\s*{graphics}",
547 'intertext': r',?\s*intertext=\".*?\"',
548 'multiline-comment': no_match,
549 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
550 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
551 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
556 # why do we have distinction between @mbinclude and @include?
559 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
562 'preamble-end': no_match,
563 'landscape': no_match,
564 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
565 'verb': r'''(?P<code>@code{.*?})''',
566 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
567 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
568 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
569 'option-sep' : ',\s*',
570 'intertext': r',?\s*intertext=\".*?\"',
571 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
572 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
574 'multicols': no_match,
580 for r in re_dict.keys ():
583 for k in olddict.keys ():
585 newdict[k] = re.compile (olddict[k])
587 print 'invalid regexp: %s' % olddict[k]
589 ## we'd like to catch and reraise a more
590 ## detailed error, but alas, the exceptions
591 ## changed across the 1.5/2.1 boundary.
607 def get_output (name):
608 return output_dict[format][name]
611 return re_dict[format][name]
613 def bounding_box_dimensions (fname):
615 fname = os.path.join (g_outdir, fname)
619 error ("Error opening `%s'" % fname)
621 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
624 gs = map (lambda x: string.atoi (x), s.groups ())
625 return (int (gs[2] - gs[0] + 0.5),
626 int (gs[3] - gs[1] + 0.5))
631 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
635 def compose_full_body (body, opts):
636 '''Construct the lilypond code to send to LilyPond.
637 Add stuff to BODY using OPTS as options.'''
638 music_size = default_music_fontsize
639 if g_force_music_fontsize:
640 music_size = g_force_music_fontsize
645 if not g_force_music_fontsize:
646 m = re.match ('([0-9]+)pt', o)
648 music_size = string.atoi (m.group (1))
650 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
652 f = float (m.group (1))
653 indent = 'indent = %f\\%s' % (f, m.group (2))
655 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
657 f = float (m.group (1))
658 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
660 if re.search ('\\\\score', body):
664 if 'fragment' in opts:
666 if 'nofragment' in opts:
669 if is_fragment and not 'multiline' in opts:
670 opts.append ('singleline')
672 if 'raggedright' in opts or 'singleline' in opts:
674 linewidth = 'raggedright = ##t'
676 indent = 'indent = 0.0\mm'
679 l = paperguru.get_linewidth ()
680 linewidth = 'linewidth = %f\pt' % l
682 if 'noindent' in opts:
683 indent = 'indent = 0.0\mm'
689 \remove Time_signature_engraver
694 m= re.search ('relative(.*)', o)
698 v = string.atoi (m.group (1))
705 pitch = pitch + '\,' * v
707 pitch = pitch + '\'' * v
709 body = '\\relative %s { %s }' % (pitch, body)
721 optstring = string.join (opts, ' ')
722 optstring = re.sub ('\n', ' ', optstring)
724 %% Generated automatically by: lilypond-book.py
726 \include "paper%d.ly"
732 ''' % (optstring, music_size, linewidth, indent, notime) + body
734 # ughUGH not original options
737 def scan_html_preamble (chunks):
740 def scan_latex_preamble (chunks):
741 # First we want to scan the \documentclass line
742 # it should be the first non-comment line.
743 # The only thing we really need to know about the \documentclass line
744 # is if there are one or two columns to begin with.
747 if chunks[idx][0] == 'ignore':
750 m = get_re ('header').match (chunks[idx][1])
752 error ("Latex documents must start with a \documentclass command")
754 options = re.split (',[\n \t]*', m.group (1)[1:-1])
757 if 'twocolumn' in options:
758 paperguru.m_num_cols = 2
762 # Then we add everything before \begin{document} to
763 # paperguru.m_document_preamble so that we can later write this header
764 # to a temporary file in find_latex_dims() to find textwidth.
765 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
766 if chunks[idx] == 'ignore':
769 paperguru.m_document_preamble.append (chunks[idx][1])
772 if len (chunks) == idx:
773 error ("Didn't find end of preamble (\\begin{document})")
775 paperguru.find_latex_dims ()
777 def scan_texi_preamble (chunks):
778 # this is not bulletproof..., it checks the first 10 chunks
779 for c in chunks[:10]:
781 for s in ('afourpaper', 'afourwide', 'letterpaper',
782 'afourlatex', 'smallbook'):
783 if string.find (c[1], "@%s" % s) != -1:
784 paperguru.m_papersize = s
787 def scan_preamble (chunks):
790 scan_html_preamble (chunks)
791 elif format == 'latex':
792 scan_latex_preamble (chunks)
793 elif format == 'texi':
794 scan_texi_preamble (chunks)
797 def completize_preamble (chunks):
799 if format != 'latex':
801 pre_b = post_b = graphics_b = None
803 if chunk[0] == 'preamble-end':
805 if chunk[0] == 'input':
806 m = get_re ('def-pre-re').search (chunk[1])
809 if chunk[0] == 'input':
810 m = get_re ('def-post-re').search (chunk[1])
814 if chunk[0] == 'input':
815 m = get_re ('usepackage-graphics').search (chunk[1])
819 while x < len (chunks) and chunks[x][0] != 'preamble-end':
822 if x == len (chunks):
826 chunks.insert (x, ('input', get_output ('output-default-pre')))
828 chunks.insert (x, ('input', get_output ('output-default-post')))
830 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
836 def find_file (name):
838 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
842 return (sys.stdin.read (), '<stdin>')
845 for a in include_path:
847 nm = os.path.join (a, name)
850 read_files.append (nm)
855 sys.stderr.write ("Reading `%s'\n" % nm)
856 return (f.read (), nm)
858 error ("File not found `%s'\n" % name)
861 def do_ignore (match_object):
862 return [('ignore', match_object.group ('code'))]
863 def do_preamble_end (match_object):
864 return [('preamble-end', match_object.group ('code'))]
866 def make_verbatim (match_object):
867 return [('verbatim', match_object.group ('code'))]
869 def make_verb (match_object):
870 return [('verb', match_object.group ('code'))]
872 def do_include_file (m):
874 return [('input', get_output ('pagebreak'))] \
875 + read_doc_file (m.group ('filename')) \
876 + [('input', get_output ('pagebreak'))]
878 def do_input_file (m):
879 return read_doc_file (m.group ('filename'))
881 def make_lilypond (m):
882 if m.group ('options'):
883 options = m.group ('options')
886 return [('input', get_output ('output-lilypond-fragment') %
887 (options, m.group ('code')))]
889 def make_lilypond_file (m):
892 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
893 into a @lilypond .. @end lilypond block.
897 if m.group ('options'):
898 options = get_re ('option-sep').split (m.group ('options'))
901 (content, nm) = find_file (m.group ('filename'))
902 options.append ("filename=%s" % nm)
905 return [('lilypond', content, options)]
908 def make_ly2dvi_block (m):
911 Find <ly2dvifile .. >
914 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
917 def make_lilypond_block (m):
921 if m.group ('options'):
922 options = get_re ('option-sep').split (m.group ('options'))
925 options = filter (lambda s: s != '', options)
926 return [('lilypond', m.group ('code'), options)]
931 if format != 'latex':
933 if m.group ('num') == 'one':
934 return [('numcols', m.group ('code'), 1)]
935 if m.group ('num') == 'two':
936 return [('numcols', m.group ('code'), 2)]
938 def do_multicols (m):
940 if format != 'latex':
942 if m.group ('be') == 'begin':
943 return [('multicols', m.group ('code'), int (m.group ('num')))]
945 return [('multicols', m.group ('code'), 1)]
948 def chop_chunks (chunks, re_name, func, use_match=0):
954 m = get_re (re_name).search (str)
956 newchunks.append (('input', str))
960 newchunks.append (('input', str[:m.start ('match')]))
962 newchunks.append (('input', str[:m.start (0)]))
963 #newchunks.extend (func (m))
964 # python 1.5 compatible:
965 newchunks = newchunks + func (m)
966 str = str [m.end (0):]
971 def determine_format (str):
974 SIDE EFFECT! This sets FORMAT and PAPERGURU
980 html = re.search ('(?i)<[dh]tml', str[:200])
981 latex = re.search (r'''\\document''', str[:200])
982 texi = re.search ('@node|@setfilename', str[:200])
987 if html and not latex and not texi:
989 elif latex and not html and not texi:
991 elif texi and not html and not latex:
994 error ("can't determine format, please specify")
998 if paperguru == None:
1001 elif format == 'latex':
1003 elif format == 'texi':
1009 def read_doc_file (filename):
1010 '''Read the input file, find verbatim chunks and do \input and \include
1012 (str, path) = find_file (filename)
1013 determine_format (str)
1015 chunks = [('input', str)]
1017 # we have to check for verbatim before doing include,
1018 # because we don't want to include files that are mentioned
1019 # inside a verbatim environment
1020 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1022 chunks = chop_chunks (chunks, 'verb', make_verb)
1023 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1025 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1026 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1030 taken_file_names = {}
1032 def unique_file_name (body):
1033 return 'lily-' + `abs (hash (body))`
1035 def schedule_lilypond_block (chunk):
1036 '''Take the body and options from CHUNK, figure out how the
1037 real .ly should look. The .ly is written, and scheduled in
1040 Return: a single chunk.
1042 The chunk pertaining to the lilypond output
1043 has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE),
1044 where TODO has format [basename, extension, extension, ... ]
1047 (type, body, opts) = chunk
1048 assert type == 'lilypond'
1049 file_body = compose_full_body (body, opts)
1050 ## Hmm, we should hash only lilypond source, and skip the
1053 basename = unique_file_name (file_body)
1055 m = re.search ('filename="(.*?)"', o)
1057 basename = m.group (1)
1058 if not taken_file_names.has_key (basename):
1059 taken_file_names[basename] = 0
1061 taken_file_names[basename] = taken_file_names[basename] + 1
1062 basename = basename + "-%i" % taken_file_names[basename]
1063 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1064 needed_filetypes = ['tex']
1066 if format == 'html' or g_make_html:
1067 needed_filetypes.append ('eps')
1068 needed_filetypes.append ('png')
1069 if 'eps' in opts and not ('eps' in needed_filetypes):
1070 needed_filetypes.append ('eps')
1072 pathbase = os.path.join (g_outdir, basename)
1073 def must_rebuild (base, ext1, ext2):
1077 fp2 = base + '-page1' + ext2
1079 isfile2 = os.path.isfile (f2)
1081 if not isfile2 and os.path.isfile (fp2):
1083 isfile2 = os.path.isfile (fp2)
1085 if (os.path.isfile (f2) and isfile2 and
1086 os.stat (f1)[stat.ST_MTIME] >
1087 os.stat (f2)[stat.ST_MTIME]) or \
1093 if 'tex' in needed_filetypes and must_rebuild (pathbase, '.ly', '.tex'):
1095 if 'eps' in needed_filetypes and must_rebuild (pathbase, '.tex', '.eps'):
1097 if 'png' in needed_filetypes and must_rebuild (pathbase, '.eps', '.png'):
1100 return ('lilypond', body, opts, todo, basename)
1102 def format_lilypond_block (chunk):
1105 Figure out what should be left MAIN_STR (meant
1106 for the main file) from a lilypond chunk: process
1107 verbatim, and other options. Return: multiple chunks.
1115 (type, body, opts, todo, basename) = chunk
1116 assert type == 'lilypond'
1120 filename_chunk = None
1121 if 'printfilename' in opts:
1123 m= re.match ("filename=(.*)", o)
1125 template = get_output ("output-filename")
1126 b = basename + '.ly'
1127 human_base = os.path.basename (m.group (1))
1129 ## todo: include path, but strip
1130 ## first part of the path.
1131 filename_chunk = ('input', template % (human_base, b,human_base))
1135 if 'smallverbatim' in opts:
1136 newbody += output_verbatim (body, 1)
1137 elif 'verbatim' in opts:
1138 newbody += output_verbatim (body, 0)
1141 m = re.search ('intertext="(.*?)"', o)
1143 newbody = newbody + "\n"
1144 if format == 'texi':
1145 newbody = newbody + "@noindent\n"
1146 elif format == 'latex':
1147 newbody = newbody + "\\noindent\n"
1148 newbody = newbody + m.group (1) + "\n"
1150 if 'noinline' in opts:
1151 s = 'output-noinline'
1152 elif format == 'latex':
1157 s = 'output-latex-quoted'
1159 s = 'output-latex-noquote'
1160 elif format == 'texi':
1162 s = 'output-texi-quoted'
1164 s = 'output-texi-noquote'
1165 else: # format == 'html'
1168 def html_pages (basename):
1169 pat = os.path.join (g_outdir, "%s-page*.png"% basename)
1171 files = glob.glob (pat)
1174 template = '''<img align="center" valign="center"
1175 border="0" src="%s" alt="[picture of music]">'''
1179 files = [basename+'.png' ]
1181 files = map (os.path.basename, files)
1186 str = '<a href="%s.ly">%s</a>' % (basename, str)
1191 newbody = newbody + get_output (s) % {'fn': basename,
1192 'htmlimages': html_pages(basename)
1196 return_chunks += [filename_chunk]
1198 return_chunks += [('lilypond', newbody, opts, todo, basename)]
1200 return return_chunks
1202 def format_lilypond_output_bodies (chunks):
1206 if c[0] == 'lilypond':
1207 newchunks += format_lilypond_block (c)
1209 newchunks.append (c)
1215 def process_lilypond_blocks (chunks):#ugh rename
1217 # Count sections/chapters.
1219 if c[0] == 'lilypond':
1220 c = schedule_lilypond_block (c)
1221 elif c[0] == 'numcols':
1222 paperguru.m_num_cols = c[2]
1223 elif c[0] == 'multicols':
1224 paperguru.m_multicols = c[2]
1226 newchunks.append (c)
1230 def process_ly2dvi_blocks (chunks):
1232 def process_ly2dvi_block (chunk):
1235 Run ly2dvi script on filename specified in CHUNK.
1236 This is only supported for HTML output.
1238 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1239 a separate HTML file, and a title + preview in the main html file,
1240 linking to the menu.
1243 (tag, name, opts) = chunk
1244 assert format == 'html'
1245 (content, original_name) = find_file (name)
1247 original_name = os.path.basename (original_name)
1249 base = unique_file_name (content)
1250 outname = base + '.ly'
1251 changed = update_file (content, outname)
1253 preview = base + ".preview.png"
1254 preview_page = base + '-page1.png'
1256 if changed or not (os.path.isfile (preview) or
1257 os.path.isfile (preview_page)):
1259 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1261 ly.make_ps_images (base + '.ps')
1262 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1265 b = os.stat(fn)[stat.ST_SIZE]
1267 return '%d bytes' % b
1269 return '%d kb' % (b >> 10)
1271 return '%d mb' % (b >> 20)
1274 'pdf' : "Print (PDF, %s)",
1275 'ps.gz' : "Print (gzipped PostScript, %s)",
1276 'png' : "View (PNG, %s)",
1277 'midi' : "Listen (MIDI, %s)",
1278 'ly' : "View source code (%s)",
1282 page_files = glob.glob ('%s-page*.png' % base)
1284 for p in page_files:
1286 if os.path.isfile (p):
1288 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1290 menu += '<li><a href="%s">%s</a>' % (p, page)
1292 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1295 print 'checking,' , fn
1296 if not os.path.isfile (fn):
1299 entry = exts[e] % size_str (fn)
1301 ## TODO: do something like
1302 ## this for texinfo/latex as well ?
1304 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1307 explanatory_para = """The pictures are 90dpi
1308 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1309 use scalable fonts and should look OK at any resolution."""
1312 <title>LilyPond example %s</title>
1318 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1320 open (base + '.html','w'). write (separate_menu)
1322 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1324 return ('ly2dvi', inline_menu)
1328 if c[0] == 'ly2dvi':
1329 c = process_ly2dvi_block (c)
1330 newchunks.append (c)
1334 def compile_all_files (chunks):
1341 if c[0] != 'lilypond':
1351 if base + '.ly' not in tex:
1352 tex.append (base + '.ly')
1353 elif e == 'png' and g_do_pictures:
1359 # fixme: be sys-independent.
1361 if g_outdir and x[0] != '/' :
1362 x = os.path.join (g_here_dir, x)
1365 incs = map (incl_opt, include_path)
1366 lilyopts = string.join (incs)
1368 lilyopts += ' --dependencies'
1370 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1371 lilyopts += ' --header=texidoc'
1372 texfiles = string.join (tex)
1373 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1376 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1378 ly.system (cmd, ignore_error = 0, progress_p = 1)
1381 # Ugh, fixing up dependencies for .tex generation
1384 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1391 text=re.sub ('\n([^:\n]*):',
1392 '\n' + foutn + ':', text)
1398 cmd = r"latex '\nonstopmode \input %s'" % file
1399 # Ugh. (La)TeX writes progress and error messages on stdout
1400 # Redirect to stderr
1401 cmd += ' 1>/dev/stderr'
1403 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1406 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1410 def update_file (body, name):
1412 write the body if it has changed. Return whether BODY has changed.
1423 f = open (name , 'w')
1430 def write_deps (fn, target, chunks):
1432 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1433 f = open (os.path.join (g_outdir, fn), 'w')
1434 f.write ('%s%s: ' % (g_dep_prefix, target))
1435 for d in read_files:
1439 ## There used to be code to write .tex dependencies, but
1440 ## that is silly: lilypond-book has its own dependency scheme
1441 ## to ensure that all lily-XXX.tex files are there
1448 def check_texidoc (chunks):
1449 ## TODO: put file name in front of texidoc.
1453 if c[0] == 'lilypond':
1454 (type, body, opts, todo, basename) = c;
1455 pathbase = os.path.join (g_outdir, basename)
1456 if os.path.isfile (pathbase + '.texidoc') \
1457 and 'notexidoc' not in opts:
1458 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1463 ## what's this? Docme --hwn
1465 def fix_epswidth (chunks):
1468 if c[0] != 'lilypond' or 'eps' not in c[2]:
1469 newchunks.append (c)
1474 m = re.match ('magnification=([0-9.]+)', o)
1476 mag = string.atof (m.group (1))
1478 def replace_eps_dim (match, lmag = mag):
1479 filename = match.group (1)
1480 dims = bounding_box_dimensions (filename)
1482 return '%fpt' % (dims[0] *lmag)
1484 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1485 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1490 ##docme: why global?
1493 def do_file (input_filename):
1494 chunks = read_doc_file (input_filename)
1495 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1496 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1497 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1498 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1499 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1500 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1501 chunks = chop_chunks (chunks, 'numcols', do_columns)
1502 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1504 scan_preamble (chunks)
1505 chunks = process_lilypond_blocks (chunks)
1506 chunks = process_ly2dvi_blocks (chunks)
1509 global g_run_lilypond
1511 compile_all_files (chunks)
1512 chunks = fix_epswidth (chunks)
1515 chunks = format_lilypond_output_bodies (chunks)
1517 if format == 'texi':
1518 chunks = check_texidoc (chunks)
1522 chunks = completize_preamble (chunks)
1527 my_outname = outname
1528 elif input_filename == '-' or input_filename == "/dev/stdin":
1531 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1532 my_depname = my_outname + '.dep'
1534 if my_outname == '-' or my_outname == '/dev/stdout':
1540 foutn = os.path.join (g_outdir, my_outname)
1541 sys.stderr.write ("Writing `%s'\n" % foutn)
1542 fout = open (foutn, 'w')
1549 write_deps (my_depname, foutn, chunks)
1553 (sh, long) = ly.getopt_args (option_definitions)
1554 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1556 except getopt.error, msg:
1557 sys.stderr.write ('\n')
1558 ly.error (_ ("getopt says: `%s\'" % s))
1559 sys.stderr.write ('\n')
1568 if o == '--include' or o == '-I':
1569 include_path.append (a)
1570 elif o == '--version' or o == '-v':
1571 ly.identify (sys.stdout)
1573 elif o == '--verbose' or o == '-V':
1575 elif o == '--format' or o == '-f':
1577 if a == 'texi-html':
1580 elif o == '--outname' or o == '-o':
1583 sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1586 elif o == '--help' or o == '-h':
1589 elif o == '--no-lily' or o == '-n':
1591 elif o == '--preview-resolution':
1592 preview_resolution = string.atoi (a)
1593 elif o == '--dependencies' or o == '-M':
1595 elif o == '--default-music-fontsize':
1596 default_music_fontsize = string.atoi (a)
1597 elif o == '--default-lilypond-fontsize':
1598 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1599 default_music_fontsize = string.atoi (a)
1600 elif o == '--extra-options':
1602 elif o == '--force-music-fontsize':
1603 g_force_music_fontsize = string.atoi (a)
1604 elif o == '--force-lilypond-fontsize':
1605 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1606 g_force_music_fontsize = string.atoi (a)
1607 elif o == '--dep-prefix':
1609 elif o == '--no-pictures':
1611 elif o == '--no-music':
1613 elif o == '--outdir':
1615 elif o == '--warranty' or o == '-w':
1616 #status = os.system ('lilypond -w')
1621 ly.identify (sys.stderr)
1624 if os.path.isfile (g_outdir):
1625 error ("outdir is a file: %s" % g_outdir)
1626 if not os.path.exists (g_outdir):
1631 ly.error (_ ("no files specified on command line"))
1634 ly.setup_environment ()
1637 for input_filename in files:
1638 do_file (input_filename)
1642 # Petr, ik zou willen dat ik iets zinvoller deed,
1643 # maar wat ik kan ik doen, het verandert toch niets?