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 ## do -P or -p by default?
117 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
118 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
119 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
120 'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
122 option_definitions = [
123 (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
124 (_ ("DIM"), '', 'default-music-fontsize', _ ("default fontsize for music. DIM is assumed to be in points")),
125 (_ ("DIM"), '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
126 (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
127 (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed to be in points")),
128 (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
129 ('', 'h', 'help', _ ("print this help")),
130 (_ ("DIR"), 'I', 'include', _ ("include path")),
131 ('', 'M', 'dependencies', _ ("write dependencies")),
132 (_ ("PREF"), '', 'dep-prefix', _ ("prepend PREF before each -M dependency")),
133 ('', 'n', 'no-lily', _ ("don't run lilypond")),
134 ('', '', 'no-pictures', _ ("don't generate pictures")),
135 ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
136 (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
137 (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
138 (_ ('RES'), '', 'preview-resolution',
139 _ ("set the resolution of the preview to RES")),
140 ('', 'V', 'verbose', _ ("be verbose")),
141 ('', 'v', 'version', _ ("print version information")),
142 ('', 'w', 'warranty', _ ("show warranty and copyright")),
145 # format specific strings, ie. regex-es for input, and % strings for output
149 include_path = [os.getcwd ()]
151 #lilypond_binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
153 lilypond_binary = os.path.join ('@bindir@', 'lilypond-bin')
155 # only use installed binary when we're installed too.
156 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
157 lilypond_binary = 'lilypond-bin'
161 ly2dvi_binary = os.path.join ('@bindir@', 'ly2dvi')
163 # only use installed binary when we're installed too.
164 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
165 ly2dvi_binary = 'ly2dvi'
170 g_here_dir = os.getcwd ()
173 g_force_music_fontsize = 0
182 default_music_fontsize = 16
183 default_text_fontsize = 12
186 ################################################################
187 # Dimension handling for LaTeX.
191 self.m_document_preamble = []
195 def find_latex_dims (self):
197 fname = os.path.join (g_outdir, "lily-tmp.tex")
199 fname = "lily-tmp.tex"
201 f = open (fname, "w")
203 error ("Error creating temporary file '%s'" % fname)
205 for s in self.m_document_preamble:
210 \typeout{\columnsep \the\columnsep}
211 \typeout{\textwidth \the\textwidth}
216 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
218 cmd = "latex '\\nonstopmode \input %s'" % fname
219 # Ugh. (La)TeX writes progress and error messages on stdout
221 cmd = '(( %s >&2 ) >&- )' % cmd
222 status = ly.system (cmd, ignore_error = 1)
223 signal = 0xf & status
224 exit_status = status >> 8
227 ly.error (_ ("LaTeX failed."))
228 ly.error (_ ("The error log is as follows:"))
232 lns = open ('lily-tmp.log').readlines ()
237 sys.stderr.write (ln)
238 if re.match ('^!', ln):
245 countdown = countdown -1
247 sys.stderr.write (" ... (further messages elided)...\n")
250 lns = open ('lily-tmp.log').readlines ()
252 ln = string.strip (ln)
253 m = re_dim.match (ln)
255 if m.groups ()[0] in ('textwidth', 'columnsep'):
256 self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
260 os.remove (os.path.splitext (fname)[0]+".aux")
261 os.remove (os.path.splitext (fname)[0]+".log")
265 if not self.__dict__.has_key ('m_textwidth'):
268 def get_linewidth (self):
269 if self.m_num_cols == 1:
272 w = (self.m_textwidth - self.m_columnsep)/2
273 if self.m_multicols > 1:
274 return (w - self.m_columnsep* (self.m_multicols-1)) \
281 self.m_papersize = 'letterpaper'
283 def get_linewidth (self):
284 return html_linewidths[self.m_papersize][self.m_fontsize]
288 self.m_papersize = 'letterpaper'
290 def get_linewidth (self):
291 return texi_linewidths[self.m_papersize][self.m_fontsize]
297 def em2pt (x, fontsize = 10):
298 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
299 def ex2pt (x, fontsize = 10):
300 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
305 dimension_conversion_dict ={
307 'cm': lambda x: mm2pt (10*x),
314 # Convert numeric values, with or without specific dimension, to floats.
316 def conv_dimen_to_float (value):
317 if type (value) == type (""):
318 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
321 num = string.atof (m.group (1))
322 conv = dimension_conversion_dict[m.group (2)]
326 elif re.match ("^[0-9.]+$",value):
327 value = float (value)
332 'afourpaper': {12: mm2pt (160)},
333 'afourwide': {12: in2pt (6.5)},
334 'afourlatex': {12: mm2pt (150)},
335 'smallbook': {12: in2pt (5)},
336 'letterpaper': {12: in2pt (6)}}
339 'afourpaper': {12: mm2pt (160)},
340 'afourwide': {12: in2pt (6.5)},
341 'afourlatex': {12: mm2pt (150)},
342 'smallbook': {12: in2pt (5)},
343 'letterpaper': {12: in2pt (6)}}
346 ################################################################
347 # How to output various structures.
353 'output-filename' : r'''
356 <pre>%s</pre></a>:''',
357 'output-lilypond-fragment': '''<lilypond%s>
358 \context Staff\context Voice{ %s }
360 'output-noinline': r'''
361 <!-- generated: %(fn)s.png !-->
365 # Verbatim text is always finished with \n. FIXME: For HTML,
366 # this newline should be removed.
367 'output-verbatim': r'''<pre>
369 # Verbatim text is always finished with \n. FIXME: For HTML,
370 # this newline should be removed.
371 'output-small-verbatim': r'''<font size=-1><pre>
373 ## Ugh we need to differentiate on origin:
374 ## lilypond-block origin wants an extra <p>, but
375 ## inline music doesn't.
376 ## possibly other center options?
384 'output-lilypond-fragment' : r'''\begin[singleline,%s]{lilypond}
389 'output-filename' : r'''\verb+%s+:\\
394 # verbatim text is always finished with \n
395 'output-verbatim': r'''\begin{verbatim}
398 # verbatim text is always finished with \n
399 'output-small-verbatim': r'''{\small\begin{verbatim}
402 'output-default-post': "\\def\postLilyPondExample{}\n",
403 'output-default-pre': "\\def\preLilyPondExample{}\n",
404 'usepackage-graphics': '\\usepackage{graphics}\n',
405 'output-eps': '\\noindent\includegraphics{%(fn)s}',
406 'output-noinline': r'''
407 %% generated: %(fn)s.eps
409 'output-latex-quoted': r'''{\preLilyPondExample
412 \postLilyPondExample}''',
413 'output-latex-noquote': r'''{\parindent 0pt
417 \postLilyPondExample}''',
418 'pagebreak': r'\pagebreak',
425 'output-filename' : r'''
433 'output-lilypond-fragment': '''@lilypond[%s]
434 \context Staff\context Voice{ %s }
436 'output-noinline': r'''
437 @c generated: %(fn)s.png
440 # verbatim text is always finished with \n
441 'output-small-verbatim': r'''@smallexample
444 # verbatim text is always finished with \n
445 'output-verbatim': r'''@example
448 # do some tweaking: @ is needed in some ps stuff.
450 # ugh, the <p> below breaks inline images...
451 'output-texi-noquote': r'''@tex
463 'output-texi-quoted': r'''@quotation
480 def output_verbatim (body, small):
483 body = re.sub ('&', '&', body)
484 body = re.sub ('>', '>', body)
485 body = re.sub ('<', '<', body)
486 elif format == 'texi':
487 # clumsy workaround for python 2.2 pre bug.
488 body = re.sub ('@', '@@', body)
489 body = re.sub ('{', '@{', body)
490 body = re.sub ('}', '@}', body)
493 key = 'output-small-verbatim'
495 key = 'output-verbatim'
496 return get_output (key) % body
499 ################################################################
500 # Recognize special sequences in the input
503 # Warning: This uses extended regular expressions. Tread with care.
507 # (?P<name>regex) -- assign result of REGEX to NAME
508 # *? -- match non-greedily.
509 # (?m) -- multiline regex: make ^ and $ match at each line
510 # (?s) -- make the dot match all characters including newline
516 'preamble-end': no_match,
517 'landscape': no_match,
518 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
519 'verb': r'''(?P<code><pre>.*?</pre>)''',
520 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
521 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
522 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
523 'option-sep' : '\s*',
524 'intertext': r',?\s*intertext=\".*?\"',
525 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
526 'singleline-comment': no_match,
528 'multicols': no_match,
529 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
533 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
534 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
535 'option-sep' : ',\s*',
536 'header': r"\n*\\documentclass\s*(\[.*?\])?",
537 'preamble-end': r'(?P<code>\\begin\s*{document})',
538 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
539 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
540 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
541 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
542 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
543 'def-post-re': r"\\def\\postLilyPondExample",
544 'def-pre-re': r"\\def\\preLilyPondExample",
545 'usepackage-graphics': r"\usepackage\s*{graphics}",
546 'intertext': r',?\s*intertext=\".*?\"',
547 'multiline-comment': no_match,
548 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
549 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
550 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
555 # why do we have distinction between @mbinclude and @include?
558 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude\s+(?P<filename>\S*))',
561 'preamble-end': no_match,
562 'landscape': no_match,
563 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
564 'verb': r'''(?P<code>@code{.*?})''',
565 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
566 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
567 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
568 'option-sep' : ',\s*',
569 'intertext': r',?\s*intertext=\".*?\"',
570 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
571 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
573 'multicols': no_match,
579 for r in re_dict.keys ():
582 for k in olddict.keys ():
584 newdict[k] = re.compile (olddict[k])
586 print 'invalid regexp: %s' % olddict[k]
588 ## we'd like to catch and reraise a more
589 ## detailed error, but alas, the exceptions
590 ## changed across the 1.5/2.1 boundary.
606 def get_output (name):
607 return output_dict[format][name]
610 return re_dict[format][name]
612 def bounding_box_dimensions (fname):
614 fname = os.path.join (g_outdir, fname)
618 error ("Error opening `%s'" % fname)
620 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
623 gs = map (lambda x: string.atoi (x), s.groups ())
624 return (int (gs[2] - gs[0] + 0.5),
625 int (gs[3] - gs[1] + 0.5))
630 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
634 def compose_full_body (body, opts):
635 '''Construct the lilypond code to send to LilyPond.
636 Add stuff to BODY using OPTS as options.'''
637 music_size = default_music_fontsize
638 if g_force_music_fontsize:
639 music_size = g_force_music_fontsize
644 if not g_force_music_fontsize:
645 m = re.match ('([0-9]+)pt', o)
647 music_size = string.atoi (m.group (1))
649 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
651 f = float (m.group (1))
652 indent = 'indent = %f\\%s' % (f, m.group (2))
654 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
656 f = float (m.group (1))
657 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
659 if re.search ('\\\\score', body):
663 if 'fragment' in opts:
665 if 'nofragment' in opts:
668 if is_fragment and not 'multiline' in opts:
669 opts.append ('singleline')
671 if 'raggedright' in opts or 'singleline' in opts:
673 linewidth = 'raggedright = ##t'
675 indent = 'indent = 0.0\mm'
678 l = paperguru.get_linewidth ()
679 linewidth = 'linewidth = %f\pt' % l
681 if 'noindent' in opts:
682 indent = 'indent = 0.0\mm'
688 \remove Time_signature_engraver
694 m= re.search ('relative(.*)', o)
698 v = string.atoi (m.group (1))
705 pitch = pitch + '\,' * v
707 pitch = pitch + '\'' * v
709 body = '\\relative %s { %s }' % (pitch, body)
710 m =re.search ("filename=(.*)", o)
712 orig_name = m.group (1)
724 optstring = string.join (opts, ' ')
725 optstring = re.sub ('\n', ' ', optstring)
727 %% Generated automatically by: lilypond-book.py
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 (r',\s*', 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)
911 (path, base) = os.path.split (nm)
913 if path not in include_path:
914 include_path.append (path)
916 return [('lilypond', content, options)]
919 def make_ly2dvi_block (m):
922 Find <ly2dvifile .. >
925 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
928 def make_lilypond_block (m):
932 if m.group ('options'):
933 options = get_re ('option-sep').split (m.group ('options'))
936 options = filter (lambda s: s != '', options)
937 return [('lilypond', m.group ('code'), options)]
942 if format != 'latex':
944 if m.group ('num') == 'one':
945 return [('numcols', m.group ('code'), 1)]
946 if m.group ('num') == 'two':
947 return [('numcols', m.group ('code'), 2)]
949 def do_multicols (m):
951 if format != 'latex':
953 if m.group ('be') == 'begin':
954 return [('multicols', m.group ('code'), int (m.group ('num')))]
956 return [('multicols', m.group ('code'), 1)]
959 def chop_chunks (chunks, re_name, func, use_match=0):
965 m = get_re (re_name).search (str)
967 newchunks.append (('input', str))
971 newchunks.append (('input', str[:m.start ('match')]))
973 newchunks.append (('input', str[:m.start (0)]))
974 #newchunks.extend (func (m))
975 # python 1.5 compatible:
976 newchunks = newchunks + func (m)
977 str = str [m.end (0):]
982 def determine_format (str):
985 SIDE EFFECT! This sets FORMAT and PAPERGURU
991 html = re.search ('(?i)<[dh]tml', str[:200])
992 latex = re.search (r'''\\document''', str[:200])
993 texi = re.search ('@node|@setfilename', str[:200])
998 if html and not latex and not texi:
1000 elif latex and not html and not texi:
1002 elif texi and not html and not latex:
1005 error ("can't determine format, please specify")
1009 if paperguru == None:
1010 if format == 'html':
1012 elif format == 'latex':
1014 elif format == 'texi':
1020 def read_doc_file (filename):
1021 '''Read the input file, find verbatim chunks and do \input and \include
1023 (str, path) = find_file (filename)
1024 determine_format (str)
1026 chunks = [('input', str)]
1028 # we have to check for verbatim before doing include,
1029 # because we don't want to include files that are mentioned
1030 # inside a verbatim environment
1031 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1033 chunks = chop_chunks (chunks, 'verb', make_verb)
1034 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1036 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1037 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1041 taken_file_names = {}
1043 def unique_file_name (body):
1044 return 'lily-' + `abs (hash (body))`
1046 def schedule_lilypond_block (chunk):
1047 '''Take the body and options from CHUNK, figure out how the
1048 real .ly should look. The .ly is written, and scheduled in
1051 Return: a single chunk.
1053 The chunk pertaining to the lilypond output
1054 has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE),
1055 where TODO has format [basename, extension, extension, ... ]
1058 (type, body, opts) = chunk
1059 assert type == 'lilypond'
1060 file_body = compose_full_body (body, opts)
1061 ## Hmm, we should hash only lilypond source, and skip the
1064 basename = unique_file_name (file_body)
1066 m = re.search ('filename="(.*?)"', o)
1068 basename = m.group (1)
1069 if not taken_file_names.has_key (basename):
1070 taken_file_names[basename] = 0
1072 taken_file_names[basename] = taken_file_names[basename] + 1
1073 basename = basename + "-t%i" % taken_file_names[basename]
1074 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1075 needed_filetypes = ['tex']
1077 if format == 'html' or g_make_html:
1078 needed_filetypes.append ('eps')
1079 needed_filetypes.append ('png')
1080 if 'eps' in opts and not ('eps' in needed_filetypes):
1081 needed_filetypes.append ('eps')
1083 pathbase = os.path.join (g_outdir, basename)
1084 def must_rebuild (base, ext1, ext2):
1088 fp2 = base + '-page1' + ext2
1090 isfile2 = os.path.isfile (f2)
1092 if not isfile2 and os.path.isfile (fp2):
1094 isfile2 = os.path.isfile (fp2)
1096 if (os.path.isfile (f2) and isfile2 and
1097 os.stat (f1)[stat.ST_MTIME] >
1098 os.stat (f2)[stat.ST_MTIME]) or \
1104 if 'tex' in needed_filetypes and must_rebuild (pathbase, '.ly', '.tex'):
1106 if 'eps' in needed_filetypes and must_rebuild (pathbase, '.tex', '.eps'):
1108 if 'png' in needed_filetypes and must_rebuild (pathbase, '.eps', '.png'):
1111 return ('lilypond', body, opts, todo, basename)
1113 def format_lilypond_block (chunk):
1116 Figure out what should be left MAIN_STR (meant
1117 for the main file) from a lilypond chunk: process
1118 verbatim, and other options. Return: multiple chunks.
1126 (type, body, opts, todo, basename) = chunk
1127 assert type == 'lilypond'
1131 filename_chunk = None
1132 if 'printfilename' in opts:
1134 m= re.match ("filename=(.*)", o)
1136 template = get_output ("output-filename")
1137 b = basename + '.ly'
1138 human_base = os.path.basename (m.group (1))
1140 ## todo: include path, but strip
1141 ## first part of the path.
1142 filename_chunk = ('input', template % (human_base, b,human_base))
1146 if 'smallverbatim' in opts:
1147 newbody += output_verbatim (body, 1)
1148 elif 'verbatim' in opts:
1149 newbody += output_verbatim (body, 0)
1152 m = re.search ('intertext="(.*?)"', o)
1154 newbody = newbody + "\n"
1155 if format == 'texi':
1156 newbody = newbody + "@noindent\n"
1157 elif format == 'latex':
1158 newbody = newbody + "\\noindent\n"
1159 newbody = newbody + m.group (1) + "\n"
1161 if 'noinline' in opts:
1162 s = 'output-noinline'
1163 elif format == 'latex':
1165 s = 'output-latex-quoted'
1169 s = 'output-latex-noquote'
1170 elif format == 'texi':
1172 s = 'output-texi-quoted'
1174 s = 'output-texi-noquote'
1175 else: # format == 'html'
1178 def html_pages (basename):
1179 pat = os.path.join (g_outdir, "%s-page*.png"% basename)
1181 files = glob.glob (pat)
1184 template = '''<img align="center" valign="center"
1185 border="0" src="%s" alt="[picture of music]">'''
1189 files = [basename+'.png' ]
1191 files = map (os.path.basename, files)
1196 str = '<a href="%s.ly">%s</a>' % (basename, str)
1201 newbody = newbody + get_output (s) % {'fn': basename,
1202 'htmlimages': html_pages(basename)
1206 return_chunks += [filename_chunk]
1208 return_chunks += [('lilypond', newbody, opts, todo, basename)]
1210 return return_chunks
1212 def format_lilypond_output_bodies (chunks):
1216 if c[0] == 'lilypond':
1217 newchunks += format_lilypond_block (c)
1219 newchunks.append (c)
1225 def process_lilypond_blocks (chunks):#ugh rename
1227 # Count sections/chapters.
1229 if c[0] == 'lilypond':
1230 c = schedule_lilypond_block (c)
1231 elif c[0] == 'numcols':
1232 paperguru.m_num_cols = c[2]
1233 elif c[0] == 'multicols':
1234 paperguru.m_multicols = c[2]
1236 newchunks.append (c)
1240 def process_ly2dvi_blocks (chunks):
1242 def process_ly2dvi_block (chunk):
1245 Run ly2dvi script on filename specified in CHUNK.
1246 This is only supported for HTML output.
1248 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1249 a separate HTML file, and a title + preview in the main html file,
1250 linking to the menu.
1253 (tag, name, opts) = chunk
1254 assert format == 'html'
1255 (content, original_name) = find_file (name)
1257 original_name = os.path.basename (original_name)
1259 base = unique_file_name (content)
1260 outname = base + '.ly'
1261 changed = update_file (content, outname)
1263 preview = base + ".preview.png"
1264 preview_page = base + '-page1.png'
1266 if changed or not (os.path.isfile (preview) or
1267 os.path.isfile (preview_page)):
1269 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1271 ly.make_ps_images (base + '.ps')
1272 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1275 b = os.stat(fn)[stat.ST_SIZE]
1277 return '%d bytes' % b
1279 return '%d kb' % (b >> 10)
1281 return '%d mb' % (b >> 20)
1284 'pdf' : "Print (PDF, %s)",
1285 'ps.gz' : "Print (gzipped PostScript, %s)",
1286 'png' : "View (PNG, %s)",
1287 'midi' : "Listen (MIDI, %s)",
1288 'ly' : "View source code (%s)",
1292 page_files = glob.glob ('%s-page*.png' % base)
1294 for p in page_files:
1296 if os.path.isfile (p):
1298 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1300 menu += '<li><a href="%s">%s</a>' % (p, page)
1302 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1305 print 'checking,' , fn
1306 if not os.path.isfile (fn):
1309 entry = exts[e] % size_str (fn)
1311 ## TODO: do something like
1312 ## this for texinfo/latex as well ?
1314 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1317 explanatory_para = """The pictures are 90dpi
1318 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1319 use scalable fonts and should look OK at any resolution."""
1322 <title>LilyPond example %s</title>
1328 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1330 open (base + '.html','w'). write (separate_menu)
1332 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1334 return ('ly2dvi', inline_menu)
1338 if c[0] == 'ly2dvi':
1339 c = process_ly2dvi_block (c)
1340 newchunks.append (c)
1344 def compile_all_files (chunks):
1351 if c[0] != 'lilypond':
1361 if base + '.ly' not in tex:
1362 tex.append (base + '.ly')
1363 elif e == 'png' and g_do_pictures:
1369 # fixme: be sys-independent.
1371 if g_outdir and x[0] != '/' :
1372 x = os.path.join (g_here_dir, x)
1375 incs = map (incl_opt, include_path)
1376 lilyopts = string.join (incs)
1378 lilyopts += ' --dependencies'
1380 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1381 lilyopts += ' --header=texidoc'
1382 texfiles = string.join (tex)
1383 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1386 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1388 ly.system (cmd, ignore_error = 0, progress_p = 1)
1391 # Ugh, fixing up dependencies for .tex generation
1394 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1401 text=re.sub ('\n([^:\n]*):',
1402 '\n' + foutn + ':', text)
1408 cmd = r"latex '\nonstopmode \input %s'" % file
1409 # Ugh. (La)TeX writes progress and error messages on stdout
1410 # Redirect to stderr
1411 cmd = '(( %s >&2 ) >&- )' % cmd
1414 ly.system ("dvips -Ppdf -u+lilypond.map -E -o %s.eps %s" % (file, file))
1417 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1421 def update_file (body, name):
1423 write the body if it has changed. Return whether BODY has changed.
1434 f = open (name , 'w')
1441 def write_deps (fn, target, chunks):
1443 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1444 f = open (os.path.join (g_outdir, fn), 'w')
1445 f.write ('%s%s: ' % (g_dep_prefix, target))
1446 for d in read_files:
1450 ## There used to be code to write .tex dependencies, but
1451 ## that is silly: lilypond-book has its own dependency scheme
1452 ## to ensure that all lily-XXX.tex files are there
1459 def check_texidoc (chunks):
1460 ## TODO: put file name in front of texidoc.
1464 if c[0] == 'lilypond':
1465 (type, body, opts, todo, basename) = c;
1466 pathbase = os.path.join (g_outdir, basename)
1467 if os.path.isfile (pathbase + '.texidoc') \
1468 and 'notexidoc' not in opts:
1469 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1474 ## what's this? Docme --hwn
1476 def fix_epswidth (chunks):
1479 if c[0] != 'lilypond' or 'eps' not in c[2]:
1480 newchunks.append (c)
1485 m = re.match ('magnification=([0-9.]+)', o)
1487 mag = string.atof (m.group (1))
1489 def replace_eps_dim (match, lmag = mag):
1490 filename = match.group (1)
1491 dims = bounding_box_dimensions (filename)
1493 return '%fpt' % (dims[0] *lmag)
1495 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1496 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1501 ##docme: why global?
1504 def do_file (input_filename):
1505 chunks = read_doc_file (input_filename)
1506 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1507 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1508 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1509 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1510 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1511 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1512 chunks = chop_chunks (chunks, 'numcols', do_columns)
1513 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1515 scan_preamble (chunks)
1516 chunks = process_lilypond_blocks (chunks)
1517 chunks = process_ly2dvi_blocks (chunks)
1520 global g_run_lilypond
1522 compile_all_files (chunks)
1523 chunks = fix_epswidth (chunks)
1526 chunks = format_lilypond_output_bodies (chunks)
1528 if format == 'texi':
1529 chunks = check_texidoc (chunks)
1533 chunks = completize_preamble (chunks)
1538 my_outname = outname
1539 elif input_filename == '-' or input_filename == "/dev/stdin":
1542 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1543 my_depname = my_outname + '.dep'
1545 if my_outname == '-' or my_outname == '/dev/stdout':
1551 foutn = os.path.join (g_outdir, my_outname)
1552 sys.stderr.write ("Writing `%s'\n" % foutn)
1553 fout = open (foutn, 'w')
1560 write_deps (my_depname, foutn, chunks)
1564 (sh, long) = ly.getopt_args (option_definitions)
1565 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1567 except getopt.error, msg:
1568 sys.stderr.write ('\n')
1569 ly.error (_ ("getopt says: `%s\'" % s))
1570 sys.stderr.write ('\n')
1579 if o == '--include' or o == '-I':
1580 include_path.append (a)
1581 elif o == '--version' or o == '-v':
1582 ly.identify (sys.stdout)
1584 elif o == '--verbose' or o == '-V':
1586 elif o == '--format' or o == '-f':
1588 if a == 'texi-html':
1591 elif o == '--outname' or o == '-o':
1594 sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1597 elif o == '--help' or o == '-h':
1600 elif o == '--no-lily' or o == '-n':
1602 elif o == '--preview-resolution':
1603 preview_resolution = string.atoi (a)
1604 elif o == '--dependencies' or o == '-M':
1606 elif o == '--default-music-fontsize':
1607 default_music_fontsize = string.atoi (a)
1608 elif o == '--default-lilypond-fontsize':
1609 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1610 default_music_fontsize = string.atoi (a)
1611 elif o == '--extra-options':
1613 elif o == '--force-music-fontsize':
1614 g_force_music_fontsize = string.atoi (a)
1615 elif o == '--force-lilypond-fontsize':
1616 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1617 g_force_music_fontsize = string.atoi (a)
1618 elif o == '--dep-prefix':
1620 elif o == '--no-pictures':
1622 elif o == '--no-music':
1624 elif o == '--outdir':
1626 elif o == '--warranty' or o == '-w':
1627 #status = os.system ('lilypond -w')
1632 ly.identify (sys.stderr)
1635 if os.path.isfile (g_outdir):
1636 error ("outdir is a file: %s" % g_outdir)
1637 if not os.path.exists (g_outdir):
1642 ly.error (_ ("no files specified on command line"))
1645 ly.setup_environment ()
1648 for input_filename in files:
1649 do_file (input_filename)
1653 # Petr, ik zou willen dat ik iets zinvoller deed,
1654 # maar wat ik kan ik doen, het verandert toch niets?