diff options
| author | Mikhail Borisov <[email protected]> | 2022-02-10 16:45:39 +0300 |
|---|---|---|
| committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:45:39 +0300 |
| commit | a6a92afe03e02795227d2641b49819b687f088f8 (patch) | |
| tree | f6984a1d27d5a7ec88a6fdd6e20cd5b7693b6ece /contrib/python/pexpect | |
| parent | c6dc8b8bd530985bc4cce0137e9a5de32f1087cb (diff) | |
Restoring authorship annotation for Mikhail Borisov <[email protected]>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pexpect')
| -rw-r--r-- | contrib/python/pexpect/LICENSE | 30 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/ANSI.py | 702 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/FSM.py | 668 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/__init__.py | 168 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/_async.py | 114 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/exceptions.py | 70 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/expect.py | 508 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/fdpexpect.py | 212 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/popen_spawn.py | 332 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/pty_spawn.py | 1446 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/pxssh.py | 754 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/replwrap.py | 218 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/run.py | 312 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/screen.py | 858 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/spawnbase.py | 940 | ||||
| -rw-r--r-- | contrib/python/pexpect/pexpect/utils.py | 216 | ||||
| -rw-r--r-- | contrib/python/pexpect/ya.make | 46 |
17 files changed, 3797 insertions, 3797 deletions
diff --git a/contrib/python/pexpect/LICENSE b/contrib/python/pexpect/LICENSE index 754db5afcb8..11f4b1686f4 100644 --- a/contrib/python/pexpect/LICENSE +++ b/contrib/python/pexpect/LICENSE @@ -1,20 +1,20 @@ ISC LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2013-2014, Pexpect development team - Copyright (c) 2012, Noah Spurrier <[email protected]> - + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2013-2014, Pexpect development team + Copyright (c) 2012, Noah Spurrier <[email protected]> + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/contrib/python/pexpect/pexpect/ANSI.py b/contrib/python/pexpect/pexpect/ANSI.py index 1cd2e90e7ab..50bd7732f7f 100644 --- a/contrib/python/pexpect/pexpect/ANSI.py +++ b/contrib/python/pexpect/pexpect/ANSI.py @@ -1,351 +1,351 @@ -'''This implements an ANSI (VT100) terminal emulator as a subclass of screen. - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -# references: -# http://en.wikipedia.org/wiki/ANSI_escape_code -# http://www.retards.org/terminals/vt102.html -# http://vt100.net/docs/vt102-ug/contents.html -# http://vt100.net/docs/vt220-rm/ -# http://www.termsys.demon.co.uk/vtansi.htm - -from . import screen -from . import FSM -import string - -# -# The 'Do.*' functions are helper functions for the ANSI class. -# -def DoEmit (fsm): - - screen = fsm.memory[0] - screen.write_ch(fsm.input_symbol) - -def DoStartNumber (fsm): - - fsm.memory.append (fsm.input_symbol) - -def DoBuildNumber (fsm): - - ns = fsm.memory.pop() - ns = ns + fsm.input_symbol - fsm.memory.append (ns) - -def DoBackOne (fsm): - - screen = fsm.memory[0] - screen.cursor_back () - -def DoBack (fsm): - - count = int(fsm.memory.pop()) - screen = fsm.memory[0] - screen.cursor_back (count) - -def DoDownOne (fsm): - - screen = fsm.memory[0] - screen.cursor_down () - -def DoDown (fsm): - - count = int(fsm.memory.pop()) - screen = fsm.memory[0] - screen.cursor_down (count) - -def DoForwardOne (fsm): - - screen = fsm.memory[0] - screen.cursor_forward () - -def DoForward (fsm): - - count = int(fsm.memory.pop()) - screen = fsm.memory[0] - screen.cursor_forward (count) - -def DoUpReverse (fsm): - - screen = fsm.memory[0] - screen.cursor_up_reverse() - -def DoUpOne (fsm): - - screen = fsm.memory[0] - screen.cursor_up () - -def DoUp (fsm): - - count = int(fsm.memory.pop()) - screen = fsm.memory[0] - screen.cursor_up (count) - -def DoHome (fsm): - - c = int(fsm.memory.pop()) - r = int(fsm.memory.pop()) - screen = fsm.memory[0] - screen.cursor_home (r,c) - -def DoHomeOrigin (fsm): - - c = 1 - r = 1 - screen = fsm.memory[0] - screen.cursor_home (r,c) - -def DoEraseDown (fsm): - - screen = fsm.memory[0] - screen.erase_down() - -def DoErase (fsm): - - arg = int(fsm.memory.pop()) - screen = fsm.memory[0] - if arg == 0: - screen.erase_down() - elif arg == 1: - screen.erase_up() - elif arg == 2: - screen.erase_screen() - -def DoEraseEndOfLine (fsm): - - screen = fsm.memory[0] - screen.erase_end_of_line() - -def DoEraseLine (fsm): - - arg = int(fsm.memory.pop()) - screen = fsm.memory[0] - if arg == 0: - screen.erase_end_of_line() - elif arg == 1: - screen.erase_start_of_line() - elif arg == 2: - screen.erase_line() - -def DoEnableScroll (fsm): - - screen = fsm.memory[0] - screen.scroll_screen() - -def DoCursorSave (fsm): - - screen = fsm.memory[0] - screen.cursor_save_attrs() - -def DoCursorRestore (fsm): - - screen = fsm.memory[0] - screen.cursor_restore_attrs() - -def DoScrollRegion (fsm): - - screen = fsm.memory[0] - r2 = int(fsm.memory.pop()) - r1 = int(fsm.memory.pop()) - screen.scroll_screen_rows (r1,r2) - -def DoMode (fsm): - - screen = fsm.memory[0] - mode = fsm.memory.pop() # Should be 4 - # screen.setReplaceMode () - -def DoLog (fsm): - - screen = fsm.memory[0] - fsm.memory = [screen] - fout = open ('log', 'a') - fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') - fout.close() - -class term (screen.screen): - - '''This class is an abstract, generic terminal. - This does nothing. This is a placeholder that - provides a common base class for other terminals - such as an ANSI terminal. ''' - - def __init__ (self, r=24, c=80, *args, **kwargs): - - screen.screen.__init__(self, r,c,*args,**kwargs) - -class ANSI (term): - '''This class implements an ANSI (VT100) terminal. - It is a stream filter that recognizes ANSI terminal - escape sequences and maintains the state of a screen object. ''' - - def __init__ (self, r=24,c=80,*args,**kwargs): - - term.__init__(self,r,c,*args,**kwargs) - - #self.screen = screen (24,80) - self.state = FSM.FSM ('INIT',[self]) - self.state.set_default_transition (DoLog, 'INIT') - self.state.add_transition_any ('INIT', DoEmit, 'INIT') - self.state.add_transition ('\x1b', 'INIT', None, 'ESC') - self.state.add_transition_any ('ESC', DoLog, 'INIT') - self.state.add_transition ('(', 'ESC', None, 'G0SCS') - self.state.add_transition (')', 'ESC', None, 'G1SCS') - self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') - self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') - self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') - self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') - self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') - self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') - self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') - self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. - self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') - self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') - self.state.add_transition ('[', 'ESC', None, 'ELB') - # ELB means Escape Left Bracket. That is ^[[ - self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') - self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') - self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') - self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') - self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') - self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') - self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') - self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') - self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT') - self.state.add_transition ('?', 'ELB', None, 'MODECRAP') - self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1') - self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1') - self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') - self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') - self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') - self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') - self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') - self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') - self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') - ### It gets worse... the 'm' code can have infinite number of - ### number;number;number before it. I've never seen more than two, - ### but the specs say it's allowed. crap! - self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT') - ### LED control. Same implementation problem as 'm' code. - self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT') - - # \E[?47h switch to alternate screen - # \E[?47l restores to normal screen from alternate screen. - self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM') - self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM') - self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT') - self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT') - -#RM Reset Mode Esc [ Ps l none - self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') - self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT') - self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2') - self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2') - self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT') - self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') - self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') - self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') - ### It gets worse... the 'm' code can have infinite number of - ### number;number;number before it. I've never seen more than two, - ### but the specs say it's allowed. crap! - self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT') - ### LED control. Same problem as 'm' code. - self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT') - self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') - - # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers - self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT') - self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X') - self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X') - self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT') - self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT') - self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT') - self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X') - - def process (self, c): - """Process a single character. Called by :meth:`write`.""" - if isinstance(c, bytes): - c = self._decode(c) - self.state.process(c) - - def process_list (self, l): - - self.write(l) - - def write (self, s): - """Process text, writing it to the virtual screen while handling - ANSI escape codes. - """ - if isinstance(s, bytes): - s = self._decode(s) - for c in s: - self.process(c) - - def flush (self): - pass - - def write_ch (self, ch): - '''This puts a character at the current cursor position. The cursor - position is moved forward with wrap-around, but no scrolling is done if - the cursor hits the lower-right corner of the screen. ''' - - if isinstance(ch, bytes): - ch = self._decode(ch) - - #\r and \n both produce a call to cr() and lf(), respectively. - ch = ch[0] - - if ch == u'\r': - self.cr() - return - if ch == u'\n': - self.crlf() - return - if ch == chr(screen.BS): - self.cursor_back() - return - self.put_abs(self.cur_r, self.cur_c, ch) - old_r = self.cur_r - old_c = self.cur_c - self.cursor_forward() - if old_c == self.cur_c: - self.cursor_down() - if old_r != self.cur_r: - self.cursor_home (self.cur_r, 1) - else: - self.scroll_up () - self.cursor_home (self.cur_r, 1) - self.erase_line() - - def do_sgr (self, fsm): - '''Select Graphic Rendition, e.g. color. ''' - screen = fsm.memory[0] - fsm.memory = [screen] - - def do_decsca (self, fsm): - '''Select character protection attribute. ''' - screen = fsm.memory[0] - fsm.memory = [screen] - - def do_modecrap (self, fsm): - '''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone - wanted to actually use these, they'd need to add more states to the - FSM rather than just improve or override this method. ''' - screen = fsm.memory[0] - fsm.memory = [screen] +'''This implements an ANSI (VT100) terminal emulator as a subclass of screen. + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +# references: +# http://en.wikipedia.org/wiki/ANSI_escape_code +# http://www.retards.org/terminals/vt102.html +# http://vt100.net/docs/vt102-ug/contents.html +# http://vt100.net/docs/vt220-rm/ +# http://www.termsys.demon.co.uk/vtansi.htm + +from . import screen +from . import FSM +import string + +# +# The 'Do.*' functions are helper functions for the ANSI class. +# +def DoEmit (fsm): + + screen = fsm.memory[0] + screen.write_ch(fsm.input_symbol) + +def DoStartNumber (fsm): + + fsm.memory.append (fsm.input_symbol) + +def DoBuildNumber (fsm): + + ns = fsm.memory.pop() + ns = ns + fsm.input_symbol + fsm.memory.append (ns) + +def DoBackOne (fsm): + + screen = fsm.memory[0] + screen.cursor_back () + +def DoBack (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_back (count) + +def DoDownOne (fsm): + + screen = fsm.memory[0] + screen.cursor_down () + +def DoDown (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_down (count) + +def DoForwardOne (fsm): + + screen = fsm.memory[0] + screen.cursor_forward () + +def DoForward (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_forward (count) + +def DoUpReverse (fsm): + + screen = fsm.memory[0] + screen.cursor_up_reverse() + +def DoUpOne (fsm): + + screen = fsm.memory[0] + screen.cursor_up () + +def DoUp (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_up (count) + +def DoHome (fsm): + + c = int(fsm.memory.pop()) + r = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_home (r,c) + +def DoHomeOrigin (fsm): + + c = 1 + r = 1 + screen = fsm.memory[0] + screen.cursor_home (r,c) + +def DoEraseDown (fsm): + + screen = fsm.memory[0] + screen.erase_down() + +def DoErase (fsm): + + arg = int(fsm.memory.pop()) + screen = fsm.memory[0] + if arg == 0: + screen.erase_down() + elif arg == 1: + screen.erase_up() + elif arg == 2: + screen.erase_screen() + +def DoEraseEndOfLine (fsm): + + screen = fsm.memory[0] + screen.erase_end_of_line() + +def DoEraseLine (fsm): + + arg = int(fsm.memory.pop()) + screen = fsm.memory[0] + if arg == 0: + screen.erase_end_of_line() + elif arg == 1: + screen.erase_start_of_line() + elif arg == 2: + screen.erase_line() + +def DoEnableScroll (fsm): + + screen = fsm.memory[0] + screen.scroll_screen() + +def DoCursorSave (fsm): + + screen = fsm.memory[0] + screen.cursor_save_attrs() + +def DoCursorRestore (fsm): + + screen = fsm.memory[0] + screen.cursor_restore_attrs() + +def DoScrollRegion (fsm): + + screen = fsm.memory[0] + r2 = int(fsm.memory.pop()) + r1 = int(fsm.memory.pop()) + screen.scroll_screen_rows (r1,r2) + +def DoMode (fsm): + + screen = fsm.memory[0] + mode = fsm.memory.pop() # Should be 4 + # screen.setReplaceMode () + +def DoLog (fsm): + + screen = fsm.memory[0] + fsm.memory = [screen] + fout = open ('log', 'a') + fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') + fout.close() + +class term (screen.screen): + + '''This class is an abstract, generic terminal. + This does nothing. This is a placeholder that + provides a common base class for other terminals + such as an ANSI terminal. ''' + + def __init__ (self, r=24, c=80, *args, **kwargs): + + screen.screen.__init__(self, r,c,*args,**kwargs) + +class ANSI (term): + '''This class implements an ANSI (VT100) terminal. + It is a stream filter that recognizes ANSI terminal + escape sequences and maintains the state of a screen object. ''' + + def __init__ (self, r=24,c=80,*args,**kwargs): + + term.__init__(self,r,c,*args,**kwargs) + + #self.screen = screen (24,80) + self.state = FSM.FSM ('INIT',[self]) + self.state.set_default_transition (DoLog, 'INIT') + self.state.add_transition_any ('INIT', DoEmit, 'INIT') + self.state.add_transition ('\x1b', 'INIT', None, 'ESC') + self.state.add_transition_any ('ESC', DoLog, 'INIT') + self.state.add_transition ('(', 'ESC', None, 'G0SCS') + self.state.add_transition (')', 'ESC', None, 'G1SCS') + self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') + self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') + self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') + self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') + self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. + self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') + self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') + self.state.add_transition ('[', 'ESC', None, 'ELB') + # ELB means Escape Left Bracket. That is ^[[ + self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') + self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') + self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') + self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') + self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') + self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') + self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') + self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') + self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT') + self.state.add_transition ('?', 'ELB', None, 'MODECRAP') + self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1') + self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1') + self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') + self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') + self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') + self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') + self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') + self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') + self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') + ### It gets worse... the 'm' code can have infinite number of + ### number;number;number before it. I've never seen more than two, + ### but the specs say it's allowed. crap! + self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT') + ### LED control. Same implementation problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT') + + # \E[?47h switch to alternate screen + # \E[?47l restores to normal screen from alternate screen. + self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM') + self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM') + self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT') + self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT') + +#RM Reset Mode Esc [ Ps l none + self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') + self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT') + self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2') + self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2') + self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT') + self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') + self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') + self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') + ### It gets worse... the 'm' code can have infinite number of + ### number;number;number before it. I've never seen more than two, + ### but the specs say it's allowed. crap! + self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT') + ### LED control. Same problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT') + self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') + + # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers + self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT') + self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X') + self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X') + self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT') + self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT') + self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT') + self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X') + + def process (self, c): + """Process a single character. Called by :meth:`write`.""" + if isinstance(c, bytes): + c = self._decode(c) + self.state.process(c) + + def process_list (self, l): + + self.write(l) + + def write (self, s): + """Process text, writing it to the virtual screen while handling + ANSI escape codes. + """ + if isinstance(s, bytes): + s = self._decode(s) + for c in s: + self.process(c) + + def flush (self): + pass + + def write_ch (self, ch): + '''This puts a character at the current cursor position. The cursor + position is moved forward with wrap-around, but no scrolling is done if + the cursor hits the lower-right corner of the screen. ''' + + if isinstance(ch, bytes): + ch = self._decode(ch) + + #\r and \n both produce a call to cr() and lf(), respectively. + ch = ch[0] + + if ch == u'\r': + self.cr() + return + if ch == u'\n': + self.crlf() + return + if ch == chr(screen.BS): + self.cursor_back() + return + self.put_abs(self.cur_r, self.cur_c, ch) + old_r = self.cur_r + old_c = self.cur_c + self.cursor_forward() + if old_c == self.cur_c: + self.cursor_down() + if old_r != self.cur_r: + self.cursor_home (self.cur_r, 1) + else: + self.scroll_up () + self.cursor_home (self.cur_r, 1) + self.erase_line() + + def do_sgr (self, fsm): + '''Select Graphic Rendition, e.g. color. ''' + screen = fsm.memory[0] + fsm.memory = [screen] + + def do_decsca (self, fsm): + '''Select character protection attribute. ''' + screen = fsm.memory[0] + fsm.memory = [screen] + + def do_modecrap (self, fsm): + '''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone + wanted to actually use these, they'd need to add more states to the + FSM rather than just improve or override this method. ''' + screen = fsm.memory[0] + fsm.memory = [screen] diff --git a/contrib/python/pexpect/pexpect/FSM.py b/contrib/python/pexpect/pexpect/FSM.py index 46b392ea08a..45fcc1baa96 100644 --- a/contrib/python/pexpect/pexpect/FSM.py +++ b/contrib/python/pexpect/pexpect/FSM.py @@ -1,334 +1,334 @@ -#!/usr/bin/env python - -'''This module implements a Finite State Machine (FSM). In addition to state -this FSM also maintains a user defined "memory". So this FSM can be used as a -Push-down Automata (PDA) since a PDA is a FSM + memory. - -The following describes how the FSM works, but you will probably also need to -see the example function to understand how the FSM is used in practice. - -You define an FSM by building tables of transitions. For a given input symbol -the process() method uses these tables to decide what action to call and what -the next state will be. The FSM has a table of transitions that associate: - - (input_symbol, current_state) --> (action, next_state) - -Where "action" is a function you define. The symbols and states can be any -objects. You use the add_transition() and add_transition_list() methods to add -to the transition table. The FSM also has a table of transitions that -associate: - - (current_state) --> (action, next_state) - -You use the add_transition_any() method to add to this transition table. The -FSM also has one default transition that is not associated with any specific -input_symbol or state. You use the set_default_transition() method to set the -default transition. - -When an action function is called it is passed a reference to the FSM. The -action function may then access attributes of the FSM such as input_symbol, -current_state, or "memory". The "memory" attribute can be any object that you -want to pass along to the action functions. It is not used by the FSM itself. -For parsing you would typically pass a list to be used as a stack. - -The processing sequence is as follows. The process() method is given an -input_symbol to process. The FSM will search the table of transitions that -associate: - - (input_symbol, current_state) --> (action, next_state) - -If the pair (input_symbol, current_state) is found then process() will call the -associated action function and then set the current state to the next_state. - -If the FSM cannot find a match for (input_symbol, current_state) it will then -search the table of transitions that associate: - - (current_state) --> (action, next_state) - -If the current_state is found then the process() method will call the -associated action function and then set the current state to the next_state. -Notice that this table lacks an input_symbol. It lets you define transitions -for a current_state and ANY input_symbol. Hence, it is called the "any" table. -Remember, it is always checked after first searching the table for a specific -(input_symbol, current_state). - -For the case where the FSM did not match either of the previous two cases the -FSM will try to use the default transition. If the default transition is -defined then the process() method will call the associated action function and -then set the current state to the next_state. This lets you define a default -transition as a catch-all case. You can think of it as an exception handler. -There can be only one default transition. - -Finally, if none of the previous cases are defined for an input_symbol and -current_state then the FSM will raise an exception. This may be desirable, but -you can always prevent this just by defining a default transition. - -Noah Spurrier 20020822 - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -class ExceptionFSM(Exception): - - '''This is the FSM Exception class.''' - - def __init__(self, value): - self.value = value - - def __str__(self): - return 'ExceptionFSM: ' + str(self.value) - -class FSM: - - '''This is a Finite State Machine (FSM). - ''' - - def __init__(self, initial_state, memory=None): - - '''This creates the FSM. You set the initial state here. The "memory" - attribute is any object that you want to pass along to the action - functions. It is not used by the FSM. For parsing you would typically - pass a list to be used as a stack. ''' - - # Map (input_symbol, current_state) --> (action, next_state). - self.state_transitions = {} - # Map (current_state) --> (action, next_state). - self.state_transitions_any = {} - self.default_transition = None - - self.input_symbol = None - self.initial_state = initial_state - self.current_state = self.initial_state - self.next_state = None - self.action = None - self.memory = memory - - def reset (self): - - '''This sets the current_state to the initial_state and sets - input_symbol to None. The initial state was set by the constructor - __init__(). ''' - - self.current_state = self.initial_state - self.input_symbol = None - - def add_transition (self, input_symbol, state, action=None, next_state=None): - - '''This adds a transition that associates: - - (input_symbol, current_state) --> (action, next_state) - - The action may be set to None in which case the process() method will - ignore the action and only set the next_state. The next_state may be - set to None in which case the current state will be unchanged. - - You can also set transitions for a list of symbols by using - add_transition_list(). ''' - - if next_state is None: - next_state = state - self.state_transitions[(input_symbol, state)] = (action, next_state) - - def add_transition_list (self, list_input_symbols, state, action=None, next_state=None): - - '''This adds the same transition for a list of input symbols. - You can pass a list or a string. Note that it is handy to use - string.digits, string.whitespace, string.letters, etc. to add - transitions that match character classes. - - The action may be set to None in which case the process() method will - ignore the action and only set the next_state. The next_state may be - set to None in which case the current state will be unchanged. ''' - - if next_state is None: - next_state = state - for input_symbol in list_input_symbols: - self.add_transition (input_symbol, state, action, next_state) - - def add_transition_any (self, state, action=None, next_state=None): - - '''This adds a transition that associates: - - (current_state) --> (action, next_state) - - That is, any input symbol will match the current state. - The process() method checks the "any" state associations after it first - checks for an exact match of (input_symbol, current_state). - - The action may be set to None in which case the process() method will - ignore the action and only set the next_state. The next_state may be - set to None in which case the current state will be unchanged. ''' - - if next_state is None: - next_state = state - self.state_transitions_any [state] = (action, next_state) - - def set_default_transition (self, action, next_state): - - '''This sets the default transition. This defines an action and - next_state if the FSM cannot find the input symbol and the current - state in the transition list and if the FSM cannot find the - current_state in the transition_any list. This is useful as a final - fall-through state for catching errors and undefined states. - - The default transition can be removed by setting the attribute - default_transition to None. ''' - - self.default_transition = (action, next_state) - - def get_transition (self, input_symbol, state): - - '''This returns (action, next state) given an input_symbol and state. - This does not modify the FSM state, so calling this method has no side - effects. Normally you do not call this method directly. It is called by - process(). - - The sequence of steps to check for a defined transition goes from the - most specific to the least specific. - - 1. Check state_transitions[] that match exactly the tuple, - (input_symbol, state) - - 2. Check state_transitions_any[] that match (state) - In other words, match a specific state and ANY input_symbol. - - 3. Check if the default_transition is defined. - This catches any input_symbol and any state. - This is a handler for errors, undefined states, or defaults. - - 4. No transition was defined. If we get here then raise an exception. - ''' - - if (input_symbol, state) in self.state_transitions: - return self.state_transitions[(input_symbol, state)] - elif state in self.state_transitions_any: - return self.state_transitions_any[state] - elif self.default_transition is not None: - return self.default_transition - else: - raise ExceptionFSM ('Transition is undefined: (%s, %s).' % - (str(input_symbol), str(state)) ) - - def process (self, input_symbol): - - '''This is the main method that you call to process input. This may - cause the FSM to change state and call an action. This method calls - get_transition() to find the action and next_state associated with the - input_symbol and current_state. If the action is None then the action - is not called and only the current state is changed. This method - processes one complete input symbol. You can process a list of symbols - (or a string) by calling process_list(). ''' - - self.input_symbol = input_symbol - (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state) - if self.action is not None: - self.action (self) - self.current_state = self.next_state - self.next_state = None - - def process_list (self, input_symbols): - - '''This takes a list and sends each element to process(). The list may - be a string or any iterable object. ''' - - for s in input_symbols: - self.process (s) - -############################################################################## -# The following is an example that demonstrates the use of the FSM class to -# process an RPN expression. Run this module from the command line. You will -# get a prompt > for input. Enter an RPN Expression. Numbers may be integers. -# Operators are * / + - Use the = sign to evaluate and print the expression. -# For example: -# -# 167 3 2 2 * * * 1 - = -# -# will print: -# -# 2003 -############################################################################## - -import sys -import string - -PY3 = (sys.version_info[0] >= 3) - -# -# These define the actions. -# Note that "memory" is a list being used as a stack. -# - -def BeginBuildNumber (fsm): - fsm.memory.append (fsm.input_symbol) - -def BuildNumber (fsm): - s = fsm.memory.pop () - s = s + fsm.input_symbol - fsm.memory.append (s) - -def EndBuildNumber (fsm): - s = fsm.memory.pop () - fsm.memory.append (int(s)) - -def DoOperator (fsm): - ar = fsm.memory.pop() - al = fsm.memory.pop() - if fsm.input_symbol == '+': - fsm.memory.append (al + ar) - elif fsm.input_symbol == '-': - fsm.memory.append (al - ar) - elif fsm.input_symbol == '*': - fsm.memory.append (al * ar) - elif fsm.input_symbol == '/': - fsm.memory.append (al / ar) - -def DoEqual (fsm): - print(str(fsm.memory.pop())) - -def Error (fsm): - print('That does not compute.') - print(str(fsm.input_symbol)) - -def main(): - - '''This is where the example starts and the FSM state transitions are - defined. Note that states are strings (such as 'INIT'). This is not - necessary, but it makes the example easier to read. ''' - - f = FSM ('INIT', []) - f.set_default_transition (Error, 'INIT') - f.add_transition_any ('INIT', None, 'INIT') - f.add_transition ('=', 'INIT', DoEqual, 'INIT') - f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER') - f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER') - f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT') - f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT') - - print() - print('Enter an RPN Expression.') - print('Numbers may be integers. Operators are * / + -') - print('Use the = sign to evaluate and print the expression.') - print('For example: ') - print(' 167 3 2 2 * * * 1 - =') - inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore - f.process_list(inputstr) - - -if __name__ == '__main__': - main() +#!/usr/bin/env python + +'''This module implements a Finite State Machine (FSM). In addition to state +this FSM also maintains a user defined "memory". So this FSM can be used as a +Push-down Automata (PDA) since a PDA is a FSM + memory. + +The following describes how the FSM works, but you will probably also need to +see the example function to understand how the FSM is used in practice. + +You define an FSM by building tables of transitions. For a given input symbol +the process() method uses these tables to decide what action to call and what +the next state will be. The FSM has a table of transitions that associate: + + (input_symbol, current_state) --> (action, next_state) + +Where "action" is a function you define. The symbols and states can be any +objects. You use the add_transition() and add_transition_list() methods to add +to the transition table. The FSM also has a table of transitions that +associate: + + (current_state) --> (action, next_state) + +You use the add_transition_any() method to add to this transition table. The +FSM also has one default transition that is not associated with any specific +input_symbol or state. You use the set_default_transition() method to set the +default transition. + +When an action function is called it is passed a reference to the FSM. The +action function may then access attributes of the FSM such as input_symbol, +current_state, or "memory". The "memory" attribute can be any object that you +want to pass along to the action functions. It is not used by the FSM itself. +For parsing you would typically pass a list to be used as a stack. + +The processing sequence is as follows. The process() method is given an +input_symbol to process. The FSM will search the table of transitions that +associate: + + (input_symbol, current_state) --> (action, next_state) + +If the pair (input_symbol, current_state) is found then process() will call the +associated action function and then set the current state to the next_state. + +If the FSM cannot find a match for (input_symbol, current_state) it will then +search the table of transitions that associate: + + (current_state) --> (action, next_state) + +If the current_state is found then the process() method will call the +associated action function and then set the current state to the next_state. +Notice that this table lacks an input_symbol. It lets you define transitions +for a current_state and ANY input_symbol. Hence, it is called the "any" table. +Remember, it is always checked after first searching the table for a specific +(input_symbol, current_state). + +For the case where the FSM did not match either of the previous two cases the +FSM will try to use the default transition. If the default transition is +defined then the process() method will call the associated action function and +then set the current state to the next_state. This lets you define a default +transition as a catch-all case. You can think of it as an exception handler. +There can be only one default transition. + +Finally, if none of the previous cases are defined for an input_symbol and +current_state then the FSM will raise an exception. This may be desirable, but +you can always prevent this just by defining a default transition. + +Noah Spurrier 20020822 + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +class ExceptionFSM(Exception): + + '''This is the FSM Exception class.''' + + def __init__(self, value): + self.value = value + + def __str__(self): + return 'ExceptionFSM: ' + str(self.value) + +class FSM: + + '''This is a Finite State Machine (FSM). + ''' + + def __init__(self, initial_state, memory=None): + + '''This creates the FSM. You set the initial state here. The "memory" + attribute is any object that you want to pass along to the action + functions. It is not used by the FSM. For parsing you would typically + pass a list to be used as a stack. ''' + + # Map (input_symbol, current_state) --> (action, next_state). + self.state_transitions = {} + # Map (current_state) --> (action, next_state). + self.state_transitions_any = {} + self.default_transition = None + + self.input_symbol = None + self.initial_state = initial_state + self.current_state = self.initial_state + self.next_state = None + self.action = None + self.memory = memory + + def reset (self): + + '''This sets the current_state to the initial_state and sets + input_symbol to None. The initial state was set by the constructor + __init__(). ''' + + self.current_state = self.initial_state + self.input_symbol = None + + def add_transition (self, input_symbol, state, action=None, next_state=None): + + '''This adds a transition that associates: + + (input_symbol, current_state) --> (action, next_state) + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. + + You can also set transitions for a list of symbols by using + add_transition_list(). ''' + + if next_state is None: + next_state = state + self.state_transitions[(input_symbol, state)] = (action, next_state) + + def add_transition_list (self, list_input_symbols, state, action=None, next_state=None): + + '''This adds the same transition for a list of input symbols. + You can pass a list or a string. Note that it is handy to use + string.digits, string.whitespace, string.letters, etc. to add + transitions that match character classes. + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. ''' + + if next_state is None: + next_state = state + for input_symbol in list_input_symbols: + self.add_transition (input_symbol, state, action, next_state) + + def add_transition_any (self, state, action=None, next_state=None): + + '''This adds a transition that associates: + + (current_state) --> (action, next_state) + + That is, any input symbol will match the current state. + The process() method checks the "any" state associations after it first + checks for an exact match of (input_symbol, current_state). + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. ''' + + if next_state is None: + next_state = state + self.state_transitions_any [state] = (action, next_state) + + def set_default_transition (self, action, next_state): + + '''This sets the default transition. This defines an action and + next_state if the FSM cannot find the input symbol and the current + state in the transition list and if the FSM cannot find the + current_state in the transition_any list. This is useful as a final + fall-through state for catching errors and undefined states. + + The default transition can be removed by setting the attribute + default_transition to None. ''' + + self.default_transition = (action, next_state) + + def get_transition (self, input_symbol, state): + + '''This returns (action, next state) given an input_symbol and state. + This does not modify the FSM state, so calling this method has no side + effects. Normally you do not call this method directly. It is called by + process(). + + The sequence of steps to check for a defined transition goes from the + most specific to the least specific. + + 1. Check state_transitions[] that match exactly the tuple, + (input_symbol, state) + + 2. Check state_transitions_any[] that match (state) + In other words, match a specific state and ANY input_symbol. + + 3. Check if the default_transition is defined. + This catches any input_symbol and any state. + This is a handler for errors, undefined states, or defaults. + + 4. No transition was defined. If we get here then raise an exception. + ''' + + if (input_symbol, state) in self.state_transitions: + return self.state_transitions[(input_symbol, state)] + elif state in self.state_transitions_any: + return self.state_transitions_any[state] + elif self.default_transition is not None: + return self.default_transition + else: + raise ExceptionFSM ('Transition is undefined: (%s, %s).' % + (str(input_symbol), str(state)) ) + + def process (self, input_symbol): + + '''This is the main method that you call to process input. This may + cause the FSM to change state and call an action. This method calls + get_transition() to find the action and next_state associated with the + input_symbol and current_state. If the action is None then the action + is not called and only the current state is changed. This method + processes one complete input symbol. You can process a list of symbols + (or a string) by calling process_list(). ''' + + self.input_symbol = input_symbol + (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state) + if self.action is not None: + self.action (self) + self.current_state = self.next_state + self.next_state = None + + def process_list (self, input_symbols): + + '''This takes a list and sends each element to process(). The list may + be a string or any iterable object. ''' + + for s in input_symbols: + self.process (s) + +############################################################################## +# The following is an example that demonstrates the use of the FSM class to +# process an RPN expression. Run this module from the command line. You will +# get a prompt > for input. Enter an RPN Expression. Numbers may be integers. +# Operators are * / + - Use the = sign to evaluate and print the expression. +# For example: +# +# 167 3 2 2 * * * 1 - = +# +# will print: +# +# 2003 +############################################################################## + +import sys +import string + +PY3 = (sys.version_info[0] >= 3) + +# +# These define the actions. +# Note that "memory" is a list being used as a stack. +# + +def BeginBuildNumber (fsm): + fsm.memory.append (fsm.input_symbol) + +def BuildNumber (fsm): + s = fsm.memory.pop () + s = s + fsm.input_symbol + fsm.memory.append (s) + +def EndBuildNumber (fsm): + s = fsm.memory.pop () + fsm.memory.append (int(s)) + +def DoOperator (fsm): + ar = fsm.memory.pop() + al = fsm.memory.pop() + if fsm.input_symbol == '+': + fsm.memory.append (al + ar) + elif fsm.input_symbol == '-': + fsm.memory.append (al - ar) + elif fsm.input_symbol == '*': + fsm.memory.append (al * ar) + elif fsm.input_symbol == '/': + fsm.memory.append (al / ar) + +def DoEqual (fsm): + print(str(fsm.memory.pop())) + +def Error (fsm): + print('That does not compute.') + print(str(fsm.input_symbol)) + +def main(): + + '''This is where the example starts and the FSM state transitions are + defined. Note that states are strings (such as 'INIT'). This is not + necessary, but it makes the example easier to read. ''' + + f = FSM ('INIT', []) + f.set_default_transition (Error, 'INIT') + f.add_transition_any ('INIT', None, 'INIT') + f.add_transition ('=', 'INIT', DoEqual, 'INIT') + f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER') + f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER') + f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT') + f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT') + + print() + print('Enter an RPN Expression.') + print('Numbers may be integers. Operators are * / + -') + print('Use the = sign to evaluate and print the expression.') + print('For example: ') + print(' 167 3 2 2 * * * 1 - =') + inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore + f.process_list(inputstr) + + +if __name__ == '__main__': + main() diff --git a/contrib/python/pexpect/pexpect/__init__.py b/contrib/python/pexpect/pexpect/__init__.py index 7e30453787c..0e5f215e7c3 100644 --- a/contrib/python/pexpect/pexpect/__init__.py +++ b/contrib/python/pexpect/pexpect/__init__.py @@ -1,85 +1,85 @@ -'''Pexpect is a Python module for spawning child applications and controlling -them automatically. Pexpect can be used for automating interactive applications -such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup -scripts for duplicating software package installations on different servers. It -can be used for automated software testing. Pexpect is in the spirit of Don -Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python -require TCL and Expect or require C extensions to be compiled. Pexpect does not -use C, Expect, or TCL extensions. It should work on any platform that supports -the standard Python pty module. The Pexpect interface focuses on ease of use so -that simple tasks are easy. - -There are two main interfaces to the Pexpect system; these are the function, -run() and the class, spawn. The spawn class is more powerful. The run() -function is simpler than spawn, and is good for quickly calling program. When -you call the run() function it executes a given program and then returns the -output. This is a handy replacement for os.system(). - -For example:: - - pexpect.run('ls -la') - -The spawn class is the more powerful interface to the Pexpect system. You can -use this to spawn a child program then interact with it by sending input and -expecting responses (waiting for patterns in the child's output). - -For example:: - - child = pexpect.spawn('scp foo [email protected]:.') - child.expect('Password:') - child.sendline(mypassword) - -This works even for commands that ask for passwords or other input outside of -the normal stdio streams. For example, ssh reads input directly from the TTY -device which bypasses stdin. - -Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, -Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids -vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, -Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, -Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume -Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John -Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone. - -Pexpect is free, open source, and all that good stuff. -http://pexpect.sourceforge.net/ - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -import sys -PY3 = (sys.version_info[0] >= 3) - -from .exceptions import ExceptionPexpect, EOF, TIMEOUT -from .utils import split_command_line, which, is_executable_file -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 - +'''Pexpect is a Python module for spawning child applications and controlling +them automatically. Pexpect can be used for automating interactive applications +such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup +scripts for duplicating software package installations on different servers. It +can be used for automated software testing. Pexpect is in the spirit of Don +Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python +require TCL and Expect or require C extensions to be compiled. Pexpect does not +use C, Expect, or TCL extensions. It should work on any platform that supports +the standard Python pty module. The Pexpect interface focuses on ease of use so +that simple tasks are easy. + +There are two main interfaces to the Pexpect system; these are the function, +run() and the class, spawn. The spawn class is more powerful. The run() +function is simpler than spawn, and is good for quickly calling program. When +you call the run() function it executes a given program and then returns the +output. This is a handy replacement for os.system(). + +For example:: + + pexpect.run('ls -la') + +The spawn class is the more powerful interface to the Pexpect system. You can +use this to spawn a child program then interact with it by sending input and +expecting responses (waiting for patterns in the child's output). + +For example:: + + child = pexpect.spawn('scp foo [email protected]:.') + child.expect('Password:') + child.sendline(mypassword) + +This works even for commands that ask for passwords or other input outside of +the normal stdio streams. For example, ssh reads input directly from the TTY +device which bypasses stdin. + +Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, +Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids +vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, +Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, +Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume +Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John +Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone. + +Pexpect is free, open source, and all that good stuff. +http://pexpect.sourceforge.net/ + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +import sys +PY3 = (sys.version_info[0] >= 3) + +from .exceptions import ExceptionPexpect, EOF, TIMEOUT +from .utils import split_command_line, which, is_executable_file +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.8.0' -__revision__ = '' -__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', - 'which', 'split_command_line', '__version__', '__revision__'] - - - -# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : +__revision__ = '' +__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', + 'which', 'split_command_line', '__version__', '__revision__'] + + + +# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : diff --git a/contrib/python/pexpect/pexpect/_async.py b/contrib/python/pexpect/pexpect/_async.py index dfbfeef5fb9..4260ad6107b 100644 --- a/contrib/python/pexpect/pexpect/_async.py +++ b/contrib/python/pexpect/pexpect/_async.py @@ -1,16 +1,16 @@ -import asyncio -import errno +import asyncio +import errno import signal - -from pexpect import EOF - -def expect_async(expecter, timeout=None): - # First process data that was previously read - if it maches, we don't need - # async stuff. + +from pexpect import EOF + +def expect_async(expecter, timeout=None): + # First process data that was previously read - if it maches, we don't need + # async stuff. idx = expecter.existing_data() - if idx is not None: - return idx + if idx is not None: + return idx if not expecter.spawn.async_pw_transport: pw = PatternWaiter() pw.set_expecter(expecter) @@ -21,12 +21,12 @@ def expect_async(expecter, timeout=None): pw, transport = expecter.spawn.async_pw_transport pw.set_expecter(expecter) transport.resume_reading() - try: - return (yield from asyncio.wait_for(pw.fut, timeout)) - except asyncio.TimeoutError as e: - transport.pause_reading() - return expecter.timeout(e) - + try: + return (yield from asyncio.wait_for(pw.fut, timeout)) + except asyncio.TimeoutError as e: + transport.pause_reading() + return expecter.timeout(e) + @asyncio.coroutine def repl_run_command_async(repl, cmdlines, timeout=-1): res = [] @@ -45,59 +45,59 @@ def repl_run_command_async(repl, cmdlines, timeout=-1): raise ValueError("Continuation prompt found - input was incomplete:") return u''.join(res + [repl.child.before]) -class PatternWaiter(asyncio.Protocol): +class PatternWaiter(asyncio.Protocol): transport = None def set_expecter(self, expecter): - self.expecter = expecter - self.fut = asyncio.Future() + self.expecter = expecter + self.fut = asyncio.Future() - def found(self, result): - if not self.fut.done(): - self.fut.set_result(result) + def found(self, result): + if not self.fut.done(): + self.fut.set_result(result) self.transport.pause_reading() - def error(self, exc): - if not self.fut.done(): - self.fut.set_exception(exc) + def error(self, exc): + if not self.fut.done(): + self.fut.set_exception(exc) self.transport.pause_reading() def connection_made(self, transport): self.transport = transport - def data_received(self, data): - spawn = self.expecter.spawn - s = spawn._decoder.decode(data) - spawn._log(s, 'read') - - if self.fut.done(): + def data_received(self, data): + spawn = self.expecter.spawn + s = spawn._decoder.decode(data) + spawn._log(s, 'read') + + if self.fut.done(): spawn._before.write(s) spawn._buffer.write(s) - return - - try: + return + + try: index = self.expecter.new_data(s) - if index is not None: - # Found a match - self.found(index) - except Exception as e: - self.expecter.errored() - self.error(e) + if index is not None: + # Found a match + self.found(index) + except Exception as e: + self.expecter.errored() + self.error(e) - def eof_received(self): - # N.B. If this gets called, async will close the pipe (the spawn object) - # for us - try: - self.expecter.spawn.flag_eof = True - index = self.expecter.eof() - except EOF as e: - self.error(e) - else: - self.found(index) + def eof_received(self): + # N.B. If this gets called, async will close the pipe (the spawn object) + # for us + try: + self.expecter.spawn.flag_eof = True + index = self.expecter.eof() + except EOF as e: + self.error(e) + else: + self.found(index) - def connection_lost(self, exc): - if isinstance(exc, OSError) and exc.errno == errno.EIO: - # We may get here without eof_received being called, e.g on Linux - self.eof_received() - elif exc is not None: - self.error(exc) + def connection_lost(self, exc): + if isinstance(exc, OSError) and exc.errno == errno.EIO: + # We may get here without eof_received being called, e.g on Linux + self.eof_received() + elif exc is not None: + self.error(exc) diff --git a/contrib/python/pexpect/pexpect/exceptions.py b/contrib/python/pexpect/pexpect/exceptions.py index cb360f02614..f1c10df2e1f 100644 --- a/contrib/python/pexpect/pexpect/exceptions.py +++ b/contrib/python/pexpect/pexpect/exceptions.py @@ -1,35 +1,35 @@ -"""Exception classes used by Pexpect""" - -import traceback -import sys - -class ExceptionPexpect(Exception): - '''Base class for all exceptions raised by this module. - ''' - - def __init__(self, value): - super(ExceptionPexpect, self).__init__(value) - self.value = value - - def __str__(self): - return str(self.value) - - def get_trace(self): - '''This returns an abbreviated stack trace with lines that only concern - the caller. In other words, the stack trace inside the Pexpect module - is not included. ''' - - tblist = traceback.extract_tb(sys.exc_info()[2]) - tblist = [item for item in tblist if ('pexpect/__init__' not in item[0]) - and ('pexpect/expect' not in item[0])] - tblist = traceback.format_list(tblist) - return ''.join(tblist) - - -class EOF(ExceptionPexpect): - '''Raised when EOF is read from a child. - This usually means the child has exited.''' - - -class TIMEOUT(ExceptionPexpect): - '''Raised when a read time exceeds the timeout. ''' +"""Exception classes used by Pexpect""" + +import traceback +import sys + +class ExceptionPexpect(Exception): + '''Base class for all exceptions raised by this module. + ''' + + def __init__(self, value): + super(ExceptionPexpect, self).__init__(value) + self.value = value + + def __str__(self): + return str(self.value) + + def get_trace(self): + '''This returns an abbreviated stack trace with lines that only concern + the caller. In other words, the stack trace inside the Pexpect module + is not included. ''' + + tblist = traceback.extract_tb(sys.exc_info()[2]) + tblist = [item for item in tblist if ('pexpect/__init__' not in item[0]) + and ('pexpect/expect' not in item[0])] + tblist = traceback.format_list(tblist) + return ''.join(tblist) + + +class EOF(ExceptionPexpect): + '''Raised when EOF is read from a child. + This usually means the child has exited.''' + + +class TIMEOUT(ExceptionPexpect): + '''Raised when a read time exceeds the timeout. ''' diff --git a/contrib/python/pexpect/pexpect/expect.py b/contrib/python/pexpect/pexpect/expect.py index d3409db9d73..00ad4f07483 100644 --- a/contrib/python/pexpect/pexpect/expect.py +++ b/contrib/python/pexpect/pexpect/expect.py @@ -1,27 +1,27 @@ -import time - -from .exceptions import EOF, TIMEOUT - -class Expecter(object): - def __init__(self, spawn, searcher, searchwindowsize=-1): - self.spawn = spawn - self.searcher = searcher +import time + +from .exceptions import EOF, TIMEOUT + +class Expecter(object): + def __init__(self, spawn, searcher, searchwindowsize=-1): + self.spawn = spawn + self.searcher = searcher # A value of -1 means to use the figure from spawn, which should # be None or a positive number. - if searchwindowsize == -1: - searchwindowsize = spawn.searchwindowsize - self.searchwindowsize = searchwindowsize + if searchwindowsize == -1: + searchwindowsize = spawn.searchwindowsize + self.searchwindowsize = searchwindowsize self.lookback = None if hasattr(searcher, 'longest_string'): self.lookback = searcher.longest_string def do_search(self, window, freshlen): - spawn = self.spawn - searcher = self.searcher + spawn = self.spawn + searcher = self.searcher if freshlen > len(window): freshlen = len(window) index = searcher.search(window, freshlen, self.searchwindowsize) - if index >= 0: + if index >= 0: spawn._buffer = spawn.buffer_type() spawn._buffer.write(window[searcher.end:]) spawn.before = spawn._before.getvalue()[ @@ -29,10 +29,10 @@ class Expecter(object): spawn._before = spawn.buffer_type() spawn._before.write(window[searcher.end:]) spawn.after = window[searcher.start:searcher.end] - spawn.match = searcher.match - spawn.match_index = index - # Found a match - return index + spawn.match = searcher.match + spawn.match_index = index + # Found a match + return index elif self.searchwindowsize or self.lookback: maintain = self.searchwindowsize or self.lookback if spawn._buffer.tell() > maintain: @@ -97,275 +97,275 @@ class Expecter(object): window = spawn._buffer.read() return self.do_search(window, freshlen) - def eof(self, err=None): - spawn = self.spawn - + def eof(self, err=None): + spawn = self.spawn + spawn.before = spawn._before.getvalue() spawn._buffer = spawn.buffer_type() spawn._before = spawn.buffer_type() - spawn.after = EOF - index = self.searcher.eof_index - if index >= 0: - spawn.match = EOF - spawn.match_index = index - return index - else: - spawn.match = None - spawn.match_index = None - msg = str(spawn) + spawn.after = EOF + index = self.searcher.eof_index + if index >= 0: + spawn.match = EOF + spawn.match_index = index + return index + else: + spawn.match = None + spawn.match_index = None + msg = str(spawn) msg += '\nsearcher: %s' % self.searcher - if err is not None: - msg = str(err) + '\n' + msg + if err is not None: + msg = str(err) + '\n' + msg exc = EOF(msg) exc.__cause__ = None # in Python 3.x we can use "raise exc from None" raise exc - def timeout(self, err=None): - spawn = self.spawn - + def timeout(self, err=None): + spawn = self.spawn + spawn.before = spawn._before.getvalue() - spawn.after = TIMEOUT - index = self.searcher.timeout_index - if index >= 0: - spawn.match = TIMEOUT - spawn.match_index = index - return index - else: - spawn.match = None - spawn.match_index = None - msg = str(spawn) + spawn.after = TIMEOUT + index = self.searcher.timeout_index + if index >= 0: + spawn.match = TIMEOUT + spawn.match_index = index + return index + else: + spawn.match = None + spawn.match_index = None + msg = str(spawn) msg += '\nsearcher: %s' % self.searcher - if err is not None: - msg = str(err) + '\n' + msg - + if err is not None: + msg = str(err) + '\n' + msg + exc = TIMEOUT(msg) exc.__cause__ = None # in Python 3.x we can use "raise exc from None" raise exc - def errored(self): - spawn = self.spawn + def errored(self): + spawn = self.spawn spawn.before = spawn._before.getvalue() - spawn.after = None - spawn.match = None - spawn.match_index = None - - def expect_loop(self, timeout=-1): - """Blocking expect""" - spawn = self.spawn + spawn.after = None + spawn.match = None + spawn.match_index = None - if timeout is not None: - end_time = time.time() + timeout - - try: + def expect_loop(self, timeout=-1): + """Blocking expect""" + spawn = self.spawn + + if timeout is not None: + end_time = time.time() + timeout + + try: idx = self.existing_data() if idx is not None: return idx - while True: - # No match at this point - if (timeout is not None) and (timeout < 0): - return self.timeout() - # Still have time left, so read more data - incoming = spawn.read_nonblocking(spawn.maxread, timeout) + while True: + # No match at this point + if (timeout is not None) and (timeout < 0): + return self.timeout() + # Still have time left, so read more data + incoming = spawn.read_nonblocking(spawn.maxread, timeout) if self.spawn.delayafterread is not None: time.sleep(self.spawn.delayafterread) idx = self.new_data(incoming) # Keep reading until exception or return. if idx is not None: return idx - if timeout is not None: - timeout = end_time - time.time() - except EOF as e: - return self.eof(e) - except TIMEOUT as e: - return self.timeout(e) - except: - self.errored() - raise - - -class searcher_string(object): - '''This is a plain string search helper for the spawn.expect_any() method. - This helper class is for speed. For more powerful regex patterns - see the helper class, searcher_re. - - Attributes: - - eof_index - index of EOF, or -1 - timeout_index - index of TIMEOUT, or -1 - - After a successful match by the search() method the following attributes - are available: - - start - index into the buffer, first byte of match - end - index into the buffer, first byte after match - match - the matching string itself - - ''' - - def __init__(self, strings): - '''This creates an instance of searcher_string. This argument 'strings' - may be a list; a sequence of strings; or the EOF or TIMEOUT types. ''' - - self.eof_index = -1 - self.timeout_index = -1 - self._strings = [] + if timeout is not None: + timeout = end_time - time.time() + except EOF as e: + return self.eof(e) + except TIMEOUT as e: + return self.timeout(e) + except: + self.errored() + raise + + +class searcher_string(object): + '''This is a plain string search helper for the spawn.expect_any() method. + This helper class is for speed. For more powerful regex patterns + see the helper class, searcher_re. + + Attributes: + + eof_index - index of EOF, or -1 + timeout_index - index of TIMEOUT, or -1 + + After a successful match by the search() method the following attributes + are available: + + start - index into the buffer, first byte of match + end - index into the buffer, first byte after match + match - the matching string itself + + ''' + + def __init__(self, strings): + '''This creates an instance of searcher_string. This argument 'strings' + may be a list; a sequence of strings; or the EOF or TIMEOUT types. ''' + + self.eof_index = -1 + self.timeout_index = -1 + self._strings = [] self.longest_string = 0 - for n, s in enumerate(strings): - if s is EOF: - self.eof_index = n - continue - if s is TIMEOUT: - self.timeout_index = n - continue - self._strings.append((n, s)) + for n, s in enumerate(strings): + if s is EOF: + self.eof_index = n + continue + if s is TIMEOUT: + self.timeout_index = n + continue + self._strings.append((n, s)) if len(s) > self.longest_string: self.longest_string = len(s) - - def __str__(self): - '''This returns a human-readable string that represents the state of - the object.''' - + + def __str__(self): + '''This returns a human-readable string that represents the state of + the object.''' + ss = [(ns[0], ' %d: %r' % ns) for ns in self._strings] - ss.append((-1, 'searcher_string:')) - if self.eof_index >= 0: - ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) - if self.timeout_index >= 0: - ss.append((self.timeout_index, - ' %d: TIMEOUT' % self.timeout_index)) - ss.sort() - ss = list(zip(*ss))[1] - return '\n'.join(ss) - - def search(self, buffer, freshlen, searchwindowsize=None): + ss.append((-1, 'searcher_string:')) + if self.eof_index >= 0: + ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) + if self.timeout_index >= 0: + ss.append((self.timeout_index, + ' %d: TIMEOUT' % self.timeout_index)) + ss.sort() + ss = list(zip(*ss))[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): '''This searches 'buffer' for the first occurrence of one of the search - strings. 'freshlen' must indicate the number of bytes at the end of - 'buffer' which have not been searched before. It helps to avoid - searching the same, possibly big, buffer over and over again. - - See class spawn for the 'searchwindowsize' argument. - - If there is a match this returns the index of that string, and sets - 'start', 'end' and 'match'. Otherwise, this returns -1. ''' - - first_match = None - - # 'freshlen' helps a lot here. Further optimizations could - # possibly include: - # - # using something like the Boyer-Moore Fast String Searching - # Algorithm; pre-compiling the search through a list of - # strings into something that can scan the input once to - # search for all N strings; realize that if we search for - # ['bar', 'baz'] and the input is '...foo' we need not bother - # rescanning until we've read three more bytes. - # - # Sadly, I don't know enough about this interesting topic. /grahn - - for index, s in self._strings: - if searchwindowsize is None: - # the match, if any, can only be in the fresh data, - # or at the very end of the old data - offset = -(freshlen + len(s)) - else: - # better obey searchwindowsize - offset = -searchwindowsize - n = buffer.find(s, offset) - if n >= 0 and (first_match is None or n < first_match): - first_match = n - best_index, best_match = index, s - if first_match is None: - return -1 - self.match = best_match - self.start = first_match - self.end = self.start + len(self.match) - return best_index - - -class searcher_re(object): - '''This is regular expression string search helper for the - spawn.expect_any() method. This helper class is for powerful - pattern matching. For speed, see the helper class, searcher_string. - - Attributes: - - eof_index - index of EOF, or -1 - timeout_index - index of TIMEOUT, or -1 - - After a successful match by the search() method the following attributes - are available: - - start - index into the buffer, first byte of match - end - index into the buffer, first byte after match + strings. 'freshlen' must indicate the number of bytes at the end of + 'buffer' which have not been searched before. It helps to avoid + searching the same, possibly big, buffer over and over again. + + See class spawn for the 'searchwindowsize' argument. + + If there is a match this returns the index of that string, and sets + 'start', 'end' and 'match'. Otherwise, this returns -1. ''' + + first_match = None + + # 'freshlen' helps a lot here. Further optimizations could + # possibly include: + # + # using something like the Boyer-Moore Fast String Searching + # Algorithm; pre-compiling the search through a list of + # strings into something that can scan the input once to + # search for all N strings; realize that if we search for + # ['bar', 'baz'] and the input is '...foo' we need not bother + # rescanning until we've read three more bytes. + # + # Sadly, I don't know enough about this interesting topic. /grahn + + for index, s in self._strings: + if searchwindowsize is None: + # the match, if any, can only be in the fresh data, + # or at the very end of the old data + offset = -(freshlen + len(s)) + else: + # better obey searchwindowsize + offset = -searchwindowsize + n = buffer.find(s, offset) + if n >= 0 and (first_match is None or n < first_match): + first_match = n + best_index, best_match = index, s + if first_match is None: + return -1 + self.match = best_match + self.start = first_match + self.end = self.start + len(self.match) + return best_index + + +class searcher_re(object): + '''This is regular expression string search helper for the + spawn.expect_any() method. This helper class is for powerful + pattern matching. For speed, see the helper class, searcher_string. + + Attributes: + + eof_index - index of EOF, or -1 + timeout_index - index of TIMEOUT, or -1 + + After a successful match by the search() method the following attributes + are available: + + start - index into the buffer, first byte of match + end - index into the buffer, first byte after match match - the re.match object returned by a successful re.search - - ''' - - def __init__(self, patterns): - '''This creates an instance that searches for 'patterns' Where - 'patterns' may be a list or other sequence of compiled regular - expressions, or the EOF or TIMEOUT types.''' - - self.eof_index = -1 - self.timeout_index = -1 - self._searches = [] + + ''' + + def __init__(self, patterns): + '''This creates an instance that searches for 'patterns' Where + 'patterns' may be a list or other sequence of compiled regular + expressions, or the EOF or TIMEOUT types.''' + + self.eof_index = -1 + self.timeout_index = -1 + self._searches = [] for n, s in enumerate(patterns): - if s is EOF: - self.eof_index = n - continue - if s is TIMEOUT: - self.timeout_index = n - continue - self._searches.append((n, s)) - - def __str__(self): - '''This returns a human-readable string that represents the state of - the object.''' - - #ss = [(n, ' %d: re.compile("%s")' % - # (n, repr(s.pattern))) for n, s in self._searches] - ss = list() - for n, s in self._searches: + if s is EOF: + self.eof_index = n + continue + if s is TIMEOUT: + self.timeout_index = n + continue + self._searches.append((n, s)) + + def __str__(self): + '''This returns a human-readable string that represents the state of + the object.''' + + #ss = [(n, ' %d: re.compile("%s")' % + # (n, repr(s.pattern))) for n, s in self._searches] + ss = list() + for n, s in self._searches: ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern))) - ss.append((-1, 'searcher_re:')) - if self.eof_index >= 0: - ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) - if self.timeout_index >= 0: - ss.append((self.timeout_index, ' %d: TIMEOUT' % - self.timeout_index)) - ss.sort() - ss = list(zip(*ss))[1] - return '\n'.join(ss) - - def search(self, buffer, freshlen, searchwindowsize=None): + ss.append((-1, 'searcher_re:')) + if self.eof_index >= 0: + ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) + if self.timeout_index >= 0: + ss.append((self.timeout_index, ' %d: TIMEOUT' % + self.timeout_index)) + ss.sort() + ss = list(zip(*ss))[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): '''This searches 'buffer' for the first occurrence of one of the regular - expressions. 'freshlen' must indicate the number of bytes at the end of - 'buffer' which have not been searched before. - - See class spawn for the 'searchwindowsize' argument. - - If there is a match this returns the index of that string, and sets - 'start', 'end' and 'match'. Otherwise, returns -1.''' - - first_match = None - # 'freshlen' doesn't help here -- we cannot predict the - # length of a match, and the re module provides no help. - if searchwindowsize is None: - searchstart = 0 - else: - searchstart = max(0, len(buffer) - searchwindowsize) - for index, s in self._searches: - match = s.search(buffer, searchstart) - if match is None: - continue - n = match.start() - if first_match is None or n < first_match: - first_match = n - the_match = match - best_index = index - if first_match is None: - return -1 - self.start = first_match - self.match = the_match - self.end = self.match.end() + expressions. 'freshlen' must indicate the number of bytes at the end of + 'buffer' which have not been searched before. + + See class spawn for the 'searchwindowsize' argument. + + If there is a match this returns the index of that string, and sets + 'start', 'end' and 'match'. Otherwise, returns -1.''' + + first_match = None + # 'freshlen' doesn't help here -- we cannot predict the + # length of a match, and the re module provides no help. + if searchwindowsize is None: + searchstart = 0 + else: + searchstart = max(0, len(buffer) - searchwindowsize) + for index, s in self._searches: + match = s.search(buffer, searchstart) + if match is None: + continue + n = match.start() + if first_match is None or n < first_match: + first_match = n + the_match = match + best_index = index + if first_match is None: + return -1 + self.start = first_match + self.match = the_match + self.end = self.match.end() return best_index diff --git a/contrib/python/pexpect/pexpect/fdpexpect.py b/contrib/python/pexpect/pexpect/fdpexpect.py index cddd50e1005..a3096537f66 100644 --- a/contrib/python/pexpect/pexpect/fdpexpect.py +++ b/contrib/python/pexpect/pexpect/fdpexpect.py @@ -1,119 +1,119 @@ -'''This is like pexpect, but it will work with any file descriptor that you +'''This is like pexpect, but it will work with any file descriptor that you pass it. You are responsible for opening and close the file descriptor. -This allows you to use Pexpect with sockets and named pipes (FIFOs). - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -from .spawnbase import SpawnBase +This allows you to use Pexpect with sockets and named pipes (FIFOs). + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +from .spawnbase import SpawnBase from .exceptions import ExceptionPexpect, TIMEOUT from .utils import select_ignore_interrupts, poll_ignore_interrupts -import os - -__all__ = ['fdspawn'] - -class fdspawn(SpawnBase): - '''This is like pexpect.spawn but allows you to supply your own open file - descriptor. For example, you could use it to read through a file looking - for patterns, or to control a modem or serial device. ''' - - def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None, +import os + +__all__ = ['fdspawn'] + +class fdspawn(SpawnBase): + '''This is like pexpect.spawn but allows you to supply your own open file + descriptor. For example, you could use it to read through a file looking + for patterns, or to control a modem or serial device. ''' + + def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, encoding=None, codec_errors='strict', use_poll=False): - '''This takes a file descriptor (an int) or an object that support the - fileno() method (returning an int). All Python file-like objects - support fileno(). ''' - - if type(fd) != type(0) and hasattr(fd, 'fileno'): - fd = fd.fileno() - - if type(fd) != type(0): - raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.') - - try: # make sure fd is a valid file descriptor - os.fstat(fd) - except OSError: - raise ExceptionPexpect('The fd argument is not a valid file descriptor.') - - self.args = None - self.command = None - SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile, - encoding=encoding, codec_errors=codec_errors) - self.child_fd = fd - self.own_fd = False - self.closed = False - self.name = '<file descriptor %d>' % fd + '''This takes a file descriptor (an int) or an object that support the + fileno() method (returning an int). All Python file-like objects + support fileno(). ''' + + if type(fd) != type(0) and hasattr(fd, 'fileno'): + fd = fd.fileno() + + if type(fd) != type(0): + raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.') + + try: # make sure fd is a valid file descriptor + os.fstat(fd) + except OSError: + raise ExceptionPexpect('The fd argument is not a valid file descriptor.') + + self.args = None + self.command = None + SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile, + encoding=encoding, codec_errors=codec_errors) + self.child_fd = fd + self.own_fd = False + self.closed = False + self.name = '<file descriptor %d>' % fd self.use_poll = use_poll + + def close (self): + """Close the file descriptor. + + Calling this method a second time does nothing, but if the file + descriptor was closed elsewhere, :class:`OSError` will be raised. + """ + if self.child_fd == -1: + return + + self.flush() + os.close(self.child_fd) + self.child_fd = -1 + self.closed = True + + def isalive (self): + '''This checks if the file descriptor is still valid. If :func:`os.fstat` + does not raise an exception then we assume it is alive. ''' + + if self.child_fd == -1: + return False + try: + os.fstat(self.child_fd) + return True + except: + 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.') - def close (self): - """Close the file descriptor. - - Calling this method a second time does nothing, but if the file - descriptor was closed elsewhere, :class:`OSError` will be raised. - """ - if self.child_fd == -1: - return - - self.flush() - os.close(self.child_fd) - self.child_fd = -1 - self.closed = True - - def isalive (self): - '''This checks if the file descriptor is still valid. If :func:`os.fstat` - does not raise an exception then we assume it is alive. ''' - - if self.child_fd == -1: - return False - try: - os.fstat(self.child_fd) - return True - except: - 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 - # directly. - def send(self, s): - "Write to fd, return number of bytes written" - s = self._coerce_send_string(s) - self._log(s, 'send') + # These four methods are left around for backwards compatibility, but not + # 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" + s = self._coerce_send_string(s) + self._log(s, 'send') - b = self._encoder.encode(s, final=False) - return os.write(self.child_fd, b) + b = self._encoder.encode(s, final=False) + return os.write(self.child_fd, b) - def sendline(self, s): - "Write to fd with trailing newline, return number of bytes written" - s = self._coerce_send_string(s) - return self.send(s + self.linesep) + def sendline(self, s): + "Write to fd with trailing newline, return number of bytes written" + s = self._coerce_send_string(s) + return self.send(s + self.linesep) - def write(self, s): - "Write to fd, return None" - self.send(s) + def write(self, s): + "Write to fd, return None" + self.send(s) - def writelines(self, sequence): - "Call self.write() for each item in sequence" - for s in sequence: - self.write(s) + def writelines(self, sequence): + "Call self.write() for each item in sequence" + for s in sequence: + self.write(s) def read_nonblocking(self, size=1, timeout=-1): """ diff --git a/contrib/python/pexpect/pexpect/popen_spawn.py b/contrib/python/pexpect/pexpect/popen_spawn.py index 4bb58cfe76c..59cd85738a2 100644 --- a/contrib/python/pexpect/pexpect/popen_spawn.py +++ b/contrib/python/pexpect/pexpect/popen_spawn.py @@ -1,30 +1,30 @@ -"""Provides an interface like pexpect.spawn interface using subprocess.Popen -""" -import os -import threading -import subprocess -import sys -import time -import signal -import shlex - -try: - from queue import Queue, Empty # Python 3 -except ImportError: - from Queue import Queue, Empty # Python 2 - -from .spawnbase import SpawnBase, PY3 -from .exceptions import EOF +"""Provides an interface like pexpect.spawn interface using subprocess.Popen +""" +import os +import threading +import subprocess +import sys +import time +import signal +import shlex + +try: + from queue import Queue, Empty # Python 3 +except ImportError: + from Queue import Queue, Empty # Python 2 + +from .spawnbase import SpawnBase, PY3 +from .exceptions import EOF from .utils import string_types - -class PopenSpawn(SpawnBase): - def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None, + +class PopenSpawn(SpawnBase): + def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, encoding=None, codec_errors='strict', preexec_fn=None): - super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread, - searchwindowsize=searchwindowsize, logfile=logfile, - encoding=encoding, codec_errors=codec_errors) - + super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread, + searchwindowsize=searchwindowsize, logfile=logfile, + encoding=encoding, codec_errors=codec_errors) + # Note that `SpawnBase` initializes `self.crlf` to `\r\n` # because the default behaviour for a PTY is to convert # incoming LF to `\r\n` (see the `onlcr` flag and @@ -37,152 +37,152 @@ class PopenSpawn(SpawnBase): else: self.crlf = self.string_type (os.linesep) - kwargs = dict(bufsize=0, stdin=subprocess.PIPE, - stderr=subprocess.STDOUT, stdout=subprocess.PIPE, + kwargs = dict(bufsize=0, stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, stdout=subprocess.PIPE, cwd=cwd, preexec_fn=preexec_fn, env=env) - - if sys.platform == 'win32': - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - kwargs['startupinfo'] = startupinfo - kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP - + + if sys.platform == 'win32': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + kwargs['startupinfo'] = startupinfo + kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP + if isinstance(cmd, string_types) and sys.platform != 'win32': cmd = shlex.split(cmd, posix=os.name == 'posix') - - self.proc = subprocess.Popen(cmd, **kwargs) + + self.proc = subprocess.Popen(cmd, **kwargs) self.pid = self.proc.pid - self.closed = False - self._buf = self.string_type() - - self._read_queue = Queue() - self._read_thread = threading.Thread(target=self._read_incoming) - self._read_thread.setDaemon(True) - self._read_thread.start() - - _read_reached_eof = False - - def read_nonblocking(self, size, timeout): - buf = self._buf - if self._read_reached_eof: - # We have already finished reading. Use up any buffered data, - # then raise EOF - if buf: - self._buf = buf[size:] - return buf[:size] - else: - self.flag_eof = True - raise EOF('End Of File (EOF).') - - if timeout == -1: - timeout = self.timeout - elif timeout is None: - timeout = 1e6 - - t0 = time.time() - while (time.time() - t0) < timeout and size and len(buf) < size: - try: - incoming = self._read_queue.get_nowait() - except Empty: - break - else: - if incoming is None: - self._read_reached_eof = True - break - - buf += self._decoder.decode(incoming, final=False) - - r, self._buf = buf[:size], buf[size:] - - self._log(r, 'read') - return r - - def _read_incoming(self): - """Run in a thread to move output from a pipe to a queue.""" - fileno = self.proc.stdout.fileno() - while 1: - buf = b'' - try: - buf = os.read(fileno, 1024) - except OSError as e: - self._log(e, 'read') - - if not buf: - # This indicates we have reached EOF - self._read_queue.put(None) - return - - self._read_queue.put(buf) - - def write(self, s): - '''This is similar to send() except that there is no return value. - ''' - self.send(s) - - def writelines(self, sequence): - '''This calls write() for each element in the sequence. - - The sequence can be any iterable object producing strings, typically a - list of strings. This does not add line separators. There is no return - value. - ''' - for s in sequence: - 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') - - b = self._encoder.encode(s, final=False) - if PY3: - return self.proc.stdin.write(b) - else: - # On Python 2, .write() returns None, so we return the length of - # bytes written ourselves. This assumes they all got written. - self.proc.stdin.write(b) - return len(b) - - def sendline(self, s=''): - '''Wraps send(), sending string ``s`` to child process, with os.linesep - automatically appended. Returns number of bytes written. ''' - - n = self.send(s) - 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 - self.signalstatus = None - else: - self.exitstatus = None - self.signalstatus = -status - self.terminated = True - return status - - def kill(self, sig): - '''Sends a Unix signal to the subprocess. + self.closed = False + self._buf = self.string_type() + + self._read_queue = Queue() + self._read_thread = threading.Thread(target=self._read_incoming) + self._read_thread.setDaemon(True) + self._read_thread.start() + + _read_reached_eof = False + + def read_nonblocking(self, size, timeout): + buf = self._buf + if self._read_reached_eof: + # We have already finished reading. Use up any buffered data, + # then raise EOF + if buf: + self._buf = buf[size:] + return buf[:size] + else: + self.flag_eof = True + raise EOF('End Of File (EOF).') + + if timeout == -1: + timeout = self.timeout + elif timeout is None: + timeout = 1e6 + + t0 = time.time() + while (time.time() - t0) < timeout and size and len(buf) < size: + try: + incoming = self._read_queue.get_nowait() + except Empty: + break + else: + if incoming is None: + self._read_reached_eof = True + break + + buf += self._decoder.decode(incoming, final=False) + + r, self._buf = buf[:size], buf[size:] + + self._log(r, 'read') + return r + + def _read_incoming(self): + """Run in a thread to move output from a pipe to a queue.""" + fileno = self.proc.stdout.fileno() + while 1: + buf = b'' + try: + buf = os.read(fileno, 1024) + except OSError as e: + self._log(e, 'read') + + if not buf: + # This indicates we have reached EOF + self._read_queue.put(None) + return + + self._read_queue.put(buf) + + def write(self, s): + '''This is similar to send() except that there is no return value. + ''' + self.send(s) + + def writelines(self, sequence): + '''This calls write() for each element in the sequence. + + The sequence can be any iterable object producing strings, typically a + list of strings. This does not add line separators. There is no return + value. + ''' + for s in sequence: + self.send(s) + + def send(self, s): + '''Send data to the subprocess' stdin. - 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 - elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]: - sig = signal.CTRL_BREAK_EVENT - else: - sig = signal.SIGTERM + Returns the number of bytes written. + ''' + s = self._coerce_send_string(s) + self._log(s, 'send') + + b = self._encoder.encode(s, final=False) + if PY3: + return self.proc.stdin.write(b) + else: + # On Python 2, .write() returns None, so we return the length of + # bytes written ourselves. This assumes they all got written. + self.proc.stdin.write(b) + return len(b) + + def sendline(self, s=''): + '''Wraps send(), sending string ``s`` to child process, with os.linesep + automatically appended. Returns number of bytes written. ''' + + n = self.send(s) + return n + self.send(self.linesep) + + def wait(self): + '''Wait for the subprocess to finish. - os.kill(self.proc.pid, sig) + Returns the exit code. + ''' + status = self.proc.wait() + if status >= 0: + self.exitstatus = status + self.signalstatus = None + else: + self.exitstatus = None + self.signalstatus = -status + self.terminated = True + return status + + def kill(self, sig): + '''Sends a Unix signal to the subprocess. - def sendeof(self): - '''Closes the stdin pipe from the writing end.''' - self.proc.stdin.close() + 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 + elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]: + sig = signal.CTRL_BREAK_EVENT + else: + sig = signal.SIGTERM + + os.kill(self.proc.pid, sig) + + def sendeof(self): + '''Closes the stdin pipe from the writing end.''' + self.proc.stdin.close() diff --git a/contrib/python/pexpect/pexpect/pty_spawn.py b/contrib/python/pexpect/pexpect/pty_spawn.py index 8e28ca7cd7d..3c055cdda33 100644 --- a/contrib/python/pexpect/pexpect/pty_spawn.py +++ b/contrib/python/pexpect/pexpect/pty_spawn.py @@ -1,447 +1,447 @@ -import os -import sys -import time -import pty -import tty -import errno -import signal -from contextlib import contextmanager - -import ptyprocess -from ptyprocess.ptyprocess import use_native_pty_fork - -from .exceptions import ExceptionPexpect, EOF, TIMEOUT -from .spawnbase import SpawnBase +import os +import sys +import time +import pty +import tty +import errno +import signal +from contextlib import contextmanager + +import ptyprocess +from ptyprocess.ptyprocess import use_native_pty_fork + +from .exceptions import ExceptionPexpect, EOF, TIMEOUT +from .spawnbase import SpawnBase from .utils import ( which, split_command_line, select_ignore_interrupts, poll_ignore_interrupts ) - -@contextmanager -def _wrap_ptyprocess_err(): - """Turn ptyprocess errors into our own ExceptionPexpect errors""" - try: - yield - except ptyprocess.PtyProcessError as e: - raise ExceptionPexpect(*e.args) - -PY3 = (sys.version_info[0] >= 3) - -class spawn(SpawnBase): - '''This is the main class interface for Pexpect. Use this class to start - and control child applications. ''' - - # This is purely informational now - changing it has no effect - use_native_pty_fork = use_native_pty_fork - - def __init__(self, command, args=[], timeout=30, maxread=2000, - searchwindowsize=None, logfile=None, cwd=None, env=None, - ignore_sighup=False, echo=True, preexec_fn=None, + +@contextmanager +def _wrap_ptyprocess_err(): + """Turn ptyprocess errors into our own ExceptionPexpect errors""" + try: + yield + except ptyprocess.PtyProcessError as e: + raise ExceptionPexpect(*e.args) + +PY3 = (sys.version_info[0] >= 3) + +class spawn(SpawnBase): + '''This is the main class interface for Pexpect. Use this class to start + and control child applications. ''' + + # This is purely informational now - changing it has no effect + use_native_pty_fork = use_native_pty_fork + + def __init__(self, command, args=[], timeout=30, maxread=2000, + searchwindowsize=None, logfile=None, cwd=None, env=None, + ignore_sighup=False, echo=True, preexec_fn=None, encoding=None, codec_errors='strict', dimensions=None, use_poll=False): - '''This is the constructor. The command parameter may be a string that - includes a command and any arguments to the command. For example:: - - child = pexpect.spawn('/usr/bin/ftp') - child = pexpect.spawn('/usr/bin/ssh [email protected]') - child = pexpect.spawn('ls -latr /tmp') - - You may also construct it with a list of arguments like so:: - - child = pexpect.spawn('/usr/bin/ftp', []) - child = pexpect.spawn('/usr/bin/ssh', ['[email protected]']) - child = pexpect.spawn('ls', ['-latr', '/tmp']) - - After this the child application will be created and will be ready to - talk to. For normal use, see expect() and send() and sendline(). - - Remember that Pexpect does NOT interpret shell meta characters such as - redirect, pipe, or wild cards (``>``, ``|``, or ``*``). This is a - common mistake. If you want to run a command and pipe it through - another command then you must also start a shell. For example:: - - child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"') - child.expect(pexpect.EOF) - - The second form of spawn (where you pass a list of arguments) is useful - in situations where you wish to spawn a command and pass it its own - argument list. This can make syntax more clear. For example, the - following is equivalent to the previous example:: - - shell_cmd = 'ls -l | grep LOG > logs.txt' - child = pexpect.spawn('/bin/bash', ['-c', shell_cmd]) - child.expect(pexpect.EOF) - - The maxread attribute sets the read buffer size. This is maximum number - of bytes that Pexpect will try to read from a TTY at one time. Setting - the maxread size to 1 will turn off buffering. Setting the maxread - value higher may help performance in cases where large amounts of - output are read back from the child. This feature is useful in - conjunction with searchwindowsize. - - When the keyword argument *searchwindowsize* is None (default), the - full buffer is searched at each iteration of receiving incoming data. - The default number of bytes scanned at each iteration is very large - and may be reduced to collaterally reduce search cost. After - :meth:`~.expect` returns, the full buffer attribute remains up to - size *maxread* irrespective of *searchwindowsize* value. - - When the keyword argument ``timeout`` is specified as a number, - (default: *30*), then :class:`TIMEOUT` will be raised after the value - specified has elapsed, in seconds, for any of the :meth:`~.expect` - family of method calls. When None, TIMEOUT will not be raised, and - :meth:`~.expect` may block indefinitely until match. - - - The logfile member turns on or off logging. All input and output will - be copied to the given file object. Set logfile to None to stop - logging. This is the default. Set logfile to sys.stdout to echo - everything to standard output. The logfile is flushed after each write. - - Example log input and output to a file:: - - child = pexpect.spawn('some_command') - fout = open('mylog.txt','wb') - child.logfile = fout - - Example log to stdout:: - - # In Python 2: - child = pexpect.spawn('some_command') - child.logfile = sys.stdout - + '''This is the constructor. The command parameter may be a string that + includes a command and any arguments to the command. For example:: + + child = pexpect.spawn('/usr/bin/ftp') + child = pexpect.spawn('/usr/bin/ssh [email protected]') + child = pexpect.spawn('ls -latr /tmp') + + You may also construct it with a list of arguments like so:: + + child = pexpect.spawn('/usr/bin/ftp', []) + child = pexpect.spawn('/usr/bin/ssh', ['[email protected]']) + child = pexpect.spawn('ls', ['-latr', '/tmp']) + + After this the child application will be created and will be ready to + talk to. For normal use, see expect() and send() and sendline(). + + Remember that Pexpect does NOT interpret shell meta characters such as + redirect, pipe, or wild cards (``>``, ``|``, or ``*``). This is a + common mistake. If you want to run a command and pipe it through + another command then you must also start a shell. For example:: + + child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"') + child.expect(pexpect.EOF) + + The second form of spawn (where you pass a list of arguments) is useful + in situations where you wish to spawn a command and pass it its own + argument list. This can make syntax more clear. For example, the + following is equivalent to the previous example:: + + shell_cmd = 'ls -l | grep LOG > logs.txt' + child = pexpect.spawn('/bin/bash', ['-c', shell_cmd]) + child.expect(pexpect.EOF) + + The maxread attribute sets the read buffer size. This is maximum number + of bytes that Pexpect will try to read from a TTY at one time. Setting + the maxread size to 1 will turn off buffering. Setting the maxread + value higher may help performance in cases where large amounts of + output are read back from the child. This feature is useful in + conjunction with searchwindowsize. + + When the keyword argument *searchwindowsize* is None (default), the + full buffer is searched at each iteration of receiving incoming data. + The default number of bytes scanned at each iteration is very large + and may be reduced to collaterally reduce search cost. After + :meth:`~.expect` returns, the full buffer attribute remains up to + size *maxread* irrespective of *searchwindowsize* value. + + When the keyword argument ``timeout`` is specified as a number, + (default: *30*), then :class:`TIMEOUT` will be raised after the value + specified has elapsed, in seconds, for any of the :meth:`~.expect` + family of method calls. When None, TIMEOUT will not be raised, and + :meth:`~.expect` may block indefinitely until match. + + + The logfile member turns on or off logging. All input and output will + be copied to the given file object. Set logfile to None to stop + logging. This is the default. Set logfile to sys.stdout to echo + everything to standard output. The logfile is flushed after each write. + + Example log input and output to a file:: + + child = pexpect.spawn('some_command') + fout = open('mylog.txt','wb') + child.logfile = fout + + Example log to stdout:: + + # In Python 2: + child = pexpect.spawn('some_command') + child.logfile = sys.stdout + # In Python 3, we'll use the ``encoding`` argument to decode data # from the subprocess and handle it as unicode: child = pexpect.spawn('some_command', encoding='utf-8') - child.logfile = sys.stdout - - The logfile_read and logfile_send members can be used to separately log - the input from the child and output sent to the child. Sometimes you - don't want to see everything you write to the child. You only want to - log what the child sends back. For example:: - - child = pexpect.spawn('some_command') - child.logfile_read = sys.stdout - - You will need to pass an encoding to spawn in the above code if you are - using Python 3. - - To separately log output sent to the child use logfile_send:: - - child.logfile_send = fout - - If ``ignore_sighup`` is True, the child process will ignore SIGHUP - signals. The default is False from Pexpect 4.0, meaning that SIGHUP - will be handled normally by the child. - - The delaybeforesend helps overcome a weird behavior that many users - were experiencing. The typical problem was that a user would expect() a - "Password:" prompt and then immediately call sendline() to send the - password. The user would then see that their password was echoed back - to them. Passwords don't normally echo. The problem is caused by the - fact that most applications print out the "Password" prompt and then - turn off stdin echo, but if you send your password before the - application turned off echo, then you get your password echoed. - Normally this wouldn't be a problem when interacting with a human at a - real keyboard. If you introduce a slight delay just before writing then - this seems to clear up the problem. This was such a common problem for - many users that I decided that the default pexpect behavior should be - to sleep just before writing to the child application. 1/20th of a - second (50 ms) seems to be enough to clear up the problem. You can set + child.logfile = sys.stdout + + The logfile_read and logfile_send members can be used to separately log + the input from the child and output sent to the child. Sometimes you + don't want to see everything you write to the child. You only want to + log what the child sends back. For example:: + + child = pexpect.spawn('some_command') + child.logfile_read = sys.stdout + + You will need to pass an encoding to spawn in the above code if you are + using Python 3. + + To separately log output sent to the child use logfile_send:: + + child.logfile_send = fout + + If ``ignore_sighup`` is True, the child process will ignore SIGHUP + signals. The default is False from Pexpect 4.0, meaning that SIGHUP + will be handled normally by the child. + + The delaybeforesend helps overcome a weird behavior that many users + were experiencing. The typical problem was that a user would expect() a + "Password:" prompt and then immediately call sendline() to send the + password. The user would then see that their password was echoed back + to them. Passwords don't normally echo. The problem is caused by the + fact that most applications print out the "Password" prompt and then + turn off stdin echo, but if you send your password before the + application turned off echo, then you get your password echoed. + Normally this wouldn't be a problem when interacting with a human at a + real keyboard. If you introduce a slight delay just before writing then + this seems to clear up the problem. This was such a common problem for + many users that I decided that the default pexpect behavior should be + to sleep just before writing to the child application. 1/20th of a + second (50 ms) seems to be enough to clear up the problem. You can set delaybeforesend to None to return to the old behavior. - - Note that spawn is clever about finding commands on your path. - It uses the same logic that "which" uses to find executables. - - If you wish to get the exit status of the child you must call the - close() method. The exit or signal status of the child will be stored - in self.exitstatus or self.signalstatus. If the child exited normally - then exitstatus will store the exit return code and signalstatus will - be None. If the child was terminated abnormally with a signal then + + Note that spawn is clever about finding commands on your path. + It uses the same logic that "which" uses to find executables. + + If you wish to get the exit status of the child you must call the + close() method. The exit or signal status of the child will be stored + in self.exitstatus or self.signalstatus. If the child exited normally + then exitstatus will store the exit return code and signalstatus will + be None. If the child was terminated abnormally with a signal then signalstatus will store the signal value and exitstatus will be None:: child = pexpect.spawn('some_command') child.close() print(child.exitstatus, child.signalstatus) - If you need more detail you can also read the self.status member which - stores the status returned by os.waitpid. You can interpret this using - os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. - - The echo attribute may be set to False to disable echoing of input. - As a pseudo-terminal, all input echoed by the "keyboard" (send() - or sendline()) will be repeated to output. For many cases, it is - not desirable to have echo enabled, and it may be later disabled - using setecho(False) followed by waitnoecho(). However, for some - platforms such as Solaris, this is not possible, and should be - disabled immediately on spawn. + If you need more detail you can also read the self.status member which + stores the status returned by os.waitpid. You can interpret this using + os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. + + The echo attribute may be set to False to disable echoing of input. + As a pseudo-terminal, all input echoed by the "keyboard" (send() + or sendline()) will be repeated to output. For many cases, it is + not desirable to have echo enabled, and it may be later disabled + using setecho(False) followed by waitnoecho(). However, for some + platforms such as Solaris, this is not possible, and should be + disabled immediately on spawn. - If preexec_fn is given, it will be called in the child process before - launching the given command. This is useful to e.g. reset inherited - signal handlers. - - The dimensions attribute specifies the size of the pseudo-terminal as - seen by the subprocess, and is specified as a two-entry tuple (rows, - columns). If this is unspecified, the defaults in ptyprocess will apply. + If preexec_fn is given, it will be called in the child process before + launching the given command. This is useful to e.g. reset inherited + signal handlers. + + The dimensions attribute specifies the size of the pseudo-terminal as + seen by the subprocess, and is specified as a two-entry tuple (rows, + columns). If this is unspecified, the defaults in ptyprocess will apply. The use_poll attribute enables using select.poll() over select.select() for socket handling. This is handy if your system could have > 1024 fds - ''' - super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, - logfile=logfile, encoding=encoding, codec_errors=codec_errors) - self.STDIN_FILENO = pty.STDIN_FILENO - self.STDOUT_FILENO = pty.STDOUT_FILENO - self.STDERR_FILENO = pty.STDERR_FILENO + ''' + super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, + logfile=logfile, encoding=encoding, codec_errors=codec_errors) + self.STDIN_FILENO = pty.STDIN_FILENO + self.STDOUT_FILENO = pty.STDOUT_FILENO + self.STDERR_FILENO = pty.STDERR_FILENO self.str_last_chars = 100 - self.cwd = cwd - self.env = env - self.echo = echo - self.ignore_sighup = ignore_sighup - self.__irix_hack = sys.platform.lower().startswith('irix') - if command is None: - self.command = None - self.args = None - self.name = '<pexpect factory incomplete>' - else: - self._spawn(command, args, preexec_fn, dimensions) + self.cwd = cwd + self.env = env + self.echo = echo + self.ignore_sighup = ignore_sighup + self.__irix_hack = sys.platform.lower().startswith('irix') + if command is None: + self.command = None + self.args = None + self.name = '<pexpect factory incomplete>' + else: + self._spawn(command, args, preexec_fn, dimensions) self.use_poll = use_poll - - def __str__(self): - '''This returns a human-readable string that represents the state of - the object. ''' - - s = [] - s.append(repr(self)) - s.append('command: ' + str(self.command)) - s.append('args: %r' % (self.args,)) + + def __str__(self): + '''This returns a human-readable string that represents the state of + the object. ''' + + s = [] + s.append(repr(self)) + s.append('command: ' + str(self.command)) + s.append('args: %r' % (self.args,)) s.append('buffer (last %s chars): %r' % (self.str_last_chars,self.buffer[-self.str_last_chars:])) s.append('before (last %s chars): %r' % (self.str_last_chars,self.before[-self.str_last_chars:] if self.before else '')) - s.append('after: %r' % (self.after,)) - s.append('match: %r' % (self.match,)) - s.append('match_index: ' + str(self.match_index)) - s.append('exitstatus: ' + str(self.exitstatus)) + s.append('after: %r' % (self.after,)) + s.append('match: %r' % (self.match,)) + s.append('match_index: ' + str(self.match_index)) + s.append('exitstatus: ' + str(self.exitstatus)) if hasattr(self, 'ptyproc'): s.append('flag_eof: ' + str(self.flag_eof)) - s.append('pid: ' + str(self.pid)) - s.append('child_fd: ' + str(self.child_fd)) - s.append('closed: ' + str(self.closed)) - s.append('timeout: ' + str(self.timeout)) - s.append('delimiter: ' + str(self.delimiter)) - s.append('logfile: ' + str(self.logfile)) - s.append('logfile_read: ' + str(self.logfile_read)) - s.append('logfile_send: ' + str(self.logfile_send)) - s.append('maxread: ' + str(self.maxread)) - s.append('ignorecase: ' + str(self.ignorecase)) - s.append('searchwindowsize: ' + str(self.searchwindowsize)) - s.append('delaybeforesend: ' + str(self.delaybeforesend)) - s.append('delayafterclose: ' + str(self.delayafterclose)) - s.append('delayafterterminate: ' + str(self.delayafterterminate)) - return '\n'.join(s) - - def _spawn(self, command, args=[], preexec_fn=None, dimensions=None): - '''This starts the given command in a child process. This does all the - fork/exec type of stuff for a pty. This is called by __init__. If args - is empty then command will be parsed (split on spaces) and args will be - set to parsed arguments. ''' - - # The pid and child_fd of this object get set by this method. - # Note that it is difficult for this method to fail. - # You cannot detect if the child process cannot start. - # So the only way you can tell if the child process started - # or not is to try to read from the file descriptor. If you get - # EOF immediately then it means that the child is already dead. - # That may not necessarily be bad because you may have spawned a child - # that performs some task; creates no stdout output; and then dies. - - # If command is an int type then it may represent a file descriptor. - if isinstance(command, type(0)): - raise ExceptionPexpect('Command is an int type. ' + - 'If this is a file descriptor then maybe you want to ' + - 'use fdpexpect.fdspawn which takes an existing ' + - 'file descriptor instead of a command string.') - - if not isinstance(args, type([])): - raise TypeError('The argument, args, must be a list.') - - if args == []: - self.args = split_command_line(command) - self.command = self.args[0] - else: - # Make a shallow copy of the args list. - self.args = args[:] - self.args.insert(0, command) - self.command = command - + s.append('pid: ' + str(self.pid)) + s.append('child_fd: ' + str(self.child_fd)) + s.append('closed: ' + str(self.closed)) + s.append('timeout: ' + str(self.timeout)) + s.append('delimiter: ' + str(self.delimiter)) + s.append('logfile: ' + str(self.logfile)) + s.append('logfile_read: ' + str(self.logfile_read)) + s.append('logfile_send: ' + str(self.logfile_send)) + s.append('maxread: ' + str(self.maxread)) + s.append('ignorecase: ' + str(self.ignorecase)) + s.append('searchwindowsize: ' + str(self.searchwindowsize)) + s.append('delaybeforesend: ' + str(self.delaybeforesend)) + s.append('delayafterclose: ' + str(self.delayafterclose)) + s.append('delayafterterminate: ' + str(self.delayafterterminate)) + return '\n'.join(s) + + def _spawn(self, command, args=[], preexec_fn=None, dimensions=None): + '''This starts the given command in a child process. This does all the + fork/exec type of stuff for a pty. This is called by __init__. If args + is empty then command will be parsed (split on spaces) and args will be + set to parsed arguments. ''' + + # The pid and child_fd of this object get set by this method. + # Note that it is difficult for this method to fail. + # You cannot detect if the child process cannot start. + # So the only way you can tell if the child process started + # or not is to try to read from the file descriptor. If you get + # EOF immediately then it means that the child is already dead. + # That may not necessarily be bad because you may have spawned a child + # that performs some task; creates no stdout output; and then dies. + + # If command is an int type then it may represent a file descriptor. + if isinstance(command, type(0)): + raise ExceptionPexpect('Command is an int type. ' + + 'If this is a file descriptor then maybe you want to ' + + 'use fdpexpect.fdspawn which takes an existing ' + + 'file descriptor instead of a command string.') + + if not isinstance(args, type([])): + raise TypeError('The argument, args, must be a list.') + + if args == []: + self.args = split_command_line(command) + self.command = self.args[0] + else: + # Make a shallow copy of the args list. + self.args = args[:] + self.args.insert(0, command) + self.command = command + command_with_path = which(self.command, env=self.env) - if command_with_path is None: - raise ExceptionPexpect('The command was not found or was not ' + - 'executable: %s.' % self.command) - self.command = command_with_path - self.args[0] = self.command - - self.name = '<' + ' '.join(self.args) + '>' - - assert self.pid is None, 'The pid member must be None.' - assert self.command is not None, 'The command member must not be None.' - - kwargs = {'echo': self.echo, 'preexec_fn': preexec_fn} - if self.ignore_sighup: - def preexec_wrapper(): - "Set SIGHUP to be ignored, then call the real preexec_fn" - signal.signal(signal.SIGHUP, signal.SIG_IGN) - if preexec_fn is not None: - preexec_fn() - kwargs['preexec_fn'] = preexec_wrapper - - if dimensions is not None: - kwargs['dimensions'] = dimensions - + if command_with_path is None: + raise ExceptionPexpect('The command was not found or was not ' + + 'executable: %s.' % self.command) + self.command = command_with_path + self.args[0] = self.command + + self.name = '<' + ' '.join(self.args) + '>' + + assert self.pid is None, 'The pid member must be None.' + assert self.command is not None, 'The command member must not be None.' + + kwargs = {'echo': self.echo, 'preexec_fn': preexec_fn} + if self.ignore_sighup: + def preexec_wrapper(): + "Set SIGHUP to be ignored, then call the real preexec_fn" + signal.signal(signal.SIGHUP, signal.SIG_IGN) + if preexec_fn is not None: + preexec_fn() + kwargs['preexec_fn'] = preexec_wrapper + + if dimensions is not None: + kwargs['dimensions'] = dimensions + if self.encoding is not None: # Encode command line using the specified encoding self.args = [a if isinstance(a, bytes) else a.encode(self.encoding) for a in self.args] - + self.ptyproc = self._spawnpty(self.args, env=self.env, cwd=self.cwd, **kwargs) - self.pid = self.ptyproc.pid - self.child_fd = self.ptyproc.fd - - - self.terminated = False - self.closed = False - + self.pid = self.ptyproc.pid + self.child_fd = self.ptyproc.fd + + + self.terminated = False + self.closed = False + def _spawnpty(self, args, **kwargs): '''Spawn a pty and return an instance of PtyProcess.''' return ptyprocess.PtyProcess.spawn(args, **kwargs) - def close(self, force=True): - '''This closes the connection with the child application. Note that - calling close() more than once is valid. This emulates standard Python - behavior with files. Set force to True if you want to make sure that - the child is terminated (SIGKILL is sent if the child ignores SIGHUP - and SIGINT). ''' - - self.flush() + def close(self, force=True): + '''This closes the connection with the child application. Note that + calling close() more than once is valid. This emulates standard Python + behavior with files. Set force to True if you want to make sure that + the child is terminated (SIGKILL is sent if the child ignores SIGHUP + and SIGINT). ''' + + self.flush() with _wrap_ptyprocess_err(): # PtyProcessError may be raised if it is not possible to terminate # the child. self.ptyproc.close(force=force) - self.isalive() # Update exit status from ptyproc - self.child_fd = -1 + self.isalive() # Update exit status from ptyproc + self.child_fd = -1 self.closed = True - - def isatty(self): - '''This returns True if the file descriptor is open and connected to a - tty(-like) device, else False. - - On SVR4-style platforms implementing streams, such as SunOS and HP-UX, - the child pty may not appear as a terminal device. This means - methods such as setecho(), setwinsize(), getwinsize() may raise an - IOError. ''' - - return os.isatty(self.child_fd) - - def waitnoecho(self, timeout=-1): - '''This waits until the terminal ECHO flag is set False. This returns - True if the echo mode is off. This returns False if the ECHO flag was - not set False before the timeout. This can be used to detect when the - child is waiting for a password. Usually a child application will turn - off echo mode when it is waiting for the user to enter a password. For - example, instead of expecting the "password:" prompt you can wait for - the child to set ECHO off:: - - p = pexpect.spawn('ssh [email protected]') - p.waitnoecho() - p.sendline(mypassword) - - If timeout==-1 then this method will use the value in self.timeout. - If timeout==None then this method to block until ECHO flag is False. - ''' - - if timeout == -1: - timeout = self.timeout - if timeout is not None: - end_time = time.time() + timeout - while True: - if not self.getecho(): - return True - if timeout < 0 and timeout is not None: - return False - if timeout is not None: - timeout = end_time - time.time() - time.sleep(0.1) - - def getecho(self): - '''This returns the terminal echo mode. This returns True if echo is - on or False if echo is off. Child applications that are expecting you - to enter a password often set ECHO False. See waitnoecho(). - - Not supported on platforms where ``isatty()`` returns False. ''' - return self.ptyproc.getecho() - - def setecho(self, state): - '''This sets the terminal echo mode on or off. Note that anything the - child sent before the echo will be lost, so you should be sure that - your input buffer is empty before you call setecho(). For example, the - following will work as expected:: - - p = pexpect.spawn('cat') # Echo is on by default. - p.sendline('1234') # We expect see this twice from the child... - p.expect(['1234']) # ... once from the tty echo... - p.expect(['1234']) # ... and again from cat itself. - p.setecho(False) # Turn off tty echo - p.sendline('abcd') # We will set this only once (echoed by cat). - p.sendline('wxyz') # We will set this only once (echoed by cat) - p.expect(['abcd']) - p.expect(['wxyz']) - - The following WILL NOT WORK because the lines sent before the setecho - will be lost:: - - p = pexpect.spawn('cat') - p.sendline('1234') - p.setecho(False) # Turn off tty echo - p.sendline('abcd') # We will set this only once (echoed by cat). - p.sendline('wxyz') # We will set this only once (echoed by cat) - p.expect(['1234']) - p.expect(['1234']) - p.expect(['abcd']) - p.expect(['wxyz']) - - - Not supported on platforms where ``isatty()`` returns False. - ''' - return self.ptyproc.setecho(state) - - def read_nonblocking(self, size=1, timeout=-1): - '''This reads at most size characters from the child application. It - includes a timeout. If the read does not complete within the timeout - period then a TIMEOUT exception is raised. If the end of file is read - then an EOF exception will be raised. If a logfile is specified, a - copy is written to that log. - - If timeout is None then the read may block indefinitely. - If timeout is -1 then the self.timeout value is used. If timeout is 0 - then the child is polled and if there is no data immediately ready - then this will raise a TIMEOUT exception. - - The timeout refers only to the amount of time to read at least one - character. This is not affected by the 'size' parameter, so if you call - read_nonblocking(size=100, timeout=30) and only one character is - available right away then one character will be returned immediately. - It will not wait for 30 seconds for another 99 characters to come in. - + + def isatty(self): + '''This returns True if the file descriptor is open and connected to a + tty(-like) device, else False. + + On SVR4-style platforms implementing streams, such as SunOS and HP-UX, + the child pty may not appear as a terminal device. This means + methods such as setecho(), setwinsize(), getwinsize() may raise an + IOError. ''' + + return os.isatty(self.child_fd) + + def waitnoecho(self, timeout=-1): + '''This waits until the terminal ECHO flag is set False. This returns + True if the echo mode is off. This returns False if the ECHO flag was + not set False before the timeout. This can be used to detect when the + child is waiting for a password. Usually a child application will turn + off echo mode when it is waiting for the user to enter a password. For + example, instead of expecting the "password:" prompt you can wait for + the child to set ECHO off:: + + p = pexpect.spawn('ssh [email protected]') + p.waitnoecho() + p.sendline(mypassword) + + If timeout==-1 then this method will use the value in self.timeout. + If timeout==None then this method to block until ECHO flag is False. + ''' + + if timeout == -1: + timeout = self.timeout + if timeout is not None: + end_time = time.time() + timeout + while True: + if not self.getecho(): + return True + if timeout < 0 and timeout is not None: + return False + if timeout is not None: + timeout = end_time - time.time() + time.sleep(0.1) + + def getecho(self): + '''This returns the terminal echo mode. This returns True if echo is + on or False if echo is off. Child applications that are expecting you + to enter a password often set ECHO False. See waitnoecho(). + + Not supported on platforms where ``isatty()`` returns False. ''' + return self.ptyproc.getecho() + + def setecho(self, state): + '''This sets the terminal echo mode on or off. Note that anything the + child sent before the echo will be lost, so you should be sure that + your input buffer is empty before you call setecho(). For example, the + following will work as expected:: + + p = pexpect.spawn('cat') # Echo is on by default. + p.sendline('1234') # We expect see this twice from the child... + p.expect(['1234']) # ... once from the tty echo... + p.expect(['1234']) # ... and again from cat itself. + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['abcd']) + p.expect(['wxyz']) + + The following WILL NOT WORK because the lines sent before the setecho + will be lost:: + + p = pexpect.spawn('cat') + p.sendline('1234') + p.setecho(False) # Turn off tty echo + p.sendline('abcd') # We will set this only once (echoed by cat). + p.sendline('wxyz') # We will set this only once (echoed by cat) + p.expect(['1234']) + p.expect(['1234']) + p.expect(['abcd']) + p.expect(['wxyz']) + + + Not supported on platforms where ``isatty()`` returns False. + ''' + return self.ptyproc.setecho(state) + + def read_nonblocking(self, size=1, timeout=-1): + '''This reads at most size characters from the child application. It + includes a timeout. If the read does not complete within the timeout + period then a TIMEOUT exception is raised. If the end of file is read + then an EOF exception will be raised. If a logfile is specified, a + copy is written to that log. + + If timeout is None then the read may block indefinitely. + If timeout is -1 then the self.timeout value is used. If timeout is 0 + then the child is polled and if there is no data immediately ready + then this will raise a TIMEOUT exception. + + The timeout refers only to the amount of time to read at least one + character. This is not affected by the 'size' parameter, so if you call + read_nonblocking(size=100, timeout=30) and only one character is + available right away then one character will be returned immediately. + It will not wait for 30 seconds for another 99 characters to come in. + On the other hand, if there are bytes available to read immediately, all those bytes will be read (up to the buffer size). So, if the buffer size is 1 megabyte and there is 1 megabyte of data available to read, the buffer will be filled, regardless of timeout. - + This is a wrapper around os.read(). It uses select.select() or select.poll() to implement the timeout. ''' - if self.closed: - raise ValueError('I/O operation on closed file.') - + if self.closed: + raise ValueError('I/O operation on closed file.') + if self.use_poll: def select(timeout): return poll_ignore_interrupts([self.child_fd], timeout) @@ -472,10 +472,10 @@ class spawn(SpawnBase): return incoming return incoming - if timeout == -1: - timeout = self.timeout - - if not self.isalive(): + if timeout == -1: + timeout = self.timeout + + if not self.isalive(): # The process is dead, but there may or may not be data # available to read. Note that some systems such as Solaris # do not give an EOF when the child dies. In fact, you can @@ -486,20 +486,20 @@ class spawn(SpawnBase): return super(spawn, self).read_nonblocking(size) self.flag_eof = True raise EOF('End Of File (EOF). Braindead platform.') - elif self.__irix_hack: - # Irix takes a long time before it realizes a child was terminated. + elif self.__irix_hack: + # Irix takes a long time before it realizes a child was terminated. # Make sure that the timeout is at least 2 seconds. - # FIXME So does this mean Irix systems are forced to always have - # FIXME a 2 second delay when calling read_nonblocking? That sucks. + # FIXME So does this mean Irix systems are forced to always have + # FIXME a 2 second delay when calling read_nonblocking? That sucks. if timeout is not None and timeout < 2: timeout = 2 - + # Because of the select(0) check above, we know that no data # is available right now. But if a non-zero timeout is given # (possibly timeout=None), we call select() with a timeout. if (timeout != 0) and select(timeout): - return super(spawn, self).read_nonblocking(size) - + return super(spawn, self).read_nonblocking(size) + if not self.isalive(): # Some platforms, such as Irix, will claim that their # processes are alive; timeout on the select; and @@ -508,353 +508,353 @@ class spawn(SpawnBase): raise EOF('End of File (EOF). Very slow platform.') else: raise TIMEOUT('Timeout exceeded.') - - def write(self, s): - '''This is similar to send() except that there is no return value. - ''' - - self.send(s) - - def writelines(self, sequence): - '''This calls write() for each element in the sequence. The sequence - can be any iterable object producing strings, typically a list of - strings. This does not add line separators. There is no return value. - ''' - - for s in sequence: - self.write(s) - - def send(self, s): - '''Sends string ``s`` to the child process, returning the number of - bytes written. If a logfile is specified, a copy is written to that - log. - - The default terminal input mode is canonical processing unless set - otherwise by the child process. This allows backspace and other line - processing to be performed prior to transmitting to the receiving - program. As this is buffered, there is a limited size of such buffer. - - On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All - other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024 - on OSX, 256 on OpenSolaris, and 1920 on FreeBSD. - - This value may be discovered using fpathconf(3):: - + + def write(self, s): + '''This is similar to send() except that there is no return value. + ''' + + self.send(s) + + def writelines(self, sequence): + '''This calls write() for each element in the sequence. The sequence + can be any iterable object producing strings, typically a list of + strings. This does not add line separators. There is no return value. + ''' + + for s in sequence: + self.write(s) + + def send(self, s): + '''Sends string ``s`` to the child process, returning the number of + bytes written. If a logfile is specified, a copy is written to that + log. + + The default terminal input mode is canonical processing unless set + otherwise by the child process. This allows backspace and other line + processing to be performed prior to transmitting to the receiving + program. As this is buffered, there is a limited size of such buffer. + + On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All + other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024 + on OSX, 256 on OpenSolaris, and 1920 on FreeBSD. + + This value may be discovered using fpathconf(3):: + >>> from os import fpathconf >>> print(fpathconf(0, 'PC_MAX_CANON')) 256 - - On such a system, only 256 bytes may be received per line. Any - subsequent bytes received will be discarded. BEL (``'\a'``) is then - sent to output if IMAXBEL (termios.h) is set by the tty driver. - This is usually enabled by default. Linux does not honor this as - an option -- it behaves as though it is always set on. - - Canonical input processing may be disabled altogether by executing - a shell, then stty(1), before executing the final program:: - + + On such a system, only 256 bytes may be received per line. Any + subsequent bytes received will be discarded. BEL (``'\a'``) is then + sent to output if IMAXBEL (termios.h) is set by the tty driver. + This is usually enabled by default. Linux does not honor this as + an option -- it behaves as though it is always set on. + + Canonical input processing may be disabled altogether by executing + a shell, then stty(1), before executing the final program:: + >>> bash = pexpect.spawn('/bin/bash', echo=False) >>> bash.sendline('stty -icanon') >>> bash.sendline('base64') >>> bash.sendline('x' * 5000) - ''' - + ''' + if self.delaybeforesend is not None: time.sleep(self.delaybeforesend) - - s = self._coerce_send_string(s) - self._log(s, 'send') - - b = self._encoder.encode(s, final=False) - return os.write(self.child_fd, b) - - def sendline(self, s=''): - '''Wraps send(), sending string ``s`` to child process, with - ``os.linesep`` automatically appended. Returns number of bytes - written. Only a limited number of bytes may be sent for each - line in the default terminal mode, see docstring of :meth:`send`. - ''' + + s = self._coerce_send_string(s) + self._log(s, 'send') + + b = self._encoder.encode(s, final=False) + return os.write(self.child_fd, b) + + def sendline(self, s=''): + '''Wraps send(), sending string ``s`` to child process, with + ``os.linesep`` automatically appended. Returns number of bytes + written. Only a limited number of bytes may be sent for each + line in the default terminal mode, see docstring of :meth:`send`. + ''' s = self._coerce_send_string(s) return self.send(s + self.linesep) - - def _log_control(self, s): - """Write control characters to the appropriate log files""" - if self.encoding is not None: - s = s.decode(self.encoding, 'replace') - self._log(s, 'send') - - def sendcontrol(self, char): - '''Helper method that wraps send() with mnemonic access for sending control - character to the child (such as Ctrl-C or Ctrl-D). For example, to send - Ctrl-G (ASCII 7, bell, '\a'):: - - child.sendcontrol('g') - - See also, sendintr() and sendeof(). - ''' - n, byte = self.ptyproc.sendcontrol(char) - self._log_control(byte) - return n - - def sendeof(self): - '''This sends an EOF to the child. This sends a character which causes - the pending parent output buffer to be sent to the waiting child - program without waiting for end-of-line. If it is the first character - of the line, the read() in the user program returns 0, which signifies - end-of-file. This means to work as expected a sendeof() has to be - called at the beginning of a line. This method does not send a newline. - It is the responsibility of the caller to ensure the eof is sent at the - beginning of a line. ''' - - n, byte = self.ptyproc.sendeof() - self._log_control(byte) - - def sendintr(self): - '''This sends a SIGINT to the child. It does not require - the SIGINT to be the first character on a line. ''' - - n, byte = self.ptyproc.sendintr() - self._log_control(byte) - - @property - def flag_eof(self): - return self.ptyproc.flag_eof - - @flag_eof.setter - def flag_eof(self, value): - self.ptyproc.flag_eof = value - - def eof(self): - '''This returns True if the EOF exception was ever raised. - ''' - return self.flag_eof - - def terminate(self, force=False): - '''This forces a child process to terminate. It starts nicely with - SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This - returns True if the child was terminated. This returns False if the - child could not be terminated. ''' - - if not self.isalive(): - return True - try: - self.kill(signal.SIGHUP) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - self.kill(signal.SIGCONT) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - self.kill(signal.SIGINT) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - if force: - self.kill(signal.SIGKILL) - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - else: - return False - return False - except OSError: - # I think there are kernel timing issues that sometimes cause - # this to happen. I think isalive() reports True, but the - # process is dead to the kernel. - # Make one last attempt to see if the kernel is up to date. - time.sleep(self.delayafterterminate) - if not self.isalive(): - return True - else: - return False - - def wait(self): - '''This waits until the child exits. This is a blocking call. This will - not read any data from the child, so this will block forever if the - child has unread output and has terminated. In other words, the child - may have printed output then called exit(), but, the child is - technically still alive until its output is read by the parent. - - This method is non-blocking if :meth:`wait` has already been called - previously or :meth:`isalive` method returns False. It simply returns - the previously determined exit status. - ''' - - ptyproc = self.ptyproc - with _wrap_ptyprocess_err(): - # exception may occur if "Is some other process attempting - # "job control with our child pid?" - exitstatus = ptyproc.wait() - self.status = ptyproc.status - self.exitstatus = ptyproc.exitstatus - self.signalstatus = ptyproc.signalstatus - self.terminated = True - - return exitstatus - - def isalive(self): - '''This tests if the child process is running or not. This is - non-blocking. If the child was terminated then this will read the - exitstatus or signalstatus of the child. This returns True if the child - process appears to be running or False if not. It can take literally - SECONDS for Solaris to return the right status. ''' - - ptyproc = self.ptyproc - with _wrap_ptyprocess_err(): - alive = ptyproc.isalive() - - if not alive: - self.status = ptyproc.status - self.exitstatus = ptyproc.exitstatus - self.signalstatus = ptyproc.signalstatus - self.terminated = True - - return alive - - def kill(self, sig): - - '''This sends the given signal to the child application. In keeping - with UNIX tradition it has a misleading name. It does not necessarily - kill the child unless you send the right signal. ''' - - # Same as os.kill, but the pid is given for you. - if self.isalive(): - os.kill(self.pid, sig) - - def getwinsize(self): - '''This returns the terminal window size of the child tty. The return - value is a tuple of (rows, cols). ''' - return self.ptyproc.getwinsize() - - def setwinsize(self, rows, cols): - '''This sets the terminal window size of the child tty. This will cause - a SIGWINCH signal to be sent to the child. This does not change the - physical window size. It changes the size reported to TTY-aware - applications like vi or curses -- applications that respond to the - SIGWINCH signal. ''' - return self.ptyproc.setwinsize(rows, cols) - - - def interact(self, escape_character=chr(29), - input_filter=None, output_filter=None): - - '''This gives control of the child process to the interactive user (the - human at the keyboard). Keystrokes are sent to the child process, and - the stdout and stderr output of the child process is printed. This - simply echos the child stdout and child stderr to the real stdout and - it echos the real stdin to the child stdin. When the user types the - escape_character this method will return None. The escape_character - will not be transmitted. The default for escape_character is - entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent - escaping, escape_character may be set to None. - - If a logfile is specified, then the data sent and received from the - child process in interact mode is duplicated to the given log. - - You may pass in optional input and output filter functions. These + + def _log_control(self, s): + """Write control characters to the appropriate log files""" + if self.encoding is not None: + s = s.decode(self.encoding, 'replace') + self._log(s, 'send') + + def sendcontrol(self, char): + '''Helper method that wraps send() with mnemonic access for sending control + character to the child (such as Ctrl-C or Ctrl-D). For example, to send + Ctrl-G (ASCII 7, bell, '\a'):: + + child.sendcontrol('g') + + See also, sendintr() and sendeof(). + ''' + n, byte = self.ptyproc.sendcontrol(char) + self._log_control(byte) + return n + + def sendeof(self): + '''This sends an EOF to the child. This sends a character which causes + the pending parent output buffer to be sent to the waiting child + program without waiting for end-of-line. If it is the first character + of the line, the read() in the user program returns 0, which signifies + end-of-file. This means to work as expected a sendeof() has to be + called at the beginning of a line. This method does not send a newline. + It is the responsibility of the caller to ensure the eof is sent at the + beginning of a line. ''' + + n, byte = self.ptyproc.sendeof() + self._log_control(byte) + + def sendintr(self): + '''This sends a SIGINT to the child. It does not require + the SIGINT to be the first character on a line. ''' + + n, byte = self.ptyproc.sendintr() + self._log_control(byte) + + @property + def flag_eof(self): + return self.ptyproc.flag_eof + + @flag_eof.setter + def flag_eof(self, value): + self.ptyproc.flag_eof = value + + def eof(self): + '''This returns True if the EOF exception was ever raised. + ''' + return self.flag_eof + + def terminate(self, force=False): + '''This forces a child process to terminate. It starts nicely with + SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This + returns True if the child was terminated. This returns False if the + child could not be terminated. ''' + + if not self.isalive(): + return True + try: + self.kill(signal.SIGHUP) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGCONT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGINT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + if force: + self.kill(signal.SIGKILL) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + return False + except OSError: + # I think there are kernel timing issues that sometimes cause + # this to happen. I think isalive() reports True, but the + # process is dead to the kernel. + # Make one last attempt to see if the kernel is up to date. + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + + def wait(self): + '''This waits until the child exits. This is a blocking call. This will + not read any data from the child, so this will block forever if the + child has unread output and has terminated. In other words, the child + may have printed output then called exit(), but, the child is + technically still alive until its output is read by the parent. + + This method is non-blocking if :meth:`wait` has already been called + previously or :meth:`isalive` method returns False. It simply returns + the previously determined exit status. + ''' + + ptyproc = self.ptyproc + with _wrap_ptyprocess_err(): + # exception may occur if "Is some other process attempting + # "job control with our child pid?" + exitstatus = ptyproc.wait() + self.status = ptyproc.status + self.exitstatus = ptyproc.exitstatus + self.signalstatus = ptyproc.signalstatus + self.terminated = True + + return exitstatus + + def isalive(self): + '''This tests if the child process is running or not. This is + non-blocking. If the child was terminated then this will read the + exitstatus or signalstatus of the child. This returns True if the child + process appears to be running or False if not. It can take literally + SECONDS for Solaris to return the right status. ''' + + ptyproc = self.ptyproc + with _wrap_ptyprocess_err(): + alive = ptyproc.isalive() + + if not alive: + self.status = ptyproc.status + self.exitstatus = ptyproc.exitstatus + self.signalstatus = ptyproc.signalstatus + self.terminated = True + + return alive + + def kill(self, sig): + + '''This sends the given signal to the child application. In keeping + with UNIX tradition it has a misleading name. It does not necessarily + kill the child unless you send the right signal. ''' + + # Same as os.kill, but the pid is given for you. + if self.isalive(): + os.kill(self.pid, sig) + + def getwinsize(self): + '''This returns the terminal window size of the child tty. The return + value is a tuple of (rows, cols). ''' + return self.ptyproc.getwinsize() + + def setwinsize(self, rows, cols): + '''This sets the terminal window size of the child tty. This will cause + a SIGWINCH signal to be sent to the child. This does not change the + physical window size. It changes the size reported to TTY-aware + applications like vi or curses -- applications that respond to the + SIGWINCH signal. ''' + return self.ptyproc.setwinsize(rows, cols) + + + def interact(self, escape_character=chr(29), + input_filter=None, output_filter=None): + + '''This gives control of the child process to the interactive user (the + human at the keyboard). Keystrokes are sent to the child process, and + the stdout and stderr output of the child process is printed. This + simply echos the child stdout and child stderr to the real stdout and + it echos the real stdin to the child stdin. When the user types the + escape_character this method will return None. The escape_character + will not be transmitted. The default for escape_character is + entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent + escaping, escape_character may be set to None. + + If a logfile is specified, then the data sent and received from the + child process in interact mode is duplicated to the given log. + + You may pass in optional input and output filter functions. These functions should take bytes array and return bytes array too. Even with ``encoding='utf-8'`` support, meth:`interact` will always pass input_filter and output_filter bytes. You may need to wrap your function to decode and encode back to UTF-8. - + The output_filter will be passed all the output from the child process. The input_filter will be passed all the keyboard input from the user. The input_filter is run BEFORE the check for the escape_character. - Note that if you change the window size of the parent the SIGWINCH - signal will not be passed through to the child. If you want the child - window size to change when the parent's window size changes then do - something like the following example:: - - import pexpect, struct, fcntl, termios, signal, sys - def sigwinch_passthrough (sig, data): - s = struct.pack("HHHH", 0, 0, 0, 0) - a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), - termios.TIOCGWINSZ , s)) + Note that if you change the window size of the parent the SIGWINCH + signal will not be passed through to the child. If you want the child + window size to change when the parent's window size changes then do + something like the following example:: + + import pexpect, struct, fcntl, termios, signal, sys + def sigwinch_passthrough (sig, data): + s = struct.pack("HHHH", 0, 0, 0, 0) + a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), + termios.TIOCGWINSZ , s)) if not p.closed: p.setwinsize(a[0],a[1]) # Note this 'p' is global and used in sigwinch_passthrough. - p = pexpect.spawn('/bin/bash') - signal.signal(signal.SIGWINCH, sigwinch_passthrough) - p.interact() - ''' - - # Flush the buffer. - self.write_to_stdout(self.buffer) - self.stdout.flush() + p = pexpect.spawn('/bin/bash') + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + p.interact() + ''' + + # Flush the buffer. + self.write_to_stdout(self.buffer) + self.stdout.flush() self._buffer = self.buffer_type() - mode = tty.tcgetattr(self.STDIN_FILENO) - tty.setraw(self.STDIN_FILENO) - if escape_character is not None and PY3: - escape_character = escape_character.encode('latin-1') - try: - self.__interact_copy(escape_character, input_filter, output_filter) - finally: - tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode) - - def __interact_writen(self, fd, data): - '''This is used by the interact() method. - ''' - - while data != b'' and self.isalive(): - n = os.write(fd, data) - data = data[n:] - - def __interact_read(self, fd): - '''This is used by the interact() method. - ''' - - return os.read(fd, 1000) - + mode = tty.tcgetattr(self.STDIN_FILENO) + tty.setraw(self.STDIN_FILENO) + if escape_character is not None and PY3: + escape_character = escape_character.encode('latin-1') + try: + self.__interact_copy(escape_character, input_filter, output_filter) + finally: + tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode) + + def __interact_writen(self, fd, data): + '''This is used by the interact() method. + ''' + + while data != b'' and self.isalive(): + n = os.write(fd, data) + data = data[n:] + + def __interact_read(self, fd): + '''This is used by the interact() method. + ''' + + return os.read(fd, 1000) + def __interact_copy( self, escape_character=None, input_filter=None, output_filter=None ): - - '''This is used by the interact() method. - ''' - - while self.isalive(): + + '''This is used by the interact() method. + ''' + + while self.isalive(): if self.use_poll: r = poll_ignore_interrupts([self.child_fd, self.STDIN_FILENO]) else: r, w, e = select_ignore_interrupts( [self.child_fd, self.STDIN_FILENO], [], [] ) - if self.child_fd in r: - try: - data = self.__interact_read(self.child_fd) - except OSError as err: - if err.args[0] == errno.EIO: - # Linux-style EOF - break - raise - if data == b'': - # BSD-style EOF - break - if output_filter: - data = output_filter(data) - self._log(data, 'read') - os.write(self.STDOUT_FILENO, data) - if self.STDIN_FILENO in r: - data = self.__interact_read(self.STDIN_FILENO) - if input_filter: - data = input_filter(data) - i = -1 - if escape_character is not None: - i = data.rfind(escape_character) - if i != -1: - data = data[:i] - if data: - self._log(data, 'send') - self.__interact_writen(self.child_fd, data) - break - self._log(data, 'send') - self.__interact_writen(self.child_fd, data) - - -def spawnu(*args, **kwargs): - """Deprecated: pass encoding to spawn() instead.""" - kwargs.setdefault('encoding', 'utf-8') - return spawn(*args, **kwargs) + if self.child_fd in r: + try: + data = self.__interact_read(self.child_fd) + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + break + raise + if data == b'': + # BSD-style EOF + break + if output_filter: + data = output_filter(data) + self._log(data, 'read') + os.write(self.STDOUT_FILENO, data) + if self.STDIN_FILENO in r: + data = self.__interact_read(self.STDIN_FILENO) + if input_filter: + data = input_filter(data) + i = -1 + if escape_character is not None: + i = data.rfind(escape_character) + if i != -1: + data = data[:i] + if data: + self._log(data, 'send') + self.__interact_writen(self.child_fd, data) + break + self._log(data, 'send') + self.__interact_writen(self.child_fd, data) + + +def spawnu(*args, **kwargs): + """Deprecated: pass encoding to spawn() instead.""" + kwargs.setdefault('encoding', 'utf-8') + return spawn(*args, **kwargs) diff --git a/contrib/python/pexpect/pexpect/pxssh.py b/contrib/python/pexpect/pexpect/pxssh.py index 3d53bd97462..00e10aad071 100644 --- a/contrib/python/pexpect/pexpect/pxssh.py +++ b/contrib/python/pexpect/pexpect/pxssh.py @@ -1,38 +1,38 @@ -'''This class extends pexpect.spawn to specialize setting up SSH connections. -This adds methods for login, logout, and expecting the shell prompt. - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn -import time -import os +'''This class extends pexpect.spawn to specialize setting up SSH connections. +This adds methods for login, logout, and expecting the shell prompt. + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn +import time +import os import sys import re - -__all__ = ['ExceptionPxssh', 'pxssh'] - -# Exception classes used by this module. -class ExceptionPxssh(ExceptionPexpect): - '''Raised for pxssh exceptions. - ''' - + +__all__ = ['ExceptionPxssh', 'pxssh'] + +# Exception classes used by this module. +class ExceptionPxssh(ExceptionPexpect): + '''Raised for pxssh exceptions. + ''' + if sys.version_info > (3, 0): from shlex import quote else: @@ -49,240 +49,240 @@ else: # the string $'b is then quoted as '$'"'"'b' return "'" + s.replace("'", "'\"'\"'") + "'" -class pxssh (spawn): - '''This class extends pexpect.spawn to specialize setting up SSH - connections. This adds methods for login, logout, and expecting the shell - prompt. It does various tricky things to handle many situations in the SSH - login process. For example, if the session is your first login, then pxssh - automatically accepts the remote certificate; or if you have public key - authentication setup then pxssh won't wait for the password prompt. - - pxssh uses the shell prompt to synchronize output from the remote host. In - order to make this more robust it sets the shell prompt to something more - unique than just $ or #. This should work on most Borne/Bash or Csh style - shells. - - Example that runs a few commands on a remote server and prints the result:: - +class pxssh (spawn): + '''This class extends pexpect.spawn to specialize setting up SSH + connections. This adds methods for login, logout, and expecting the shell + prompt. It does various tricky things to handle many situations in the SSH + login process. For example, if the session is your first login, then pxssh + automatically accepts the remote certificate; or if you have public key + authentication setup then pxssh won't wait for the password prompt. + + pxssh uses the shell prompt to synchronize output from the remote host. In + order to make this more robust it sets the shell prompt to something more + unique than just $ or #. This should work on most Borne/Bash or Csh style + shells. + + Example that runs a few commands on a remote server and prints the result:: + from pexpect import pxssh - import getpass - try: - s = pxssh.pxssh() - hostname = raw_input('hostname: ') - username = raw_input('username: ') - password = getpass.getpass('password: ') - s.login(hostname, username, password) - s.sendline('uptime') # run a command - s.prompt() # match the prompt - print(s.before) # print everything before the prompt. - s.sendline('ls -l') - s.prompt() - print(s.before) - s.sendline('df') - s.prompt() - print(s.before) - s.logout() - except pxssh.ExceptionPxssh as e: - print("pxssh failed on login.") - print(e) - - Example showing how to specify SSH options:: - + import getpass + try: + s = pxssh.pxssh() + hostname = raw_input('hostname: ') + username = raw_input('username: ') + password = getpass.getpass('password: ') + s.login(hostname, username, password) + s.sendline('uptime') # run a command + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.sendline('ls -l') + s.prompt() + print(s.before) + s.sendline('df') + s.prompt() + print(s.before) + s.logout() + except pxssh.ExceptionPxssh as e: + print("pxssh failed on login.") + print(e) + + Example showing how to specify SSH options:: + from pexpect import pxssh - s = pxssh.pxssh(options={ - "StrictHostKeyChecking": "no", - "UserKnownHostsFile": "/dev/null"}) - ... - - Note that if you have ssh-agent running while doing development with pxssh - then this can lead to a lot of confusion. Many X display managers (xdm, - gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI - dialog box popup asking for a password during development. You should turn - off any key agents during testing. The 'force_password' attribute will turn - off public key authentication. This will only work if the remote SSH server - is configured to allow password logins. Example of using 'force_password' - attribute:: - - s = pxssh.pxssh() - s.force_password = True - hostname = raw_input('hostname: ') - username = raw_input('username: ') - password = getpass.getpass('password: ') - s.login (hostname, username, password) + s = pxssh.pxssh(options={ + "StrictHostKeyChecking": "no", + "UserKnownHostsFile": "/dev/null"}) + ... + + Note that if you have ssh-agent running while doing development with pxssh + then this can lead to a lot of confusion. Many X display managers (xdm, + gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI + dialog box popup asking for a password during development. You should turn + off any key agents during testing. The 'force_password' attribute will turn + off public key authentication. This will only work if the remote SSH server + is configured to allow password logins. Example of using 'force_password' + attribute:: + + s = pxssh.pxssh() + s.force_password = True + hostname = raw_input('hostname: ') + username = raw_input('username: ') + password = getpass.getpass('password: ') + s.login (hostname, username, password) `debug_command_string` is only for the test suite to confirm that the string generated for SSH is correct, using this will not allow you to do anything other than get a string back from `pxssh.pxssh.login()`. - ''' - - def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, - logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True, + ''' + + def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, + logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True, options={}, encoding=None, codec_errors='strict', debug_command_string=False, use_poll=False): - - spawn.__init__(self, None, timeout=timeout, maxread=maxread, - searchwindowsize=searchwindowsize, logfile=logfile, - cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo, + + spawn.__init__(self, None, timeout=timeout, maxread=maxread, + searchwindowsize=searchwindowsize, logfile=logfile, + cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo, encoding=encoding, codec_errors=codec_errors, use_poll=use_poll) - - self.name = '<pxssh>' - - #SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a - #slightly different string than the regular expression to match it. This - #is because when you set the prompt the command will echo back, but we - #don't want to match the echoed command. So if we make the set command - #slightly different than the regex we eliminate the problem. To make the - #set command different we add a backslash in front of $. The $ doesn't - #need to be escaped, but it doesn't hurt and serves to make the set - #prompt command different than the regex. - - # used to match the command-line prompt + + self.name = '<pxssh>' + + #SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a + #slightly different string than the regular expression to match it. This + #is because when you set the prompt the command will echo back, but we + #don't want to match the echoed command. So if we make the set command + #slightly different than the regex we eliminate the problem. To make the + #set command different we add a backslash in front of $. The $ doesn't + #need to be escaped, but it doesn't hurt and serves to make the set + #prompt command different than the regex. + + # used to match the command-line prompt self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] " - self.PROMPT = self.UNIQUE_PROMPT - - # used to set shell command-line prompt to UNIQUE_PROMPT. + self.PROMPT = self.UNIQUE_PROMPT + + # used to set shell command-line prompt to UNIQUE_PROMPT. self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '" self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '" - self.SSH_OPTS = ("-o'RSAAuthentication=no'" - + " -o 'PubkeyAuthentication=no'") -# Disabling host key checking, makes you vulnerable to MITM attacks. -# + " -o 'StrictHostKeyChecking=no'" -# + " -o 'UserKnownHostsFile /dev/null' ") - # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from - # displaying a GUI password dialog. I have not figured out how to - # disable only SSH_ASKPASS without also disabling X11 forwarding. - # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying! - #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" - self.force_password = False + self.SSH_OPTS = ("-o'RSAAuthentication=no'" + + " -o 'PubkeyAuthentication=no'") +# Disabling host key checking, makes you vulnerable to MITM attacks. +# + " -o 'StrictHostKeyChecking=no'" +# + " -o 'UserKnownHostsFile /dev/null' ") + # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from + # displaying a GUI password dialog. I have not figured out how to + # disable only SSH_ASKPASS without also disabling X11 forwarding. + # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying! + #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" + self.force_password = False self.debug_command_string = debug_command_string + + # User defined SSH options, eg, + # ssh.otions = dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null") + self.options = options + + def levenshtein_distance(self, a, b): + '''This calculates the Levenshtein distance between a and b. + ''' + + n, m = len(a), len(b) + if n > m: + a,b = b,a + n,m = m,n + current = range(n+1) + for i in range(1,m+1): + previous, current = current, [i]+[0]*n + for j in range(1,n+1): + add, delete = previous[j]+1, current[j-1]+1 + change = previous[j-1] + if a[j-1] != b[i-1]: + change = change + 1 + current[j] = min(add, delete, change) + return current[n] + + def try_read_prompt(self, timeout_multiplier): + '''This facilitates using communication timeouts to perform + synchronization as quickly as possible, while supporting high latency + connections with a tunable worst case performance. Fast connections + should be read almost immediately. Worst case performance for this + method is timeout_multiplier * 3 seconds. + ''' + + # maximum time allowed to read the first response + first_char_timeout = timeout_multiplier * 0.5 + + # maximum time allowed between subsequent characters + inter_char_timeout = timeout_multiplier * 0.1 + + # maximum time for reading the entire prompt + total_timeout = timeout_multiplier * 3.0 + + prompt = self.string_type() + begin = time.time() + expired = 0.0 + timeout = first_char_timeout + + while expired < total_timeout: + try: + prompt += self.read_nonblocking(size=1, timeout=timeout) + expired = time.time() - begin # updated total time expired + timeout = inter_char_timeout + except TIMEOUT: + break + + return prompt + + def sync_original_prompt (self, sync_multiplier=1.0): + '''This attempts to find the prompt. Basically, press enter and record + the response; press enter again and record the response; if the two + responses are similar then assume we are at the original prompt. + This can be a slow function. Worst case with the default sync_multiplier + can take 12 seconds. Low latency connections are more likely to fail + with a low sync_multiplier. Best case sync time gets worse with a + high sync multiplier (500 ms with default). ''' - # User defined SSH options, eg, - # ssh.otions = dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null") - self.options = options - - def levenshtein_distance(self, a, b): - '''This calculates the Levenshtein distance between a and b. - ''' - - n, m = len(a), len(b) - if n > m: - a,b = b,a - n,m = m,n - current = range(n+1) - for i in range(1,m+1): - previous, current = current, [i]+[0]*n - for j in range(1,n+1): - add, delete = previous[j]+1, current[j-1]+1 - change = previous[j-1] - if a[j-1] != b[i-1]: - change = change + 1 - current[j] = min(add, delete, change) - return current[n] - - def try_read_prompt(self, timeout_multiplier): - '''This facilitates using communication timeouts to perform - synchronization as quickly as possible, while supporting high latency - connections with a tunable worst case performance. Fast connections - should be read almost immediately. Worst case performance for this - method is timeout_multiplier * 3 seconds. - ''' - - # maximum time allowed to read the first response - first_char_timeout = timeout_multiplier * 0.5 - - # maximum time allowed between subsequent characters - inter_char_timeout = timeout_multiplier * 0.1 - - # maximum time for reading the entire prompt - total_timeout = timeout_multiplier * 3.0 - - prompt = self.string_type() - begin = time.time() - expired = 0.0 - timeout = first_char_timeout - - while expired < total_timeout: - try: - prompt += self.read_nonblocking(size=1, timeout=timeout) - expired = time.time() - begin # updated total time expired - timeout = inter_char_timeout - except TIMEOUT: - break - - return prompt - - def sync_original_prompt (self, sync_multiplier=1.0): - '''This attempts to find the prompt. Basically, press enter and record - the response; press enter again and record the response; if the two - responses are similar then assume we are at the original prompt. - This can be a slow function. Worst case with the default sync_multiplier - can take 12 seconds. Low latency connections are more likely to fail - with a low sync_multiplier. Best case sync time gets worse with a - high sync multiplier (500 ms with default). ''' - - # All of these timing pace values are magic. - # I came up with these based on what seemed reliable for - # connecting to a heavily loaded machine I have. - self.sendline() - time.sleep(0.1) - - try: - # Clear the buffer before getting the prompt. - self.try_read_prompt(sync_multiplier) - except TIMEOUT: - pass - - self.sendline() - x = self.try_read_prompt(sync_multiplier) - - self.sendline() - a = self.try_read_prompt(sync_multiplier) - - self.sendline() - b = self.try_read_prompt(sync_multiplier) - - ld = self.levenshtein_distance(a,b) - len_a = len(a) - if len_a == 0: - return False - if float(ld)/len_a < 0.4: - return True - return False - - ### TODO: This is getting messy and I'm pretty sure this isn't perfect. - ### TODO: I need to draw a flow chart for this. + # All of these timing pace values are magic. + # I came up with these based on what seemed reliable for + # connecting to a heavily loaded machine I have. + self.sendline() + time.sleep(0.1) + + try: + # Clear the buffer before getting the prompt. + self.try_read_prompt(sync_multiplier) + except TIMEOUT: + pass + + self.sendline() + x = self.try_read_prompt(sync_multiplier) + + self.sendline() + a = self.try_read_prompt(sync_multiplier) + + self.sendline() + b = self.try_read_prompt(sync_multiplier) + + ld = self.levenshtein_distance(a,b) + len_a = len(a) + if len_a == 0: + return False + if float(ld)/len_a < 0.4: + return True + return False + + ### TODO: This is getting messy and I'm pretty sure this isn't perfect. + ### TODO: I need to draw a flow chart for this. ### TODO: Unit tests for SSH tunnels, remote SSH command exec, disabling original prompt sync def login (self, server, username=None, password='', terminal_type='ansi', - original_prompt=r"[#$]", login_timeout=10, port=None, - auto_prompt_reset=True, ssh_key=None, quiet=True, + original_prompt=r"[#$]", login_timeout=10, port=None, + auto_prompt_reset=True, ssh_key=None, quiet=True, sync_multiplier=1, check_local_ip=True, password_regex=r'(?i)(?:password:)|(?:passphrase for key)', ssh_tunnels={}, spawn_local_ssh=True, sync_original_prompt=True, ssh_config=None, cmd='ssh'): - '''This logs the user into the given server. - + '''This logs the user into the given server. + It uses 'original_prompt' to try to find the prompt right after login. When it finds the prompt it immediately tries to reset the prompt to something more easily matched. The default 'original_prompt' is very optimistic and is easily fooled. It's more reliable to try to match the original - prompt as exactly as possible to prevent false matches by server - strings such as the "Message Of The Day". On many systems you can - disable the MOTD on the remote server by creating a zero-length file - called :file:`~/.hushlogin` on the remote server. If a prompt cannot be found - then this will not necessarily cause the login to fail. In the case of - a timeout when looking for the prompt we assume that the original - prompt was so weird that we could not match it, so we use a few tricks - to guess when we have reached the prompt. Then we hope for the best and - blindly try to reset the prompt to something more unique. If that fails - then login() raises an :class:`ExceptionPxssh` exception. - - In some situations it is not possible or desirable to reset the - original prompt. In this case, pass ``auto_prompt_reset=False`` to - inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh - uses a unique prompt in the :meth:`prompt` method. If the original prompt is - not reset then this will disable the :meth:`prompt` method unless you - manually set the :attr:`PROMPT` attribute. + prompt as exactly as possible to prevent false matches by server + strings such as the "Message Of The Day". On many systems you can + disable the MOTD on the remote server by creating a zero-length file + called :file:`~/.hushlogin` on the remote server. If a prompt cannot be found + then this will not necessarily cause the login to fail. In the case of + a timeout when looking for the prompt we assume that the original + prompt was so weird that we could not match it, so we use a few tricks + to guess when we have reached the prompt. Then we hope for the best and + blindly try to reset the prompt to something more unique. If that fails + then login() raises an :class:`ExceptionPxssh` exception. + + In some situations it is not possible or desirable to reset the + original prompt. In this case, pass ``auto_prompt_reset=False`` to + inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh + uses a unique prompt in the :meth:`prompt` method. If the original prompt is + not reset then this will disable the :meth:`prompt` method unless you + manually set the :attr:`PROMPT` attribute. Set ``password_regex`` if there is a MOTD message with `password` in it. Changing this is like playing in traffic, don't (p)expect it to match straight @@ -306,27 +306,27 @@ class pxssh (spawn): Alter the ``cmd`` to change the ssh client used, or to prepend it with network namespaces. For example ```cmd="ip netns exec vlan2 ssh"``` to execute the ssh in network namespace named ```vlan```. - ''' + ''' session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT] session_init_regex_array = [] session_init_regex_array.extend(session_regex_array) session_init_regex_array.extend(["(?i)connection closed by remote host", EOF]) - - ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in self.options.items()]) - if quiet: - ssh_options = ssh_options + ' -q' - if not check_local_ip: - ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'" - if self.force_password: - ssh_options = ssh_options + ' ' + self.SSH_OPTS + + ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in self.options.items()]) + if quiet: + ssh_options = ssh_options + ' -q' + if not check_local_ip: + ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'" + if self.force_password: + ssh_options = ssh_options + ' ' + self.SSH_OPTS if ssh_config is not None: if spawn_local_ssh and not os.path.isfile(ssh_config): raise ExceptionPxssh('SSH config does not exist or is not a file.') ssh_options = ssh_options + ' -F ' + ssh_config - if port is not None: - ssh_options = ssh_options + ' -p %s'%(str(port)) - if ssh_key is not None: + if port is not None: + ssh_options = ssh_options + ' -p %s'%(str(port)) + if ssh_key is not None: # Allow forwarding our SSH key to the current session if ssh_key==True: ssh_options = ssh_options + ' -A' @@ -395,143 +395,143 @@ class pxssh (spawn): cmd += " %s %s" % (ssh_options, server) if self.debug_command_string: return(cmd) - + # Are we asking for a local ssh command or to spawn one in another session? if spawn_local_ssh: spawn._spawn(self, cmd) else: self.sendline(cmd) - # This does not distinguish between a remote server 'password' prompt - # and a local ssh 'passphrase' prompt (for unlocking a private key). + # This does not distinguish between a remote server 'password' prompt + # and a local ssh 'passphrase' prompt (for unlocking a private key). i = self.expect(session_init_regex_array, timeout=login_timeout) - - # First phase - if i==0: - # New certificate -- always accept it. - # This is what you get if SSH does not have the remote host's - # public key stored in the 'known_hosts' cache. - self.sendline("yes") + + # First phase + if i==0: + # New certificate -- always accept it. + # This is what you get if SSH does not have the remote host's + # public key stored in the 'known_hosts' cache. + self.sendline("yes") i = self.expect(session_regex_array) - if i==2: # password or passphrase - self.sendline(password) + if i==2: # password or passphrase + self.sendline(password) i = self.expect(session_regex_array) - if i==4: - self.sendline(terminal_type) + if i==4: + self.sendline(terminal_type) i = self.expect(session_regex_array) if i==7: self.close() raise ExceptionPxssh('Could not establish connection to host') - - # Second phase - if i==0: - # This is weird. This should not happen twice in a row. - self.close() - raise ExceptionPxssh('Weird error. Got "are you sure" prompt twice.') - elif i==1: # can occur if you have a public key pair set to authenticate. - ### TODO: May NOT be OK if expect() got tricked and matched a false prompt. - pass - elif i==2: # password prompt again - # For incorrect passwords, some ssh servers will - # ask for the password again, others return 'denied' right away. - # If we get the password prompt again then this means - # we didn't get the password right the first time. - self.close() - raise ExceptionPxssh('password refused') - elif i==3: # permission denied -- password was bad. - self.close() - raise ExceptionPxssh('permission denied') - elif i==4: # terminal type again? WTF? - self.close() - raise ExceptionPxssh('Weird error. Got "terminal type" prompt twice.') - elif i==5: # Timeout - #This is tricky... I presume that we are at the command-line prompt. - #It may be that the shell prompt was so weird that we couldn't match - #it. Or it may be that we couldn't log in for some other reason. I - #can't be sure, but it's safe to guess that we did login because if - #I presume wrong and we are not logged in then this should be caught - #later when I try to set the shell prompt. - pass - elif i==6: # Connection closed by remote host - self.close() - raise ExceptionPxssh('connection closed') - else: # Unexpected - self.close() - raise ExceptionPxssh('unexpected login response') + + # Second phase + if i==0: + # This is weird. This should not happen twice in a row. + self.close() + raise ExceptionPxssh('Weird error. Got "are you sure" prompt twice.') + elif i==1: # can occur if you have a public key pair set to authenticate. + ### TODO: May NOT be OK if expect() got tricked and matched a false prompt. + pass + elif i==2: # password prompt again + # For incorrect passwords, some ssh servers will + # ask for the password again, others return 'denied' right away. + # If we get the password prompt again then this means + # we didn't get the password right the first time. + self.close() + raise ExceptionPxssh('password refused') + elif i==3: # permission denied -- password was bad. + self.close() + raise ExceptionPxssh('permission denied') + elif i==4: # terminal type again? WTF? + self.close() + raise ExceptionPxssh('Weird error. Got "terminal type" prompt twice.') + elif i==5: # Timeout + #This is tricky... I presume that we are at the command-line prompt. + #It may be that the shell prompt was so weird that we couldn't match + #it. Or it may be that we couldn't log in for some other reason. I + #can't be sure, but it's safe to guess that we did login because if + #I presume wrong and we are not logged in then this should be caught + #later when I try to set the shell prompt. + pass + elif i==6: # Connection closed by remote host + self.close() + raise ExceptionPxssh('connection closed') + else: # Unexpected + self.close() + raise ExceptionPxssh('unexpected login response') if sync_original_prompt: if not self.sync_original_prompt(sync_multiplier): self.close() raise ExceptionPxssh('could not synchronize with original prompt') - # We appear to be in. - # set shell prompt to something unique. - if auto_prompt_reset: - if not self.set_unique_prompt(): - self.close() - raise ExceptionPxssh('could not set shell prompt ' - '(received: %r, expected: %r).' % ( - self.before, self.PROMPT,)) - return True - - def logout (self): - '''Sends exit to the remote shell. - - If there are stopped jobs then this automatically sends exit twice. - ''' - self.sendline("exit") - index = self.expect([EOF, "(?i)there are stopped jobs"]) - if index==1: - self.sendline("exit") - self.expect(EOF) - self.close() - - def prompt(self, timeout=-1): - '''Match the next shell prompt. - - This is little more than a short-cut to the :meth:`~pexpect.spawn.expect` - method. Note that if you called :meth:`login` with - ``auto_prompt_reset=False``, then before calling :meth:`prompt` you must - set the :attr:`PROMPT` attribute to a regex that it will use for - matching the prompt. - - Calling :meth:`prompt` will erase the contents of the :attr:`before` - attribute even if no prompt is ever matched. If timeout is not given or - it is set to -1 then self.timeout is used. - - :return: True if the shell prompt was matched, False if the timeout was - reached. - ''' - - if timeout == -1: - timeout = self.timeout - i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout) - if i==1: - return False - return True - - def set_unique_prompt(self): - '''This sets the remote prompt to something more unique than ``#`` or ``$``. - This makes it easier for the :meth:`prompt` method to match the shell prompt - unambiguously. This method is called automatically by the :meth:`login` - method, but you may want to call it manually if you somehow reset the - shell prompt. For example, if you 'su' to a different user then you - will need to manually reset the prompt. This sends shell commands to - the remote host to set the prompt, so this assumes the remote host is - ready to receive commands. - - Alternatively, you may use your own prompt pattern. In this case you - should call :meth:`login` with ``auto_prompt_reset=False``; then set the - :attr:`PROMPT` attribute to a regular expression. After that, the - :meth:`prompt` method will try to match your prompt pattern. - ''' - - self.sendline("unset PROMPT_COMMAND") - self.sendline(self.PROMPT_SET_SH) # sh-style - i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) - if i == 0: # csh-style - self.sendline(self.PROMPT_SET_CSH) - i = self.expect([TIMEOUT, self.PROMPT], timeout=10) - if i == 0: - return False - return True - -# vi:ts=4:sw=4:expandtab:ft=python: + # We appear to be in. + # set shell prompt to something unique. + if auto_prompt_reset: + if not self.set_unique_prompt(): + self.close() + raise ExceptionPxssh('could not set shell prompt ' + '(received: %r, expected: %r).' % ( + self.before, self.PROMPT,)) + return True + + def logout (self): + '''Sends exit to the remote shell. + + If there are stopped jobs then this automatically sends exit twice. + ''' + self.sendline("exit") + index = self.expect([EOF, "(?i)there are stopped jobs"]) + if index==1: + self.sendline("exit") + self.expect(EOF) + self.close() + + def prompt(self, timeout=-1): + '''Match the next shell prompt. + + This is little more than a short-cut to the :meth:`~pexpect.spawn.expect` + method. Note that if you called :meth:`login` with + ``auto_prompt_reset=False``, then before calling :meth:`prompt` you must + set the :attr:`PROMPT` attribute to a regex that it will use for + matching the prompt. + + Calling :meth:`prompt` will erase the contents of the :attr:`before` + attribute even if no prompt is ever matched. If timeout is not given or + it is set to -1 then self.timeout is used. + + :return: True if the shell prompt was matched, False if the timeout was + reached. + ''' + + if timeout == -1: + timeout = self.timeout + i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout) + if i==1: + return False + return True + + def set_unique_prompt(self): + '''This sets the remote prompt to something more unique than ``#`` or ``$``. + This makes it easier for the :meth:`prompt` method to match the shell prompt + unambiguously. This method is called automatically by the :meth:`login` + method, but you may want to call it manually if you somehow reset the + shell prompt. For example, if you 'su' to a different user then you + will need to manually reset the prompt. This sends shell commands to + the remote host to set the prompt, so this assumes the remote host is + ready to receive commands. + + Alternatively, you may use your own prompt pattern. In this case you + should call :meth:`login` with ``auto_prompt_reset=False``; then set the + :attr:`PROMPT` attribute to a regular expression. After that, the + :meth:`prompt` method will try to match your prompt pattern. + ''' + + self.sendline("unset PROMPT_COMMAND") + self.sendline(self.PROMPT_SET_SH) # sh-style + i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) + if i == 0: # csh-style + self.sendline(self.PROMPT_SET_CSH) + i = self.expect([TIMEOUT, self.PROMPT], timeout=10) + if i == 0: + return False + return True + +# vi:ts=4:sw=4:expandtab:ft=python: diff --git a/contrib/python/pexpect/pexpect/replwrap.py b/contrib/python/pexpect/pexpect/replwrap.py index c930f1e4fe0..79562de4c12 100644 --- a/contrib/python/pexpect/pexpect/replwrap.py +++ b/contrib/python/pexpect/pexpect/replwrap.py @@ -1,122 +1,122 @@ -"""Generic wrapper for read-eval-print-loops, a.k.a. interactive shells -""" -import os.path -import signal -import sys - -import pexpect - -PY3 = (sys.version_info[0] >= 3) - -if PY3: - basestring = str - -PEXPECT_PROMPT = u'[PEXPECT_PROMPT>' -PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+' - -class REPLWrapper(object): - """Wrapper for a REPL. - - :param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn` - in which a REPL has already been started, or a str command to start a new - REPL process. - :param str orig_prompt: The prompt to expect at first. - :param str prompt_change: A command to change the prompt to something more - unique. If this is ``None``, the prompt will not be changed. This will - be formatted with the new and continuation prompts as positional - parameters, so you can use ``{}`` style formatting to insert them into - the command. - :param str new_prompt: The more unique prompt to expect after the change. - :param str extra_init_cmd: Commands to do extra initialisation, such as - disabling pagers. - """ - def __init__(self, cmd_or_spawn, orig_prompt, prompt_change, - new_prompt=PEXPECT_PROMPT, - continuation_prompt=PEXPECT_CONTINUATION_PROMPT, - extra_init_cmd=None): - if isinstance(cmd_or_spawn, basestring): - self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8') - else: - self.child = cmd_or_spawn - if self.child.echo: - # Existing spawn instance has echo enabled, disable it - # to prevent our input from being repeated to output. - self.child.setecho(False) - self.child.waitnoecho() - - if prompt_change is None: - self.prompt = orig_prompt - else: - self.set_prompt(orig_prompt, - prompt_change.format(new_prompt, continuation_prompt)) - self.prompt = new_prompt - self.continuation_prompt = continuation_prompt - - self._expect_prompt() - - if extra_init_cmd is not None: - self.run_command(extra_init_cmd) - - def set_prompt(self, orig_prompt, prompt_change): - self.child.expect(orig_prompt) - self.child.sendline(prompt_change) - +"""Generic wrapper for read-eval-print-loops, a.k.a. interactive shells +""" +import os.path +import signal +import sys + +import pexpect + +PY3 = (sys.version_info[0] >= 3) + +if PY3: + basestring = str + +PEXPECT_PROMPT = u'[PEXPECT_PROMPT>' +PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+' + +class REPLWrapper(object): + """Wrapper for a REPL. + + :param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn` + in which a REPL has already been started, or a str command to start a new + REPL process. + :param str orig_prompt: The prompt to expect at first. + :param str prompt_change: A command to change the prompt to something more + unique. If this is ``None``, the prompt will not be changed. This will + be formatted with the new and continuation prompts as positional + parameters, so you can use ``{}`` style formatting to insert them into + the command. + :param str new_prompt: The more unique prompt to expect after the change. + :param str extra_init_cmd: Commands to do extra initialisation, such as + disabling pagers. + """ + def __init__(self, cmd_or_spawn, orig_prompt, prompt_change, + new_prompt=PEXPECT_PROMPT, + continuation_prompt=PEXPECT_CONTINUATION_PROMPT, + extra_init_cmd=None): + if isinstance(cmd_or_spawn, basestring): + self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8') + else: + self.child = cmd_or_spawn + if self.child.echo: + # Existing spawn instance has echo enabled, disable it + # to prevent our input from being repeated to output. + self.child.setecho(False) + self.child.waitnoecho() + + if prompt_change is None: + self.prompt = orig_prompt + else: + self.set_prompt(orig_prompt, + prompt_change.format(new_prompt, continuation_prompt)) + self.prompt = new_prompt + self.continuation_prompt = continuation_prompt + + self._expect_prompt() + + if extra_init_cmd is not None: + self.run_command(extra_init_cmd) + + def set_prompt(self, orig_prompt, prompt_change): + self.child.expect(orig_prompt) + self.child.sendline(prompt_change) + def _expect_prompt(self, timeout=-1, async_=False): - return self.child.expect_exact([self.prompt, self.continuation_prompt], + return self.child.expect_exact([self.prompt, self.continuation_prompt], timeout=timeout, async_=async_) - + def run_command(self, command, timeout=-1, async_=False): - """Send a command to the REPL, wait for and return output. - - :param str command: The command to send. Trailing newlines are not needed. - This should be a complete block of input that will trigger execution; - if a continuation prompt is found after sending input, :exc:`ValueError` - will be raised. - :param int timeout: How long to wait for the next prompt. -1 means the - default from the :class:`pexpect.spawn` object (default 30 seconds). - None means to wait indefinitely. + """Send a command to the REPL, wait for and return output. + + :param str command: The command to send. Trailing newlines are not needed. + This should be a complete block of input that will trigger execution; + if a continuation prompt is found after sending input, :exc:`ValueError` + will be raised. + :param int timeout: How long to wait for the next prompt. -1 means the + default from the :class:`pexpect.spawn` object (default 30 seconds). + None means to wait indefinitely. :param bool async_: On Python 3.4, or Python 3.3 with asyncio installed, passing ``async_=True`` will make this return an :mod:`asyncio` Future, which you can yield from to get the same result that this method would normally give directly. - """ - # Split up multiline commands and feed them in bit-by-bit - cmdlines = command.splitlines() - # splitlines ignores trailing newlines - add it back in manually - if command.endswith('\n'): - cmdlines.append('') - if not cmdlines: - raise ValueError("No command was given") - + """ + # Split up multiline commands and feed them in bit-by-bit + cmdlines = command.splitlines() + # splitlines ignores trailing newlines - add it back in manually + if command.endswith('\n'): + cmdlines.append('') + if not cmdlines: + raise ValueError("No command was given") + if async_: from ._async import repl_run_command_async return repl_run_command_async(self, cmdlines, timeout) - res = [] - self.child.sendline(cmdlines[0]) - for line in cmdlines[1:]: - self._expect_prompt(timeout=timeout) - res.append(self.child.before) - self.child.sendline(line) - - # Command was fully submitted, now wait for the next prompt - if self._expect_prompt(timeout=timeout) == 1: - # We got the continuation prompt - command was incomplete - self.child.kill(signal.SIGINT) - self._expect_prompt(timeout=1) - raise ValueError("Continuation prompt found - input was incomplete:\n" - + command) - return u''.join(res + [self.child.before]) - -def python(command="python"): - """Start a Python shell and return a :class:`REPLWrapper` object.""" - return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}") - -def bash(command="bash"): - """Start a bash shell and return a :class:`REPLWrapper` object.""" - bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh') - child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False, - encoding='utf-8') + res = [] + self.child.sendline(cmdlines[0]) + for line in cmdlines[1:]: + self._expect_prompt(timeout=timeout) + res.append(self.child.before) + self.child.sendline(line) + + # Command was fully submitted, now wait for the next prompt + if self._expect_prompt(timeout=timeout) == 1: + # We got the continuation prompt - command was incomplete + self.child.kill(signal.SIGINT) + self._expect_prompt(timeout=1) + raise ValueError("Continuation prompt found - input was incomplete:\n" + + command) + return u''.join(res + [self.child.before]) + +def python(command="python"): + """Start a Python shell and return a :class:`REPLWrapper` object.""" + return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}") + +def bash(command="bash"): + """Start a bash shell and return a :class:`REPLWrapper` object.""" + bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh') + child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False, + encoding='utf-8') # If the user runs 'env', the value of PS1 will be in the output. To avoid # replwrap seeing that as the next prompt, we'll embed the marker characters @@ -127,4 +127,4 @@ def bash(command="bash"): prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2) return REPLWrapper(child, u'\\$', prompt_change, - extra_init_cmd="export PAGER=cat") + extra_init_cmd="export PAGER=cat") diff --git a/contrib/python/pexpect/pexpect/run.py b/contrib/python/pexpect/pexpect/run.py index ff288a12461..ccfd6050d64 100644 --- a/contrib/python/pexpect/pexpect/run.py +++ b/contrib/python/pexpect/pexpect/run.py @@ -1,157 +1,157 @@ -import sys -import types +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 [email protected]:.') + child.expect('(?i)password') + child.sendline(mypassword) + + The previous code can be replace with the following:: + + from pexpect import * + run('scp foo [email protected]:.', 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 [email protected] '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: -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 [email protected]:.') - child.expect('(?i)password') - child.sendline(mypassword) - - The previous code can be replace with the following:: - - from pexpect import * - run('scp foo [email protected]:.', 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 [email protected] '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) + 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) diff --git a/contrib/python/pexpect/pexpect/screen.py b/contrib/python/pexpect/pexpect/screen.py index 79f95c4e542..21055841af3 100644 --- a/contrib/python/pexpect/pexpect/screen.py +++ b/contrib/python/pexpect/pexpect/screen.py @@ -1,431 +1,431 @@ -'''This implements a virtual screen. This is used to support ANSI terminal -emulation. The screen representation and state is implemented in this class. -Most of the methods are inspired by ANSI screen control codes. The -:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI -escape codes. - -PEXPECT LICENSE - - This license is approved by the OSI and FSF as GPL-compatible. - http://opensource.org/licenses/isc-license.txt - - Copyright (c) 2012, Noah Spurrier <[email protected]> - PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY - PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE - COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -''' - -import codecs -import copy -import sys - -import warnings - -warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. " - "We recommend using pyte to emulate a terminal screen: " - "https://pypi.python.org/pypi/pyte"), - stacklevel=2) - -NUL = 0 # Fill character; ignored on input. -ENQ = 5 # Transmit answerback message. -BEL = 7 # Ring the bell. -BS = 8 # Move cursor left. -HT = 9 # Move cursor to next tab stop. -LF = 10 # Line feed. -VT = 11 # Same as LF. -FF = 12 # Same as LF. -CR = 13 # Move cursor to left margin or newline. -SO = 14 # Invoke G1 character set. -SI = 15 # Invoke G0 character set. -XON = 17 # Resume transmission. -XOFF = 19 # Halt transmission. -CAN = 24 # Cancel escape sequence. -SUB = 26 # Same as CAN. -ESC = 27 # Introduce a control sequence. -DEL = 127 # Fill character; ignored on input. -SPACE = u' ' # Space or blank character. - -PY3 = (sys.version_info[0] >= 3) -if PY3: - unicode = str - -def constrain (n, min, max): - - '''This returns a number, n constrained to the min and max bounds. ''' - - if n < min: - return min - if n > max: - return max - return n - -class screen: - '''This object maintains the state of a virtual text screen as a +'''This implements a virtual screen. This is used to support ANSI terminal +emulation. The screen representation and state is implemented in this class. +Most of the methods are inspired by ANSI screen control codes. The +:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI +escape codes. + +PEXPECT LICENSE + + This license is approved by the OSI and FSF as GPL-compatible. + http://opensource.org/licenses/isc-license.txt + + Copyright (c) 2012, Noah Spurrier <[email protected]> + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +''' + +import codecs +import copy +import sys + +import warnings + +warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. " + "We recommend using pyte to emulate a terminal screen: " + "https://pypi.python.org/pypi/pyte"), + stacklevel=2) + +NUL = 0 # Fill character; ignored on input. +ENQ = 5 # Transmit answerback message. +BEL = 7 # Ring the bell. +BS = 8 # Move cursor left. +HT = 9 # Move cursor to next tab stop. +LF = 10 # Line feed. +VT = 11 # Same as LF. +FF = 12 # Same as LF. +CR = 13 # Move cursor to left margin or newline. +SO = 14 # Invoke G1 character set. +SI = 15 # Invoke G0 character set. +XON = 17 # Resume transmission. +XOFF = 19 # Halt transmission. +CAN = 24 # Cancel escape sequence. +SUB = 26 # Same as CAN. +ESC = 27 # Introduce a control sequence. +DEL = 127 # Fill character; ignored on input. +SPACE = u' ' # Space or blank character. + +PY3 = (sys.version_info[0] >= 3) +if PY3: + unicode = str + +def constrain (n, min, max): + + '''This returns a number, n constrained to the min and max bounds. ''' + + if n < min: + return min + if n > max: + return max + return n + +class screen: + '''This object maintains the state of a virtual text screen as a rectangular array. This maintains a virtual cursor position and handles - scrolling as characters are added. This supports most of the methods needed - by an ANSI text screen. Row and column indexes are 1-based (not zero-based, - like arrays). - - Characters are represented internally using unicode. Methods that accept - input characters, when passed 'bytes' (which in Python 2 is equivalent to - 'str'), convert them from the encoding specified in the 'encoding' - parameter to the constructor. Methods that return screen contents return - unicode strings, with the exception of __str__() under Python 2. Passing - ``encoding=None`` limits the API to only accept unicode input, so passing - bytes in will raise :exc:`TypeError`. - ''' - def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'): - '''This initializes a blank screen of the given dimensions.''' - - self.rows = r - self.cols = c - self.encoding = encoding - self.encoding_errors = encoding_errors - if encoding is not None: + scrolling as characters are added. This supports most of the methods needed + by an ANSI text screen. Row and column indexes are 1-based (not zero-based, + like arrays). + + Characters are represented internally using unicode. Methods that accept + input characters, when passed 'bytes' (which in Python 2 is equivalent to + 'str'), convert them from the encoding specified in the 'encoding' + parameter to the constructor. Methods that return screen contents return + unicode strings, with the exception of __str__() under Python 2. Passing + ``encoding=None`` limits the API to only accept unicode input, so passing + bytes in will raise :exc:`TypeError`. + ''' + def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'): + '''This initializes a blank screen of the given dimensions.''' + + self.rows = r + self.cols = c + self.encoding = encoding + self.encoding_errors = encoding_errors + if encoding is not None: self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors) - else: - self.decoder = None - self.cur_r = 1 - self.cur_c = 1 - self.cur_saved_r = 1 - self.cur_saved_c = 1 - self.scroll_row_start = 1 - self.scroll_row_end = self.rows - self.w = [ [SPACE] * self.cols for _ in range(self.rows)] - - def _decode(self, s): - '''This converts from the external coding system (as passed to - the constructor) to the internal one (unicode). ''' - if self.decoder is not None: - return self.decoder.decode(s) - else: - raise TypeError("This screen was constructed with encoding=None, " - "so it does not handle bytes.") - - def _unicode(self): - '''This returns a printable representation of the screen as a unicode - string (which, under Python 3.x, is the same as 'str'). The end of each - screen line is terminated by a newline.''' - - return u'\n'.join ([ u''.join(c) for c in self.w ]) - - if PY3: - __str__ = _unicode - else: - __unicode__ = _unicode - - def __str__(self): - '''This returns a printable representation of the screen. The end of - each screen line is terminated by a newline. ''' - encoding = self.encoding or 'ascii' - return self._unicode().encode(encoding, 'replace') - - def dump (self): - '''This returns a copy of the screen as a unicode string. This is similar to - __str__/__unicode__ except that lines are not terminated with line - feeds.''' - - return u''.join ([ u''.join(c) for c in self.w ]) - - def pretty (self): - '''This returns a copy of the screen as a unicode string with an ASCII - text box around the screen border. This is similar to - __str__/__unicode__ except that it adds a box.''' - - top_bot = u'+' + u'-'*self.cols + u'+\n' - return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot - - def fill (self, ch=SPACE): - - if isinstance(ch, bytes): - ch = self._decode(ch) - - self.fill_region (1,1,self.rows,self.cols, ch) - - def fill_region (self, rs,cs, re,ce, ch=SPACE): - - if isinstance(ch, bytes): - ch = self._decode(ch) - - rs = constrain (rs, 1, self.rows) - re = constrain (re, 1, self.rows) - cs = constrain (cs, 1, self.cols) - ce = constrain (ce, 1, self.cols) - if rs > re: - rs, re = re, rs - if cs > ce: - cs, ce = ce, cs - for r in range (rs, re+1): - for c in range (cs, ce + 1): - self.put_abs (r,c,ch) - - def cr (self): - '''This moves the cursor to the beginning (col 1) of the current row. - ''' - - self.cursor_home (self.cur_r, 1) - - def lf (self): - '''This moves the cursor down with scrolling. - ''' - - old_r = self.cur_r - self.cursor_down() - if old_r == self.cur_r: - self.scroll_up () - self.erase_line() - - def crlf (self): - '''This advances the cursor with CRLF properties. - The cursor will line wrap and the screen may scroll. - ''' - - self.cr () - self.lf () - - def newline (self): - '''This is an alias for crlf(). - ''' - - self.crlf() - - def put_abs (self, r, c, ch): - '''Screen array starts at 1 index.''' - - r = constrain (r, 1, self.rows) - c = constrain (c, 1, self.cols) - if isinstance(ch, bytes): - ch = self._decode(ch)[0] - else: - ch = ch[0] - self.w[r-1][c-1] = ch - - def put (self, ch): - '''This puts a characters at the current cursor position. - ''' - - if isinstance(ch, bytes): - ch = self._decode(ch) - - self.put_abs (self.cur_r, self.cur_c, ch) - - def insert_abs (self, r, c, ch): - '''This inserts a character at (r,c). Everything under - and to the right is shifted right one character. - The last character of the line is lost. - ''' - - if isinstance(ch, bytes): - ch = self._decode(ch) - - r = constrain (r, 1, self.rows) - c = constrain (c, 1, self.cols) - for ci in range (self.cols, c, -1): - self.put_abs (r,ci, self.get_abs(r,ci-1)) - self.put_abs (r,c,ch) - - def insert (self, ch): - - if isinstance(ch, bytes): - ch = self._decode(ch) - - self.insert_abs (self.cur_r, self.cur_c, ch) - - def get_abs (self, r, c): - - r = constrain (r, 1, self.rows) - c = constrain (c, 1, self.cols) - return self.w[r-1][c-1] - - def get (self): - - self.get_abs (self.cur_r, self.cur_c) - - def get_region (self, rs,cs, re,ce): - '''This returns a list of lines representing the region. - ''' - - rs = constrain (rs, 1, self.rows) - re = constrain (re, 1, self.rows) - cs = constrain (cs, 1, self.cols) - ce = constrain (ce, 1, self.cols) - if rs > re: - rs, re = re, rs - if cs > ce: - cs, ce = ce, cs - sc = [] - for r in range (rs, re+1): - line = u'' - for c in range (cs, ce + 1): - ch = self.get_abs (r,c) - line = line + ch - sc.append (line) - return sc - - def cursor_constrain (self): - '''This keeps the cursor within the screen area. - ''' - - self.cur_r = constrain (self.cur_r, 1, self.rows) - self.cur_c = constrain (self.cur_c, 1, self.cols) - - def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H - - self.cur_r = r - self.cur_c = c - self.cursor_constrain () - - def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down) - - self.cur_c = self.cur_c - count - self.cursor_constrain () - - def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back) - - self.cur_r = self.cur_r + count - self.cursor_constrain () - - def cursor_forward (self,count=1): # <ESC>[{COUNT}C - - self.cur_c = self.cur_c + count - self.cursor_constrain () - - def cursor_up (self,count=1): # <ESC>[{COUNT}A - - self.cur_r = self.cur_r - count - self.cursor_constrain () - - def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index) - - old_r = self.cur_r - self.cursor_up() - if old_r == self.cur_r: - self.scroll_up() - - def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f - '''Identical to Cursor Home.''' - - self.cursor_home (r, c) - - def cursor_save (self): # <ESC>[s - '''Save current cursor position.''' - - self.cursor_save_attrs() - - def cursor_unsave (self): # <ESC>[u - '''Restores cursor position after a Save Cursor.''' - - self.cursor_restore_attrs() - - def cursor_save_attrs (self): # <ESC>7 - '''Save current cursor position.''' - - self.cur_saved_r = self.cur_r - self.cur_saved_c = self.cur_c - - def cursor_restore_attrs (self): # <ESC>8 - '''Restores cursor position after a Save Cursor.''' - - self.cursor_home (self.cur_saved_r, self.cur_saved_c) - - def scroll_constrain (self): - '''This keeps the scroll region within the screen region.''' - - if self.scroll_row_start <= 0: - self.scroll_row_start = 1 - if self.scroll_row_end > self.rows: - self.scroll_row_end = self.rows - - def scroll_screen (self): # <ESC>[r - '''Enable scrolling for entire display.''' - - self.scroll_row_start = 1 - self.scroll_row_end = self.rows - - def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r - '''Enable scrolling from row {start} to row {end}.''' - - self.scroll_row_start = rs - self.scroll_row_end = re - self.scroll_constrain() - - def scroll_down (self): # <ESC>D - '''Scroll display down one line.''' - - # Screen is indexed from 1, but arrays are indexed from 0. - s = self.scroll_row_start - 1 - e = self.scroll_row_end - 1 - self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) - - def scroll_up (self): # <ESC>M - '''Scroll display up one line.''' - - # Screen is indexed from 1, but arrays are indexed from 0. - s = self.scroll_row_start - 1 - e = self.scroll_row_end - 1 - self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) - - def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K - '''Erases from the current cursor position to the end of the current - line.''' - - self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) - - def erase_start_of_line (self): # <ESC>[1K - '''Erases from the current cursor position to the start of the current - line.''' - - self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) - - def erase_line (self): # <ESC>[2K - '''Erases the entire current line.''' - - self.fill_region (self.cur_r, 1, self.cur_r, self.cols) - - def erase_down (self): # <ESC>[0J -or- <ESC>[J - '''Erases the screen from the current line down to the bottom of the - screen.''' - - self.erase_end_of_line () - self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) - - def erase_up (self): # <ESC>[1J - '''Erases the screen from the current line up to the top of the - screen.''' - - self.erase_start_of_line () - self.fill_region (self.cur_r-1, 1, 1, self.cols) - - def erase_screen (self): # <ESC>[2J - '''Erases the screen with the background color.''' - - self.fill () - - def set_tab (self): # <ESC>H - '''Sets a tab at the current position.''' - - pass - - def clear_tab (self): # <ESC>[g - '''Clears tab at the current position.''' - - pass - - def clear_all_tabs (self): # <ESC>[3g - '''Clears all tabs.''' - - pass - -# Insert line Esc [ Pn L -# Delete line Esc [ Pn M -# Delete character Esc [ Pn P -# Scrolling region Esc [ Pn(top);Pn(bot) r - + else: + self.decoder = None + self.cur_r = 1 + self.cur_c = 1 + self.cur_saved_r = 1 + self.cur_saved_c = 1 + self.scroll_row_start = 1 + self.scroll_row_end = self.rows + self.w = [ [SPACE] * self.cols for _ in range(self.rows)] + + def _decode(self, s): + '''This converts from the external coding system (as passed to + the constructor) to the internal one (unicode). ''' + if self.decoder is not None: + return self.decoder.decode(s) + else: + raise TypeError("This screen was constructed with encoding=None, " + "so it does not handle bytes.") + + def _unicode(self): + '''This returns a printable representation of the screen as a unicode + string (which, under Python 3.x, is the same as 'str'). The end of each + screen line is terminated by a newline.''' + + return u'\n'.join ([ u''.join(c) for c in self.w ]) + + if PY3: + __str__ = _unicode + else: + __unicode__ = _unicode + + def __str__(self): + '''This returns a printable representation of the screen. The end of + each screen line is terminated by a newline. ''' + encoding = self.encoding or 'ascii' + return self._unicode().encode(encoding, 'replace') + + def dump (self): + '''This returns a copy of the screen as a unicode string. This is similar to + __str__/__unicode__ except that lines are not terminated with line + feeds.''' + + return u''.join ([ u''.join(c) for c in self.w ]) + + def pretty (self): + '''This returns a copy of the screen as a unicode string with an ASCII + text box around the screen border. This is similar to + __str__/__unicode__ except that it adds a box.''' + + top_bot = u'+' + u'-'*self.cols + u'+\n' + return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot + + def fill (self, ch=SPACE): + + if isinstance(ch, bytes): + ch = self._decode(ch) + + self.fill_region (1,1,self.rows,self.cols, ch) + + def fill_region (self, rs,cs, re,ce, ch=SPACE): + + if isinstance(ch, bytes): + ch = self._decode(ch) + + rs = constrain (rs, 1, self.rows) + re = constrain (re, 1, self.rows) + cs = constrain (cs, 1, self.cols) + ce = constrain (ce, 1, self.cols) + if rs > re: + rs, re = re, rs + if cs > ce: + cs, ce = ce, cs + for r in range (rs, re+1): + for c in range (cs, ce + 1): + self.put_abs (r,c,ch) + + def cr (self): + '''This moves the cursor to the beginning (col 1) of the current row. + ''' + + self.cursor_home (self.cur_r, 1) + + def lf (self): + '''This moves the cursor down with scrolling. + ''' + + old_r = self.cur_r + self.cursor_down() + if old_r == self.cur_r: + self.scroll_up () + self.erase_line() + + def crlf (self): + '''This advances the cursor with CRLF properties. + The cursor will line wrap and the screen may scroll. + ''' + + self.cr () + self.lf () + + def newline (self): + '''This is an alias for crlf(). + ''' + + self.crlf() + + def put_abs (self, r, c, ch): + '''Screen array starts at 1 index.''' + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + if isinstance(ch, bytes): + ch = self._decode(ch)[0] + else: + ch = ch[0] + self.w[r-1][c-1] = ch + + def put (self, ch): + '''This puts a characters at the current cursor position. + ''' + + if isinstance(ch, bytes): + ch = self._decode(ch) + + self.put_abs (self.cur_r, self.cur_c, ch) + + def insert_abs (self, r, c, ch): + '''This inserts a character at (r,c). Everything under + and to the right is shifted right one character. + The last character of the line is lost. + ''' + + if isinstance(ch, bytes): + ch = self._decode(ch) + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + for ci in range (self.cols, c, -1): + self.put_abs (r,ci, self.get_abs(r,ci-1)) + self.put_abs (r,c,ch) + + def insert (self, ch): + + if isinstance(ch, bytes): + ch = self._decode(ch) + + self.insert_abs (self.cur_r, self.cur_c, ch) + + def get_abs (self, r, c): + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + return self.w[r-1][c-1] + + def get (self): + + self.get_abs (self.cur_r, self.cur_c) + + def get_region (self, rs,cs, re,ce): + '''This returns a list of lines representing the region. + ''' + + rs = constrain (rs, 1, self.rows) + re = constrain (re, 1, self.rows) + cs = constrain (cs, 1, self.cols) + ce = constrain (ce, 1, self.cols) + if rs > re: + rs, re = re, rs + if cs > ce: + cs, ce = ce, cs + sc = [] + for r in range (rs, re+1): + line = u'' + for c in range (cs, ce + 1): + ch = self.get_abs (r,c) + line = line + ch + sc.append (line) + return sc + + def cursor_constrain (self): + '''This keeps the cursor within the screen area. + ''' + + self.cur_r = constrain (self.cur_r, 1, self.rows) + self.cur_c = constrain (self.cur_c, 1, self.cols) + + def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H + + self.cur_r = r + self.cur_c = c + self.cursor_constrain () + + def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down) + + self.cur_c = self.cur_c - count + self.cursor_constrain () + + def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back) + + self.cur_r = self.cur_r + count + self.cursor_constrain () + + def cursor_forward (self,count=1): # <ESC>[{COUNT}C + + self.cur_c = self.cur_c + count + self.cursor_constrain () + + def cursor_up (self,count=1): # <ESC>[{COUNT}A + + self.cur_r = self.cur_r - count + self.cursor_constrain () + + def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index) + + old_r = self.cur_r + self.cursor_up() + if old_r == self.cur_r: + self.scroll_up() + + def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f + '''Identical to Cursor Home.''' + + self.cursor_home (r, c) + + def cursor_save (self): # <ESC>[s + '''Save current cursor position.''' + + self.cursor_save_attrs() + + def cursor_unsave (self): # <ESC>[u + '''Restores cursor position after a Save Cursor.''' + + self.cursor_restore_attrs() + + def cursor_save_attrs (self): # <ESC>7 + '''Save current cursor position.''' + + self.cur_saved_r = self.cur_r + self.cur_saved_c = self.cur_c + + def cursor_restore_attrs (self): # <ESC>8 + '''Restores cursor position after a Save Cursor.''' + + self.cursor_home (self.cur_saved_r, self.cur_saved_c) + + def scroll_constrain (self): + '''This keeps the scroll region within the screen region.''' + + if self.scroll_row_start <= 0: + self.scroll_row_start = 1 + if self.scroll_row_end > self.rows: + self.scroll_row_end = self.rows + + def scroll_screen (self): # <ESC>[r + '''Enable scrolling for entire display.''' + + self.scroll_row_start = 1 + self.scroll_row_end = self.rows + + def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r + '''Enable scrolling from row {start} to row {end}.''' + + self.scroll_row_start = rs + self.scroll_row_end = re + self.scroll_constrain() + + def scroll_down (self): # <ESC>D + '''Scroll display down one line.''' + + # Screen is indexed from 1, but arrays are indexed from 0. + s = self.scroll_row_start - 1 + e = self.scroll_row_end - 1 + self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) + + def scroll_up (self): # <ESC>M + '''Scroll display up one line.''' + + # Screen is indexed from 1, but arrays are indexed from 0. + s = self.scroll_row_start - 1 + e = self.scroll_row_end - 1 + self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) + + def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K + '''Erases from the current cursor position to the end of the current + line.''' + + self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) + + def erase_start_of_line (self): # <ESC>[1K + '''Erases from the current cursor position to the start of the current + line.''' + + self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) + + def erase_line (self): # <ESC>[2K + '''Erases the entire current line.''' + + self.fill_region (self.cur_r, 1, self.cur_r, self.cols) + + def erase_down (self): # <ESC>[0J -or- <ESC>[J + '''Erases the screen from the current line down to the bottom of the + screen.''' + + self.erase_end_of_line () + self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) + + def erase_up (self): # <ESC>[1J + '''Erases the screen from the current line up to the top of the + screen.''' + + self.erase_start_of_line () + self.fill_region (self.cur_r-1, 1, 1, self.cols) + + def erase_screen (self): # <ESC>[2J + '''Erases the screen with the background color.''' + + self.fill () + + def set_tab (self): # <ESC>H + '''Sets a tab at the current position.''' + + pass + + def clear_tab (self): # <ESC>[g + '''Clears tab at the current position.''' + + pass + + def clear_all_tabs (self): # <ESC>[3g + '''Clears all tabs.''' + + pass + +# Insert line Esc [ Pn L +# Delete line Esc [ Pn M +# Delete character Esc [ Pn P +# Scrolling region Esc [ Pn(top);Pn(bot) r + diff --git a/contrib/python/pexpect/pexpect/spawnbase.py b/contrib/python/pexpect/pexpect/spawnbase.py index 59e905764cd..b33eadd069a 100644 --- a/contrib/python/pexpect/pexpect/spawnbase.py +++ b/contrib/python/pexpect/pexpect/spawnbase.py @@ -1,121 +1,121 @@ from io import StringIO, BytesIO -import codecs -import os -import sys -import re -import errno -from .exceptions import ExceptionPexpect, EOF, TIMEOUT -from .expect import Expecter, searcher_string, searcher_re - -PY3 = (sys.version_info[0] >= 3) -text_type = str if PY3 else unicode - -class _NullCoder(object): - """Pass bytes through unchanged.""" - @staticmethod - def encode(b, final=False): - return b - - @staticmethod - def decode(b, final=False): - return b - -class SpawnBase(object): - """A base class providing the backwards-compatible spawn API for Pexpect. - - This should not be instantiated directly: use :class:`pexpect.spawn` or - :class:`pexpect.fdpexpect.fdspawn`. - """ - encoding = None - pid = None - flag_eof = False - - def __init__(self, timeout=30, maxread=2000, searchwindowsize=None, - logfile=None, encoding=None, codec_errors='strict'): - self.stdin = sys.stdin - self.stdout = sys.stdout - self.stderr = sys.stderr - - self.searcher = None - self.ignorecase = False - self.before = None - self.after = None - self.match = None - self.match_index = None - self.terminated = True - self.exitstatus = None - self.signalstatus = None - # status returned by os.waitpid - self.status = None - # the child file descriptor is initially closed - self.child_fd = -1 - self.timeout = timeout - self.delimiter = EOF - self.logfile = logfile - # input from child (read_nonblocking) - self.logfile_read = None - # output to send (send, sendline) - self.logfile_send = None - # max bytes to read at one time into buffer - self.maxread = maxread - # Data before searchwindowsize point is preserved, but not searched. - self.searchwindowsize = searchwindowsize - # Delay used before sending data to child. Time in seconds. +import codecs +import os +import sys +import re +import errno +from .exceptions import ExceptionPexpect, EOF, TIMEOUT +from .expect import Expecter, searcher_string, searcher_re + +PY3 = (sys.version_info[0] >= 3) +text_type = str if PY3 else unicode + +class _NullCoder(object): + """Pass bytes through unchanged.""" + @staticmethod + def encode(b, final=False): + return b + + @staticmethod + def decode(b, final=False): + return b + +class SpawnBase(object): + """A base class providing the backwards-compatible spawn API for Pexpect. + + This should not be instantiated directly: use :class:`pexpect.spawn` or + :class:`pexpect.fdpexpect.fdspawn`. + """ + encoding = None + pid = None + flag_eof = False + + def __init__(self, timeout=30, maxread=2000, searchwindowsize=None, + logfile=None, encoding=None, codec_errors='strict'): + self.stdin = sys.stdin + self.stdout = sys.stdout + self.stderr = sys.stderr + + self.searcher = None + self.ignorecase = False + self.before = None + self.after = None + self.match = None + self.match_index = None + self.terminated = True + self.exitstatus = None + self.signalstatus = None + # status returned by os.waitpid + self.status = None + # the child file descriptor is initially closed + self.child_fd = -1 + self.timeout = timeout + self.delimiter = EOF + self.logfile = logfile + # input from child (read_nonblocking) + self.logfile_read = None + # output to send (send, sendline) + self.logfile_send = None + # max bytes to read at one time into buffer + self.maxread = maxread + # Data before searchwindowsize point is preserved, but not searched. + self.searchwindowsize = searchwindowsize + # Delay used before sending data to child. Time in seconds. # Set this to None to skip the time.sleep() call completely. - self.delaybeforesend = 0.05 - # Used by close() to give kernel time to update process status. - # Time in seconds. - self.delayafterclose = 0.1 - # Used by terminate() to give kernel time to update process status. - # Time in seconds. - self.delayafterterminate = 0.1 + self.delaybeforesend = 0.05 + # Used by close() to give kernel time to update process status. + # Time in seconds. + self.delayafterclose = 0.1 + # Used by terminate() to give kernel time to update process status. + # Time in seconds. + self.delayafterterminate = 0.1 # Delay in seconds to sleep after each call to read_nonblocking(). # Set this to None to skip the time.sleep() call completely: that # would restore the behavior from pexpect-2.0 (for performance # reasons or because you don't want to release Python's global # interpreter lock). self.delayafterread = 0.0001 - self.softspace = False - self.name = '<' + repr(self) + '>' - self.closed = True - - # Unicode interface - self.encoding = encoding - self.codec_errors = codec_errors - if encoding is None: - # bytes mode (accepts some unicode for backwards compatibility) - self._encoder = self._decoder = _NullCoder() - self.string_type = bytes + self.softspace = False + self.name = '<' + repr(self) + '>' + self.closed = True + + # Unicode interface + self.encoding = encoding + self.codec_errors = codec_errors + if encoding is None: + # bytes mode (accepts some unicode for backwards compatibility) + self._encoder = self._decoder = _NullCoder() + self.string_type = bytes self.buffer_type = BytesIO - self.crlf = b'\r\n' - if PY3: - self.allowed_string_types = (bytes, str) - self.linesep = os.linesep.encode('ascii') - def write_to_stdout(b): - try: - return sys.stdout.buffer.write(b) - except AttributeError: - # If stdout has been replaced, it may not have .buffer - return sys.stdout.write(b.decode('ascii', 'replace')) - self.write_to_stdout = write_to_stdout - else: - self.allowed_string_types = (basestring,) # analysis:ignore - self.linesep = os.linesep - self.write_to_stdout = sys.stdout.write - else: - # unicode mode - self._encoder = codecs.getincrementalencoder(encoding)(codec_errors) - self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors) - self.string_type = text_type + self.crlf = b'\r\n' + if PY3: + self.allowed_string_types = (bytes, str) + self.linesep = os.linesep.encode('ascii') + def write_to_stdout(b): + try: + return sys.stdout.buffer.write(b) + except AttributeError: + # If stdout has been replaced, it may not have .buffer + return sys.stdout.write(b.decode('ascii', 'replace')) + self.write_to_stdout = write_to_stdout + else: + self.allowed_string_types = (basestring,) # analysis:ignore + self.linesep = os.linesep + self.write_to_stdout = sys.stdout.write + else: + # unicode mode + self._encoder = codecs.getincrementalencoder(encoding)(codec_errors) + self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors) + self.string_type = text_type self.buffer_type = StringIO - self.crlf = u'\r\n' - self.allowed_string_types = (text_type, ) - if PY3: - self.linesep = os.linesep - else: - self.linesep = os.linesep.decode('ascii') - # This can handle unicode in both Python 2 and 3 - self.write_to_stdout = sys.stdout.write + self.crlf = u'\r\n' + self.allowed_string_types = (text_type, ) + if PY3: + self.linesep = os.linesep + else: + self.linesep = os.linesep.decode('ascii') + # This can handle unicode in both Python 2 and 3 + self.write_to_stdout = sys.stdout.write # storage for async transport self.async_pw_transport = None # This is the read buffer. See maxread. @@ -123,29 +123,29 @@ class SpawnBase(object): # The buffer may be trimmed for efficiency reasons. This is the # untrimmed buffer, used to create the before attribute. self._before = self.buffer_type() - - def _log(self, s, direction): - if self.logfile is not None: - self.logfile.write(s) - self.logfile.flush() - second_log = self.logfile_send if (direction=='send') else self.logfile_read - if second_log is not None: - second_log.write(s) - second_log.flush() - - # For backwards compatibility, in bytes mode (when encoding is None) - # unicode is accepted for send and expect. Unicode mode is strictly unicode - # only. - def _coerce_expect_string(self, s): - if self.encoding is None and not isinstance(s, bytes): - return s.encode('ascii') - return s - - def _coerce_send_string(self, s): - if self.encoding is None and not isinstance(s, bytes): - return s.encode('utf-8') - return s - + + def _log(self, s, direction): + if self.logfile is not None: + self.logfile.write(s) + self.logfile.flush() + second_log = self.logfile_send if (direction=='send') else self.logfile_read + if second_log is not None: + second_log.write(s) + second_log.flush() + + # For backwards compatibility, in bytes mode (when encoding is None) + # unicode is accepted for send and expect. Unicode mode is strictly unicode + # only. + def _coerce_expect_string(self, s): + if self.encoding is None and not isinstance(s, bytes): + return s.encode('ascii') + return s + + def _coerce_send_string(self, s): + if self.encoding is None and not isinstance(s, bytes): + return s.encode('utf-8') + return s + def _get_buffer(self): return self._buffer.getvalue() @@ -157,369 +157,369 @@ class SpawnBase(object): # to be a string/bytes object) buffer = property(_get_buffer, _set_buffer) - def read_nonblocking(self, size=1, timeout=None): - """This reads data from the file descriptor. - - This is a simple implementation suitable for a regular file. Subclasses using ptys or pipes should override it. - - The timeout parameter is ignored. - """ - - try: - s = os.read(self.child_fd, size) - except OSError as err: - if err.args[0] == errno.EIO: - # Linux-style EOF - self.flag_eof = True - raise EOF('End Of File (EOF). Exception style platform.') - raise - if s == b'': - # BSD-style EOF - self.flag_eof = True - raise EOF('End Of File (EOF). Empty string style platform.') - - s = self._decoder.decode(s, final=False) - self._log(s, 'read') - return s - - def _pattern_type_err(self, pattern): - raise TypeError('got {badtype} ({badobj!r}) as pattern, must be one' - ' of: {goodtypes}, pexpect.EOF, pexpect.TIMEOUT'\ - .format(badtype=type(pattern), - badobj=pattern, - goodtypes=', '.join([str(ast)\ - for ast in self.allowed_string_types]) - ) - ) - - def compile_pattern_list(self, patterns): - '''This compiles a pattern-string or a list of pattern-strings. - Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of - those. Patterns may also be None which results in an empty list (you - might do this if waiting for an EOF or TIMEOUT condition without - expecting any pattern). - - This is used by expect() when calling expect_list(). Thus expect() is - nothing more than:: - - cpl = self.compile_pattern_list(pl) - return self.expect_list(cpl, timeout) - - If you are using expect() within a loop it may be more - efficient to compile the patterns first and then call expect_list(). - This avoid calls in a loop to compile_pattern_list():: - - cpl = self.compile_pattern_list(my_pattern) - while some_condition: - ... - i = self.expect_list(cpl, timeout) - ... - ''' - - if patterns is None: - return [] - if not isinstance(patterns, list): - patterns = [patterns] - - # Allow dot to match \n - compile_flags = re.DOTALL - if self.ignorecase: - compile_flags = compile_flags | re.IGNORECASE - compiled_pattern_list = [] - for idx, p in enumerate(patterns): - if isinstance(p, self.allowed_string_types): - p = self._coerce_expect_string(p) - compiled_pattern_list.append(re.compile(p, compile_flags)) - elif p is EOF: - compiled_pattern_list.append(EOF) - elif p is TIMEOUT: - compiled_pattern_list.append(TIMEOUT) - elif isinstance(p, type(re.compile(''))): - compiled_pattern_list.append(p) - else: - self._pattern_type_err(p) - return compiled_pattern_list - + def read_nonblocking(self, size=1, timeout=None): + """This reads data from the file descriptor. + + This is a simple implementation suitable for a regular file. Subclasses using ptys or pipes should override it. + + The timeout parameter is ignored. + """ + + try: + s = os.read(self.child_fd, size) + except OSError as err: + if err.args[0] == errno.EIO: + # Linux-style EOF + self.flag_eof = True + raise EOF('End Of File (EOF). Exception style platform.') + raise + if s == b'': + # BSD-style EOF + self.flag_eof = True + raise EOF('End Of File (EOF). Empty string style platform.') + + s = self._decoder.decode(s, final=False) + self._log(s, 'read') + return s + + def _pattern_type_err(self, pattern): + raise TypeError('got {badtype} ({badobj!r}) as pattern, must be one' + ' of: {goodtypes}, pexpect.EOF, pexpect.TIMEOUT'\ + .format(badtype=type(pattern), + badobj=pattern, + goodtypes=', '.join([str(ast)\ + for ast in self.allowed_string_types]) + ) + ) + + def compile_pattern_list(self, patterns): + '''This compiles a pattern-string or a list of pattern-strings. + Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of + those. Patterns may also be None which results in an empty list (you + might do this if waiting for an EOF or TIMEOUT condition without + expecting any pattern). + + This is used by expect() when calling expect_list(). Thus expect() is + nothing more than:: + + cpl = self.compile_pattern_list(pl) + return self.expect_list(cpl, timeout) + + If you are using expect() within a loop it may be more + efficient to compile the patterns first and then call expect_list(). + This avoid calls in a loop to compile_pattern_list():: + + cpl = self.compile_pattern_list(my_pattern) + while some_condition: + ... + i = self.expect_list(cpl, timeout) + ... + ''' + + if patterns is None: + return [] + if not isinstance(patterns, list): + patterns = [patterns] + + # Allow dot to match \n + compile_flags = re.DOTALL + if self.ignorecase: + compile_flags = compile_flags | re.IGNORECASE + compiled_pattern_list = [] + for idx, p in enumerate(patterns): + if isinstance(p, self.allowed_string_types): + p = self._coerce_expect_string(p) + compiled_pattern_list.append(re.compile(p, compile_flags)) + elif p is EOF: + compiled_pattern_list.append(EOF) + elif p is TIMEOUT: + compiled_pattern_list.append(TIMEOUT) + elif isinstance(p, type(re.compile(''))): + compiled_pattern_list.append(p) + else: + self._pattern_type_err(p) + return compiled_pattern_list + def expect(self, pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw): - '''This seeks through the stream until a pattern is matched. The - pattern is overloaded and may take several types. The pattern can be a - StringType, EOF, a compiled re, or a list of any of those types. - Strings will be compiled to re types. This returns the index into the - pattern list. If the pattern was not a list this returns index 0 on a - successful match. This may raise exceptions for EOF or TIMEOUT. To - avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern - list. That will cause expect to match an EOF or TIMEOUT condition - instead of raising an exception. - - If you pass a list of patterns and more than one matches, the first - match in the stream is chosen. If more than one pattern matches at that - point, the leftmost in the pattern list is chosen. For example:: - - # the input is 'foobar' - index = p.expect(['bar', 'foo', 'foobar']) - # returns 1('foo') even though 'foobar' is a "better" match - - Please note, however, that buffering can affect this behavior, since - input arrives in unpredictable chunks. For example:: - - # the input is 'foobar' - index = p.expect(['foobar', 'foo']) - # returns 0('foobar') if all input is available at once, + '''This seeks through the stream until a pattern is matched. The + pattern is overloaded and may take several types. The pattern can be a + StringType, EOF, a compiled re, or a list of any of those types. + Strings will be compiled to re types. This returns the index into the + pattern list. If the pattern was not a list this returns index 0 on a + successful match. This may raise exceptions for EOF or TIMEOUT. To + avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern + list. That will cause expect to match an EOF or TIMEOUT condition + instead of raising an exception. + + If you pass a list of patterns and more than one matches, the first + match in the stream is chosen. If more than one pattern matches at that + point, the leftmost in the pattern list is chosen. For example:: + + # the input is 'foobar' + index = p.expect(['bar', 'foo', 'foobar']) + # returns 1('foo') even though 'foobar' is a "better" match + + Please note, however, that buffering can affect this behavior, since + input arrives in unpredictable chunks. For example:: + + # the input is 'foobar' + index = p.expect(['foobar', 'foo']) + # returns 0('foobar') if all input is available at once, # but returns 1('foo') if parts of the final 'bar' arrive late - - When a match is found for the given pattern, the class instance - attribute *match* becomes an re.MatchObject result. Should an EOF - or TIMEOUT pattern match, then the match attribute will be an instance - of that exception class. The pairing before and after class - instance attributes are views of the data preceding and following - the matching pattern. On general exception, class attribute - *before* is all data received up to the exception, while *match* and - *after* attributes are value None. - - When the keyword argument timeout is -1 (default), then TIMEOUT will - raise after the default value specified by the class timeout - attribute. When None, TIMEOUT will not be raised and may block - indefinitely until match. - - When the keyword argument searchwindowsize is -1 (default), then the - value specified by the class maxread attribute is used. - - A list entry may be EOF or TIMEOUT instead of a string. This will - catch these exceptions and return the index of the list entry instead - of raising the exception. The attribute 'after' will be set to the - exception type. The attribute 'match' will be None. This allows you to - write code like this:: - - index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT]) - if index == 0: - do_something() - elif index == 1: - do_something_else() - elif index == 2: - do_some_other_thing() - elif index == 3: - do_something_completely_different() - - instead of code like this:: - - try: - index = p.expect(['good', 'bad']) - if index == 0: - do_something() - elif index == 1: - do_something_else() - except EOF: - do_some_other_thing() - except TIMEOUT: - do_something_completely_different() - - These two forms are equivalent. It all depends on what you want. You - can also just expect the EOF if you are waiting for all output of a - child to finish. For example:: - - p = pexpect.spawn('/bin/ls') - p.expect(pexpect.EOF) - print p.before - - If you are trying to optimize for speed then see expect_list(). - - On Python 3.4, or Python 3.3 with asyncio installed, passing + + When a match is found for the given pattern, the class instance + attribute *match* becomes an re.MatchObject result. Should an EOF + or TIMEOUT pattern match, then the match attribute will be an instance + of that exception class. The pairing before and after class + instance attributes are views of the data preceding and following + the matching pattern. On general exception, class attribute + *before* is all data received up to the exception, while *match* and + *after* attributes are value None. + + When the keyword argument timeout is -1 (default), then TIMEOUT will + raise after the default value specified by the class timeout + attribute. When None, TIMEOUT will not be raised and may block + indefinitely until match. + + When the keyword argument searchwindowsize is -1 (default), then the + value specified by the class maxread attribute is used. + + A list entry may be EOF or TIMEOUT instead of a string. This will + catch these exceptions and return the index of the list entry instead + of raising the exception. The attribute 'after' will be set to the + exception type. The attribute 'match' will be None. This allows you to + write code like this:: + + index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT]) + if index == 0: + do_something() + elif index == 1: + do_something_else() + elif index == 2: + do_some_other_thing() + elif index == 3: + do_something_completely_different() + + instead of code like this:: + + try: + index = p.expect(['good', 'bad']) + if index == 0: + do_something() + elif index == 1: + do_something_else() + except EOF: + do_some_other_thing() + except TIMEOUT: + do_something_completely_different() + + These two forms are equivalent. It all depends on what you want. You + can also just expect the EOF if you are waiting for all output of a + child to finish. For example:: + + p = pexpect.spawn('/bin/ls') + p.expect(pexpect.EOF) + print p.before + + If you are trying to optimize for speed then see expect_list(). + + On Python 3.4, or Python 3.3 with asyncio installed, passing ``async_=True`` will make this return an :mod:`asyncio` coroutine, - which you can yield from to get the same result that this method would - normally give directly. So, inside a coroutine, you can replace this code:: - - index = p.expect(patterns) - - With this non-blocking form:: - + which you can yield from to get the same result that this method would + normally give directly. So, inside a coroutine, you can replace this code:: + + index = p.expect(patterns) + + With this non-blocking form:: + index = yield from p.expect(patterns, async_=True) - ''' + ''' if 'async' in kw: async_ = kw.pop('async') if kw: raise TypeError("Unknown keyword arguments: {}".format(kw)) - - compiled_pattern_list = self.compile_pattern_list(pattern) - return self.expect_list(compiled_pattern_list, + + compiled_pattern_list = self.compile_pattern_list(pattern) + return self.expect_list(compiled_pattern_list, timeout, searchwindowsize, async_) - - def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1, + + def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1, async_=False, **kw): - '''This takes a list of compiled regular expressions and returns the - index into the pattern_list that matched the child output. The list may - also contain EOF or TIMEOUT(which are not compiled regular - expressions). This method is similar to the expect() method except that - expect_list() does not recompile the pattern list on every call. This - may help if you are trying to optimize for speed, otherwise just use - the expect() method. This is called by expect(). - - + '''This takes a list of compiled regular expressions and returns the + index into the pattern_list that matched the child output. The list may + also contain EOF or TIMEOUT(which are not compiled regular + expressions). This method is similar to the expect() method except that + expect_list() does not recompile the pattern list on every call. This + may help if you are trying to optimize for speed, otherwise just use + the expect() method. This is called by expect(). + + Like :meth:`expect`, passing ``async_=True`` will make this return an - asyncio coroutine. - ''' - if timeout == -1: - timeout = self.timeout + asyncio coroutine. + ''' + if timeout == -1: + timeout = self.timeout if 'async' in kw: async_ = kw.pop('async') if kw: raise TypeError("Unknown keyword arguments: {}".format(kw)) - - exp = Expecter(self, searcher_re(pattern_list), searchwindowsize) + + exp = Expecter(self, searcher_re(pattern_list), searchwindowsize) if async_: from ._async import expect_async - return expect_async(exp, timeout) - else: - return exp.expect_loop(timeout) - - def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1, + return expect_async(exp, timeout) + else: + return exp.expect_loop(timeout) + + def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1, async_=False, **kw): - - '''This is similar to expect(), but uses plain string matching instead - of compiled regular expressions in 'pattern_list'. The 'pattern_list' - may be a string; a list or other sequence of strings; or TIMEOUT and - EOF. - - This call might be faster than expect() for two reasons: string - searching is faster than RE matching and it is possible to limit the - search to just the end of the input buffer. - - This method is also useful when you don't want to have to worry about - escaping regular expression characters that you want to match. - + + '''This is similar to expect(), but uses plain string matching instead + of compiled regular expressions in 'pattern_list'. The 'pattern_list' + may be a string; a list or other sequence of strings; or TIMEOUT and + EOF. + + This call might be faster than expect() for two reasons: string + searching is faster than RE matching and it is possible to limit the + search to just the end of the input buffer. + + This method is also useful when you don't want to have to worry about + escaping regular expression characters that you want to match. + Like :meth:`expect`, passing ``async_=True`` will make this return an - asyncio coroutine. - ''' - if timeout == -1: - timeout = self.timeout + asyncio coroutine. + ''' + if timeout == -1: + timeout = self.timeout if 'async' in kw: async_ = kw.pop('async') if kw: raise TypeError("Unknown keyword arguments: {}".format(kw)) - - if (isinstance(pattern_list, self.allowed_string_types) or - pattern_list in (TIMEOUT, EOF)): - pattern_list = [pattern_list] - - def prepare_pattern(pattern): - if pattern in (TIMEOUT, EOF): - return pattern - if isinstance(pattern, self.allowed_string_types): - return self._coerce_expect_string(pattern) - self._pattern_type_err(pattern) - - try: - pattern_list = iter(pattern_list) - except TypeError: - self._pattern_type_err(pattern_list) - pattern_list = [prepare_pattern(p) for p in pattern_list] - - exp = Expecter(self, searcher_string(pattern_list), searchwindowsize) + + if (isinstance(pattern_list, self.allowed_string_types) or + pattern_list in (TIMEOUT, EOF)): + pattern_list = [pattern_list] + + def prepare_pattern(pattern): + if pattern in (TIMEOUT, EOF): + return pattern + if isinstance(pattern, self.allowed_string_types): + return self._coerce_expect_string(pattern) + self._pattern_type_err(pattern) + + try: + pattern_list = iter(pattern_list) + except TypeError: + self._pattern_type_err(pattern_list) + pattern_list = [prepare_pattern(p) for p in pattern_list] + + exp = Expecter(self, searcher_string(pattern_list), searchwindowsize) if async_: from ._async import expect_async - return expect_async(exp, timeout) - else: - return exp.expect_loop(timeout) - - def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1): - '''This is the common loop used inside expect. The 'searcher' should be - an instance of searcher_re or searcher_string, which describes how and - what to search for in the input. - - See expect() for other arguments, return value and exceptions. ''' - - exp = Expecter(self, searcher, searchwindowsize) - return exp.expect_loop(timeout) - - def read(self, size=-1): - '''This reads at most "size" bytes from the file (less if the read hits - EOF before obtaining size bytes). If the size argument is negative or - omitted, read all data until EOF is reached. The bytes are returned as - a string object. An empty string is returned when EOF is encountered - immediately. ''' - - if size == 0: - return self.string_type() - if size < 0: - # delimiter default is EOF - self.expect(self.delimiter) - return self.before - - # I could have done this more directly by not using expect(), but - # I deliberately decided to couple read() to expect() so that + return expect_async(exp, timeout) + else: + return exp.expect_loop(timeout) + + def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1): + '''This is the common loop used inside expect. The 'searcher' should be + an instance of searcher_re or searcher_string, which describes how and + what to search for in the input. + + See expect() for other arguments, return value and exceptions. ''' + + exp = Expecter(self, searcher, searchwindowsize) + return exp.expect_loop(timeout) + + def read(self, size=-1): + '''This reads at most "size" bytes from the file (less if the read hits + EOF before obtaining size bytes). If the size argument is negative or + omitted, read all data until EOF is reached. The bytes are returned as + a string object. An empty string is returned when EOF is encountered + immediately. ''' + + if size == 0: + return self.string_type() + if size < 0: + # delimiter default is EOF + self.expect(self.delimiter) + return self.before + + # I could have done this more directly by not using expect(), but + # I deliberately decided to couple read() to expect() so that # I would catch any bugs early and ensure consistent behavior. - # It's a little less efficient, but there is less for me to - # worry about if I have to later modify read() or expect(). - # Note, it's OK if size==-1 in the regex. That just means it - # will never match anything in which case we stop only on EOF. - cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL) - # delimiter default is EOF - index = self.expect([cre, self.delimiter]) - if index == 0: - ### FIXME self.before should be ''. Should I assert this? - return self.after - return self.before - - def readline(self, size=-1): - '''This reads and returns one entire line. The newline at the end of - line is returned as part of the string, unless the file ends without a - newline. An empty string is returned if EOF is encountered immediately. - This looks for a newline as a CR/LF pair (\\r\\n) even on UNIX because - this is what the pseudotty device returns. So contrary to what you may - expect you will receive newlines as \\r\\n. - - If the size argument is 0 then an empty string is returned. In all - other cases the size argument is ignored, which is not standard - behavior for a file-like object. ''' - - if size == 0: - return self.string_type() - # delimiter default is EOF - index = self.expect([self.crlf, self.delimiter]) - if index == 0: - return self.before + self.crlf - else: - return self.before - - def __iter__(self): - '''This is to support iterators over a file-like object. - ''' - return iter(self.readline, self.string_type()) - - def readlines(self, sizehint=-1): - '''This reads until EOF using readline() and returns a list containing - the lines thus read. The optional 'sizehint' argument is ignored. - Remember, because this reads until EOF that means the child - process should have closed its stdout. If you run this method on - a child that is still running with its stdout open then this - method will block until it timesout.''' - - lines = [] - while True: - line = self.readline() - if not line: - break - lines.append(line) - return lines - - def fileno(self): - '''Expose file descriptor for a file-like interface - ''' - return self.child_fd - - def flush(self): - '''This does nothing. It is here to support the interface for a - File-like object. ''' - pass - - def isatty(self): - """Overridden in subclass using tty""" - return False - - # For 'with spawn(...) as child:' - def __enter__(self): - return self + # It's a little less efficient, but there is less for me to + # worry about if I have to later modify read() or expect(). + # Note, it's OK if size==-1 in the regex. That just means it + # will never match anything in which case we stop only on EOF. + cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL) + # delimiter default is EOF + index = self.expect([cre, self.delimiter]) + if index == 0: + ### FIXME self.before should be ''. Should I assert this? + return self.after + return self.before + + def readline(self, size=-1): + '''This reads and returns one entire line. The newline at the end of + line is returned as part of the string, unless the file ends without a + newline. An empty string is returned if EOF is encountered immediately. + This looks for a newline as a CR/LF pair (\\r\\n) even on UNIX because + this is what the pseudotty device returns. So contrary to what you may + expect you will receive newlines as \\r\\n. + + If the size argument is 0 then an empty string is returned. In all + other cases the size argument is ignored, which is not standard + behavior for a file-like object. ''' + + if size == 0: + return self.string_type() + # delimiter default is EOF + index = self.expect([self.crlf, self.delimiter]) + if index == 0: + return self.before + self.crlf + else: + return self.before + + def __iter__(self): + '''This is to support iterators over a file-like object. + ''' + return iter(self.readline, self.string_type()) + + def readlines(self, sizehint=-1): + '''This reads until EOF using readline() and returns a list containing + the lines thus read. The optional 'sizehint' argument is ignored. + Remember, because this reads until EOF that means the child + process should have closed its stdout. If you run this method on + a child that is still running with its stdout open then this + method will block until it timesout.''' + + lines = [] + while True: + line = self.readline() + if not line: + break + lines.append(line) + return lines + + def fileno(self): + '''Expose file descriptor for a file-like interface + ''' + return self.child_fd + + def flush(self): + '''This does nothing. It is here to support the interface for a + File-like object. ''' + pass + + def isatty(self): + """Overridden in subclass using tty""" + return False + + # For 'with spawn(...) as child:' + def __enter__(self): + return self - def __exit__(self, etype, evalue, tb): - # We rely on subclasses to implement close(). If they don't, it's not - # clear what a context manager should do. - self.close() + def __exit__(self, etype, evalue, tb): + # We rely on subclasses to implement close(). If they don't, it's not + # clear what a context manager should do. + self.close() diff --git a/contrib/python/pexpect/pexpect/utils.py b/contrib/python/pexpect/pexpect/utils.py index f7745196090..13a7849a0e1 100644 --- a/contrib/python/pexpect/pexpect/utils.py +++ b/contrib/python/pexpect/pexpect/utils.py @@ -1,130 +1,130 @@ -import os -import sys -import stat +import os +import sys +import stat import select import time import errno - + try: InterruptedError except NameError: # Alias Python2 exception to Python3 InterruptedError = select.error - + if sys.version_info[0] >= 3: string_types = (str,) else: string_types = (unicode, str) -def is_executable_file(path): - """Checks that path is an executable regular file, or a symlink towards one. - - This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``. - """ - # follow symlinks, - fpath = os.path.realpath(path) - - if not os.path.isfile(fpath): - # non-files (directories, fifo, etc.) - return False - - mode = os.stat(fpath).st_mode - - if (sys.platform.startswith('sunos') - and os.getuid() == 0): - # When root on Solaris, os.X_OK is True for *all* files, irregardless - # of their executability -- instead, any permission bit of any user, - # group, or other is fine enough. - # - # (This may be true for other "Unix98" OS's such as HP-UX and AIX) - return bool(mode & (stat.S_IXUSR | - stat.S_IXGRP | - stat.S_IXOTH)) - - return os.access(fpath, os.X_OK) - - +def is_executable_file(path): + """Checks that path is an executable regular file, or a symlink towards one. + + This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``. + """ + # follow symlinks, + fpath = os.path.realpath(path) + + if not os.path.isfile(fpath): + # non-files (directories, fifo, etc.) + return False + + mode = os.stat(fpath).st_mode + + if (sys.platform.startswith('sunos') + and os.getuid() == 0): + # When root on Solaris, os.X_OK is True for *all* files, irregardless + # of their executability -- instead, any permission bit of any user, + # group, or other is fine enough. + # + # (This may be true for other "Unix98" OS's such as HP-UX and AIX) + return bool(mode & (stat.S_IXUSR | + stat.S_IXGRP | + stat.S_IXOTH)) + + return os.access(fpath, os.X_OK) + + def which(filename, env=None): - '''This takes a given filename; tries to find it in the environment path; - then checks if it is executable. This returns the full path to the filename - if found and executable. Otherwise this returns None.''' - - # Special case where filename contains an explicit path. - if os.path.dirname(filename) != '' and is_executable_file(filename): - return filename + '''This takes a given filename; tries to find it in the environment path; + then checks if it is executable. This returns the full path to the filename + if found and executable. Otherwise this returns None.''' + + # Special case where filename contains an explicit path. + if os.path.dirname(filename) != '' and is_executable_file(filename): + return filename if env is None: env = os.environ p = env.get('PATH') if not p: - p = os.defpath - pathlist = p.split(os.pathsep) - for path in pathlist: - ff = os.path.join(path, filename) - if is_executable_file(ff): - return ff - return None - - -def split_command_line(command_line): - - '''This splits a command line into a list of arguments. It splits arguments - on spaces, but handles embedded quotes, doublequotes, and escaped - characters. It's impossible to do this with a regular expression, so I - wrote a little state machine to parse the command line. ''' - - arg_list = [] - arg = '' - - # Constants to name the states we can be in. - state_basic = 0 - state_esc = 1 - state_singlequote = 2 - state_doublequote = 3 - # The state when consuming whitespace between commands. - state_whitespace = 4 - state = state_basic - - for c in command_line: - if state == state_basic or state == state_whitespace: - if c == '\\': - # Escape the next character - state = state_esc - elif c == r"'": - # Handle single quote - state = state_singlequote - elif c == r'"': - # Handle double quote - state = state_doublequote - elif c.isspace(): - # Add arg to arg_list if we aren't in the middle of whitespace. - if state == state_whitespace: - # Do nothing. - None - else: - arg_list.append(arg) - arg = '' - state = state_whitespace - else: - arg = arg + c - state = state_basic - elif state == state_esc: - arg = arg + c - state = state_basic - elif state == state_singlequote: - if c == r"'": - state = state_basic - else: - arg = arg + c - elif state == state_doublequote: - if c == r'"': - state = state_basic - else: - arg = arg + c - - if arg != '': - arg_list.append(arg) - return arg_list + p = os.defpath + pathlist = p.split(os.pathsep) + for path in pathlist: + ff = os.path.join(path, filename) + if is_executable_file(ff): + return ff + return None + + +def split_command_line(command_line): + + '''This splits a command line into a list of arguments. It splits arguments + on spaces, but handles embedded quotes, doublequotes, and escaped + characters. It's impossible to do this with a regular expression, so I + wrote a little state machine to parse the command line. ''' + + arg_list = [] + arg = '' + + # Constants to name the states we can be in. + state_basic = 0 + state_esc = 1 + state_singlequote = 2 + state_doublequote = 3 + # The state when consuming whitespace between commands. + state_whitespace = 4 + state = state_basic + + for c in command_line: + if state == state_basic or state == state_whitespace: + if c == '\\': + # Escape the next character + state = state_esc + elif c == r"'": + # Handle single quote + state = state_singlequote + elif c == r'"': + # Handle double quote + state = state_doublequote + elif c.isspace(): + # Add arg to arg_list if we aren't in the middle of whitespace. + if state == state_whitespace: + # Do nothing. + None + else: + arg_list.append(arg) + arg = '' + state = state_whitespace + else: + arg = arg + c + state = state_basic + elif state == state_esc: + arg = arg + c + state = state_basic + elif state == state_singlequote: + if c == r"'": + state = state_basic + else: + arg = arg + c + elif state == state_doublequote: + if c == r'"': + state = state_basic + else: + arg = arg + c + + if arg != '': + arg_list.append(arg) + return arg_list def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None): diff --git a/contrib/python/pexpect/ya.make b/contrib/python/pexpect/ya.make index a5bb92fcacb..2fdba71fba3 100644 --- a/contrib/python/pexpect/ya.make +++ b/contrib/python/pexpect/ya.make @@ -1,35 +1,35 @@ PY23_LIBRARY() - + LICENSE(ISC) OWNER(g:python-contrib borman) VERSION(4.8.0) -PEERDIR( +PEERDIR( contrib/python/ptyprocess -) - +) + NO_LINT() -PY_SRCS( - TOP_LEVEL - pexpect/ANSI.py - pexpect/FSM.py - pexpect/__init__.py - pexpect/exceptions.py - pexpect/expect.py - pexpect/fdpexpect.py - pexpect/popen_spawn.py - pexpect/pty_spawn.py - pexpect/pxssh.py - pexpect/replwrap.py - pexpect/run.py - pexpect/screen.py - pexpect/spawnbase.py - pexpect/utils.py -) - +PY_SRCS( + TOP_LEVEL + pexpect/ANSI.py + pexpect/FSM.py + pexpect/__init__.py + pexpect/exceptions.py + pexpect/expect.py + pexpect/fdpexpect.py + pexpect/popen_spawn.py + pexpect/pty_spawn.py + pexpect/pxssh.py + pexpect/replwrap.py + pexpect/run.py + pexpect/screen.py + pexpect/spawnbase.py + pexpect/utils.py +) + IF (PYTHON3) PY_SRCS( TOP_LEVEL @@ -43,4 +43,4 @@ RESOURCE_FILES( .dist-info/top_level.txt ) -END() +END() |
