1 """Generic wrapper for read-eval-print-loops, a.k.a. interactive shells
9 PY3
= (sys
.version_info
[0] >= 3)
14 PEXPECT_PROMPT
= u
'[PEXPECT_PROMPT>'
15 PEXPECT_CONTINUATION_PROMPT
= u
'[PEXPECT_PROMPT+'
17 class REPLWrapper(object):
18 """Wrapper for a REPL.
20 :param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn`
21 in which a REPL has already been started, or a str command to start a new
23 :param str orig_prompt: The prompt to expect at first.
24 :param str prompt_change: A command to change the prompt to something more
25 unique. If this is ``None``, the prompt will not be changed. This will
26 be formatted with the new and continuation prompts as positional
27 parameters, so you can use ``{}`` style formatting to insert them into
29 :param str new_prompt: The more unique prompt to expect after the change.
30 :param str extra_init_cmd: Commands to do extra initialisation, such as
33 def __init__(self
, cmd_or_spawn
, orig_prompt
, prompt_change
,
34 new_prompt
=PEXPECT_PROMPT
,
35 continuation_prompt
=PEXPECT_CONTINUATION_PROMPT
,
37 if isinstance(cmd_or_spawn
, basestring
):
38 self
.child
= pexpect
.spawn(cmd_or_spawn
, echo
=False, encoding
='utf-8')
40 self
.child
= cmd_or_spawn
42 # Existing spawn instance has echo enabled, disable it
43 # to prevent our input from being repeated to output.
44 self
.child
.setecho(False)
45 self
.child
.waitnoecho()
47 if prompt_change
is None:
48 self
.prompt
= orig_prompt
50 self
.set_prompt(orig_prompt
,
51 prompt_change
.format(new_prompt
, continuation_prompt
))
52 self
.prompt
= new_prompt
53 self
.continuation_prompt
= continuation_prompt
57 if extra_init_cmd
is not None:
58 self
.run_command(extra_init_cmd
)
60 def set_prompt(self
, orig_prompt
, prompt_change
):
61 self
.child
.expect(orig_prompt
)
62 self
.child
.sendline(prompt_change
)
64 def _expect_prompt(self
, timeout
=-1):
65 return self
.child
.expect_exact([self
.prompt
, self
.continuation_prompt
],
68 def run_command(self
, command
, timeout
=-1):
69 """Send a command to the REPL, wait for and return output.
71 :param str command: The command to send. Trailing newlines are not needed.
72 This should be a complete block of input that will trigger execution;
73 if a continuation prompt is found after sending input, :exc:`ValueError`
75 :param int timeout: How long to wait for the next prompt. -1 means the
76 default from the :class:`pexpect.spawn` object (default 30 seconds).
77 None means to wait indefinitely.
79 # Split up multiline commands and feed them in bit-by-bit
80 cmdlines
= command
.splitlines()
81 # splitlines ignores trailing newlines - add it back in manually
82 if command
.endswith('\n'):
85 raise ValueError("No command was given")
88 self
.child
.sendline(cmdlines
[0])
89 for line
in cmdlines
[1:]:
90 self
._expect
_prompt
(timeout
=timeout
)
91 res
.append(self
.child
.before
)
92 self
.child
.sendline(line
)
94 # Command was fully submitted, now wait for the next prompt
95 if self
._expect
_prompt
(timeout
=timeout
) == 1:
96 # We got the continuation prompt - command was incomplete
97 self
.child
.kill(signal
.SIGINT
)
98 self
._expect
_prompt
(timeout
=1)
99 raise ValueError("Continuation prompt found - input was incomplete:\n"
101 return u
''.join(res
+ [self
.child
.before
])
103 def python(command
="python"):
104 """Start a Python shell and return a :class:`REPLWrapper` object."""
105 return REPLWrapper(command
, u
">>> ", u
"import sys; sys.ps1={0!r}; sys.ps2={1!r}")
107 def bash(command
="bash"):
108 """Start a bash shell and return a :class:`REPLWrapper` object."""
109 bashrc
= os
.path
.join(os
.path
.dirname(__file__
), 'bashrc.sh')
110 child
= pexpect
.spawn(command
, ['--rcfile', bashrc
], echo
=False,
113 # If the user runs 'env', the value of PS1 will be in the output. To avoid
114 # replwrap seeing that as the next prompt, we'll embed the marker characters
115 # for invisible characters in the prompt; these show up when inspecting the
116 # environment variable, but not when bash displays the prompt.
117 ps1
= PEXPECT_PROMPT
[:5] + u
'\\[\\]' + PEXPECT_PROMPT
[5:]
118 ps2
= PEXPECT_CONTINUATION_PROMPT
[:5] + u
'\\[\\]' + PEXPECT_CONTINUATION_PROMPT
[5:]
119 prompt_change
= u
"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1
, ps2
)
121 return REPLWrapper(child
, u
'\\$', prompt_change
,
122 extra_init_cmd
="export PAGER=cat")