forked from brl/citadel
168 lines
5.9 KiB
Python
168 lines
5.9 KiB
Python
# pysh.py - command processing for pysh.
|
|
#
|
|
# Copyright 2007 Patrick Mezard
|
|
#
|
|
# This software may be used and distributed according to the terms
|
|
# of the GNU General Public License, incorporated herein by reference.
|
|
|
|
import optparse
|
|
import os
|
|
import sys
|
|
|
|
import interp
|
|
|
|
SH_OPT = optparse.OptionParser(prog='pysh', usage="%prog [OPTIONS]", version='0.1')
|
|
SH_OPT.add_option('-c', action='store_true', dest='command_string', default=None,
|
|
help='A string that shall be interpreted by the shell as one or more commands')
|
|
SH_OPT.add_option('--redirect-to', dest='redirect_to', default=None,
|
|
help='Redirect script commands stdout and stderr to the specified file')
|
|
# See utility_command in builtin.py about the reason for this flag.
|
|
SH_OPT.add_option('--redirected', dest='redirected', action='store_true', default=False,
|
|
help='Tell the interpreter that stdout and stderr are actually the same objects, which is really stdout')
|
|
SH_OPT.add_option('--debug-parsing', action='store_true', dest='debug_parsing', default=False,
|
|
help='Trace PLY execution')
|
|
SH_OPT.add_option('--debug-tree', action='store_true', dest='debug_tree', default=False,
|
|
help='Display the generated syntax tree.')
|
|
SH_OPT.add_option('--debug-cmd', action='store_true', dest='debug_cmd', default=False,
|
|
help='Trace command execution before parameters expansion and exit status.')
|
|
SH_OPT.add_option('--debug-utility', action='store_true', dest='debug_utility', default=False,
|
|
help='Trace utility calls, after parameters expansions')
|
|
SH_OPT.add_option('--ast', action='store_true', dest='ast', default=False,
|
|
help='Encoded commands to execute in a subprocess')
|
|
SH_OPT.add_option('--profile', action='store_true', default=False,
|
|
help='Profile pysh run')
|
|
|
|
|
|
def split_args(args):
|
|
# Separate shell arguments from command ones
|
|
# Just stop at the first argument not starting with a dash. I know, this is completely broken,
|
|
# it ignores files starting with a dash or may take option values for command file. This is not
|
|
# supposed to happen for now
|
|
command_index = len(args)
|
|
for i,arg in enumerate(args):
|
|
if not arg.startswith('-'):
|
|
command_index = i
|
|
break
|
|
|
|
return args[:command_index], args[command_index:]
|
|
|
|
|
|
def fixenv(env):
|
|
path = env.get('PATH')
|
|
if path is not None:
|
|
parts = path.split(os.pathsep)
|
|
# Remove Windows utilities from PATH, they are useless at best and
|
|
# some of them (find) may be confused with other utilities.
|
|
parts = [p for p in parts if 'system32' not in p.lower()]
|
|
env['PATH'] = os.pathsep.join(parts)
|
|
if env.get('HOME') is None:
|
|
# Several utilities, including cvsps, cannot work without
|
|
# a defined HOME directory.
|
|
env['HOME'] = os.path.expanduser('~')
|
|
return env
|
|
|
|
def _sh(cwd, shargs, cmdargs, options, debugflags=None, env=None):
|
|
if os.environ.get('PYSH_TEXT') != '1':
|
|
import msvcrt
|
|
for fp in (sys.stdin, sys.stdout, sys.stderr):
|
|
msvcrt.setmode(fp.fileno(), os.O_BINARY)
|
|
|
|
hgbin = os.environ.get('PYSH_HGTEXT') != '1'
|
|
|
|
if debugflags is None:
|
|
debugflags = []
|
|
if options.debug_parsing: debugflags.append('debug-parsing')
|
|
if options.debug_utility: debugflags.append('debug-utility')
|
|
if options.debug_cmd: debugflags.append('debug-cmd')
|
|
if options.debug_tree: debugflags.append('debug-tree')
|
|
|
|
if env is None:
|
|
env = fixenv(dict(os.environ))
|
|
if cwd is None:
|
|
cwd = os.getcwd()
|
|
|
|
if not cmdargs:
|
|
# Nothing to do
|
|
return 0
|
|
|
|
ast = None
|
|
command_file = None
|
|
if options.command_string:
|
|
input = cmdargs[0]
|
|
if not options.ast:
|
|
input += '\n'
|
|
else:
|
|
args, input = interp.decodeargs(input), None
|
|
env, ast = args
|
|
cwd = env.get('PWD', cwd)
|
|
else:
|
|
command_file = cmdargs[0]
|
|
arguments = cmdargs[1:]
|
|
|
|
prefix = interp.resolve_shebang(command_file, ignoreshell=True)
|
|
if prefix:
|
|
input = ' '.join(prefix + [command_file] + arguments)
|
|
else:
|
|
# Read commands from file
|
|
f = file(command_file)
|
|
try:
|
|
# Trailing newline to help the parser
|
|
input = f.read() + '\n'
|
|
finally:
|
|
f.close()
|
|
|
|
redirect = None
|
|
try:
|
|
if options.redirected:
|
|
stdout = sys.stdout
|
|
stderr = stdout
|
|
elif options.redirect_to:
|
|
redirect = open(options.redirect_to, 'wb')
|
|
stdout = redirect
|
|
stderr = redirect
|
|
else:
|
|
stdout = sys.stdout
|
|
stderr = sys.stderr
|
|
|
|
# TODO: set arguments to environment variables
|
|
opts = interp.Options()
|
|
opts.hgbinary = hgbin
|
|
ip = interp.Interpreter(cwd, debugflags, stdout=stdout, stderr=stderr,
|
|
opts=opts)
|
|
try:
|
|
# Export given environment in shell object
|
|
for k,v in env.iteritems():
|
|
ip.get_env().export(k,v)
|
|
return ip.execute_script(input, ast, scriptpath=command_file)
|
|
finally:
|
|
ip.close()
|
|
finally:
|
|
if redirect is not None:
|
|
redirect.close()
|
|
|
|
def sh(cwd=None, args=None, debugflags=None, env=None):
|
|
if args is None:
|
|
args = sys.argv[1:]
|
|
shargs, cmdargs = split_args(args)
|
|
options, shargs = SH_OPT.parse_args(shargs)
|
|
|
|
if options.profile:
|
|
import lsprof
|
|
p = lsprof.Profiler()
|
|
p.enable(subcalls=True)
|
|
try:
|
|
return _sh(cwd, shargs, cmdargs, options, debugflags, env)
|
|
finally:
|
|
p.disable()
|
|
stats = lsprof.Stats(p.getstats())
|
|
stats.sort()
|
|
stats.pprint(top=10, file=sys.stderr, climit=5)
|
|
else:
|
|
return _sh(cwd, shargs, cmdargs, options, debugflags, env)
|
|
|
|
def main():
|
|
sys.exit(sh())
|
|
|
|
if __name__=='__main__':
|
|
main()
|