X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=7b79c9e2046e1af8bd411f3d66dac2588f4a8130;hb=bc4806967a924f0a705f1418293f3948fcf938af;hp=ff90a2865905778641508226f212fbb66fe1270b;hpb=bbd80ffd2a9e17a9ccabd2fbc95a6b9fc51beb48;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index ff90a28659..7b79c9e204 100644 --- a/scripts/lilypond-book.py +++ b/scripts/lilypond-book.py @@ -13,10 +13,14 @@ classic lilypond-book: lilypond-book --process="lilypond" BOOK.tely TODO: - * ly-options: intertext ? - * --linewidth? - * eps in latex / eps by lilypond -fps ? + + * this script is too complex. Modularize. + + * ly-options: intertext? + * --line-width? + * eps in latex / eps by lilypond -b ps? * check latex parameters, twocolumn, multicolumn? + * use --png --ps --pdf for making images? * Converting from lilypond-book source, substitute: @mbinclude foo.itely -> @include foo.itely @@ -28,93 +32,127 @@ import __main__ import glob import stat import string +import tempfile +import commands +import optparse +import os +import sys +import re -# -# TODO: -# -# * use --png --ps --pdf for making images? -# - -################################################################ # Users of python modules should include this snippet # and customize variables below. -# We'll suffer this path init stuff as long as we don't install our -# python packages in /lib/pythonx.y (and don't kludge around -# it as we do with teTeX on Red Hat Linux: set some environment var -# (PYTHONPATH) in profile) +# We'll suffer this path initialization stuff as long as we don't install +# our python packages in /lib/pythonX.Y + +# If set, LILYPONDPREFIX must take prevalence. +# if datadir is not set, we're doing a build and LILYPONDPREFIX. + +################ +# RELOCATION +################ -# If set, LILYPONDPREFIX must take prevalence -# if datadir is not set, we're doing a build and LILYPONDPREFIX -import getopt, os, sys datadir = '@local_lilypond_datadir@' if not os.path.isdir (datadir): datadir = '@lilypond_datadir@' + +sys.path.insert (0, os.path.join (datadir, 'python')) + if os.environ.has_key ('LILYPONDPREFIX'): datadir = os.environ['LILYPONDPREFIX'] while datadir[-1] == os.sep: datadir= datadir[:-1] - + + datadir = os.path.join (datadir, "share/lilypond/current/") sys.path.insert (0, os.path.join (datadir, 'python')) -# Customize these -#if __name__ == '__main__': +# dynamic relocation, for GUB binaries. +bindir = os.path.split (sys.argv[0])[0] +for p in ['share', 'lib']: + datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % p) + sys.path.insert (0, os.path.join (datadir)) + import lilylib as ly +import fontextract global _;_=ly._ -global re;re = ly.re -# lilylib globals +# Lilylib globals. program_version = '@TOPLEVEL_VERSION@' -program_name = sys.argv[0] -verbose_p = 0 -pseudo_filter_p = 0 +program_name = os.path.basename (sys.argv[0]) + original_dir = os.getcwd () +backend = 'ps' +help_summary = _ ( +'''Process LilyPond snippets in hybrid HTML, LaTeX, or texinfo document. -help_summary = _ ("""Process LilyPond snippets in hybrid HTML, LaTeX or texinfo document. Example usage: lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" BOOK lilypond-book --process='lilypond -I include' BOOK - -""") +''') copyright = ('Jan Nieuwenhuizen ', 'Han-Wen Nienhuys ') -option_definitions = [ - (_ ("EXT"), 'f', 'format', _ ("use output format EXT (texi [default], texi-html, latex, html)")), - (_ ("FILTER"), 'F', 'filter', _ ("pipe snippets through FILTER [convert-ly -n -]")), - ('', 'h', 'help', _ ("print this help")), - (_ ("DIR"), 'I', 'include', _ ("add DIR to include path")), - (_ ("COMMAND"), 'P', 'process', _ ("process ly_files using COMMAND FILE...")), - (_ ("DIR"), 'o', 'output', _ ("write output to DIR")), - ('', 'V', 'verbose', _ ("be verbose")), - ('', 'v', 'version', _ ("print version information")), - ('', 'w', 'warranty', _ ("show warranty and copyright")), - ] - -include_path = [ly.abspath (os.getcwd ())] +def get_option_parser (): + p = ly.get_option_parser (usage='lilypond-book [OPTIONS] FILE', + version="@TOPLEVEL_VERSION@", + description=help_summary) + + p.add_option ('-F', '--filter', metavar=_ ("FILTER"), + action="store", + dest="filter_cmd", + help=_ ("pipe snippets through FILTER [convert-ly -n -]"), + default=None) + p.add_option ('-f', '--format', help=_('''use output format FORMAT (texi [default], texi-html, latex, html)'''), + action='store') + p.add_option ("-I", '--include', help=_('add DIR to include path'), + metavar="DIR", + action='append', dest='include_path', + default=[os.path.abspath (os.getcwd ())]) + + p.add_option ("-o", '--output', help=_('write output to DIR'), + metavar="DIR", + action='store', dest='output_name', default=None) + p.add_option ('-P', '--process', metavar=_("COMMAND"), + help = _ ("process ly_files using COMMAND FILE..."), + action='store', + dest='process_cmd', default='lilypond -b eps') + + p.add_option ('', '--psfonts', action="store_true", dest="psfonts", + help=_ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX''' + '''must use this with dvips -h INPUT.psfonts'''), + default=None) + p.add_option ('-V', '--verbose', help=_("be verbose"), action="store_true", + dest="verbose") + + p.add_option ('-w', '--warranty', help=_("show warranty and copyright"), + action='store_true'), + + + p.add_option_group ('bugs', + description='''Report bugs via http://post.gmane.org/post.php''' + '''?group=gmane.comp.gnu.lilypond.bugs\n''') + + return p + lilypond_binary = os.path.join ('@bindir@', 'lilypond') -# only use installed binary when we're installed too. +# Only use installed binary when we are installed too. if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary): lilypond_binary = 'lilypond' +global_options = None + -use_hash_p = 1 -format = 0 -output_name = 0 -latex_filter_cmd = 'latex "\\nonstopmode \input /dev/stdin"' -filter_cmd = 0 -process_cmd = '' -default_ly_options = {} +default_ly_options = { 'alt': "[image of music]" } # -# is this pythonic? Personally, I find this rather #define-nesque. --hwn +# Is this pythonic? Personally, I find this rather #define-nesque. --hwn # AFTER = 'after' BEFORE = 'before' @@ -125,493 +163,531 @@ HTML = 'html' INDENT = 'indent' LATEX = 'latex' LAYOUT = 'layout' -LINEWIDTH = 'linewidth' +LINE_WIDTH = 'line-width' NOFRAGMENT = 'nofragment' NOINDENT = 'noindent' +NOQUOTE = 'noquote' NOTES = 'body' NOTIME = 'notime' OUTPUT = 'output' +OUTPUTIMAGE = 'outputimage' +PACKED = 'packed' PAPER = 'paper' PREAMBLE = 'preamble' PRINTFILENAME = 'printfilename' QUOTE = 'quote' -RAGGEDRIGHT = 'raggedright' +RAGGED_RIGHT = 'ragged-right' RELATIVE = 'relative' STAFFSIZE = 'staffsize' TEXIDOC = 'texidoc' TEXINFO = 'texinfo' VERBATIM = 'verbatim' +FONTLOAD = 'fontload' +FILENAME = 'filename' +ALT = 'alt' + -# Recognize special sequences in the input; +# NOTIME has no opposite so it isn't part of this dictionary. +# NOQUOTE is used internally only. +no_options = { + NOFRAGMENT: FRAGMENT, + NOINDENT: INDENT, +} + + +# Recognize special sequences in the input. # -# (?Pregex) -- assign result of REGEX to NAME -# *? -- match non-greedily. -# (?m) -- multiline regex: make ^ and $ match at each line -# (?s) -- make the dot match all characters including newline -# (?x) -- ignore whitespace in patterns +# (?Pregex) -- Assign result of REGEX to NAME. +# *? -- Match non-greedily. +# (?m) -- Multiline regex: Make ^ and $ match at each line. +# (?s) -- Make the dot match all characters including newline. +# (?x) -- Ignore whitespace in patterns. no_match = 'a\ba' snippet_res = { + ## HTML: { - 'include': - no_match, - 'lilypond': - r'''(?mx) - (?P - [^:]*):) - (?P.*?) - />)''', - 'lilypond_block': - r'''(?msx) - (?P - [^>]+)? - > - (?P.*?) - )''', - 'lilypond_file': - r'''(?mx) - (?P - [^>]+)? - >\s* - (?P[^<]+)\s* - )''', - 'multiline_comment': - r'''(?smx) - (?P - \s*(?!@c\s+) - (?P) - \s)''', - 'singleline_comment': - no_match, - 'verb': - r'''(?x) - (?P - (?P
.*?
))''', - 'verbatim': - r'''(?x) - (?s) - (?P - (?P
\s.*?
\s))''', + 'include': + no_match, + + 'lilypond': + r'''(?mx) + (?P + .*?)\s*:)?\s* + (?P.*?) + />)''', + + 'lilypond_block': + r'''(?msx) + (?P + .*?)\s* + > + (?P.*?) + )''', + + 'lilypond_file': + r'''(?mx) + (?P + .*?)\s* + > + \s*(?P.*?)\s* + )''', + + 'multiline_comment': + r'''(?smx) + (?P + \s*(?!@c\s+) + (?P) + \s)''', + + 'singleline_comment': + no_match, + + 'verb': + r'''(?x) + (?P + (?P
.*?
))''', + + 'verbatim': + r'''(?x) + (?s) + (?P + (?P
\s.*?
\s))''', }, + ## LATEX: { - 'include': - r'''(?smx) - ^[^%\n]*? - (?P - \\input\s*{ - (?P\S+?) - })''', - 'lilypond': - r'''(?smx) - ^[^%\n]*? - (?P - \\lilypond\s*( - \[ - (?P.*?) - \])?\s*{ - (?P.*?) - })''', - 'lilypond_block': - r'''(?smx) - ^[^%\n]*? - (?P - \\begin\s*( - \[ - (?P.*?) - \])?\s*{lilypond} - (?P.*?) - ^[^%\n]*? - \\end\s*{lilypond})''', - 'lilypond_file': - r'''(?smx) - ^[^%\n]*? - (?P - \\lilypondfile\s*( - \[ - (?P.*?) - \])?\s*\{ - (?P\S+?) - })''', - 'multiline_comment': - no_match, - 'singleline_comment': - r'''(?mx) - ^.*? - (?P - (?P - %.*$\n+))''', - 'verb': - r'''(?mx) - ^[^%\n]*? - (?P - (?P - \\verb(?P.) - .*? - (?P=del)))''', - 'verbatim': - r'''(?msx) - ^[^%\n]*? - (?P - (?P - \\begin\s*{verbatim} - .*? - \\end\s*{verbatim}))''', + 'include': + r'''(?smx) + ^[^%\n]*? + (?P + \\input\s*{ + (?P\S+?) + })''', + + 'lilypond': + r'''(?smx) + ^[^%\n]*? + (?P + \\lilypond\s*( + \[ + \s*(?P.*?)\s* + \])?\s*{ + (?P.*?) + })''', + + 'lilypond_block': + r'''(?smx) + ^[^%\n]*? + (?P + \\begin\s*( + \[ + \s*(?P.*?)\s* + \])?\s*{lilypond} + (?P.*?) + ^[^%\n]*? + \\end\s*{lilypond})''', + + 'lilypond_file': + r'''(?smx) + ^[^%\n]*? + (?P + \\lilypondfile\s*( + \[ + \s*(?P.*?)\s* + \])?\s*\{ + (?P\S+?) + })''', + + 'multiline_comment': + no_match, + + 'singleline_comment': + r'''(?mx) + ^.*? + (?P + (?P + %.*$\n+))''', + + 'verb': + r'''(?mx) + ^[^%\n]*? + (?P + (?P + \\verb(?P.) + .*? + (?P=del)))''', + + 'verbatim': + r'''(?msx) + ^[^%\n]*? + (?P + (?P + \\begin\s*{verbatim} + .*? + \\end\s*{verbatim}))''', }, + ## TEXINFO: { - 'include': - r'''(?mx) - ^(?P - @include\s+ - (?P\S+))''', - 'lilypond': - r'''(?smx) - ^[^\n]*?(?!@c\s+)[^\n]*? - (?P - @lilypond\s*( - \[ - (?P.*?) - \])?\s*{ - (?P.*?) - })''', - 'lilypond_block': - r'''(?msx) - ^(?P - @lilypond\s*( - \[ - (?P.*?) - \])?\s+? - ^(?P.*?) - ^@end\s+lilypond)\s''', - 'lilypond_file': - r'''(?mx) - ^(?P - @lilypondfile\s*( - \[ - (?P.*?) - \])?\s*{ - (?P\S+) - })''', - 'multiline_comment': - r'''(?smx) - ^(?P - (?P - @ignore\s - .*? - @end\s+ignore))\s''', - 'singleline_comment': - r'''(?mx) - ^.* - (?P - (?P - @c([ \t][^\n]*|)\n))''', - -# don't do this: fucks up with @code{@{} -# 'verb': r'''(?P@code{.*?})''', - 'verbatim': - r'''(?sx) - (?P - (?P - @example - \s.*? - @end\s+example\s))''', + 'include': + r'''(?mx) + ^(?P + @include\s+ + (?P\S+))''', + + 'lilypond': + r'''(?smx) + ^[^\n]*?(?!@c\s+)[^\n]*? + (?P + @lilypond\s*( + \[ + \s*(?P.*?)\s* + \])?\s*{ + (?P.*?) + })''', + + 'lilypond_block': + r'''(?msx) + ^(?P + @lilypond\s*( + \[ + \s*(?P.*?)\s* + \])?\s+? + ^(?P.*?) + ^@end\s+lilypond)\s''', + + 'lilypond_file': + r'''(?mx) + ^(?P + @lilypondfile\s*( + \[ + \s*(?P.*?)\s* + \])?\s*{ + (?P\S+) + })''', + + 'multiline_comment': + r'''(?smx) + ^(?P + (?P + @ignore\s + .*? + @end\s+ignore))\s''', + + 'singleline_comment': + r'''(?mx) + ^.* + (?P + (?P + @c([ \t][^\n]*|)\n))''', + + # Don't do this: It interferes with @code{@{}. + # 'verb': r'''(?P@code{.*?})''', + + 'verbatim': + r'''(?sx) + (?P + (?P + @example + \s.*? + @end\s+example\s))''', }, } + + + format_res = { HTML: { - 'option-sep': '\s*', - 'intertext': r',?\s*intertext=\".*?\"', + 'intertext': r',?\s*intertext=\".*?\"', + 'option_sep': '\s*', }, + LATEX: { - 'intertext': r',?\s*intertext=\".*?\"', - 'option-sep': ',\s*', + 'intertext': r',?\s*intertext=\".*?\"', + 'option_sep': '\s*,\s*', }, + TEXINFO: { - 'intertext': r',?\s*intertext=\".*?\"', - 'option-sep': ',\s*', + 'intertext': r',?\s*intertext=\".*?\"', + 'option_sep': '\s*,\s*', }, } +# Options without a pattern in ly_options. +simple_options = [ + EXAMPLEINDENT, + FRAGMENT, + NOFRAGMENT, + NOINDENT, + PRINTFILENAME, + TEXIDOC, + VERBATIM, + FONTLOAD, + FILENAME, + ALT +] + ly_options = { + ## NOTES: { RELATIVE: r'''\relative c%(relative_quotes)s''', }, + + ## PAPER: { - INDENT: r''' - indent = %(indent)s''', - 'linewidth': r''' - linewidth = %(linewidth)s''', - NOINDENT: r''' - indent = 0.0\mm''', - QUOTE: r''' - linewidth = %(linewidth)s - 2.0 * %(exampleindent)s -''', - RAGGEDRIGHT: r''' - indent = 0.0\mm - raggedright = ##t''', + INDENT: r'''indent = %(indent)s''', + + LINE_WIDTH: r'''line-width = %(line-width)s''', + + QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''', + + RAGGED_RIGHT: r'''ragged-right = ##t''', + + PACKED: r'''packed = ##t''', }, ## LAYOUT: { - EXAMPLEINDENT: '', - NOTIME: r''' - \context { - \Staff - \remove Time_signature_engraver - }''', + \context { + \Score + timing = ##f + } + \context { + \Staff + \remove Time_signature_engraver + }''', }, ## PREAMBLE: { - STAFFSIZE: r''' -#(set-global-staff-size %(staffsize)s)''', + STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''', }, } output = { + ## HTML: { - FILTER: r''' + FILTER: r''' %(code)s ''', - AFTER: r''' + AFTER: r'''

