X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=python%2Flilylib.py;h=a512f2c6e5494098b5cfb4aaca2ffe5a6d21746b;hb=47db9a3883d726ca53e2133a3b2298f78dd6a32e;hp=17992d8c96d99f87cc28a15d5a6429bf86a88968;hpb=ab7a4832dbcc1964c023d6035830b96b8e9076a3;p=lilypond.git diff --git a/python/lilylib.py b/python/lilylib.py index 17992d8c96..a911cac23f 100644 --- a/python/lilylib.py +++ b/python/lilylib.py @@ -1,444 +1,349 @@ -################################################################ -# lilylib.py -- options and stuff -# -# source file of the GNU LilyPond music typesetter +# This file is part of LilyPond, the GNU music typesetter. # -# (c) 1998--2002 Han-Wen Nienhuys -# Jan Nieuwenhuizen - -### subst:\(^\|[^._a-z]\)\(abspath\|identify\|warranty\|progress\|warning\|error\|exit\|getopt_args\|option_help_str\|options_help_str\|help\|setup_temp\|read_pipe\|system\|cleanup_temp\|strip_extension\|cp_to_dir\|mkdir_p\|init\) *( -### replace:\1ly.\2 ( - -### subst: \(help_summary\|keep_temp_dir_p\|option_definitions\|original_dir\|program_name\|pseudo_filter_p\|temp_dir\|verbose_p\) +# Copyright (C) 1998--2015 Han-Wen Nienhuys +# Jan Nieuwenhuizen +# +# LilyPond is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LilyPond is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LilyPond. If not, see . import __main__ +import glob +import os +import re import shutil -import string import sys -import tempfile +import optparse +import time ################################################################ # 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) -# 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@' -if os.environ.has_key ('LILYPONDPREFIX') : - datadir = os.environ['LILYPONDPREFIX'] - while datadir[-1] == os.sep: - datadir= datadir[:-1] +# Python 2.5 only accepts strings with proper Python internal encoding +# (i.e. ASCII or Unicode) when writing to stdout/stderr, so we must +# use ugettext iso gettext, and encode the string when writing to +# stdout/stderr -sys.path.insert (0, os.path.join (datadir, 'python')) +localedir = '@localedir@' +try: + import gettext + t = gettext.translation ('lilypond', localedir) + _ = t.ugettext + ungettext = t.ungettext +except: + def _ (s): + return s + def ungettext (s, p, n): + if n == 1: + return s + return p +underscore = _ +# Urg, Python 2.4 does not define stderr/stdout encoding +# Maybe guess encoding from LANG/LC_ALL/LC_CTYPE? +reload (sys) +sys.setdefaultencoding ('utf-8') +import codecs +sys.stdout = codecs.getwriter ('utf8') (sys.stdout) +sys.stderr = codecs.getwriter ('utf8') (sys.stderr) -# Customize these -if __name__ == '__main__': - import lilylib as ly - global _;_=ly._ - global re;re = ly.re +def encoded_write(f, s): + f.write (s.encode (f.encoding or 'utf-8', 'replace')) - # lilylib globals - program_name = 'unset' - pseudo_filter_p = 0 - original_dir = os.getcwd () - temp_dir = os.path.join (original_dir, '%s.dir' % program_name) - keep_temp_dir_p = 0 - verbose_p = 0 +# ugh, Python 2.5 optparse requires Unicode strings in some argument +# functions, and refuse them in some other places +def display_encode (s): + return s.encode (sys.stderr.encoding or 'utf-8', 'replace') - help_summary = _ ("lilylib module") +# Lilylib globals. +program_version = '@TOPLEVEL_VERSION@' +program_name = os.path.basename (sys.argv[0]) + + +# Check if program_version contains @ characters. This will be the case if +# the .py file is called directly while building the lilypond documentation. +# If so, try to check for the env var LILYPOND_VERSION, which is set by our +# makefiles and use its value. +at_re = re.compile (r'@') +if at_re.match (program_version): + if os.environ.has_key('LILYPOND_VERSION'): + program_version = os.environ['LILYPOND_VERSION'] + else: + program_version = "unknown" + + +# Logging framework: We have the following output functions: +# error +# warning +# progress +# debug + +loglevels = {"NONE":0, "ERROR":1, "WARN":2, "BASIC":3, "PROGRESS":4, "INFO":5, "DEBUG":6} + +loglevel = loglevels["PROGRESS"] + +def set_loglevel (l): + global loglevel + newlevel = loglevels.get (l, -1) + if newlevel > 0: + debug_output (_ ("Setting loglevel to %s") % l) + loglevel = newlevel + else: + error (_ ("Unknown or invalid loglevel '%s'") % l) + + +def handle_loglevel_option (option, opt_str, value, parser, *args): + if value: + set_loglevel (value); + elif args: + set_loglevel (args[0]); + +def is_loglevel (l): + global loglevel + return loglevel >= loglevels[l]; + +def is_verbose (): + return is_loglevel ("DEBUG") + +def stderr_write (s): + encoded_write (sys.stderr, s) + +def print_logmessage (level, s, fullmessage = True, newline = True): + if (is_loglevel (level)): + if fullmessage: + stderr_write (program_name + ": " + s + '\n') + elif newline: + stderr_write (s + '\n') + else: + stderr_write (s) - option_definitions = [ - ('', 'h', 'help', _ ("this help")), - ] +def error (s): + print_logmessage ("ERROR", _ ("error: %s") % s); - from lilylib import * -################################################################ +def warning (s): + print_logmessage ("WARN", _ ("warning: %s") % s); -# Handle bug in Python 1.6-2.1 -# -# there are recursion limits for some patterns in Python 1.6 til 2.1. -# fix this by importing pre instead. Fix by Mats. - -if float (sys.version[0:3]) <= 2.1: - try: - import pre - re = pre - del pre - except ImportError: - import re -else: - import re - -# Attempt to fix problems with limited stack size set by Python! -# Sets unlimited stack size. Note that the resource module only -# is available on UNIX. -try: - import resource - resource.setrlimit (resource.RLIMIT_STACK, (-1, -1)) -except: - pass +def basic_progress (s): + print_logmessage ("BASIC", s); -localedir = '@localedir@' -try: - import gettext - gettext.bindtextdomain ('lilypond', localedir) - gettext.textdomain ('lilypond') - _ = gettext.gettext -except: - def _ (s): - return s -underscore = _ +def progress (s, fullmessage = False, newline = True): + print_logmessage ("PROGRESS", s, fullmessage, newline); -program_version = '@TOPLEVEL_VERSION@' -if program_version == '@' + 'TOPLEVEL_VERSION' + '@': - program_version = '1.7.14' - -def identify (port): - port.write ('%s (GNU LilyPond) %s\n' % (__main__.program_name, program_version)) - -def warranty (): - identify (sys.stdout) - sys.stdout.write ('\n') - sys.stdout.write (_ ('Copyright (c) %s by' % ' 1998--2002')) - sys.stdout.write ('\n') - map (lambda x: sys.stdout.write (' %s\n' % x), __main__.copyright) - sys.stdout.write ('\n') - sys.stdout.write (_ ("Distributed under terms of the GNU General Public License.")) - sys.stdout.write ('\n') - sys.stdout.write (_ ("It comes with NO WARRANTY.")) - sys.stdout.write ('\n') - -def progress (s): - sys.stderr.write (s) +def debug_output (s, fullmessage = False, newline = True): + print_logmessage ("DEBUG", s, fullmessage, newline); -def warning (s): - sys.stderr.write (__main__.program_name + ": " + _ ("warning: ") + s + '\n') -def error (s): - sys.stderr.write (__main__.program_name + ": " + _ ("error: ") + s + '\n') - -def exit (i): - if __main__.verbose_p: - raise _ ('Exiting (%d)...') % i - else: - sys.exit (1) - -def getopt_args (opts): - '''Construct arguments (LONG, SHORT) for getopt from list of options.''' - short = '' - long = [] - for o in opts: - if o[1]: - short = short + o[1] - if o[0]: - short = short + ':' - if o[2]: - l = o[2] - if o[0]: - l = l + '=' - long.append (l) - return (short, long) - -def option_help_str (o): - '''Transform one option description (4-tuple ) into neatly formatted string''' - sh = ' ' - if o[1]: - sh = '-%s' % o[1] - - sep = ' ' - if o[1] and o[2]: - sep = ',' - - long = '' - if o[2]: - long= '--%s' % o[2] - - arg = '' - if o[0]: - if o[2]: - arg = '=' - arg = arg + o[0] - return ' ' + sh + sep + long + arg - - -def options_help_str (opts): - '''Convert a list of options into a neatly formatted string''' - w = 0 - strs =[] - helps = [] - - for o in opts: - s = option_help_str (o) - strs.append ((s, o[3])) - if len (s) > w: - w = len (s) - - str = '' - for s in strs: - str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0]) + 3), s[1]) - return str - -def help (): - ls = [(_ ("Usage: %s [OPTION]... FILE") % __main__.program_name), - ('\n\n'), - (__main__.help_summary), - ('\n\n'), - (_ ("Options:")), - ('\n'), - (options_help_str (__main__.option_definitions)), - ('\n\n'), - (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'), - ('\n')] - map (sys.stdout.write, ls) - -def setup_temp (): - - ''' Create a temporary directory, and return its name. ''' - - if not __main__.keep_temp_dir_p: - __main__.temp_dir = tempfile.mktemp (__main__.program_name) - try: - os.mkdir (__main__.temp_dir, 0700) - except OSError: - pass - - return __main__.temp_dir -def command_name (cmd): - return re.match ('^[ \t]*([^ \t]*)', cmd).group (1) - -def error_log (name): - return os.path.join (__main__.temp_dir, '%s.errorlog' % name) - -def read_pipe (cmd, mode = 'r'): - redirect = '' - if __main__.verbose_p: - progress (_ ("Opening pipe `%s\'") % cmd) - redirect = ' 2>%s' % error_log (command_name (cmd)) - pipe = os.popen (cmd + redirect, mode) - output = pipe.read () - status = pipe.close () - # successful pipe close returns 'None' - if not status: - status = 0 - signal = 0x0f & status - exit_status = status >> 8 - - if status: - error (_ ("`%s\' failed (%d)") % (cmd, exit_status)) - if not __main__.verbose_p: - error (_ ("The error log is as follows:")) - sys.stderr.write (open (error_log (command_name (cmd)).read ())) - exit (status) - if __main__.verbose_p: - progress ('\n') - return output - -def system (cmd, ignore_error = 0, progress_p = 0): - - '''System CMD. If IGNORE_ERROR, do not complain when CMD -returns non zero. If PROGRESS_P, always show progress. - -RETURN VALUE - -Exit status of CMD ''' - - name = command_name (cmd) - - if __main__.verbose_p: - progress_p = 1 - progress (_ ("Invoking `%s\'") % cmd) - else: - progress ( _("Running %s...") % name) - - redirect = '' - if not progress_p: - redirect = ' 1>/dev/null 2>' + error_log (name) - elif __main__.pseudo_filter_p: - redirect = ' 1>/dev/null' - - status = os.system (cmd + redirect) - signal = 0x0f & status - exit_status = status >> 8 - - if status: - - exit_type = 'status %d' % exit_status - if signal: - exit_type = 'signal %d' % signal - - msg = _ ("`%s\' failed (%s)") % (name, exit_type) - if ignore_error: - if __main__.verbose_p: - warning (msg + ' ' + _ ("(ignored)")) - else: - error (msg) - if not progress_p: - error (_ ("The error log is as follows:")) - sys.stderr.write (open (error_log (name)).read ()) - exit (status) - - progress ('\n') - return status - -def cleanup_temp (): - if not __main__.keep_temp_dir_p: - if __main__.verbose_p: - progress (_ ("Cleaning %s...") % __main__.temp_dir) - shutil.rmtree (__main__.temp_dir) - if __main__.verbose_p: - progress ('\n') +def require_python_version (): + if sys.hexversion < 0x02040000: + error ("Python 2.4 or newer is required to run this program.\n\ +Please upgrade Python from http://python.org/download/, and if you use MacOS X,\n\ +please read 'Setup for MacOS X' in Application Usage.") + os.system ("open http://python.org/download/") + sys.exit (2) +# A modified version of the commands.mkarg(x) that always uses +# double quotes (since Windows can't handle the single quotes) +# and escapes the characters \, $, ", and ` for unix shells. +def mkarg(x): + if os.name == 'nt': + return ' "%s"' % x + s = ' "' + for c in x: + if c in '\\$"`': + s = s + '\\' + s = s + c + s = s + '"' + return s -def strip_extension (f, ext): - (p, e) = os.path.splitext (f) - if e == ext: - e = '' - return p + e +def command_name (cmd): + # Strip all stuf after command, + # deal with "((latex ) >& 1 ) .." too + cmd = re.match ('([\(\)]*)([^\\\ ]*)', cmd).group (2) + return os.path.basename (cmd) + +def subprocess_system (cmd, + ignore_error=False, + progress_p=True, + be_verbose=False, + redirect_output=False, + log_file=None): + import subprocess + + show_progress= progress_p + name = command_name (cmd) + error_log_file = '' + + if redirect_output: + progress (_ ("Processing %s.ly") % log_file) + else: + if be_verbose: + show_progress = 1 + progress (_ ("Invoking `%s\'") % cmd) + else: + progress ( _("Running %s...") % name) + + stdout_setting = None + stderr_setting = None + if not show_progress: + stdout_setting = subprocess.PIPE + + if redirect_output: + stderr_filename = log_file + '.log' + stderr_setting = open(stderr_filename, 'w') + + proc = subprocess.Popen (cmd, + shell=True, + universal_newlines=True, + stdout=stdout_setting, + stderr=stderr_setting) + + log = '' + + if redirect_output: + while proc.poll()==None: + time.sleep(0.01) + retval = proc.returncode + stderr_setting.close() + else: + if show_progress: + retval = proc.wait() + else: + log = proc.communicate () + retval = proc.returncode + + if retval: + print >>sys.stderr, 'command failed:', cmd + if retval < 0: + print >>sys.stderr, "Child was terminated by signal", -retval + elif retval > 0: + print >>sys.stderr, "Child returned", retval + + if ignore_error: + print >>sys.stderr, "Error ignored by lilylib" + else: + if not show_progress: + print log[0] + print log[1] + sys.exit (1) + + return abs (retval) + +def ossystem_system (cmd, + ignore_error=False, + progress_p=True, + be_verbose=False, + redirect_output=False, + log_file=None): + + + name = command_name (cmd) + if be_verbose: + show_progress = 1 + progress (_ ("Invoking `%s\'") % cmd) + else: + progress ( _("Running %s...") % name) + + retval = os.system (cmd) + if retval: + print >>sys.stderr, 'command failed:', cmd + if retval < 0: + print >>sys.stderr, "Child was terminated by signal", -retval + elif retval > 0: + print >>sys.stderr, "Child returned", retval + + if ignore_error: + print >>sys.stderr, "Error ignored" + else: + sys.exit (1) + + return abs (retval) + + +system = subprocess_system +if sys.platform == 'mingw32': + + ## subprocess x-compile doesn't work. + system = ossystem_system +def strip_extension (f, ext): + (p, e) = os.path.splitext (f) + if e == ext: + e = '' + return p + e -def cp_to_dir (pattern, dir): - "Copy files matching re PATTERN from cwd to DIR" - # Duh. Python style portable: cp *.EXT OUTDIR - # system ('cp *.%s %s' % (ext, outdir), 1) - files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.')) - map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files) +def search_exe_path (name): + p = os.environ['PATH'] + exe_paths = p.split (':') + for e in exe_paths: + full = os.path.join (e, name) + if os.path.exists (full): + return full + return None -# Python < 1.5.2 compatibility -# -# On most platforms, this is equivalent to -#`normpath(join(os.getcwd()), PATH)'. *Added in Python version 1.5.2* -if os.path.__dict__.has_key ('abspath'): - abspath = os.path.abspath -else: - def abspath (path): - return os.path.normpath (os.path.join (os.getcwd (), path)) - -if os.__dict__.has_key ('makedirs'): - makedirs = os.makedirs -else: - def makedirs (dir, mode=0777): - system ('mkdir -p %s' % dir) - - -def mkdir_p (dir, mode=0777): - if not os.path.isdir (dir): - makedirs (dir, mode) - - -environment = {} - -# tex needs lots of memory, more than it gets by default on Debian -non_path_environment = { - 'extra_mem_top' : '1000000', - 'extra_mem_bottom' : '1000000', - 'pool_size' : '250000', -} - -def setup_environment (): - global environment - - kpse = read_pipe ('kpsexpand \$TEXMF') - texmf = re.sub ('[ \t\n]+$','', kpse) - type1_paths = read_pipe ('kpsewhich -expand-path=\$T1FONTS') - - environment = { - # TODO: * prevent multiple addition. - # * clean TEXINPUTS, MFINPUTS, TFMFONTS, - # as these take prevalence over $TEXMF - # and thus may break tex run? - 'TEXMF' : "{%s,%s}" % (datadir, texmf) , - 'GS_FONTPATH' : type1_paths, - 'GS_LIB' : datadir + '/ps', - } - - # $TEXMF is special, previous value is already taken care of - if os.environ.has_key ('TEXMF'): - del os.environ['TEXMF'] - - for key in environment.keys (): - val = environment[key] - if os.environ.has_key (key): - val = os.environ[key] + os.pathsep + val - os.environ[key] = val - - for key in non_path_environment.keys (): - val = non_path_environment[key] - os.environ[key] = val def print_environment (): - for (k,v) in os.environ.items (): - sys.stderr.write ("%s=\"%s\"\n" % (k, v)) - -def get_bbox (filename): - bbox = filename + '.bbox' - ## -sOutputFile does not work with bbox? - cmd = 'gs -sDEVICE=bbox -q -dNOPAUSE %s -c quit 2>%s' % \ - (filename, bbox) - system (cmd, progress_p = 1) - box = open (bbox).read () - m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', - box) - gr = [] - if m: - gr = map (string.atoi, m.groups ()) - - return gr - -def make_preview (name): - ## ly2dvi/lilypond-book discrepancy - preview_ps = name + '.preview.ps' - if not os.path.isfile (preview_ps): - preview_ps = name + '.eps' - bbox = get_bbox (preview_ps) - trans_ps = name + '.trans.ps' - png = name + '.png' - - margin = 0 - fo = open (trans_ps, 'w') - fo.write ('%d %d translate\n' % (-bbox[0] + margin, - -bbox[1] + margin)) - fo.close () - - x = (2* margin + bbox[2] - bbox[0]) \ - * __main__.preview_resolution / 72.0 - y = (2* margin + bbox[3] - bbox[1]) \ - * __main__.preview_resolution / 72.0 - if x == 0: - x = 1 - if y == 0: - y = 1 - - cmd = r'''gs -g%dx%d -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -q -sOutputFile=%s -r%d -dNOPAUSE %s %s -c quit ''' % \ - (x, y, png, __main__.preview_resolution, trans_ps, preview_ps) - - status = system (cmd) - signal = 0xf & status - exit_status = status >> 8 - - if status: - os.unlink (png) - error (_ ("Removing output file")) - exit (1) - -def make_page_images (name, resolution = 90): - - """ Generate images for - all pages in the PS file NAME. NAME should be the basename - (not including the extension.). - """ - - cmd = 'gs -sDEVICE=pnggray -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -sOutputFile="%s-page%%d.png" -r%d -dNOPAUSE %s -c quit' - cmd = cmd % (name, resolution, name + '.ps') - system (cmd) + for (k,v) in os.environ.items (): + sys.stderr.write ("%s=\"%s\"\n" % (k, v)) + +class NonDentedHeadingFormatter (optparse.IndentedHelpFormatter): + def format_heading(self, heading): + if heading: + return heading[0].upper() + heading[1:] + ':\n' + return '' + def format_option_strings(self, option): + sep = ' ' + if option._short_opts and option._long_opts: + sep = ',' + + metavar = '' + if option.takes_value(): + metavar = '=%s' % option.metavar or option.dest.upper() + + return "%3s%s %s%s" % (" ".join (option._short_opts), + sep, + " ".join (option._long_opts), + metavar) + + # Only use one level of indentation (even for groups and nested groups), + # since we don't indent the headeings, either + def indent(self): + self.current_indent = self.indent_increment + self.level += 1 + def dedent(self): + self.level -= 1 + if self.level <= 0: + self.current_indent = '' + self.level = 0; + + def format_usage(self, usage): + return _("Usage: %s") % usage + '\n' + + def format_description(self, description): + return description + +class NonEmptyOptionParser (optparse.OptionParser): + "A subclass of OptionParser that gobbles empty string arguments." + + def parse_args (self, args=None, values=None): + options, args = optparse.OptionParser.parse_args (self, args, values) + return options, filter (None, args) + +def get_option_parser (*args, **kwargs): + p = NonEmptyOptionParser (*args, **kwargs) + p.formatter = NonDentedHeadingFormatter () + p.formatter.set_parser (p) + return p