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-bin')
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-bin'
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 = '(( %s >&2 ) >&- )' % cmd
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 = '(( %s >&2 ) >&- )' % cmd
1412 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1415 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1419 def update_file (body, name):
1421 write the body if it has changed. Return whether BODY has changed.
1432 f = open (name , 'w')
1439 def write_deps (fn, target, chunks):
1441 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1442 f = open (os.path.join (g_outdir, fn), 'w')
1443 f.write ('%s%s: ' % (g_dep_prefix, target))
1444 for d in read_files:
1448 ## There used to be code to write .tex dependencies, but
1449 ## that is silly: lilypond-book has its own dependency scheme
1450 ## to ensure that all lily-XXX.tex files are there
1457 def check_texidoc (chunks):
1458 ## TODO: put file name in front of texidoc.
1462 if c[0] == 'lilypond':
1463 (type, body, opts, todo, basename) = c;
1464 pathbase = os.path.join (g_outdir, basename)
1465 if os.path.isfile (pathbase + '.texidoc') \
1466 and 'notexidoc' not in opts:
1467 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1472 ## what's this? Docme --hwn
1474 def fix_epswidth (chunks):
1477 if c[0] != 'lilypond' or 'eps' not in c[2]:
1478 newchunks.append (c)
1483 m = re.match ('magnification=([0-9.]+)', o)
1485 mag = string.atof (m.group (1))
1487 def replace_eps_dim (match, lmag = mag):
1488 filename = match.group (1)
1489 dims = bounding_box_dimensions (filename)
1491 return '%fpt' % (dims[0] *lmag)
1493 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1494 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1499 ##docme: why global?
1502 def do_file (input_filename):
1503 chunks = read_doc_file (input_filename)
1504 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1505 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1506 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1507 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1508 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1509 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1510 chunks = chop_chunks (chunks, 'numcols', do_columns)
1511 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1513 scan_preamble (chunks)
1514 chunks = process_lilypond_blocks (chunks)
1515 chunks = process_ly2dvi_blocks (chunks)
1518 global g_run_lilypond
1520 compile_all_files (chunks)
1521 chunks = fix_epswidth (chunks)
1524 chunks = format_lilypond_output_bodies (chunks)
1526 if format == 'texi':
1527 chunks = check_texidoc (chunks)
1531 chunks = completize_preamble (chunks)
1536 my_outname = outname
1537 elif input_filename == '-' or input_filename == "/dev/stdin":
1540 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1541 my_depname = my_outname + '.dep'
1543 if my_outname == '-' or my_outname == '/dev/stdout':
1549 foutn = os.path.join (g_outdir, my_outname)
1550 sys.stderr.write ("Writing `%s'\n" % foutn)
1551 fout = open (foutn, 'w')
1558 write_deps (my_depname, foutn, chunks)
1562 (sh, long) = ly.getopt_args (option_definitions)
1563 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1565 except getopt.error, msg:
1566 sys.stderr.write ('\n')
1567 ly.error (_ ("getopt says: `%s\'" % s))
1568 sys.stderr.write ('\n')
1577 if o == '--include' or o == '-I':
1578 include_path.append (a)
1579 elif o == '--version' or o == '-v':
1580 ly.identify (sys.stdout)
1582 elif o == '--verbose' or o == '-V':
1584 elif o == '--format' or o == '-f':
1586 if a == 'texi-html':
1589 elif o == '--outname' or o == '-o':
1592 sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1595 elif o == '--help' or o == '-h':
1598 elif o == '--no-lily' or o == '-n':
1600 elif o == '--preview-resolution':
1601 preview_resolution = string.atoi (a)
1602 elif o == '--dependencies' or o == '-M':
1604 elif o == '--default-music-fontsize':
1605 default_music_fontsize = string.atoi (a)
1606 elif o == '--default-lilypond-fontsize':
1607 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1608 default_music_fontsize = string.atoi (a)
1609 elif o == '--extra-options':
1611 elif o == '--force-music-fontsize':
1612 g_force_music_fontsize = string.atoi (a)
1613 elif o == '--force-lilypond-fontsize':
1614 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1615 g_force_music_fontsize = string.atoi (a)
1616 elif o == '--dep-prefix':
1618 elif o == '--no-pictures':
1620 elif o == '--no-music':
1622 elif o == '--outdir':
1624 elif o == '--warranty' or o == '-w':
1625 #status = os.system ('lilypond -w')
1630 ly.identify (sys.stderr)
1633 if os.path.isfile (g_outdir):
1634 error ("outdir is a file: %s" % g_outdir)
1635 if not os.path.exists (g_outdir):
1640 ly.error (_ ("no files specified on command line"))
1643 ly.setup_environment ()
1646 for input_filename in files:
1647 do_file (input_filename)
1651 # Petr, ik zou willen dat ik iets zinvoller deed,
1652 # maar wat ik kan ik doen, het verandert toch niets?