''', - BEFORE: r'''

+ + BEFORE: r'''

''', - OUTPUT: r''' + + OUTPUT: r''' [image of music]''', - PRINTFILENAME:'

%(filename)s

', - QUOTE: r'''
+ border="0" src="%(image)s" alt="%(alt)s">''', + + PRINTFILENAME: '

%(filename)s

', + + QUOTE: r'''
%(str)s
''', - VERBATIM: r'''
+
+		VERBATIM: r'''
 %(verb)s
''', }, - LATEX: { - AFTER: '', - BEFORE: '', - OUTPUT: r'''{\parindent 0pt -\catcode`\@=12 -\ifx\preLilyPondExample\undefined\relax\else\preLilyPondExample\fi + ## + LATEX: { + OUTPUT: r'''{%% +\parindent 0pt%% +\catcode`\@=12%% +\ifx\preLilyPondExample \undefined%% + \relax%% +\else%% + \preLilyPondExample%% +\fi%% \def\lilypondbook{}%% -\input %(base)s.tex -\ifx\postLilyPondExample\undefined\relax\else\postLilyPondExample\fi -\catcode`\@=0}''', - PRINTFILENAME: '''\\texttt{%(filename)s} +\input %(base)s-systems.tex%% +\ifx\postLilyPondExample \undefined%% + \relax%% +\else%% + \postLilyPondExample%% +\fi%% +}''', + + PRINTFILENAME: '''\\texttt{%(filename)s} ''', - QUOTE: r'''\begin{quotation} -%(str)s -\end{quotation} -''', - VERBATIM: r'''\noindent -\begin{verbatim} -%(verb)s\end{verbatim} -''', - FILTER: r'''\begin{lilypond}[%(options)s] + + QUOTE: r'''\begin{quotation}%(str)s +\end{quotation}''', + + VERBATIM: r'''\noindent +\begin{verbatim}%(verb)s\end{verbatim}''', + + FILTER: r'''\begin{lilypond}[%(options)s] %(code)s \end{lilypond}''', }, + ## TEXINFO: { - FILTER: r'''@lilypond[%(options)s] + FILTER: r'''@lilypond[%(options)s] %(code)s @lilypond''', - AFTER: '', - BEFORE: '', - OUTPUT: r'''@noindent -@image{%(base)s,,,[image of music],%(ext)s}''', - PRINTFILENAME: '''@file{%(filename)s} + + OUTPUT: r''' +@iftex +@include %(base)s-systems.texi +@end iftex +''', + + OUTPUTIMAGE: r'''@noindent +@ifinfo +@image{%(base)s,,,%(alt)s,%(ext)s} +@end ifinfo +@html +

