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[singleline,%s]{lilypond}
390 'output-filename' : r'''\verb+%s+:\\
395 # verbatim text is always finished with \n
396 'output-verbatim': r'''\begin{verbatim}
399 # verbatim text is always finished with \n
400 'output-small-verbatim': r'''{\small\begin{verbatim}
403 'output-default-post': "\\def\postLilyPondExample{}\n",
404 'output-default-pre': "\\def\preLilyPondExample{}\n",
405 'usepackage-graphics': '\\usepackage{graphics}\n',
406 'output-eps': '\\noindent\includegraphics{%(fn)s}',
407 'output-noinline': r'''
408 %% generated: %(fn)s.eps
410 'output-latex-quoted': r'''{\preLilyPondExample
412 \postLilyPondExample}''',
413 'output-latex-noquote': r'''{\parindent 0pt
416 \postLilyPondExample}''',
417 'pagebreak': r'\pagebreak',
424 'output-filename' : r'''
432 'output-lilypond-fragment': '''@lilypond[%s]
433 \context Staff\context Voice{ %s }
435 'output-noinline': r'''
436 @c generated: %(fn)s.png
439 # verbatim text is always finished with \n
440 'output-small-verbatim': r'''@smallexample
443 # verbatim text is always finished with \n
444 'output-verbatim': r'''@example
447 # do some tweaking: @ is needed in some ps stuff.
449 # ugh, the <p> below breaks inline images...
450 'output-texi-noquote': r'''@tex
462 'output-texi-quoted': r'''@quotation
479 def output_verbatim (body, small):
482 body = re.sub ('&', '&', body)
483 body = re.sub ('>', '>', body)
484 body = re.sub ('<', '<', body)
485 elif format == 'texi':
486 # clumsy workaround for python 2.2 pre bug.
487 body = re.sub ('@', '@@', body)
488 body = re.sub ('{', '@{', body)
489 body = re.sub ('}', '@}', body)
492 key = 'output-small-verbatim'
494 key = 'output-verbatim'
495 return get_output (key) % body
498 ################################################################
499 # Recognize special sequences in the input
502 # Warning: This uses extended regular expressions. Tread with care.
506 # (?P<name>regex) -- assign result of REGEX to NAME
507 # *? -- match non-greedily.
508 # (?m) -- multiline regex: make ^ and $ match at each line
509 # (?s) -- make the dot match all characters including newline
515 'preamble-end': no_match,
516 'landscape': no_match,
517 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
518 'verb': r'''(?P<code><pre>.*?</pre>)''',
519 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
520 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
521 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
522 'option-sep' : '\s*',
523 'intertext': r',?\s*intertext=\".*?\"',
524 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
525 'singleline-comment': no_match,
527 'multicols': no_match,
528 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
532 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
533 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
534 'option-sep' : ',\s*',
535 'header': r"\n*\\documentclass\s*(\[.*?\])?",
536 'preamble-end': r'(?P<code>\\begin\s*{document})',
537 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
538 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
539 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
540 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
541 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
542 'def-post-re': r"\\def\\postLilyPondExample",
543 'def-pre-re': r"\\def\\preLilyPondExample",
544 'usepackage-graphics': r"\usepackage\s*{graphics}",
545 'intertext': r',?\s*intertext=\".*?\"',
546 'multiline-comment': no_match,
547 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
548 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
549 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
554 # why do we have distinction between @mbinclude and @include?
557 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude\s+(?P<filename>\S*))',
560 'preamble-end': no_match,
561 'landscape': no_match,
562 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
563 'verb': r'''(?P<code>@code{.*?})''',
564 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
565 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
566 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
567 'option-sep' : ',\s*',
568 'intertext': r',?\s*intertext=\".*?\"',
569 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
570 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
572 'multicols': no_match,
578 for r in re_dict.keys ():
581 for k in olddict.keys ():
583 newdict[k] = re.compile (olddict[k])
585 print 'invalid regexp: %s' % olddict[k]
587 ## we'd like to catch and reraise a more
588 ## detailed error, but alas, the exceptions
589 ## changed across the 1.5/2.1 boundary.
605 def get_output (name):
606 return output_dict[format][name]
609 return re_dict[format][name]
611 def bounding_box_dimensions (fname):
613 fname = os.path.join (g_outdir, fname)
617 error ("Error opening `%s'" % fname)
619 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
622 gs = map (lambda x: string.atoi (x), s.groups ())
623 return (int (gs[2] - gs[0] + 0.5),
624 int (gs[3] - gs[1] + 0.5))
629 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
633 def compose_full_body (body, opts):
634 '''Construct the lilypond code to send to LilyPond.
635 Add stuff to BODY using OPTS as options.'''
636 music_size = default_music_fontsize
637 if g_force_music_fontsize:
638 music_size = g_force_music_fontsize
643 if not g_force_music_fontsize:
644 m = re.match ('([0-9]+)pt', o)
646 music_size = string.atoi (m.group (1))
648 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
650 f = float (m.group (1))
651 indent = 'indent = %f\\%s' % (f, m.group (2))
653 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
655 f = float (m.group (1))
656 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
658 if re.search ('\\\\score', body):
662 if 'fragment' in opts:
664 if 'nofragment' in opts:
667 if is_fragment and not 'multiline' in opts:
668 opts.append ('singleline')
670 if 'raggedright' in opts or 'singleline' in opts:
672 linewidth = 'raggedright = ##t'
674 indent = 'indent = 0.0\mm'
677 l = paperguru.get_linewidth ()
678 linewidth = 'linewidth = %f\pt' % l
680 if 'noindent' in opts:
681 indent = 'indent = 0.0\mm'
687 \remove Time_signature_engraver
693 m= re.search ('relative(.*)', o)
697 v = string.atoi (m.group (1))
704 pitch = pitch + '\,' * v
706 pitch = pitch + '\'' * v
708 body = '\\relative %s { %s }' % (pitch, body)
709 m =re.search ("filename=(.*)", o)
711 orig_name = m.group (1)
723 optstring = string.join (opts, ' ')
724 optstring = re.sub ('\n', ' ', optstring)
726 %% Generated automatically by: lilypond-book.py
728 \include "paper%d.ly"
734 ''' % (optstring, music_size, linewidth, indent, notime) + body
737 body = '\\renameinput \"%s\"\n%s' % (orig_name, body)
740 # ughUGH not original options
743 def scan_html_preamble (chunks):
746 def scan_latex_preamble (chunks):
747 # First we want to scan the \documentclass line
748 # it should be the first non-comment line.
749 # The only thing we really need to know about the \documentclass line
750 # is if there are one or two columns to begin with.
753 if chunks[idx][0] == 'ignore':
756 m = get_re ('header').match (chunks[idx][1])
758 error ("Latex documents must start with a \documentclass command")
760 options = re.split (r',\s*', m.group (1)[1:-1])
763 if 'twocolumn' in options:
764 paperguru.m_num_cols = 2
768 # Then we add everything before \begin{document} to
769 # paperguru.m_document_preamble so that we can later write this header
770 # to a temporary file in find_latex_dims() to find textwidth.
771 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
772 if chunks[idx] == 'ignore':
775 paperguru.m_document_preamble.append (chunks[idx][1])
778 if len (chunks) == idx:
779 error ("Didn't find end of preamble (\\begin{document})")
781 paperguru.find_latex_dims ()
783 def scan_texi_preamble (chunks):
784 # this is not bulletproof..., it checks the first 10 chunks
785 for c in chunks[:10]:
787 for s in ('afourpaper', 'afourwide', 'letterpaper',
788 'afourlatex', 'smallbook'):
789 if string.find (c[1], "@%s" % s) != -1:
790 paperguru.m_papersize = s
793 def scan_preamble (chunks):
796 scan_html_preamble (chunks)
797 elif format == 'latex':
798 scan_latex_preamble (chunks)
799 elif format == 'texi':
800 scan_texi_preamble (chunks)
803 def completize_preamble (chunks):
805 if format != 'latex':
807 pre_b = post_b = graphics_b = None
809 if chunk[0] == 'preamble-end':
811 if chunk[0] == 'input':
812 m = get_re ('def-pre-re').search (chunk[1])
815 if chunk[0] == 'input':
816 m = get_re ('def-post-re').search (chunk[1])
820 if chunk[0] == 'input':
821 m = get_re ('usepackage-graphics').search (chunk[1])
825 while x < len (chunks) and chunks[x][0] != 'preamble-end':
828 if x == len (chunks):
832 chunks.insert (x, ('input', get_output ('output-default-pre')))
834 chunks.insert (x, ('input', get_output ('output-default-post')))
836 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
842 def find_file (name):
844 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
848 return (sys.stdin.read (), '<stdin>')
851 for a in include_path:
853 nm = os.path.join (a, name)
856 read_files.append (nm)
861 sys.stderr.write ("Reading `%s'\n" % nm)
862 return (f.read (), nm)
864 error ("File not found `%s'\n" % name)
867 def do_ignore (match_object):
868 return [('ignore', match_object.group ('code'))]
869 def do_preamble_end (match_object):
870 return [('preamble-end', match_object.group ('code'))]
872 def make_verbatim (match_object):
873 return [('verbatim', match_object.group ('code'))]
875 def make_verb (match_object):
876 return [('verb', match_object.group ('code'))]
878 def do_include_file (m):
880 return [('input', get_output ('pagebreak'))] \
881 + read_doc_file (m.group ('filename')) \
882 + [('input', get_output ('pagebreak'))]
884 def do_input_file (m):
885 return read_doc_file (m.group ('filename'))
887 def make_lilypond (m):
888 if m.group ('options'):
889 options = m.group ('options')
892 return [('input', get_output ('output-lilypond-fragment') %
893 (options, m.group ('code')))]
895 def make_lilypond_file (m):
898 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
899 into a @lilypond .. @end lilypond block.
903 if m.group ('options'):
904 options = get_re ('option-sep').split (m.group ('options'))
907 (content, nm) = find_file (m.group ('filename'))
908 options.append ("filename=%s" % nm)
909 (path, base) = os.path.split (nm)
911 if path not in include_path:
912 include_path.append (path)
914 return [('lilypond', content, options)]
917 def make_ly2dvi_block (m):
920 Find <ly2dvifile .. >
923 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
926 def make_lilypond_block (m):
930 if m.group ('options'):
931 options = get_re ('option-sep').split (m.group ('options'))
934 options = filter (lambda s: s != '', options)
935 return [('lilypond', m.group ('code'), options)]
940 if format != 'latex':
942 if m.group ('num') == 'one':
943 return [('numcols', m.group ('code'), 1)]
944 if m.group ('num') == 'two':
945 return [('numcols', m.group ('code'), 2)]
947 def do_multicols (m):
949 if format != 'latex':
951 if m.group ('be') == 'begin':
952 return [('multicols', m.group ('code'), int (m.group ('num')))]
954 return [('multicols', m.group ('code'), 1)]
957 def chop_chunks (chunks, re_name, func, use_match=0):
963 m = get_re (re_name).search (str)
965 newchunks.append (('input', str))
969 newchunks.append (('input', str[:m.start ('match')]))
971 newchunks.append (('input', str[:m.start (0)]))
972 #newchunks.extend (func (m))
973 # python 1.5 compatible:
974 newchunks = newchunks + func (m)
975 str = str [m.end (0):]
980 def determine_format (str):
983 SIDE EFFECT! This sets FORMAT and PAPERGURU
989 html = re.search ('(?i)<[dh]tml', str[:200])
990 latex = re.search (r'''\\document''', str[:200])
991 texi = re.search ('@node|@setfilename', str[:200])
996 if html and not latex and not texi:
998 elif latex and not html and not texi:
1000 elif texi and not html and not latex:
1003 error ("can't determine format, please specify")
1007 if paperguru == None:
1008 if format == 'html':
1010 elif format == 'latex':
1012 elif format == 'texi':
1018 def read_doc_file (filename):
1019 '''Read the input file, find verbatim chunks and do \input and \include
1021 (str, path) = find_file (filename)
1022 determine_format (str)
1024 chunks = [('input', str)]
1026 # we have to check for verbatim before doing include,
1027 # because we don't want to include files that are mentioned
1028 # inside a verbatim environment
1029 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1031 chunks = chop_chunks (chunks, 'verb', make_verb)
1032 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1034 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1035 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1039 taken_file_names = {}
1041 def unique_file_name (body):
1042 return 'lily-' + `abs (hash (body))`
1044 def schedule_lilypond_block (chunk):
1045 '''Take the body and options from CHUNK, figure out how the
1046 real .ly should look. The .ly is written, and scheduled in
1049 Return: a single chunk.
1051 The chunk pertaining to the lilypond output
1052 has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE),
1053 where TODO has format [basename, extension, extension, ... ]
1056 (type, body, opts) = chunk
1057 assert type == 'lilypond'
1058 file_body = compose_full_body (body, opts)
1059 ## Hmm, we should hash only lilypond source, and skip the
1062 basename = unique_file_name (file_body)
1064 m = re.search ('filename="(.*?)"', o)
1066 basename = m.group (1)
1067 if not taken_file_names.has_key (basename):
1068 taken_file_names[basename] = 0
1070 taken_file_names[basename] = taken_file_names[basename] + 1
1071 basename = basename + "-t%i" % taken_file_names[basename]
1072 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1073 needed_filetypes = ['tex']
1075 if format == 'html' or g_make_html:
1076 needed_filetypes.append ('eps')
1077 needed_filetypes.append ('png')
1078 if 'eps' in opts and not ('eps' in needed_filetypes):
1079 needed_filetypes.append ('eps')
1081 pathbase = os.path.join (g_outdir, basename)
1082 def must_rebuild (base, ext1, ext2):
1086 fp2 = base + '-page1' + ext2
1088 isfile2 = os.path.isfile (f2)
1090 if not isfile2 and os.path.isfile (fp2):
1092 isfile2 = os.path.isfile (fp2)
1094 if (os.path.isfile (f2) and isfile2 and
1095 os.stat (f1)[stat.ST_MTIME] >
1096 os.stat (f2)[stat.ST_MTIME]) or \
1102 if 'tex' in needed_filetypes and must_rebuild (pathbase, '.ly', '.tex'):
1104 if 'eps' in needed_filetypes and must_rebuild (pathbase, '.tex', '.eps'):
1106 if 'png' in needed_filetypes and must_rebuild (pathbase, '.eps', '.png'):
1109 return ('lilypond', body, opts, todo, basename)
1111 def format_lilypond_block (chunk):
1114 Figure out what should be left MAIN_STR (meant
1115 for the main file) from a lilypond chunk: process
1116 verbatim, and other options. Return: multiple chunks.
1124 (type, body, opts, todo, basename) = chunk
1125 assert type == 'lilypond'
1129 filename_chunk = None
1130 if 'printfilename' in opts:
1132 m= re.match ("filename=(.*)", o)
1134 template = get_output ("output-filename")
1135 b = basename + '.ly'
1136 human_base = os.path.basename (m.group (1))
1138 ## todo: include path, but strip
1139 ## first part of the path.
1140 filename_chunk = ('input', template % (human_base, b,human_base))
1144 if 'smallverbatim' in opts:
1145 newbody += output_verbatim (body, 1)
1146 elif 'verbatim' in opts:
1147 newbody += output_verbatim (body, 0)
1150 m = re.search ('intertext="(.*?)"', o)
1152 newbody = newbody + "\n"
1153 if format == 'texi':
1154 newbody = newbody + "@noindent\n"
1155 elif format == 'latex':
1156 newbody = newbody + "\\noindent\n"
1157 newbody = newbody + m.group (1) + "\n"
1159 if 'noinline' in opts:
1160 s = 'output-noinline'
1161 elif format == 'latex':
1163 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 -Ppdf -u+lilypond.map -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?