[UDT] updated wexpect with a working one

This commit is contained in:
Benedek Racz 2019-04-24 15:11:09 +02:00
parent f946cbf8e1
commit d6a8cc5e57
2 changed files with 334 additions and 87 deletions

View File

@ -2,6 +2,7 @@
wexpect is a Windows alternative of [pexpect](https://pexpect.readthedocs.io/en/stable/). wexpect is a Windows alternative of [pexpect](https://pexpect.readthedocs.io/en/stable/).
---
## pexpect ## pexpect
Pexpect is a Python module for spawning child applications and controlling Pexpect is a Python module for spawning child applications and controlling
@ -36,6 +37,7 @@ For example::
This works even for commands that ask for passwords or other input outside of This works even for commands that ask for passwords or other input outside of
the normal stdio streams. the normal stdio streams.
---
## Wexpect ## Wexpect
Wexpect is a one-file code developed at University of Washington. There are several copy of this code, Wexpect is a one-file code developed at University of Washington. There are several copy of this code,
@ -47,16 +49,28 @@ Here are some useful links:
This repo tries to fix these limitations. This repo tries to fix these limitations.
---
## Installation and limitation of wexpect ## Installation and limitation of wexpect
Current version does *not* work on python-3.x. You need to use **python 2.x** to use wexpect. Current version does *not* work on python-3.x. You need to use **python 2.x** to use wexpect.
One (non stanbdard) package, **pypiwin32** needed to use wexpect. ### Standard installation
This version is uploaded to pypi server so you can easily install with pip:
pip install wexpect
### Manual installation
Because this is a tiny project dropping the wexpect.py file into your working directory is usually
good enough instead of installing. However in this case you need to install manually the one dependence.
One (non stanbdard) package, **pypiwin32** is needed by wexpect.
pip install pypiwin32 pip install pypiwin32
Dropping the wexpect.py file into your working directory is usually good enough instead of installing.
---
## Usage ## Usage
See pexpect examples for usage. See pexpect examples for usage.

View File

@ -82,8 +82,12 @@ try:
import resource import resource
import fcntl import fcntl
else: else:
from StringIO import StringIO
try: try:
from ctypes import windll
import pywintypes import pywintypes
from win32com.shell.shellcon import CSIDL_APPDATA
from win32com.shell.shell import SHGetSpecialFolderPath
from win32console import * from win32console import *
from win32process import * from win32process import *
from win32con import * from win32con import *
@ -93,6 +97,8 @@ try:
import winerror import winerror
except ImportError, e: except ImportError, e:
raise ImportError(str(e) + "\nThis package requires the win32 python packages.") raise ImportError(str(e) + "\nThis package requires the win32 python packages.")
screenbufferfillchar = u'\4'
maxconsoleY = 8000
except ImportError, e: except ImportError, e:
raise ImportError (str(e) + """ raise ImportError (str(e) + """
@ -274,9 +280,23 @@ def run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None
else: else:
return child_result return child_result
def spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): def spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None,
codepage=None):
log('=' * 80)
log('Buffer size: %s' % maxread)
if searchwindowsize:
log('Search window size: %s' % searchwindowsize)
log('Timeout: %ss' % timeout)
if env:
log('Environment:')
for name in env:
log('\t%s=%s' % (name, env[name]))
if cwd:
log('Working directory: %s' % cwd)
log('Spawning %s' % join_args([command] + args))
if sys.platform == 'win32': if sys.platform == 'win32':
return spawn_windows(command, args, timeout, maxread, searchwindowsize, logfile, cwd, env) return spawn_windows(command, args, timeout, maxread, searchwindowsize, logfile, cwd, env,
codepage)
else: else:
return spawn_unix(command, args, timeout, maxread, searchwindowsize, logfile, cwd, env) return spawn_unix(command, args, timeout, maxread, searchwindowsize, logfile, cwd, env)
@ -1543,7 +1563,10 @@ class spawn_unix (object):
while self.isalive(): while self.isalive():
r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], [])
if self.child_fd in r: if self.child_fd in r:
try:
data = self.__interact_read(self.child_fd) data = self.__interact_read(self.child_fd)
except OSError, e:
break
if output_filter: data = output_filter(data) if output_filter: data = output_filter(data)
if self.logfile is not None: if self.logfile is not None:
self.logfile.write (data) self.logfile.write (data)
@ -1608,7 +1631,8 @@ class spawn_windows (spawn_unix, object):
"""This is the main class interface for Pexpect. Use this class to start """This is the main class interface for Pexpect. Use this class to start
and control child applications. """ and control child applications. """
def __init__(self, command, args=[], timeout=30, maxread=60000, searchwindowsize=None, logfile=None, cwd=None, env=None): def __init__(self, command, args=[], timeout=30, maxread=60000, searchwindowsize=None, logfile=None, cwd=None, env=None,
codepage=None):
self.stdin = sys.stdin self.stdin = sys.stdin
self.stdout = sys.stdout self.stdout = sys.stdout
self.stderr = sys.stderr self.stderr = sys.stderr
@ -1643,6 +1667,7 @@ class spawn_windows (spawn_unix, object):
self.ocwd = os.getcwdu() self.ocwd = os.getcwdu()
self.cwd = cwd self.cwd = cwd
self.env = env self.env = env
self.codepage = codepage
# allow dummy instances for subclasses that may not use command or args. # allow dummy instances for subclasses that may not use command or args.
if command is None: if command is None:
@ -1701,12 +1726,20 @@ class spawn_windows (spawn_unix, object):
self.name = '<' + ' '.join (self.args) + '>' self.name = '<' + ' '.join (self.args) + '>'
self.wtty = Wtty() #assert self.pid is None, 'The pid member should be None.'
#assert self.command is not None, 'The command member should not be None.'
self.wtty = Wtty(codepage=self.codepage)
if self.cwd is not None: if self.cwd is not None:
os.chdir(self.cwd) os.chdir(self.cwd)
self.child_fd = self.wtty.spawn(self.command, self.args, self.env) self.child_fd = self.wtty.spawn(self.command, self.args, self.env)
if self.cwd is not None:
# Restore the original working dir
os.chdir(self.ocwd)
self.terminated = False self.terminated = False
self.closed = False self.closed = False
self.pid = self.wtty.pid self.pid = self.wtty.pid
@ -1759,20 +1792,20 @@ class spawn_windows (spawn_unix, object):
available right away then one character will be returned immediately. available right away then one character will be returned immediately.
It will not wait for 30 seconds for another 99 characters to come in. It will not wait for 30 seconds for another 99 characters to come in.
This is a wrapper around wtty.read(). It uses select.select() to This is a wrapper around Wtty.read(). """
implement the timeout. """
if self.closed: if self.closed:
raise ValueError ('I/O operation on closed file in read_nonblocking().') raise ValueError ('I/O operation on closed file in read_nonblocking().')
if not self.wtty.isalive():
self.flag_eof = True
if timeout is None:
# Do not raise TIMEOUT because we might be waiting for EOF
# sleep to keep CPU utilization down
time.sleep(.05)
else:
raise TIMEOUT ('Timeout exceeded in read_nonblocking().')
if timeout == -1: if timeout == -1:
timeout = self.timeout timeout = self.timeout
@ -1780,6 +1813,14 @@ class spawn_windows (spawn_unix, object):
s = self.wtty.read_nonblocking(timeout, size) s = self.wtty.read_nonblocking(timeout, size)
if s == '': if s == '':
if not self.wtty.isalive():
self.flag_eof = True
raise EOF('End Of File (EOF) in read_nonblocking().')
if timeout is None:
# Do not raise TIMEOUT because we might be waiting for EOF
# sleep to keep CPU utilization down
time.sleep(.05)
else:
raise TIMEOUT ('Timeout exceeded in read_nonblocking().') raise TIMEOUT ('Timeout exceeded in read_nonblocking().')
if self.logfile is not None: if self.logfile is not None:
@ -1903,23 +1944,31 @@ class spawn_windows (spawn_unix, object):
class Wtty: class Wtty:
def __init__(self, timeout=30): def __init__(self, timeout=30, codepage=None):
self.__buffer = StringIO()
self.__bufferY = 0
self.__currentReadCo = PyCOORDType(0, 0) self.__currentReadCo = PyCOORDType(0, 0)
self.__consSize = [80, 16000] self.__consSize = [80, 16000]
self.__parentPid = 0 self.__parentPid = 0
self.__oproc = 0 self.__oproc = 0
self.__opid = 0 self.conpid = 0
self.__otid = 0 self.__otid = 0
self.__switch = True self.__switch = True
self.__childProcess = None self.__childProcess = None
self.codepage = codepage
self.console = False self.console = False
self.lastRead = 0
self.lastReadData = ""
self.pid = None self.pid = None
self.processList = [] self.processList = []
# We need a timeout for connecting to the child process
self.timeout = timeout self.timeout = timeout
self.totalRead = 0
def spawn(self, command, args=[], env=None): def spawn(self, command, args=[], env=None):
"""Spawns spawner.py with correct arguments.""" """Spawns spawner.py with correct arguments."""
ts = time.time()
self.startChild(args, env) self.startChild(args, env)
while True: while True:
@ -1942,7 +1991,7 @@ class Wtty:
if not self.__childProcess: if not self.__childProcess:
raise ExceptionPexpect ('The process ' + args[0] + ' could not be started.') raise ExceptionPexpect ('The process ' + args[0] + ' could not be started.')
self.__childProcess = win32api.OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, 0, childPid)
winHandle = int(GetConsoleWindow()) winHandle = int(GetConsoleWindow())
@ -1971,6 +2020,11 @@ class Wtty:
dirname = os.path.dirname(sys.executable dirname = os.path.dirname(sys.executable
if getattr(sys, 'frozen', False) else if getattr(sys, 'frozen', False) else
os.path.abspath(__file__)) os.path.abspath(__file__))
if getattr(sys, 'frozen', False):
logdir = os.path.splitext(sys.executable)[0]
else:
logdir = dirname
logdir = os.path.basename(logdir)
spath = [dirname] spath = [dirname]
pyargs = ['-c'] pyargs = ['-c']
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
@ -1979,10 +2033,17 @@ class Wtty:
# py2exe: Needs appropriate 'zipfile' option in setup script and # py2exe: Needs appropriate 'zipfile' option in setup script and
# 'bundle_files' 3 # 'bundle_files' 3
spath.append(os.path.join(dirname, 'library.zip')) spath.append(os.path.join(dirname, 'library.zip'))
spath.append(os.path.join(dirname, 'lib', 'library.zip')) spath.append(os.path.join(dirname, 'library.zip',
os.path.basename(os.path.splitext(sys.executable)[0])))
if os.path.isdir(os.path.join(dirname, 'lib')):
dirname = os.path.join(dirname, 'lib')
spath.append(os.path.join(dirname, 'library.zip'))
spath.append(os.path.join(dirname, 'library.zip',
os.path.basename(os.path.splitext(sys.executable)[0])))
pyargs.insert(0, '-S') # skip 'import site' pyargs.insert(0, '-S') # skip 'import site'
pid = GetCurrentProcessId() pid = GetCurrentProcessId()
tid = win32api.GetCurrentThreadId() tid = win32api.GetCurrentThreadId()
cp = self.codepage or windll.kernel32.GetACP()
# If we are running 'frozen', expect python.exe in the same directory # If we are running 'frozen', expect python.exe in the same directory
# as the packed executable. # as the packed executable.
# py2exe: The python executable can be included via setup script by # py2exe: The python executable can be included via setup script by
@ -1993,9 +2054,10 @@ class Wtty:
' '.join(pyargs), ' '.join(pyargs),
"import sys; sys.path = %r + sys.path;" "import sys; sys.path = %r + sys.path;"
"args = %r; import wexpect;" "args = %r; import wexpect;"
"wexpect.ConsoleReader(wexpect.join_args(args), %i, %i)" % (spath, args, pid, tid)) "wexpect.ConsoleReader(wexpect.join_args(args), %i, %i, cp=%i, logdir=%r)" % (spath, args, pid, tid, cp, logdir))
self.__oproc, _, self.__opid, self.__otid = CreateProcess(None, commandLine, None, None, False,
self.__oproc, _, self.conpid, self.__otid = CreateProcess(None, commandLine, None, None, False,
CREATE_NEW_CONSOLE, env, None, si) CREATE_NEW_CONSOLE, env, None, si)
@ -2010,19 +2072,24 @@ class Wtty:
FreeConsole() FreeConsole()
try: try:
AttachConsole(self.__opid) AttachConsole(self.conpid)
self.__consin = GetStdHandle(STD_INPUT_HANDLE)
self.__consout = self.getConsoleOut()
except Exception, e: except Exception, e:
#e = traceback.format_exc()
try: try:
AttachConsole(self.__parentPid) AttachConsole(self.__parentPid)
except Exception, ex: except Exception, ex:
log_error(e) pass
log_error(ex) #log(e)
self.__consin = None #log(ex)
self.__consout = None return
raise e #self.__consin = None
#self.__consout = None
#raise e
self.__consin = GetStdHandle(STD_INPUT_HANDLE)
self.__consout = self.getConsoleOut()
def switchBack(self): def switchBack(self):
"""Releases from the current console and attaches """Releases from the current console and attaches
@ -2087,12 +2154,18 @@ class Wtty:
if s[-1] == '\n': if s[-1] == '\n':
s = s[:-1] s = s[:-1]
records = [self.createKeyEvent(c) for c in unicode(s)] records = [self.createKeyEvent(c) for c in unicode(s)]
if not self.__consout:
return ""
consinfo = self.__consout.GetConsoleScreenBufferInfo() consinfo = self.__consout.GetConsoleScreenBufferInfo()
startCo = consinfo['CursorPosition'] startCo = consinfo['CursorPosition']
wrote = self.__consin.WriteConsoleInput(records) wrote = self.__consin.WriteConsoleInput(records)
while self.__consin.PeekConsoleInput(8) != (): ts = time.time()
time.sleep(0) while self.__consin and self.__consin.PeekConsoleInput(8) != ():
self.__consout.FillConsoleOutputCharacter(u'\0', len(s), startCo) if time.time() > ts + len(s) * .05:
break
time.sleep(.05)
if self.__consout:
self.__consout.FillConsoleOutputCharacter(screenbufferfillchar, len(s), startCo)
except: except:
self.switchBack() self.switchBack()
raise raise
@ -2116,7 +2189,7 @@ class Wtty:
as a string.""" as a string."""
buff = [] buff = []
totalRead = 0 self.lastRead = 0
startX = startCo.X startX = startCo.X
startY = startCo.Y startY = startCo.Y
@ -2135,6 +2208,9 @@ class Wtty:
endPoint = self.getPoint(endOff) endPoint = self.getPoint(endOff)
s = self.__consout.ReadConsoleOutputCharacter(readlen, startCo) s = self.__consout.ReadConsoleOutputCharacter(readlen, startCo)
ln = len(s)
self.lastRead += ln
self.totalRead += ln
buff.append(s) buff.append(s)
startX, startY = endPoint[0], endPoint[1] startX, startY = endPoint[0], endPoint[1]
@ -2149,31 +2225,101 @@ class Wtty:
characters or screen-buffer-fill characters.""" characters or screen-buffer-fill characters."""
strlist = [] strlist = []
for c in s: for i, c in enumerate(s):
if ord(c) == 9834: if c == screenbufferfillchar:
strlist.append('\r') if (self.totalRead - self.lastRead + i + 1) % 80 == 0:
elif ord(c) == 9689:
strlist.append('\n')
elif ord(c) == 0:
if strlist[-1:] != ['\r\n']:
strlist.append('\r\n') strlist.append('\r\n')
else: else:
strlist.append(c) strlist.append(c)
return ''.join(strlist).encode('ascii', 'ignore') s = ''.join(strlist)
try:
return s.encode(str(self.codepage), 'replace')
except LookupError:
return s.encode(getattr(sys.stdout, 'encoding', None) or
sys.getdefaultencoding(), 'replace')
def readConsoleToCursor(self): def readConsoleToCursor(self):
"""Reads from the current read position to the current cursor """Reads from the current read position to the current cursor
position and inserts the string into self.__buffer.""" position and inserts the string into self.__buffer."""
if not self.__consout:
return ""
consinfo = self.__consout.GetConsoleScreenBufferInfo() consinfo = self.__consout.GetConsoleScreenBufferInfo()
cursorPos = consinfo['CursorPosition'] cursorPos = consinfo['CursorPosition']
if cursorPos.X == self.__currentReadCo.X and cursorPos.Y == self.__currentReadCo.Y: #log('=' * 80)
return '' #log('cursor: %r, current: %r' % (cursorPos, self.__currentReadCo))
s = self.readConsole(self.__currentReadCo, cursorPos) isSameX = cursorPos.X == self.__currentReadCo.X
s = self.parseData(s) isSameY = cursorPos.Y == self.__currentReadCo.Y
isSamePos = isSameX and isSameY
#log('isSameY: %r' % isSameY)
#log('isSamePos: %r' % isSamePos)
if isSameY or not self.lastReadData.endswith('\r\n'):
# Read the current slice again
self.totalRead -= self.lastRead
self.__currentReadCo.X = 0
self.__currentReadCo.Y = self.__bufferY
#log('cursor: %r, current: %r' % (cursorPos, self.__currentReadCo))
raw = self.readConsole(self.__currentReadCo, cursorPos)
rawlist = []
while raw:
rawlist.append(raw[:self.__consSize[0]])
raw = raw[self.__consSize[0]:]
raw = ''.join(rawlist)
s = self.parseData(raw)
for i, line in enumerate(reversed(rawlist)):
if line.endswith(screenbufferfillchar):
# Record the Y offset where the most recent line break was detected
self.__bufferY += len(rawlist) - i
break
#log('lastReadData: %r' % self.lastReadData)
#log('s: %r' % s)
#isSameData = False
if isSamePos and self.lastReadData == s:
#isSameData = True
s = ''
#log('isSameData: %r' % isSameData)
#log('s: %r' % s)
if s:
lastReadData = self.lastReadData
pos = self.getOffset(self.__currentReadCo.X, self.__currentReadCo.Y)
self.lastReadData = s
if isSameY or not lastReadData.endswith('\r\n'):
# Detect changed lines
self.__buffer.seek(pos)
buf = self.__buffer.read()
#log('buf: %r' % buf)
#log('raw: %r' % raw)
if raw.startswith(buf):
# Line has grown
rawslice = raw[len(buf):]
# Update last read bytes so line breaks can be detected in parseData
lastRead = self.lastRead
self.lastRead = len(rawslice)
s = self.parseData(rawslice)
self.lastRead = lastRead
else:
# Cursor has been repositioned
s = '\r' + s
#log('s: %r' % s)
self.__buffer.seek(pos)
self.__buffer.truncate()
self.__buffer.write(raw)
self.__currentReadCo.X = cursorPos.X self.__currentReadCo.X = cursorPos.X
self.__currentReadCo.Y = cursorPos.Y self.__currentReadCo.Y = cursorPos.Y
@ -2190,17 +2336,20 @@ class Wtty:
try: try:
while True: while True:
#Wait for child process to be paused #Wait for child process to be paused
if self.__currentReadCo.Y > 8000: if self.__currentReadCo.Y > maxconsoleY:
time.sleep(.2) time.sleep(.2)
start = time.clock() start = time.clock()
s = self.readConsoleToCursor() s = self.readConsoleToCursor()
if self.__currentReadCo.Y > maxconsoleY:
self.refreshConsole()
if len(s) != 0: if len(s) != 0:
self.switchBack() self.switchBack()
return s return s
if timeout <= 0: if not self.isalive() or timeout <= 0:
self.switchBack() self.switchBack()
return '' return ''
@ -2208,11 +2357,13 @@ class Wtty:
end = time.clock() end = time.clock()
timeout -= end - start timeout -= end - start
if self.__currentReadCo.Y > 8000:
self.resetConsole()
except Exception, e: except Exception, e:
log(e)
log('End Of File (EOF) in Wtty.read_nonblocking().')
self.switchBack() self.switchBack()
raise e raise EOF('End Of File (EOF) in Wtty.read_nonblocking().')
self.switchBack() self.switchBack()
return s return s
@ -2226,7 +2377,15 @@ class Wtty:
self.__currentReadCo.X = 0 self.__currentReadCo.X = 0
self.__currentReadCo.Y = 0 self.__currentReadCo.Y = 0
writelen = self.__consSize[0] * self.__consSize[1] writelen = self.__consSize[0] * self.__consSize[1]
self.__consout.FillConsoleOutputCharacter(u'\4', writelen, orig) # Use NUL as fill char because it displays as whitespace
# (if we interact() with the child)
self.__consout.FillConsoleOutputCharacter(screenbufferfillchar, writelen, orig)
self.__bufferY = 0
self.__buffer.truncate(0)
#consinfo = self.__consout.GetConsoleScreenBufferInfo()
#cursorPos = consinfo['CursorPosition']
#log('refreshConsole: cursorPos %s' % cursorPos)
def setecho(self, state): def setecho(self, state):
@ -2329,7 +2488,21 @@ class Wtty:
class ConsoleReader: class ConsoleReader:
def __init__(self, path, pid, tid, env = None): def __init__(self, path, pid, tid, env = None, cp=None, logdir=None):
self.logdir = logdir
log('=' * 80, 'consolereader', logdir)
log("OEM code page: %s" % windll.kernel32.GetOEMCP(), 'consolereader', logdir)
log("ANSI code page: %s" % windll.kernel32.GetACP(), 'consolereader', logdir)
log("Console output code page: %s" % windll.kernel32.GetConsoleOutputCP(), 'consolereader', logdir)
if cp:
log("Setting console output code page to %s" % cp, 'consolereader', logdir)
try:
SetConsoleOutputCP(cp)
except Exception, e:
log(e, 'consolereader', logdir)
else:
log("Console output code page: %s" % windll.kernel32.GetConsoleOutputCP(), 'consolereader', logdir)
log('Spawning %s' % path, 'consolereader', logdir)
try: try:
try: try:
consout = self.getConsoleOut() consout = self.getConsoleOut()
@ -2339,7 +2512,7 @@ class ConsoleReader:
self.__childProcess, _, childPid, self.__tid = CreateProcess(None, path, None, None, False, self.__childProcess, _, childPid, self.__tid = CreateProcess(None, path, None, None, False,
0, None, None, si) 0, None, None, si)
except Exception, e: except Exception, e:
log_error(e) log(e, 'consolereader', logdir)
time.sleep(.1) time.sleep(.1)
win32api.PostThreadMessage(int(tid), WM_USER, 0, 0) win32api.PostThreadMessage(int(tid), WM_USER, 0, 0)
sys.exit() sys.exit()
@ -2356,6 +2529,7 @@ class ConsoleReader:
cursorPos = consinfo['CursorPosition'] cursorPos = consinfo['CursorPosition']
if GetExitCodeProcess(parent) != STILL_ACTIVE or GetExitCodeProcess(self.__childProcess) != STILL_ACTIVE: if GetExitCodeProcess(parent) != STILL_ACTIVE or GetExitCodeProcess(self.__childProcess) != STILL_ACTIVE:
time.sleep(.1)
try: try:
TerminateProcess(self.__childProcess, 0) TerminateProcess(self.__childProcess, 0)
except pywintypes.error, e: except pywintypes.error, e:
@ -2364,22 +2538,30 @@ class ConsoleReader:
# Don't log. Child process will exit regardless when # Don't log. Child process will exit regardless when
# calling sys.exit # calling sys.exit
if e.args[0] != winerror.ERROR_ACCESS_DENIED: if e.args[0] != winerror.ERROR_ACCESS_DENIED:
log_error(e) log(e, 'consolereader', logdir)
sys.exit()
if cursorPos.Y > 8000: if cursorPos.Y > maxconsoleY and not paused:
#log('ConsoleReader.__init__: cursorPos %s'
#% cursorPos, 'consolereader', logdir)
#log('suspendThread', 'consolereader', logdir)
self.suspendThread() self.suspendThread()
paused = True paused = True
if cursorPos.Y <= 8000 and paused: if cursorPos.Y <= maxconsoleY and paused:
#log('ConsoleReader.__init__: cursorPos %s'
#% cursorPos, 'consolereader', logdir)
#log('resumeThread', 'consolereader', logdir)
self.resumeThread() self.resumeThread()
paused = False paused = False
time.sleep(.1) time.sleep(.1)
except Exception, e: except Exception, e:
log_error(e) log(e, 'consolereader', logdir)
time.sleep(.1)
def handler(self, sig): def handler(self, sig):
log_error(sig) log(sig, 'consolereader', logdir)
return False return False
def getConsoleOut(self): def getConsoleOut(self):
@ -2394,12 +2576,14 @@ class ConsoleReader:
return PyConsoleScreenBufferType(consout) return PyConsoleScreenBufferType(consout)
def initConsole(self, consout): def initConsole(self, consout):
rect = PySMALL_RECTType(0, 0, 79, 70) rect = PySMALL_RECTType(0, 0, 79, 24)
consout.SetConsoleWindowInfo(True, rect) consout.SetConsoleWindowInfo(True, rect)
size = PyCOORDType(80, 16000) size = PyCOORDType(80, 16000)
consout.SetConsoleScreenBufferSize(size) consout.SetConsoleScreenBufferSize(size)
pos = PyCOORDType(0, 0) pos = PyCOORDType(0, 0)
consout.FillConsoleOutputCharacter(u'\0', size.X * size.Y, pos) # Use NUL as fill char because it displays as whitespace
# (if we interact() with the child)
consout.FillConsoleOutputCharacter(screenbufferfillchar, size.X * size.Y, pos)
def suspendThread(self): def suspendThread(self):
"""Pauses the main thread of the child process.""" """Pauses the main thread of the child process."""
@ -2595,27 +2779,76 @@ class searcher_re (object):
self.end = self.match.end() self.end = self.match.end()
return best_index return best_index
def log_error(e): def log(e, suffix='', logdir=None):
if isinstance(e, Exception): if isinstance(e, Exception):
# Get the full traceback # Get the full traceback
e = traceback.format_exc() e = traceback.format_exc()
if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): #if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
# Only try to print if stdout is a tty, otherwise we might get ## Only try to print if stdout is a tty, otherwise we might get
# an 'invalid handle' exception ## an 'invalid handle' exception
print e #print e
# Log to the script (or packed executable if running 'frozen') directory if not logdir:
# if it is writable (packed executable might be installed to a location if getattr(sys, 'frozen', False):
# where we don't have write access) logdir = os.path.splitext(os.path.basename(sys.executable))[0]
dirname = os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else os.path.abspath(__file__)) else:
if os.access(dirname, os.W_OK): logdir = os.path.split(os.path.dirname(os.path.abspath(__file__)))
fout = open(os.path.join(dirname, 'pexpect_error.txt'), 'a') if logdir[-1] == 'lib':
fout.write(str(e) + '\n') logdir.pop()
logdir = logdir[-1]
if sys.platform == "win32":
logdir = os.path.join(SHGetSpecialFolderPath(0, CSIDL_APPDATA),
logdir, "logs")
elif sys.platform == "darwin":
logdir = os.path.join(os.path.expanduser("~"), "Library", "Logs",
logdir)
else:
logdir = os.path.join(os.getenv("XDG_DATA_HOME",
os.path.expandvars("$HOME/.local/share")),
logdir, "logs")
if not os.path.exists(logdir):
try:
os.makedirs(logdir)
except (OSError, WindowsError):
pass
if os.path.isdir(logdir) and os.access(logdir, os.W_OK):
logfile = os.path.join(logdir, 'wexpect%s.log' % suffix)
if os.path.isfile(logfile):
try:
logstat = os.stat(logfile)
except Exception, exception:
pass
else:
try:
mtime = time.localtime(logstat.st_mtime)
except ValueError, exception:
# This can happen on Windows because localtime() is buggy on
# that platform. See:
# http://stackoverflow.com/questions/4434629/zipfile-module-in-python-runtime-problems
# http://bugs.python.org/issue1760357
# To overcome this problem, we ignore the real modification
# date and force a rollover
mtime = time.localtime(time() - 60 * 60 * 24)
if time.localtime()[:3] > mtime[:3]:
# do rollover
try:
os.remove(logfile)
except Exception, exception:
pass
try:
fout = open(logfile, 'a')
except:
pass
else:
ts = time.time()
fout.write('%s,%s %s\n' % (time.strftime("%Y-%m-%d %H:%M:%S",
time.localtime(ts)),
('%3f' % (ts - int(ts)))[2:5], e))
fout.close() fout.close()
def excepthook(etype, value, tb): def excepthook(etype, value, tb):
log_error(''.join(traceback.format_exception(etype, value, tb))) log(''.join(traceback.format_exception(etype, value, tb)))
sys.excepthook = excepthook #sys.excepthook = excepthook
def which (filename): def which (filename):
@ -2646,10 +2879,10 @@ def which (filename):
def join_args(args): def join_args(args):
"""Joins arguments into a command line. It quotes all arguments that contain """Joins arguments into a command line. It quotes all arguments that contain
spaces or any of the characters ^!$%&()[]""" spaces or any of the characters ^!$%&()[]{}=;'+,`~"""
commandline = [] commandline = []
for arg in args: for arg in args:
if re.search('[\^!$%&()[\]\s]', arg): if re.search('[\^!$%&()[\]{}=;\'+,`~\s]', arg):
arg = '"%s"' % arg arg = '"%s"' % arg
commandline.append(arg) commandline.append(arg)
return ' '.join(commandline) return ' '.join(commandline)