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
695 m= re.search ('relative(.*)', o)
699 v = string.atoi (m.group (1))
706 pitch = pitch + '\,' * v
708 pitch = pitch + '\'' * v
710 body = '\\relative %s { %s }' % (pitch, body)
711 m =re.search ("filename=(.*)", o)
713 orig_name = m.group (1)
725 optstring = string.join (opts, ' ')
726 optstring = re.sub ('\n', ' ', optstring)
728 %% Generated automatically by: lilypond-book.py
730 \include "paper%d.ly"
736 ''' % (optstring, music_size, linewidth, indent, notime) + body
739 body = '\\renameinput \"%s\"\n%s' % (orig_name, body)
742 # ughUGH not original options
745 def scan_html_preamble (chunks):
748 def scan_latex_preamble (chunks):
749 # First we want to scan the \documentclass line
750 # it should be the first non-comment line.
751 # The only thing we really need to know about the \documentclass line
752 # is if there are one or two columns to begin with.
755 if chunks[idx][0] == 'ignore':
758 m = get_re ('header').match (chunks[idx][1])
760 error ("Latex documents must start with a \documentclass command")
762 options = re.split (',[\n \t]*', m.group (1)[1:-1])
765 if 'twocolumn' in options:
766 paperguru.m_num_cols = 2
770 # Then we add everything before \begin{document} to
771 # paperguru.m_document_preamble so that we can later write this header
772 # to a temporary file in find_latex_dims() to find textwidth.
773 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
774 if chunks[idx] == 'ignore':
777 paperguru.m_document_preamble.append (chunks[idx][1])
780 if len (chunks) == idx:
781 error ("Didn't find end of preamble (\\begin{document})")
783 paperguru.find_latex_dims ()
785 def scan_texi_preamble (chunks):
786 # this is not bulletproof..., it checks the first 10 chunks
787 for c in chunks[:10]:
789 for s in ('afourpaper', 'afourwide', 'letterpaper',
790 'afourlatex', 'smallbook'):
791 if string.find (c[1], "@%s" % s) != -1:
792 paperguru.m_papersize = s
795 def scan_preamble (chunks):
798 scan_html_preamble (chunks)
799 elif format == 'latex':
800 scan_latex_preamble (chunks)
801 elif format == 'texi':
802 scan_texi_preamble (chunks)
805 def completize_preamble (chunks):
807 if format != 'latex':
809 pre_b = post_b = graphics_b = None
811 if chunk[0] == 'preamble-end':
813 if chunk[0] == 'input':
814 m = get_re ('def-pre-re').search (chunk[1])
817 if chunk[0] == 'input':
818 m = get_re ('def-post-re').search (chunk[1])
822 if chunk[0] == 'input':
823 m = get_re ('usepackage-graphics').search (chunk[1])
827 while x < len (chunks) and chunks[x][0] != 'preamble-end':
830 if x == len (chunks):
834 chunks.insert (x, ('input', get_output ('output-default-pre')))
836 chunks.insert (x, ('input', get_output ('output-default-post')))
838 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
844 def find_file (name):
846 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
850 return (sys.stdin.read (), '<stdin>')
853 for a in include_path:
855 nm = os.path.join (a, name)
858 read_files.append (nm)
863 sys.stderr.write ("Reading `%s'\n" % nm)
864 return (f.read (), nm)
866 error ("File not found `%s'\n" % name)
869 def do_ignore (match_object):
870 return [('ignore', match_object.group ('code'))]
871 def do_preamble_end (match_object):
872 return [('preamble-end', match_object.group ('code'))]
874 def make_verbatim (match_object):
875 return [('verbatim', match_object.group ('code'))]
877 def make_verb (match_object):
878 return [('verb', match_object.group ('code'))]
880 def do_include_file (m):
882 return [('input', get_output ('pagebreak'))] \
883 + read_doc_file (m.group ('filename')) \
884 + [('input', get_output ('pagebreak'))]
886 def do_input_file (m):
887 return read_doc_file (m.group ('filename'))
889 def make_lilypond (m):
890 if m.group ('options'):
891 options = m.group ('options')
894 return [('input', get_output ('output-lilypond-fragment') %
895 (options, m.group ('code')))]
897 def make_lilypond_file (m):
900 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
901 into a @lilypond .. @end lilypond block.
905 if m.group ('options'):
906 options = get_re ('option-sep').split (m.group ('options'))
909 (content, nm) = find_file (m.group ('filename'))
910 options.append ("filename=%s" % nm)
913 return [('lilypond', content, options)]
916 def make_ly2dvi_block (m):
919 Find <ly2dvifile .. >
922 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
925 def make_lilypond_block (m):
929 if m.group ('options'):
930 options = get_re ('option-sep').split (m.group ('options'))
933 options = filter (lambda s: s != '', options)
934 return [('lilypond', m.group ('code'), options)]
939 if format != 'latex':
941 if m.group ('num') == 'one':
942 return [('numcols', m.group ('code'), 1)]
943 if m.group ('num') == 'two':
944 return [('numcols', m.group ('code'), 2)]
946 def do_multicols (m):
948 if format != 'latex':
950 if m.group ('be') == 'begin':
951 return [('multicols', m.group ('code'), int (m.group ('num')))]
953 return [('multicols', m.group ('code'), 1)]
956 def chop_chunks (chunks, re_name, func, use_match=0):
962 m = get_re (re_name).search (str)
964 newchunks.append (('input', str))
968 newchunks.append (('input', str[:m.start ('match')]))
970 newchunks.append (('input', str[:m.start (0)]))
971 #newchunks.extend (func (m))
972 # python 1.5 compatible:
973 newchunks = newchunks + func (m)
974 str = str [m.end (0):]
979 def determine_format (str):
982 SIDE EFFECT! This sets FORMAT and PAPERGURU
988 html = re.search ('(?i)<[dh]tml', str[:200])
989 latex = re.search (r'''\\document''', str[:200])
990 texi = re.search ('@node|@setfilename', str[:200])
995 if html and not latex and not texi:
997 elif latex and not html and not texi:
999 elif texi and not html and not latex:
1002 error ("can't determine format, please specify")
1006 if paperguru == None:
1007 if format == 'html':
1009 elif format == 'latex':
1011 elif format == 'texi':
1017 def read_doc_file (filename):
1018 '''Read the input file, find verbatim chunks and do \input and \include
1020 (str, path) = find_file (filename)
1021 determine_format (str)
1023 chunks = [('input', str)]
1025 # we have to check for verbatim before doing include,
1026 # because we don't want to include files that are mentioned
1027 # inside a verbatim environment
1028 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1030 chunks = chop_chunks (chunks, 'verb', make_verb)
1031 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1033 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1034 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1038 taken_file_names = {}
1040 def unique_file_name (body):
1041 return 'lily-' + `abs (hash (body))`
1043 def schedule_lilypond_block (chunk):
1044 '''Take the body and options from CHUNK, figure out how the
1045 real .ly should look. The .ly is written, and scheduled in
1048 Return: a single chunk.
1050 The chunk pertaining to the lilypond output
1051 has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE),
1052 where TODO has format [basename, extension, extension, ... ]
1055 (type, body, opts) = chunk
1056 assert type == 'lilypond'
1057 file_body = compose_full_body (body, opts)
1058 ## Hmm, we should hash only lilypond source, and skip the
1061 basename = unique_file_name (file_body)
1063 m = re.search ('filename="(.*?)"', o)
1065 basename = m.group (1)
1066 if not taken_file_names.has_key (basename):
1067 taken_file_names[basename] = 0
1069 taken_file_names[basename] = taken_file_names[basename] + 1
1070 basename = basename + "-%i" % taken_file_names[basename]
1071 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1072 needed_filetypes = ['tex']
1074 if format == 'html' or g_make_html:
1075 needed_filetypes.append ('eps')
1076 needed_filetypes.append ('png')
1077 if 'eps' in opts and not ('eps' in needed_filetypes):
1078 needed_filetypes.append ('eps')
1080 pathbase = os.path.join (g_outdir, basename)
1081 def must_rebuild (base, ext1, ext2):
1085 fp2 = base + '-page1' + ext2
1087 isfile2 = os.path.isfile (f2)
1089 if not isfile2 and os.path.isfile (fp2):
1091 isfile2 = os.path.isfile (fp2)
1093 if (os.path.isfile (f2) and isfile2 and
1094 os.stat (f1)[stat.ST_MTIME] >
1095 os.stat (f2)[stat.ST_MTIME]) or \
1101 if 'tex' in needed_filetypes and must_rebuild (pathbase, '.ly', '.tex'):
1103 if 'eps' in needed_filetypes and must_rebuild (pathbase, '.tex', '.eps'):
1105 if 'png' in needed_filetypes and must_rebuild (pathbase, '.eps', '.png'):
1108 return ('lilypond', body, opts, todo, basename)
1110 def format_lilypond_block (chunk):
1113 Figure out what should be left MAIN_STR (meant
1114 for the main file) from a lilypond chunk: process
1115 verbatim, and other options. Return: multiple chunks.
1123 (type, body, opts, todo, basename) = chunk
1124 assert type == 'lilypond'
1128 filename_chunk = None
1129 if 'printfilename' in opts:
1131 m= re.match ("filename=(.*)", o)
1133 template = get_output ("output-filename")
1134 b = basename + '.ly'
1135 human_base = os.path.basename (m.group (1))
1137 ## todo: include path, but strip
1138 ## first part of the path.
1139 filename_chunk = ('input', template % (human_base, b,human_base))
1143 if 'smallverbatim' in opts:
1144 newbody += output_verbatim (body, 1)
1145 elif 'verbatim' in opts:
1146 newbody += output_verbatim (body, 0)
1149 m = re.search ('intertext="(.*?)"', o)
1151 newbody = newbody + "\n"
1152 if format == 'texi':
1153 newbody = newbody + "@noindent\n"
1154 elif format == 'latex':
1155 newbody = newbody + "\\noindent\n"
1156 newbody = newbody + m.group (1) + "\n"
1158 if 'noinline' in opts:
1159 s = 'output-noinline'
1160 elif format == 'latex':
1165 s = 'output-latex-quoted'
1167 s = 'output-latex-noquote'
1168 elif format == 'texi':
1170 s = 'output-texi-quoted'
1172 s = 'output-texi-noquote'
1173 else: # format == 'html'
1176 def html_pages (basename):
1177 pat = os.path.join (g_outdir, "%s-page*.png"% basename)
1179 files = glob.glob (pat)
1182 template = '''<img align="center" valign="center"
1183 border="0" src="%s" alt="[picture of music]">'''
1187 files = [basename+'.png' ]
1189 files = map (os.path.basename, files)
1194 str = '<a href="%s.ly">%s</a>' % (basename, str)
1199 newbody = newbody + get_output (s) % {'fn': basename,
1200 'htmlimages': html_pages(basename)
1204 return_chunks += [filename_chunk]
1206 return_chunks += [('lilypond', newbody, opts, todo, basename)]
1208 return return_chunks
1210 def format_lilypond_output_bodies (chunks):
1214 if c[0] == 'lilypond':
1215 newchunks += format_lilypond_block (c)
1217 newchunks.append (c)
1223 def process_lilypond_blocks (chunks):#ugh rename
1225 # Count sections/chapters.
1227 if c[0] == 'lilypond':
1228 c = schedule_lilypond_block (c)
1229 elif c[0] == 'numcols':
1230 paperguru.m_num_cols = c[2]
1231 elif c[0] == 'multicols':
1232 paperguru.m_multicols = c[2]
1234 newchunks.append (c)
1238 def process_ly2dvi_blocks (chunks):
1240 def process_ly2dvi_block (chunk):
1243 Run ly2dvi script on filename specified in CHUNK.
1244 This is only supported for HTML output.
1246 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1247 a separate HTML file, and a title + preview in the main html file,
1248 linking to the menu.
1251 (tag, name, opts) = chunk
1252 assert format == 'html'
1253 (content, original_name) = find_file (name)
1255 original_name = os.path.basename (original_name)
1257 base = unique_file_name (content)
1258 outname = base + '.ly'
1259 changed = update_file (content, outname)
1261 preview = base + ".preview.png"
1262 preview_page = base + '-page1.png'
1264 if changed or not (os.path.isfile (preview) or
1265 os.path.isfile (preview_page)):
1267 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1269 ly.make_ps_images (base + '.ps')
1270 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1273 b = os.stat(fn)[stat.ST_SIZE]
1275 return '%d bytes' % b
1277 return '%d kb' % (b >> 10)
1279 return '%d mb' % (b >> 20)
1282 'pdf' : "Print (PDF, %s)",
1283 'ps.gz' : "Print (gzipped PostScript, %s)",
1284 'png' : "View (PNG, %s)",
1285 'midi' : "Listen (MIDI, %s)",
1286 'ly' : "View source code (%s)",
1290 page_files = glob.glob ('%s-page*.png' % base)
1292 for p in page_files:
1294 if os.path.isfile (p):
1296 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1298 menu += '<li><a href="%s">%s</a>' % (p, page)
1300 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1303 print 'checking,' , fn
1304 if not os.path.isfile (fn):
1307 entry = exts[e] % size_str (fn)
1309 ## TODO: do something like
1310 ## this for texinfo/latex as well ?
1312 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1315 explanatory_para = """The pictures are 90dpi
1316 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1317 use scalable fonts and should look OK at any resolution."""
1320 <title>LilyPond example %s</title>
1326 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1328 open (base + '.html','w'). write (separate_menu)
1330 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1332 return ('ly2dvi', inline_menu)
1336 if c[0] == 'ly2dvi':
1337 c = process_ly2dvi_block (c)
1338 newchunks.append (c)
1342 def compile_all_files (chunks):
1349 if c[0] != 'lilypond':
1359 if base + '.ly' not in tex:
1360 tex.append (base + '.ly')
1361 elif e == 'png' and g_do_pictures:
1367 # fixme: be sys-independent.
1369 if g_outdir and x[0] != '/' :
1370 x = os.path.join (g_here_dir, x)
1373 incs = map (incl_opt, include_path)
1374 lilyopts = string.join (incs)
1376 lilyopts += ' --dependencies'
1378 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1379 lilyopts += ' --header=texidoc'
1380 texfiles = string.join (tex)
1381 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1384 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1386 ly.system (cmd, ignore_error = 0, progress_p = 1)
1389 # Ugh, fixing up dependencies for .tex generation
1392 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1399 text=re.sub ('\n([^:\n]*):',
1400 '\n' + foutn + ':', text)
1406 cmd = r"latex '\nonstopmode \input %s'" % file
1407 # Ugh. (La)TeX writes progress and error messages on stdout
1408 # Redirect to stderr
1409 cmd += ' 1>/dev/stderr'
1411 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1414 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1418 def update_file (body, name):
1420 write the body if it has changed. Return whether BODY has changed.
1431 f = open (name , 'w')
1438 def write_deps (fn, target, chunks):
1440 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1441 f = open (os.path.join (g_outdir, fn), 'w')
1442 f.write ('%s%s: ' % (g_dep_prefix, target))
1443 for d in read_files:
1447 ## There used to be code to write .tex dependencies, but
1448 ## that is silly: lilypond-book has its own dependency scheme
1449 ## to ensure that all lily-XXX.tex files are there
1456 def check_texidoc (chunks):
1457 ## TODO: put file name in front of texidoc.
1461 if c[0] == 'lilypond':
1462 (type, body, opts, todo, basename) = c;
1463 pathbase = os.path.join (g_outdir, basename)
1464 if os.path.isfile (pathbase + '.texidoc') \
1465 and 'notexidoc' not in opts:
1466 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1471 ## what's this? Docme --hwn
1473 def fix_epswidth (chunks):
1476 if c[0] != 'lilypond' or 'eps' not in c[2]:
1477 newchunks.append (c)
1482 m = re.match ('magnification=([0-9.]+)', o)
1484 mag = string.atof (m.group (1))
1486 def replace_eps_dim (match, lmag = mag):
1487 filename = match.group (1)
1488 dims = bounding_box_dimensions (filename)
1490 return '%fpt' % (dims[0] *lmag)
1492 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1493 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1498 ##docme: why global?
1501 def do_file (input_filename):
1502 chunks = read_doc_file (input_filename)
1503 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1504 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1505 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1506 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1507 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1508 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1509 chunks = chop_chunks (chunks, 'numcols', do_columns)
1510 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1512 scan_preamble (chunks)
1513 chunks = process_lilypond_blocks (chunks)
1514 chunks = process_ly2dvi_blocks (chunks)
1517 global g_run_lilypond
1519 compile_all_files (chunks)
1520 chunks = fix_epswidth (chunks)
1523 chunks = format_lilypond_output_bodies (chunks)
1525 if format == 'texi':
1526 chunks = check_texidoc (chunks)
1530 chunks = completize_preamble (chunks)
1535 my_outname = outname
1536 elif input_filename == '-' or input_filename == "/dev/stdin":
1539 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1540 my_depname = my_outname + '.dep'
1542 if my_outname == '-' or my_outname == '/dev/stdout':
1548 foutn = os.path.join (g_outdir, my_outname)
1549 sys.stderr.write ("Writing `%s'\n" % foutn)
1550 fout = open (foutn, 'w')
1557 write_deps (my_depname, foutn, chunks)
1561 (sh, long) = ly.getopt_args (option_definitions)
1562 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1564 except getopt.error, msg:
1565 sys.stderr.write ('\n')
1566 ly.error (_ ("getopt says: `%s\'" % s))
1567 sys.stderr.write ('\n')
1576 if o == '--include' or o == '-I':
1577 include_path.append (a)
1578 elif o == '--version' or o == '-v':
1579 ly.identify (sys.stdout)
1581 elif o == '--verbose' or o == '-V':
1583 elif o == '--format' or o == '-f':
1585 if a == 'texi-html':
1588 elif o == '--outname' or o == '-o':
1591 sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1594 elif o == '--help' or o == '-h':
1597 elif o == '--no-lily' or o == '-n':
1599 elif o == '--preview-resolution':
1600 preview_resolution = string.atoi (a)
1601 elif o == '--dependencies' or o == '-M':
1603 elif o == '--default-music-fontsize':
1604 default_music_fontsize = string.atoi (a)
1605 elif o == '--default-lilypond-fontsize':
1606 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1607 default_music_fontsize = string.atoi (a)
1608 elif o == '--extra-options':
1610 elif o == '--force-music-fontsize':
1611 g_force_music_fontsize = string.atoi (a)
1612 elif o == '--force-lilypond-fontsize':
1613 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1614 g_force_music_fontsize = string.atoi (a)
1615 elif o == '--dep-prefix':
1617 elif o == '--no-pictures':
1619 elif o == '--no-music':
1621 elif o == '--outdir':
1623 elif o == '--warranty' or o == '-w':
1624 #status = os.system ('lilypond -w')
1629 ly.identify (sys.stderr)
1632 if os.path.isfile (g_outdir):
1633 error ("outdir is a file: %s" % g_outdir)
1634 if not os.path.exists (g_outdir):
1639 ly.error (_ ("no files specified on command line"))
1642 ly.setup_environment ()
1645 for input_filename in files:
1646 do_file (input_filename)
1650 # Petr, ik zou willen dat ik iets zinvoller deed,
1651 # maar wat ik kan ik doen, het verandert toch niets?