# 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()