summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Kluyver <takowl@gmail.com>2015-10-05 10:40:11 +0100
committerThomas Kluyver <takowl@gmail.com>2015-10-05 10:40:11 +0100
commitae07c52f56a2470f35257a6734b6b646cce13976 (patch)
treed8e9f477f26fda5ea6c53a26f7505c19cec4be49
parent697ba7f78933cbb81ac685eef5d93b6a72aa033a (diff)
parent823ad5164891ba148cc6857dc1400dedd1e78693 (diff)
downloadpexpect-git-ae07c52f56a2470f35257a6734b6b646cce13976.tar.gz
Merge pull request #268 from takluyver/windows-importable
Make pexpect importable on Windows, and update docs
-rw-r--r--doc/api/fdpexpect.rst12
-rw-r--r--doc/api/index.rst1
-rw-r--r--doc/api/popen_spawn.rst24
-rw-r--r--doc/history.rst7
-rw-r--r--doc/install.rst7
-rw-r--r--doc/overview.rst17
-rw-r--r--pexpect/__init__.py160
-rw-r--r--pexpect/fdpexpect.py3
-rw-r--r--pexpect/popen_spawn.py15
-rw-r--r--pexpect/pty_spawn.py2
-rw-r--r--pexpect/run.py157
11 files changed, 235 insertions, 170 deletions
diff --git a/doc/api/fdpexpect.rst b/doc/api/fdpexpect.rst
index 8321454..3ddf2cd 100644
--- a/doc/api/fdpexpect.rst
+++ b/doc/api/fdpexpect.rst
@@ -13,10 +13,8 @@ fdspawn class
.. automethod:: isalive
.. automethod:: close
- .. note::
- :class:`fdspawn` inherits all of the methods of :class:`~pexpect.spawn`,
- but not all of them can be used, especially if the file descriptor is not
- a terminal. Some methods may do nothing (e.g. :meth:`~fdspawn.kill`), while
- others will raise an exception (e.g. :meth:`~fdspawn.terminate`).
- This behaviour might be made more consistent in the future, so try to
- avoid relying on it. \ No newline at end of file
+ .. method:: expect
+ expect_exact
+ expect_list
+
+ As :class:`pexpect.spawn`.
diff --git a/doc/api/index.rst b/doc/api/index.rst
index fd017a5..5277d1c 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -6,6 +6,7 @@ API documentation
pexpect
fdpexpect
+ popen_spawn
replwrap
pxssh
diff --git a/doc/api/popen_spawn.rst b/doc/api/popen_spawn.rst
new file mode 100644
index 0000000..64cae15
--- /dev/null
+++ b/doc/api/popen_spawn.rst
@@ -0,0 +1,24 @@
+popen_spawn - use pexpect with a piped subprocess
+=================================================
+
+.. automodule:: pexpect.popen_spawn
+
+PopenSpawn class
+----------------
+
+.. autoclass:: PopenSpawn
+
+ .. automethod:: __init__
+ .. automethod:: send
+ .. automethod:: sendline
+ .. automethod:: write
+ .. automethod:: writelines
+ .. automethod:: kill
+ .. automethod:: sendeof
+ .. automethod:: wait
+
+ .. method:: expect
+ expect_exact
+ expect_list
+
+ As :class:`pexpect.spawn`.
diff --git a/doc/history.rst b/doc/history.rst
index b33e4d9..1167865 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -7,14 +7,15 @@ Releases
Version 4.0
```````````
-* Integration with :mod:`asyncio`: passing ``async=True`` to :meth:`~.expect`,
- :meth:`~.expect_exact` or :meth:`~.expect_list` will make them return a
+* Integration with :mod:`asyncio`: passing ``async=True`` to :meth:`~.spawn.expect`,
+ :meth:`~.spawn.expect_exact` or :meth:`~.spawn.expect_list` will make them return a
coroutine. You can get the result using ``yield from``, or wrap it in an
:class:`asyncio.Task`. This allows the event loop to do other things while
waiting for output that matches a pattern.
+* Experimental support for Windows (with some caveats)—see :ref:`windows`.
* Enhancement: allow method as callbacks of argument ``events`` for
:func:`pexpect.run` (:ghissue:`176`).
-* It is now possible to call :meth:`~.wait` multiple times, or after a process
+* It is now possible to call :meth:`~.spawn.wait` multiple times, or after a process
is already determined to be terminated without raising an exception
(:ghpull:`211`).
* New :class:`pexpect.spawn` keyword argument, ``dimensions=(rows, columns)``
diff --git a/doc/install.rst b/doc/install.rst
index 297acf3..d310871 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -15,6 +15,7 @@ Requirements
This version of Pexpect requires Python 2.6 or 3.2 or above. For older
versions of Python, continue using Pexpect 2.4.
-Pexpect only works on POSIX systems, where the :mod:`pty` module
-is present in the standard library. It may be possible to run it on Windows
-using `Cygwin <http://www.cygwin.com/>`_.
+As of version 4.0, Pexpect can be used on Windows and POSIX systems. However,
+:class:`pexpect.spawn` and :func:`pexpect.run` are only available on POSIX,
+where the :mod:`pty` module is present in the standard library. See
+:ref:`windows` for more information.
diff --git a/doc/overview.rst b/doc/overview.rst
index c5bcb05..d394ef1 100644
--- a/doc/overview.rst
+++ b/doc/overview.rst
@@ -238,3 +238,20 @@ You can have these methods ignore timeout and block indefinitely by passing
``None`` for the timeout parameter::
child.expect(pexpect.EOF, timeout=None)
+
+.. _windows:
+
+Pexpect on Windows
+------------------
+
+.. versionadded:: 4.0
+ Windows support
+
+Pexpect can be used on Windows to wait for a pattern to be produced by a child
+process, using :class:`pexpect.popen_spawn.PopenSpawn`, or a file descriptor,
+using :class:`pexpect.fdpexpect.fdspawn`. This should be considered experimental
+for now.
+
+:class:`pexpect.spawn` and :func:`pexpect.run` are *not* available on Windows,
+as they rely on Unix pseudoterminals (ptys). Cross platform code must not use
+these.
diff --git a/pexpect/__init__.py b/pexpect/__init__.py
index db5be16..1901531 100644
--- a/pexpect/__init__.py
+++ b/pexpect/__init__.py
@@ -64,168 +64,22 @@ PEXPECT LICENSE
'''
import sys
-import types
+PY3 = (sys.version_info[0] >= 3)
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
from .utils import split_command_line, which, is_executable_file
-from .pty_spawn import spawn, spawnu, PY3
from .expect import Expecter, searcher_re, searcher_string
+if sys.platform != 'win32':
+ # On Unix, these are available at the top level for backwards compatibility
+ from .pty_spawn import spawn, spawnu
+ from .run import run, runu
+
__version__ = '4.0.dev'
__revision__ = ''
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
'which', 'split_command_line', '__version__', '__revision__']
-def run(command, timeout=30, withexitstatus=False, events=None,
- extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
-
- '''
- This function runs the given command; waits for it to finish; then
- returns all output as a string. STDERR is included in output. If the full
- path to the command is not given then the path is searched.
-
- Note that lines are terminated by CR/LF (\\r\\n) combination even on
- UNIX-like systems because this is the standard for pseudottys. If you set
- 'withexitstatus' to true, then run will return a tuple of (command_output,
- exitstatus). If 'withexitstatus' is false then this returns just
- command_output.
-
- The run() function can often be used instead of creating a spawn instance.
- For example, the following code uses spawn::
-
- from pexpect import *
- child = spawn('scp foo user@example.com:.')
- child.expect('(?i)password')
- child.sendline(mypassword)
-
- The previous code can be replace with the following::
-
- from pexpect import *
- run('scp foo user@example.com:.', events={'(?i)password': mypassword})
-
- **Examples**
-
- Start the apache daemon on the local machine::
-
- from pexpect import *
- run("/usr/local/apache/bin/apachectl start")
-
- Check in a file using SVN::
-
- from pexpect import *
- run("svn ci -m 'automatic commit' my_file.py")
-
- Run a command and capture exit status::
-
- from pexpect import *
- (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1)
-
- The following will run SSH and execute 'ls -l' on the remote machine. The
- password 'secret' will be sent if the '(?i)password' pattern is ever seen::
-
- run("ssh username@machine.example.com 'ls -l'",
- events={'(?i)password':'secret\\n'})
-
- This will start mencoder to rip a video from DVD. This will also display
- progress ticks every 5 seconds as it runs. For example::
-
- from pexpect import *
- def print_ticks(d):
- print d['event_count'],
- run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
- events={TIMEOUT:print_ticks}, timeout=5)
-
- The 'events' argument should be either a dictionary or a tuple list that
- contains patterns and responses. Whenever one of the patterns is seen
- in the command output, run() will send the associated response string.
- So, run() in the above example can be also written as:
-
- run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
- events=[(TIMEOUT,print_ticks)], timeout=5)
-
- Use a tuple list for events if the command output requires a delicate
- control over what pattern should be matched, since the tuple list is passed
- to pexpect() as its pattern list, with the order of patterns preserved.
-
- Note that you should put newlines in your string if Enter is necessary.
-
- Like the example above, the responses may also contain a callback, either
- a function or method. It should accept a dictionary value as an argument.
- The dictionary contains all the locals from the run() function, so you can
- access the child spawn object or any other variable defined in run()
- (event_count, child, and extra_args are the most useful). A callback may
- return True to stop the current run process. Otherwise run() continues
- until the next event. A callback may also return a string which will be
- sent to the child. 'extra_args' is not used by directly run(). It provides
- a way to pass data to a callback function through run() through the locals
- dictionary passed to a callback.
-
- Like :class:`spawn`, passing *encoding* will make it work with unicode
- instead of bytes. You can pass *codec_errors* to control how errors in
- encoding and decoding are handled.
- '''
- if timeout == -1:
- child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
- **kwargs)
- else:
- child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
- cwd=cwd, env=env, **kwargs)
- if isinstance(events, list):
- patterns= [x for x,y in events]
- responses = [y for x,y in events]
- elif isinstance(events, dict):
- patterns = list(events.keys())
- responses = list(events.values())
- else:
- # This assumes EOF or TIMEOUT will eventually cause run to terminate.
- patterns = None
- responses = None
- child_result_list = []
- event_count = 0
- while True:
- try:
- index = child.expect(patterns)
- if isinstance(child.after, child.allowed_string_types):
- child_result_list.append(child.before + child.after)
- else:
- # child.after may have been a TIMEOUT or EOF,
- # which we don't want appended to the list.
- child_result_list.append(child.before)
- if isinstance(responses[index], child.allowed_string_types):
- child.send(responses[index])
- elif (isinstance(responses[index], types.FunctionType) or
- isinstance(responses[index], types.MethodType)):
- callback_result = responses[index](locals())
- sys.stdout.flush()
- if isinstance(callback_result, child.allowed_string_types):
- child.send(callback_result)
- elif callback_result:
- break
- else:
- raise TypeError("parameter `event' at index {index} must be "
- "a string, method, or function: {value!r}"
- .format(index=index, value=responses[index]))
- event_count = event_count + 1
- except TIMEOUT:
- child_result_list.append(child.before)
- break
- except EOF:
- child_result_list.append(child.before)
- break
- child_result = child.string_type().join(child_result_list)
- if withexitstatus:
- child.close()
- return (child_result, child.exitstatus)
- else:
- return child_result
-
-def runu(command, timeout=30, withexitstatus=False, events=None,
- extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
- """Deprecated: pass encoding to run() instead.
- """
- kwargs.setdefault('encoding', 'utf-8')
- return run(command, timeout=timeout, withexitstatus=withexitstatus,
- events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
- env=env, **kwargs)
+
# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
diff --git a/pexpect/fdpexpect.py b/pexpect/fdpexpect.py
index ca8cf07..dd1b492 100644
--- a/pexpect/fdpexpect.py
+++ b/pexpect/fdpexpect.py
@@ -85,10 +85,11 @@ class fdspawn(SpawnBase):
return False
def terminate (self, force=False): # pragma: no cover
+ '''Deprecated and invalid. Just raises an exception.'''
raise ExceptionPexpect('This method is not valid for file descriptors.')
# These four methods are left around for backwards compatibility, but not
- # documented as part of fdpexpect. You're encouraged to use os.write#
+ # documented as part of fdpexpect. You're encouraged to use os.write
# directly.
def send(self, s):
"Write to fd, return number of bytes written"
diff --git a/pexpect/popen_spawn.py b/pexpect/popen_spawn.py
index ab51499..680dd8a 100644
--- a/pexpect/popen_spawn.py
+++ b/pexpect/popen_spawn.py
@@ -1,4 +1,4 @@
-"""Spawn interface using subprocess.Popen
+"""Provides an interface like pexpect.spawn interface using subprocess.Popen
"""
import os
import threading
@@ -121,6 +121,10 @@ class PopenSpawn(SpawnBase):
self.send(s)
def send(self, s):
+ '''Send data to the subprocess' stdin.
+
+ Returns the number of bytes written.
+ '''
s = self._coerce_send_string(s)
self._log(s, 'send')
@@ -141,6 +145,10 @@ class PopenSpawn(SpawnBase):
return n + self.send(self.linesep)
def wait(self):
+ '''Wait for the subprocess to finish.
+
+ Returns the exit code.
+ '''
status = self.proc.wait()
if status >= 0:
self.exitstatus = status
@@ -152,6 +160,10 @@ class PopenSpawn(SpawnBase):
return status
def kill(self, sig):
+ '''Sends a Unix signal to the subprocess.
+
+ Use constants from the :mod:`signal` module to specify which signal.
+ '''
if sys.platform == 'win32':
if sig in [signal.SIGINT, signal.CTRL_C_EVENT]:
sig = signal.CTRL_C_EVENT
@@ -163,4 +175,5 @@ class PopenSpawn(SpawnBase):
os.kill(self.proc.pid, sig)
def sendeof(self):
+ '''Closes the stdin pipe from the writing end.'''
self.proc.stdin.close()
diff --git a/pexpect/pty_spawn.py b/pexpect/pty_spawn.py
index 09c008b..1d9554b 100644
--- a/pexpect/pty_spawn.py
+++ b/pexpect/pty_spawn.py
@@ -2,10 +2,8 @@ import os
import sys
import time
import select
-import re
import pty
import tty
-import termios
import errno
import signal
from contextlib import contextmanager
diff --git a/pexpect/run.py b/pexpect/run.py
new file mode 100644
index 0000000..d9dfe76
--- /dev/null
+++ b/pexpect/run.py
@@ -0,0 +1,157 @@
+import sys
+import types
+
+from .exceptions import EOF, TIMEOUT
+from .pty_spawn import spawn
+
+def run(command, timeout=30, withexitstatus=False, events=None,
+ extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
+
+ '''
+ This function runs the given command; waits for it to finish; then
+ returns all output as a string. STDERR is included in output. If the full
+ path to the command is not given then the path is searched.
+
+ Note that lines are terminated by CR/LF (\\r\\n) combination even on
+ UNIX-like systems because this is the standard for pseudottys. If you set
+ 'withexitstatus' to true, then run will return a tuple of (command_output,
+ exitstatus). If 'withexitstatus' is false then this returns just
+ command_output.
+
+ The run() function can often be used instead of creating a spawn instance.
+ For example, the following code uses spawn::
+
+ from pexpect import *
+ child = spawn('scp foo user@example.com:.')
+ child.expect('(?i)password')
+ child.sendline(mypassword)
+
+ The previous code can be replace with the following::
+
+ from pexpect import *
+ run('scp foo user@example.com:.', events={'(?i)password': mypassword})
+
+ **Examples**
+
+ Start the apache daemon on the local machine::
+
+ from pexpect import *
+ run("/usr/local/apache/bin/apachectl start")
+
+ Check in a file using SVN::
+
+ from pexpect import *
+ run("svn ci -m 'automatic commit' my_file.py")
+
+ Run a command and capture exit status::
+
+ from pexpect import *
+ (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1)
+
+ The following will run SSH and execute 'ls -l' on the remote machine. The
+ password 'secret' will be sent if the '(?i)password' pattern is ever seen::
+
+ run("ssh username@machine.example.com 'ls -l'",
+ events={'(?i)password':'secret\\n'})
+
+ This will start mencoder to rip a video from DVD. This will also display
+ progress ticks every 5 seconds as it runs. For example::
+
+ from pexpect import *
+ def print_ticks(d):
+ print d['event_count'],
+ run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
+ events={TIMEOUT:print_ticks}, timeout=5)
+
+ The 'events' argument should be either a dictionary or a tuple list that
+ contains patterns and responses. Whenever one of the patterns is seen
+ in the command output, run() will send the associated response string.
+ So, run() in the above example can be also written as:
+
+ run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
+ events=[(TIMEOUT,print_ticks)], timeout=5)
+
+ Use a tuple list for events if the command output requires a delicate
+ control over what pattern should be matched, since the tuple list is passed
+ to pexpect() as its pattern list, with the order of patterns preserved.
+
+ Note that you should put newlines in your string if Enter is necessary.
+
+ Like the example above, the responses may also contain a callback, either
+ a function or method. It should accept a dictionary value as an argument.
+ The dictionary contains all the locals from the run() function, so you can
+ access the child spawn object or any other variable defined in run()
+ (event_count, child, and extra_args are the most useful). A callback may
+ return True to stop the current run process. Otherwise run() continues
+ until the next event. A callback may also return a string which will be
+ sent to the child. 'extra_args' is not used by directly run(). It provides
+ a way to pass data to a callback function through run() through the locals
+ dictionary passed to a callback.
+
+ Like :class:`spawn`, passing *encoding* will make it work with unicode
+ instead of bytes. You can pass *codec_errors* to control how errors in
+ encoding and decoding are handled.
+ '''
+ if timeout == -1:
+ child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
+ **kwargs)
+ else:
+ child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
+ cwd=cwd, env=env, **kwargs)
+ if isinstance(events, list):
+ patterns= [x for x,y in events]
+ responses = [y for x,y in events]
+ elif isinstance(events, dict):
+ patterns = list(events.keys())
+ responses = list(events.values())
+ else:
+ # This assumes EOF or TIMEOUT will eventually cause run to terminate.
+ patterns = None
+ responses = None
+ child_result_list = []
+ event_count = 0
+ while True:
+ try:
+ index = child.expect(patterns)
+ if isinstance(child.after, child.allowed_string_types):
+ child_result_list.append(child.before + child.after)
+ else:
+ # child.after may have been a TIMEOUT or EOF,
+ # which we don't want appended to the list.
+ child_result_list.append(child.before)
+ if isinstance(responses[index], child.allowed_string_types):
+ child.send(responses[index])
+ elif (isinstance(responses[index], types.FunctionType) or
+ isinstance(responses[index], types.MethodType)):
+ callback_result = responses[index](locals())
+ sys.stdout.flush()
+ if isinstance(callback_result, child.allowed_string_types):
+ child.send(callback_result)
+ elif callback_result:
+ break
+ else:
+ raise TypeError("parameter `event' at index {index} must be "
+ "a string, method, or function: {value!r}"
+ .format(index=index, value=responses[index]))
+ event_count = event_count + 1
+ except TIMEOUT:
+ child_result_list.append(child.before)
+ break
+ except EOF:
+ child_result_list.append(child.before)
+ break
+ child_result = child.string_type().join(child_result_list)
+ if withexitstatus:
+ child.close()
+ return (child_result, child.exitstatus)
+ else:
+ return child_result
+
+def runu(command, timeout=30, withexitstatus=False, events=None,
+ extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
+ """Deprecated: pass encoding to run() instead.
+ """
+ kwargs.setdefault('encoding', 'utf-8')
+ return run(command, timeout=timeout, withexitstatus=withexitstatus,
+ events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
+ env=env, **kwargs)