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-noinline': r'''
407 %% generated: %(fn)s.eps
409 'output-latex-quoted': r'''{\preLilyPondExample
411 \postLilyPondExample}''',
412 'output-latex-noquote': r'''{\parindent 0pt
415 \postLilyPondExample}''',
416 'pagebreak': r'\pagebreak',
423 'output-filename' : r'''
431 'output-lilypond-fragment': '''@lilypond[%s]
432 \context Staff\context Voice{ %s }
434 'output-noinline': r'''
435 @c generated: %(fn)s.png
438 # verbatim text is always finished with \n
439 'output-small-verbatim': r'''@smallexample
442 # verbatim text is always finished with \n
443 'output-verbatim': r'''@example
446 # do some tweaking: @ is needed in some ps stuff.
448 # ugh, the <p> below breaks inline images...
449 'output-texi-noquote': r'''@tex
461 'output-texi-quoted': r'''@quotation
478 def output_verbatim (body, small):
481 body = re.sub ('&', '&', body)
482 body = re.sub ('>', '>', body)
483 body = re.sub ('<', '<', body)
484 elif format == 'texi':
485 # clumsy workaround for python 2.2 pre bug.
486 body = re.sub ('@', '@@', body)
487 body = re.sub ('{', '@{', body)
488 body = re.sub ('}', '@}', body)
491 key = 'output-small-verbatim'
493 key = 'output-verbatim'
494 return get_output (key) % body
497 ################################################################
498 # Recognize special sequences in the input
501 # Warning: This uses extended regular expressions. Tread with care.
505 # (?P<name>regex) -- assign result of REGEX to NAME
506 # *? -- match non-greedily.
507 # (?m) -- multiline regex: make ^ and $ match at each line
508 # (?s) -- make the dot match all characters including newline
514 'preamble-end': no_match,
515 'landscape': no_match,
516 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
517 'verb': r'''(?P<code><pre>.*?</pre>)''',
518 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
519 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
520 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
521 'option-sep' : '\s*',
522 'intertext': r',?\s*intertext=\".*?\"',
523 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
524 'singleline-comment': no_match,
526 'multicols': no_match,
527 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
531 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
532 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
533 'option-sep' : ',\s*',
534 'header': r"\n*\\documentclass\s*(\[.*?\])?",
535 'preamble-end': r'(?P<code>\\begin\s*{document})',
536 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
537 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
538 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
539 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
540 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
541 'def-post-re': r"\\def\\postLilyPondExample",
542 'def-pre-re': r"\\def\\preLilyPondExample",
543 'usepackage-graphics': r"\usepackage\s*{graphics}",
544 'intertext': r',?\s*intertext=\".*?\"',
545 'multiline-comment': no_match,
546 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
547 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
548 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
553 # why do we have distinction between @mbinclude and @include?
556 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude\s+(?P<filename>\S*))',
559 'preamble-end': no_match,
560 'landscape': no_match,
561 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
562 'verb': r'''(?P<code>@code{.*?})''',
563 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
564 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
565 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
566 'option-sep' : ',\s*',
567 'intertext': r',?\s*intertext=\".*?\"',
568 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
569 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
571 'multicols': no_match,
577 for r in re_dict.keys ():
580 for k in olddict.keys ():
582 newdict[k] = re.compile (olddict[k])
584 print 'invalid regexp: %s' % olddict[k]
586 ## we'd like to catch and reraise a more
587 ## detailed error, but alas, the exceptions
588 ## changed across the 1.5/2.1 boundary.
604 def get_output (name):
605 return output_dict[format][name]
608 return re_dict[format][name]
610 def bounding_box_dimensions (fname):
612 fname = os.path.join (g_outdir, fname)
616 error ("Error opening `%s'" % fname)
618 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
621 gs = map (lambda x: string.atoi (x), s.groups ())
622 return (int (gs[2] - gs[0] + 0.5),
623 int (gs[3] - gs[1] + 0.5))
628 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
632 def compose_full_body (body, opts):
633 '''Construct the lilypond code to send to LilyPond.
634 Add stuff to BODY using OPTS as options.'''
635 music_size = default_music_fontsize
636 if g_force_music_fontsize:
637 music_size = g_force_music_fontsize
642 if not g_force_music_fontsize:
643 m = re.match ('([0-9]+)pt', o)
645 music_size = string.atoi (m.group (1))
647 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
649 f = float (m.group (1))
650 indent = 'indent = %f\\%s' % (f, m.group (2))
652 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
654 f = float (m.group (1))
655 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
657 if re.search ('\\\\score', body):
661 if 'fragment' in opts:
663 if 'nofragment' in opts:
666 if is_fragment and not 'multiline' in opts:
667 opts.append ('singleline')
669 if 'raggedright' in opts or 'singleline' in opts:
671 linewidth = 'raggedright = ##t'
673 indent = 'indent = 0.0\mm'
676 l = paperguru.get_linewidth ()
677 linewidth = 'linewidth = %f\pt' % l
679 if 'noindent' in opts:
680 indent = 'indent = 0.0\mm'
686 \remove Time_signature_engraver
692 m= re.search ('relative(.*)', o)
696 v = string.atoi (m.group (1))
703 pitch = pitch + '\,' * v
705 pitch = pitch + '\'' * v
707 body = '\\relative %s { %s }' % (pitch, body)
708 m =re.search ("filename=(.*)", o)
710 orig_name = m.group (1)
722 optstring = string.join (opts, ' ')
723 optstring = re.sub ('\n', ' ', optstring)
725 %% Generated automatically by: lilypond-book.py
727 \include "paper%d.ly"
733 ''' % (optstring, music_size, linewidth, indent, notime) + body
736 body = '\\renameinput \"%s\"\n%s' % (orig_name, body)
739 # ughUGH not original options
742 def scan_html_preamble (chunks):
745 def scan_latex_preamble (chunks):
746 # First we want to scan the \documentclass line
747 # it should be the first non-comment line.
748 # The only thing we really need to know about the \documentclass line
749 # is if there are one or two columns to begin with.
752 if chunks[idx][0] == 'ignore':
755 m = get_re ('header').match (chunks[idx][1])
757 error ("Latex documents must start with a \documentclass command")
759 options = re.split (',\s*', m.group (1)[1:-1])
762 if 'twocolumn' in options:
763 paperguru.m_num_cols = 2
767 # Then we add everything before \begin{document} to
768 # paperguru.m_document_preamble so that we can later write this header
769 # to a temporary file in find_latex_dims() to find textwidth.
770 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
771 if chunks[idx] == 'ignore':
774 paperguru.m_document_preamble.append (chunks[idx][1])
777 if len (chunks) == idx:
778 error ("Didn't find end of preamble (\\begin{document})")
780 paperguru.find_latex_dims ()
782 def scan_texi_preamble (chunks):
783 # this is not bulletproof..., it checks the first 10 chunks
784 for c in chunks[:10]:
786 for s in ('afourpaper', 'afourwide', 'letterpaper',
787 'afourlatex', 'smallbook'):
788 if string.find (c[1], "@%s" % s) != -1:
789 paperguru.m_papersize = s
792 def scan_preamble (chunks):
795 scan_html_preamble (chunks)
796 elif format == 'latex':
797 scan_latex_preamble (chunks)
798 elif format == 'texi':
799 scan_texi_preamble (chunks)
802 def completize_preamble (chunks):
804 if format != 'latex':
806 pre_b = post_b = graphics_b = None
808 if chunk[0] == 'preamble-end':
810 if chunk[0] == 'input':
811 m = get_re ('def-pre-re').search (chunk[1])
814 if chunk[0] == 'input':
815 m = get_re ('def-post-re').search (chunk[1])
819 if chunk[0] == 'input':
820 m = get_re ('usepackage-graphics').search (chunk[1])
824 while x < len (chunks) and chunks[x][0] != 'preamble-end':
827 if x == len (chunks):
831 chunks.insert (x, ('input', get_output ('output-default-pre')))
833 chunks.insert (x, ('input', get_output ('output-default-post')))
835 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
841 def find_file (name):
843 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
847 return (sys.stdin.read (), '<stdin>')
850 for a in include_path:
852 nm = os.path.join (a, name)
855 read_files.append (nm)
860 sys.stderr.write ("Reading `%s'\n" % nm)
861 return (f.read (), nm)
863 error ("File not found `%s'\n" % name)
866 def do_ignore (match_object):
867 return [('ignore', match_object.group ('code'))]
868 def do_preamble_end (match_object):
869 return [('preamble-end', match_object.group ('code'))]
871 def make_verbatim (match_object):
872 return [('verbatim', match_object.group ('code'))]
874 def make_verb (match_object):
875 return [('verb', match_object.group ('code'))]
877 def do_include_file (m):
879 return [('input', get_output ('pagebreak'))] \
880 + read_doc_file (m.group ('filename')) \
881 + [('input', get_output ('pagebreak'))]
883 def do_input_file (m):
884 return read_doc_file (m.group ('filename'))
886 def make_lilypond (m):
887 if m.group ('options'):
888 options = m.group ('options')
891 return [('input', get_output ('output-lilypond-fragment') %
892 (options, m.group ('code')))]
894 def make_lilypond_file (m):
897 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
898 into a @lilypond .. @end lilypond block.
902 if m.group ('options'):
903 options = get_re ('option-sep').split (m.group ('options'))
906 (content, nm) = find_file (m.group ('filename'))
907 options.append ("filename=%s" % nm)
908 (path, base) = os.path.split (nm)
910 if path not in include_path:
911 include_path.append (path)
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 + "-t%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':
1162 s = 'output-latex-quoted'
1164 s = 'output-latex-noquote'
1165 elif format == 'texi':
1167 s = 'output-texi-quoted'
1169 s = 'output-texi-noquote'
1170 else: # format == 'html'
1173 def html_pages (basename):
1174 pat = os.path.join (g_outdir, "%s-page*.png"% basename)
1176 files = glob.glob (pat)
1179 template = '''<img align="center" valign="center"
1180 border="0" src="%s" alt="[picture of music]">'''
1184 files = [basename+'.png' ]
1186 files = map (os.path.basename, files)
1191 str = '<a href="%s.ly">%s</a>' % (basename, str)
1196 newbody = newbody + get_output (s) % {'fn': basename,
1197 'htmlimages': html_pages(basename)
1201 return_chunks += [filename_chunk]
1203 return_chunks += [('lilypond', newbody, opts, todo, basename)]
1205 return return_chunks
1207 def format_lilypond_output_bodies (chunks):
1211 if c[0] == 'lilypond':
1212 newchunks += format_lilypond_block (c)
1214 newchunks.append (c)
1220 def process_lilypond_blocks (chunks):#ugh rename
1222 # Count sections/chapters.
1224 if c[0] == 'lilypond':
1225 c = schedule_lilypond_block (c)
1226 elif c[0] == 'numcols':
1227 paperguru.m_num_cols = c[2]
1228 elif c[0] == 'multicols':
1229 paperguru.m_multicols = c[2]
1231 newchunks.append (c)
1235 def process_ly2dvi_blocks (chunks):
1237 def process_ly2dvi_block (chunk):
1240 Run ly2dvi script on filename specified in CHUNK.
1241 This is only supported for HTML output.
1243 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1244 a separate HTML file, and a title + preview in the main html file,
1245 linking to the menu.
1248 (tag, name, opts) = chunk
1249 assert format == 'html'
1250 (content, original_name) = find_file (name)
1252 original_name = os.path.basename (original_name)
1254 base = unique_file_name (content)
1255 outname = base + '.ly'
1256 changed = update_file (content, outname)
1258 preview = base + ".preview.png"
1259 preview_page = base + '-page1.png'
1261 if changed or not (os.path.isfile (preview) or
1262 os.path.isfile (preview_page)):
1264 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1266 ly.make_ps_images (base + '.ps')
1267 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1270 b = os.stat(fn)[stat.ST_SIZE]
1272 return '%d bytes' % b
1274 return '%d kb' % (b >> 10)
1276 return '%d mb' % (b >> 20)
1279 'pdf' : "Print (PDF, %s)",
1280 'ps.gz' : "Print (gzipped PostScript, %s)",
1281 'png' : "View (PNG, %s)",
1282 'midi' : "Listen (MIDI, %s)",
1283 'ly' : "View source code (%s)",
1287 page_files = glob.glob ('%s-page*.png' % base)
1289 for p in page_files:
1291 if os.path.isfile (p):
1293 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1295 menu += '<li><a href="%s">%s</a>' % (p, page)
1297 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1300 print 'checking,' , fn
1301 if not os.path.isfile (fn):
1304 entry = exts[e] % size_str (fn)
1306 ## TODO: do something like
1307 ## this for texinfo/latex as well ?
1309 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1312 explanatory_para = """The pictures are 90dpi
1313 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1314 use scalable fonts and should look OK at any resolution."""
1317 <title>LilyPond example %s</title>
1323 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1325 open (base + '.html','w'). write (separate_menu)
1327 inline_menu = '<p/><a href="%s.html"><img alt="%s" src="%s"></a><p/>' % (base, original_name, preview)
1329 return ('ly2dvi', inline_menu)
1333 if c[0] == 'ly2dvi':
1334 c = process_ly2dvi_block (c)
1335 newchunks.append (c)
1339 def compile_all_files (chunks):
1346 if c[0] != 'lilypond':
1356 if base + '.ly' not in tex:
1357 tex.append (base + '.ly')
1358 elif e == 'png' and g_do_pictures:
1364 # fixme: be sys-independent.
1366 if g_outdir and x[0] != '/' :
1367 x = os.path.join (g_here_dir, x)
1370 incs = map (incl_opt, include_path)
1371 lilyopts = string.join (incs)
1373 lilyopts += ' --dependencies'
1375 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1376 lilyopts += ' --header=texidoc'
1377 texfiles = string.join (tex)
1378 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1381 ly.lilypond_version_check (lilypond_binary, '@TOPLEVEL_VERSION@')
1383 ly.system (cmd, ignore_error = 0, progress_p = 1)
1386 # Ugh, fixing up dependencies for .tex generation
1389 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1396 text=re.sub ('\n([^:\n]*):',
1397 '\n' + foutn + ':', text)
1403 cmd = r"latex '\nonstopmode \input %s'" % file
1404 # Ugh. (La)TeX writes progress and error messages on stdout
1405 # Redirect to stderr
1406 cmd = '(( %s >&2 ) >&- )' % cmd
1409 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1412 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1416 def update_file (body, name):
1418 write the body if it has changed. Return whether BODY has changed.
1429 f = open (name , 'w')
1436 def write_deps (fn, target, chunks):
1438 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1439 f = open (os.path.join (g_outdir, fn), 'w')
1440 f.write ('%s%s: ' % (g_dep_prefix, target))
1441 for d in read_files:
1445 ## There used to be code to write .tex dependencies, but
1446 ## that is silly: lilypond-book has its own dependency scheme
1447 ## to ensure that all lily-XXX.tex files are there
1454 def check_texidoc (chunks):
1455 ## TODO: put file name in front of texidoc.
1459 if c[0] == 'lilypond':
1460 (type, body, opts, todo, basename) = c;
1461 pathbase = os.path.join (g_outdir, basename)
1462 if os.path.isfile (pathbase + '.texidoc') \
1463 and 'notexidoc' not in opts:
1464 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1469 ## what's this? Docme --hwn
1471 def fix_epswidth (chunks):
1474 if c[0] != 'lilypond' or 'eps' not in c[2]:
1475 newchunks.append (c)
1480 m = re.match ('magnification=([0-9.]+)', o)
1482 mag = string.atof (m.group (1))
1484 def replace_eps_dim (match, lmag = mag):
1485 filename = match.group (1)
1486 dims = bounding_box_dimensions (filename)
1488 return '%fpt' % (dims[0] *lmag)
1490 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1491 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1496 ##docme: why global?
1499 def do_file (input_filename):
1500 chunks = read_doc_file (input_filename)
1501 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1502 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1503 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1504 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1505 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1506 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1507 chunks = chop_chunks (chunks, 'numcols', do_columns)
1508 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1510 scan_preamble (chunks)
1511 chunks = process_lilypond_blocks (chunks)
1512 chunks = process_ly2dvi_blocks (chunks)
1515 global g_run_lilypond
1517 compile_all_files (chunks)
1518 chunks = fix_epswidth (chunks)
1521 chunks = format_lilypond_output_bodies (chunks)
1523 if format == 'texi':
1524 chunks = check_texidoc (chunks)
1528 chunks = completize_preamble (chunks)
1533 my_outname = outname
1534 elif input_filename == '-' or input_filename == "/dev/stdin":
1537 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1538 my_depname = my_outname + '.dep'
1540 if my_outname == '-' or my_outname == '/dev/stdout':
1546 foutn = os.path.join (g_outdir, my_outname)
1547 sys.stderr.write ("Writing `%s'\n" % foutn)
1548 fout = open (foutn, 'w')
1555 write_deps (my_depname, foutn, chunks)
1559 (sh, long) = ly.getopt_args (option_definitions)
1560 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1562 except getopt.error, msg:
1563 sys.stderr.write ('\n')
1564 ly.error (_ ("getopt says: `%s\'" % s))
1565 sys.stderr.write ('\n')
1574 if o == '--include' or o == '-I':
1575 include_path.append (a)
1576 elif o == '--version' or o == '-v':
1577 ly.identify (sys.stdout)
1579 elif o == '--verbose' or o == '-V':
1581 elif o == '--format' or o == '-f':
1583 if a == 'texi-html':
1586 elif o == '--outname' or o == '-o':
1589 sys.stderr.write ("lilypond-book is confused by --outname on multiple files")
1592 elif o == '--help' or o == '-h':
1595 elif o == '--no-lily' or o == '-n':
1597 elif o == '--preview-resolution':
1598 preview_resolution = string.atoi (a)
1599 elif o == '--dependencies' or o == '-M':
1601 elif o == '--default-music-fontsize':
1602 default_music_fontsize = string.atoi (a)
1603 elif o == '--default-lilypond-fontsize':
1604 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1605 default_music_fontsize = string.atoi (a)
1606 elif o == '--extra-options':
1608 elif o == '--force-music-fontsize':
1609 g_force_music_fontsize = string.atoi (a)
1610 elif o == '--force-lilypond-fontsize':
1611 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1612 g_force_music_fontsize = string.atoi (a)
1613 elif o == '--dep-prefix':
1615 elif o == '--no-pictures':
1617 elif o == '--no-music':
1619 elif o == '--outdir':
1621 elif o == '--warranty' or o == '-w':
1622 #status = os.system ('lilypond -w')
1627 ly.identify (sys.stderr)
1630 if os.path.isfile (g_outdir):
1631 error ("outdir is a file: %s" % g_outdir)
1632 if not os.path.exists (g_outdir):
1637 ly.error (_ ("no files specified on command line"))
1640 ly.setup_environment ()
1643 for input_filename in files:
1644 do_file (input_filename)
1648 # Petr, ik zou willen dat ik iets zinvoller deed,
1649 # maar wat ik kan ik doen, het verandert toch niets?