X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=c6fdb3ab00217d6ce4898c3fdeff2790503b0d03;hb=20af64b688c9232a99c47facfd1cf56fe680f6e5;hp=8dfaa411dfd1246c0dee93d9e3a47447e9331479;hpb=1473398a2ccb95b30eb7fb720e9f7a9286daba17;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index 8dfaa411df..c6fdb3ab00 100644 --- a/scripts/lilypond-book.py +++ b/scripts/lilypond-book.py @@ -13,8 +13,11 @@ classic lilypond-book: lilypond-book --process="lilypond" BOOK.tely TODO: + + * this script is too complex. Modularize. + * ly-options: intertext? - * --linewidth? + * --line-width? * eps in latex / eps by lilypond -b ps? * check latex parameters, twocolumn, multicolumn? * use --png --ps --pdf for making images? @@ -29,14 +32,14 @@ import __main__ import glob import stat import string +import tempfile +import commands # Users of python modules should include this snippet # and customize variables below. # We'll suffer this path initialization 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 variables -# (PYTHONPATH) in `etc/profile'). +# 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. @@ -48,7 +51,9 @@ 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. @@ -93,9 +98,12 @@ option_definitions = [ _ ("write output to DIR")), (_ ("COMMAND"), 'P', 'process', _ ("process ly_files using COMMAND FILE...")), - (_('FILE'), '', 'psfonts', - _ ('''extract all PostScript fonts into FILE for LaTeX - must use this with dvips -h FILE''')), + ('', '', 'psfonts', + _ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX + must use this with dvips -h INPUT.psfonts''')), + ('', '', 'keep-line-breaks', + _ ('''do not alter the number of newline characters in output + .tex files. Useful when the LaTeX package srcltx is used''')), ('', 'V', 'verbose', _ ("be verbose")), ('', 'v', 'version', @@ -104,18 +112,18 @@ option_definitions = [ _ ("show warranty and copyright")), ] -include_path = [ly.abspath (os.getcwd ())] +include_path = [os.path.abspath (os.getcwd ())] lilypond_binary = os.path.join ('@bindir@', 'lilypond') # Only use installed binary when we are installed too. if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary): lilypond_binary = 'lilypond' -psfonts_file = '' +psfonts_p = 0 +srcltx_p = 0 use_hash_p = 1 format = 0 output_name = '' -latex_filter_cmd = 'latex "\\nonstopmode \input /dev/stdin"' filter_cmd = 0 process_cmd = '' default_ly_options = { 'alt': "[image of music]" } @@ -132,7 +140,7 @@ HTML = 'html' INDENT = 'indent' LATEX = 'latex' LAYOUT = 'layout' -LINEWIDTH = 'linewidth' +LINE_WIDTH = 'line-width' NOFRAGMENT = 'nofragment' NOINDENT = 'noindent' NOQUOTE = 'noquote' @@ -145,7 +153,7 @@ PAPER = 'paper' PREAMBLE = 'preamble' PRINTFILENAME = 'printfilename' QUOTE = 'quote' -RAGGEDRIGHT = 'raggedright' +RAGGED_RIGHT = 'ragged-right' RELATIVE = 'relative' STAFFSIZE = 'staffsize' TEXIDOC = 'texidoc' @@ -407,11 +415,11 @@ ly_options = { PAPER: { INDENT: r'''indent = %(indent)s''', - LINEWIDTH: r'''linewidth = %(linewidth)s''', + LINE_WIDTH: r'''line-width = %(line-width)s''', - QUOTE: r'''linewidth = %(linewidth)s - 2.0 * %(exampleindent)s''', + QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''', - RAGGEDRIGHT: r'''raggedright = ##t''', + RAGGED_RIGHT: r'''ragged-right = ##t''', PACKED: r'''packed = ##t''', }, @@ -467,7 +475,7 @@ output = { ## LATEX: { - OUTPUT: r'''{%% + OUTPUT: r'''{ \parindent 0pt \catcode`\@=12 \ifx\preLilyPondExample \undefined @@ -475,7 +483,7 @@ output = { \else \preLilyPondExample \fi -\def\lilypondbook{}%% +\def\lilypondbook{} \input %(base)s-systems.tex \ifx\postLilyPondExample \undefined \relax @@ -487,15 +495,11 @@ output = { PRINTFILENAME: '''\\texttt{%(filename)s} ''', - QUOTE: r'''\begin{quotation} -%(str)s -\end{quotation} -''', + QUOTE: r'''\begin{quotation}%(str)s +\end{quotation}''', VERBATIM: r'''\noindent -\begin{verbatim} -%(verb)s\end{verbatim} -''', +\begin{verbatim}%(verb)s\end{verbatim}''', FILTER: r'''\begin{lilypond}[%(options)s] %(code)s @@ -546,21 +550,29 @@ output = { }, } -PREAMBLE_LY = r'''%%%% Generated by %(program_name)s +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) @@ -576,19 +588,35 @@ PREAMBLE_LY = r'''%%%% Generated by %(program_name)s FRAGMENT_LY = r''' %(notes_string)s { + + +%% **************************************************************** %% ly snippet contents follows: +%% **************************************************************** %(code)s + + +%% **************************************************************** %% end ly snippet +%% **************************************************************** } ''' FULL_LY = ''' + + +%% **************************************************************** %% ly snippet: +%% **************************************************************** %(code)s + + +%% **************************************************************** %% end ly snippet +%% **************************************************************** ''' -texinfo_linewidths = { +texinfo_line_widths = { '@afourpaper': '160\\mm', '@afourwide': '6.5\\in', '@afourlatex': '150\\mm', @@ -598,7 +626,7 @@ texinfo_linewidths = { def classic_lilypond_book_compatibility (key, value): if key == 'singleline' and value == None: - return (RAGGEDRIGHT, None) + return (RAGGED_RIGHT, None) m = re.search ('relative\s*([-0-9])', key) if m: @@ -608,7 +636,7 @@ def classic_lilypond_book_compatibility (key, value): if m: return ('staffsize', m.group (1)) - if key == 'indent' or key == 'linewidth': + 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)) @@ -648,7 +676,28 @@ def split_options (option_string): return [] def invokes_lilypond (): - return re.search ('^[\'\"0-9A-Za-z/]*lilypond', process_cmd) + return re.search ('^[\'\"0-9A-Za-z/.]*lilypond', process_cmd) + +def set_default_options (source): + global default_ly_options + if not default_ly_options.has_key (LINE_WIDTH): + if format == LATEX: + textwidth = get_latex_textwidth (source) + default_ly_options[LINE_WIDTH] = \ + '''%.0f\\pt''' % textwidth + elif 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): @@ -663,15 +712,25 @@ class Chunk: def png_is_outdated (self): return 0 + def is_plain (self): + return False + class Substring (Chunk): 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, line_number): @@ -733,42 +792,42 @@ class Lilypond_snippet (Snippet): else: self.option_dict[i] = None - has_linewidth = self.option_dict.has_key (LINEWIDTH) - no_linewidth_value = 0 + has_line_width = self.option_dict.has_key (LINE_WIDTH) + no_line_width_value = 0 - # If LINEWIDTH is used without parameter, set it to default. - if has_linewidth and self.option_dict[LINEWIDTH] == None: - no_linewidth_value = 1 - del self.option_dict[LINEWIDTH] + # 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_linewidth: + if not has_line_width: if type == 'lilypond' or FRAGMENT in self.option_dict.keys (): - self.option_dict[RAGGEDRIGHT] = None + self.option_dict[RAGGED_RIGHT] = None if type == 'lilypond': - if LINEWIDTH in self.option_dict.keys (): - del self.option_dict[LINEWIDTH] + if LINE_WIDTH in self.option_dict.keys (): + del self.option_dict[LINE_WIDTH] else: - if RAGGEDRIGHT in self.option_dict.keys (): - if LINEWIDTH in self.option_dict.keys (): - del self.option_dict[LINEWIDTH] + 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 LINEWIDTH in self.option_dict.keys (): - del self.option_dict[LINEWIDTH] + 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 `linewidth' + # The QUOTE pattern from ly_options only emits the `line-width' # keyword. - if has_linewidth and QUOTE in self.option_dict.keys (): - if no_linewidth_value: - del self.option_dict[LINEWIDTH] + 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] @@ -805,7 +864,7 @@ class Lilypond_snippet (Snippet): # To set @exampleindent locally to zero, we use the @format # environment for non-quoted snippets. override[EXAMPLEINDENT] = r'0.4\in' - override[LINEWIDTH] = texinfo_linewidths['@smallbook'] + override[LINE_WIDTH] = texinfo_line_widths['@smallbook'] override.update (default_ly_options) option_list = [] @@ -929,9 +988,20 @@ class Lilypond_snippet (Snippet): 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')) + 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 @@ -965,7 +1035,9 @@ class Lilypond_snippet (Snippet): 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): @@ -1010,10 +1082,12 @@ class Lilypond_snippet (Snippet): if VERBATIM in self.option_dict: verb = self.substring ('code') str += (output[LATEX][VERBATIM] % vars ()) - if QUOTE in self.option_dict: - str = output[LATEX][QUOTE] % vars () - + elif srcltx_p: + breaks = self.ly ().count ("\n") + str += "".ljust (breaks, "\n").replace ("\n","%\n") str += (output[LATEX][OUTPUT] % vars ()) + if QUOTE in self.option_dict: + str = output[LATEX][QUOTE] % vars () return str def output_print_filename (self, format): @@ -1063,7 +1137,7 @@ class Lilypond_snippet (Snippet): class Lilypond_file_snippet (Lilypond_snippet): def ly (self): name = self.substring ('filename') - return '\\renameinput \"%s\"\n%s' \ + return '\\sourcefilename \"%s\"\n%s' \ % (name, open (find_file (name)).read ()) snippet_type_to_class = { @@ -1229,7 +1303,7 @@ def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets): # UGH # the --process=CMD switch is a bad idea # it is too generic for lilypond-book. - if texstr_names and invokes_lilypond: + if texstr_names and invokes_lilypond (): my_system (string.join ([cmd, '--backend texstr', 'snippet-map.ly'] + texstr_names)) for l in texstr_names: @@ -1238,7 +1312,8 @@ def process_snippets (cmd, ly_snippets, texstr_snippets, png_snippets): 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} @@ -1251,8 +1326,16 @@ LATEX_DOCUMENT = r''' 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) @@ -1273,6 +1356,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, @@ -1380,7 +1475,7 @@ def do_file (input_filename): else: if os.path.exists (input_filename): input_fullname = input_filename - elif format == LATEX: + elif format == LATEX and ly.search_exe_path ('kpsewhich'): # urg python interface to libkpathsea? input_fullname = ly.read_pipe ('kpsewhich ' + input_filename)[:-1] @@ -1399,24 +1494,28 @@ def do_file (input_filename): output_filename = '-' output_file = sys.stdout else: + # don't complain when output_name is existing + output_filename = input_base + format2ext[format] if output_name: if not os.path.isdir (output_name): os.mkdir (output_name, 0777) os.chdir (output_name) - - output_filename = 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) + 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') + set_default_options (source) + + # FIXME: Containing blocks must be first, see # find_toplevel_snippets. snippet_types = ( @@ -1431,28 +1530,14 @@ def do_file (input_filename): ) ly.progress (_ ("Dissecting...")) chunks = find_toplevel_snippets (source, snippet_types) - 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 items of - # source. - # s = chunks[0].replacement_text () - if re.search (k, source[:1024]): - default_ly_options[LINEWIDTH] = v - break + if 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') if filter_cmd: write_if_updated (output_filename, @@ -1487,7 +1572,7 @@ def do_file (input_filename): raise Compile_error def do_options (): - global format, output_name, psfonts_file + global format, output_name, psfonts_p, srcltx_p global filter_cmd, process_cmd, verbose_p (sh, long) = ly.getopt_args (option_definitions) @@ -1520,7 +1605,7 @@ def do_options (): sys.exit (0) elif o == '--include' or o == '-I': include_path.append (os.path.join (original_dir, - ly.abspath (a))) + os.path.abspath (a))) elif o == '--output' or o == '-o': output_name = a elif o == '--process' or o == '-P': @@ -1532,7 +1617,11 @@ def do_options (): elif o == '--verbose' or o == '-V': verbose_p = 1 elif o == '--psfonts': - psfonts_file = a + psfonts_p = 1 + elif o == '--keep-line-breaks': + srcltx_p = 1 + for s in (OUTPUT, QUOTE, VERBATIM): + output[LATEX][s] = output[LATEX][s].replace("\n"," ") elif o == '--warranty' or o == '-w': if 1 or status: ly.warranty () @@ -1546,6 +1635,10 @@ def main (): ly.exit (2) file = files[0] + + basename = os.path.splitext (file)[0] + basename = os.path.split (basename)[1] + global process_cmd, format if not format: format = guess_format (files[0]) @@ -1558,19 +1651,20 @@ def main (): + ' --formats=%s --backend eps ' % formats if process_cmd: - process_cmd += string.join ([(' -I %s' % p) + process_cmd += string.join ([(' -I %s' % commands.mkarg (p)) for p in include_path]) ly.identify (sys.stderr) - ly.setup_environment () try: chunks = do_file (file) - if psfonts_file and invokes_lilypond (): + if psfonts_p and invokes_lilypond (): fontextract.verbose = verbose_p snippet_chunks = filter (lambda x: is_derived_class (x.__class__, Lilypond_snippet), chunks) + + psfonts_file = basename + '.psfonts' if not verbose_p: ly.progress (_ ("Writing fonts to %s...") % psfonts_file) fontextract.extract_fonts (psfonts_file, @@ -1583,20 +1677,17 @@ def main (): ly.exit (1) if format == TEXINFO or format == LATEX: - psfonts = 'PSFONTS-FILE' - if not psfonts_file: - ly.warning (_ ("option --psfonts=FILE not used")) + if not psfonts_p: + ly.warning (_ ("option --psfonts not used")) ly.warning (_ ("processing with dvips will have no fonts")) - else: - psfonts = os.path.join (output_name, psfonts_file) - - output = os.path.join (output_name, - os.path.splitext (os.path.basename - (file))[0]) + '.dvi' + + psfonts_file = os.path.join (output_name, basename + '.psfonts') + output = os.path.join (output_name, basename + '.dvi' ) + ly.progress ('\n') ly.progress (_ ("DVIPS usage:")) ly.progress ('\n') - ly.progress (" dvips -h %(psfonts)s %(output)s" % vars ()) + ly.progress (" dvips -h %(psfonts_file)s %(output)s" % vars ()) ly.progress ('\n') if __name__ == '__main__':