+ + %(alt)s + +

+@end html +''', + + PRINTFILENAME: '''@file{%(filename)s} ''', - QUOTE: r'''@quotation -%(str)s -@end quotation + + QUOTE: r'''@quotation +%(str)s@end quotation ''', - # FIXME: @exampleindent 5 is the default... - VERBATIM: r'''@exampleindent 0 + + NOQUOTE: r'''@format +%(str)s@end format +''', + + VERBATIM: r'''@exampleindent 0 @example %(verb)s@end example -@exampleindent 5 ''', }, } -PREAMBLE_LY = r"""%%%% Generated by %(program_name)s +# +# Maintain line numbers. +# + +## TODO +if 0: + for f in [HTML, LATEX]: + for s in (QUOTE, VERBATIM): + output[f][s] = output[f][s].replace("\n"," ") + + +PREAMBLE_LY = '''%%%% Generated by %(program_name)s %%%% Options: [%(option_string)s] -#(set! toplevel-score-handler ly:parser-print-score) -#(set! toplevel-music-handler (lambda (p m) - (ly:parser-print-score - p (ly:music-scorify m p)) - )) + +#(set! toplevel-score-handler print-score-with-defaults) +#(set! toplevel-music-handler + (lambda (p m) + (if (not (eq? (ly:music-property m \'void) #t)) + (print-score-with-defaults + p (scorify-music m p))))) + +#(ly:set-option (quote no-point-and-click)) +#(define inside-lilypond-book #t) +#(define version-seen? #t) %(preamble_string)s + + + + + + +%% **************************************************************** +%% Start cut-&-pastable-section +%% **************************************************************** + \paper { #(define dump-extents #t) + %(font_dump_setting)s %(paper_string)s } -\layout { %(layout_string)s -} - -""" - -FRAGMENT_LY = r''' - %(notes_string)s{ - %(code)s } -''' -FULL_LY = '%(code)s' -texinfo_linewidths = { - '@afourpaper': '160 \\mm', - '@afourwide': '6.5 \\in', - '@afourlatex': '150 \\mm', - '@smallbook': '5 \\in' , - '@letterpaper': '6\\in', +\layout { + %(layout_string)s } +''' -def classic_lilypond_book_compatibility (o): - if o == 'singleline': - return RAGGEDRIGHT - m = re.search ('relative\s*([-0-9])', o) - if m: - return 'relative=%s' % m.group (1) - m = re.match ('([0-9]+)pt', o) - if m: - return 'staffsize=%s' % m.group (1) - m = re.match ('indent=([-.0-9]+)(cm|in|mm|pt|staffspace)', o) - if m: - f = float (m.group (1)) - return 'indent=%f\\%s' % (f, m.group (2)) - m = re.match ('linewidth=([-.0-9]+)(cm|in|mm|pt|staffspace)', o) - if m: - f = float (m.group (1)) - return 'linewidth=%f\\%s' % (f, m.group (2)) - return None - -def compose_ly (code, options): - #Hmm - for i in default_ly_options.keys (): - if i not in options: - options.append (i) - - #Hmm - if QUOTE in options and LINEWIDTH in options: - options.remove (LINEWIDTH) - - if FRAGMENT in options: - if RAGGEDRIGHT not in options: - options.append (RAGGEDRIGHT) - body = FRAGMENT_LY - else: - body = FULL_LY - - # defaults - relative = 1 - staffsize = 16 - override = {} - #FIXME: where to get sane value for exampleindent? - override[EXAMPLEINDENT] = r'9.0 \mm' - override[LINEWIDTH] = None - override.update (default_ly_options) - - option_string = string.join (options, ',') - - options_dict = {} - option_types = [NOTES, PREAMBLE, LAYOUT, PAPER] - for a in option_types: - options_dict[a] = [] - - for i in options: - c = classic_lilypond_book_compatibility (i) - if c: - ly.warning (_ ("deprecated ly-option used: %s" % i)) - ly.warning (_ ("compatibility mode translation: %s" \ - % c)) - i = c - - if string.find (i, '=') > 0: - key, value = string.split (i, '=') - override[key] = value - else: - key = i - if not override.has_key (i): - override[i] = None - - found = 0 - for type in option_types: - if ly_options[type].has_key (key): +FRAGMENT_LY = r''' +%(notes_string)s +{ - options_dict[type].append (ly_options[type][key]) - found = 1 - break - if not found and key not in (FRAGMENT, NOFRAGMENT, PRINTFILENAME, - RELATIVE, VERBATIM, TEXIDOC): - ly.warning (_("ignoring unknown ly option: %s") % i) +%% **************************************************************** +%% ly snippet contents follows: +%% **************************************************************** +%(code)s - #URGS - if RELATIVE in override.keys () and override[RELATIVE]: - relative = string.atoi (override[RELATIVE]) - relative_quotes = '' +%% **************************************************************** +%% end ly snippet +%% **************************************************************** +} +''' - # 1 = central C - if relative < 0: - relative_quotes += ',' * (- relative) - elif relative > 0: - relative_quotes += "'" * relative +FULL_LY = ''' - program_name = __main__.program_name - paper_string = string.join (options_dict[PAPER], '\n ') % override - layout_string = string.join (options_dict[LAYOUT], '\n ') % override - notes_string = string.join (options_dict[NOTES], '\n ') % vars () - preamble_string = string.join (options_dict[PREAMBLE], '\n ') % override - return (PREAMBLE_LY + body) % vars () +%% **************************************************************** +%% ly snippet: +%% **************************************************************** +%(code)s -# BARF -# use lilypond for latex (.lytex) books, -# and lilypond --preview for html, texinfo books? -def to_eps (file): - cmd = r'latex "\nonstopmode \input %s"' % file - # Ugh. (La)TeX writes progress and error messages on stdout - # Redirect to stderr - cmd = '(( %s >&2 ) >&- )' % cmd - ly.system (cmd) - ly.system ('dvips -Ppdf -u+ec-mftrace.map -u+lilypond.map -E -o %s.eps %s' \ - % (file, file)) +%% **************************************************************** +%% end ly snippet +%% **************************************************************** +''' +texinfo_line_widths = { + '@afourpaper': '160\\mm', + '@afourwide': '6.5\\in', + '@afourlatex': '150\\mm', + '@smallbook': '5\\in', + '@letterpaper': '6\\in', +} - # check if it really is EPS. - # Otherwise music glyphs disappear from 2nd and following pages. +def classic_lilypond_book_compatibility (key, value): + if key == 'singleline' and value == None: + return (RAGGED_RIGHT, None) - # TODO: should run dvips -pp -E per page, then we get proper - # cropping as well. + m = re.search ('relative\s*([-0-9])', key) + if m: + return ('relative', m.group (1)) - f = open ('%s.eps' % file) - for x in range(0,10): - if re.search ("^%%Pages: ", f.readline ()): + m = re.match ('([0-9]+)pt', key) + if m: + return ('staffsize', m.group (1)) - # make non EPS. - ly.system ('dvips -Ppdf -u+ec-mftrace.map -u+lilypond.map -o %s.eps %s' \ - % (file, file)) - break + if key == 'indent' or key == 'line-width': + m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value) + if m: + f = float (m.group (1)) + return (key, '%f\\%s' % (f, m.group (2))) + return (None, None) def find_file (name): - for i in include_path: + for i in global_options.include_path: full = os.path.join (i, name) if os.path.exists (full): return full - ly.error (_ ('file not found: %s') % name + '\n') + + ly.error (_ ("file not found: %s") % name + '\n') ly.exit (1) return '' @@ -626,8 +702,37 @@ def verbatim_texinfo (s): re.sub ('@', '@@', s))) def split_options (option_string): - return re.split (format_res[format]['option-sep'], option_string) - + if option_string: + if global_options.format == HTML: + options = re.findall('[\w\.-:]+(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|\S+))?',option_string) + for i in range(len(options)): + options[i] = re.sub('^([^=]+=\s*)(?P["\'])(.*)(?P=q)','\g<1>\g<3>',options[i]) + return options + else: + return re.split (format_res[global_options.format]['option_sep'], + option_string) + return [] + +def set_default_options (source): + global default_ly_options + if not default_ly_options.has_key (LINE_WIDTH): + if global_options.format == LATEX: + textwidth = get_latex_textwidth (source) + default_ly_options[LINE_WIDTH] = \ + '''%.0f\\pt''' % textwidth + elif global_options.format == TEXINFO: + for (k, v) in texinfo_line_widths.items (): + # FIXME: @layout is usually not in + # chunk #0: + # + # \input texinfo @c -*-texinfo-*- + # + # Bluntly search first K items of + # source. + # s = chunks[0].replacement_text () + if re.search (k, source[:1024]): + default_ly_options[LINE_WIDTH] = v + break class Chunk: def replacement_text (self): @@ -636,56 +741,66 @@ class Chunk: def filter_text (self): return self.replacement_text () - def ly_is_outdated (self): return 0 def png_is_outdated (self): return 0 + def is_plain (self): + return False + class Substring (Chunk): - def __init__ (self, source, start, end): + def __init__ (self, source, start, end, line_number): self.source = source self.start = start self.end = end + self.line_number = line_number + self.override_text = None + + def is_plain (self): + return True def replacement_text (self): - return self.source [self.start:self.end] + if self.override_text: + return self.override_text + else: + return self.source[self.start:self.end] class Snippet (Chunk): - def __init__ (self, type, match, format): + def __init__ (self, type, match, format, line_number): self.type = type self.match = match self.hash = 0 - self.options = [] + self.option_dict = {} self.format = format + self.line_number = line_number def replacement_text (self): - return self.match.group (0) + return self.match.group ('match') def substring (self, s): return self.match.group (s) def __repr__ (self): - return `self.__class__` + " type = " + self.type + return `self.__class__` + ' type = ' + self.type class Include_snippet (Snippet): def processed_filename (self): f = self.substring ('filename') - return os.path.splitext (f)[0] + format2ext[format] + return os.path.splitext (f)[0] + format2ext[global_options.format] def replacement_text (self): - s = self.match.group (0) + s = self.match.group ('match') f = self.substring ('filename') return re.sub (f, self.processed_filename (), s) class Lilypond_snippet (Snippet): - def __init__ (self, type, match, format): - Snippet.__init__ (self, type, match, format) + def __init__ (self, type, match, format, line_number): + Snippet.__init__ (self, type, match, format, line_number) os = match.group ('options') - if os: - self.options = split_options (os) + self.do_options (os, self.type) def ly (self): return self.substring ('code') @@ -693,17 +808,189 @@ class Lilypond_snippet (Snippet): def full_ly (self): s = self.ly () if s: - return compose_ly (s, self.options) + return self.compose_ly (s) return '' - # todo: use md5? + def do_options (self, option_string, type): + self.option_dict = {} + + options = split_options (option_string) + + for i in options: + if string.find (i, '=') > 0: + (key, value) = re.split ('\s*=\s*', i) + self.option_dict[key] = value + else: + if i in no_options.keys (): + if no_options[i] in self.option_dict.keys (): + del self.option_dict[no_options[i]] + else: + self.option_dict[i] = None + + has_line_width = self.option_dict.has_key (LINE_WIDTH) + no_line_width_value = 0 + + # If LINE_WIDTH is used without parameter, set it to default. + if has_line_width and self.option_dict[LINE_WIDTH] == None: + no_line_width_value = 1 + del self.option_dict[LINE_WIDTH] + + for i in default_ly_options.keys (): + if i not in self.option_dict.keys (): + self.option_dict[i] = default_ly_options[i] + + if not has_line_width: + if type == 'lilypond' or FRAGMENT in self.option_dict.keys (): + self.option_dict[RAGGED_RIGHT] = None + + if type == 'lilypond': + if LINE_WIDTH in self.option_dict.keys (): + del self.option_dict[LINE_WIDTH] + else: + if RAGGED_RIGHT in self.option_dict.keys (): + if LINE_WIDTH in self.option_dict.keys (): + del self.option_dict[LINE_WIDTH] + + if QUOTE in self.option_dict.keys () or type == 'lilypond': + if LINE_WIDTH in self.option_dict.keys (): + del self.option_dict[LINE_WIDTH] + + if not INDENT in self.option_dict.keys (): + self.option_dict[INDENT] = '0\\mm' + + # The QUOTE pattern from ly_options only emits the `line-width' + # keyword. + if has_line_width and QUOTE in self.option_dict.keys (): + if no_line_width_value: + del self.option_dict[LINE_WIDTH] + else: + del self.option_dict[QUOTE] + + def compose_ly (self, code): + if FRAGMENT in self.option_dict.keys (): + body = FRAGMENT_LY + else: + body = FULL_LY + + # Defaults. + relative = 1 + override = {} + # The original concept of the `exampleindent' option is broken. + # It is not possible to get a sane value for @exampleindent at all + # without processing the document itself. Saying + # + # @exampleindent 0 + # @example + # ... + # @end example + # @exampleindent 5 + # + # causes ugly results with the DVI backend of texinfo since the + # default value for @exampleindent isn't 5em but 0.4in (or a smaller + # value). Executing the above code changes the environment + # indentation to an unknown value because we don't know the amount + # of 1em in advance since it is font-dependent. Modifying + # @exampleindent in the middle of a document is simply not + # supported within texinfo. + # + # As a consequence, the only function of @exampleindent is now to + # specify the amount of indentation for the `quote' option. + # + # To set @exampleindent locally to zero, we use the @format + # environment for non-quoted snippets. + override[EXAMPLEINDENT] = r'0.4\in' + override[LINE_WIDTH] = texinfo_line_widths['@smallbook'] + override.update (default_ly_options) + + option_list = [] + for (key, value) in self.option_dict.items (): + if value == None: + option_list.append (key) + else: + option_list.append (key + '=' + value) + option_string = string.join (option_list, ',') + + compose_dict = {} + compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER] + for a in compose_types: + compose_dict[a] = [] + + for (key, value) in self.option_dict.items (): + (c_key, c_value) = \ + classic_lilypond_book_compatibility (key, value) + if c_key: + if c_value: + ly.warning \ + (_ ("deprecated ly-option used: %s=%s" \ + % (key, value))) + ly.warning \ + (_ ("compatibility mode translation: %s=%s" \ + % (c_key, c_value))) + else: + ly.warning \ + (_ ("deprecated ly-option used: %s" \ + % key)) + ly.warning \ + (_ ("compatibility mode translation: %s" \ + % c_key)) + + (key, value) = (c_key, c_value) + + if value: + override[key] = value + else: + if not override.has_key (key): + override[key] = None + + found = 0 + for type in compose_types: + if ly_options[type].has_key (key): + compose_dict[type].append (ly_options[type][key]) + found = 1 + break + + if not found and key not in simple_options: + ly.warning (_ ("ignoring unknown ly option: %s") % key) + + # URGS + if RELATIVE in override.keys () and override[RELATIVE]: + relative = string.atoi (override[RELATIVE]) + + relative_quotes = '' + + # 1 = central C + if relative < 0: + relative_quotes += ',' * (- relative) + elif relative > 0: + relative_quotes += "'" * relative + + program_name = __main__.program_name + + paper_string = string.join (compose_dict[PAPER], + '\n ') % override + layout_string = string.join (compose_dict[LAYOUT], + '\n ') % override + notes_string = string.join (compose_dict[NOTES], + '\n ') % vars () + preamble_string = string.join (compose_dict[PREAMBLE], + '\n ') % override + + font_dump_setting = '' + if FONTLOAD in self.option_dict: + font_dump_setting = '#(define-public force-eps-font-include #t)\n' + + return (PREAMBLE_LY + body) % vars () + + # TODO: Use md5? def get_hash (self): if not self.hash: self.hash = abs (hash (self.full_ly ())) return self.hash def basename (self): - if use_hash_p: + if FILENAME in self.option_dict: + return self.option_dict[FILENAME] + if global_options.use_hash: return 'lily-%d' % self.get_hash () raise 'to be done' @@ -711,138 +998,174 @@ class Lilypond_snippet (Snippet): outf = open (self.basename () + '.ly', 'w') outf.write (self.full_ly ()) - open (self.basename() + '.txt', 'w').write("image of music") - + open (self.basename () + '.txt', 'w').write ('image of music') def ly_is_outdated (self): base = self.basename () tex_file = '%s.tex' % base + eps_file = '%s.eps' % base + system_file = '%s-systems.tex' % base ly_file = '%s.ly' % base - ok = os.path.exists (ly_file) and os.path.exists (tex_file)\ - and os.stat (tex_file)[stat.ST_SIZE] \ - and open (tex_file).readlines ()[-1][1:-1] \ - == 'lilypondend' - - if ok and (use_hash_p or self.ly () == open (ly_file).read ()): - # TODO: something smart with target formats - # (ps, png) and m/ctimes + ok = os.path.exists (ly_file) \ + and os.path.exists (system_file)\ + and os.stat (system_file)[stat.ST_SIZE] \ + and re.match ('% eof', open (system_file).readlines ()[-1]) + if ok and (not global_options.use_hash or FILENAME in self.option_dict): + ok = (self.full_ly () == open (ly_file).read ()) + if ok: + # TODO: Do something smart with target formats + # (ps, png) and m/ctimes. return None return self def png_is_outdated (self): base = self.basename () ok = self.ly_is_outdated () - if format == HTML or format == TEXINFO: - ok = ok and (os.path.exists (base + '.png') - or glob.glob (base + '-page*.png')) + if global_options.format in (HTML, TEXINFO): + ok = ok and os.path.exists (base + '.eps') + + page_count = 0 + if ok: + page_count = ly.ps_page_count (base + '.eps') + + if page_count == 1: + ok = ok and os.path.exists (base + '.png') + elif page_count > 1: + for a in range (1, page_count + 1): + ok = ok and os.path.exists (base + '-page%d.png' % a) + + return not ok + + def texstr_is_outdated (self): + if backend == 'ps': + return 0 + + base = self.basename () + ok = self.ly_is_outdated () + ok = ok and (os.path.exists (base + '.texstr')) return not ok def filter_text (self): code = self.substring ('code') s = run_filter (code) d = { - 'code': s, - 'options': self.match.group ('options') + 'code': s, + 'options': self.match.group ('options') } # TODO return output[self.format][FILTER] % d def replacement_text (self): - func = Lilypond_snippet.__dict__ ['output_' + self.format] + func = Lilypond_snippet.__dict__['output_' + self.format] return func (self) def get_images (self): base = self.basename () - # URGUGHUGHUGUGHU + # URGUGHUGHUGUGH single = '%(base)s.png' % vars () multiple = '%(base)s-page1.png' % vars () images = (single,) if os.path.exists (multiple) \ - and (not os.path.exists (single)\ + and (not os.path.exists (single) \ or (os.stat (multiple)[stat.ST_MTIME] \ > os.stat (single)[stat.ST_MTIME])): - images = glob.glob ('%(base)s-page*.png' % vars ()) + count = ly.ps_page_count ('%(base)s.eps' % vars ()) + images = ['%s-page%d.png' % (base, a) for a in range (1, count+1)] + images = tuple (images) return images def output_html (self): str = '' base = self.basename () - if format == HTML: + if global_options.format == HTML: str += self.output_print_filename (HTML) - if VERBATIM in self.options: + if VERBATIM in self.option_dict: verb = verbatim_html (self.substring ('code')) str += write (output[HTML][VERBATIM] % vars ()) - if QUOTE in self.options: + if QUOTE in self.option_dict: str = output[HTML][QUOTE] % vars () str += output[HTML][BEFORE] % vars () for image in self.get_images (): - base, ext = os.path.splitext (image) + (base, ext) = os.path.splitext (image) + alt = self.option_dict[ALT] str += output[HTML][OUTPUT] % vars () str += output[HTML][AFTER] % vars () return str def output_info (self): - str = self.output_print_filename (HTML) - str = output[TEXINFO][BEFORE] % vars () + str = '' for image in self.get_images (): - base, ext = os.path.splitext (image) + (base, ext) = os.path.splitext (image) - # URG, makeinfo implicitely prepends dot to ext - # specifying no extension is most robust + # URG, makeinfo implicitly prepends dot to extension. + # Specifying no extension is most robust. ext = '' - str += output[TEXINFO][OUTPUT] % vars () - str += output[TEXINFO][AFTER] % vars () + alt = self.option_dict[ALT] + str += output[TEXINFO][OUTPUTIMAGE] % vars () + + base = self.basename () + str += output[global_options.format][OUTPUT] % vars () return str def output_latex (self): str = '' base = self.basename () - if format == LATEX: + if global_options.format == LATEX: str += self.output_print_filename (LATEX) - if VERBATIM in self.options: + if VERBATIM in self.option_dict: verb = self.substring ('code') str += (output[LATEX][VERBATIM] % vars ()) - if QUOTE in self.options: - str = output[LATEX][QUOTE] % vars () + + str += (output[LATEX][OUTPUT] % vars ()) - str += (output[LATEX][BEFORE] - + (output[LATEX][OUTPUT] % vars ()) - + output[LATEX][AFTER]) + ## todo: maintain breaks + if 0: + breaks = self.ly ().count ("\n") + str += "".ljust (breaks, "\n").replace ("\n","%\n") + + if QUOTE in self.option_dict: + str = output[LATEX][QUOTE] % vars () return str - def output_print_filename (self,format): + def output_print_filename (self, format): str = '' - if PRINTFILENAME in self.options: + if PRINTFILENAME in self.option_dict: base = self.basename () filename = self.substring ('filename') - str = output[format][PRINTFILENAME] % vars () + str = output[global_options.format][PRINTFILENAME] % vars () + return str def output_texinfo (self): str = '' - # self.output_print_filename (TEXINFO) - if self.output_print_filename (0): - str += ('@html\n' + self.output_print_filename (HTML) + if self.output_print_filename (TEXINFO): + str += ('@html\n' + + self.output_print_filename (HTML) + '\n@end html\n') - str += ('@tex\n' + self.output_print_filename (LATEX) + str += ('@tex\n' + + self.output_print_filename (LATEX) + '\n@end tex\n') base = self.basename () - if TEXIDOC in self.options: + if TEXIDOC in self.option_dict: texidoc = base + '.texidoc' if os.path.exists (texidoc): str += '@include %(texidoc)s\n\n' % vars () - if VERBATIM in self.options: + if VERBATIM in self.option_dict: verb = verbatim_texinfo (self.substring ('code')) str += (output[TEXINFO][VERBATIM] % vars ()) + if not QUOTE in self.option_dict: + str = output[TEXINFO][NOQUOTE] % vars () + + str += self.output_info () - str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n') - str += ('@tex\n' + self.output_latex () + '\n@end tex\n') - str += ('@html\n' + self.output_html () + '\n@end html\n') +# str += ('@ifinfo\n' + self.output_info () + '\n@end ifinfo\n') +# str += ('@tex\n' + self.output_latex () + '\n@end tex\n') +# str += ('@html\n' + self.output_html () + '\n@end html\n') - if QUOTE in self.options: + if QUOTE in self.option_dict: str = output[TEXINFO][QUOTE] % vars () # need par after image @@ -853,7 +1176,8 @@ class Lilypond_snippet (Snippet): class Lilypond_file_snippet (Lilypond_snippet): def ly (self): name = self.substring ('filename') - return '\\renameinput \"%s\"\n%s' % (name, open (find_file (name)).read ()) + return '\\sourcefilename \"%s\"\n%s' \ + % (name, open (find_file (name)).read ()) snippet_type_to_class = { 'lilypond_file': Lilypond_file_snippet, @@ -862,18 +1186,38 @@ snippet_type_to_class = { 'include': Include_snippet, } +def find_linestarts (s): + nls = [0] + start = 0 + end = len (s) + while 1: + i = s.find ('\n', start) + if i < 0: + break + + i = i + 1 + nls.append (i) + start = i + + nls.append (len (s)) + return nls + def find_toplevel_snippets (s, types): res = {} for i in types: - res[i] = ly.re.compile (snippet_res[format][i]) + res[i] = ly.re.compile (snippet_res[global_options.format][i]) snippets = [] index = 0 - ## found = dict (map (lambda x: (x, None), types)) + ## found = dict (map (lambda x: (x, None), + ## types)) ## urg python2.1 found = {} - map (lambda x, f=found: f.setdefault (x, None), types) + map (lambda x, f = found: f.setdefault (x, None), + types) + line_starts = find_linestarts (s) + line_start_idx = 0 # We want to search for multiple regexes, without searching # the string multiple times for one regex. # Hence, we use earlier results to limit the string portion @@ -887,6 +1231,7 @@ def find_toplevel_snippets (s, types): for type in types: if not found[type] or found[type][0] < index: found[type] = None + m = res[type].search (s[index:endex]) if not m: continue @@ -894,12 +1239,21 @@ def find_toplevel_snippets (s, types): cl = Snippet if snippet_type_to_class.has_key (type): cl = snippet_type_to_class[type] - snip = cl (type, m, format) + + start = index + m.start ('match') + line_number = line_start_idx + while (line_starts[line_number] < start): + line_number += 1 + + line_number += 1 + snip = cl (type, m, global_options.format, line_number) + found[type] = (start, snip) if found[type] \ - and (not first or found[type][0] < found[first][0]): + and (not first \ + or found[type][0] < found[first][0]): first = type # FIXME. @@ -915,11 +1269,14 @@ def find_toplevel_snippets (s, types): endex = found[first][0] if not first: - snippets.append (Substring (s, index, len (s))) + snippets.append (Substring (s, index, len (s), line_start_idx)) break + while (start > line_starts[line_start_idx+1]): + line_start_idx += 1 + (start, snip) = found[first] - snippets.append (Substring (s, index, start)) + snippets.append (Substring (s, index, start, line_start_idx + 1)) snippets.append (snip) found[first] = None index = start + len (snip.match.group ('match')) @@ -927,10 +1284,10 @@ def find_toplevel_snippets (s, types): return snippets def filter_pipe (input, cmd): - if verbose_p: - ly.progress (_ ("Opening filter `%s\'") % cmd) + if global_options.verbose: + ly.progress (_ ("Opening filter `%s'") % cmd) - stdin, stdout, stderr = os.popen3 (cmd) + (stdin, stdout, stderr) = os.popen3 (cmd) stdin.write (input) status = stdin.close () @@ -945,19 +1302,19 @@ def filter_pipe (input, cmd): signal = 0x0f & status if status or (not output and error): exit_status = status >> 8 - ly.error (_ ("`%s\' failed (%d)") % (cmd, exit_status)) + ly.error (_ ("`%s' failed (%d)") % (cmd, exit_status)) ly.error (_ ("The error log is as follows:")) sys.stderr.write (error) sys.stderr.write (stderr.read ()) ly.exit (status) - if verbose_p: + if global_options.verbose: ly.progress ('\n') return output def run_filter (s): - return filter_pipe (s, filter_cmd) + return filter_pipe (s, global_options.filter_cmd) def is_derived_class (cl, baseclass): if cl == baseclass: @@ -967,33 +1324,37 @@ def is_derived_class (cl, baseclass): return 1 return 0 - -def process_snippets (cmd, ly_snippets, png_snippets): - ly_names = filter (lambda x: x, map (Lilypond_snippet.basename, ly_snippets)) - png_names = filter (lambda x: x, map (Lilypond_snippet.basename, png_snippets)) - +def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets): + ly_names = filter (lambda x: x, + map (Lilypond_snippet.basename, ly_snippets)) + texstr_names = filter (lambda x: x, + map (Lilypond_snippet.basename, texstr_snippets)) + png_names = filter (lambda x: x, + map (Lilypond_snippet.basename, png_snippets)) status = 0 - if ly_names: - status = ly.system (string.join ([cmd] + ly_names), + def my_system (cmd): + status = ly.system (cmd, ignore_error = 1, progress_p = 1) + if status: + ly.error ('Process %s exited unsuccessfully.' % cmd) + raise Compile_error - if status: - ly.error( 'Process %s exited unsuccessfully.' % cmd ) - raise Compile_error - - if format == HTML or format == TEXINFO: - for i in png_names: - if not os.path.exists (i + '.eps') and os.path.exists (i + '.tex'): - to_eps (i) - ly.make_ps_images (i + '.eps', resolution=110) - -# elif os.path.exists (i + '.ps'): -# ly.make_ps_images (i + '.ps', resolution=110) + # UGH + # the --process=CMD switch is a bad idea + # it is too generic for lilypond-book. + if texstr_names: + my_system (string.join ([cmd, '--backend texstr', + 'snippet-map.ly'] + texstr_names)) + for l in texstr_names: + my_system ('latex %s.texstr' % l) + if ly_names: + my_system (string.join ([cmd, 'snippet-map.ly'] + ly_names)) -LATEX_DOCUMENT = r''' +LATEX_INSPECTION_DOCUMENT = r''' +\nonstopmode %(preamble)s \begin{document} \typeout{textwidth=\the\textwidth} @@ -1001,12 +1362,21 @@ LATEX_DOCUMENT = r''' \makeatletter\if@twocolumn\typeout{columns=2}\fi\makeatother \end{document} ''' -#need anything else besides textwidth? + +# Do we need anything else besides `textwidth'? def get_latex_textwidth (source): m = re.search (r'''(?P\\begin\s*{document})''', source) preamble = source[:m.start (0)] - latex_document = LATEX_DOCUMENT % vars () - parameter_string = filter_pipe (latex_document, latex_filter_cmd) + latex_document = LATEX_INSPECTION_DOCUMENT % vars () + # Workaround problems with unusable $TMP on Cygwin: + tempfile.tempdir = '' + tmpfile = tempfile.mktemp('.tex') + logfile = os.path.splitext (tmpfile)[0] + '.log' + open (tmpfile,'w').write (latex_document) + ly.system ('latex %s' % tmpfile) + parameter_string = open (logfile).read() + os.unlink (tmpfile) + os.unlink (logfile) columns = 0 m = re.search ('columns=([0-9.]*)', parameter_string) @@ -1019,7 +1389,7 @@ def get_latex_textwidth (source): columnsep = string.atof (m.group (1)) textwidth = 0 - m = re.search('textwidth=([0-9.]*)pt', parameter_string) + m = re.search ('textwidth=([0-9.]*)pt', parameter_string) if m: textwidth = string.atof (m.group (1)) if columns: @@ -1027,6 +1397,18 @@ def get_latex_textwidth (source): return textwidth +def modify_preamble (chunk): + str = chunk.replacement_text () + if (re.search (r"\\begin *{document}", str) + and not re.search ("{graphic[sx]", str)): + str = re.sub (r"\\begin{document}", + r"\\usepackage{graphics}" + '\n' + + r"\\begin{document}", + str) + chunk.override_text = str + + + ext2format = { '.html': HTML, '.itely': TEXINFO, @@ -1041,7 +1423,7 @@ ext2format = { format2ext = { HTML: '.html', - #TEXINFO: '.texinfo', + # TEXINFO: '.texinfo', TEXINFO: '.texi', LATEX: '.tex', } @@ -1049,11 +1431,42 @@ format2ext = { class Compile_error: pass -def do_process_cmd (chunks): - ly_outdated = filter (lambda x: is_derived_class (x.__class__, Lilypond_snippet) \ - and x.ly_is_outdated (), chunks) - png_outdated = filter (lambda x: is_derived_class (x.__class__, Lilypond_snippet) \ - and x.png_is_outdated (), chunks) +def write_file_map (lys, name): + snippet_map = open ('snippet-map.ly', 'w') + snippet_map.write (""" +#(define version-seen? #t) +#(ly:add-file-name-alist '( +""") + for ly in lys: + snippet_map.write ('("%s.ly" . "%s:%d (%s.ly)")\n' + % (ly.basename (), + name, + ly.line_number, + ly.basename ())) + + snippet_map.write ('))\n') + +def do_process_cmd (chunks, input_name): + all_lys = filter (lambda x: is_derived_class (x.__class__, + Lilypond_snippet), + chunks) + + write_file_map (all_lys, input_name) + ly_outdated = \ + filter (lambda x: is_derived_class (x.__class__, + Lilypond_snippet) + and x.ly_is_outdated (), + chunks) + texstr_outdated = \ + filter (lambda x: is_derived_class (x.__class__, + Lilypond_snippet) + and x.texstr_is_outdated (), + chunks) + png_outdated = \ + filter (lambda x: is_derived_class (x.__class__, + Lilypond_snippet) + and x.png_is_outdated (), + chunks) ly.progress (_ ("Writing snippets...")) map (Lilypond_snippet.write_ly, ly_outdated) @@ -1061,37 +1474,60 @@ def do_process_cmd (chunks): if ly_outdated: ly.progress (_ ("Processing...")) - process_snippets (process_cmd, ly_outdated, png_outdated) + ly.progress ('\n') + process_snippets (global_options.process_cmd, ly_outdated, texstr_outdated, png_outdated) else: ly.progress (_ ("All snippets are up to date...")) ly.progress ('\n') +def guess_format (input_filename): + format = None + e = os.path.splitext (input_filename)[1] + if e in ext2format.keys (): + # FIXME + format = ext2format[e] + else: + ly.error (_ ("can't determine format for: %s" \ + % input_filename)) + ly.exit (1) + return format -def do_file (input_filename): - #ugh - global format - if not format: - e = os.path.splitext (input_filename)[1] - if e in ext2format.keys (): - #FIXME - format = ext2format[e] - else: - ly.error (_ ("cannot determine format for: %s" \ - % input_filename)) - ly.exit (1) +def write_if_updated (file_name, lines): + try: + f = open (file_name) + oldstr = f.read () + new_str = string.join (lines, '') + if oldstr == new_str: + ly.progress (_ ("%s is up to date.") % file_name) + ly.progress ('\n') + return + except: + pass + ly.progress (_ ("Writing `%s'...") % file_name) + open (file_name, 'w').writelines (lines) + ly.progress ('\n') + +def note_input_file (name, inputs=[]): + inputs.append (name) + return inputs + +def do_file (input_filename): + # Ugh. if not input_filename or input_filename == '-': in_handle = sys.stdin input_fullname = '' else: if os.path.exists (input_filename): input_fullname = input_filename - elif format == LATEX: + elif global_options.format == LATEX and ly.search_exe_path ('kpsewhich'): # urg python interface to libkpathsea? input_fullname = ly.read_pipe ('kpsewhich ' + input_filename)[:-1] else: input_fullname = find_file (input_filename) + + note_input_file (input_fullname) in_handle = open (input_fullname) if input_filename == '-': @@ -1100,36 +1536,35 @@ def do_file (input_filename): input_base = os.path.basename \ (os.path.splitext (input_filename)[0]) - # only default to stdout when filtering - if output_name == '-' or (not output_name and filter_cmd): + # Only default to stdout when filtering. + if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd): output_filename = '-' output_file = sys.stdout else: - if not output_name: - output_filename = input_base + format2ext[format] - else: - if not os.path.isdir (output_name): - os.mkdir (output_name, 0777) - output_filename = (output_name - + '/' + input_base - + format2ext[format]) - - - if (os.path.exists (input_filename) and - os.path.exists (output_filename) and - os.path.samefile (output_filename, input_fullname)): - ly.error (_("Output would overwrite input file; use --output.")) - ly.exit (2) - - output_file = open (output_filename, 'w') - if output_name: - os.chdir (output_name) + # don't complain when global_options.output_name is existing + output_filename = input_base + format2ext[global_options.format] + if global_options.output_name: + if not os.path.isdir (global_options.output_name): + os.mkdir (global_options.output_name, 0777) + os.chdir (global_options.output_name) + else: + if os.path.exists (input_filename) \ + and os.path.exists (output_filename) \ + and os.path.samefile (output_filename, input_fullname): + ly.error ( + _ ("Output would overwrite input file; use --output.")) + ly.exit (2) + try: ly.progress (_ ("Reading %s...") % input_fullname) source = in_handle.read () ly.progress ('\n') - # FIXME: containing blocks must be first, see find_toplevel_snippets + set_default_options (source) + + + # FIXME: Containing blocks must be first, see + # find_toplevel_snippets. snippet_types = ( 'multiline_comment', 'verbatim', @@ -1138,125 +1573,138 @@ def do_file (input_filename): 'singleline_comment', 'lilypond_file', 'include', - 'lilypond', ) + 'lilypond', + ) ly.progress (_ ("Dissecting...")) chunks = find_toplevel_snippets (source, snippet_types) + + if global_options.format == LATEX: + for c in chunks: + if (c.is_plain () and + re.search (r"\\begin *{document}", c.replacement_text())): + modify_preamble (c) + break ly.progress ('\n') - global default_ly_options - textwidth = 0 - if not default_ly_options.has_key (LINEWIDTH): - if format == LATEX: - textwidth = get_latex_textwidth (source) - default_ly_options[LINEWIDTH] = '''%.0f\\pt''' \ - % textwidth - elif format == TEXINFO: - for (k, v) in texinfo_linewidths.items (): - # FIXME: @layout is usually not in chunk #0: - # \input texinfo @c -*-texinfo-*- - # bluntly search first K of source - # s = chunks[0].replacement_text () - if re.search (k, source[:1024]): - default_ly_options[LINEWIDTH] = v - break - - if filter_cmd: - output_file.writelines ([c.filter_text () for c in chunks]) - - - elif process_cmd: - do_process_cmd (chunks) + if global_options.filter_cmd: + write_if_updated (output_filename, + [c.filter_text () for c in chunks]) + elif global_options.process_cmd: + do_process_cmd (chunks, input_fullname) ly.progress (_ ("Compiling %s...") % output_filename) - output_file.writelines ([s.replacement_text () \ - for s in chunks]) ly.progress ('\n') - + write_if_updated (output_filename, + [s.replacement_text () + for s in chunks]) + def process_include (snippet): os.chdir (original_dir) name = snippet.substring ('filename') - ly.progress (_ ('Processing include: %s') % name) + ly.progress (_ ("Processing include: %s") % name) ly.progress ('\n') - do_file (name) + return do_file (name) - map (process_include, - filter (lambda x: is_derived_class (x.__class__, Include_snippet), chunks)) + include_chunks = map (process_include, + filter (lambda x: is_derived_class (x.__class__, + Include_snippet), + chunks)) + + + return chunks + reduce (lambda x,y: x + y, include_chunks, []) + except Compile_error: os.chdir (original_dir) - ly.progress (_('Removing `%s\'') % output_filename) + ly.progress (_ ("Removing `%s'") % output_filename) ly.progress ('\n') - - os.unlink (output_filename) raise Compile_error - def do_options (): - global format, output_name - global filter_cmd, process_cmd, verbose_p + opt_parser = get_option_parser() + (options, args) = opt_parser.parse_args () - (sh, long) = ly.getopt_args (option_definitions) - try: - (options, files) = getopt.getopt (sys.argv[1:], sh, long) - except getopt.error, s: - sys.stderr.write ('\n') - ly.error (_ ("getopt says: `%s\'" % s)) - sys.stderr.write ('\n') - ly.help () - ly.exit (2) + if options.format in ('texi-html', 'texi'): + options.format = TEXINFO + options.use_hash = True + options.pseudo_filter = False - for opt in options: - o = opt[0] - a = opt[1] + options.include_path = map (os.path.abspath, options.include_path) - if 0: - pass - elif o == '--filter' or o == '-F': - filter_cmd = a - process_cmd = 0 - elif o == '--format' or o == '-f': - format = a - if a == 'texi-html' or a == 'texi': - format = TEXINFO - elif o == '--help' or o == '-h': - ly.help () - sys.exit (0) - elif o == '--include' or o == '-I': - include_path.append (os.path.join (original_dir, - ly.abspath (a))) - elif o == '--output' or o == '-o': - output_name = a - elif o == '--outdir': - output_name = a - elif o == '--process' or o == '-P': - process_cmd = a - filter_cmd = 0 - elif o == '--version' or o == '-v': - ly.identify (sys.stdout) - sys.exit (0) - elif o == '--verbose' or o == '-V': - verbose_p = 1 - elif o == '--warranty' or o == '-w': - if 1 or status: - ly.warranty () - sys.exit (0) - return files + global global_options + global_options = options + return args def main (): files = do_options () - global process_cmd - if process_cmd == '': - process_cmd = lilypond_binary + " -f tex " + if not files or len (files) > 1: + ly.help () + ly.exit (2) + + file = files[0] - if process_cmd: - process_cmd += string.join ([(' -I %s' % p) - for p in include_path]) + basename = os.path.splitext (file)[0] + basename = os.path.split (basename)[1] + + if not global_options.format: + global_options.format = guess_format (files[0]) + + formats = 'ps' + if global_options.format in (TEXINFO, HTML): + formats += ',png' + if global_options.process_cmd == '': + global_options.process_cmd = lilypond_binary \ + + ' --formats=%s --backend eps ' % formats + + if global_options.process_cmd: + global_options.process_cmd += string.join ([(' -I %s' % commands.mkarg (p)) + for p in global_options.include_path]) ly.identify (sys.stderr) - ly.setup_environment () - if files: - try: - do_file (files[0]) - except Compile_error: - ly.exit (1) + + try: + chunks = do_file (file) + if global_options.psfonts: + fontextract.verbose = global_options.verbose + snippet_chunks = filter (lambda x: is_derived_class (x.__class__, + Lilypond_snippet), + chunks) + + psfonts_file = basename + '.psfonts' + if not global_options.verbose: + ly.progress (_ ("Writing fonts to %s...") % psfonts_file) + fontextract.extract_fonts (psfonts_file, + [x.basename() + '.eps' + for x in snippet_chunks]) + if not global_options.verbose: + ly.progress ('\n') + + except Compile_error: + ly.exit (1) + + if global_options.format in (TEXINFO, LATEX): + if not global_options.psfonts: + ly.warning (_ ("option --psfonts not used")) + ly.warning (_ ("processing with dvips will have no fonts")) + + psfonts_file = os.path.join (global_options.output_name, basename + '.psfonts') + output = os.path.join (global_options.output_name, basename + '.dvi' ) + + ly.progress ('\n') + ly.progress (_ ("DVIPS usage:")) + ly.progress ('\n') + ly.progress (" dvips -h %(psfonts_file)s %(output)s" % vars ()) + ly.progress ('\n') + + inputs = note_input_file ('') + inputs.pop () + + base_file_name = os.path.splitext (os.path.basename (file))[0] + dep_file = os.path.join (global_options.output_name, base_file_name + '.dep') + final_output_file = os.path.join (global_options.output_name, + base_file_name + + '.%s' % global_options.format) + + os.chdir (original_dir) + open (dep_file, 'w').write ('%s: %s' % (final_output_file, ' '.join (inputs))) if __name__ == '__main__': main ()