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_name = 'lilypond-book'
109 original_dir = os.getcwd ()
110 #temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
114 preview_resolution = 90
117 ## ly2dvi: silly name?
118 ## do -P or -p by default?
119 ##help_summary = _ ("Run LilyPond using LaTeX for titling")
120 help_summary = _ ("Process LilyPond snippets in hybrid html, LaTeX or texinfo document")
121 copyright = ('Tom Cato Amundsen <tca@gnu.org>',
122 'Han-Wen Nienhuys <hanwen@cs.uu.nl>')
124 option_definitions = [
125 (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")),
126 (_ ("DIM"), '', 'default-music-fontsize', _ ("default fontsize for music. DIM is assumed to be in points")),
127 (_ ("DIM"), '', 'default-lilypond-fontsize', _ ("deprecated, use --default-music-fontsize")),
128 (_ ("OPT"), '', 'extra-options', _ ("pass OPT quoted to the lilypond command line")),
129 (_ ("DIM"), '', 'force-music-fontsize', _ ("force fontsize for all inline lilypond. DIM is assumed be to in points")),
130 (_ ("DIM"), '', 'force-lilypond-fontsize', _ ("deprecated, use --force-music-fontsize")),
131 ('', 'h', 'help', _ ("this help")),
132 (_ ("DIR"), 'I', 'include', _ ("include path")),
133 ('', 'M', 'dependencies', _ ("write dependencies")),
134 (_ ("PREF"), '', 'dep-prefix', _ ("prepend PREF before each -M dependency")),
135 ('', 'n', 'no-lily', _ ("don't run lilypond")),
136 ('', '', 'no-pictures', _ ("don't generate pictures")),
137 ('', '', 'no-music', _ ("strip all lilypond blocks from output")),
138 (_ ("FILE"), 'o', 'outname', _ ("filename main output file")),
139 (_ ("FILE"), '', 'outdir', _ ("where to place generated files")),
140 (_ ('RES'), '', 'preview-resolution',
141 _ ("set the resolution of the preview to RES")),
142 ('', 'V', 'verbose', _ ("verbose")),
143 ('', 'v', 'version', _ ("print version information")),
144 ('', 'w', 'warranty', _ ("show warranty and copyright")),
147 # format specific strings, ie. regex-es for input, and % strings for output
151 include_path = [os.getcwd ()]
154 #lilypond_binary = 'valgrind --suppressions=/home/hanwen/usr/src/guile-1.6.supp --num-callers=10 /home/hanwen/usr/src/lilypond/lily/out/lilypond'
156 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
158 # only use installed binary when we're installed too.
159 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
160 lilypond_binary = 'lilypond'
164 ly2dvi_binary = os.path.join ('@bindir@', 'ly2dvi')
166 # only use installed binary when we're installed too.
167 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
168 ly2dvi_binary = 'ly2dvi'
173 g_here_dir = os.getcwd ()
176 g_force_music_fontsize = 0
185 default_music_fontsize = 16
186 default_text_fontsize = 12
189 ################################################################
190 # Dimension handling for LaTeX.
194 self.m_document_preamble = []
198 def find_latex_dims (self):
200 fname = os.path.join (g_outdir, "lily-tmp.tex")
202 fname = "lily-tmp.tex"
204 f = open (fname, "w")
206 error ("Error creating temporary file '%s'" % fname)
208 for s in self.m_document_preamble:
213 \typeout{\columnsep \the\columnsep}
214 \typeout{\textwidth \the\textwidth}
219 re_dim = re.compile (r"\\(\w+)\s+(\d+\.\d+)")
221 cmd = "latex '\\nonstopmode \input %s'" % fname
222 # Ugh. (La)TeX writes progress and error messages on stdout
224 cmd += ' 1>/dev/stderr'
225 status = ly.system (cmd, ignore_error = 1)
226 signal = 0xf & status
227 exit_status = status >> 8
230 ly.error (_ ("LaTeX failed."))
231 ly.error (_ ("The error log is as follows:"))
235 lns = open ('lily-tmp.log').readlines ()
240 sys.stderr.write (ln)
241 if re.match ('^!', ln):
248 countdown = countdown -1
250 sys.stderr.write (" ... (further messages elided)...\n")
253 lns = open ('lily-tmp.log').readlines ()
255 ln = string.strip (ln)
256 m = re_dim.match (ln)
258 if m.groups ()[0] in ('textwidth', 'columnsep'):
259 self.__dict__['m_%s' % m.groups ()[0]] = float (m.groups ()[1])
263 os.remove (os.path.splitext (fname)[0]+".aux")
264 os.remove (os.path.splitext (fname)[0]+".log")
268 if not self.__dict__.has_key ('m_textwidth'):
271 def get_linewidth (self):
272 if self.m_num_cols == 1:
275 w = (self.m_textwidth - self.m_columnsep)/2
276 if self.m_multicols > 1:
277 return (w - self.m_columnsep* (self.m_multicols-1)) \
284 self.m_papersize = 'letterpaper'
286 def get_linewidth (self):
287 return html_linewidths[self.m_papersize][self.m_fontsize]
291 self.m_papersize = 'letterpaper'
293 def get_linewidth (self):
294 return texi_linewidths[self.m_papersize][self.m_fontsize]
300 def em2pt (x, fontsize = 10):
301 return {10: 10.00002, 11: 10.8448, 12: 11.74988}[fontsize] * x
302 def ex2pt (x, fontsize = 10):
303 return {10: 4.30554, 11: 4.7146, 12: 5.16667}[fontsize] * x
308 dimension_conversion_dict ={
310 'cm': lambda x: mm2pt (10*x),
317 # Convert numeric values, with or without specific dimension, to floats.
319 def conv_dimen_to_float (value):
320 if type (value) == type (""):
321 m = re.match ("([0-9.]+)(cm|in|pt|mm|em|ex)",value)
324 num = string.atof (m.group (1))
325 conv = dimension_conversion_dict[m.group (2)]
329 elif re.match ("^[0-9.]+$",value):
330 value = float (value)
335 'afourpaper': {12: mm2pt (160)},
336 'afourwide': {12: in2pt (6.5)},
337 'afourlatex': {12: mm2pt (150)},
338 'smallbook': {12: in2pt (5)},
339 'letterpaper': {12: in2pt (6)}}
342 'afourpaper': {12: mm2pt (160)},
343 'afourwide': {12: in2pt (6.5)},
344 'afourlatex': {12: mm2pt (150)},
345 'smallbook': {12: in2pt (5)},
346 'letterpaper': {12: in2pt (6)}}
349 ################################################################
350 # How to output various structures.
356 'output-filename' : r'''
359 <pre>%s</pre></a>:''',
360 'output-lilypond-fragment': '''<lilypond%s>
361 \context Staff\context Voice{ %s }
363 'output-noinline': r'''
364 <!-- generated: %(fn)s.png !-->
368 # Verbatim text is always finished with \n. FIXME: For HTML,
369 # this newline should be removed.
370 'output-verbatim': r'''<pre>
372 # Verbatim text is always finished with \n. FIXME: For HTML,
373 # this newline should be removed.
374 'output-small-verbatim': r'''<font size=-1><pre>
376 ## Ugh we need to differentiate on origin:
377 ## lilypond-block origin wants an extra <p>, but
378 ## inline music doesn't.
379 ## possibly other center options?
387 'output-lilypond-fragment' : r'''\begin[eps,singleline,%s]{lilypond}
394 'output-filename' : r'''\verb+%s+:\\
399 # verbatim text is always finished with \n
400 'output-verbatim': r'''\begin{verbatim}
403 # verbatim text is always finished with \n
404 'output-small-verbatim': r'''{\small\begin{verbatim}
407 'output-default-post': "\\def\postLilypondExample{}\n",
408 'output-default-pre': "\\def\preLilypondExample{}\n",
409 'usepackage-graphics': '\\usepackage{graphics}\n',
410 'output-eps': '\\noindent\\parbox{\\lilypondepswidth{%(fn)s.eps}}{\includegraphics{%(fn)s}}',
411 'output-noinline': r'''
412 %% generated: %(fn)s.eps
414 'output-latex-quoted': r'''{\preLilypondExample
416 \postLilypondExample}''',
417 'output-latex-noquote': r'''{\parindent 0pt
420 \postLilypondExample}''',
421 'pagebreak': r'\pagebreak',
428 'output-filename' : r'''
436 'output-lilypond-fragment': '''@lilypond[%s]
437 \context Staff\context Voice{ %s }
439 'output-noinline': r'''
440 @c generated: %(fn)s.png
443 # verbatim text is always finished with \n
444 'output-small-verbatim': r'''@smallexample
447 # verbatim text is always finished with \n
448 'output-verbatim': r'''@example
451 # do some tweaking: @ is needed in some ps stuff.
453 # ugh, the <p> below breaks inline images...
454 'output-texi-noquote': r'''@tex
466 'output-texi-quoted': r'''@quotation
483 def output_verbatim (body, small):
486 body = re.sub ('&', '&', body)
487 body = re.sub ('>', '>', body)
488 body = re.sub ('<', '<', body)
489 elif format == 'texi':
490 # clumsy workaround for python 2.2 pre bug.
491 body = re.sub ('@', '@@', body)
492 body = re.sub ('{', '@{', body)
493 body = re.sub ('}', '@}', body)
496 key = 'output-small-verbatim'
498 key = 'output-verbatim'
499 return get_output (key) % body
502 ################################################################
503 # Recognize special sequences in the input
506 # Warning: This uses extended regular expressions. Tread with care.
510 # (?P<name>regex) -- assign result of REGEX to NAME
511 # *? -- match non-greedily.
512 # (?m) -- multiline regex: make ^ and $ match at each line
513 # (?s) -- make the dot match all characters including newline
519 'preamble-end': no_match,
520 'landscape': no_match,
521 'verbatim': r'''(?s)(?P<code><pre>\s.*?</pre>\s)''',
522 'verb': r'''(?P<code><pre>.*?</pre>)''',
523 'lilypond-file': r'(?m)(?P<match><lilypondfile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</lilypondfile>)',
524 'lilypond' : '(?m)(?P<match><lilypond((?P<options>[^:]*):)(?P<code>.*?)/>)',
525 'lilypond-block': r'''(?ms)(?P<match><lilypond(?P<options>[^>]+)?>(?P<code>.*?)</lilypond>)''',
526 'option-sep' : '\s*',
527 'intertext': r',?\s*intertext=\".*?\"',
528 'multiline-comment': r"(?sm)\s*(?!@c\s+)(?P<code><!--\s.*?!-->)\s",
529 'singleline-comment': no_match,
531 'multicols': no_match,
532 'ly2dvi': r'(?m)(?P<match><ly2dvifile(?P<options>[^>]+)?>\s*(?P<filename>[^<]+)\s*</ly2dvifile>)',
536 'input': r'(?m)^[^%\n]*?(?P<match>\\mbinput{?([^}\t \n}]*))',
537 'include': r'(?m)^[^%\n]*?(?P<match>\\mbinclude{(?P<filename>[^}]+)})',
538 'option-sep' : ',\s*',
539 'header': r"\n*\\documentclass\s*(\[.*?\])?",
540 'preamble-end': r'(?P<code>\\begin\s*{document})',
541 'verbatim': r"(?s)(?P<code>\\begin\s*{verbatim}.*?\\end{verbatim})",
542 'verb': r"(?P<code>\\verb(?P<del>.).*?(?P=del))",
543 'lilypond-file': r'(?m)^[^%\n]*?(?P<match>\\lilypondfile\s*(\[(?P<options>.*?)\])?\s*\{(?P<filename>.+)})',
544 'lilypond' : r'(?m)^[^%\n]*?(?P<match>\\lilypond\s*(\[(?P<options>.*?)\])?\s*{(?P<code>.*?)})',
545 'lilypond-block': r"(?sm)^[^%\n]*?(?P<match>\\begin\s*(\[(?P<options>.*?)\])?\s*{lilypond}(?P<code>.*?)\\end{lilypond})",
546 'def-post-re': r"\\def\\postLilypondExample",
547 'def-pre-re': r"\\def\\preLilypondExample",
548 'usepackage-graphics': r"\usepackage\s*{graphics}",
549 'intertext': r',?\s*intertext=\".*?\"',
550 'multiline-comment': no_match,
551 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>^%.*$\n+))",
552 'numcols': r"(?P<code>\\(?P<num>one|two)column)",
553 'multicols': r"(?P<code>\\(?P<be>begin|end)\s*{multicols}({(?P<num>\d+)?})?)",
558 # why do we have distinction between @mbinclude and @include?
561 'include': '(?m)^[^%\n]*?(?P<match>@mbinclude[ \n\t]+(?P<filename>[^\t \n]*))',
564 'preamble-end': no_match,
565 'landscape': no_match,
566 'verbatim': r'''(?s)(?P<code>@example\s.*?@end example\s)''',
567 'verb': r'''(?P<code>@code{.*?})''',
568 'lilypond-file': '(?m)^(?P<match>@lilypondfile(\[(?P<options>[^]]*)\])?{(?P<filename>[^}]+)})',
569 'lilypond' : '(?m)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?{(?P<code>.*?)})',
570 'lilypond-block': r'''(?ms)^(?P<match>@lilypond(\[(?P<options>[^]]*)\])?\s(?P<code>.*?)@end lilypond)\s''',
571 'option-sep' : ',\s*',
572 'intertext': r',?\s*intertext=\".*?\"',
573 'multiline-comment': r"(?sm)^\s*(?!@c\s+)(?P<code>@ignore\s.*?@end ignore)\s",
574 'singleline-comment': r"(?m)^.*?(?P<match>(?P<code>@c.*$\n+))",
576 'multicols': no_match,
582 for r in re_dict.keys ():
585 for k in olddict.keys ():
587 newdict[k] = re.compile (olddict[k])
589 print 'invalid regexp: %s' % olddict[k]
591 ## we'd like to catch and reraise a more
592 ## detailed error, but alas, the exceptions
593 ## changed across the 1.5/2.1 boundary.
609 def get_output (name):
610 return output_dict[format][name]
613 return re_dict[format][name]
615 def bounding_box_dimensions (fname):
617 fname = os.path.join (g_outdir, fname)
621 error ("Error opening `%s'" % fname)
623 s = re.search ('%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', str)
626 gs = map (lambda x: string.atoi (x), s.groups ())
627 return (int (gs[2] - gs[0] + 0.5),
628 int (gs[3] - gs[1] + 0.5))
633 sys.stderr.write ("\n\n" + str + "\nExiting ... \n\n")
637 def compose_full_body (body, opts):
638 '''Construct the lilypond code to send to Lilypond.
639 Add stuff to BODY using OPTS as options.'''
640 music_size = default_music_fontsize
641 if g_force_music_fontsize:
642 music_size = g_force_music_fontsize
647 if not g_force_music_fontsize:
648 m = re.match ('([0-9]+)pt', o)
650 music_size = string.atoi (m.group (1))
652 m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt)', o)
654 f = float (m.group (1))
655 indent = 'indent = %f\\%s' % (f, m.group (2))
657 m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt)', o)
659 f = float (m.group (1))
660 linewidth = 'linewidth = %f\\%s' % (f, m.group (2))
662 if re.search ('\\\\score', body):
666 if 'fragment' in opts:
668 if 'nofragment' in opts:
671 if is_fragment and not 'multiline' in opts:
672 opts.append ('singleline')
674 if 'raggedright' in opts or 'singleline' in opts:
676 linewidth = 'raggedright = ##t'
678 indent = 'indent = 0.0\mm'
681 l = paperguru.get_linewidth ()
682 linewidth = 'linewidth = %f\pt' % l
684 if 'noindent' in opts:
685 indent = 'indent = 0.0\mm'
691 \remove Time_signature_engraver
696 m= re.search ('relative(.*)', o)
700 v = string.atoi (m.group (1))
707 pitch = pitch + '\,' * v
709 pitch = pitch + '\'' * v
711 body = '\\relative %s { %s }' % (pitch, body)
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
736 # ughUGH not original options
739 def scan_html_preamble (chunks):
742 def scan_latex_preamble (chunks):
743 # First we want to scan the \documentclass line
744 # it should be the first non-comment line.
745 # The only thing we really need to know about the \documentclass line
746 # is if there are one or two columns to begin with.
749 if chunks[idx][0] == 'ignore':
752 m = get_re ('header').match (chunks[idx][1])
754 error ("Latex documents must start with a \documentclass command")
756 options = re.split (',[\n \t]*', m.group (1)[1:-1])
759 if 'twocolumn' in options:
760 paperguru.m_num_cols = 2
764 # Then we add everything before \begin{document} to
765 # paperguru.m_document_preamble so that we can later write this header
766 # to a temporary file in find_latex_dims() to find textwidth.
767 while idx < len (chunks) and chunks[idx][0] != 'preamble-end':
768 if chunks[idx] == 'ignore':
771 paperguru.m_document_preamble.append (chunks[idx][1])
774 if len (chunks) == idx:
775 error ("Didn't find end of preamble (\\begin{document})")
777 paperguru.find_latex_dims ()
779 def scan_texi_preamble (chunks):
780 # this is not bulletproof..., it checks the first 10 chunks
781 for c in chunks[:10]:
783 for s in ('afourpaper', 'afourwide', 'letterpaper',
784 'afourlatex', 'smallbook'):
785 if string.find (c[1], "@%s" % s) != -1:
786 paperguru.m_papersize = s
789 def scan_preamble (chunks):
792 scan_html_preamble (chunks)
793 elif format == 'latex':
794 scan_latex_preamble (chunks)
795 elif format == 'texi':
796 scan_texi_preamble (chunks)
799 def completize_preamble (chunks):
801 if format != 'latex':
803 pre_b = post_b = graphics_b = None
805 if chunk[0] == 'preamble-end':
807 if chunk[0] == 'input':
808 m = get_re ('def-pre-re').search (chunk[1])
811 if chunk[0] == 'input':
812 m = get_re ('def-post-re').search (chunk[1])
816 if chunk[0] == 'input':
817 m = get_re ('usepackage-graphics').search (chunk[1])
821 while x < len (chunks) and chunks[x][0] != 'preamble-end':
824 if x == len (chunks):
828 chunks.insert (x, ('input', get_output ('output-default-pre')))
830 chunks.insert (x, ('input', get_output ('output-default-post')))
832 chunks.insert (x, ('input', get_output ('usepackage-graphics')))
838 def find_file (name):
840 Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
844 return (sys.stdin.read (), '<stdin>')
847 for a in include_path:
849 nm = os.path.join (a, name)
852 read_files.append (nm)
857 sys.stderr.write ("Reading `%s'\n" % nm)
858 return (f.read (), nm)
860 error ("File not found `%s'\n" % name)
863 def do_ignore (match_object):
864 return [('ignore', match_object.group ('code'))]
865 def do_preamble_end (match_object):
866 return [('preamble-end', match_object.group ('code'))]
868 def make_verbatim (match_object):
869 return [('verbatim', match_object.group ('code'))]
871 def make_verb (match_object):
872 return [('verb', match_object.group ('code'))]
874 def do_include_file (m):
876 return [('input', get_output ('pagebreak'))] \
877 + read_doc_file (m.group ('filename')) \
878 + [('input', get_output ('pagebreak'))]
880 def do_input_file (m):
881 return read_doc_file (m.group ('filename'))
883 def make_lilypond (m):
884 if m.group ('options'):
885 options = m.group ('options')
888 return [('input', get_output ('output-lilypond-fragment') %
889 (options, m.group ('code')))]
891 def make_lilypond_file (m):
894 Find @lilypondfile{bla.ly} occurences and substitute bla.ly
895 into a @lilypond .. @end lilypond block.
899 if m.group ('options'):
900 options = get_re ('option-sep').split (m.group ('options'))
903 (content, nm) = find_file (m.group ('filename'))
904 options.append ("filename=%s" % nm)
907 return [('lilypond', content, options)]
910 def make_ly2dvi_block (m):
913 Find <ly2dvifile .. >
916 return [('ly2dvi', m.group ('filename'), m.group ('options'))]
919 def make_lilypond_block (m):
923 if m.group ('options'):
924 options = get_re ('option-sep').split (m.group ('options'))
927 options = filter (lambda s: s != '', options)
928 return [('lilypond', m.group ('code'), options)]
933 if format != 'latex':
935 if m.group ('num') == 'one':
936 return [('numcols', m.group ('code'), 1)]
937 if m.group ('num') == 'two':
938 return [('numcols', m.group ('code'), 2)]
940 def do_multicols (m):
942 if format != 'latex':
944 if m.group ('be') == 'begin':
945 return [('multicols', m.group ('code'), int (m.group ('num')))]
947 return [('multicols', m.group ('code'), 1)]
950 def chop_chunks (chunks, re_name, func, use_match=0):
956 m = get_re (re_name).search (str)
958 newchunks.append (('input', str))
962 newchunks.append (('input', str[:m.start ('match')]))
964 newchunks.append (('input', str[:m.start (0)]))
965 #newchunks.extend (func (m))
966 # python 1.5 compatible:
967 newchunks = newchunks + func (m)
968 str = str [m.end (0):]
973 def determine_format (str):
976 SIDE EFFECT! This sets FORMAT and PAPERGURU
982 html = re.search ('(?i)<[dh]tml', str[:200])
983 latex = re.search (r'''\\document''', str[:200])
984 texi = re.search ('@node|@setfilename', str[:200])
989 if html and not latex and not texi:
991 elif latex and not html and not texi:
993 elif texi and not html and not latex:
996 error ("can't determine format, please specify")
1000 if paperguru == None:
1001 if format == 'html':
1003 elif format == 'latex':
1005 elif format == 'texi':
1011 def read_doc_file (filename):
1012 '''Read the input file, find verbatim chunks and do \input and \include
1014 (str, path) = find_file (filename)
1015 determine_format (str)
1017 chunks = [('input', str)]
1019 # we have to check for verbatim before doing include,
1020 # because we don't want to include files that are mentioned
1021 # inside a verbatim environment
1022 chunks = chop_chunks (chunks, 'verbatim', make_verbatim)
1024 chunks = chop_chunks (chunks, 'verb', make_verb)
1025 chunks = chop_chunks (chunks, 'multiline-comment', do_ignore)
1027 chunks = chop_chunks (chunks, 'include', do_include_file, 1)
1028 chunks = chop_chunks (chunks, 'input', do_input_file, 1)
1032 taken_file_names = {}
1034 def unique_file_name (body):
1035 return 'lily-' + `abs (hash (body))`
1037 def schedule_lilypond_block (chunk):
1038 '''Take the body and options from CHUNK, figure out how the
1039 real .ly should look. The .ly is written, and scheduled in
1042 Return: a single chunk.
1044 The chunk pertaining to the lilypond output
1045 has the format (TYPE_STR, MAIN_STR, OPTIONS, TODO, BASE),
1046 where TODO has format [basename, extension, extension, ... ]
1049 (type, body, opts) = chunk
1050 assert type == 'lilypond'
1051 file_body = compose_full_body (body, opts)
1052 ## Hmm, we should hash only lilypond source, and skip the
1055 basename = unique_file_name (file_body)
1057 m = re.search ('filename="(.*?)"', o)
1059 basename = m.group (1)
1060 if not taken_file_names.has_key (basename):
1061 taken_file_names[basename] = 0
1063 taken_file_names[basename] = taken_file_names[basename] + 1
1064 basename = basename + "-%i" % taken_file_names[basename]
1065 update_file (file_body, os.path.join (g_outdir, basename) + '.ly')
1066 needed_filetypes = ['tex']
1068 if format == 'html' or g_make_html:
1069 needed_filetypes.append ('eps')
1070 needed_filetypes.append ('png')
1071 if 'eps' in opts and not ('eps' in needed_filetypes):
1072 needed_filetypes.append ('eps')
1074 pathbase = os.path.join (g_outdir, basename)
1075 def f (base, ext1, ext2):
1076 a = os.path.isfile (base + ext2)
1077 if (os.path.isfile (base + ext1) and
1078 os.path.isfile (base + ext2) and
1079 os.stat (base+ext1)[stat.ST_MTIME] >
1080 os.stat (base+ext2)[stat.ST_MTIME]) or \
1081 not os.path.isfile (base + ext2):
1084 if 'tex' in needed_filetypes and f (pathbase, '.ly', '.tex'):
1086 if 'eps' in needed_filetypes and f (pathbase, '.tex', '.eps'):
1088 if 'png' in needed_filetypes and f (pathbase, '.eps', '.png'):
1091 return ('lilypond', body, opts, todo, basename)
1093 def format_lilypond_block (chunk):
1096 Figure out what should be left MAIN_STR (meant
1097 for the main file) from a lilypond chunk: process
1098 verbatim, and other options. Return: multiple chunks.
1106 (type, body, opts, todo, basename) = chunk
1107 assert type == 'lilypond'
1111 filename_chunk = None
1112 if 'printfilename' in opts:
1114 m= re.match ("filename=(.*)", o)
1116 template = get_output ("output-filename")
1117 b = basename + '.ly'
1118 human_base = os.path.basename (m.group (1))
1120 ## todo: include path, but strip
1121 ## first part of the path.
1122 filename_chunk = ('input', template % (human_base, b,human_base))
1126 if 'smallverbatim' in opts:
1127 newbody += output_verbatim (body, 1)
1128 elif 'verbatim' in opts:
1129 newbody += output_verbatim (body, 0)
1132 m = re.search ('intertext="(.*?)"', o)
1134 newbody = newbody + "\n"
1135 if format == 'texi':
1136 newbody = newbody + "@noindent\n"
1137 elif format == 'latex':
1138 newbody = newbody + "\\noindent\n"
1139 newbody = newbody + m.group (1) + "\n"
1141 if 'noinline' in opts:
1142 s = 'output-noinline'
1143 elif format == 'latex':
1148 s = 'output-latex-quoted'
1150 s = 'output-latex-noquote'
1151 elif format == 'texi':
1153 s = 'output-texi-quoted'
1155 s = 'output-texi-noquote'
1156 else: # format == 'html'
1159 def html_pages (basename):
1160 pat = os.path.join (g_outdir, "%s-page*.png"% basename)
1162 files = glob.glob (pat)
1165 template = '''<img align="center" valign="center"
1166 border="0" src="%s" alt="[picture of music]">'''
1170 files = [basename+'.png' ]
1172 files = map (os.path.basename, files)
1177 str = '<a href="%s.ly">%s</a>' % (basename, str)
1182 newbody = newbody + get_output (s) % {'fn': basename,
1183 'htmlimages': html_pages(basename)
1187 return_chunks += [filename_chunk]
1189 return_chunks += [('lilypond', newbody, opts, todo, basename)]
1191 return return_chunks
1193 def format_lilypond_output_bodies (chunks):
1197 if c[0] == 'lilypond':
1198 newchunks += format_lilypond_block (c)
1200 newchunks.append (c)
1206 def process_lilypond_blocks (chunks):#ugh rename
1208 # Count sections/chapters.
1210 if c[0] == 'lilypond':
1211 c = schedule_lilypond_block (c)
1212 elif c[0] == 'numcols':
1213 paperguru.m_num_cols = c[2]
1214 elif c[0] == 'multicols':
1215 paperguru.m_multicols = c[2]
1217 newchunks.append (c)
1221 def process_ly2dvi_blocks (chunks):
1223 def process_ly2dvi_block (chunk):
1226 Run ly2dvi script on filename specified in CHUNK.
1227 This is only supported for HTML output.
1229 In HTML output it will leave a download menu with ps/pdf/midi etc. in
1230 a separate HTML file, and a title + preview in the main html file,
1231 linking to the menu.
1234 (tag, name, opts) = chunk
1235 assert format == 'html'
1236 (content, original_name) = find_file (name)
1238 original_name = os.path.basename (original_name)
1240 base = unique_file_name (content)
1241 outname = base + '.ly'
1242 changed = update_file (content, outname)
1244 preview = base + ".png"
1245 preview_page = base + '-page1.png'
1247 if changed or not (os.path.isfile (preview) or
1248 os.path.isfile (preview_page)):
1250 ly.system ('%s --preview --postscript --verbose %s ' % (ly2dvi_binary, base) )
1252 ly.make_ps_images (base)
1253 ly.system ('gzip -9 - < %s.ps > %s.ps.gz' % (base, base))
1256 b = os.stat(fn)[stat.ST_SIZE]
1258 return '%d bytes' % b
1260 return '%d kb' % (b >> 10)
1262 return '%d mb' % (b >> 20)
1265 'pdf' : "Print (PDF, %s)",
1266 'ps.gz' : "Print (gzipped PostScript, %s)",
1267 'png' : "View (PNG, %s)",
1268 'midi' : "Listen (MIDI, %s)",
1269 'ly' : "View source code (%s)",
1273 page_files = glob.glob ('%s-page*.png' % base)
1275 for p in string.split (page_files, '\n'):
1277 if os.path.isfile (p):
1279 page = re.sub ('.*page([0-9])+.*', 'View page \\1 (PNG picture, %s)\n', p)
1281 menu += '<li><a href="%s">%s</a>' % (p, page)
1283 ext_order = ['ly', 'pdf', 'ps.gz', 'midi']
1286 print 'checking,' , fn
1287 if not os.path.isfile (fn):
1290 entry = exts[e] % size_str (fn)
1292 ## TODO: do something like
1293 ## this for texinfo/latex as well ?
1295 menu += '<li><a href="%s">%s</a>\n\n' % (fn, entry)
1298 explanatory_para = """The pictures are 90dpi
1299 anti-aliased snapshots of the printed output, in PNG format. Both PDF and PS
1300 use scalable fonts and should look OK at any resolution."""
1303 <title>LilyPond example %s</title>
1309 <ul>%s</ul>''' % (original_name,original_name, preview, explanatory_para, menu)
1311 open (base + '.html','w'). write (separate_menu)
1313 inline_menu = '<p/><a href="%s.html"><img src="%s"><p/></a>' % (base, original_name, preview)
1315 return ('ly2dvi', inline_menu)
1319 if c[0] == 'ly2dvi':
1320 c = process_ly2dvi_block (c)
1321 newchunks.append (c)
1325 def compile_all_files (chunks):
1332 if c[0] != 'lilypond':
1342 if base + '.ly' not in tex:
1343 tex.append (base + '.ly')
1344 elif e == 'png' and g_do_pictures:
1350 # fixme: be sys-independent.
1352 if g_outdir and x[0] != '/' :
1353 x = os.path.join (g_here_dir, x)
1356 incs = map (incl_opt, include_path)
1357 lilyopts = string.join (incs)
1359 lilyopts += ' --dependencies'
1361 lilyopts += ' --dep-prefix=' + g_outdir + '/'
1362 lilyopts += ' --header=texidoc'
1363 texfiles = string.join (tex)
1364 cmd = string.join ((lilypond_binary, lilyopts, g_extra_opts,
1366 ly.system (cmd, ignore_error = 0, progress_p = 1)
1369 # Ugh, fixing up dependencies for .tex generation
1372 depfiles=map (lambda x: re.sub ('(.*)\.ly', '\\1.dep',
1379 text=re.sub ('\n([^:\n]*):',
1380 '\n' + foutn + ':', text)
1386 cmd = r"latex '\nonstopmode \input %s'" % file
1387 # Ugh. (La)TeX writes progress and error messages on stdout
1388 # Redirect to stderr
1389 cmd += ' 1>/dev/stderr'
1391 ly.system ("dvips -E -o %s.eps %s" % (file, file))
1394 map (ly.make_ps_images, map (lambda x: x + '.eps', png))
1398 def update_file (body, name):
1400 write the body if it has changed. Return whether BODY has changed.
1411 f = open (name , 'w')
1418 def write_deps (fn, target, chunks):
1420 sys.stderr.write ('Writing `%s\'\n' % os.path.join (g_outdir, fn))
1421 f = open (os.path.join (g_outdir, fn), 'w')
1422 f.write ('%s%s: ' % (g_dep_prefix, target))
1423 for d in read_files:
1427 ## There used to be code to write .tex dependencies, but
1428 ## that is silly: lilypond-book has its own dependency scheme
1429 ## to ensure that all lily-XXX.tex files are there
1436 def check_texidoc (chunks):
1437 ## TODO: put file name in front of texidoc.
1441 if c[0] == 'lilypond':
1442 (type, body, opts, todo, basename) = c;
1443 pathbase = os.path.join (g_outdir, basename)
1444 if os.path.isfile (pathbase + '.texidoc') \
1445 and 'notexidoc' not in opts:
1446 n.append( ('input', '\n@include %s.texidoc\n\n' % basename))
1451 ## what's this? Docme --hwn
1453 def fix_epswidth (chunks):
1456 if c[0] != 'lilypond' or 'eps' not in c[2]:
1457 newchunks.append (c)
1462 m = re.match ('magnification=([0-9.]+)', o)
1464 mag = string.atof (m.group (1))
1466 def replace_eps_dim (match, lmag = mag):
1467 filename = match.group (1)
1468 dims = bounding_box_dimensions (filename)
1470 return '%fpt' % (dims[0] *lmag)
1472 body = re.sub (r'''\\lilypondepswidth{(.*?)}''', replace_eps_dim, c[1])
1473 newchunks.append (('lilypond', body, c[2], c[3], c[4]))
1478 ##docme: why global?
1481 def do_file (input_filename):
1482 chunks = read_doc_file (input_filename)
1483 chunks = chop_chunks (chunks, 'ly2dvi', make_ly2dvi_block, 1)
1484 chunks = chop_chunks (chunks, 'lilypond', make_lilypond, 1)
1485 chunks = chop_chunks (chunks, 'lilypond-file', make_lilypond_file, 1)
1486 chunks = chop_chunks (chunks, 'lilypond-block', make_lilypond_block, 1)
1487 chunks = chop_chunks (chunks, 'singleline-comment', do_ignore, 1)
1488 chunks = chop_chunks (chunks, 'preamble-end', do_preamble_end)
1489 chunks = chop_chunks (chunks, 'numcols', do_columns)
1490 chunks = chop_chunks (chunks, 'multicols', do_multicols)
1492 scan_preamble (chunks)
1493 chunks = process_lilypond_blocks (chunks)
1494 chunks = process_ly2dvi_blocks (chunks)
1497 global g_run_lilypond
1499 compile_all_files (chunks)
1500 chunks = fix_epswidth (chunks)
1503 chunks = format_lilypond_output_bodies (chunks)
1505 if format == 'texi':
1506 chunks = check_texidoc (chunks)
1510 chunks = completize_preamble (chunks)
1515 my_outname = outname
1516 elif input_filename == '-' or input_filename == "/dev/stdin":
1519 my_outname = os.path.basename (os.path.splitext (input_filename)[0]) + '.' + format
1520 my_depname = my_outname + '.dep'
1522 if my_outname == '-' or my_outname == '/dev/stdout':
1528 foutn = os.path.join (g_outdir, my_outname)
1529 sys.stderr.write ("Writing `%s'\n" % foutn)
1530 fout = open (foutn, 'w')
1537 write_deps (my_depname, foutn, chunks)
1541 (sh, long) = ly.getopt_args (option_definitions)
1542 (options, files) = getopt.getopt (sys.argv[1:], sh, long)
1544 except getopt.error, msg:
1545 sys.stderr.write ('\n')
1546 ly.error (_ ("getopt says: `%s\'" % s))
1547 sys.stderr.write ('\n')
1556 if o == '--include' or o == '-I':
1557 include_path.append (a)
1558 elif o == '--version' or o == '-v':
1559 ly.identify (sys.stdout)
1561 elif o == '--verbose' or o == '-V':
1563 elif o == '--format' or o == '-f':
1565 if a == 'texi-html':
1568 elif o == '--outname' or o == '-o':
1571 sys.stderr.write ("Lilypond-book is confused by --outname on multiple files")
1574 elif o == '--help' or o == '-h':
1577 elif o == '--no-lily' or o == '-n':
1579 elif o == '--preview-resolution':
1580 preview_resolution = string.atoi (a)
1581 elif o == '--dependencies' or o == '-M':
1583 elif o == '--default-music-fontsize':
1584 default_music_fontsize = string.atoi (a)
1585 elif o == '--default-lilypond-fontsize':
1586 print "--default-lilypond-fontsize is deprecated, use --default-music-fontsize"
1587 default_music_fontsize = string.atoi (a)
1588 elif o == '--extra-options':
1590 elif o == '--force-music-fontsize':
1591 g_force_music_fontsize = string.atoi (a)
1592 elif o == '--force-lilypond-fontsize':
1593 print "--force-lilypond-fontsize is deprecated, use --default-lilypond-fontsize"
1594 g_force_music_fontsize = string.atoi (a)
1595 elif o == '--dep-prefix':
1597 elif o == '--no-pictures':
1599 elif o == '--no-music':
1601 elif o == '--outdir':
1603 elif o == '--warranty' or o == '-w':
1604 #status = os.system ('lilypond -w')
1609 ly.identify (sys.stderr)
1612 if os.path.isfile (g_outdir):
1613 error ("outdir is a file: %s" % g_outdir)
1614 if not os.path.exists (g_outdir):
1619 ly.error (_ ("no files specified on command line"))
1622 ly.setup_environment ()
1625 for input_filename in files:
1626 do_file (input_filename)
1630 # Petr, ik zou willen dat ik iets zinvoller deed,
1631 # maar wat ik kan ik doen, het verandert toch niets?