From cd42b53c196672694396e695ae17fd94ba7d58b4 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 3 May 2010 22:13:51 +0800 Subject: [PATCH] implemented working externAgent wrapper --- .gitignore | 4 + src/SampleAgents.pyc | Bin 4730 -> 0 bytes src/djaAgents.py | 77 - .../c-link-lib.xcodeproj/project.pbxproj | 2 + src/link/__init__.py | 0 src/link/cangel.py | 8 + src/link/externAgent.py | 56 + src/link/pexpect/ANSI.py | 334 +++ src/link/pexpect/FSM.py | 331 +++ src/link/pexpect/INSTALL | 31 + src/link/pexpect/LICENSE | 21 + src/link/pexpect/PKG-INFO | 10 + src/link/pexpect/README | 45 + src/link/pexpect/__init__.py | 0 src/link/pexpect/doc/ANSI.html | 316 +++ src/link/pexpect/doc/FSM.html | 272 +++ src/link/pexpect/doc/clean.css | 103 + src/link/pexpect/doc/email.png | Bin 0 -> 322 bytes src/link/pexpect/doc/examples.html | 135 ++ src/link/pexpect/doc/fdpexpect.html | 402 ++++ src/link/pexpect/doc/index.html | 868 ++++++++ src/link/pexpect/doc/index.template.html | 868 ++++++++ src/link/pexpect/doc/pexpect.html | 874 ++++++++ src/link/pexpect/doc/pxssh.html | 589 ++++++ src/link/pexpect/doc/screen.html | 180 ++ src/link/pexpect/examples/README | 72 + src/link/pexpect/examples/astat.py | 74 + src/link/pexpect/examples/bd_client.py | 38 + src/link/pexpect/examples/bd_serv.py | 316 +++ src/link/pexpect/examples/cgishell.cgi | 762 +++++++ src/link/pexpect/examples/chess.py | 131 ++ src/link/pexpect/examples/chess2.py | 131 ++ src/link/pexpect/examples/chess3.py | 138 ++ src/link/pexpect/examples/df.py | 34 + src/link/pexpect/examples/fix_cvs_files.py | 95 + src/link/pexpect/examples/ftp.py | 47 + src/link/pexpect/examples/hive.py | 437 ++++ src/link/pexpect/examples/log_69.80.212.10 | 16 + src/link/pexpect/examples/log_69.80.212.11 | 16 + src/link/pexpect/examples/monitor.py | 208 ++ src/link/pexpect/examples/passmass.py | 90 + src/link/pexpect/examples/python.py | 22 + src/link/pexpect/examples/rippy.py | 984 +++++++++ src/link/pexpect/examples/script.py | 103 + src/link/pexpect/examples/ssh_session.py | 94 + src/link/pexpect/examples/ssh_tunnel.py | 72 + src/link/pexpect/examples/sshls.py | 56 + src/link/pexpect/examples/table_test.html | 106 + src/link/pexpect/examples/topip.py | 267 +++ src/link/pexpect/examples/uptime.py | 57 + src/link/pexpect/fdpexpect.py | 82 + src/link/pexpect/pexpect.py | 1845 +++++++++++++++++ src/link/pexpect/pxssh.py | 307 +++ src/link/pexpect/screen.py | 380 ++++ src/link/pexpect/setup.py | 39 + src/rpsconst.pyc | Bin 719 -> 0 bytes src/simulate.py | 5 +- src/uccProgComp.pyc | Bin 9847 -> 0 bytes 58 files changed, 12471 insertions(+), 79 deletions(-) delete mode 100644 src/SampleAgents.pyc delete mode 100644 src/djaAgents.py create mode 100644 src/link/__init__.py create mode 100644 src/link/cangel.py create mode 100644 src/link/externAgent.py create mode 100644 src/link/pexpect/ANSI.py create mode 100644 src/link/pexpect/FSM.py create mode 100644 src/link/pexpect/INSTALL create mode 100644 src/link/pexpect/LICENSE create mode 100644 src/link/pexpect/PKG-INFO create mode 100644 src/link/pexpect/README create mode 100644 src/link/pexpect/__init__.py create mode 100644 src/link/pexpect/doc/ANSI.html create mode 100644 src/link/pexpect/doc/FSM.html create mode 100644 src/link/pexpect/doc/clean.css create mode 100644 src/link/pexpect/doc/email.png create mode 100644 src/link/pexpect/doc/examples.html create mode 100644 src/link/pexpect/doc/fdpexpect.html create mode 100644 src/link/pexpect/doc/index.html create mode 100644 src/link/pexpect/doc/index.template.html create mode 100644 src/link/pexpect/doc/pexpect.html create mode 100644 src/link/pexpect/doc/pxssh.html create mode 100644 src/link/pexpect/doc/screen.html create mode 100644 src/link/pexpect/examples/README create mode 100755 src/link/pexpect/examples/astat.py create mode 100755 src/link/pexpect/examples/bd_client.py create mode 100755 src/link/pexpect/examples/bd_serv.py create mode 100755 src/link/pexpect/examples/cgishell.cgi create mode 100755 src/link/pexpect/examples/chess.py create mode 100755 src/link/pexpect/examples/chess2.py create mode 100755 src/link/pexpect/examples/chess3.py create mode 100755 src/link/pexpect/examples/df.py create mode 100755 src/link/pexpect/examples/fix_cvs_files.py create mode 100755 src/link/pexpect/examples/ftp.py create mode 100755 src/link/pexpect/examples/hive.py create mode 100644 src/link/pexpect/examples/log_69.80.212.10 create mode 100644 src/link/pexpect/examples/log_69.80.212.11 create mode 100755 src/link/pexpect/examples/monitor.py create mode 100755 src/link/pexpect/examples/passmass.py create mode 100755 src/link/pexpect/examples/python.py create mode 100755 src/link/pexpect/examples/rippy.py create mode 100755 src/link/pexpect/examples/script.py create mode 100755 src/link/pexpect/examples/ssh_session.py create mode 100755 src/link/pexpect/examples/ssh_tunnel.py create mode 100755 src/link/pexpect/examples/sshls.py create mode 100644 src/link/pexpect/examples/table_test.html create mode 100755 src/link/pexpect/examples/topip.py create mode 100755 src/link/pexpect/examples/uptime.py create mode 100644 src/link/pexpect/fdpexpect.py create mode 100644 src/link/pexpect/pexpect.py create mode 100644 src/link/pexpect/pxssh.py create mode 100644 src/link/pexpect/screen.py create mode 100755 src/link/pexpect/setup.py delete mode 100644 src/rpsconst.pyc delete mode 100644 src/uccProgComp.pyc diff --git a/.gitignore b/.gitignore index 483ef52..047c57f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ *.pbxuser *.mode1v3 +# gcc noise *.o *.a @@ -12,3 +13,6 @@ build/ # osx noise .DS_Store profile + +# python noise +*.pyc diff --git a/src/SampleAgents.pyc b/src/SampleAgents.pyc deleted file mode 100644 index 7b9193275876b926a94297b9e5f99c475cf62267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4730 zcmc&&ZEqVz5S}|bPV6>qDKv-|p+iW>Le#E`kSZuu)1)mGA!;`%5#fWm-t8q<--~;@ zrm~cNO8*i34gL&&0Opyqg-+Bn7Xt@q~kc4wY>W_H%U|FztDzWwJ90gXQk{Ck@( z^9MH}(Jr?Rg)a3SN?h7?DXGzJjgmU;*2%%M8ub@wJFJs-DdPJDdP#Ih^z&|m!p20p zSV1)7cFILiX6kejO_B+tN7KUxs*5UU-AVAH~_8r}CihJ@f~<@V0{3n7lBZ z)=m*yt26J($lDn9wYL)|N$jV_yJ_~*Jn!q#?O_mfe3Nzj;VqfZ?rZPy+M4&U$oC3A zO)>Xco({B)MTt1`u3x)$qtjCQ*)T4&@g9!sK5KWq^L3}y+K2<486A4VEM!&u%=_`d z_Elp?iT0B6**Bii+QT{<57zE(Zr$~wn4iL!P+LDvSn>F8{n5V1O`a!?VK$1oCg#|a(hiN*h*aG<-zRZW*Xl4t$pWqPY2~z`| z8mXy2aI;N7?`dthFQNmLkipA_e)EGg@JzL1Xu?bR1#I8!NF8X zz9Yd?;eEBpjiB=+8Aj2ZwOiMb&cchl#8|sFl(9?P^m>_}>R!(R$zCtb!(qa=3eOZ| zQ&psQTC@~a215)w4X5df)vMW_P6C$5=e0QjpnEg|ukJ&2c-{+g7&gZ=I)TO*dj&NkZ4K>t3Xy4@XnmQ2Df~~m%-$q(lWK=YLujU1%?|u1ZL8VnPcl` zL4oxgE^K|R%A$xVbqNhDRM-<|&4h(w>zv8?DbH)bMLJ^STcauRBsQZ7O&hYuZP`L= z8^wG3_N|lEt-L4{&EPJf?a5^Q56_(yL9YV0IS_PevCvX1miRrEpy1}Qad=h){R(FO zZv;gS{Sq3C^Jqg7(!D}w!G5eymq;h=O_fNQZJa)1&JUbc96Dt8G$_AOrqX)F z&IP29XTO%>MkTIK#9S&N1v7n_xX0yM><80ayOA5CPzy)^-q&^~&c2V0&5Kb7JWw8eDLU8H=5+vJmr zMcw+`H{}6L7jhbV9%mVMkxjUwe_(Yg{ZL2JhhlQfk~^qB$m7he>%{tPzME6+|31(3 z6JL`uTgs?o%4~&!({-hH>KY&Q{Ua<6z;MiJx|?n6Sb{{a7v=#U`k)Q}-+;ds@Rx$( z+i@O&3uIg2b8nDm#>&A>p@df`kQB0@Lcl33HD~qB63= 0: - if attack == Rock: bluff = Paper # Here we choose the thing - elif attack == Paper: bluff = Scissors # that will hurt them - else: bluff = Rock # if they try to kill us. - else: - if attack == Rock: bluff = Scissors # Here we choose the thing - elif attack == Paper: bluff = Rock # that will hurt them - else: bluff = Paper # if they go for a tie. - return attack, bluff - - def Defend (self, foe, bluff): - if len(LearningAgent.GetWinHistory (self, foe)) > 0: - if bluff == Rock: attack = Scissors # They've fucked us in the past, - elif bluff == Paper: attack = Rock # so we assume they're lying and - else: attack = Paper # hoping we go for a kill. - else: - if bluff == Rock: attack = Paper # Here we trust that they - elif bluff == Paper: attack = Scissors # are telling the truth. - else: attack = Rock # And we try to kill them. - return attack - -#Fish is somewhat intelligent; it builds up trust and then stabs you in the back. -# If Fish detects that a bot is being predictably nice (tie 2+ times in a row), it will attack. -# If Fish detects that a bot has betrayed it (Loss), it will attack. -# Otherwise, Fish is nice. - -class Fish (LearningAgent): - def Attack (self, foe): - #print "Attacking" , foe - #print LearningAgent.GetWinHistory (self, foe) - attack = RandomAttack () - - history = LearningAgent.GetWinHistory (self, foe) - - #no history; be nice - if len(history) == 0: - bluff = attack - - #if we just lost to them, try to destroy them. - elif Loss == history[-1] or (len(history)>1 and [Tie,Tie] == history[-2:-1]): - if attack == Rock: bluff = Scissors - elif attack == Paper: bluff = Rock - else: bluff = Paper - else: - bluff = attack - return attack, bluff - - - def Defend (self, foe, bluff): - - history = LearningAgent.GetWinHistory (self, foe) - - if len(history) > 0 and Loss == history[-1]: - if bluff == Rock: attack = Scissors # They've fucked us in the past, - elif bluff == Paper: attack = Rock # so we assume they're lying and - else: attack = Paper # hoping we go for a kill. - else: - attack = bluff - return attack \ No newline at end of file diff --git a/src/link/C/c-link-lib/c-link-lib.xcodeproj/project.pbxproj b/src/link/C/c-link-lib/c-link-lib.xcodeproj/project.pbxproj index 80d9803..b01730e 100644 --- a/src/link/C/c-link-lib/c-link-lib.xcodeproj/project.pbxproj +++ b/src/link/C/c-link-lib/c-link-lib.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2291A1BE117EE3FD00854CBE /* c-lucifer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "c-lucifer.c"; sourceTree = ""; }; 2291A1BF117EE3FD00854CBE /* c-streetfighter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "c-streetfighter.c"; sourceTree = ""; }; 2291A1EC117FF85D00854CBE /* c-frechie.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "c-frechie.c"; sourceTree = ""; }; + 22C9FA0A118EE5ED003CF235 /* SampleAgents.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = SampleAgents.py; sourceTree = ""; }; 22F652F5117C679300A3793D /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 22F652F6117C6C9500A3793D /* c_link.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = c_link.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -20,6 +21,7 @@ 08FB7794FE84155DC02AAC07 /* c-link-lib */ = { isa = PBXGroup; children = ( + 22C9FA0A118EE5ED003CF235 /* SampleAgents.py */, 2291A1BC117EE3FD00854CBE /* agents */, 22F652F6117C6C9500A3793D /* c_link.h */, 2291A1BB117EDB9600854CBE /* c_link.c */, diff --git a/src/link/__init__.py b/src/link/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/link/cangel.py b/src/link/cangel.py new file mode 100644 index 0000000..45a64d1 --- /dev/null +++ b/src/link/cangel.py @@ -0,0 +1,8 @@ +from uccProgComp import BaseAgent, LearningAgent, RandomAttack +from externAgent import externAgent +from rpsconst import * + +class CAngel (externAgent): + def __init__ (self): + externAgent.__init__(self, "./link/C/c-link-lib/agents/c-angel") + diff --git a/src/link/externAgent.py b/src/link/externAgent.py new file mode 100644 index 0000000..10118c1 --- /dev/null +++ b/src/link/externAgent.py @@ -0,0 +1,56 @@ +'''externAgent.py - a bot shell for talking to external I/O bots. +Written by Daniel Axtens for the UCC Programming Competition in 2010. + +Licensed under an MIT-style license: see the LICENSE file for details. +''' + +from uccProgComp import BaseAgent, LearningAgent, RandomAttack +from rpsconst import * +from pexpect import pexpect + +class externAgent (BaseAgent): + + def __init__ (self, externName): + BaseAgent.__init__(self) + self.process = pexpect.spawn(externName) + self.process.delaybeforesend=0 + + + def stringToItem( self, str ): + if str == "Rock": + return Rock + elif str == "Paper": + return Paper + elif str == "Scissors": + return Scissors + else: + # Something has gone wrong! + print "Error: tried to convert \"%s\" into an item!" % str + return None + + def itemToString( self, item ): + if item == Rock: + return "Rock" + elif item == Paper: + return "Paper" + elif item == Scissors: + return "Scissors" + else: + # Something has gone wrong! + print "Error: tried to convert '%d' to Rock/Paper/Scissors string!" % item + + def Attack (self, foe): + self.process.sendline( "ATTACK %s" % foe ) + self.process.expect( "ATTACKING (.+) (.+)\n" ) + attack, bluff = self.process.match.groups() + attack, bluff = attack.strip(), bluff.strip() + return self.stringToItem(attack), self.stringToItem(bluff) + + def Defend( self, foe, bluff ): + #print "DEFEND %s %s" % (foe, self.itemToString(bluff)) + self.process.sendline( "DEFEND %s %s" % (foe, self.itemToString(bluff) ) ) + self.process.expect( "DEFENDING (.+)" ) + #print '------------------ ', self.process.match.groups()[0].strip() + defence = self.process.match.groups()[0].strip() + return self.stringToItem(defence) + \ No newline at end of file diff --git a/src/link/pexpect/ANSI.py b/src/link/pexpect/ANSI.py new file mode 100644 index 0000000..537017e --- /dev/null +++ b/src/link/pexpect/ANSI.py @@ -0,0 +1,334 @@ +"""This implements an ANSI terminal emulator as a subclass of screen. + +$Id: ANSI.py 491 2007-12-16 20:04:57Z noah $ +""" +# references: +# 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 + +import screen +import FSM +import copy +import string + +def Emit (fsm): + + screen = fsm.memory[0] + screen.write_ch(fsm.input_symbol) + +def StartNumber (fsm): + + fsm.memory.append (fsm.input_symbol) + +def BuildNumber (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): + + screen = fsm.memory[0] + if arg == 0: + screen.end_of_line() + elif arg == 1: + screen.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 Log (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 is a placeholder. + In theory I might want to add other terminal types. + """ + def __init__ (self, r=24, c=80): + screen.screen.__init__(self, r,c) + +class ANSI (term): + + """This class encapsulates a generic terminal. It filters a stream and + maintains the state of a screen object. """ + + def __init__ (self, r=24,c=80): + + term.__init__(self,r,c) + + #self.screen = screen (24,80) + self.state = FSM.FSM ('INIT',[self]) + self.state.set_default_transition (Log, 'INIT') + self.state.add_transition_any ('INIT', Emit, 'INIT') + self.state.add_transition ('\x1b', 'INIT', None, 'ESC') + self.state.add_transition_any ('ESC', Log, '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', None, 'INIT') + self.state.add_transition ('?', 'ELB', None, 'MODECRAP') + self.state.add_transition_list (string.digits, 'ELB', StartNumber, 'NUMBER_1') + self.state.add_transition_list (string.digits, 'NUMBER_1', BuildNumber, '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', None, 'INIT') + ### LED control. Same problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_1', None, 'INIT') + + # \E[?47h appears to be "switch to alternate screen" + # \E[?47l restores alternate screen... I think. + self.state.add_transition_list (string.digits, 'MODECRAP', StartNumber, 'MODECRAP_NUM') + self.state.add_transition_list (string.digits, 'MODECRAP_NUM', BuildNumber, 'MODECRAP_NUM') + self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT') + self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT') + +#RM Reset Mode Esc [ Ps l none + self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') + self.state.add_transition_any ('SEMICOLON', Log, 'INIT') + self.state.add_transition_list (string.digits, 'SEMICOLON', StartNumber, 'NUMBER_2') + self.state.add_transition_list (string.digits, 'NUMBER_2', BuildNumber, 'NUMBER_2') + self.state.add_transition_any ('NUMBER_2', Log, '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', None, 'INIT') + ### LED control. Same problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_2', None, 'INIT') + + def process (self, c): + + self.state.process(c) + + def process_list (self, l): + + self.write(l) + + def write (self, 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. cursor + position if moved forward with wrap-around, but no scrolling is done if + the cursor hits the lower-right corner of the screen. """ + + #\r and \n both produce a call to crlf(). + ch = ch[0] + + if ch == '\r': + # self.crlf() + return + if ch == '\n': + self.crlf() + return + if ch == chr(screen.BS): + self.cursor_back() + self.put_abs(self.cur_r, self.cur_c, ' ') + return + + if ch not in string.printable: + fout = open ('log', 'a') + fout.write ('Nonprint: ' + str(ord(ch)) + '\n') + fout.close() + 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 test (self): +# +# import sys +# write_text = 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(He\'s got a ferret sticking up his nose.)\n' + \ +# 'How it got there I can\'t tell\n' + \ +# 'But now it\'s there it hurts like hell\n' + \ +# 'And what is more it radically affects my sense of smell.\n' + \ +# '(His sense of smell.)\n' + \ +# 'I can see a bare-bottomed mandril.\n' + \ +# '(Slyly eyeing his other nostril.)\n' + \ +# 'If it jumps inside there too I really don\'t know what to do\n' + \ +# 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \ +# '(A nasal zoo.)\n' + \ +# 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(And what is worst of all it constantly explodes.)\n' + \ +# '"Ferrets don\'t explode," you say\n' + \ +# 'But it happened nine times yesterday\n' + \ +# 'And I should know for each time I was standing in the way.\n' + \ +# 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(He\'s got a ferret sticking up his nose.)\n' + \ +# 'How it got there I can\'t tell\n' + \ +# 'But now it\'s there it hurts like hell\n' + \ +# 'And what is more it radically affects my sense of smell.\n' + \ +# '(His sense of smell.)' +# self.fill('.') +# self.cursor_home() +# for c in write_text: +# self.write_ch (c) +# print str(self) +# +#if __name__ == '__main__': +# t = ANSI(6,65) +# t.test() diff --git a/src/link/pexpect/FSM.py b/src/link/pexpect/FSM.py new file mode 100644 index 0000000..751eb37 --- /dev/null +++ b/src/link/pexpect/FSM.py @@ -0,0 +1,331 @@ +#!/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 +""" + +class ExceptionFSM(Exception): + + """This is the FSM Exception class.""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return `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 self.state_transitions.has_key((input_symbol, state)): + return self.state_transitions[(input_symbol, state)] + elif self.state_transitions_any.has_key (state): + 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, os, traceback, optparse, time, string + +# +# 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', []) # "memory" will be used as a stack. + 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 = raw_input ('> ') + f.process_list(inputstr) + +if __name__ == '__main__': + try: + start_time = time.time() + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: FSM.py 490 2007-12-07 15:46:24Z noah $') + parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output') + (options, args) = parser.parse_args() + if options.verbose: print time.asctime() + main() + if options.verbose: print time.asctime() + if options.verbose: print 'TOTAL TIME IN MINUTES:', + if options.verbose: print (time.time() - start_time) / 60.0 + sys.exit(0) + except KeyboardInterrupt, e: # Ctrl-C + raise e + except SystemExit, e: # sys.exit() + raise e + except Exception, e: + print 'ERROR, UNEXPECTED EXCEPTION' + print str(e) + traceback.print_exc() + os._exit(1) diff --git a/src/link/pexpect/INSTALL b/src/link/pexpect/INSTALL new file mode 100644 index 0000000..509e925 --- /dev/null +++ b/src/link/pexpect/INSTALL @@ -0,0 +1,31 @@ +Installation +------------ +This is a standard Python Distutil distribution. To install simply run: + + python setup.py install + +This makes Pexpect available to any script on the machine. You need +root access to install it this way. If you do not have root access or +if you do not wish to install Pexpect so that is available to any script +then you can just copy the pexpect.py file to same directory as your script. + +Trouble on Debian and Ubuntu +---------------------------- +For some stupid reason Debian Linux does not include the distutils module +in the standard 'python' package. Instead, the distutils module is packaged +separately in the 'python-dev' package. So to add distutils back +into Python, simply use aptitude or apt-get to install 'python-dev'. +As root, run this command: + apt-get install python-dev +Why they do this is mysterious because: + - It breaks the Python model of "batteries included". + 'distutils' isn't an extra or optional module -- + it's parts of the Standard Python Library. + - The Debian 'python-dev' package is a microscopic 50K installed. + So what are they saving? + - Distutils is not only interesting to developers. Many non-development + oriented Python packages use 'distutils' to install applications. + - As far as I can tell, the package maintainers must go through + more trouble to remove 'distutils' from the standard Python + distribution than it would take just to leave it in. + diff --git a/src/link/pexpect/LICENSE b/src/link/pexpect/LICENSE new file mode 100644 index 0000000..e611443 --- /dev/null +++ b/src/link/pexpect/LICENSE @@ -0,0 +1,21 @@ +Free, open source, and all that good stuff. +Pexpect Copyright (c) 2008 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/link/pexpect/PKG-INFO b/src/link/pexpect/PKG-INFO new file mode 100644 index 0000000..2a5c859 --- /dev/null +++ b/src/link/pexpect/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: pexpect +Version: 2.3 +Summary: Pexpect is a pure Python Expect. It allows easy control of other applications. +Home-page: http://pexpect.sourceforge.net/ +Author: Noah Spurrier +Author-email: noah@noah.org +License: MIT license +Description: UNKNOWN +Platform: UNIX diff --git a/src/link/pexpect/README b/src/link/pexpect/README new file mode 100644 index 0000000..3101dc8 --- /dev/null +++ b/src/link/pexpect/README @@ -0,0 +1,45 @@ +Pexpect is a Pure Python Expect-like module + +Pexpect makes Python a better tool for controlling other applications. + +Pexpect is a pure Python module for spawning child applications; controlling +them; and responding to expected patterns in their output. Pexpect works like +Don Libes' Expect. Pexpect allows your script to spawn a child application and +control it as if a human were typing commands. + +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. Unlike other Expect-like modules for +Python, Pexpect does not require TCL or Expect nor does it require C +extensions to be compiled. It should work on any platform that supports the +standard Python pty module. The Pexpect interface was designed to be easy to use. + +If you want to work with the development version of the source code then please +read the DEVELOPERS document in the root of the source code tree. + +Free, open source, and all that good stuff. +Pexpect Copyright (c) 2008 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +Noah Spurrier +http://pexpect.sourceforge.net/ + diff --git a/src/link/pexpect/__init__.py b/src/link/pexpect/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/link/pexpect/doc/ANSI.html b/src/link/pexpect/doc/ANSI.html new file mode 100644 index 0000000..82ce3d5 --- /dev/null +++ b/src/link/pexpect/doc/ANSI.html @@ -0,0 +1,316 @@ + + +Python: module ANSI + + + + +
 
+ 
ANSI
index
/home/noah/pexpect/trunk/pexpect/ANSI.py
+

This implements an ANSI terminal emulator as a subclass of screen.

+$Id: ANSI.py 491 2007-12-16 20:04:57Z noah $

+

+ + + + + +
 
+Modules
       
FSM
+
copy
+
screen
+
string
+

+ + + + + +
 
+Classes
       
+
screen.screen +
+
+
term +
+
+
ANSI +
+
+
+
+
+

+ + + + + + + +
 
+class ANSI(term)
   This class encapsulates a generic terminal. It filters a stream and
+maintains the state of a screen object.
 
 
Method resolution order:
+
ANSI
+
term
+
screen.screen
+
+
+Methods defined here:
+
__init__(self, r=24, c=80)
+ +
flush(self)
+ +
process(self, c)
+ +
process_list(self, l)
+ +
write(self, s)
+ +
write_ch(self, ch)
This puts a character at the current cursor position. cursor
+position if moved forward with wrap-around, but no scrolling is done if
+the cursor hits the lower-right corner of the screen.
+ +
+Methods inherited from screen.screen:
+
__str__(self)
This returns a printable representation of the screen. The end of
+each screen line is terminated by a newline.
+ +
clear_all_tabs(self)
Clears all tabs.
+ +
clear_tab(self)
Clears tab at the current position.
+ +
cr(self)
This moves the cursor to the beginning (col 1) of the current row.
+ +
crlf(self)
This advances the cursor with CRLF properties.
+The cursor will line wrap and the screen may scroll.
+ +
cursor_back(self, count=1)
+ +
cursor_constrain(self)
This keeps the cursor within the screen area.
+ +
cursor_down(self, count=1)
+ +
cursor_force_position(self, r, c)
Identical to Cursor Home.
+ +
cursor_forward(self, count=1)
+ +
cursor_home(self, r=1, c=1)
+ +
cursor_restore_attrs(self)
Restores cursor position after a Save Cursor.
+ +
cursor_save(self)
Save current cursor position.
+ +
cursor_save_attrs(self)
Save current cursor position.
+ +
cursor_unsave(self)
Restores cursor position after a Save Cursor.
+ +
cursor_up(self, count=1)
+ +
cursor_up_reverse(self)
+ +
dump(self)
This returns a copy of the screen as a string. This is similar to
+__str__ except that lines are not terminated with line feeds.
+ +
erase_down(self)
Erases the screen from the current line down to the bottom of the
+screen.
+ +
erase_end_of_line(self)
Erases from the current cursor position to the end of the current
+line.
+ +
erase_line(self)
Erases the entire current line.
+ +
erase_screen(self)
Erases the screen with the background color.
+ +
erase_start_of_line(self)
Erases from the current cursor position to the start of the current
+line.
+ +
erase_up(self)
Erases the screen from the current line up to the top of the
+screen.
+ +
fill(self, ch=' ')
+ +
fill_region(self, rs, cs, re, ce, ch=' ')
+ +
get(self)
+ +
get_abs(self, r, c)
+ +
get_region(self, rs, cs, re, ce)
This returns a list of lines representing the region.
+ +
insert(self, ch)
+ +
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.
+ +
lf(self)
This moves the cursor down with scrolling.
+ +
newline(self)
This is an alias for crlf().
+ +
pretty(self)
This returns a copy of the screen as a string with an ASCII text box
+around the screen border. This is similar to __str__ except that it
+adds a box.
+ +
put(self, ch)
This puts a characters at the current cursor position.
+ +
put_abs(self, r, c, ch)
Screen array starts at 1 index.
+ +
scroll_constrain(self)
This keeps the scroll region within the screen region.
+ +
scroll_down(self)
Scroll display down one line.
+ +
scroll_screen(self)
Enable scrolling for entire display.
+ +
scroll_screen_rows(self, rs, re)
Enable scrolling from row {start} to row {end}.
+ +
scroll_up(self)
Scroll display up one line.
+ +
set_tab(self)
Sets a tab at the current position.
+ +

+ + + + + + + +
 
+class term(screen.screen)
   This is a placeholder. 
+In theory I might want to add other terminal types.
 
 Methods defined here:
+
__init__(self, r=24, c=80)
+ +
+Methods inherited from screen.screen:
+
__str__(self)
This returns a printable representation of the screen. The end of
+each screen line is terminated by a newline.
+ +
clear_all_tabs(self)
Clears all tabs.
+ +
clear_tab(self)
Clears tab at the current position.
+ +
cr(self)
This moves the cursor to the beginning (col 1) of the current row.
+ +
crlf(self)
This advances the cursor with CRLF properties.
+The cursor will line wrap and the screen may scroll.
+ +
cursor_back(self, count=1)
+ +
cursor_constrain(self)
This keeps the cursor within the screen area.
+ +
cursor_down(self, count=1)
+ +
cursor_force_position(self, r, c)
Identical to Cursor Home.
+ +
cursor_forward(self, count=1)
+ +
cursor_home(self, r=1, c=1)
+ +
cursor_restore_attrs(self)
Restores cursor position after a Save Cursor.
+ +
cursor_save(self)
Save current cursor position.
+ +
cursor_save_attrs(self)
Save current cursor position.
+ +
cursor_unsave(self)
Restores cursor position after a Save Cursor.
+ +
cursor_up(self, count=1)
+ +
cursor_up_reverse(self)
+ +
dump(self)
This returns a copy of the screen as a string. This is similar to
+__str__ except that lines are not terminated with line feeds.
+ +
erase_down(self)
Erases the screen from the current line down to the bottom of the
+screen.
+ +
erase_end_of_line(self)
Erases from the current cursor position to the end of the current
+line.
+ +
erase_line(self)
Erases the entire current line.
+ +
erase_screen(self)
Erases the screen with the background color.
+ +
erase_start_of_line(self)
Erases from the current cursor position to the start of the current
+line.
+ +
erase_up(self)
Erases the screen from the current line up to the top of the
+screen.
+ +
fill(self, ch=' ')
+ +
fill_region(self, rs, cs, re, ce, ch=' ')
+ +
get(self)
+ +
get_abs(self, r, c)
+ +
get_region(self, rs, cs, re, ce)
This returns a list of lines representing the region.
+ +
insert(self, ch)
+ +
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.
+ +
lf(self)
This moves the cursor down with scrolling.
+ +
newline(self)
This is an alias for crlf().
+ +
pretty(self)
This returns a copy of the screen as a string with an ASCII text box
+around the screen border. This is similar to __str__ except that it
+adds a box.
+ +
put(self, ch)
This puts a characters at the current cursor position.
+ +
put_abs(self, r, c, ch)
Screen array starts at 1 index.
+ +
scroll_constrain(self)
This keeps the scroll region within the screen region.
+ +
scroll_down(self)
Scroll display down one line.
+ +
scroll_screen(self)
Enable scrolling for entire display.
+ +
scroll_screen_rows(self, rs, re)
Enable scrolling from row {start} to row {end}.
+ +
scroll_up(self)
Scroll display up one line.
+ +
set_tab(self)
Sets a tab at the current position.
+ +

+ + + + + +
 
+Functions
       
BuildNumber(fsm)
+
DoBack(fsm)
+
DoBackOne(fsm)
+
DoCursorRestore(fsm)
+
DoCursorSave(fsm)
+
DoDown(fsm)
+
DoDownOne(fsm)
+
DoEnableScroll(fsm)
+
DoErase(fsm)
+
DoEraseDown(fsm)
+
DoEraseEndOfLine(fsm)
+
DoEraseLine(fsm)
+
DoForward(fsm)
+
DoForwardOne(fsm)
+
DoHome(fsm)
+
DoHomeOrigin(fsm)
+
DoMode(fsm)
+
DoScrollRegion(fsm)
+
DoUp(fsm)
+
DoUpOne(fsm)
+
DoUpReverse(fsm)
+
Emit(fsm)
+
Log(fsm)
+
StartNumber(fsm)
+
+ \ No newline at end of file diff --git a/src/link/pexpect/doc/FSM.html b/src/link/pexpect/doc/FSM.html new file mode 100644 index 0000000..6335df2 --- /dev/null +++ b/src/link/pexpect/doc/FSM.html @@ -0,0 +1,272 @@ + + +Python: module FSM + + + + +
 
+ 
FSM
index
/home/noah/pexpect/trunk/pexpect/FSM.py
+

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

+

+ + + + + +
 
+Modules
       
optparse
+os
+
string
+sys
+
time
+traceback
+

+ + + + + +
 
+Classes
       
+
FSM +
exceptions.Exception(exceptions.BaseException) +
+
+
ExceptionFSM +
+
+
+

+ + + + + + + +
 
+class ExceptionFSM(exceptions.Exception)
   This is the FSM Exception class.
 
 
Method resolution order:
+
ExceptionFSM
+
exceptions.Exception
+
exceptions.BaseException
+
__builtin__.object
+
+
+Methods defined here:
+
__init__(self, value)
+ +
__str__(self)
+ +
+Data descriptors defined here:
+
__weakref__
+
list of weak references to the object (if defined)
+
+
+Data and other attributes inherited from exceptions.Exception:
+
__new__ = <built-in method __new__ of type object at 0x81400e0>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+Methods inherited from exceptions.BaseException:
+
__delattr__(...)
x.__delattr__('name') <==> del x.name
+ +
__getattribute__(...)
x.__getattribute__('name') <==> x.name
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__reduce__(...)
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
+ +
__setstate__(...)
+ +
+Data descriptors inherited from exceptions.BaseException:
+
__dict__
+
+
args
+
+
message
+
exception message
+
+

+ + + + + + + +
 
+class FSM
   This is a Finite State Machine (FSM).
 
 Methods defined here:
+
__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.
+ +
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().
+ +
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.
+ +
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.
+ +
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.
+ +
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().
+ +
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.
+ +
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__().
+ +
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.
+ +

+ + + + + +
 
+Functions
       
BeginBuildNumber(fsm)
+
BuildNumber(fsm)
+
DoEqual(fsm)
+
DoOperator(fsm)
+
EndBuildNumber(fsm)
+
Error(fsm)
+
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.
+
+ \ No newline at end of file diff --git a/src/link/pexpect/doc/clean.css b/src/link/pexpect/doc/clean.css new file mode 100644 index 0000000..e8d98dd --- /dev/null +++ b/src/link/pexpect/doc/clean.css @@ -0,0 +1,103 @@ + +body { + margin:0px; + padding:0px; + font-family:verdana, arial, helvetica, sans-serif; + color:#333; + background-color:white; + } +pre { + background: #eeeeee; + border: 1px solid #888888; + color: black; + padding: 1em; + white-space: pre; +} +h1 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:20px; + line-height:28px; + font-weight:900; + color:#44f; + } +h2 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:17px; + line-height:28px; + font-weight:900; + color:#226; + } +h3 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:15px; + line-height:28px; + font-weight:900; + } +p +{ + margin:0px 0px 16px 0px; + font:11px/20px verdana, arial, helvetica, sans-serif; + padding:0px; +} +table +{ + font-size: 10pt; + color: #000000; +} +td{border:1px solid #999;} + +table.pymenu {color: #000000; background-color: #99ccff} +th.pymenu {color: #ffffff; background-color: #003366} + +.code +{ + font-family: "Lucida Console", monospace; font-weight: bold; + color: #007700; background-color: #eeeeee +} + +#Content>p {margin:0px;} +#Content>p+p {text-indent:30px;} + +a { + text-decoration:none; + font-weight:600; + font-family:verdana, arial, helvetica, sans-serif; + color: #900; +} +//a:link {color:#09c;} +//a x:visited {color:#07a;} +a:hover {background-color:#ee0;} + +#Header { + margin:10px 0px 10px 0px; + padding:10px 0px 10px 20px; + /* For IE5/Win's benefit height = [correct height] + [top padding] + [top and bottom border widths] */ + height:33px; /* 14px + 17px + 2px = 33px */ + border-style:solid; + border-color:black; + border-width:1px 0px; /* top and bottom borders: 1px; left and right borders: 0px */ + line-height:33px; + background-color:#eee; + height:66px; /* the correct height */ + } + +#Content { + margin:0px 210px 50px 10px; + padding:10px; + } + +#Menu { + position:absolute; + top:100px; + right:20px; + width:172px; + padding:10px; + background-color:#eee; + border:1px solid #999; // dashed #999; + line-height:17px; + width:150px; + font-size:11px; + } diff --git a/src/link/pexpect/doc/email.png b/src/link/pexpect/doc/email.png new file mode 100644 index 0000000000000000000000000000000000000000..511113815b180db1558feafff79f0a4b040a6da9 GIT binary patch literal 322 zcmV-I0lof-P)NLwXB@*TGEL~WX>lFk^=`ICL#a#>NK00 zjgmQh?P{!=9gd&dtl3UhAFX|Rbged}HJ+Y78mrYxJu$ml)}4NBt+Dh{;4L^-VExyR zX20v9-n-9_Jj|n>NoiE_A{S>g)^Jl+j{s*SX+<$KpDu>*NvGUprS8s{D%mcl5-&SO zVCBrxd}w$ypNR05)!L5UbV01e65?WL?b6JYCl{lcg^^x+YoOo63M(e;=l5800VieL Ue0PPCuK)l507*qoM6N<$f(NgTO8@`> literal 0 HcmV?d00001 diff --git a/src/link/pexpect/doc/examples.html b/src/link/pexpect/doc/examples.html new file mode 100644 index 0000000..2884a5c --- /dev/null +++ b/src/link/pexpect/doc/examples.html @@ -0,0 +1,135 @@ + + + +Pexpect - Examples + + + + + + + +

+
+ +

hive.py

+

+This script creates SSH connections to a list of hosts that +you provide. Then you are given a command line prompt. Each +shell command that you enter is sent to all the hosts. The +response from each host is collected and printed. For example, +you could connect to a dozen different machines and reboot +them all at once. +

+ +

script.py

+

+ This implements a command similar to the classic BSD +"script" command. + This will start a subshell and log all input and +output to a file. + This demonstrates the interact() method of Pexpect. +

+ +

fix_cvs_files.py

+

+ This is for cleaning up binary files improperly +added to CVS. + This script scans the given path to find binary +files; + checks with CVS to see if the sticky options are set +to -kb; + finally if sticky options are not -kb then uses 'cvs +admin' to + set the -kb option. +

+ +

ftp.py

+

+ This demonstrates an FTP "bookmark". + This connects to an ftp site; does a few ftp stuff; +and then gives the user + interactive control over the session. In this case +the "bookmark" is to a + directory on the OpenBSD ftp server. It puts you in +the i386 packages + directory. You can easily modify this for other +sites. + This demonstrates the interact() method of Pexpect. +

+ +

monitor.py

+

+ This runs a sequence of commands on a remote host +using SSH. + It runs a simple system checks such as uptime and +free to monitor + the state of the remote host. +

+ +

passmass.py

+

+ This will login to each given server and change the +password of the + given user. This demonstrates scripting logins and +passwords. +

+ +

python.py

+

+ This starts the python interpreter and prints the +greeting message backwards. + It then gives the user iteractive control of Python. +It's pretty useless! +

+ +

rippy.py

+

+ This is a wizard for mencoder. It greatly simplifies +the process of + ripping a DVD to Divx (mpeg4) format. It can +transcode from any + video file to another. It has options for resampling +the audio stream; + removing interlace artifacts, fitting to a target +file size, etc. + There are lots of options, but the process is simple +and easy to use. +

+ +

sshls.py

+

+ This lists a directory on a remote machine. +

+

ssh_tunnel.py

+

+ This starts an SSH tunnel to a remote machine. It +monitors the connection + and restarts the tunnel if it goes down. +

+

uptime.py

+

+ This will run the uptime command and parse the +output into variables. + This demonstrates using a single regular expression +to match the output + of a command and capturing different variable in +match groups. + The grouping regular expression handles a wide variety of different +uptime formats. +
+ +

+ The Pexpect project page on SourceForge.net +

+
+ + + diff --git a/src/link/pexpect/doc/fdpexpect.html b/src/link/pexpect/doc/fdpexpect.html new file mode 100644 index 0000000..545c8df --- /dev/null +++ b/src/link/pexpect/doc/fdpexpect.html @@ -0,0 +1,402 @@ + + +Python: module fdpexpect + + + + +
 
+ 
fdpexpect (version 2.3)
index
/home/noah/pexpect/trunk/pexpect/fdpexpect.py
+

This is like pexpect, but will work on any file descriptor that you pass it.
+So you are reponsible for opening and close the file descriptor.

+$Id: fdpexpect.py 505 2007-12-26 21:33:50Z noah $

+

+ + + + + +
 
+Modules
       
os
+

+ + + + + +
 
+Classes
       
+
pexpect.spawn(__builtin__.object) +
+
+
fdspawn +
+
+
+

+ + + + + + + +
 
+class fdspawn(pexpect.spawn)
   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.
 
 
Method resolution order:
+
fdspawn
+
pexpect.spawn
+
__builtin__.object
+
+
+Methods defined here:
+
__del__(self)
+ +
__init__(self, fd, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None)
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().
+ +
close(self)
+ +
isalive(self)
This checks if the file descriptor is still valid. If os.fstat()
+does not raise an exception then we assume it is alive.
+ +
kill(self, sig)
+ +
terminate(self, force=False)
+ +
+Methods inherited from pexpect.spawn:
+
__iter__(self)
This is to support iterators over a file-like object.
+ +
__str__(self)
This returns a human-readable string that represents the state of
+the object.
+ +
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 = compile_pattern_list(pl)
+     return 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 = compile_pattern_list(my_pattern)
+     while some_condition:
+        ...
+        i = expect_list(clp, timeout)
+        ...
+ +
eof(self)
This returns True if the EOF exception was ever raised.
+ +
expect(self, pattern, timeout=-1, searchwindowsize=None)
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 returs 1 ('foo') if parts of the final 'bar' arrive late

+After a match is found the instance attributes 'before', 'after' and
+'match' will be set. You can see all the data read before the match in
+'before'. You can see the data that was matched in 'after'. The
+re.MatchObject used in the re match will be in 'match'. If an error
+occurred then 'before' will be set to all the data read so far and
+'after' and 'match' will be None.

+If timeout is -1 then timeout will be set to the self.timeout value.

+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().
+ +
expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1)
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.
+ +
expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1)
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(). If timeout==-1 then
+the self.timeout value is used. If searchwindowsize==-1 then the
+self.searchwindowsize value is used.
+ +
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.
+ +
fileno(self)
This returns the file descriptor of the pty for the child.
+ +
flush(self)
This does nothing. It is here to support the interface for a
+File-like object.
+ +
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().
+ +
getwinsize(self)
This returns the terminal window size of the child tty. The return
+value is a tuple of (rows, cols).
+ +
interact(self, escape_character='\x1d', 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 stop. The default for
+escape_character is ^]. This should not be confused with ASCII 27 --
+the ESC character. ASCII 29 was chosen for historical merit because
+this is the character used by 'telnet' as the escape character. The
+escape_character will not be sent to the child process.

+You may pass in optional input and output filter functions. These
+functions should take a string and return a string. 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))
+        global p
+        p.setwinsize(a[0],a[1])
+    p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough.
+    signal.signal(signal.SIGWINCH, sigwinch_passthrough)
+    p.interact()
+ +
isatty(self)
This returns True if the file descriptor is open and connected to a
+tty(-like) device, else False.
+ +
next(self)
This is to support iterators over a file-like object.
+ +
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.
+ +
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 log file was set using
+setlog() then all data will also be written to the log file.

+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 was 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 effected 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.

+This is a wrapper around os.read(). It uses select.select() to
+implement the timeout.
+ +
readline(self, size=-1)
This reads and returns one entire line. A trailing newline is kept
+in the string, but may be absent when a file ends with an incomplete
+line. Note: This readline() looks for a \r\n pair even on UNIX
+because this is what the pseudo tty device returns. So contrary to what
+you may expect you will receive the newline as \r\n. An empty string
+is returned when EOF is hit immediately. Currently, the size argument is
+mostly ignored, so this behavior is not standard for a file-like
+object. If size is 0 then an empty string is returned.
+ +
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.
+ +
send(self, s)
This sends a string to the child process. This returns the number of
+bytes written. If a log file was set then the data is also written to
+the log.
+ +
sendcontrol(self, char)
This sends a control character to the child such as Ctrl-C or
+Ctrl-D. For example, to send a Ctrl-G (ASCII 7)::

+    child.sendcontrol('g')

+See also, sendintr() and sendeof().
+ +
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.
+ +
sendintr(self)
This sends a SIGINT to the child. It does not require
+the SIGINT to be the first character on a line.
+ +
sendline(self, s='')
This is like send(), but it adds a line feed (os.linesep). This
+returns the number of bytes written.
+ +
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')
+    p.sendline ('1234') # We will see this twice (once from tty echo and again from cat).
+    p.expect (['1234'])
+    p.expect (['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 (['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') # We will see this twice (once from tty echo and again from cat).
+    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'])
+ +
setlog(self, fileobject)
This method is no longer supported or allowed.
+ +
setmaxread(self, maxread)
This method is no longer supported or allowed. I don't like getters
+and setters without a good reason.
+ +
setwinsize(self, r, c)
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.
+ +
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, technically, the child
+is still alive until its output is read.
+ +
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 user@example.com')
+    p.waitnoecho()
+    p.sendline(mypassword)

+If timeout is None then this method to block forever until ECHO flag is
+False.
+ +
write(self, s)
This is similar to send() except that there is no return value.
+ +
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.
+ +
+Data descriptors inherited from pexpect.spawn:
+
__dict__
+
dictionary for instance variables (if defined)
+
+
__weakref__
+
list of weak references to the object (if defined)
+
+

+ + + + + +
 
+Data
       __all__ = ['fdspawn']
+__revision__ = '$Revision: 399 $'
+__version__ = '2.3'
+ \ No newline at end of file diff --git a/src/link/pexpect/doc/index.html b/src/link/pexpect/doc/index.html new file mode 100644 index 0000000..c72fdff --- /dev/null +++ b/src/link/pexpect/doc/index.html @@ -0,0 +1,868 @@ + + + +Pexpect - a Pure Python Expect-like module + + + + + + + +

+
+

Pexpect makes Python a better tool for controlling other +applications.

+

Pexpect is a pure Python module for spawning child applications; +controlling them; and responding to expected patterns in their output. +Pexpect works like Don Libes' Expect. Pexpect allows your script to +spawn a child application and control it as if a human were typing +commands.

+

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. Unlike +other Expect-like modules for Python, Pexpect does not require TCL or +Expect nor does it require C extensions to be compiled. It should work +on any platform that supports the standard Python pty module. The +Pexpect interface was designed to be easy to use.

+ + + + + + + +
Send questions to:Click to send email.
+
+

License: MIT style

+

+Free, open source, and all that good stuff.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Pexpect Copyright (c) 2008 Noah Spurrier
+http://pexpect.sourceforge.net/ +

+ +
+

Download

+

Download the +current version here from the SourceForge site. Grab the current Pexpect tarball. +

+

Installing Pexpect

+

The Pexpect tarball is a standard Python Distutil distribution.

+
    +
  1. download pexpect-2.3.tar.gz
  2. +
  3. tar zxf pexpect-2.3.tar.gz
  4. +
  5. cd pexpect-2.3
  6. +
  7. python setup.py install do this as root
  8. +
+

Examples

+

+Under the pexpect-2.3 directory you should find +the examples directory. +This is the best way to learn to use Pexpect. +See the descriptions of Pexpect Examples. +

+

API Documentation

+

+

+pexpect This is the main module that you want.
+pxssh Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.
+
+the following are experimental extensions to Pexpect
+
+fdpexpect fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.
+SCREEN This represents a virtual 'screen'.
+ANSI This parses ANSI/VT-100 terminal escape codes.
+FSM This is a finite state machine used by ANSI.
+
+

+
+

Project Status

+

Automated pyunit tests reach over 80% +code coverage on pexpect.py. I regularly test on Linux and BSD +platforms. I try to test on Solaris and Irix. +

+
+

Requirements for use of Pexpect

+

Python

+
+

Pexpect was written and tested with Python 2.4. It should work on +earlier versions that have the pty module. I +sometimes even manually test it with Python 1.5.2, but I can't easily +run the PyUnit test framework against Python 1.5.2, so I have less +confidence in Pexpect on Python 1.5.2.

+
+

pty module

+
+

Any POSIX system (UNIX) with a working pty +module should be able to run Pexpect. The pty +module is part of the Standard Python Library, so if you are running on +a POSIX system you should have it. The pty +module does not run the same on all platforms. It should be solid on Linux +and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more +about the wrinkles see Bugs and Testing.

+
+

Pexpect does not currently work on the standard Windows Python (see +the pty requirement); however, it seems to work fine using Cygwin. It is possible to build +something like a pty for Windows, but it would have to use a different +technique that I am still investigating. I know it's possible because +Libes' Expect was ported to Windows. If you have any ideas or +skills to contribute in this area then I would really appreciate some +tips on how to approach this problem.

+
+

Overview

+

Pexpect can be used for automating interactive applications such as +ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be +easy to use. Here is an example of Pexpect in action:

+
+
# This connects to the openbsd ftp site and
# downloads the recursive directory listing.
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('cd pub')
child.expect('ftp> ')
child.sendline ('get ls-lR.gz')
child.expect('ftp> ')
child.sendline ('bye')
+
+

Obviously you could write an ftp client using Python's own ftplib module, but this is just a demonstration. +You can use this technique with any application. This is especially +handy if you are writing automated test tools.

+ +

There are two important methods in Pexpect -- expect() +and send() (or sendline() +which is like send() with a linefeed). +The expect() method waits for the child application +to return a given string. The string you specify is a regular expression, so +you can match complicated patterns. The send() method +writes a string to the child application. From the child's point of +view it looks just like someone typed the text from a terminal. After +each call to expect() the before and after +properties will be set to the text printed by child application. The before property will contain all text up to +the expected string pattern. The after string +will contain the text that was matched by the expected pattern. +The match property is set to the re MatchObject. +

+ +

An example of Pexpect in action may make things more clear. This example uses +ftp to login to the OpenBSD site; list files +in a directory; and then pass interactive control of the ftp session to +the human user.

+
+
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('ls /pub/OpenBSD/')
child.expect ('ftp> ')
print child.before # Print the result of the ls command.
child.interact() # Give control of the child to the user.
+
+

Special EOF and TIMEOUT patterns

+

+There are two special patterns to match the End Of File or a Timeout condition. +You you can pass these patterns to expect(). +These patterns are not regular expressions. Use them like predefined constants. +

+

If the child has died and you have read all the child's output then ordinarily +expect() will raise an EOF +exception. You can read everything up to the EOF without generating an +exception by using the EOF pattern expect(pexpect.EOF). +In this case everything the child has output will be available in the before property.

+

The pattern given to expect() may be a +regular expression or it may also be a list of regular expressions. +This allows you to match multiple optional responses. The expect() +method returns the index of the pattern that was matched. For example, +say you wanted to login to a server. After entering a password you +could get various responses from the server -- your password could be +rejected; or you could be allowed in and asked for your terminal type; +or you could be let right in and given a command prompt. The following +code fragment gives an example of this:

+
+
child.expect('password:')
child.sendline (my_secret_password)
# We expect any of these three patterns...
i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
if i==0:
print 'Permission denied on host. Can't login'
child.kill(0)
elif i==2:
print 'Login OK... need to send terminal type.'
child.sendline('vt100')
child.expect ('[#\$] ')
elif i==3:
print 'Login OK.'
print 'Shell command prompt', child.after
+
+

If nothing matches an expected pattern then expect will eventually +raise a TIMEOUT exception. The default time is 30 seconds, but you can +change this by passing a timeout argument to expect():

+
+
# Wait no more than 2 minutes (120 seconds) for password prompt.
child.expect('password:', timeout=120)
+
+

Find the end of line -- CR/LF conventions
+Matching at the end of a line can be tricky
+$ regex pattern is useless.
+

+

Pexpect matches regular expressions a little differently than what +you might be used to. +

+

The $ pattern for end of line match is useless. +The $ matches the end of string, but Pexpect reads from the child +one character at a time, so each character looks like the end of a line. +Pexpect can't do a look-ahead into the child's output stream. +In general you would have this situation when using regular expressions +with any stream.
+Note, pexpect does have an internal buffer, so reads are faster +than one character at a time, but from the user's perspective the regex +patterns test happens one character at a time.

+

The best way to match the end of a line is to look for the +newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style. +It may surprise some UNIX people to learn that terminal TTY device drivers +(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify +the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so +when the child app prints "\n" you actually see "\r\n". +

+

UNIX uses just linefeeds to end lines of text, but not when it +comes to TTY devices! TTY devices are more like the Windows world. +Each line of text end with a CR/LF combination. When you intercept data +from a UNIX command from a TTY device you will find that the TTY device +outputs a CR/LF combination. A UNIX command may only write a linefeed +(\n), but the TTY device driver converts it to CR/LF. This means that +your terminal will see lines end with CR/LF (hex 0D 0A). +Since Pexpect emulates a terminal, to match ends of lines you have to +expect the CR/LF combination.

+
+

child.expect ('\r\n')

+
+

If you just need to skip past a new line then expect +('\n') by itself will work, but if you are expecting a specific +pattern before the end of line then you need to explicitly look for the +\r. For example the following expects a word at the end of a line:

+
+

child.expect ('\w+\r\n')

+
+

But the following would both fail:

+
+

child.expect ('\w+\n')

+
+

And as explained before, trying to use '$' to match the end of line +would not work either:

+
+

child.expect ('\w+$')

+
+

So if you need to explicitly look for the END OF LINE, you want to +look for the CR/LF combination -- not just the LF and not the $ pattern.

+

This problem is not limited to Pexpect. This problem happens any +time you try to perform a regular expression match on a stream. Regular +expressions need to look ahead. With a stream it is hard to look ahead +because the process generating the stream may not be finished. There is no +way to know if the process has paused momentarily or is finished and +waiting for you. Pexpect must implicitly always +do a NON greedy match (minimal) at the end of a input {### already said +this}.

+

Pexpect compiles all regular expressions with the DOTALL flag. With +the DOTALL flag a "." will match a newline. See the Python documentation

+

Beware of + and * at the end of input.

+

Remember that any time you try to match a pattern that needs +look-ahead that you will always get a minimal match (non greedy). For +example, the following will always return just one character:

+
+

child.expect ('.+')

+
+

This example will match successfully, but will always return no +characters:

+
+

child.expect ('.*')

+
+

Generally any star * expression will match as little as possible

+

One thing you can do is to try to force a non-ambiguous character at +the end of your \d+ pattern. Expect that +character to delimit the string. For example, you might try making the +end of your pattrn be \D+ instead of \D*. That means number digits alone would not +satisfy the (\d+) pattern. You would need +some number(s) and at least one \D at the +end.

+

Matching groups

+

You can group regular expression using parenthesis. After a match, +the match parameter of the spawn object will +contain the Python Match object.

+

Examples

+

Using "match" and groups...

+

Debugging

+

If you get the string value of a pexpect.spawn object you will get +lots of useful debugging information. For debugging it's very useful to +use the following pattern:

+

try:
+    i = child.expect ([pattern1, pattern2, pattern3, +etc])
+except:
+    print "Exception was thrown"
+    print "debug information:"
+    print str(child)
+

+

It is also useful to log the child's input and out to a file or the +screen. The following will turn on logging and send output to stdout +(the screen).
+

+

    child = pexpect.spawn (foo)
+    child.logfile = sys.stdout
+
+

+
+

Exceptions

+

EOF

+

Note that two flavors of EOF Exception may be thrown. They are +virtually identical except for the message string. For practical +purposes you should have no need to distinguish between them, but they +do give a little extra information about what type of platform you are +running. The two messages are:

+
+

End Of File (EOF) in read(). Exception style platform.

+

End Of File (EOF) in read(). Empty string style +platform.

+
+

Some UNIX platforms will throw an exception when you try to read +from a file descriptor in the EOF state. Other UNIX platforms instead +quietly return an empty string to indicate that the EOF state has been +reached.

+

Expecting EOF

+

If you wish to read up to the end of the child's output without +generating an EOF exception then use the expect(pexpect.EOF) method.

+

TIMEOUT

+

The expect() and read() +methods will also timeout if the child does not generate any output for +a given amount of time. If this happens they will raise a TIMEOUT exception. You can have these method +ignore a timeout and block indefinitely by passing None for the timeout +parameter.

+
+

child.expect(pexpect.EOF, timeout=None)

+
+
+

FAQ

+

Q: Why don't shell pipe and redirect (| and >) work when I +spawn a command?

+

+ +A: Remember that Pexpect does NOT interpret shell meta characters such as +redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the +command you are spawning. 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 > log_list.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 > log_list.txt'
+    child = pexpect.spawn ('/bin/bash', ['-c', shell_cmd])
+    child.expect (pexpect.EOF)
+
+ +

+

Q: Isn't there already a Python Expect?

+

A: Yes, there are several of them. They usually require you to +compile C. I wanted something that was pure Python and preferably a +single module that was simple to install. I also wanted something that +was easy to use. This pure Python expect only recently became possible +with the introduction of the pty module in the standard Python library. +Previously C extensions were required.

+ +

Q: The before and after properties sound weird.

+

Originally I was going to model Pexpect more after Expect, but then +I found that I could never remember how to get the context of the stuff +I was trying to parse. I hate having to read my own documentation. I +decided that it was easier for me to remember what before and after +was. It just so happens that this is how the -B and -A options in grep +works, so that made it even easier for me to remember. Whatever makes +my life easier is what's best.

+ +

Q: Why not just use Expect?

+

A: I love it. It's great. I has bailed me out of some real jams, but +I wanted something that would do 90% of what I need from Expect; be 10% +of the size; and allow me to write my code in Python instead of TCL. +Pexpect is not nearly as big as Expect, but Pexpect does everything I +have ever used Expect for. +

+ +

Q: Why not just use a pipe (popen())?

+

A: A pipe works fine for getting the output to non-interactive +programs. If you just want to get the output from ls, +uname, or ping +then this works. Pipes do not work very well for interactive programs +and pipes will almost certainly fail for most applications that ask for +passwords such as telnet, ftp, or ssh.

+

There are two reasons for this.

+

First an application may bypass stdout and print directly to its +controlling TTY. Something like SSH will do this when it asks you for a +password. This is why you cannot redirect the password prompt because +it does not go through stdout or stderr.

+

The second reason is because most applications are built using the C +Standard IO Library (anything that uses #include +<stdio.h>). One of the features of the stdio library is +that it buffers all input and output. Normally output is line +buffered when a program is printing to a TTY (your terminal +screen). Every time the program prints a line-feed the currently +buffered data will get printed to your screen. The problem comes when +you connect a pipe. The stdio library is smart and can tell that it is +printing to a pipe instead of a TTY. In that case it switches from line +buffer mode to block buffered. In this mode the +currently buffered data is flushed when the buffer is full. This causes +most interactive programs to deadlock. Block buffering is more +efficient when writing to disks and pipes. Take the situation where a +program prints a message "Enter your user name:\n" and then waits for +you type type something. In block buffered mode, the stdio library will +not put the message into the pipe even though a linefeed is printed. +The result is that you never receive the message, yet the child +application will sit and wait for you to type a response. Don't confuse +the stdio lib's buffer with the pipe's buffer. The pipe buffer is +another area that can cause problems. You could flush the input side of +a pipe, whereas you have no control over the stdio library buffer.

+

More information: the Standard IO library has three states for a +FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered; +and _IONBF for unbuffered. The STDIO lib will use block buffering when +talking to a block file descriptor such as a pipe. This is usually not +helpful for interactive programs. Short of recompiling your program to +include fflush() everywhere or recompiling a custom stdio library there +is not much a controlling application can do about this if talking over +a pipe.

+

The program may have put data in its output that remains unflushed +because the output buffer is not full; then the program will go and +deadlock while waiting for input -- because you never send it any +because you are still waiting for its output (still stuck in the +STDIO's output buffer).

+

The answer is to use a pseudo-tty. A TTY device will force line +buffering (as opposed to block buffering). Line buffering means that +you will get each line when the child program sends a line feed. This +corresponds to the way most interactive programs operate -- send a line +of output then wait for a line of input.

+

I put "answer" in quotes because it's ugly solution and because +there is no POSIX standard for pseudo-TTY devices (even though they +have a TTY standard...). What would make more sense to me would be to +have some way to set a mode on a file descriptor so that it will tell +the STDIO to be line-buffered. I have investigated, and I don't think +there is a way to set the buffered state of a child process. The STDIO +Library does not maintain any external state in the kernel or whatnot, +so I don't think there is any way for you to alter it. I'm not quite +sure how this line-buffered/block-buffered state change happens +internally in the STDIO library. I think the STDIO lib looks at the +file descriptor and decides to change behavior based on whether it's a +TTY or a block file (see isatty()).

+

I hope that this qualifies as helpful.

+ +

Don't use a pipe to control another application...

+

Pexpect may seem similar to os.popen() or +commands module. The main difference is that +Pexpect (like Expect) uses a pseudo-TTY to talk to the child +application. Most applications do no work well through the system() +call or through pipes. And probably all applications that ask a user to +type in a password will fail. These applications bypass the stdin and +read directly from the TTY device. Many applications do not explicitly +flush their output buffers. This causes deadlocks if you try to control +an interactive application using a pipe. What happens is that most UNIX +applications use the stdio (#include <stdio.h>) for input and +output. The stdio library behaves differently depending on where the +output is going. There is no way to control this behavior from the +client end.
+

+ +

Q: Can I do screen scraping with this thing?

+

A: That depends. If your application just does line-oriented output +then this is easy. If it does screen-oriented output then it may work, +but it could be hard. For example, trying to scrape data from the 'top' +command would be hard. The top command repaints the text window.

+

I am working on an ANSI / VT100 terminal emulator that will have +methods to get characters from an arbitrary X,Y coordinate of the +virtual screen. It works and you can play with it, but I have no +working examples at this time.

+
+

Bugs

+

Threads

+

On Linux (RH 8) you cannot spawn a child from a different thread and +pass the handle back to a worker thread. The child is successfully +spawned but you can't interact with it. The only way to make it work is +to spawn and interact with the child all in the same thread. [Adam +Kerrison]

+

Timing issue with send() and sendline()

+

This problem has been addressed and should not effect most users.

+

It is sometimes possible to read an echo of the string sent with send() and sendline(). +If you call sendline() and then immediately +call readline() you may get part of your +output echoed back. You may read back what you just wrote even if the +child application does not explicitly echo it. Timing is critical. This +could be a security issue when talking to an application that asks for +a password; otherwise, this does not seem like a big deal. But why +do TTYs do this?

+

People usually report this when they are trying to control SSH or +some other login. For example, if your code looks something like this:

+
child.expect ('[pP]assword:')
child.sendline (my_password)
+


+

+1. SSH prints "password:" prompt to the user.
+2. SSH turns off echo on the TTY device.
+3. SSH waits for user to enter a password.
+
+When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt +before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between +steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug. +

+

+Pexpect now automatically adds a short delay before sending data to a child process. +This more closely mimics what happens in the usual human-to-app interaction. +The delay can be tuned with the 'delaybeforesend' attribute of the spawn class. +In general, this fixes the problem for everyone and so this should not be an issue +for most users. For some applications you might with to turn it off. + child = pexpect.spawn ("ssh user@example.com") + child.delaybeforesend = 0 +

+


+

+

Try changing it to look like the following. I know that this fix +does not look correct, but it works. I have not figured out exactly +what is happening. You would think that the sleep should be after the +sendline(). The fact that the sleep helps when it's between the +expect() and the sendline() must be a clue.

+
child.expect ('[pP]assword:')
child.sendline (my_password)
+

Timing issue with isalive()

+

Reading the state of isalive() immediately after a child exits may +sometimes return 1. This is a race condition. The child has closed its +file descriptor, but has not yet fully exited before Pexpect's +isalive() executes. Addings a slight delay before the isalive() will +help. In the following example isalive() +sometimes returns 1:

+
+
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
print child.isalive()
+
+

But if there is any delay before the call to isalive() +then it will always return 0 as expected.

+
+
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
time.sleep(0.1)
print child.isalive()
+
+ +

Truncated output just before child exits

+

So far I have seen this only on older versions of Apple's MacOS X. +If the child application quits it may not flush its output buffer. This +means that your Pexpect application will receive an EOF even though it +should have received a little more data before the child died. This is +not generally a problem when talking to interactive child applications. +One example where it is a problem is when trying to read output from a +program like 'ls'. You may receive most of +the directory listing, but the last few lines will get lost before you +receive an EOF. The reason for this is that 'ls' +runs; completes its task; and then exits. The buffer is not flushed +before exit so the last few lines are lost. The following example +demonstrates the problem:

+

+
+
child = pexpect.spawn ('ls -l')
child.expect (pexpect.EOF)
print child.before
+
+

+ +

Controlling SSH on Solaris

+

Pexpect does not yet work perfectly on Solaris. +One common problem is that SSH sometimes will not allow TTY password +authentication. For example, you may expect SSH to ask you for a +password using code like this: +

+
child = pexpect.spawn ('ssh user@example.com')
child.expect ('assword')
child.sendline ('mypassword')
+You may see the following error come back from a spawned +child SSH: +

+
Permission denied (publickey,keyboard-interactive).
+

+This means that SSH thinks it can't access the TTY to ask you for your +password. +The only solution I have found is to use public key authentication with +SSH. +This bypasses the need for a password. I'm not happy with this +solution. +The problem is due to poor support for Solaris Pseudo TTYs in the +Python +Standard Library.

+
+

CHANGES

+

Current Release

+

Fixed OSError exception when a pexpect object is cleaned up. +Previously you might have seen this exception:

+
+
Exception exceptions.OSError: (10, 'No child processes') 
in <bound method spawn.__del__ of
<pexpect.spawn instance at 0xd248c>> ignored
+
+

You should not see that anymore. Thanks to Michael Surette.

+

Added support for buffering reads. This greatly improves speed when +trying to match long output from a child process. When you create an +instance of the spawn object you can then set a buffer size. For now +you MUST do the following to turn on buffering -- it may be on by +default in future version.

+
+
child = pexpect.spawn ('my_command')
child.maxread=1000 # Sets buffer to 1000 characters.
+
+
+

I made a subtle change to the way TIMEOUT and EOF exceptions behave. +Previously you could either expect these states in which case pexpect +will not raise an exception, or you could just let pexpect raise an +exception when these states were encountered. If you expected the +states then the 'before' property was set to everything before the +state was encountered, but if you let pexpect raise the exception then +'before' was not set. Now the 'before' property will get set either way +you choose to handle these states.

+

Older changes...

+

The spawn object now provides iterators for a file-like interface. +This makes Pexpect a more complete file-like object. You can now write +code like this:

+
+
child = pexpect.spawn ('ls -l')
for line in child:
print line
+
+

I added the attribute exitstatus. This +will give the exit code returned by the child process. This will be set +to None while the child is still alive. When +isalive() returns 0 then exitstatus +will be set.

+

I made a few more tweaks to isalive() so +that it will operate more consistently on different platforms. Solaris +is the most difficult to support.

+

 

+

You can now put TIMEOUT in a list of +expected patterns. This is just like putting EOF +in the pattern list. Expecting for a TIMEOUT +may not be used as often as EOF, but this +makes Pexpect more consitent.

+

Thanks to a suggestion and sample code from Chad J. Schroeder I +added the ability for Pexpect to operate on a file descriptor that is +already open. This means that Pexpect can be used to control streams +such as those from serial port devices. Now you just pass the integer +file descriptor as the "command" when contsructing a spawn open. For +example on a Linux box with a modem on ttyS1:

+
+
fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)
m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.
m.send("+++") # Escape sequence
m.send("ATZ0\r") # Reset modem to profile 0
rval = m.expect(["OK", "ERROR"])
+
+

Pexpect now tests itself on Compile Farm!

+

I wrote a nice script that uses ssh to connect to each machine on +Source Forge's Compile Farm and then run the testall.py script for each +platform. The result of the test is then recorded for each platform. +Now it's easy to run regression tests across multiple platforms.

+

Pexpect is a file-like object

+

The spawn object now provides a file-like interface. It +supports most of the methods and attributes defined for Python File +Objects.

+

I changed write and writelines() so that they no longer return a +value. Use send() if you need that functionality. I did this to make +the Spawn object more closely match a file-like object.

+

read() was renamed to read_nonblocking(). I added a new read() +method that matches file-like object interface. In general, you should +not notice the difference except that read() no longer allows you to +directly set the timeout value. I hope this will not effect any +existing code. Switching to read_nonblocking() should fix existing code.

+

I changed the name of set_echo() to setecho().

+

I changed the name of send_eof() to sendeof().

+

I modified kill() so that it checks to +make sure the pid isalive().

+

I modified spawn() (really called from __spawn())so that it does not raise an expection +if setwinsize() fails. Some platforms such +as Cygwin do not like setwinsize. This was a constant problem and since +it is not a critical feature I decided to just silence the error. +Normally I don't like to do that, but in this case I'm making an +exception.

+

Added a method close() that does what you +think. It closes the file descriptor of the child application. It makes +no attempt to actually kill the child or wait for its status.

+

Add variables __version__ and __revision__ (from cvs) to the pexpect modules. +This is mainly helpful to me so that I can make sure that I'm testing +with the right version instead of one already installed.

+

Logging changes

+
+

log_open() and log_close() +have been removed. Now use setlog(). The setlog() method takes a file object. This is far +more flexible than the previous log method. Each time data is written +to the file object it will be flushed. To turn logging off simply call setlog() with None.

+
+

isalive changes

+
+

I renamed the isAlive() method to isalive() to match the more typical naming style +in Python. Also the technique used to detect child process status has +been drastically modified. Previously I did some funky stuff with +signals which caused indigestion in other Python modules on some +platforms. It's was a big headache. It still is, but I think it works +better now.

+
+

attribute name changes

+
+

The names of some attributes have been changed. This effects the +names of the attributes that are set after called the expect() method.

+ + + + + + + + + + + + + + + + + + + +
NEW NAMEOLD NAME
before
+ Everything before the match.
before
after
+ Everything after and including the first character of the +match
matched
match
+ This is the re MatchObject from the match.
+You can get groups() from this.
+See 'uptime.py' in the examples tar ball.
New -- Did not exist
+
+

EOF changes

+
+

The expect_eof() method is gone. You +can now simply use the expect() method to +look for EOF.

+

Was:

+
+

p.expect_eof ()

+
+

Now:

+
+

p.expect (pexpect.EOF)

+
+
+
+

TESTING

+

The following platforms have been tested:

+ +

 

+

TO DO

+

Add an option to add a delay after each expect() or before each +read()/readline() call to automatically avoid the echo +bug.

+

 

+
+
+ + + + + + +
Click to send email.
+
+ + + diff --git a/src/link/pexpect/doc/index.template.html b/src/link/pexpect/doc/index.template.html new file mode 100644 index 0000000..9236df1 --- /dev/null +++ b/src/link/pexpect/doc/index.template.html @@ -0,0 +1,868 @@ + + + +Pexpect - a Pure Python Expect-like module + + + + + + + + +
+

Pexpect makes Python a better tool for controlling other +applications.

+

Pexpect is a pure Python module for spawning child applications; +controlling them; and responding to expected patterns in their output. +Pexpect works like Don Libes' Expect. Pexpect allows your script to +spawn a child application and control it as if a human were typing +commands.

+

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. Unlike +other Expect-like modules for Python, Pexpect does not require TCL or +Expect nor does it require C extensions to be compiled. It should work +on any platform that supports the standard Python pty module. The +Pexpect interface was designed to be easy to use.

+ + + + + + + +
Send questions to:Click to send email.
+
+

License: MIT style

+

+Free, open source, and all that good stuff.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Pexpect Copyright (c) 2008 Noah Spurrier
+http://pexpect.sourceforge.net/ +

+ +
+

Download

+

Download the +current version here from the SourceForge site. Grab the current Pexpect tarball. +

+

Installing Pexpect

+

The Pexpect tarball is a standard Python Distutil distribution.

+
    +
  1. download pexpect-VERSION.tar.gz
  2. +
  3. tar zxf pexpect-VERSION.tar.gz
  4. +
  5. cd pexpect-VERSION
  6. +
  7. python setup.py install do this as root
  8. +
+

Examples

+

+Under the pexpect-VERSION directory you should find +the examples directory. +This is the best way to learn to use Pexpect. +See the descriptions of Pexpect Examples. +

+

API Documentation

+

+

+pexpect This is the main module that you want.
+pxssh Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.
+
+the following are experimental extensions to Pexpect
+
+fdpexpect fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.
+SCREEN This represents a virtual 'screen'.
+ANSI This parses ANSI/VT-100 terminal escape codes.
+FSM This is a finite state machine used by ANSI.
+
+

+
+

Project Status

+

Automated pyunit tests reach over 80% +code coverage on pexpect.py. I regularly test on Linux and BSD +platforms. I try to test on Solaris and Irix. +

+
+

Requirements for use of Pexpect

+

Python

+
+

Pexpect was written and tested with Python 2.4. It should work on +earlier versions that have the pty module. I +sometimes even manually test it with Python 1.5.2, but I can't easily +run the PyUnit test framework against Python 1.5.2, so I have less +confidence in Pexpect on Python 1.5.2.

+
+

pty module

+
+

Any POSIX system (UNIX) with a working pty +module should be able to run Pexpect. The pty +module is part of the Standard Python Library, so if you are running on +a POSIX system you should have it. The pty +module does not run the same on all platforms. It should be solid on Linux +and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more +about the wrinkles see Bugs and Testing.

+
+

Pexpect does not currently work on the standard Windows Python (see +the pty requirement); however, it seems to work fine using Cygwin. It is possible to build +something like a pty for Windows, but it would have to use a different +technique that I am still investigating. I know it's possible because +Libes' Expect was ported to Windows. If you have any ideas or +skills to contribute in this area then I would really appreciate some +tips on how to approach this problem.

+
+

Overview

+

Pexpect can be used for automating interactive applications such as +ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be +easy to use. Here is an example of Pexpect in action:

+
+
# This connects to the openbsd ftp site and
# downloads the recursive directory listing.
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('cd pub')
child.expect('ftp> ')
child.sendline ('get ls-lR.gz')
child.expect('ftp> ')
child.sendline ('bye')
+
+

Obviously you could write an ftp client using Python's own ftplib module, but this is just a demonstration. +You can use this technique with any application. This is especially +handy if you are writing automated test tools.

+ +

There are two important methods in Pexpect -- expect() +and send() (or sendline() +which is like send() with a linefeed). +The expect() method waits for the child application +to return a given string. The string you specify is a regular expression, so +you can match complicated patterns. The send() method +writes a string to the child application. From the child's point of +view it looks just like someone typed the text from a terminal. After +each call to expect() the before and after +properties will be set to the text printed by child application. The before property will contain all text up to +the expected string pattern. The after string +will contain the text that was matched by the expected pattern. +The match property is set to the re MatchObject. +

+ +

An example of Pexpect in action may make things more clear. This example uses +ftp to login to the OpenBSD site; list files +in a directory; and then pass interactive control of the ftp session to +the human user.

+
+
import pexpect
child = pexpect.spawn ('ftp ftp.openbsd.org')
child.expect ('Name .*: ')
child.sendline ('anonymous')
child.expect ('Password:')
child.sendline ('noah@example.com')
child.expect ('ftp> ')
child.sendline ('ls /pub/OpenBSD/')
child.expect ('ftp> ')
print child.before # Print the result of the ls command.
child.interact() # Give control of the child to the user.
+
+

Special EOF and TIMEOUT patterns

+

+There are two special patterns to match the End Of File or a Timeout condition. +You you can pass these patterns to expect(). +These patterns are not regular expressions. Use them like predefined constants. +

+

If the child has died and you have read all the child's output then ordinarily +expect() will raise an EOF +exception. You can read everything up to the EOF without generating an +exception by using the EOF pattern expect(pexpect.EOF). +In this case everything the child has output will be available in the before property.

+

The pattern given to expect() may be a +regular expression or it may also be a list of regular expressions. +This allows you to match multiple optional responses. The expect() +method returns the index of the pattern that was matched. For example, +say you wanted to login to a server. After entering a password you +could get various responses from the server -- your password could be +rejected; or you could be allowed in and asked for your terminal type; +or you could be let right in and given a command prompt. The following +code fragment gives an example of this:

+
+
child.expect('password:')
child.sendline (my_secret_password)
# We expect any of these three patterns...
i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
if i==0:
print 'Permission denied on host. Can't login'
child.kill(0)
elif i==2:
print 'Login OK... need to send terminal type.'
child.sendline('vt100')
child.expect ('[#\$] ')
elif i==3:
print 'Login OK.'
print 'Shell command prompt', child.after
+
+

If nothing matches an expected pattern then expect will eventually +raise a TIMEOUT exception. The default time is 30 seconds, but you can +change this by passing a timeout argument to expect():

+
+
# Wait no more than 2 minutes (120 seconds) for password prompt.
child.expect('password:', timeout=120)
+
+

Find the end of line -- CR/LF conventions
+Matching at the end of a line can be tricky
+$ regex pattern is useless.
+

+

Pexpect matches regular expressions a little differently than what +you might be used to. +

+

The $ pattern for end of line match is useless. +The $ matches the end of string, but Pexpect reads from the child +one character at a time, so each character looks like the end of a line. +Pexpect can't do a look-ahead into the child's output stream. +In general you would have this situation when using regular expressions +with any stream.
+Note, pexpect does have an internal buffer, so reads are faster +than one character at a time, but from the user's perspective the regex +patterns test happens one character at a time.

+

The best way to match the end of a line is to look for the +newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style. +It may surprise some UNIX people to learn that terminal TTY device drivers +(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify +the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so +when the child app prints "\n" you actually see "\r\n". +

+

UNIX uses just linefeeds to end lines of text, but not when it +comes to TTY devices! TTY devices are more like the Windows world. +Each line of text end with a CR/LF combination. When you intercept data +from a UNIX command from a TTY device you will find that the TTY device +outputs a CR/LF combination. A UNIX command may only write a linefeed +(\n), but the TTY device driver converts it to CR/LF. This means that +your terminal will see lines end with CR/LF (hex 0D 0A). +Since Pexpect emulates a terminal, to match ends of lines you have to +expect the CR/LF combination.

+
+

child.expect ('\r\n')

+
+

If you just need to skip past a new line then expect +('\n') by itself will work, but if you are expecting a specific +pattern before the end of line then you need to explicitly look for the +\r. For example the following expects a word at the end of a line:

+
+

child.expect ('\w+\r\n')

+
+

But the following would both fail:

+
+

child.expect ('\w+\n')

+
+

And as explained before, trying to use '$' to match the end of line +would not work either:

+
+

child.expect ('\w+$')

+
+

So if you need to explicitly look for the END OF LINE, you want to +look for the CR/LF combination -- not just the LF and not the $ pattern.

+

This problem is not limited to Pexpect. This problem happens any +time you try to perform a regular expression match on a stream. Regular +expressions need to look ahead. With a stream it is hard to look ahead +because the process generating the stream may not be finished. There is no +way to know if the process has paused momentarily or is finished and +waiting for you. Pexpect must implicitly always +do a NON greedy match (minimal) at the end of a input {### already said +this}.

+

Pexpect compiles all regular expressions with the DOTALL flag. With +the DOTALL flag a "." will match a newline. See the Python documentation

+

Beware of + and * at the end of input.

+

Remember that any time you try to match a pattern that needs +look-ahead that you will always get a minimal match (non greedy). For +example, the following will always return just one character:

+
+

child.expect ('.+')

+
+

This example will match successfully, but will always return no +characters:

+
+

child.expect ('.*')

+
+

Generally any star * expression will match as little as possible

+

One thing you can do is to try to force a non-ambiguous character at +the end of your \d+ pattern. Expect that +character to delimit the string. For example, you might try making the +end of your pattrn be \D+ instead of \D*. That means number digits alone would not +satisfy the (\d+) pattern. You would need +some number(s) and at least one \D at the +end.

+

Matching groups

+

You can group regular expression using parenthesis. After a match, +the match parameter of the spawn object will +contain the Python Match object.

+

Examples

+

Using "match" and groups...

+

Debugging

+

If you get the string value of a pexpect.spawn object you will get +lots of useful debugging information. For debugging it's very useful to +use the following pattern:

+

try:
+    i = child.expect ([pattern1, pattern2, pattern3, +etc])
+except:
+    print "Exception was thrown"
+    print "debug information:"
+    print str(child)
+

+

It is also useful to log the child's input and out to a file or the +screen. The following will turn on logging and send output to stdout +(the screen).
+

+

    child = pexpect.spawn (foo)
+    child.logfile = sys.stdout
+
+

+
+

Exceptions

+

EOF

+

Note that two flavors of EOF Exception may be thrown. They are +virtually identical except for the message string. For practical +purposes you should have no need to distinguish between them, but they +do give a little extra information about what type of platform you are +running. The two messages are:

+
+

End Of File (EOF) in read(). Exception style platform.

+

End Of File (EOF) in read(). Empty string style +platform.

+
+

Some UNIX platforms will throw an exception when you try to read +from a file descriptor in the EOF state. Other UNIX platforms instead +quietly return an empty string to indicate that the EOF state has been +reached.

+

Expecting EOF

+

If you wish to read up to the end of the child's output without +generating an EOF exception then use the expect(pexpect.EOF) method.

+

TIMEOUT

+

The expect() and read() +methods will also timeout if the child does not generate any output for +a given amount of time. If this happens they will raise a TIMEOUT exception. You can have these method +ignore a timeout and block indefinitely by passing None for the timeout +parameter.

+
+

child.expect(pexpect.EOF, timeout=None)

+
+
+

FAQ

+

Q: Why don't shell pipe and redirect (| and >) work when I +spawn a command?

+

+ +A: Remember that Pexpect does NOT interpret shell meta characters such as +redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the +command you are spawning. 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 > log_list.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 > log_list.txt'
+    child = pexpect.spawn ('/bin/bash', ['-c', shell_cmd])
+    child.expect (pexpect.EOF)
+
+ +

+

Q: Isn't there already a Python Expect?

+

A: Yes, there are several of them. They usually require you to +compile C. I wanted something that was pure Python and preferably a +single module that was simple to install. I also wanted something that +was easy to use. This pure Python expect only recently became possible +with the introduction of the pty module in the standard Python library. +Previously C extensions were required.

+ +

Q: The before and after properties sound weird.

+

Originally I was going to model Pexpect more after Expect, but then +I found that I could never remember how to get the context of the stuff +I was trying to parse. I hate having to read my own documentation. I +decided that it was easier for me to remember what before and after +was. It just so happens that this is how the -B and -A options in grep +works, so that made it even easier for me to remember. Whatever makes +my life easier is what's best.

+ +

Q: Why not just use Expect?

+

A: I love it. It's great. I has bailed me out of some real jams, but +I wanted something that would do 90% of what I need from Expect; be 10% +of the size; and allow me to write my code in Python instead of TCL. +Pexpect is not nearly as big as Expect, but Pexpect does everything I +have ever used Expect for. +

+ +

Q: Why not just use a pipe (popen())?

+

A: A pipe works fine for getting the output to non-interactive +programs. If you just want to get the output from ls, +uname, or ping +then this works. Pipes do not work very well for interactive programs +and pipes will almost certainly fail for most applications that ask for +passwords such as telnet, ftp, or ssh.

+

There are two reasons for this.

+

First an application may bypass stdout and print directly to its +controlling TTY. Something like SSH will do this when it asks you for a +password. This is why you cannot redirect the password prompt because +it does not go through stdout or stderr.

+

The second reason is because most applications are built using the C +Standard IO Library (anything that uses #include +<stdio.h>). One of the features of the stdio library is +that it buffers all input and output. Normally output is line +buffered when a program is printing to a TTY (your terminal +screen). Every time the program prints a line-feed the currently +buffered data will get printed to your screen. The problem comes when +you connect a pipe. The stdio library is smart and can tell that it is +printing to a pipe instead of a TTY. In that case it switches from line +buffer mode to block buffered. In this mode the +currently buffered data is flushed when the buffer is full. This causes +most interactive programs to deadlock. Block buffering is more +efficient when writing to disks and pipes. Take the situation where a +program prints a message "Enter your user name:\n" and then waits for +you type type something. In block buffered mode, the stdio library will +not put the message into the pipe even though a linefeed is printed. +The result is that you never receive the message, yet the child +application will sit and wait for you to type a response. Don't confuse +the stdio lib's buffer with the pipe's buffer. The pipe buffer is +another area that can cause problems. You could flush the input side of +a pipe, whereas you have no control over the stdio library buffer.

+

More information: the Standard IO library has three states for a +FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered; +and _IONBF for unbuffered. The STDIO lib will use block buffering when +talking to a block file descriptor such as a pipe. This is usually not +helpful for interactive programs. Short of recompiling your program to +include fflush() everywhere or recompiling a custom stdio library there +is not much a controlling application can do about this if talking over +a pipe.

+

The program may have put data in its output that remains unflushed +because the output buffer is not full; then the program will go and +deadlock while waiting for input -- because you never send it any +because you are still waiting for its output (still stuck in the +STDIO's output buffer).

+

The answer is to use a pseudo-tty. A TTY device will force line +buffering (as opposed to block buffering). Line buffering means that +you will get each line when the child program sends a line feed. This +corresponds to the way most interactive programs operate -- send a line +of output then wait for a line of input.

+

I put "answer" in quotes because it's ugly solution and because +there is no POSIX standard for pseudo-TTY devices (even though they +have a TTY standard...). What would make more sense to me would be to +have some way to set a mode on a file descriptor so that it will tell +the STDIO to be line-buffered. I have investigated, and I don't think +there is a way to set the buffered state of a child process. The STDIO +Library does not maintain any external state in the kernel or whatnot, +so I don't think there is any way for you to alter it. I'm not quite +sure how this line-buffered/block-buffered state change happens +internally in the STDIO library. I think the STDIO lib looks at the +file descriptor and decides to change behavior based on whether it's a +TTY or a block file (see isatty()).

+

I hope that this qualifies as helpful.

+ +

Don't use a pipe to control another application...

+

Pexpect may seem similar to os.popen() or +commands module. The main difference is that +Pexpect (like Expect) uses a pseudo-TTY to talk to the child +application. Most applications do no work well through the system() +call or through pipes. And probably all applications that ask a user to +type in a password will fail. These applications bypass the stdin and +read directly from the TTY device. Many applications do not explicitly +flush their output buffers. This causes deadlocks if you try to control +an interactive application using a pipe. What happens is that most UNIX +applications use the stdio (#include <stdio.h>) for input and +output. The stdio library behaves differently depending on where the +output is going. There is no way to control this behavior from the +client end.
+

+ +

Q: Can I do screen scraping with this thing?

+

A: That depends. If your application just does line-oriented output +then this is easy. If it does screen-oriented output then it may work, +but it could be hard. For example, trying to scrape data from the 'top' +command would be hard. The top command repaints the text window.

+

I am working on an ANSI / VT100 terminal emulator that will have +methods to get characters from an arbitrary X,Y coordinate of the +virtual screen. It works and you can play with it, but I have no +working examples at this time.

+
+

Bugs

+

Threads

+

On Linux (RH 8) you cannot spawn a child from a different thread and +pass the handle back to a worker thread. The child is successfully +spawned but you can't interact with it. The only way to make it work is +to spawn and interact with the child all in the same thread. [Adam +Kerrison]

+

Timing issue with send() and sendline()

+

This problem has been addressed and should not effect most users.

+

It is sometimes possible to read an echo of the string sent with send() and sendline(). +If you call sendline() and then immediately +call readline() you may get part of your +output echoed back. You may read back what you just wrote even if the +child application does not explicitly echo it. Timing is critical. This +could be a security issue when talking to an application that asks for +a password; otherwise, this does not seem like a big deal. But why +do TTYs do this?

+

People usually report this when they are trying to control SSH or +some other login. For example, if your code looks something like this:

+
child.expect ('[pP]assword:')
child.sendline (my_password)
+


+

+1. SSH prints "password:" prompt to the user.
+2. SSH turns off echo on the TTY device.
+3. SSH waits for user to enter a password.
+
+When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt +before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between +steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug. +

+

+Pexpect now automatically adds a short delay before sending data to a child process. +This more closely mimics what happens in the usual human-to-app interaction. +The delay can be tuned with the 'delaybeforesend' attribute of the spawn class. +In general, this fixes the problem for everyone and so this should not be an issue +for most users. For some applications you might with to turn it off. + child = pexpect.spawn ("ssh user@example.com") + child.delaybeforesend = 0 +

+


+

+

Try changing it to look like the following. I know that this fix +does not look correct, but it works. I have not figured out exactly +what is happening. You would think that the sleep should be after the +sendline(). The fact that the sleep helps when it's between the +expect() and the sendline() must be a clue.

+
child.expect ('[pP]assword:')
child.sendline (my_password)
+

Timing issue with isalive()

+

Reading the state of isalive() immediately after a child exits may +sometimes return 1. This is a race condition. The child has closed its +file descriptor, but has not yet fully exited before Pexpect's +isalive() executes. Addings a slight delay before the isalive() will +help. In the following example isalive() +sometimes returns 1:

+
+
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
print child.isalive()
+
+

But if there is any delay before the call to isalive() +then it will always return 0 as expected.

+
+
child = pexpect.spawn('ls')
child.expect(pexpect.EOF)
time.sleep(0.1)
print child.isalive()
+
+ +

Truncated output just before child exits

+

So far I have seen this only on older versions of Apple's MacOS X. +If the child application quits it may not flush its output buffer. This +means that your Pexpect application will receive an EOF even though it +should have received a little more data before the child died. This is +not generally a problem when talking to interactive child applications. +One example where it is a problem is when trying to read output from a +program like 'ls'. You may receive most of +the directory listing, but the last few lines will get lost before you +receive an EOF. The reason for this is that 'ls' +runs; completes its task; and then exits. The buffer is not flushed +before exit so the last few lines are lost. The following example +demonstrates the problem:

+

+
+
child = pexpect.spawn ('ls -l')
child.expect (pexpect.EOF)
print child.before
+
+

+ +

Controlling SSH on Solaris

+

Pexpect does not yet work perfectly on Solaris. +One common problem is that SSH sometimes will not allow TTY password +authentication. For example, you may expect SSH to ask you for a +password using code like this: +

+
child = pexpect.spawn ('ssh user@example.com')
child.expect ('assword')
child.sendline ('mypassword')
+You may see the following error come back from a spawned +child SSH: +

+
Permission denied (publickey,keyboard-interactive).
+

+This means that SSH thinks it can't access the TTY to ask you for your +password. +The only solution I have found is to use public key authentication with +SSH. +This bypasses the need for a password. I'm not happy with this +solution. +The problem is due to poor support for Solaris Pseudo TTYs in the +Python +Standard Library.

+
+

CHANGES

+

Current Release

+

Fixed OSError exception when a pexpect object is cleaned up. +Previously you might have seen this exception:

+
+
Exception exceptions.OSError: (10, 'No child processes') 
in <bound method spawn.__del__ of
<pexpect.spawn instance at 0xd248c>> ignored
+
+

You should not see that anymore. Thanks to Michael Surette.

+

Added support for buffering reads. This greatly improves speed when +trying to match long output from a child process. When you create an +instance of the spawn object you can then set a buffer size. For now +you MUST do the following to turn on buffering -- it may be on by +default in future version.

+
+
child = pexpect.spawn ('my_command')
child.maxread=1000 # Sets buffer to 1000 characters.
+
+
+

I made a subtle change to the way TIMEOUT and EOF exceptions behave. +Previously you could either expect these states in which case pexpect +will not raise an exception, or you could just let pexpect raise an +exception when these states were encountered. If you expected the +states then the 'before' property was set to everything before the +state was encountered, but if you let pexpect raise the exception then +'before' was not set. Now the 'before' property will get set either way +you choose to handle these states.

+

Older changes...

+

The spawn object now provides iterators for a file-like interface. +This makes Pexpect a more complete file-like object. You can now write +code like this:

+
+
child = pexpect.spawn ('ls -l')
for line in child:
print line
+
+

I added the attribute exitstatus. This +will give the exit code returned by the child process. This will be set +to None while the child is still alive. When +isalive() returns 0 then exitstatus +will be set.

+

I made a few more tweaks to isalive() so +that it will operate more consistently on different platforms. Solaris +is the most difficult to support.

+

 

+

You can now put TIMEOUT in a list of +expected patterns. This is just like putting EOF +in the pattern list. Expecting for a TIMEOUT +may not be used as often as EOF, but this +makes Pexpect more consitent.

+

Thanks to a suggestion and sample code from Chad J. Schroeder I +added the ability for Pexpect to operate on a file descriptor that is +already open. This means that Pexpect can be used to control streams +such as those from serial port devices. Now you just pass the integer +file descriptor as the "command" when contsructing a spawn open. For +example on a Linux box with a modem on ttyS1:

+
+
fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)
m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.
m.send("+++") # Escape sequence
m.send("ATZ0\r") # Reset modem to profile 0
rval = m.expect(["OK", "ERROR"])
+
+

Pexpect now tests itself on Compile Farm!

+

I wrote a nice script that uses ssh to connect to each machine on +Source Forge's Compile Farm and then run the testall.py script for each +platform. The result of the test is then recorded for each platform. +Now it's easy to run regression tests across multiple platforms.

+

Pexpect is a file-like object

+

The spawn object now provides a file-like interface. It +supports most of the methods and attributes defined for Python File +Objects.

+

I changed write and writelines() so that they no longer return a +value. Use send() if you need that functionality. I did this to make +the Spawn object more closely match a file-like object.

+

read() was renamed to read_nonblocking(). I added a new read() +method that matches file-like object interface. In general, you should +not notice the difference except that read() no longer allows you to +directly set the timeout value. I hope this will not effect any +existing code. Switching to read_nonblocking() should fix existing code.

+

I changed the name of set_echo() to setecho().

+

I changed the name of send_eof() to sendeof().

+

I modified kill() so that it checks to +make sure the pid isalive().

+

I modified spawn() (really called from __spawn())so that it does not raise an expection +if setwinsize() fails. Some platforms such +as Cygwin do not like setwinsize. This was a constant problem and since +it is not a critical feature I decided to just silence the error. +Normally I don't like to do that, but in this case I'm making an +exception.

+

Added a method close() that does what you +think. It closes the file descriptor of the child application. It makes +no attempt to actually kill the child or wait for its status.

+

Add variables __version__ and __revision__ (from cvs) to the pexpect modules. +This is mainly helpful to me so that I can make sure that I'm testing +with the right version instead of one already installed.

+

Logging changes

+
+

log_open() and log_close() +have been removed. Now use setlog(). The setlog() method takes a file object. This is far +more flexible than the previous log method. Each time data is written +to the file object it will be flushed. To turn logging off simply call setlog() with None.

+
+

isalive changes

+
+

I renamed the isAlive() method to isalive() to match the more typical naming style +in Python. Also the technique used to detect child process status has +been drastically modified. Previously I did some funky stuff with +signals which caused indigestion in other Python modules on some +platforms. It's was a big headache. It still is, but I think it works +better now.

+
+

attribute name changes

+
+

The names of some attributes have been changed. This effects the +names of the attributes that are set after called the expect() method.

+ + + + + + + + + + + + + + + + + + + +
NEW NAMEOLD NAME
before
+ Everything before the match.
before
after
+ Everything after and including the first character of the +match
matched
match
+ This is the re MatchObject from the match.
+You can get groups() from this.
+See 'uptime.py' in the examples tar ball.
New -- Did not exist
+
+

EOF changes

+
+

The expect_eof() method is gone. You +can now simply use the expect() method to +look for EOF.

+

Was:

+
+

p.expect_eof ()

+
+

Now:

+
+

p.expect (pexpect.EOF)

+
+
+
+

TESTING

+

The following platforms have been tested:

+ +

 

+

TO DO

+

Add an option to add a delay after each expect() or before each +read()/readline() call to automatically avoid the echo +bug.

+

 

+
+
+ + + + + + +
Click to send email.
+
+ + + diff --git a/src/link/pexpect/doc/pexpect.html b/src/link/pexpect/doc/pexpect.html new file mode 100644 index 0000000..0857e0d --- /dev/null +++ b/src/link/pexpect/doc/pexpect.html @@ -0,0 +1,874 @@ + + +Python: module pexpect + + + + +
 
+ 
pexpect (version 2.3)
index
/home/noah/pexpect/trunk/pexpect/pexpect.py
+

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 Pexpect -- the function, run() and the class,
+spawn. You can call the run() function to execute a command and return the
+output. This is a handy replacement for os.system().

+For example::

+    pexpect.run('ls -la')

+The more powerful interface is the spawn class. You can use this to spawn an
+external child command and then interact with the child by sending lines and
+expecting responses.

+For example::

+    child = pexpect.spawn('scp foo myname@host.example.com:.')
+    child.expect ('Password:')
+    child.sendline (mypassword)

+This works even for commands that ask for passwords or other input outside of
+the normal stdio streams.

+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,
+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 (Let me know if I forgot anyone.)

+Free, open source, and all that good stuff.

+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:

+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+Pexpect Copyright (c) 2008 Noah Spurrier
+http://pexpect.sourceforge.net/

+$Id: pexpect.py 507 2007-12-27 02:40:52Z noah $

+

+ + + + + +
 
+Modules
       
errno
+fcntl
+os
+pty
+
re
+resource
+select
+signal
+
string
+struct
+sys
+termios
+
time
+traceback
+tty
+types
+

+ + + + + +
 
+Classes
       
+
__builtin__.object +
+
+
spawn +
+
+
exceptions.Exception(exceptions.BaseException) +
+
+
ExceptionPexpect +
+
+
EOF +
TIMEOUT +
+
+
+
+
+

+ + + + + + + +
 
+class EOF(ExceptionPexpect)
   Raised when EOF is read from a child. This usually means the child has exited.
 
 
Method resolution order:
+
EOF
+
ExceptionPexpect
+
exceptions.Exception
+
exceptions.BaseException
+
__builtin__.object
+
+
+Methods inherited from ExceptionPexpect:
+
__init__(self, value)
+ +
__str__(self)
+ +
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.
+ +
+Data descriptors inherited from ExceptionPexpect:
+
__weakref__
+
list of weak references to the object (if defined)
+
+
+Data and other attributes inherited from exceptions.Exception:
+
__new__ = <built-in method __new__ of type object at 0x81400e0>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+Methods inherited from exceptions.BaseException:
+
__delattr__(...)
x.__delattr__('name') <==> del x.name
+ +
__getattribute__(...)
x.__getattribute__('name') <==> x.name
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__reduce__(...)
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
+ +
__setstate__(...)
+ +
+Data descriptors inherited from exceptions.BaseException:
+
__dict__
+
+
args
+
+
message
+
exception message
+
+

+ + + + + + + +
 
+class ExceptionPexpect(exceptions.Exception)
   Base class for all exceptions raised by this module.
 
 
Method resolution order:
+
ExceptionPexpect
+
exceptions.Exception
+
exceptions.BaseException
+
__builtin__.object
+
+
+Methods defined here:
+
__init__(self, value)
+ +
__str__(self)
+ +
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.
+ +
+Data descriptors defined here:
+
__weakref__
+
list of weak references to the object (if defined)
+
+
+Data and other attributes inherited from exceptions.Exception:
+
__new__ = <built-in method __new__ of type object at 0x81400e0>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+Methods inherited from exceptions.BaseException:
+
__delattr__(...)
x.__delattr__('name') <==> del x.name
+ +
__getattribute__(...)
x.__getattribute__('name') <==> x.name
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__reduce__(...)
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
+ +
__setstate__(...)
+ +
+Data descriptors inherited from exceptions.BaseException:
+
__dict__
+
+
args
+
+
message
+
exception message
+
+

+ + + + + + + +
 
+class TIMEOUT(ExceptionPexpect)
   Raised when a read time exceeds the timeout.
 
 
Method resolution order:
+
TIMEOUT
+
ExceptionPexpect
+
exceptions.Exception
+
exceptions.BaseException
+
__builtin__.object
+
+
+Methods inherited from ExceptionPexpect:
+
__init__(self, value)
+ +
__str__(self)
+ +
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.
+ +
+Data descriptors inherited from ExceptionPexpect:
+
__weakref__
+
list of weak references to the object (if defined)
+
+
+Data and other attributes inherited from exceptions.Exception:
+
__new__ = <built-in method __new__ of type object at 0x81400e0>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+Methods inherited from exceptions.BaseException:
+
__delattr__(...)
x.__delattr__('name') <==> del x.name
+ +
__getattribute__(...)
x.__getattribute__('name') <==> x.name
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__reduce__(...)
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
+ +
__setstate__(...)
+ +
+Data descriptors inherited from exceptions.BaseException:
+
__dict__
+
+
args
+
+
message
+
exception message
+
+

+ + + + + + + +
 
+class spawn(__builtin__.object)
   This is the main class interface for Pexpect. Use this class to start
+and control child applications.
 
 Methods defined here:
+
__del__(self)
This makes sure that no system resources are left open. Python only
+garbage collects Python objects. OS file descriptors are not Python
+objects, so they must be handled explicitly. If the child file
+descriptor was opened outside of this class (passed to the constructor)
+then this does not close it.
+ +
__init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None)
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 user@example.com')
+    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', ['user@example.com'])
+    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 > log_list.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 > log_list.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.

+The searchwindowsize attribute sets the how far back in the incomming
+seach buffer Pexpect will search for pattern matches. Every time
+Pexpect reads some data from the child it will append the data to the
+incomming buffer. The default is to search from the beginning of the
+imcomming buffer each time new data is read from the child. But this is
+very inefficient if you are running a command that generates a large
+amount of data where you want to match The searchwindowsize does not
+effect the size of the incomming data buffer. You will still have
+access to the full buffer after expect() returns.

+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 = file('mylog.txt','w')
+    child.logfile = fout

+Example log to stdout::

+    child = pexpect.spawn('some_command')
+    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

+To separately log output sent to the child use logfile_send::

+    self.logfile_send = fout

+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 0 to return to the old behavior. Most Linux machines
+don't like this to be below 0.03. I don't know why.

+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.
+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.
+ +
__iter__(self)
This is to support iterators over a file-like object.
+ +
__str__(self)
This returns a human-readable string that represents the state of
+the object.
+ +
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).
+ +
compile_pattern_list(self, patterns)
This compiles a pattern-string or a list of pattern-strings.
+Patterns must be a StringType, EOFTIMEOUT, 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 = compile_pattern_list(pl)
+     return 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 = compile_pattern_list(my_pattern)
+     while some_condition:
+        ...
+        i = expect_list(clp, timeout)
+        ...
+ +
eof(self)
This returns True if the EOF exception was ever raised.
+ +
expect(self, pattern, timeout=-1, searchwindowsize=None)
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 returs 1 ('foo') if parts of the final 'bar' arrive late

+After a match is found the instance attributes 'before', 'after' and
+'match' will be set. You can see all the data read before the match in
+'before'. You can see the data that was matched in 'after'. The
+re.MatchObject used in the re match will be in 'match'. If an error
+occurred then 'before' will be set to all the data read so far and
+'after' and 'match' will be None.

+If timeout is -1 then timeout will be set to the self.timeout value.

+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().
+ +
expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1)
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.
+ +
expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1)
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(). If timeout==-1 then
+the self.timeout value is used. If searchwindowsize==-1 then the
+self.searchwindowsize value is used.
+ +
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.
+ +
fileno(self)
This returns the file descriptor of the pty for the child.
+ +
flush(self)
This does nothing. It is here to support the interface for a
+File-like object.
+ +
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().
+ +
getwinsize(self)
This returns the terminal window size of the child tty. The return
+value is a tuple of (rows, cols).
+ +
interact(self, escape_character='\x1d', 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 stop. The default for
+escape_character is ^]. This should not be confused with ASCII 27 --
+the ESC character. ASCII 29 was chosen for historical merit because
+this is the character used by 'telnet' as the escape character. The
+escape_character will not be sent to the child process.

+You may pass in optional input and output filter functions. These
+functions should take a string and return a string. 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))
+        global p
+        p.setwinsize(a[0],a[1])
+    p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough.
+    signal.signal(signal.SIGWINCH, sigwinch_passthrough)
+    p.interact()
+ +
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.
+ +
isatty(self)
This returns True if the file descriptor is open and connected to a
+tty(-like) device, else False.
+ +
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.
+ +
next(self)
This is to support iterators over a file-like object.
+ +
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.
+ +
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 log file was set using
+setlog() then all data will also be written to the log file.

+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 was 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 effected 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.

+This is a wrapper around os.read(). It uses select.select() to
+implement the timeout.
+ +
readline(self, size=-1)
This reads and returns one entire line. A trailing newline is kept
+in the string, but may be absent when a file ends with an incomplete
+line. Note: This readline() looks for a \r\n pair even on UNIX
+because this is what the pseudo tty device returns. So contrary to what
+you may expect you will receive the newline as \r\n. An empty string
+is returned when EOF is hit immediately. Currently, the size argument is
+mostly ignored, so this behavior is not standard for a file-like
+object. If size is 0 then an empty string is returned.
+ +
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.
+ +
send(self, s)
This sends a string to the child process. This returns the number of
+bytes written. If a log file was set then the data is also written to
+the log.
+ +
sendcontrol(self, char)
This sends a control character to the child such as Ctrl-C or
+Ctrl-D. For example, to send a Ctrl-G (ASCII 7)::

+    child.sendcontrol('g')

+See also, sendintr() and sendeof().
+ +
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.
+ +
sendintr(self)
This sends a SIGINT to the child. It does not require
+the SIGINT to be the first character on a line.
+ +
sendline(self, s='')
This is like send(), but it adds a line feed (os.linesep). This
+returns the number of bytes written.
+ +
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')
+    p.sendline ('1234') # We will see this twice (once from tty echo and again from cat).
+    p.expect (['1234'])
+    p.expect (['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 (['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') # We will see this twice (once from tty echo and again from cat).
+    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'])
+ +
setlog(self, fileobject)
This method is no longer supported or allowed.
+ +
setmaxread(self, maxread)
This method is no longer supported or allowed. I don't like getters
+and setters without a good reason.
+ +
setwinsize(self, r, c)
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.
+ +
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.
+ +
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, technically, the child
+is still alive until its output is read.
+ +
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 user@example.com')
+    p.waitnoecho()
+    p.sendline(mypassword)

+If timeout is None then this method to block forever until ECHO flag is
+False.
+ +
write(self, s)
This is similar to send() except that there is no return value.
+ +
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.
+ +
+Data descriptors defined here:
+
__dict__
+
dictionary for instance variables (if defined)
+
+
__weakref__
+
list of weak references to the object (if defined)
+
+

+ + + + + +
 
+Functions
       
run(command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None)
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 pseudo ttys. 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 myname@host.example.com:.')
+    child.expect ('(?i)password')
+    child.sendline (mypassword)

+The previous code can be replace with the following::

+    from pexpect import *
+    run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword})

+Examples
+========

+Start the apache daemon on the local machine::

+    from pexpect import *
+    run ("/usr/local/apache/bin/apachectl start")

+Check in a file using SVN::

+    from pexpect import *
+    run ("svn ci -m 'automatic commit' my_file.py")

+Run a command and capture exit status::

+    from pexpect import *
+    (command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)

+Tricky Examples
+===============

+The following will run SSH and execute 'ls -l' on the remote machine. The
+password 'secret' will be sent if the '(?i)password' pattern is ever seen::

+    run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\n'})

+This will start mencoder to rip a video from DVD. This will also display
+progress ticks every 5 seconds as it runs. For example::

+    from pexpect import *
+    def print_ticks(d):
+        print d['event_count'],
+    run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks}, timeout=5)

+The 'events' argument should be a dictionary of patterns and responses.
+Whenever one of the patterns is seen in the command out run() will send the
+associated response string. Note that you should put newlines in your
+string if Enter is necessary. The responses may also contain callback
+functions. Any callback is function that takes a dictionary 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.
+
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.
+
which(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.
+

+ + + + + +
 
+Data
       __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'which', 'split_command_line', '__version__', '__revision__']
+__revision__ = '$Revision: 399 $'
+__version__ = '2.3'
+ \ No newline at end of file diff --git a/src/link/pexpect/doc/pxssh.html b/src/link/pexpect/doc/pxssh.html new file mode 100644 index 0000000..645901d --- /dev/null +++ b/src/link/pexpect/doc/pxssh.html @@ -0,0 +1,589 @@ + + +Python: module pxssh + + + + +
 
+ 
pxssh (version 2.3)
index
/home/noah/pexpect/trunk/pexpect/pxssh.py
+

This class extends pexpect.spawn to specialize setting up SSH connections.
+This adds methods for login, logout, and expecting the shell prompt.

+$Id: pxssh.py 487 2007-08-29 22:33:29Z noah $

+

+ + + + + +
 
+Modules
       
pexpect
+
time
+

+ + + + + +
 
+Classes
       
+
pexpect.ExceptionPexpect(exceptions.Exception) +
+
+
ExceptionPxssh +
+
+
pexpect.spawn(__builtin__.object) +
+
+
pxssh +
+
+
+

+ + + + + + + +
 
+class ExceptionPxssh(pexpect.ExceptionPexpect)
   Raised for pxssh exceptions.
 
 
Method resolution order:
+
ExceptionPxssh
+
pexpect.ExceptionPexpect
+
exceptions.Exception
+
exceptions.BaseException
+
__builtin__.object
+
+
+Methods inherited from pexpect.ExceptionPexpect:
+
__init__(self, value)
+ +
__str__(self)
+ +
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.
+ +
+Data descriptors inherited from pexpect.ExceptionPexpect:
+
__weakref__
+
list of weak references to the object (if defined)
+
+
+Data and other attributes inherited from exceptions.Exception:
+
__new__ = <built-in method __new__ of type object at 0x81400e0>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+Methods inherited from exceptions.BaseException:
+
__delattr__(...)
x.__delattr__('name') <==> del x.name
+ +
__getattribute__(...)
x.__getattribute__('name') <==> x.name
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__reduce__(...)
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__setattr__(...)
x.__setattr__('name', value) <==> x.name = value
+ +
__setstate__(...)
+ +
+Data descriptors inherited from exceptions.BaseException:
+
__dict__
+
+
args
+
+
message
+
exception message
+
+

+ + + + + + + +
 
+class pxssh(pexpect.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::
+    
+    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, e:
+        print "pxssh failed on login."
+        print str(e)

+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)
 
 
Method resolution order:
+
pxssh
+
pexpect.spawn
+
__builtin__.object
+
+
+Methods defined here:
+
__init__(self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None)
+ +
levenshtein_distance(self, a, b)
This calculates the Levenshtein distance between a and b.
+ +
login(self, server, username, password='', terminal_type='ansi', original_prompt='[#$]', login_timeout=10, port=None, auto_prompt_reset=True)
This logs the user into the given server. It uses the
+'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 "~/.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 ExceptionPxssh exception.

+In some situations it is not possible or desirable to reset the
+original prompt. In this case, set 'auto_prompt_reset' to False to
+inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
+uses a unique prompt in the prompt() method. If the original prompt is
+not reset then this will disable the prompt() method unless you
+manually set the PROMPT attribute.
+ +
logout(self)
This sends exit to the remote shell. If there are stopped jobs then
+this automatically sends exit twice.
+ +
prompt(self, timeout=20)
This matches the shell prompt. This is little more than a short-cut
+to the expect() method. This returns True if the shell prompt was
+matched. This returns False if there was a timeout. Note that if you
+called login() with auto_prompt_reset set to False then you should have
+manually set the PROMPT attribute to a regex pattern for matching the
+prompt.
+ +
set_unique_prompt(self)
This sets the remote prompt to something more unique than # or $.
+This makes it easier for the prompt() method to match the shell prompt
+unambiguously. This method is called automatically by the 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. Just set the PROMPT
+attribute to a regular expression that matches it. In this case you
+should call login() with auto_prompt_reset=False; then set the PROMPT
+attribute. After that the prompt() method will try to match your prompt
+pattern.
+ +
synch_original_prompt(self)
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.
+ +
+Methods inherited from pexpect.spawn:
+
__del__(self)
This makes sure that no system resources are left open. Python only
+garbage collects Python objects. OS file descriptors are not Python
+objects, so they must be handled explicitly. If the child file
+descriptor was opened outside of this class (passed to the constructor)
+then this does not close it.
+ +
__iter__(self)
This is to support iterators over a file-like object.
+ +
__str__(self)
This returns a human-readable string that represents the state of
+the object.
+ +
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).
+ +
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 = compile_pattern_list(pl)
+     return 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 = compile_pattern_list(my_pattern)
+     while some_condition:
+        ...
+        i = expect_list(clp, timeout)
+        ...
+ +
eof(self)
This returns True if the EOF exception was ever raised.
+ +
expect(self, pattern, timeout=-1, searchwindowsize=None)
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 returs 1 ('foo') if parts of the final 'bar' arrive late

+After a match is found the instance attributes 'before', 'after' and
+'match' will be set. You can see all the data read before the match in
+'before'. You can see the data that was matched in 'after'. The
+re.MatchObject used in the re match will be in 'match'. If an error
+occurred then 'before' will be set to all the data read so far and
+'after' and 'match' will be None.

+If timeout is -1 then timeout will be set to the self.timeout value.

+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().
+ +
expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1)
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.
+ +
expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1)
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(). If timeout==-1 then
+the self.timeout value is used. If searchwindowsize==-1 then the
+self.searchwindowsize value is used.
+ +
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.
+ +
fileno(self)
This returns the file descriptor of the pty for the child.
+ +
flush(self)
This does nothing. It is here to support the interface for a
+File-like object.
+ +
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().
+ +
getwinsize(self)
This returns the terminal window size of the child tty. The return
+value is a tuple of (rows, cols).
+ +
interact(self, escape_character='\x1d', 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 stop. The default for
+escape_character is ^]. This should not be confused with ASCII 27 --
+the ESC character. ASCII 29 was chosen for historical merit because
+this is the character used by 'telnet' as the escape character. The
+escape_character will not be sent to the child process.

+You may pass in optional input and output filter functions. These
+functions should take a string and return a string. 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))
+        global p
+        p.setwinsize(a[0],a[1])
+    p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough.
+    signal.signal(signal.SIGWINCH, sigwinch_passthrough)
+    p.interact()
+ +
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.
+ +
isatty(self)
This returns True if the file descriptor is open and connected to a
+tty(-like) device, else False.
+ +
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.
+ +
next(self)
This is to support iterators over a file-like object.
+ +
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.
+ +
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 log file was set using
+setlog() then all data will also be written to the log file.

+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 was 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 effected 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.

+This is a wrapper around os.read(). It uses select.select() to
+implement the timeout.
+ +
readline(self, size=-1)
This reads and returns one entire line. A trailing newline is kept
+in the string, but may be absent when a file ends with an incomplete
+line. Note: This readline() looks for a \r\n pair even on UNIX
+because this is what the pseudo tty device returns. So contrary to what
+you may expect you will receive the newline as \r\n. An empty string
+is returned when EOF is hit immediately. Currently, the size argument is
+mostly ignored, so this behavior is not standard for a file-like
+object. If size is 0 then an empty string is returned.
+ +
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.
+ +
send(self, s)
This sends a string to the child process. This returns the number of
+bytes written. If a log file was set then the data is also written to
+the log.
+ +
sendcontrol(self, char)
This sends a control character to the child such as Ctrl-C or
+Ctrl-D. For example, to send a Ctrl-G (ASCII 7)::

+    child.sendcontrol('g')

+See also, sendintr() and sendeof().
+ +
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.
+ +
sendintr(self)
This sends a SIGINT to the child. It does not require
+the SIGINT to be the first character on a line.
+ +
sendline(self, s='')
This is like send(), but it adds a line feed (os.linesep). This
+returns the number of bytes written.
+ +
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')
+    p.sendline ('1234') # We will see this twice (once from tty echo and again from cat).
+    p.expect (['1234'])
+    p.expect (['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 (['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') # We will see this twice (once from tty echo and again from cat).
+    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'])
+ +
setlog(self, fileobject)
This method is no longer supported or allowed.
+ +
setmaxread(self, maxread)
This method is no longer supported or allowed. I don't like getters
+and setters without a good reason.
+ +
setwinsize(self, r, c)
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.
+ +
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.
+ +
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, technically, the child
+is still alive until its output is read.
+ +
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 user@example.com')
+    p.waitnoecho()
+    p.sendline(mypassword)

+If timeout is None then this method to block forever until ECHO flag is
+False.
+ +
write(self, s)
This is similar to send() except that there is no return value.
+ +
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.
+ +
+Data descriptors inherited from pexpect.spawn:
+
__dict__
+
dictionary for instance variables (if defined)
+
+
__weakref__
+
list of weak references to the object (if defined)
+
+

+ + + + + +
 
+Data
       __all__ = ['ExceptionPxssh', 'pxssh']
+__revision__ = '$Revision: 399 $'
+__version__ = '2.3'
+ \ No newline at end of file diff --git a/src/link/pexpect/doc/screen.html b/src/link/pexpect/doc/screen.html new file mode 100644 index 0000000..f3859ff --- /dev/null +++ b/src/link/pexpect/doc/screen.html @@ -0,0 +1,180 @@ + + +Python: module screen + + + + +
 
+ 
screen
index
/home/noah/pexpect/trunk/pexpect/screen.py
+

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 ANSI class
+extends this class to add parsing of ANSI escape codes.

+$Id: screen.py 486 2007-07-13 01:04:16Z noah $

+

+ + + + + +
 
+Modules
       
copy
+

+ + + + + +
 
+Classes
       
+
screen +
+

+ + + + + + + +
 
+class screen
   This object maintains the state of a virtual text screen as a
+rectangluar 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).
 
 Methods defined here:
+
__init__(self, r=24, c=80)
This initializes a blank scree of the given dimentions.
+ +
__str__(self)
This returns a printable representation of the screen. The end of
+each screen line is terminated by a newline.
+ +
clear_all_tabs(self)
Clears all tabs.
+ +
clear_tab(self)
Clears tab at the current position.
+ +
cr(self)
This moves the cursor to the beginning (col 1) of the current row.
+ +
crlf(self)
This advances the cursor with CRLF properties.
+The cursor will line wrap and the screen may scroll.
+ +
cursor_back(self, count=1)
+ +
cursor_constrain(self)
This keeps the cursor within the screen area.
+ +
cursor_down(self, count=1)
+ +
cursor_force_position(self, r, c)
Identical to Cursor Home.
+ +
cursor_forward(self, count=1)
+ +
cursor_home(self, r=1, c=1)
+ +
cursor_restore_attrs(self)
Restores cursor position after a Save Cursor.
+ +
cursor_save(self)
Save current cursor position.
+ +
cursor_save_attrs(self)
Save current cursor position.
+ +
cursor_unsave(self)
Restores cursor position after a Save Cursor.
+ +
cursor_up(self, count=1)
+ +
cursor_up_reverse(self)
+ +
dump(self)
This returns a copy of the screen as a string. This is similar to
+__str__ except that lines are not terminated with line feeds.
+ +
erase_down(self)
Erases the screen from the current line down to the bottom of the
+screen.
+ +
erase_end_of_line(self)
Erases from the current cursor position to the end of the current
+line.
+ +
erase_line(self)
Erases the entire current line.
+ +
erase_screen(self)
Erases the screen with the background color.
+ +
erase_start_of_line(self)
Erases from the current cursor position to the start of the current
+line.
+ +
erase_up(self)
Erases the screen from the current line up to the top of the
+screen.
+ +
fill(self, ch=' ')
+ +
fill_region(self, rs, cs, re, ce, ch=' ')
+ +
get(self)
+ +
get_abs(self, r, c)
+ +
get_region(self, rs, cs, re, ce)
This returns a list of lines representing the region.
+ +
insert(self, ch)
+ +
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.
+ +
lf(self)
This moves the cursor down with scrolling.
+ +
newline(self)
This is an alias for crlf().
+ +
pretty(self)
This returns a copy of the screen as a string with an ASCII text box
+around the screen border. This is similar to __str__ except that it
+adds a box.
+ +
put(self, ch)
This puts a characters at the current cursor position.
+ +
put_abs(self, r, c, ch)
Screen array starts at 1 index.
+ +
scroll_constrain(self)
This keeps the scroll region within the screen region.
+ +
scroll_down(self)
Scroll display down one line.
+ +
scroll_screen(self)
Enable scrolling for entire display.
+ +
scroll_screen_rows(self, rs, re)
Enable scrolling from row {start} to row {end}.
+ +
scroll_up(self)
Scroll display up one line.
+ +
set_tab(self)
Sets a tab at the current position.
+ +

+ + + + + +
 
+Functions
       
constrain(n, min, max)
This returns a number, n constrained to the min and max bounds.
+

+ + + + + +
 
+Data
       BEL = 7
+BS = 8
+CAN = 24
+CR = 13
+DEL = 127
+ENQ = 5
+ESC = 27
+FF = 12
+HT = 9
+LF = 10
+NUL = 0
+SI = 15
+SO = 14
+SPACE = ' '
+SUB = 26
+VT = 11
+XOFF = 19
+XON = 17
+ \ No newline at end of file diff --git a/src/link/pexpect/examples/README b/src/link/pexpect/examples/README new file mode 100644 index 0000000..8f2581e --- /dev/null +++ b/src/link/pexpect/examples/README @@ -0,0 +1,72 @@ +This directory contains scripts that give examples of using Pexpect. + +hive.py + This script creates SSH connections to a list of hosts that + you provide. Then you are given a command line prompt. Each + shell command that you enter is sent to all the hosts. The + response from each host is collected and printed. For example, + you could connect to a dozen different machines and reboot + them all at once. + +script.py + This implements a command similar to the classic BSD "script" command. + This will start a subshell and log all input and output to a file. + This demonstrates the interact() method of Pexpect. + +fix_cvs_files.py + This is for cleaning up binary files improperly added to + CVS. This script scans the given path to find binary files; + checks with CVS to see if the sticky options are set to -kb; + finally if sticky options are not -kb then uses 'cvs admin' + to set the -kb option. + +ftp.py + This demonstrates an FTP "bookmark". + This connects to an ftp site; does a few ftp commands; and then gives the user + interactive control over the session. In this case the "bookmark" is to a + directory on the OpenBSD ftp server. It puts you in the i386 packages + directory. You can easily modify this for other sites. + This demonstrates the interact() method of Pexpect. + +monitor.py + This runs a sequence of system status commands on a remote host using SSH. + It runs a simple system checks such as uptime and free to monitor + the state of the remote host. + +passmass.py + This will login to a list of hosts and change the password of the + given user. This demonstrates scripting logins; although, you could + more easily do this using the pxssh subclass of Pexpect. + See also the "hive.py" example script for a more general example + of scripting a collection of servers. + +python.py + This starts the python interpreter and prints the greeting message backwards. + It then gives the user interactive control of Python. It's pretty useless! + +rippy.py + This is a wizard for mencoder. It greatly simplifies the process of + ripping a DVD to mpeg4 format (XviD, DivX). It can transcode from any + video file to another. It has options for resampling the audio stream; + removing interlace artifacts, fitting to a target file size, etc. + There are lots of options, but the process is simple and easy to use. + +sshls.py + This lists a directory on a remote machine. + +ssh_tunnel.py + This starts an SSH tunnel to a remote machine. It monitors the connection + and restarts the tunnel if it goes down. + +uptime.py + This will run the uptime command and parse the output into python variables. + This demonstrates using a single regular expression to match the output + of a command and capturing different variable in match groups. + The regular expression takes into account a wide variety of different + formats for uptime output. + +df.py + This collects filesystem capacity info using the 'df' command. + Tuples of filesystem name and percentage are stored in a list. + A simple report is printed. Filesystems over 95% capacity are highlighted. + diff --git a/src/link/pexpect/examples/astat.py b/src/link/pexpect/examples/astat.py new file mode 100755 index 0000000..82fa3c6 --- /dev/null +++ b/src/link/pexpect/examples/astat.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +"""This runs Apache Status on the remote host and returns the number of requests per second. + +./astat.py [-s server_hostname] [-u username] [-p password] + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : Password to user for login. + +Example: + This will print information about the given host: + ./astat.py -s www.example.com -u mylogin -p mypassword + +""" + +import os, sys, time, re, getopt, getpass +import traceback +import pexpect, pxssh + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + ###################################################################### + ## Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if '-s' in options: + hostname = options['-s'] + else: + hostname = raw_input('hostname: ') + if '-u' in options: + username = options['-u'] + else: + username = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + + # + # Login via SSH + # + p = pxssh.pxssh() + p.login(hostname, username, password) + p.sendline('apachectl status') + p.expect('([0-9]+\.[0-9]+)\s*requests/sec') + requests_per_second = p.match.groups()[0] + p.logout() + print requests_per_second + +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/src/link/pexpect/examples/bd_client.py b/src/link/pexpect/examples/bd_client.py new file mode 100755 index 0000000..564739a --- /dev/null +++ b/src/link/pexpect/examples/bd_client.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +"""This is a very simple client for the backdoor daemon. This is intended more +for testing rather than normal use. See bd_serv.py """ + +import socket +import sys, time, select + +def recv_wrapper(s): + r,w,e = select.select([s.fileno()],[],[], 2) + if not r: + return '' + #cols = int(s.recv(4)) + #rows = int(s.recv(4)) + cols = 80 + rows = 24 + packet_size = cols * rows * 2 # double it for good measure + return s.recv(packet_size) + +#HOST = '' #'localhost' # The remote host +#PORT = 1664 # The same port as used by the server +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect(sys.argv[1])#(HOST, PORT)) +time.sleep(1) +#s.setblocking(0) +#s.send('COMMAND' + '\x01' + sys.argv[1]) +s.send(':sendline ' + sys.argv[2]) +print recv_wrapper(s) +s.close() +sys.exit() +#while True: +# data = recv_wrapper(s) +# if data == '': +# break +# sys.stdout.write (data) +# sys.stdout.flush() +#s.close() + diff --git a/src/link/pexpect/examples/bd_serv.py b/src/link/pexpect/examples/bd_serv.py new file mode 100755 index 0000000..b7def9e --- /dev/null +++ b/src/link/pexpect/examples/bd_serv.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python + +"""Back door shell server + +This exposes an shell terminal on a socket. + + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request +""" + +# Having the password on the command line is not a good idea, but +# then this entire project is probably not the most security concious thing +# I've ever built. This should be considered an experimental tool -- at best. +import pxssh, pexpect, ANSI +import time, sys, os, getopt, getpass, traceback, threading, socket + +def exit_with_usage(exit_code=1): + + print globals()['__doc__'] + os._exit(exit_code) + +class roller (threading.Thread): + + """This runs a function in a loop in a thread.""" + + def __init__(self, interval, function, args=[], kwargs={}): + + """The interval parameter defines time between each call to the function. + """ + + threading.Thread.__init__(self) + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + + def cancel(self): + + """Stop the roller.""" + + self.finished.set() + + def run(self): + + while not self.finished.isSet(): + # self.finished.wait(self.interval) + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + + """This keeps the screen updated with the output of the child. This runs in + a separate thread. See roller(). """ + + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + #while True: + # #child.prompt (timeout=refresh_timeout) + # try: + # #child.read_nonblocking(1,timeout=refresh_timeout) + # child.read_nonblocking(4000, 0.1) + # except: + # pass + +def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + + '''This forks the current process into a daemon. Almost none of this is + necessary (or advisable) if your daemon is being started by inetd. In that + case, stdin, stdout and stderr are all set up for you to refer to the + network connection, and the fork()s and session manipulation should not be + done (to avoid confusing inetd). Only the chdir() and umask() steps remain + as useful. + + References: + UNIX Programming FAQ + 1.7 How do I get my program to act like a daemon? + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + + Advanced Programming in the Unix Environment + W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7. + + The stdin, stdout, and stderr arguments are file names that will be opened + and be used to replace the standard file descriptors in sys.stdin, + sys.stdout, and sys.stderr. These arguments are optional and default to + /dev/null. Note that stderr is opened unbuffered, so if it shares a file + with stdout then interleaved output may not appear in the order that you + expect. ''' + + # Do first fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit first parent. + except OSError, e: + sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Decouple from parent environment. + os.chdir("/") + os.umask(0) + os.setsid() + + # Do second fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit second parent. + except OSError, e: + sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Now I am a daemon! + + # Redirect standard file descriptors. + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # I now return as the daemon + return 0 + +def add_cursor_blink (response, row, col): + + i = (row-1) * 80 + col + return response[:i]+''+response[i:] + +def main (): + + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + print "Login for %s@%s:%s" % (username, hostname, port) + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + if daemon_mode: + print "daemonizing server" + daemonize() + #daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log') + + sys.stdout.write ('server started with pid %d\n' % os.getpid() ) + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + child.login (hostname, username, password) + print 'created shell. command line prompt is', child.PROMPT + #child.sendline ('stty -echo') + #child.setecho(False) + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + localhost = '127.0.0.1' + s.bind('/tmp/mysock') + os.chmod('/tmp/mysock',0777) + print 'Listen' + s.listen(1) + print 'Accept' + #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #localhost = '127.0.0.1' + #s.bind((localhost, port)) + #print 'Listen' + #s.listen(1) + + r = roller (0.01, endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + print "screen poll updater started in background thread" + sys.stdout.flush() + + try: + while True: + conn, addr = s.accept() + print 'Connected by', addr + data = conn.recv(1024) + if data[0]!=':': + cmd = ':sendline' + arg = data.strip() + else: + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + if cmd == ':exit': + r.cancel() + break + elif cmd == ':sendline': + child.sendline (arg) + #child.prompt(timeout=2) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':send' or cmd==':xsend': + if cmd==':xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':cursor': + shell_window = '%x%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == ':refresh': + shell_window = str(virtual_screen) + + response = [] + response.append (shell_window) + #response = add_cursor_blink (response, row, col) + sent = conn.send('\n'.join(response)) + if watch_mode: print '\n'.join(response) + if sent < len (response): + print "Sent is too short. Some data was cut off." + conn.close() + finally: + r.cancel() + print "cleaning up socket" + s.close() + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + print "done!" + +def pretty_box (rows, cols, s): + + """This puts an ASCII text box around the given string, s. + """ + + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def error_response (msg): + + response = [] + response.append ("""All commands start with : +:{REQUEST} {ARGUMENT} +{REQUEST} may be one of the following: + :sendline: Run the ARGUMENT followed by a line feed. + :send : send the characters in the ARGUMENT without a line feed. + :refresh : Use to catch up the screen with the shell if state gets out of sync. +Example: + :sendline ls -l +You may also leave off :command and it will be assumed. +Example: + ls -l +is equivalent to: + :sendline ls -l +""") + response.append (msg) + return '\n'.join(response) + +def parse_host_connect_string (hcs): + + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. """ + + if '@' in hcs: + p = re.compile (r'(?P[^@:]*)(:?)(?P.*)(?!\\)@(?P[^:]*):?(?P[0-9]*)') + else: + p = re.compile (r'(?P)(?P)(?P[^:]*):?(?P[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +if __name__ == "__main__": + + try: + start_time = time.time() + print time.asctime() + main() + print time.asctime() + print "TOTAL TIME IN MINUTES:", + print (time.time() - start_time) / 60.0 + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) + diff --git a/src/link/pexpect/examples/cgishell.cgi b/src/link/pexpect/examples/cgishell.cgi new file mode 100755 index 0000000..1e3affc --- /dev/null +++ b/src/link/pexpect/examples/cgishell.cgi @@ -0,0 +1,762 @@ +#!/usr/bin/python +##!/usr/bin/env python +"""CGI shell server + +This exposes a shell terminal on a web page. +It uses AJAX to send keys and receive screen updates. +The client web browser needs nothing but CSS and Javascript. + + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request + +This project is probably not the most security concious thing I've ever built. +This should be considered an experimental tool -- at best. +""" +import sys,os +sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules +import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal +import pxssh, pexpect, ANSI + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + os._exit(exit_code) + +def client (command, host='localhost', port=-1): + """This sends a request to the server and returns the response. + If port <= 0 then host is assumed to be the filename of a Unix domain socket. + If port > 0 then host is an inet hostname. + """ + if port <= 0: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(host) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.send(command) + data = s.recv (2500) + s.close() + return data + +def server (hostname, username, password, socket_filename='/tmp/server_sock', daemon_mode = True, verbose=False): + """This starts and services requests from a client. + If daemon_mode is True then this forks off a separate daemon process and returns the daemon's pid. + If daemon_mode is False then this does not return until the server is done. + """ + if daemon_mode: + mypid_name = '/tmp/%d.pid' % os.getpid() + daemon_pid = daemonize(daemon_pid_filename=mypid_name) + time.sleep(1) + if daemon_pid != 0: + os.unlink(mypid_name) + return daemon_pid + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + try: + child.login (hostname, username, password, login_naked=True) + except: + return + if verbose: print 'login OK' + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists(socket_filename): os.remove(socket_filename) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.bind(socket_filename) + os.chmod(socket_filename, 0777) + if verbose: print 'Listen' + s.listen(1) + + r = roller (endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + if verbose: print "started screen-poll-updater in background thread" + sys.stdout.flush() + try: + while True: + conn, addr = s.accept() + if verbose: print 'Connected by', addr + data = conn.recv(1024) + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + arg = '' + + if cmd == 'exit': + r.cancel() + break + elif cmd == 'sendline': + child.sendline (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'send' or cmd=='xsend': + if cmd=='xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'cursor': + shell_window = '%x,%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == 'refresh': + shell_window = str(virtual_screen) + elif cmd == 'hash': + shell_window = str(hash(str(virtual_screen))) + + response = [] + response.append (shell_window) + if verbose: print '\n'.join(response) + sent = conn.send('\n'.join(response)) + if sent < len (response): + if verbose: print "Sent is too short. Some data was cut off." + conn.close() + except e: + pass + r.cancel() + if verbose: print "cleaning up socket" + s.close() + if os.path.exists(socket_filename): os.remove(socket_filename) + if verbose: print "server done!" + +class roller (threading.Thread): + """This class continuously loops a function in a thread. + This is basically a thin layer around Thread with a + while loop and a cancel. + """ + def __init__(self, function, args=[], kwargs={}): + threading.Thread.__init__(self) + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + def cancel(self): + """Stop the roller.""" + self.finished.set() + def run(self): + while not self.finished.isSet(): + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + """This keeps the screen updated with the output of the child. + This will be run in a separate thread. See roller class. + """ + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + +def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None): + """This runs the current process in the background as a daemon. + The arguments stdin, stdout, stderr allow you to set the filename that the daemon reads and writes to. + If they are set to None then all stdio for the daemon will be directed to /dev/null. + If daemon_pid_filename is set then the pid of the daemon will be written to it as plain text + and the pid will be returned. If daemon_pid_filename is None then this will return None. + """ + UMASK = 0 + WORKINGDIR = "/" + MAXFD = 1024 + + # The stdio file descriptors are redirected to /dev/null by default. + if hasattr(os, "devnull"): + DEVNULL = os.devnull + else: + DEVNULL = "/dev/null" + if stdin is None: stdin = DEVNULL + if stdout is None: stdout = DEVNULL + if stderr is None: stderr = DEVNULL + + try: + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: # The first child. + os.waitpid(pid,0) + if daemon_pid_filename is not None: + daemon_pid = int(file(daemon_pid_filename,'r').read()) + return daemon_pid + else: + return None + + # first child + os.setsid() + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + try: + pid = os.fork() # fork second child + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: + if daemon_pid_filename is not None: + file(daemon_pid_filename,'w').write(str(pid)) + os._exit(0) # exit parent (the first child) of the second child. + + # second child + os.chdir(WORKINGDIR) + os.umask(UMASK) + + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if maxfd == resource.RLIM_INFINITY: + maxfd = MAXFD + + # close all file descriptors + for fd in xrange(0, maxfd): + try: + os.close(fd) + except OSError: # fd wasn't open to begin with (ignored) + pass + + os.open (DEVNULL, os.O_RDWR) # standard input + + # redirect standard file descriptors + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + return 0 + +def client_cgi (): + """This handles the request if this script was called as a cgi. + """ + sys.stderr = sys.stdout + ajax_mode = False + TITLE="Shell" + SHELL_OUTPUT="" + SID="NOT" + print "Content-type: text/html;charset=utf-8\r\n" + try: + form = cgi.FieldStorage() + if form.has_key('ajax'): + ajax_mode = True + ajax_cmd = form['ajax'].value + SID=form['sid'].value + if ajax_cmd == 'send': + command = 'xsend' + arg = form['arg'].value.encode('hex') + result = client (command + ' ' + arg, '/tmp/'+SID) + print result + elif ajax_cmd == 'refresh': + command = 'refresh' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'cursor': + command = 'cursor' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'exit': + command = 'exit' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'hash': + command = 'hash' + result = client (command, '/tmp/'+SID) + print result + elif not form.has_key('sid'): + SID=random_sid() + print LOGIN_HTML % locals(); + else: + SID=form['sid'].value + if form.has_key('start_server'): + USERNAME = form['username'].value + PASSWORD = form['password'].value + dpid = server ('127.0.0.1', USERNAME, PASSWORD, '/tmp/'+SID) + SHELL_OUTPUT="daemon pid: " + str(dpid) + else: + if form.has_key('cli'): + command = 'sendline ' + form['cli'].value + else: + command = 'sendline' + SHELL_OUTPUT = client (command, '/tmp/'+SID) + print CGISH_HTML % locals() + except: + tb_dump = traceback.format_exc() + if ajax_mode: + print str(tb_dump) + else: + SHELL_OUTPUT=str(tb_dump) + print CGISH_HTML % locals() + +def server_cli(): + """This is the command line interface to starting the server. + This handles things if the script was not called as a CGI + (if you run it from the command line). + """ + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + #port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + server (hostname, username, password, '/tmp/mysock', daemon_mode) + +def random_sid (): + a=random.randint(0,65535) + b=random.randint(0,65535) + return '%04x%04x.sid' % (a,b) + +def parse_host_connect_string (hcs): + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. + """ + if '@' in hcs: + p = re.compile (r'(?P[^@:]*)(:?)(?P.*)(?!\\)@(?P[^:]*):?(?P[0-9]*)') + else: + p = re.compile (r'(?P)(?P)(?P[^:]*):?(?P[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +def pretty_box (s, rows=24, cols=80): + """This puts an ASCII text box around the given string. + """ + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def main (): + if os.getenv('REQUEST_METHOD') is None: + server_cli() + else: + client_cgi() + +# It's mostly HTML and Javascript from here on out. +CGISH_HTML=""" + + +%(TITLE)s %(SID)s + + + + + + + + +

+ + +

+ + + + +
+ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+
+ + +""" + +LOGIN_HTML=""" + +Shell Login + + + + + +
+ + +username:
+password:
+ +
+
+ + +""" + +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) + diff --git a/src/link/pexpect/examples/chess.py b/src/link/pexpect/examples/chess.py new file mode 100755 index 0000000..8c32cf7 --- /dev/null +++ b/src/link/pexpect/examples/chess.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI + +REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' +REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + + self.child.expect ('Chess') + if self.child.after != 'Chess': + raise IOError, 'incompatible chess program' + self.term.process_list (self.before) + self.term.process_list (self.after) + self.last_computer_move = '' + def read_until_cursor (self, r,c) + while 1: + self.child.read(1, 60) + self.term.process (c) + if self.term.cur_r == r and self.term.cur_c == c: + return 1 + + def do_first_move (self, move): + self.child.expect ('Your move is') + self.child.sendline (move) + self.term.process_list (self.before) + self.term.process_list (self.after) + return move + + def do_move (self, move): + read_until_cursor (19,60) + #self.child.expect ('\[19;60H') + self.child.sendline (move) + print 'do_move' move + return move + + def get_first_computer_move (self): + self.child.expect ('My move is') + self.child.expect (REGEX_MOVE) +# print '', self.child.after + return self.child.after + + def get_computer_move (self): + print 'Here' + i = self.child.expect (['\[17;59H', '\[17;58H']) + print i + if i == 0: + self.child.expect (REGEX_MOVE) + if len(self.child.after) < 4: + self.child.after = self.child.after + self.last_computer_move[3] + if i == 1: + self.child.expect (REGEX_MOVE_PART) + self.child.after = self.last_computer_move[0] + self.child.after + print '', self.child.after + self.last_computer_move = self.child.after + return self.child.after + + def switch (self): + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') +import sys, os +print 'Starting...' +white = Chess() +white.child.echo = 1 +white.child.expect ('Your move is') +white.set_depth(2) +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +white.do_move ('e7e5') +move_white = white.get_computer_move() +print 'move white:', move_white +white.do_move ('f8c5') +move_white = white.get_computer_move() +print 'move white:', move_white +white.do_move ('b8a6') +move_white = white.get_computer_move() +print 'move white:', move_white + +sys.exit(1) + + + +black = Chess() +white = Chess() +white.child.expect ('Your move is') +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +black.do_first_move (move_white) +move_black = black.get_first_computer_move() +print 'first move black:', move_black + +white.do_move (move_black) + +done = 0 +while not done: + move_white = white.get_computer_move() + print 'move white:', move_white + + black.do_move (move_white) + move_black = black.get_computer_move() + print 'move black:', move_black + + white.do_move (move_black) + print 'tail of loop' + +g.quit() + + diff --git a/src/link/pexpect/examples/chess2.py b/src/link/pexpect/examples/chess2.py new file mode 100755 index 0000000..c62d5ce --- /dev/null +++ b/src/link/pexpect/examples/chess2.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI +import sys, os, time + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + + #self.child.expect ('Chess') + #if self.child.after != 'Chess': + # raise IOError, 'incompatible chess program' + #self.term.process_list (self.child.before) + #self.term.process_list (self.child.after) + + self.last_computer_move = '' + + def read_until_cursor (self, r,c, e=0): + '''Eventually something like this should move into the screen class or + a subclass. Maybe a combination of pexpect and screen... + ''' + fout = open ('log','a') + while self.term.cur_r != r or self.term.cur_c != c: + try: + k = self.child.read(1, 10) + except Exception, e: + print 'EXCEPTION, (r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c) + sys.stdout.flush() + self.term.process (k) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + if e: + sys.stdout.write (k) + sys.stdout.flush() + if self.term.cur_r == r and self.term.cur_c == c: + fout.close() + return 1 + print 'DIDNT EVEN HIT.' + fout.close() + return 1 + + def expect_region (self): + '''This is another method that would be moved into the + screen class. + ''' + pass + def do_scan (self): + fout = open ('log','a') + while 1: + c = self.child.read(1,10) + self.term.process (c) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + sys.stdout.write (c) + sys.stdout.flush() + + def do_move (self, move, e = 0): + time.sleep(1) + self.read_until_cursor (19,60, e) + self.child.sendline (move) + + def wait (self, color): + while 1: + r = self.term.get_region (14,50,14,60)[0] + r = r.strip() + if r == color: + return + time.sleep (1) + + def parse_computer_move (self, s): + i = s.find ('is: ') + cm = s[i+3:i+9] + return cm + def get_computer_move (self, e = 0): + time.sleep(1) + self.read_until_cursor (19,60, e) + time.sleep(1) + r = self.term.get_region (17,50,17,62)[0] + cm = self.parse_computer_move (r) + return cm + + def switch (self): + print 'switching' + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') + +def LOG (s): + print s + sys.stdout.flush () + fout = open ('moves.log', 'a') + fout.write (s + '\n') + fout.close() + +print 'Starting...' + +black = Chess() +white = Chess() +white.read_until_cursor (19,60,1) +white.switch() + +done = 0 +while not done: + white.wait ('Black') + move_white = white.get_computer_move(1) + LOG ( 'move white:'+ move_white ) + + black.do_move (move_white) + black.wait ('White') + move_black = black.get_computer_move() + LOG ( 'move black:'+ move_black ) + + white.do_move (move_black, 1) + +g.quit() + + diff --git a/src/link/pexpect/examples/chess3.py b/src/link/pexpect/examples/chess3.py new file mode 100755 index 0000000..4404442 --- /dev/null +++ b/src/link/pexpect/examples/chess3.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI + +REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' +REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + +# self.child.expect ('Chess') + # if self.child.after != 'Chess': + # raise IOError, 'incompatible chess program' + # self.term.process_list (self.before) + # self.term.process_list (self.after) + self.last_computer_move = '' + def read_until_cursor (self, r,c): + fout = open ('log','a') + while 1: + k = self.child.read(1, 10) + self.term.process (k) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + if self.term.cur_r == r and self.term.cur_c == c: + fout.close() + return 1 + sys.stdout.write (k) + sys.stdout.flush() + + def do_scan (self): + fout = open ('log','a') + while 1: + c = self.child.read(1,10) + self.term.process (c) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + sys.stdout.write (c) + sys.stdout.flush() + + def do_move (self, move): + self.read_until_cursor (19,60) + self.child.sendline (move) + return move + + def get_computer_move (self): + print 'Here' + i = self.child.expect (['\[17;59H', '\[17;58H']) + print i + if i == 0: + self.child.expect (REGEX_MOVE) + if len(self.child.after) < 4: + self.child.after = self.child.after + self.last_computer_move[3] + if i == 1: + self.child.expect (REGEX_MOVE_PART) + self.child.after = self.last_computer_move[0] + self.child.after + print '', self.child.after + self.last_computer_move = self.child.after + return self.child.after + + def switch (self): + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') +import sys, os +print 'Starting...' +white = Chess() +white.do_move('b2b4') +white.read_until_cursor (19,60) +c1 = white.term.get_abs(17,58) +c2 = white.term.get_abs(17,59) +c3 = white.term.get_abs(17,60) +c4 = white.term.get_abs(17,61) +fout = open ('log','a') +fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4)) +fout.close() +white.do_move('c2c4') +white.read_until_cursor (19,60) +c1 = white.term.get_abs(17,58) +c2 = white.term.get_abs(17,59) +c3 = white.term.get_abs(17,60) +c4 = white.term.get_abs(17,61) +fout = open ('log','a') +fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4)) +fout.close() +white.do_scan () + +#white.do_move ('b8a6') +#move_white = white.get_computer_move() +#print 'move white:', move_white + +sys.exit(1) + + + +black = Chess() +white = Chess() +white.child.expect ('Your move is') +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +black.do_first_move (move_white) +move_black = black.get_first_computer_move() +print 'first move black:', move_black + +white.do_move (move_black) + +done = 0 +while not done: + move_white = white.get_computer_move() + print 'move white:', move_white + + black.do_move (move_white) + move_black = black.get_computer_move() + print 'move black:', move_black + + white.do_move (move_black) + print 'tail of loop' + +g.quit() + + diff --git a/src/link/pexpect/examples/df.py b/src/link/pexpect/examples/df.py new file mode 100755 index 0000000..64bbf93 --- /dev/null +++ b/src/link/pexpect/examples/df.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +"""This collects filesystem capacity info using the 'df' command. Tuples of +filesystem name and percentage are stored in a list. A simple report is +printed. Filesystems over 95% capacity are highlighted. Note that this does not +parse filesystem names after the first space, so names with spaces in them will +be truncated. This will produce ambiguous results for automount filesystems on +Apple OSX. """ + +import pexpect + +child = pexpect.spawn ('df') + +# parse 'df' output into a list. +pattern = "\n(\S+).*?([0-9]+)%" +filesystem_list = [] +for dummy in range (0, 1000): + i = child.expect ([pattern, pexpect.EOF]) + if i == 0: + filesystem_list.append (child.match.groups()) + else: + break + +# Print report +print +for m in filesystem_list: + s = "Filesystem %s is at %s%%" % (m[0], m[1]) + # highlight filesystems over 95% capacity + if int(m[1]) > 95: + s = '! ' + s + else: + s = ' ' + s + print s + diff --git a/src/link/pexpect/examples/fix_cvs_files.py b/src/link/pexpect/examples/fix_cvs_files.py new file mode 100755 index 0000000..e75a149 --- /dev/null +++ b/src/link/pexpect/examples/fix_cvs_files.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +"""This is for cleaning up binary files improperly added to CVS. This script +scans the given path to find binary files; checks with CVS to see if the sticky +options are set to -kb; finally if sticky options are not -kb then uses 'cvs +admin' to set the -kb option. + +This script ignores CVS directories, symbolic links, and files not known under +CVS control (cvs status is 'Unknown'). + +Run this on a CHECKED OUT module sandbox, not on the repository itself. After +if fixes the sticky options on any files you should manually do a 'cvs commit' +to accept the changes. Then be sure to have all users do a 'cvs up -A' to +update the Sticky Option status. + +Noah Spurrier +20030426 +""" + +import os, sys, time +import pexpect + +VERBOSE = 1 + +def is_binary (filename): + + """Assume that any file with a character where the 8th bit is set is + binary. """ + + fin = open(filename, 'rb') + wholething = fin.read() + fin.close() + for c in wholething: + if ord(c) & 0x80: + return 1 + return 0 + +def is_kb_sticky (filename): + + """This checks if 'cvs status' reports '-kb' for Sticky options. If the + Sticky Option status is '-ks' then this returns 1. If the status is + 'Unknown' then it returns 1. Otherwise 0 is returned. """ + + try: + s = pexpect.spawn ('cvs status %s' % filename) + i = s.expect (['Sticky Options:\s*(.*)\r\n', 'Status: Unknown']) + if i==1 and VERBOSE: + print 'File not part of CVS repository:', filename + return 1 # Pretend it's OK. + if s.match.group(1) == '-kb': + return 1 + s = None + except: + print 'Something went wrong trying to run external cvs command.' + print ' cvs status %s' % filename + print 'The cvs command returned:' + print s.before + return 0 + +def cvs_admin_kb (filename): + + """This uses 'cvs admin' to set the '-kb' sticky option. """ + + s = pexpect.run ('cvs admin -kb %s' % filename) + # There is a timing issue. If I run 'cvs admin' too quickly + # cvs sometimes has trouble obtaining the directory lock. + time.sleep(1) + +def walk_and_clean_cvs_binaries (arg, dirname, names): + + """This contains the logic for processing files. This is the os.path.walk + callback. This skips dirnames that end in CVS. """ + + if len(dirname)>3 and dirname[-3:]=='CVS': + return + for n in names: + fullpath = os.path.join (dirname, n) + if os.path.isdir(fullpath) or os.path.islink(fullpath): + continue + if is_binary(fullpath): + if not is_kb_sticky (fullpath): + if VERBOSE: print fullpath + cvs_admin_kb (fullpath) + +def main (): + + if len(sys.argv) == 1: + root = '.' + else: + root = sys.argv[1] + os.path.walk (root, walk_and_clean_cvs_binaries, None) + +if __name__ == '__main__': + main () + diff --git a/src/link/pexpect/examples/ftp.py b/src/link/pexpect/examples/ftp.py new file mode 100755 index 0000000..89a502e --- /dev/null +++ b/src/link/pexpect/examples/ftp.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +"""This demonstrates an FTP "bookmark". This connects to an ftp site; does a +few ftp stuff; and then gives the user interactive control over the session. In +this case the "bookmark" is to a directory on the OpenBSD ftp server. It puts +you in the i386 packages directory. You can easily modify this for other sites. +""" + +import pexpect +import sys + +child = pexpect.spawn('ftp ftp.openbsd.org') +child.expect('(?i)name .*: ') +child.sendline('anonymous') +child.expect('(?i)password') +child.sendline('pexpect@sourceforge.net') +child.expect('ftp> ') +child.sendline('cd /pub/OpenBSD/3.7/packages/i386') +child.expect('ftp> ') +child.sendline('bin') +child.expect('ftp> ') +child.sendline('prompt') +child.expect('ftp> ') +child.sendline('pwd') +child.expect('ftp> ') +print("Escape character is '^]'.\n") +sys.stdout.write (child.after) +sys.stdout.flush() +child.interact() # Escape character defaults to ^] +# At this point this script blocks until the user presses the escape character +# or until the child exits. The human user and the child should be talking +# to each other now. + +# At this point the script is running again. +print 'Left interactve mode.' + +# The rest is not strictly necessary. This just demonstrates a few functions. +# This makes sure the child is dead; although it would be killed when Python exits. +if child.isalive(): + child.sendline('bye') # Try to ask ftp child to exit. + child.close() +# Print the final state of the child. Normally isalive() should be FALSE. +if child.isalive(): + print 'Child did not exit gracefully.' +else: + print 'Child exited gracefully.' + diff --git a/src/link/pexpect/examples/hive.py b/src/link/pexpect/examples/hive.py new file mode 100755 index 0000000..fcb75bc --- /dev/null +++ b/src/link/pexpect/examples/hive.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python + +"""hive -- Hive Shell + +This lets you ssh to a group of servers and control them as if they were one. +Each command you enter is sent to each host in parallel. The response of each +host is collected and printed. In normal synchronous mode Hive will wait for +each host to return the shell command line prompt. The shell prompt is used to +sync output. + +Example: + + $ hive.py --sameuser --samepass host1.example.com host2.example.net + username: myusername + password: + connecting to host1.example.com - OK + connecting to host2.example.net - OK + targetting hosts: 192.168.1.104 192.168.1.107 + CMD (? for help) > uptime + ======================================================================= + host1.example.com + ----------------------------------------------------------------------- + uptime + 23:49:55 up 74 days, 5:14, 2 users, load average: 0.15, 0.05, 0.01 + ======================================================================= + host2.example.net + ----------------------------------------------------------------------- + uptime + 23:53:02 up 1 day, 13:36, 2 users, load average: 0.50, 0.40, 0.46 + ======================================================================= + +Other Usage Examples: + +1. You will be asked for your username and password for each host. + + hive.py host1 host2 host3 ... hostN + +2. You will be asked once for your username and password. + This will be used for each host. + + hive.py --sameuser --samepass host1 host2 host3 ... hostN + +3. Give a username and password on the command-line: + + hive.py user1:pass2@host1 user2:pass2@host2 ... userN:passN@hostN + +You can use an extended host notation to specify username, password, and host +instead of entering auth information interactively. Where you would enter a +host name use this format: + + username:password@host + +This assumes that ':' is not part of the password. If your password contains a +':' then you can use '\\:' to indicate a ':' and '\\\\' to indicate a single +'\\'. Remember that this information will appear in the process listing. Anyone +on your machine can see this auth information. This is not secure. + +This is a crude script that begs to be multithreaded. But it serves its +purpose. + +Noah Spurrier + +$Id: hive.py 509 2008-01-05 21:27:47Z noah $ +""" + +# TODO add feature to support username:password@host combination +# TODO add feature to log each host output in separate file + +import sys, os, re, optparse, traceback, types, time, getpass +import pexpect, pxssh +import readline, atexit + +#histfile = os.path.join(os.environ["HOME"], ".hive_history") +#try: +# readline.read_history_file(histfile) +#except IOError: +# pass +#atexit.register(readline.write_history_file, histfile) + +CMD_HELP="""Hive commands are preceded by a colon : (just think of vi). + +:target name1 name2 name3 ... + + set list of hosts to target commands + +:target all + + reset list of hosts to target all hosts in the hive. + +:to name command + + send a command line to the named host. This is similar to :target, but + sends only one command and does not change the list of targets for future + commands. + +:sync + + set mode to wait for shell prompts after commands are run. This is the + default. When Hive first logs into a host it sets a special shell prompt + pattern that it can later look for to synchronize output of the hosts. If + you 'su' to another user then it can upset the synchronization. If you need + to run something like 'su' then use the following pattern: + + CMD (? for help) > :async + CMD (? for help) > sudo su - root + CMD (? for help) > :prompt + CMD (? for help) > :sync + +:async + + set mode to not expect command line prompts (see :sync). Afterwards + commands are send to target hosts, but their responses are not read back + until :sync is run. This is useful to run before commands that will not + return with the special shell prompt pattern that Hive uses to synchronize. + +:refresh + + refresh the display. This shows the last few lines of output from all hosts. + This is similar to resync, but does not expect the promt. This is useful + for seeing what hosts are doing during long running commands. + +:resync + + This is similar to :sync, but it does not change the mode. It looks for the + prompt and thus consumes all input from all targetted hosts. + +:prompt + + force each host to reset command line prompt to the special pattern used to + synchronize all the hosts. This is useful if you 'su' to a different user + where Hive would not know the prompt to match. + +:send my text + + This will send the 'my text' wihtout a line feed to the targetted hosts. + This output of the hosts is not automatically synchronized. + +:control X + + This will send the given control character to the targetted hosts. + For example, ":control c" will send ASCII 3. + +:exit + + This will exit the hive shell. + +""" + +def login (args, cli_username=None, cli_password=None): + + # I have to keep a separate list of host names because Python dicts are not ordered. + # I want to keep the same order as in the args list. + host_names = [] + hive_connect_info = {} + hive = {} + # build up the list of connection information (hostname, username, password, port) + for host_connect_string in args: + hcd = parse_host_connect_string (host_connect_string) + hostname = hcd['hostname'] + port = hcd['port'] + if port == '': + port = None + if len(hcd['username']) > 0: + username = hcd['username'] + elif cli_username is not None: + username = cli_username + else: + username = raw_input('%s username: ' % hostname) + if len(hcd['password']) > 0: + password = hcd['password'] + elif cli_password is not None: + password = cli_password + else: + password = getpass.getpass('%s password: ' % hostname) + host_names.append(hostname) + hive_connect_info[hostname] = (hostname, username, password, port) + # build up the list of hive connections using the connection information. + for hostname in host_names: + print 'connecting to', hostname + try: + fout = file("log_"+hostname, "w") + hive[hostname] = pxssh.pxssh() + hive[hostname].login(*hive_connect_info[hostname]) + print hive[hostname].before + hive[hostname].logfile = fout + print '- OK' + except Exception, e: + print '- ERROR', + print str(e) + print 'Skipping', hostname + hive[hostname] = None + return host_names, hive + +def main (): + + global options, args, CMD_HELP + + if options.sameuser: + cli_username = raw_input('username: ') + else: + cli_username = None + + if options.samepass: + cli_password = getpass.getpass('password: ') + else: + cli_password = None + + host_names, hive = login(args, cli_username, cli_password) + + synchronous_mode = True + target_hostnames = host_names[:] + print 'targetting hosts:', ' '.join(target_hostnames) + while True: + cmd = raw_input('CMD (? for help) > ') + cmd = cmd.strip() + if cmd=='?' or cmd==':help' or cmd==':h': + print CMD_HELP + continue + elif cmd==':refresh': + refresh (hive, target_hostnames, timeout=0.5) + for hostname in target_hostnames: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + print '==============================================================================' + continue + elif cmd==':resync': + resync (hive, target_hostnames, timeout=0.5) + for hostname in target_hostnames: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + print '==============================================================================' + continue + elif cmd==':sync': + synchronous_mode = True + resync (hive, target_hostnames, timeout=0.5) + continue + elif cmd==':async': + synchronous_mode = False + continue + elif cmd==':prompt': + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].set_unique_prompt() + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:5] == ':send': + cmd, txt = cmd.split(None,1) + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].send(txt) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:3] == ':to': + cmd, hostname, txt = cmd.split(None,2) + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + continue + try: + hive[hostname].sendline (txt) + hive[hostname].prompt(timeout=2) + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:7] == ':expect': + cmd, pattern = cmd.split(None,1) + print 'looking for', pattern + try: + for hostname in target_hostnames: + if hive[hostname] is not None: + hive[hostname].expect(pattern) + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:7] == ':target': + target_hostnames = cmd.split()[1:] + if len(target_hostnames) == 0 or target_hostnames[0] == all: + target_hostnames = host_names[:] + print 'targetting hosts:', ' '.join(target_hostnames) + continue + elif cmd == ':exit' or cmd == ':q' or cmd == ':quit': + break + elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' : + cmd, c = cmd.split(None,1) + if ord(c)-96 < 0 or ord(c)-96 > 255: + print '/=============================================================================' + print '| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?' + print '\\-----------------------------------------------------------------------------' + continue + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].sendcontrol(c) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd == ':esc': + for hostname in target_hostnames: + if hive[hostname] is not None: + hive[hostname].send(chr(27)) + continue + # + # Run the command on all targets in parallel + # + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].sendline (cmd) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + + # + # print the response for each targeted host. + # + if synchronous_mode: + for hostname in target_hostnames: + try: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + hive[hostname].prompt(timeout=2) + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + print '==============================================================================' + +def refresh (hive, hive_names, timeout=0.5): + + """This waits for the TIMEOUT on each host. + """ + + # TODO This is ideal for threading. + for hostname in hive_names: + hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout) + +def resync (hive, hive_names, timeout=2, max_attempts=5): + + """This waits for the shell prompt for each host in an effort to try to get + them all to the same state. The timeout is set low so that hosts that are + already at the prompt will not slow things down too much. If a prompt match + is made for a hosts then keep asking until it stops matching. This is a + best effort to consume all input if it printed more than one prompt. It's + kind of kludgy. Note that this will always introduce a delay equal to the + timeout for each machine. So for 10 machines with a 2 second delay you will + get AT LEAST a 20 second delay if not more. """ + + # TODO This is ideal for threading. + for hostname in hive_names: + for attempts in xrange(0, max_attempts): + if not hive[hostname].prompt(timeout=timeout): + break + +def parse_host_connect_string (hcs): + + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. """ + + if '@' in hcs: + p = re.compile (r'(?P[^@:]*)(:?)(?P.*)(?!\\)@(?P[^:]*):?(?P[0-9]*)') + else: + p = re.compile (r'(?P)(?P)(?P[^:]*):?(?P[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +if __name__ == '__main__': + try: + start_time = time.time() + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: hive.py 509 2008-01-05 21:27:47Z noah $',conflict_handler="resolve") + parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output') + parser.add_option ('--samepass', action='store_true', default=False, help='Use same password for each login.') + parser.add_option ('--sameuser', action='store_true', default=False, help='Use same username for each login.') + (options, args) = parser.parse_args() + if len(args) < 1: + parser.error ('missing argument') + if options.verbose: print time.asctime() + main() + if options.verbose: print time.asctime() + if options.verbose: print 'TOTAL TIME IN MINUTES:', + if options.verbose: print (time.time() - start_time) / 60.0 + sys.exit(0) + except KeyboardInterrupt, e: # Ctrl-C + raise e + except SystemExit, e: # sys.exit() + raise e + except Exception, e: + print 'ERROR, UNEXPECTED EXCEPTION' + print str(e) + traceback.print_exc() + os._exit(1) diff --git a/src/link/pexpect/examples/log_69.80.212.10 b/src/link/pexpect/examples/log_69.80.212.10 new file mode 100644 index 0000000..537f8c6 --- /dev/null +++ b/src/link/pexpect/examples/log_69.80.212.10 @@ -0,0 +1,16 @@ +pwd +pwd +/home/noah +[PEXPECT]$ watch date +watch date +[?1049h(B[?7hEvery 2.0s: dateSat Jan 5 13:24:56 2008Sat Jan 5 13:24:56 PST 2008885:005:00224467991111335577992121335577993131335577994141335577995151335577996:016:01335577991111335577992121335577993131335577994141335577995151335577997:017:01335577991111335577992121335577993131335577994141335577995151date +3355[?1049l [?1l>[PEXPECT]$ date +Sat Jan 5 13:27:59 PST 2008 +[PEXPECT]$ date +exit +date +Sat Jan 5 13:28:03 PST 2008 +[PEXPECT]$ exit +logout +pwd +pwd diff --git a/src/link/pexpect/examples/log_69.80.212.11 b/src/link/pexpect/examples/log_69.80.212.11 new file mode 100644 index 0000000..d8b3103 --- /dev/null +++ b/src/link/pexpect/examples/log_69.80.212.11 @@ -0,0 +1,16 @@ +pwd +pwd +/home/noah +[PEXPECT]$ watch date +watch date +[?1049h(B[?7hEvery 2.0s: dateSat Jan 5 13:24:57 2008Sat Jan 5 13:24:57 PST 2008995:015:01335577991111335577992121335577993131335577994141335577995151335577996:016:01335577991111335577992121335577993131335577994141335577995151335577997:017:0133557799111133557799212133557799313133557799414133557799515133date +55[?1049l [?1l>[PEXPECT]$ datedate + +Sat Jan 5 13:27:59 PST 2008 +[PEXPECT]$ dateexit + +Sat Jan 5 13:28:03 PST 2008 +[PEXPECT]$ exitpwd + +logout + \ No newline at end of file diff --git a/src/link/pexpect/examples/monitor.py b/src/link/pexpect/examples/monitor.py new file mode 100755 index 0000000..e31b51b --- /dev/null +++ b/src/link/pexpect/examples/monitor.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +""" This runs a sequence of commands on a remote host using SSH. It runs a +simple system checks such as uptime and free to monitor the state of the remote +host. + +./monitor.py [-s server_hostname] [-u username] [-p password] + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : Password to user for login. + +Example: + This will print information about the given host: + ./monitor.py -s www.example.com -u mylogin -p mypassword + +It works like this: + Login via SSH (This is the hardest part). + Run and parse 'uptime'. + Run 'iostat'. + Run 'vmstat'. + Run 'netstat' + Run 'free'. + Exit the remote host. +""" + +import os, sys, time, re, getopt, getpass +import traceback +import pexpect + +# +# Some constants. +# +COMMAND_PROMPT = '[#$] ' ### This is way too simple for industrial use -- we will change is ASAP. +TERMINAL_PROMPT = '(?i)terminal type\?' +TERMINAL_TYPE = 'vt100' +# This is the prompt we get if SSH does not have the remote host's public key stored in the cache. +SSH_NEWKEY = '(?i)are you sure you want to continue connecting' + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + global COMMAND_PROMPT, TERMINAL_PROMPT, TERMINAL_TYPE, SSH_NEWKEY + ###################################################################### + ## Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if '-s' in options: + host = options['-s'] + else: + host = raw_input('hostname: ') + if '-u' in options: + user = options['-u'] + else: + user = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + + # + # Login via SSH + # + child = pexpect.spawn('ssh -l %s %s'%(user, host)) + i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, COMMAND_PROMPT, '(?i)password']) + if i == 0: # Timeout + print 'ERROR! could not login with SSH. Here is what SSH said:' + print child.before, child.after + print str(child) + sys.exit (1) + if i == 1: # In this case SSH does not have the public key cached. + child.sendline ('yes') + child.expect ('(?i)password') + if i == 2: + # This may happen if a public key was setup to automatically login. + # But beware, the COMMAND_PROMPT at this point is very trivial and + # could be fooled by some output in the MOTD or login message. + pass + if i == 3: + child.sendline(password) + # Now we are either at the command prompt or + # the login process is asking for our terminal type. + i = child.expect ([COMMAND_PROMPT, TERMINAL_PROMPT]) + if i == 1: + child.sendline (TERMINAL_TYPE) + child.expect (COMMAND_PROMPT) + # + # Set command prompt to something more unique. + # + COMMAND_PROMPT = "\[PEXPECT\]\$ " + child.sendline ("PS1='[PEXPECT]\$ '") # In case of sh-style + i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10) + if i == 0: + print "# Couldn't set sh-style prompt -- trying csh-style." + child.sendline ("set prompt='[PEXPECT]\$ '") + i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10) + if i == 0: + print "Failed to set command prompt using sh or csh style." + print "Response was:" + print child.before + sys.exit (1) + + # Now we should be at the command prompt and ready to run some commands. + print '---------------------------------------' + print 'Report of commands run on remote host.' + print '---------------------------------------' + + # Run uname. + child.sendline ('uname -a') + child.expect (COMMAND_PROMPT) + print child.before + if 'linux' in child.before.lower(): + LINUX_MODE = 1 + else: + LINUX_MODE = 0 + + # Run and parse 'uptime'. + child.sendline ('uptime') + child.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])') + duration, users, av1, av5, av15 = child.match.groups() + days = '0' + hours = '0' + mins = '0' + if 'day' in duration: + child.match = re.search('([0-9]+)\s+day',duration) + days = str(int(child.match.group(1))) + if ':' in duration: + child.match = re.search('([0-9]+):([0-9]+)',duration) + hours = str(int(child.match.group(1))) + mins = str(int(child.match.group(2))) + if 'min' in duration: + child.match = re.search('([0-9]+)\s+min',duration) + mins = str(int(child.match.group(1))) + print + print 'Uptime: %s days, %s users, %s (1 min), %s (5 min), %s (15 min)' % ( + duration, users, av1, av5, av15) + child.expect (COMMAND_PROMPT) + + # Run iostat. + child.sendline ('iostat') + child.expect (COMMAND_PROMPT) + print child.before + + # Run vmstat. + child.sendline ('vmstat') + child.expect (COMMAND_PROMPT) + print child.before + + # Run free. + if LINUX_MODE: + child.sendline ('free') # Linux systems only. + child.expect (COMMAND_PROMPT) + print child.before + + # Run df. + child.sendline ('df') + child.expect (COMMAND_PROMPT) + print child.before + + # Run lsof. + child.sendline ('lsof') + child.expect (COMMAND_PROMPT) + print child.before + +# # Run netstat +# child.sendline ('netstat') +# child.expect (COMMAND_PROMPT) +# print child.before + +# # Run MySQL show status. +# child.sendline ('mysql -p -e "SHOW STATUS;"') +# child.expect (PASSWORD_PROMPT_MYSQL) +# child.sendline (password_mysql) +# child.expect (COMMAND_PROMPT) +# print +# print child.before + + # Now exit the remote host. + child.sendline ('exit') + index = child.expect([pexpect.EOF, "(?i)there are stopped jobs"]) + if index==1: + child.sendline("exit") + child.expect(EOF) + +if __name__ == "__main__": + + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/src/link/pexpect/examples/passmass.py b/src/link/pexpect/examples/passmass.py new file mode 100755 index 0000000..b1e17b9 --- /dev/null +++ b/src/link/pexpect/examples/passmass.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +"""Change passwords on the named machines. passmass host1 host2 host3 . . . +Note that login shell prompt on remote machine must end in # or $. """ + +import pexpect +import sys, getpass + +USAGE = '''passmass host1 host2 host3 . . .''' +COMMAND_PROMPT = '[$#] ' +TERMINAL_PROMPT = r'Terminal type\?' +TERMINAL_TYPE = 'vt100' +SSH_NEWKEY = r'Are you sure you want to continue connecting \(yes/no\)\?' + +def login(host, user, password): + + child = pexpect.spawn('ssh -l %s %s'%(user, host)) + fout = file ("LOG.TXT","wb") + child.setlog (fout) + + i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, '[Pp]assword: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + sys.exit (1) + if i == 1: # SSH does not have the public key. Just accept it. + child.sendline ('yes') + child.expect ('[Pp]assword: ') + child.sendline(password) + # Now we are either at the command prompt or + # the login process is asking for our terminal type. + i = child.expect (['Permission denied', TERMINAL_PROMPT, COMMAND_PROMPT]) + if i == 0: + print 'Permission denied on host:', host + sys.exit (1) + if i == 1: + child.sendline (TERMINAL_TYPE) + child.expect (COMMAND_PROMPT) + return child + +# (current) UNIX password: +def change_password(child, user, oldpassword, newpassword): + + child.sendline('passwd') + i = child.expect(['[Oo]ld [Pp]assword', '.current.*password', '[Nn]ew [Pp]assword']) + # Root does not require old password, so it gets to bypass the next step. + if i == 0 or i == 1: + child.sendline(oldpassword) + child.expect('[Nn]ew [Pp]assword') + child.sendline(newpassword) + i = child.expect(['[Nn]ew [Pp]assword', '[Rr]etype', '[Rr]e-enter']) + if i == 0: + print 'Host did not like new password. Here is what it said...' + print child.before + child.send (chr(3)) # Ctrl-C + child.sendline('') # This should tell remote passwd command to quit. + return + child.sendline(newpassword) + +def main(): + + if len(sys.argv) <= 1: + print USAGE + return 1 + + user = raw_input('Username: ') + password = getpass.getpass('Current Password: ') + newpassword = getpass.getpass('New Password: ') + newpasswordconfirm = getpass.getpass('Confirm New Password: ') + if newpassword != newpasswordconfirm: + print 'New Passwords do not match.' + return 1 + + for host in sys.argv[1:]: + child = login(host, user, password) + if child == None: + print 'Could not login to host:', host + continue + print 'Changing password on host:', host + change_password(child, user, password, newpassword) + child.expect(COMMAND_PROMPT) + child.sendline('exit') + +if __name__ == '__main__': + try: + main() + except pexpect.ExceptionPexpect, e: + print str(e) + diff --git a/src/link/pexpect/examples/python.py b/src/link/pexpect/examples/python.py new file mode 100755 index 0000000..d8c9866 --- /dev/null +++ b/src/link/pexpect/examples/python.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +"""This starts the python interpreter; captures the startup message; then gives +the user interactive control over the session. Why? For fun... """ + +# Don't do this unless you like being John Malkovich +# c = pexpect.spawn ('/usr/bin/env python ./python.py') + +import pexpect +c = pexpect.spawn ('/usr/bin/env python') +c.expect ('>>>') +print 'And now for something completely different...' +f = lambda s:s and f(s[1:])+s[0] # Makes a function to reverse a string. +print f(c.before) +print 'Yes, it\'s python, but it\'s backwards.' +print +print 'Escape character is \'^]\'.' +print c.after, +c.interact() +c.kill(1) +print 'is alive:', c.isalive() + diff --git a/src/link/pexpect/examples/rippy.py b/src/link/pexpect/examples/rippy.py new file mode 100755 index 0000000..1301355 --- /dev/null +++ b/src/link/pexpect/examples/rippy.py @@ -0,0 +1,984 @@ +#!/usr/bin/env python + +"""Rippy! + +This script helps to convert video from one format to another. +This is useful for ripping DVD to mpeg4 video (XviD, DivX). + +Features: + * automatic crop detection + * mp3 audio compression with resampling options + * automatic bitrate calculation based on desired target size + * optional interlace removal, b/w video optimization, video scaling + +Run the script with no arguments to start with interactive prompts: + rippy.py +Run the script with the filename of a config to start automatic mode: + rippy.py rippy.conf + +After Rippy is finished it saves the current configuation in a file called +'rippy.conf' in the local directoy. This can be used to rerun process using the +exact same settings by passing the filename of the conf file as an argument to +Rippy. Rippy will read the options from the file instead of asking you for +options interactively. So if you run rippy with 'dry_run=1' then you can run +the process again later using the 'rippy.conf' file. Don't forget to edit +'rippy.conf' to set 'dry_run=0'! + +If you run rippy with 'dry_run' and 'verbose' true then the output generated is +valid command line commands. you could (in theory) cut-and-paste the commands +to a shell prompt. You will need to tweak some values such as crop area and bit +rate because these cannot be calculated in a dry run. This is useful if you +want to get an idea of what Rippy plans to do. + +For all the trouble that Rippy goes through to calculate the best bitrate for a +desired target video size it sometimes fails to get it right. Sometimes the +final video size will differ more than you wanted from the desired size, but if +you are really motivated and have a lot of time on your hands then you can run +Rippy again with a manually calculated bitrate. After all compression is done +the first time Rippy will recalculate the bitrate to give you the nearly exact +bitrate that would have worked. You can then edit the 'rippy.conf' file; set +the video_bitrate with this revised bitrate; and then run Rippy all over again. +There is nothing like 4-pass video compression to get it right! Actually, this +could be done in three passes since I don't need to do the second pass +compression before I calculate the revised bitrate. I'm also considering an +enhancement where Rippy would compress ten spread out chunks, 1-minute in +length to estimate the bitrate. + +Free, open source, and all that good stuff. +Rippy Copyright (c) 2006 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +Noah Spurrier +$Id: rippy.py 498 2007-12-17 13:44:19Z noah $ +""" + +import sys, os, re, math, stat, getopt, traceback, types, time +import pexpect + +__version__ = '1.2' +__revision__ = '$Revision: 11 $' +__all__ = ['main', __version__, __revision__] + +GLOBAL_LOGFILE_NAME = "rippy_%d.log" % os.getpid() +GLOBAL_LOGFILE = open (GLOBAL_LOGFILE_NAME, "wb") + +############################################################################### +# This giant section defines the prompts and defaults used in interactive mode. +############################################################################### +# Python dictionaries are unordered, so +# I have this list that maintains the order of the keys. +prompts_key_order = ( +'verbose_flag', +'dry_run_flag', +'video_source_filename', +'video_chapter', +'video_final_filename', +'video_length', +'video_aspect_ratio', +'video_scale', +'video_encode_passes', +'video_codec', +'video_fourcc_override', +'video_bitrate', +'video_bitrate_overhead', +'video_target_size', +'video_crop_area', +'video_deinterlace_flag', +'video_gray_flag', +'subtitle_id', +'audio_id', +'audio_codec', +'audio_raw_filename', +'audio_volume_boost', +'audio_sample_rate', +'audio_bitrate', +#'audio_lowpass_filter', +'delete_tmp_files_flag' +) +# +# The 'prompts' dictionary holds all the messages shown to the user in +# interactive mode. The 'prompts' dictionary schema is defined as follows: +# prompt_key : ( default value, prompt string, help string, level of difficulty (0,1,2) ) +# +prompts = { +'video_source_filename':("dvd://1", 'video source filename?', """This is the filename of the video that you want to convert from. +It can be any file that mencoder supports. +You can also choose a DVD device using the dvd://1 syntax. +Title 1 is usually the main title on a DVD.""",0), +'video_chapter':("none",'video chapter?',"""This is the chapter number. Usually disks such as TV series seasons will be divided into chapters. Maybe be set to none.""",0), +'video_final_filename':("video_final.avi", "video final filename?", """This is the name of the final video.""",0), +'audio_raw_filename':("audiodump.wav", "audio raw filename?", """This is the audio raw PCM filename. This is prior to compression. +Note that mplayer automatically names this audiodump.wav, so don't change this.""",1000), +#'audio_compressed_filename':("audiodump.mp3","Audio compressed filename?", """This is the name of the compressed audio that will be mixed +#into the final video. Normally you don't need to change this.""",2), +'video_length':("none","video length in seconds?","""This sets the length of the video in seconds. This is used to estimate the +bitrate for a target video file size. Set to 'calc' to have Rippy calculate +the length. Set to 'none' if you don't want rippy to estimate the bitrate -- +you will have to manually specify bitrate.""",1), +'video_aspect_ratio':("calc","aspect ratio?","""This sets the aspect ratio of the video. Most DVDs are 16/9 or 4/3.""",1), +'video_scale':("none","video scale?","""This scales the video to the given output size. The default is to do no scaling. +You may type in a resolution such as 320x240 or you may use presets. + qntsc: 352x240 (NTSC quarter screen) + qpal: 352x288 (PAL quarter screen) + ntsc: 720x480 (standard NTSC) + pal: 720x576 (standard PAL) + sntsc: 640x480 (square pixel NTSC) + spal: 768x576 (square pixel PAL)""",1), +'video_codec':("mpeg4","video codec?","""This is the video compression to use. This is passed directly to mencoder, so +any format that it recognizes should work. For XviD or DivX use mpeg4. +Almost all MS Windows systems support wmv2 out of the box. +Some common codecs include: +mjpeg, h263, h263p, h264, mpeg4, msmpeg4, wmv1, wmv2, mpeg1video, mpeg2video, huffyuv, ffv1. +""",2), +'audio_codec':("mp3","audio codec?","""This is the audio compression to use. This is passed directly to mencoder, so +any format that it recognizes will work. +Some common codecs include: +mp3, mp2, aac, pcm +See mencoder manual for details.""",2), +'video_fourcc_override':("XVID","force fourcc code?","""This forces the fourcc codec to the given value. XVID is safest for Windows. +The following are common fourcc values: + FMP4 - This is the mencoder default. This is the "real" value. + XVID - used by Xvid (safest) + DX50 - + MP4S - Microsoft""",2), +'video_encode_passes':("1","number of encode passes?","""This sets how many passes to use to encode the video. You can choose 1 or 2. +Using two pases takes twice as long as one pass, but produces a better +quality video. I found that the improvement is not that impressive.""",1), +'verbose_flag':("Y","verbose output?","""This sets verbose output. If true then all commands and arguments are printed +before they are run. This is useful to see exactly how commands are run.""",1), +'dry_run_flag':("N","dry run?","""This sets 'dry run' mode. If true then commands are not run. This is useful +if you want to see what would the script would do.""",1), +'video_bitrate':("calc","video bitrate?","""This sets the video bitrate. This overrides video_target_size. +Set to 'calc' to automatically estimate the bitrate based on the +video final target size. If you set video_length to 'none' then +you will have to specify this video_bitrate.""",1), +'video_target_size':("737280000","video final target size?","""This sets the target video size that you want to end up with. +This is over-ridden by video_bitrate. In other words, if you specify +video_bitrate then video_target_size is ignored. +Due to the unpredictable nature of VBR compression the final video size +may not exactly match. The following are common CDR sizes: + 180MB CDR (21 minutes) holds 193536000 bytes + 550MB CDR (63 minutes) holds 580608000 bytes + 650MB CDR (74 minutes) holds 681984000 bytes + 700MB CDR (80 minutes) holds 737280000 bytes""",0), +'video_bitrate_overhead':("1.0","bitrate overhead factor?","""Adjust this value if you want to leave more room for +other files such as subtitle files. +If you specify video_bitrate then this value is ignored.""",2), +'video_crop_area':("detect","crop area?","""This sets the crop area to remove black bars from the top or sides of the video. +This helps save space. Set to 'detect' to automatically detect the crop area. +Set to 'none' to not crop the video. Normally you don't need to change this.""",1), +'video_deinterlace_flag':("N","is the video interlaced?","""This sets the deinterlace flag. If set then mencoder will be instructed +to filter out interlace artifacts (using '-vf pp=md').""",1), +'video_gray_flag':("N","is the video black and white (gray)?","""This improves output for black and white video.""",1), +'subtitle_id':("None","Subtitle ID stream?","""This selects the subtitle stream to extract from the source video. +Normally, 0 is the English subtitle stream for a DVD. +Subtitles IDs with higher numbers may be other languages.""",1), +'audio_id':("128","audio ID stream?","""This selects the audio stream to extract from the source video. +If your source is a VOB file (DVD) then stream IDs start at 128. +Normally, 128 is the main audio track for a DVD. +Tracks with higher numbers may be other language dubs or audio commentary.""",1), +'audio_sample_rate':("32000","audio sample rate (Hz) 48000, 44100, 32000, 24000, 12000","""This sets the rate at which the compressed audio will be resampled. +DVD audio is 48 kHz whereas music CDs use 44.1 kHz. The higher the sample rate +the more space the audio track will take. That will leave less space for video. +32 kHz is a good trade-off if you are trying to fit a video onto a CD.""",1), +'audio_bitrate':("96","audio bitrate (kbit/s) 192, 128, 96, 64?","""This sets the bitrate for MP3 audio compression. +The higher the bitrate the more space the audio track will take. +That will leave less space for video. Most people find music to be acceptable +at 128 kBitS. 96 kBitS is a good trade-off if you are trying to fit a video onto a CD.""",1), +'audio_volume_boost':("none","volume dB boost?","""Many DVDs have very low audio volume. This sets an audio volume boost in Decibels. +Values of 6 to 10 usually adjust quiet DVDs to a comfortable level.""",1), +#'audio_lowpass_filter':("16","audio lowpass filter (kHz)?","""This sets the low-pass filter for the audio. +#Normally this should be half of the audio sample rate. +#This improves audio compression and quality. +#Normally you don't need to change this.""",1), +'delete_tmp_files_flag':("N","delete temporary files when finished?","""If Y then %s, audio_raw_filename, and 'divx2pass.log' will be deleted at the end."""%GLOBAL_LOGFILE_NAME,1) +} + +############################################################################## +# This is the important convert control function +############################################################################## +def convert (options): + """This is the heart of it all -- this performs an end-to-end conversion of + a video from one format to another. It requires a dictionary of options. + The conversion process will also add some keys to the dictionary + such as length of the video and crop area. The dictionary is returned. + This options dictionary could be used again to repeat the convert process + (it is also saved to rippy.conf as text). + """ + if options['subtitle_id'] is not None: + print "# extract subtitles" + apply_smart (extract_subtitles, options) + else: + print "# do not extract subtitles." + + # Optimization + # I really only need to calculate the exact video length if the user + # selected 'calc' for video_bitrate + # or + # selected 'detect' for video_crop_area. + if options['video_bitrate']=='calc' or options['video_crop_area']=='detect': + # As strange as it seems, the only reliable way to calculate the length + # of a video (in seconds) is to extract the raw, uncompressed PCM audio stream + # and then calculate the length of that. This is because MP4 video is VBR, so + # you cannot get exact time based on compressed size. + if options['video_length']=='calc': + print "# extract PCM raw audio to %s" % (options['audio_raw_filename']) + apply_smart (extract_audio, options) + options['video_length'] = apply_smart (get_length, options) + print "# Length of raw audio file : %d seconds (%0.2f minutes)" % (options['video_length'], float(options['video_length'])/60.0) + if options['video_bitrate']=='calc': + options['video_bitrate'] = options['video_bitrate_overhead'] * apply_smart (calc_video_bitrate, options) + print "# video bitrate : " + str(options['video_bitrate']) + if options['video_crop_area']=='detect': + options['video_crop_area'] = apply_smart (crop_detect, options) + print "# crop area : " + str(options['video_crop_area']) + print "# compression estimate" + print apply_smart (compression_estimate, options) + + print "# compress video" + apply_smart (compress_video, options) + 'audio_volume_boost', + + print "# delete temporary files:", + if options['delete_tmp_files_flag']: + print "yes" + apply_smart (delete_tmp_files, options) + else: + print "no" + + # Finish by saving options to rippy.conf and + # calclating if final_size is less than target_size. + o = ["# options used to create video\n"] + video_actual_size = get_filesize (options['video_final_filename']) + if options['video_target_size'] != 'none': + revised_bitrate = calculate_revised_bitrate (options['video_bitrate'], options['video_target_size'], video_actual_size) + o.append("# revised video_bitrate : %d\n" % revised_bitrate) + for k,v in options.iteritems(): + o.append (" %30s : %s\n" % (k, v)) + print '# '.join(o) + fout = open("rippy.conf","wb").write(''.join(o)) + print "# final actual video size = %d" % video_actual_size + if options['video_target_size'] != 'none': + if video_actual_size > options['video_target_size']: + print "# FINAL VIDEO SIZE IS GREATER THAN DESIRED TARGET" + print "# final video size is %d bytes over target size" % (video_actual_size - options['video_target_size']) + else: + print "# final video size is %d bytes under target size" % (options['video_target_size'] - video_actual_size) + print "# If you want to run the entire compression process all over again" + print "# to get closer to the target video size then trying using a revised" + print "# video_bitrate of %d" % revised_bitrate + + return options + +############################################################################## + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + print 'version:', globals()['__version__'] + sys.stdout.flush() + os._exit(exit_code) + +def check_missing_requirements (): + """This list of missing requirements (mencoder, mplayer, lame, and mkvmerge). + Returns None if all requirements are in the execution path. + """ + missing = [] + if pexpect.which("mencoder") is None: + missing.append("mencoder") + if pexpect.which("mplayer") is None: + missing.append("mplayer") + #if pexpect.which("lame") is None: + # missing.append("lame") + #if pexpect.which("mkvmerge") is None: + # missing.append("mkvmerge") + if len(missing)==0: + return None + return missing + +def input_option (message, default_value="", help=None, level=0, max_level=0): + """This is a fancy raw_input function. + If the user enters '?' then the contents of help is printed. + + The 'level' and 'max_level' are used to adjust which advanced options + are printed. 'max_level' is the level of options that the user wants + to see. 'level' is the level of difficulty for this particular option. + If this level is <= the max_level the user wants then the + message is printed and user input is allowed; otherwise, the + default value is returned automatically without user input. + """ + if default_value != '': + message = "%s [%s] " % (message, default_value) + if level > max_level: + return default_value + while 1: + user_input = raw_input (message) + if user_input=='?': + print help + elif user_input=='': + return default_value + else: + break + return user_input + +def progress_callback (d=None): + """This callback simply prints a dot to show activity. + This is used when running external commands with pexpect.run. + """ + sys.stdout.write (".") + sys.stdout.flush() + +def run(cmd): + global GLOBAL_LOGFILE + print >>GLOBAL_LOGFILE, cmd + (command_output, exitstatus) = pexpect.run(cmd, events={pexpect.TIMEOUT:progress_callback}, timeout=5, withexitstatus=True, logfile=GLOBAL_LOGFILE) + if exitstatus != 0: + print "RUN FAILED. RETURNED EXIT STATUS:", exitstatus + print >>GLOBAL_LOGFILE, "RUN FAILED. RETURNED EXIT STATUS:", exitstatus + return (command_output, exitstatus) + +def apply_smart (func, args): + """This is similar to func(**args), but this won't complain about + extra keys in 'args'. This ignores keys in 'args' that are + not required by 'func'. This passes None to arguments that are + not defined in 'args'. That's fine for arguments with a default valeue, but + that's a bug for required arguments. I should probably raise a TypeError. + The func parameter can be a function reference or a string. + If it is a string then it is converted to a function reference. + """ + if type(func) is type(''): + if func in globals(): + func = globals()[func] + else: + raise NameError("name '%s' is not defined" % func) + if hasattr(func,'im_func'): # Handle case when func is a class method. + func = func.im_func + argcount = func.func_code.co_argcount + required_args = dict([(k,args.get(k)) for k in func.func_code.co_varnames[:argcount]]) + return func(**required_args) + +def count_unique (items): + """This takes a list and returns a sorted list of tuples with a count of each unique item in the list. + Example 1: + count_unique(['a','b','c','a','c','c','a','c','c']) + returns: + [(5,'c'), (3,'a'), (1,'b')] + Example 2 -- get the most frequent item in a list: + count_unique(['a','b','c','a','c','c','a','c','c'])[0][1] + returns: + 'c' + """ + stats = {} + for i in items: + if i in stats: + stats[i] = stats[i] + 1 + else: + stats[i] = 1 + stats = [(v, k) for k, v in stats.items()] + stats.sort() + stats.reverse() + return stats + +def calculate_revised_bitrate (video_bitrate, video_target_size, video_actual_size): + """This calculates a revised video bitrate given the video_bitrate used, + the actual size that resulted, and the video_target_size. + This can be used if you want to compress the video all over again in an + attempt to get closer to the video_target_size. + """ + return int(math.floor(video_bitrate * (float(video_target_size) / float(video_actual_size)))) + +def get_aspect_ratio (video_source_filename): + """This returns the aspect ratio of the original video. + This is usualy 1.78:1(16/9) or 1.33:1(4/3). + This function is very lenient. It basically guesses 16/9 whenever + it cannot figure out the aspect ratio. + """ + cmd = "mplayer '%s' -vo png -ao null -frames 1" % video_source_filename + (command_output, exitstatus) = run(cmd) + ar = re.findall("Movie-Aspect is ([0-9]+\.?[0-9]*:[0-9]+\.?[0-9]*)", command_output) + if len(ar)==0: + return '16/9' + if ar[0] == '1.78:1': + return '16/9' + if ar[0] == '1.33:1': + return '4/3' + return '16/9' + #idh = re.findall("ID_VIDEO_HEIGHT=([0-9]+)", command_output) + #if len(idw)==0 or len(idh)==0: + # print 'WARNING!' + # print 'Could not get aspect ration. Assuming 1.78:1 (16/9).' + # return 1.78 + #return float(idw[0])/float(idh[0]) +#ID_VIDEO_WIDTH=720 +#ID_VIDEO_HEIGHT=480 +#Movie-Aspect is 1.78:1 - prescaling to correct movie aspect. + + +def get_aid_list (video_source_filename): + """This returns a list of audio ids in the source video file. + TODO: Also extract ID_AID_nnn_LANG to associate language. Not all DVDs include this. + """ + cmd = "mplayer '%s' -vo null -ao null -frames 0 -identify" % video_source_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_AUDIO_ID=([0-9]+)", command_output) + idl.sort() + return idl + +def get_sid_list (video_source_filename): + """This returns a list of subtitle ids in the source video file. + TODO: Also extract ID_SID_nnn_LANG to associate language. Not all DVDs include this. + """ + cmd = "mplayer '%s' -vo null -ao null -frames 0 -identify" % video_source_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_SUBTITLE_ID=([0-9]+)", command_output) + idl.sort() + return idl + +def extract_audio (video_source_filename, audio_id=128, verbose_flag=0, dry_run_flag=0): + """This extracts the given audio_id track as raw uncompressed PCM from the given source video. + Note that mplayer always saves this to audiodump.wav. + At this time there is no way to set the output audio name. + """ + #cmd = "mplayer %(video_source_filename)s -vc null -vo null -aid %(audio_id)s -ao pcm:fast -noframedrop" % locals() + cmd = "mplayer -quiet '%(video_source_filename)s' -vc dummy -vo null -aid %(audio_id)s -ao pcm:fast -noframedrop" % locals() + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def extract_subtitles (video_source_filename, subtitle_id=0, verbose_flag=0, dry_run_flag=0): + """This extracts the given subtitle_id track as VOBSUB format from the given source video. + """ + cmd = "mencoder -quiet '%(video_source_filename)s' -o /dev/null -nosound -ovc copy -vobsubout subtitles -vobsuboutindex 0 -sid %(subtitle_id)s" % locals() + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def get_length (audio_raw_filename): + """This attempts to get the length of the media file (length is time in seconds). + This should not be confused with size (in bytes) of the file data. + This is best used on a raw PCM AUDIO file because mplayer cannot get an accurate + time for many compressed video and audio formats -- notably MPEG4 and MP3. + Weird... + This returns -1 if it cannot get the length of the given file. + """ + cmd = "mplayer %s -vo null -ao null -frames 0 -identify" % audio_raw_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_LENGTH=([0-9.]*)", command_output) + idl.sort() + if len(idl) != 1: + print "ERROR: cannot get length of raw audio file." + print "command_output of mplayer identify:" + print command_output + print "parsed command_output:" + print str(idl) + return -1 + return float(idl[0]) + +def get_filesize (filename): + """This returns the number of bytes a file takes on storage.""" + return os.stat(filename)[stat.ST_SIZE] + +def calc_video_bitrate (video_target_size, audio_bitrate, video_length, extra_space=0, dry_run_flag=0): + """This gives an estimate of the video bitrate necessary to + fit the final target size. This will take into account room to + fit the audio and extra space if given (for container overhead or whatnot). + video_target_size is in bytes, + audio_bitrate is bits per second (96, 128, 256, etc.) ASSUMING CBR, + video_length is in seconds, + extra_space is in bytes. + a 180MB CDR (21 minutes) holds 193536000 bytes. + a 550MB CDR (63 minutes) holds 580608000 bytes. + a 650MB CDR (74 minutes) holds 681984000 bytes. + a 700MB CDR (80 minutes) holds 737280000 bytes. + """ + if dry_run_flag: + return -1 + if extra_space is None: extra_space = 0 + #audio_size = os.stat(audio_compressed_filename)[stat.ST_SIZE] + audio_size = (audio_bitrate * video_length * 1000) / 8.0 + video_target_size = video_target_size - audio_size - extra_space + return (int)(calc_video_kbitrate (video_target_size, video_length)) + +def calc_video_kbitrate (target_size, length_secs): + """Given a target byte size free for video data, this returns the bitrate in kBit/S. + For mencoder vbitrate 1 kBit = 1000 Bits -- not 1024 bits. + target_size = bitrate * 1000 * length_secs / 8 + target_size = bitrate * 125 * length_secs + bitrate = target_size/(125*length_secs) + """ + return int(target_size / (125.0 * length_secs)) + +def crop_detect (video_source_filename, video_length, dry_run_flag=0): + """This attempts to figure out the best crop for the given video file. + Basically it runs crop detect for 10 seconds on five different places in the video. + It picks the crop area that was most often detected. + """ + skip = int(video_length/9) # offset to skip (-ss option in mencoder) + sample_length = 10 + cmd1 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, skip, sample_length) + cmd2 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 2*skip, sample_length) + cmd3 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 4*skip, sample_length) + cmd4 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 6*skip, sample_length) + cmd5 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 8*skip, sample_length) + if dry_run_flag: + return "0:0:0:0" + (command_output1, exitstatus1) = run(cmd1) + (command_output2, exitstatus2) = run(cmd2) + (command_output3, exitstatus3) = run(cmd3) + (command_output4, exitstatus4) = run(cmd4) + (command_output5, exitstatus5) = run(cmd5) + idl = re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output1) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output2) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output3) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output4) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output5) + items_count = count_unique(idl) + return items_count[0][1] + + +def build_compression_command (video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None, seek_skip=None, seek_length=None, video_chapter=None): +#Notes:For DVD, VCD, and SVCD use acodec=mp2 and vcodec=mpeg2video: +#mencoder movie.avi -o movie.VOB -ovc lavc -oac lavc -lavcopts acodec=mp2:abitrate=224:vcodec=mpeg2video:vbitrate=2000 + + # + # build video filter (-vf) argument + # + video_filter = '' + if video_crop_area and video_crop_area.lower()!='none': + video_filter = video_filter + 'crop=%s' % video_crop_area + if video_deinterlace_flag: + if video_filter != '': + video_filter = video_filter + ',' + video_filter = video_filter + 'pp=md' + if video_scale and video_scale.lower()!='none': + if video_filter != '': + video_filter = video_filter + ',' + video_filter = video_filter + 'scale=%s' % video_scale + # optional video rotation -- were you holding your camera sideways? + #if video_filter != '': + # video_filter = video_filter + ',' + #video_filter = video_filter + 'rotate=2' + if video_filter != '': + video_filter = '-vf ' + video_filter + + # + # build chapter argument + # + if video_chapter is not None: + chapter = '-chapter %d-%d' %(video_chapter,video_chapter) + else: + chapter = '' +# chapter = '-chapter 2-2' + + # + # build audio_filter argument + # + audio_filter = '' + if audio_sample_rate: + if audio_filter != '': + audio_filter = audio_filter + ',' + audio_filter = audio_filter + 'lavcresample=%s' % audio_sample_rate + if audio_volume_boost is not None: + if audio_filter != '': + audio_filter = audio_filter + ',' + audio_filter = audio_filter + 'volume=%0.1f:1'%audio_volume_boost + if audio_filter != '': + audio_filter = '-af ' + audio_filter + # + #if audio_sample_rate: + # audio_filter = ('-srate %d ' % audio_sample_rate) + audio_filter + + # + # build lavcopts argument + # + #lavcopts = '-lavcopts vcodec=%s:vbitrate=%d:mbd=2:aspect=%s:acodec=%s:abitrate=%d:vpass=1' % (video_codec,video_bitrate,audio_codec,audio_bitrate) + lavcopts = '-lavcopts vcodec=%(video_codec)s:vbitrate=%(video_bitrate)d:mbd=2:aspect=%(video_aspect_ratio)s:acodec=%(audio_codec)s:abitrate=%(audio_bitrate)d:vpass=1' % (locals()) + if video_gray_flag: + lavcopts = lavcopts + ':gray' + + seek_filter = '' + if seek_skip is not None: + seek_filter = '-ss %s' % (str(seek_skip)) + if seek_length is not None: + seek_filter = seek_filter + ' -endpos %s' % (str(seek_length)) + + cmd = "mencoder -quiet -info comment='Arkivist' '%(video_source_filename)s' %(seek_filter)s %(chapter)s -aid %(audio_id)s -o '%(video_final_filename)s' -ffourcc %(video_fourcc_override)s -ovc lavc -oac lavc %(lavcopts)s %(video_filter)s %(audio_filter)s" % locals() + return cmd + +def compression_estimate (video_length, video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None): + """This attempts to figure out the best compression ratio for a given set of compression options. + """ + # TODO Need to account for AVI overhead. + skip = int(video_length/9) # offset to skip (-ss option in mencoder) + sample_length = 10 + cmd1 = build_compression_command (video_source_filename, "compression_test_1.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip, sample_length) + cmd2 = build_compression_command (video_source_filename, "compression_test_2.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*2, sample_length) + cmd3 = build_compression_command (video_source_filename, "compression_test_3.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*4, sample_length) + cmd4 = build_compression_command (video_source_filename, "compression_test_4.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*6, sample_length) + cmd5 = build_compression_command (video_source_filename, "compression_test_5.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*8, sample_length) + run(cmd1) + run(cmd2) + run(cmd3) + run(cmd4) + run(cmd5) + size = get_filesize ("compression_test_1.avi")+get_filesize ("compression_test_2.avi")+get_filesize ("compression_test_3.avi")+get_filesize ("compression_test_4.avi")+get_filesize ("compression_test_5.avi") + return (size / 5.0) + +def compress_video (video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None, seek_skip=None, seek_length=None, video_chapter=None, verbose_flag=0, dry_run_flag=0): + """This compresses the video and audio of the given source video filename to the transcoded filename. + This does a two-pass compression (I'm assuming mpeg4, I should probably make this smarter for other formats). + """ + # + # do the first pass video compression + # + #cmd = "mencoder -quiet '%(video_source_filename)s' -ss 65 -endpos 20 -aid %(audio_id)s -o '%(video_final_filename)s' -ffourcc %(video_fourcc_override)s -ovc lavc -oac lavc %(lavcopts)s %(video_filter)s %(audio_filter)s" % locals() + + cmd = build_compression_command (video_source_filename, video_final_filename, video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, seek_skip, seek_length, video_chapter) + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + + # If not doing two passes then return early. + if video_encode_passes!='2': + return + + if verbose_flag: + video_actual_size = get_filesize (video_final_filename) + if video_actual_size > video_target_size: + print "=======================================================" + print "WARNING!" + print "First pass compression resulted in" + print "actual file size greater than target size." + print "Second pass will be too big." + print "=======================================================" + + # + # do the second pass video compression + # + cmd = cmd.replace ('vpass=1', 'vpass=2') + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + return + +def compress_audio (audio_raw_filename, audio_compressed_filename, audio_lowpass_filter=None, audio_sample_rate=None, audio_bitrate=None, verbose_flag=0, dry_run_flag=0): + """This is depricated. + This compresses the raw audio file to the compressed audio filename. + """ + cmd = 'lame -h --athaa-sensitivity 1' # --cwlimit 11" + if audio_lowpass_filter: + cmd = cmd + ' --lowpass ' + audio_lowpass_filter + if audio_bitrate: + #cmd = cmd + ' --abr ' + audio_bitrate + cmd = cmd + ' --cbr -b ' + audio_bitrate + if audio_sample_rate: + cmd = cmd + ' --resample ' + audio_sample_rate + cmd = cmd + ' ' + audio_raw_filename + ' ' + audio_compressed_filename + if verbose_flag: print cmd + if not dry_run_flag: + (command_output, exitstatus) = run(cmd) + print + if exitstatus != 0: + raise Exception('ERROR: lame failed to compress raw audio file.') + +def mux (video_final_filename, video_transcoded_filename, audio_compressed_filename, video_container_format, verbose_flag=0, dry_run_flag=0): + """This is depricated. I used to use a three-pass encoding where I would mix the audio track separately, but + this never worked very well (loss of audio sync).""" + if video_container_format.lower() == 'mkv': # Matroska + mux_mkv (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag, dry_run_flag) + if video_container_format.lower() == 'avi': + mux_avi (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag, dry_run_flag) + +def mux_mkv (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag=0, dry_run_flag=0): + """This is depricated.""" + cmd = 'mkvmerge -o %s --noaudio %s %s' % (video_final_filename, video_transcoded_filename, audio_compressed_filename) + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def mux_avi (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag=0, dry_run_flag=0): + """This is depricated.""" + cmd = "mencoder -quiet -oac copy -ovc copy -o '%s' -audiofile %s '%s'" % (video_final_filename, audio_compressed_filename, video_transcoded_filename) + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def delete_tmp_files (audio_raw_filename, verbose_flag=0, dry_run_flag=0): + global GLOBAL_LOGFILE_NAME + file_list = ' '.join([GLOBAL_LOGFILE_NAME, 'divx2pass.log', audio_raw_filename ]) + cmd = 'rm -f ' + file_list + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +############################################################################## +# This is the interactive Q&A that is used if a conf file was not given. +############################################################################## +def interactive_convert (): + global prompts, prompts_key_order + + print globals()['__doc__'] + print + print "==============================================" + print " Enter '?' at any question to get extra help." + print "==============================================" + print + + # Ask for the level of options the user wants. + # A lot of code just to print a string! + level_sort = {0:'', 1:'', 2:''} + for k in prompts: + level = prompts[k][3] + if level < 0 or level > 2: + continue + level_sort[level] += " " + prompts[k][1] + "\n" + level_sort_string = "This sets the level for advanced options prompts. Set 0 for simple, 1 for advanced, or 2 for expert.\n" + level_sort_string += "[0] Basic options:\n" + str(level_sort[0]) + "\n" + level_sort_string += "[1] Advanced options:\n" + str(level_sort[1]) + "\n" + level_sort_string += "[2] Expert options:\n" + str(level_sort[2]) + c = input_option("Prompt level (0, 1, or 2)?", "1", level_sort_string) + max_prompt_level = int(c) + + options = {} + for k in prompts_key_order: + if k == 'video_aspect_ratio': + guess_aspect = get_aspect_ratio(options['video_source_filename']) + options[k] = input_option (prompts[k][1], guess_aspect, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'audio_id': + aid_list = get_aid_list (options['video_source_filename']) + default_id = '128' + if max_prompt_level>=prompts[k][3]: + if len(aid_list) > 1: + print "This video has more than one audio stream. The following stream audio IDs were found:" + for aid in aid_list: + print " " + aid + default_id = aid_list[0] + else: + print "WARNING!" + print "Rippy was unable to get the list of audio streams from this video." + print "If reading directly from a DVD then the DVD device might be busy." + print "Using a default setting of stream id 128 (main audio on most DVDs)." + default_id = '128' + options[k] = input_option (prompts[k][1], default_id, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'subtitle_id': + sid_list = get_sid_list (options['video_source_filename']) + default_id = 'None' + if max_prompt_level>=prompts[k][3]: + if len(sid_list) > 0: + print "This video has one or more subtitle streams. The following stream subtitle IDs were found:" + for sid in sid_list: + print " " + sid + #default_id = sid_list[0] + default_id = prompts[k][0] + else: + print "WARNING!" + print "Unable to get the list of subtitle streams from this video. It may have none." + print "Setting default to None." + default_id = 'None' + options[k] = input_option (prompts[k][1], default_id, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'audio_lowpass_filter': + lowpass_default = "%.1f" % (math.floor(float(options['audio_sample_rate']) / 2.0)) + options[k] = input_option (prompts[k][1], lowpass_default, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'video_bitrate': + if options['video_length'].lower() == 'none': + options[k] = input_option (prompts[k][1], '1000', prompts[k][2], prompts[k][3], max_prompt_level) + else: + options[k] = input_option (prompts[k][1], prompts[k][0], prompts[k][2], prompts[k][3], max_prompt_level) + else: + # don't bother asking for video_target_size or video_bitrate_overhead if video_bitrate was set + if (k=='video_target_size' or k=='video_bitrate_overhead') and options['video_bitrate']!='calc': + continue + # don't bother with crop area if video length is none + if k == 'video_crop_area' and options['video_length'].lower() == 'none': + options['video_crop_area'] = 'none' + continue + options[k] = input_option (prompts[k][1], prompts[k][0], prompts[k][2], prompts[k][3], max_prompt_level) + + #options['video_final_filename'] = options['video_final_filename'] + "." + options['video_container_format'] + + print "==========================================================================" + print "Ready to Rippy!" + print + print "The following options will be used:" + for k,v in options.iteritems(): + print "%27s : %s" % (k, v) + + print + c = input_option("Continue?", "Y") + c = c.strip().lower() + if c[0] != 'y': + print "Exiting..." + os._exit(1) + return options + +def clean_options (d): + """This validates and cleans up the options dictionary. + After reading options interactively or from a conf file + we need to make sure that the values make sense and are + converted to the correct type. + 1. Any key with "_flag" in it becomes a boolean True or False. + 2. Values are normalized ("No", "None", "none" all become "none"; + "Calcluate", "c", "CALC" all become "calc"). + 3. Certain values are converted from string to int. + 4. Certain combinations of options are invalid or override each other. + This is a rather annoying function, but then so it most cleanup work. + """ + for k in d: + d[k] = d[k].strip() + # convert all flag options to 0 or 1 + if '_flag' in k: + if type(d[k]) is types.StringType: + if d[k].strip().lower()[0] in 'yt1': #Yes, True, 1 + d[k] = 1 + else: + d[k] = 0 + d['video_bitrate'] = d['video_bitrate'].lower() + if d['video_bitrate'][0]=='c': + d['video_bitrate']='calc' + else: + d['video_bitrate'] = int(float(d['video_bitrate'])) + try: + d['video_target_size'] = int(d['video_target_size']) + # shorthand magic numbers get automatically expanded + if d['video_target_size'] == 180: + d['video_target_size'] = 193536000 + elif d['video_target_size'] == 550: + d['video_target_size'] = 580608000 + elif d['video_target_size'] == 650: + d['video_target_size'] = 681984000 + elif d['video_target_size'] == 700: + d['video_target_size'] = 737280000 + except: + d['video_target_size'] = 'none' + + try: + d['video_chapter'] = int(d['video_chapter']) + except: + d['video_chapter'] = None + + try: + d['subtitle_id'] = int(d['subtitle_id']) + except: + d['subtitle_id'] = None + + try: + d['video_bitrate_overhead'] = float(d['video_bitrate_overhead']) + except: + d['video_bitrate_overhead'] = -1.0 + + d['audio_bitrate'] = int(d['audio_bitrate']) + d['audio_sample_rate'] = int(d['audio_sample_rate']) + d['audio_volume_boost'] = d['audio_volume_boost'].lower() + if d['audio_volume_boost'][0]=='n': + d['audio_volume_boost'] = None + else: + d['audio_volume_boost'] = d['audio_volume_boost'].replace('db','') + d['audio_volume_boost'] = float(d['audio_volume_boost']) + +# assert (d['video_bitrate']=='calc' and d['video_target_size']!='none') +# or (d['video_bitrate']!='calc' and d['video_target_size']=='none') + + d['video_scale'] = d['video_scale'].lower() + if d['video_scale'][0]=='n': + d['video_scale']='none' + else: + al = re.findall("([0-9]+).*?([0-9]+)", d['video_scale']) + d['video_scale']=al[0][0]+':'+al[0][1] + d['video_crop_area'] = d['video_crop_area'].lower() + if d['video_crop_area'][0]=='n': + d['video_crop_area']='none' + d['video_length'] = d['video_length'].lower() + if d['video_length'][0]=='c': + d['video_length']='calc' + elif d['video_length'][0]=='n': + d['video_length']='none' + else: + d['video_length'] = int(float(d['video_length'])) + if d['video_length']==0: + d['video_length'] = 'none' + assert (not (d['video_length']=='none' and d['video_bitrate']=='calc')) + return d + +def main (): + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + command_line_options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + missing = check_missing_requirements() + if missing is not None: + print + print "==========================================================================" + print "ERROR!" + print "Some required external commands are missing." + print "please install the following packages:" + print str(missing) + print "==========================================================================" + print + c = input_option("Continue?", "Y") + c = c.strip().lower() + if c[0] != 'y': + print "Exiting..." + os._exit(1) + + if len(args) > 0: + # cute one-line string-to-dictionary parser (two-lines if you count this comment): + options = dict(re.findall('([^: \t\n]*)\s*:\s*(".*"|[^ \t\n]*)', file(args[0]).read())) + options = clean_options(options) + convert (options) + else: + options = interactive_convert () + options = clean_options(options) + convert (options) + print "# Done!" + +if __name__ == "__main__": + try: + start_time = time.time() + print time.asctime() + main() + print time.asctime() + print "TOTAL TIME IN MINUTES:", + print (time.time() - start_time) / 60.0 + except Exception, e: + tb_dump = traceback.format_exc() + print "==========================================================================" + print "ERROR -- Unexpected exception in script." + print str(e) + print str(tb_dump) + print "==========================================================================" + print >>GLOBAL_LOGFILE, "==========================================================================" + print >>GLOBAL_LOGFILE, "ERROR -- Unexpected exception in script." + print >>GLOBAL_LOGFILE, str(e) + print >>GLOBAL_LOGFILE, str(tb_dump) + print >>GLOBAL_LOGFILE, "==========================================================================" + exit_with_usage(3) + diff --git a/src/link/pexpect/examples/script.py b/src/link/pexpect/examples/script.py new file mode 100755 index 0000000..908b912 --- /dev/null +++ b/src/link/pexpect/examples/script.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +"""This spawns a sub-shell (bash) and gives the user interactive control. The +entire shell session is logged to a file called script.log. This behaves much +like the classic BSD command 'script'. + +./script.py [-a] [-c command] {logfilename} + + logfilename : This is the name of the log file. Default is script.log. + -a : Append to log file. Default is to overwrite log file. + -c : spawn command. Default is to spawn the sh shell. + +Example: + + This will start a bash shell and append to the log named my_session.log: + + ./script.py -a -c bash my_session.log + +""" + +import os, sys, time, getopt +import signal, fcntl, termios, struct +import traceback +import pexpect + +global_pexpect_instance = None # Used by signal handler + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + ###################################################################### + # Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if len(args) == 1: + script_filename = args[0] + else: + script_filename = "script.log" + if '-a' in options: + fout = file (script_filename, "ab") + else: + fout = file (script_filename, "wb") + if '-c' in options: + command = options['-c'] + else: + command = "sh" + + # Begin log with date/time in the form CCCCyymm.hhmmss + fout.write ('# %4d%02d%02d.%02d%02d%02d \n' % time.localtime()[:-3]) + + ###################################################################### + # Start the interactive session + ###################################################################### + p = pexpect.spawn(command) + p.logfile = fout + global global_pexpect_instance + global_pexpect_instance = p + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + + print "Script recording started. Type ^] (ASCII 29) to escape from the script shell." + p.interact(chr(29)) + fout.close() + return 0 + +def sigwinch_passthrough (sig, data): + + # Check for buggy platforms (see pexpect.setwinsize()). + if 'TIOCGWINSZ' in dir(termios): + TIOCGWINSZ = termios.TIOCGWINSZ + else: + TIOCGWINSZ = 1074295912 # assume + s = struct.pack ("HHHH", 0, 0, 0, 0) + a = struct.unpack ('HHHH', fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ , s)) + global global_pexpect_instance + global_pexpect_instance.setwinsize(a[0],a[1]) + +if __name__ == "__main__": + try: + main() + except SystemExit, e: + raise e + except Exception, e: + print "ERROR" + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/src/link/pexpect/examples/ssh_session.py b/src/link/pexpect/examples/ssh_session.py new file mode 100755 index 0000000..4d0e228 --- /dev/null +++ b/src/link/pexpect/examples/ssh_session.py @@ -0,0 +1,94 @@ +# +# Eric S. Raymond +# +# Greatly modified by Nigel W. Moriarty +# April 2003 +# +from pexpect import * +import os, sys +import getpass +import time + +class ssh_session: + + "Session with extra state including the password to be used." + + def __init__(self, user, host, password=None, verbose=0): + + self.user = user + self.host = host + self.verbose = verbose + self.password = password + self.keys = [ + 'authenticity', + 'assword:', + '@@@@@@@@@@@@', + 'Command not found.', + EOF, + ] + + self.f = open('ssh.out','w') + + def __repr__(self): + + outl = 'class :'+self.__class__.__name__ + for attr in self.__dict__: + if attr == 'password': + outl += '\n\t'+attr+' : '+'*'*len(self.password) + else: + outl += '\n\t'+attr+' : '+str(getattr(self, attr)) + return outl + + def __exec(self, command): + + "Execute a command on the remote host. Return the output." + child = spawn(command, + #timeout=10, + ) + if self.verbose: + sys.stderr.write("-> " + command + "\n") + seen = child.expect(self.keys) + self.f.write(str(child.before) + str(child.after)+'\n') + if seen == 0: + child.sendline('yes') + seen = child.expect(self.keys) + if seen == 1: + if not self.password: + self.password = getpass.getpass('Remote password: ') + child.sendline(self.password) + child.readline() + time.sleep(5) + # Added to allow the background running of remote process + if not child.isalive(): + seen = child.expect(self.keys) + if seen == 2: + lines = child.readlines() + self.f.write(lines) + if self.verbose: + sys.stderr.write("<- " + child.before + "|\n") + try: + self.f.write(str(child.before) + str(child.after)+'\n') + except: + pass + self.f.close() + return child.before + + def ssh(self, command): + + return self.__exec("ssh -l %s %s \"%s\"" \ + % (self.user,self.host,command)) + + def scp(self, src, dst): + + return self.__exec("scp %s %s@%s:%s" \ + % (src, session.user, session.host, dst)) + + def exists(self, file): + + "Retrieve file permissions of specified remote file." + seen = self.ssh("/bin/ls -ld %s" % file) + if string.find(seen, "No such file") > -1: + return None # File doesn't exist + else: + return seen.split()[0] # Return permission field of listing. + diff --git a/src/link/pexpect/examples/ssh_tunnel.py b/src/link/pexpect/examples/ssh_tunnel.py new file mode 100755 index 0000000..3c8bc09 --- /dev/null +++ b/src/link/pexpect/examples/ssh_tunnel.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +"""This starts an SSH tunnel to a given host. If the SSH process ever dies then +this script will detect that and restart it. I use this under Cygwin to keep +open encrypted tunnels to port 25 (SMTP), port 143 (IMAP4), and port 110 +(POP3). I set my mail client to talk to localhost and I keep this script +running in the background. + +Note that this is a rather stupid script at the moment because it just looks to +see if any ssh process is running. It should really make sure that our specific +ssh process is running. The problem is that ssh is missing a very useful +feature. It has no way to report the process id of the background daemon that +it creates with the -f command. This would be a really useful script if I could +figure a way around this problem. """ + +import pexpect +import getpass +import time + +# SMTP:25 IMAP4:143 POP3:110 +tunnel_command = 'ssh -C -N -f -L 25:127.0.0.1:25 -L 143:127.0.0.1:143 -L 110:127.0.0.1:110 %(user)@%(host)' +host = raw_input('Hostname: ') +user = raw_input('Username: ') +X = getpass.getpass('Password: ') + +def get_process_info (): + + # This seems to work on both Linux and BSD, but should otherwise be considered highly UNportable. + + ps = pexpect.run ('ps ax -O ppid') + pass +def start_tunnel (): + try: + ssh_tunnel = pexpect.spawn (tunnel_command % globals()) + ssh_tunnel.expect ('password:') + time.sleep (0.1) + ssh_tunnel.sendline (X) + time.sleep (60) # Cygwin is slow to update process status. + ssh_tunnel.expect (pexpect.EOF) + + except Exception, e: + print str(e) + +def main (): + + while True: + ps = pexpect.spawn ('ps') + time.sleep (1) + index = ps.expect (['/usr/bin/ssh', pexpect.EOF, pexpect.TIMEOUT]) + if index == 2: + print 'TIMEOUT in ps command...' + print str(ps) + time.sleep (13) + if index == 1: + print time.asctime(), + print 'restarting tunnel' + start_tunnel () + time.sleep (11) + print 'tunnel OK' + else: + # print 'tunnel OK' + time.sleep (7) + +if __name__ == '__main__': + main () + +# This was for older SSH versions that didn't have -f option +#tunnel_command = 'ssh -C -n -L 25:%(host)s:25 -L 110:%(host)s:110 %(user)s@%(host)s -f nothing.sh' +#nothing_script = """#!/bin/sh +#while true; do sleep 53; done +#""" + diff --git a/src/link/pexpect/examples/sshls.py b/src/link/pexpect/examples/sshls.py new file mode 100755 index 0000000..ef1ab9c --- /dev/null +++ b/src/link/pexpect/examples/sshls.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +"""This runs 'ls -l' on a remote host using SSH. At the prompts enter hostname, +user, and password. + +$Id: sshls.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect +import getpass, os + +def ssh_command (user, host, password, command): + + """This runs a command on the remote host. This could also be done with the +pxssh class, but this demonstrates what that class does at a simpler level. +This returns a pexpect.spawn object. This handles the case when you try to +connect to a new host and ssh asks you if you want to accept the public key +fingerprint and continue connecting. """ + + ssh_newkey = 'Are you sure you want to continue connecting' + child = pexpect.spawn('ssh -l %s %s %s'%(user, host, command)) + i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + return None + if i == 1: # SSH does not have the public key. Just accept it. + child.sendline ('yes') + child.expect ('password: ') + i = child.expect([pexpect.TIMEOUT, 'password: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + return None + child.sendline(password) + return child + +def main (): + + host = raw_input('Hostname: ') + user = raw_input('User: ') + password = getpass.getpass('Password: ') + child = ssh_command (user, host, password, '/bin/ls -l') + child.expect(pexpect.EOF) + print child.before + +if __name__ == '__main__': + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/src/link/pexpect/examples/table_test.html b/src/link/pexpect/examples/table_test.html new file mode 100644 index 0000000..5dba0ec --- /dev/null +++ b/src/link/pexpect/examples/table_test.html @@ -0,0 +1,106 @@ + + + +TEST + + + + + +
+ +
+ + + + + + + + + + + + + +
/home/noah/ 
+ + + + \ No newline at end of file diff --git a/src/link/pexpect/examples/topip.py b/src/link/pexpect/examples/topip.py new file mode 100755 index 0000000..5bd63e2 --- /dev/null +++ b/src/link/pexpect/examples/topip.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +""" This runs netstat on a local or remote server. It calculates some simple +statistical information on the number of external inet connections. It groups +by IP address. This can be used to detect if one IP address is taking up an +excessive number of connections. It can also send an email alert if a given IP +address exceeds a threshold between runs of the script. This script can be used +as a drop-in Munin plugin or it can be used stand-alone from cron. I used this +on a busy web server that would sometimes get hit with denial of service +attacks. This made it easy to see if a script was opening many multiple +connections. A typical browser would open fewer than 10 connections at once. A +script might open over 100 simultaneous connections. + +./topip.py [-s server_hostname] [-u username] [-p password] {-a from_addr,to_addr} {-n N} {-v} {--ipv6} + + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : password to user for login. + -n : print stddev for the the number of the top 'N' ipaddresses. + -v : verbose - print stats and list of top ipaddresses. + -a : send alert if stddev goes over 20. + -l : to log message to /var/log/topip.log + --ipv6 : this parses netstat output that includes ipv6 format. + Note that this actually only works with ipv4 addresses, but for versions of + netstat that print in ipv6 format. + --stdev=N : Where N is an integer. This sets the trigger point for alerts and logs. + Default is to trigger if max value is above 5 standard deviations. + +Example: + + This will print stats for the top IP addresses connected to the given host: + + ./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v + + This will send an alert email if the maxip goes over the stddev trigger value and + the the current top ip is the same as the last top ip (/tmp/topip.last): + + ./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v -a alert@example.com,user@example.com + + This will print the connection stats for the localhost in Munin format: + + ./topip.py + +Noah Spurrier + +$Id: topip.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect, pxssh # See http://pexpect.sourceforge.net/ +import os, sys, time, re, getopt, pickle, getpass, smtplib +import traceback +from pprint import pprint + +TOPIP_LOG_FILE = '/var/log/topip.log' +TOPIP_LAST_RUN_STATS = '/var/run/topip.last' + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def stats(r): + + """This returns a dict of the median, average, standard deviation, min and max of the given sequence. + + >>> from topip import stats + >>> print stats([5,6,8,9]) + {'med': 8, 'max': 9, 'avg': 7.0, 'stddev': 1.5811388300841898, 'min': 5} + >>> print stats([1000,1006,1008,1014]) + {'med': 1008, 'max': 1014, 'avg': 1007.0, 'stddev': 5.0, 'min': 1000} + >>> print stats([1,3,4,5,18,16,4,3,3,5,13]) + {'med': 4, 'max': 18, 'avg': 6.8181818181818183, 'stddev': 5.6216817577237475, 'min': 1} + >>> print stats([1,3,4,5,18,16,4,3,3,5,13,14,5,6,7,8,7,6,6,7,5,6,4,14,7]) + {'med': 6, 'max': 18, 'avg': 7.0800000000000001, 'stddev': 4.3259218670706474, 'min': 1} + """ + + total = sum(r) + avg = float(total)/float(len(r)) + sdsq = sum([(i-avg)**2 for i in r]) + s = list(r) + s.sort() + return dict(zip(['med', 'avg', 'stddev', 'min', 'max'] , (s[len(s)//2], avg, (sdsq/len(r))**.5, min(r), max(r)))) + +def send_alert (message, subject, addr_from, addr_to, smtp_server='localhost'): + + """This sends an email alert. + """ + + message = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (addr_from, addr_to, subject) + message + server = smtplib.SMTP(smtp_server) + server.sendmail(addr_from, addr_to, message) + server.quit() + +def main(): + + ###################################################################### + ## Parse the options, arguments, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?valqs:u:p:n:', ['help','h','?','ipv6','stddev=']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + + munin_flag = False + if len(args) > 0: + if args[0] == 'config': + print 'graph_title Netstat Connections per IP' + print 'graph_vlabel Socket connections per IP' + print 'connections_max.label max' + print 'connections_max.info Maximum number of connections per IP' + print 'connections_avg.label avg' + print 'connections_avg.info Average number of connections per IP' + print 'connections_stddev.label stddev' + print 'connections_stddev.info Standard deviation' + return 0 + elif args[0] != '': + print args, len(args) + return 0 + exit_with_usage() + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print 'Help:' + exit_with_usage() + if '-s' in options: + hostname = options['-s'] + else: + # if host was not specified then assume localhost munin plugin. + munin_flag = True + hostname = 'localhost' + # If localhost then don't ask for username/password. + if hostname != 'localhost' and hostname != '127.0.0.1': + if '-u' in options: + username = options['-u'] + else: + username = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + else: + use_localhost = True + + if '-l' in options: + log_flag = True + else: + log_flag = False + if '-n' in options: + average_n = int(options['-n']) + else: + average_n = None + if '-v' in options: + verbose = True + else: + verbose = False + if '-a' in options: + alert_flag = True + (alert_addr_from, alert_addr_to) = tuple(options['-a'].split(',')) + else: + alert_flag = False + if '--ipv6' in options: + ipv6_flag = True + else: + ipv6_flag = False + if '--stddev' in options: + stddev_trigger = float(options['--stddev']) + else: + stddev_trigger = 5 + + if ipv6_flag: + netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+::ffff:(\S+):(\S+)\s+.*?\r' + else: + netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(?:::ffff:)*(\S+):(\S+)\s+.*?\r' + #netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+):(\S+)\s+.*?\r' + + # run netstat (either locally or via SSH). + if use_localhost: + p = pexpect.spawn('netstat -n -t') + PROMPT = pexpect.TIMEOUT + else: + p = pxssh.pxssh() + p.login(hostname, username, password) + p.sendline('netstat -n -t') + PROMPT = p.PROMPT + + # loop through each matching netstat_pattern and put the ip address in the list. + ip_list = {} + try: + while 1: + i = p.expect([PROMPT, netstat_pattern]) + if i == 0: + break + k = p.match.groups()[4] + if k in ip_list: + ip_list[k] = ip_list[k] + 1 + else: + ip_list[k] = 1 + except: + pass + + # remove a few common, uninteresting addresses from the dictionary. + ip_list = dict([ (key,value) for key,value in ip_list.items() if '192.168.' not in key]) + ip_list = dict([ (key,value) for key,value in ip_list.items() if '127.0.0.1' not in key]) + + # sort dict by value (count) + #ip_list = sorted(ip_list.iteritems(),lambda x,y:cmp(x[1], y[1]),reverse=True) + ip_list = ip_list.items() + if len(ip_list) < 1: + if verbose: print 'Warning: no networks connections worth looking at.' + return 0 + ip_list.sort(lambda x,y:cmp(y[1],x[1])) + + # generate some stats for the ip addresses found. + if average_n <= 1: + average_n = None + s = stats(zip(*ip_list[0:average_n])[1]) # The * unary operator treats the list elements as arguments + s['maxip'] = ip_list[0] + + # print munin-style or verbose results for the stats. + if munin_flag: + print 'connections_max.value', s['max'] + print 'connections_avg.value', s['avg'] + print 'connections_stddev.value', s['stddev'] + return 0 + if verbose: + pprint (s) + print + pprint (ip_list[0:average_n]) + + # load the stats from the last run. + try: + last_stats = pickle.load(file(TOPIP_LAST_RUN_STATS)) + except: + last_stats = {'maxip':None} + + if s['maxip'][1] > (s['stddev'] * stddev_trigger) and s['maxip']==last_stats['maxip']: + if verbose: print 'The maxip has been above trigger for two consecutive samples.' + if alert_flag: + if verbose: print 'SENDING ALERT EMAIL' + send_alert(str(s), 'ALERT on %s' % hostname, alert_addr_from, alert_addr_to) + if log_flag: + if verbose: print 'LOGGING THIS EVENT' + fout = file(TOPIP_LOG_FILE,'a') + #dts = time.strftime('%Y:%m:%d:%H:%M:%S', time.localtime()) + dts = time.asctime() + fout.write ('%s - %d connections from %s\n' % (dts,s['maxip'][1],str(s['maxip'][0]))) + fout.close() + + # save state to TOPIP_LAST_RUN_STATS + try: + pickle.dump(s, file(TOPIP_LAST_RUN_STATS,'w')) + os.chmod (TOPIP_LAST_RUN_STATS, 0664) + except: + pass + # p.logout() + +if __name__ == '__main__': + try: + main() + sys.exit(0) + except SystemExit, e: + raise e + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/src/link/pexpect/examples/uptime.py b/src/link/pexpect/examples/uptime.py new file mode 100755 index 0000000..f5018df --- /dev/null +++ b/src/link/pexpect/examples/uptime.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +"""This displays uptime information using uptime. This is redundant, +but it demonstrates expecting for a regular expression that uses subgroups. + +$Id: uptime.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect +import re + +# There are many different styles of uptime results. I try to parse them all. Yeee! +# Examples from different machines: +# [x86] Linux 2.4 (Redhat 7.3) +# 2:06pm up 63 days, 18 min, 3 users, load average: 0.32, 0.08, 0.02 +# [x86] Linux 2.4.18-14 (Redhat 8.0) +# 3:07pm up 29 min, 1 user, load average: 2.44, 2.51, 1.57 +# [PPC - G4] MacOS X 10.1 SERVER Edition +# 2:11PM up 3 days, 13:50, 3 users, load averages: 0.01, 0.00, 0.00 +# [powerpc] Darwin v1-58.corefa.com 8.2.0 Darwin Kernel Version 8.2.0 +# 10:35 up 18:06, 4 users, load averages: 0.52 0.47 0.36 +# [Sparc - R220] Sun Solaris (8) +# 2:13pm up 22 min(s), 1 user, load average: 0.02, 0.01, 0.01 +# [x86] Linux 2.4.18-14 (Redhat 8) +# 11:36pm up 4 days, 17:58, 1 user, load average: 0.03, 0.01, 0.00 +# AIX jwdir 2 5 0001DBFA4C00 +# 09:43AM up 23:27, 1 user, load average: 0.49, 0.32, 0.23 +# OpenBSD box3 2.9 GENERIC#653 i386 +# 6:08PM up 4 days, 22:26, 1 user, load averages: 0.13, 0.09, 0.08 + +# This parses uptime output into the major groups using regex group matching. +p = pexpect.spawn ('uptime') +p.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])') +duration, users, av1, av5, av15 = p.match.groups() + +# The duration is a little harder to parse because of all the different +# styles of uptime. I'm sure there is a way to do this all at once with +# one single regex, but I bet it would be hard to read and maintain. +# If anyone wants to send me a version using a single regex I'd be happy to see it. +days = '0' +hours = '0' +mins = '0' +if 'day' in duration: + p.match = re.search('([0-9]+)\s+day',duration) + days = str(int(p.match.group(1))) +if ':' in duration: + p.match = re.search('([0-9]+):([0-9]+)',duration) + hours = str(int(p.match.group(1))) + mins = str(int(p.match.group(2))) +if 'min' in duration: + p.match = re.search('([0-9]+)\s+min',duration) + mins = str(int(p.match.group(1))) + +# Print the parsed fields in CSV format. +print 'days, hours, minutes, users, cpu avg 1 min, cpu avg 5 min, cpu avg 15 min' +print '%s, %s, %s, %s, %s, %s, %s' % (days, hours, mins, users, av1, av5, av15) + diff --git a/src/link/pexpect/fdpexpect.py b/src/link/pexpect/fdpexpect.py new file mode 100644 index 0000000..0ece98e --- /dev/null +++ b/src/link/pexpect/fdpexpect.py @@ -0,0 +1,82 @@ +"""This is like pexpect, but will work on any file descriptor that you pass it. +So you are reponsible for opening and close the file descriptor. + +$Id: fdpexpect.py 505 2007-12-26 21:33:50Z noah $ +""" + +from pexpect import * +import os + +__all__ = ['fdspawn'] + +class fdspawn (spawn): + + """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=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None): + + """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(). """ + + ### TODO: Add better handling of trying to use fdspawn in place of spawn + ### TODO: (overload to allow fdspawn to also handle commands as spawn does. + + 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 + spawn.__init__(self, None, args, timeout, maxread, searchwindowsize, logfile) + self.child_fd = fd + self.own_fd = False + self.closed = False + self.name = '' % fd + + def __del__ (self): + + return + + def close (self): + + if self.child_fd == -1: + return + if self.own_fd: + self.close (self) + else: + 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 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): + + raise ExceptionPexpect ('This method is not valid for file descriptors.') + + def kill (self, sig): + + return + diff --git a/src/link/pexpect/pexpect.py b/src/link/pexpect/pexpect.py new file mode 100644 index 0000000..67c6389 --- /dev/null +++ b/src/link/pexpect/pexpect.py @@ -0,0 +1,1845 @@ +"""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 Pexpect -- the function, run() and the class, +spawn. You can call the run() function to execute a command and return the +output. This is a handy replacement for os.system(). + +For example:: + + pexpect.run('ls -la') + +The more powerful interface is the spawn class. You can use this to spawn an +external child command and then interact with the child by sending lines and +expecting responses. + +For example:: + + child = pexpect.spawn('scp foo myname@host.example.com:.') + child.expect ('Password:') + child.sendline (mypassword) + +This works even for commands that ask for passwords or other input outside of +the normal stdio streams. + +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, +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 (Let me know if I forgot anyone.) + +Free, open source, and all that good stuff. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Pexpect Copyright (c) 2008 Noah Spurrier +http://pexpect.sourceforge.net/ + +$Id: pexpect.py 507 2007-12-27 02:40:52Z noah $ +""" + +try: + import os, sys, time + import select + import string + import re + import struct + import resource + import types + import pty + import tty + import termios + import fcntl + import errno + import traceback + import signal +except ImportError, e: + raise ImportError (str(e) + """ + +A critical module was not found. Probably this operating system does not +support it. Pexpect is intended for UNIX-like operating systems.""") + +__version__ = '2.3' +__revision__ = '$Revision: 399 $' +__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'which', + 'split_command_line', '__version__', '__revision__'] + +# Exception classes used by this module. +class ExceptionPexpect(Exception): + + """Base class for all exceptions raised by this module. + """ + + def __init__(self, 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 = filter(self.__filter_not_pexpect, tblist) + tblist = [item for item in tblist if self.__filter_not_pexpect(item)] + tblist = traceback.format_list(tblist) + return ''.join(tblist) + + def __filter_not_pexpect(self, trace_list_item): + + """This returns True if list item 0 the string 'pexpect.py' in it. """ + + if trace_list_item[0].find('pexpect.py') == -1: + return True + else: + return False + +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. """ + +##class TIMEOUT_PATTERN(TIMEOUT): +## """Raised when the pattern match time exceeds the timeout. +## This is different than a read TIMEOUT because the child process may +## give output, thus never give a TIMEOUT, but the output +## may never match a pattern. +## """ +##class MAXBUFFER(ExceptionPexpect): +## """Raised when a scan buffer fills before matching an expected pattern.""" + +def run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None): + + """ + 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 pseudo ttys. 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 myname@host.example.com:.') + child.expect ('(?i)password') + child.sendline (mypassword) + + The previous code can be replace with the following:: + + from pexpect import * + run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword}) + + Examples + ======== + + Start the apache daemon on the local machine:: + + from pexpect import * + run ("/usr/local/apache/bin/apachectl start") + + Check in a file using SVN:: + + from pexpect import * + run ("svn ci -m 'automatic commit' my_file.py") + + Run a command and capture exit status:: + + from pexpect import * + (command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1) + + Tricky Examples + =============== + + The following will run SSH and execute 'ls -l' on the remote machine. The + password 'secret' will be sent if the '(?i)password' pattern is ever seen:: + + run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'}) + + This will start mencoder to rip a video from DVD. This will also display + progress ticks every 5 seconds as it runs. For example:: + + from pexpect import * + def print_ticks(d): + print d['event_count'], + run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks}, timeout=5) + + The 'events' argument should be a dictionary of patterns and responses. + Whenever one of the patterns is seen in the command out run() will send the + associated response string. Note that you should put newlines in your + string if Enter is necessary. The responses may also contain callback + functions. Any callback is function that takes a dictionary 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. """ + + if timeout == -1: + child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env) + else: + child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, cwd=cwd, env=env) + if events is not None: + patterns = events.keys() + responses = events.values() + else: + patterns=None # We assume that EOF or TIMEOUT will save us. + responses=None + child_result_list = [] + event_count = 0 + while 1: + try: + index = child.expect (patterns) + if type(child.after) in types.StringTypes: + child_result_list.append(child.before + child.after) + else: # child.after may have been a TIMEOUT or EOF, so don't cat those. + child_result_list.append(child.before) + if type(responses[index]) in types.StringTypes: + child.send(responses[index]) + elif type(responses[index]) is types.FunctionType: + callback_result = responses[index](locals()) + sys.stdout.flush() + if type(callback_result) in types.StringTypes: + child.send(callback_result) + elif callback_result: + break + else: + raise TypeError ('The callback must be a string or function type.') + event_count = event_count + 1 + except TIMEOUT, e: + child_result_list.append(child.before) + break + except EOF, e: + child_result_list.append(child.before) + break + child_result = ''.join(child_result_list) + if withexitstatus: + child.close() + return (child_result, child.exitstatus) + else: + return child_result + +class spawn (object): + + """This is the main class interface for Pexpect. Use this class to start + and control child applications. """ + + def __init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): + + """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 user@example.com') + 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', ['user@example.com']) + 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 > log_list.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 > log_list.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. + + The searchwindowsize attribute sets the how far back in the incomming + seach buffer Pexpect will search for pattern matches. Every time + Pexpect reads some data from the child it will append the data to the + incomming buffer. The default is to search from the beginning of the + imcomming buffer each time new data is read from the child. But this is + very inefficient if you are running a command that generates a large + amount of data where you want to match The searchwindowsize does not + effect the size of the incomming data buffer. You will still have + access to the full buffer after expect() returns. + + 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 = file('mylog.txt','w') + child.logfile = fout + + Example log to stdout:: + + child = pexpect.spawn('some_command') + 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 + + To separately log output sent to the child use logfile_send:: + + self.logfile_send = fout + + 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 0 to return to the old behavior. Most Linux machines + don't like this to be below 0.03. I don't know why. + + 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. + 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. """ + + self.STDIN_FILENO = pty.STDIN_FILENO + self.STDOUT_FILENO = pty.STDOUT_FILENO + self.STDERR_FILENO = pty.STDERR_FILENO + 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 + self.status = None # status returned by os.waitpid + self.flag_eof = False + self.pid = None + self.child_fd = -1 # initially closed + self.timeout = timeout + self.delimiter = EOF + self.logfile = logfile + self.logfile_read = None # input from child (read_nonblocking) + self.logfile_send = None # output to send (send, sendline) + self.maxread = maxread # max bytes to read at one time into buffer + self.buffer = '' # This is the read buffer. See maxread. + self.searchwindowsize = searchwindowsize # Anything before searchwindowsize point is preserved, but not searched. + # Most Linux machines don't like delaybeforesend to be below 0.03 (30 ms). + self.delaybeforesend = 0.05 # Sets sleep time used just before sending data to child. Time in seconds. + self.delayafterclose = 0.1 # Sets delay in close() method to allow kernel time to update process status. Time in seconds. + self.delayafterterminate = 0.1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds. + self.softspace = False # File-like object. + self.name = '<' + repr(self) + '>' # File-like object. + self.encoding = None # File-like object. + self.closed = True # File-like object. + self.cwd = cwd + self.env = env + self.__irix_hack = (sys.platform.lower().find('irix')>=0) # This flags if we are running on irix + # Solaris uses internal __fork_pty(). All others use pty.fork(). + if (sys.platform.lower().find('solaris')>=0) or (sys.platform.lower().find('sunos5')>=0): + self.use_native_pty_fork = False + else: + self.use_native_pty_fork = True + + + # allow dummy instances for subclasses that may not use command or args. + if command is None: + self.command = None + self.args = None + self.name = '' + else: + self._spawn (command, args) + + def __del__(self): + + """This makes sure that no system resources are left open. Python only + garbage collects Python objects. OS file descriptors are not Python + objects, so they must be handled explicitly. If the child file + descriptor was opened outside of this class (passed to the constructor) + then this does not close it. """ + + if not self.closed: + # It is possible for __del__ methods to execute during the + # teardown of the Python VM itself. Thus self.close() may + # trigger an exception because os.close may be None. + # -- Fernando Perez + try: + self.close() + except AttributeError: + pass + + def __str__(self): + + """This returns a human-readable string that represents the state of + the object. """ + + s = [] + s.append(repr(self)) + s.append('version: ' + __version__ + ' (' + __revision__ + ')') + s.append('command: ' + str(self.command)) + s.append('args: ' + str(self.args)) + s.append('searcher: ' + str(self.searcher)) + s.append('buffer (last 100 chars): ' + str(self.buffer)[-100:]) + s.append('before (last 100 chars): ' + str(self.before)[-100:]) + s.append('after: ' + str(self.after)) + s.append('match: ' + str(self.match)) + s.append('match_index: ' + str(self.match_index)) + s.append('exitstatus: ' + str(self.exitstatus)) + 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=[]): + + """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 haved 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 type(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 type (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: + self.args = args[:] # work with a copy + self.args.insert (0, command) + self.command = command + + command_with_path = which(self.command) + 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 should be None.' + assert self.command is not None, 'The command member should not be None.' + + if self.use_native_pty_fork: + try: + self.pid, self.child_fd = pty.fork() + except OSError, e: + raise ExceptionPexpect('Error! pty.fork() failed: ' + str(e)) + else: # Use internal __fork_pty + self.pid, self.child_fd = self.__fork_pty() + + if self.pid == 0: # Child + try: + self.child_fd = sys.stdout.fileno() # used by setwinsize() + self.setwinsize(24, 80) + except: + # Some platforms do not like setwinsize (Cygwin). + # This will cause problem when running applications that + # are very picky about window size. + # This is a serious limitation, but not a show stopper. + pass + # Do not allow child to inherit open file descriptors from parent. + max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] + for i in range (3, max_fd): + try: + os.close (i) + except OSError: + pass + + # I don't know why this works, but ignoring SIGHUP fixes a + # problem when trying to start a Java daemon with sudo + # (specifically, Tomcat). + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + if self.cwd is not None: + os.chdir(self.cwd) + if self.env is None: + os.execv(self.command, self.args) + else: + os.execvpe(self.command, self.args, self.env) + + # Parent + self.terminated = False + self.closed = False + + def __fork_pty(self): + + """This implements a substitute for the forkpty system call. This + should be more portable than the pty.fork() function. Specifically, + this should work on Solaris. + + Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to + resolve the issue with Python's pty.fork() not supporting Solaris, + particularly ssh. Based on patch to posixmodule.c authored by Noah + Spurrier:: + + http://mail.python.org/pipermail/python-dev/2003-May/035281.html + + """ + + parent_fd, child_fd = os.openpty() + if parent_fd < 0 or child_fd < 0: + raise ExceptionPexpect, "Error! Could not open pty with os.openpty()." + + pid = os.fork() + if pid < 0: + raise ExceptionPexpect, "Error! Failed os.fork()." + elif pid == 0: + # Child. + os.close(parent_fd) + self.__pty_make_controlling_tty(child_fd) + + os.dup2(child_fd, 0) + os.dup2(child_fd, 1) + os.dup2(child_fd, 2) + + if child_fd > 2: + os.close(child_fd) + else: + # Parent. + os.close(child_fd) + + return pid, parent_fd + + def __pty_make_controlling_tty(self, tty_fd): + + """This makes the pseudo-terminal the controlling tty. This should be + more portable than the pty.fork() function. Specifically, this should + work on Solaris. """ + + child_name = os.ttyname(tty_fd) + + # Disconnect from controlling tty if still connected. + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); + if fd >= 0: + os.close(fd) + + os.setsid() + + # Verify we are disconnected from controlling tty + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); + if fd >= 0: + os.close(fd) + raise ExceptionPexpect, "Error! We are not disconnected from a controlling tty." + except: + # Good! We are disconnected from a controlling tty. + pass + + # Verify we can open child pty. + fd = os.open(child_name, os.O_RDWR); + if fd < 0: + raise ExceptionPexpect, "Error! Could not open child pty, " + child_name + else: + os.close(fd) + + # Verify we now have a controlling tty. + fd = os.open("/dev/tty", os.O_WRONLY) + if fd < 0: + raise ExceptionPexpect, "Error! Could not open controlling tty, /dev/tty" + else: + os.close(fd) + + def fileno (self): # File-like object. + + """This returns the file descriptor of the pty for the child. + """ + + return self.child_fd + + def close (self, force=True): # File-like object. + + """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). """ + + if not self.closed: + self.flush() + os.close (self.child_fd) + time.sleep(self.delayafterclose) # Give kernel time to update process status. + if self.isalive(): + if not self.terminate(force): + raise ExceptionPexpect ('close() could not terminate the child using terminate()') + self.child_fd = -1 + self.closed = True + #self.pid = None + + def flush (self): # File-like object. + + """This does nothing. It is here to support the interface for a + File-like object. """ + + pass + + def isatty (self): # File-like object. + + """This returns True if the file descriptor is open and connected to a + tty(-like) device, else False. """ + + 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 user@example.com') + p.waitnoecho() + p.sendline(mypassword) + + If timeout is None then this method to block forever 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(). """ + + attr = termios.tcgetattr(self.child_fd) + if attr[3] & termios.ECHO: + return True + return False + + 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') + p.sendline ('1234') # We will see this twice (once from tty echo and again from cat). + p.expect (['1234']) + p.expect (['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 (['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') # We will see this twice (once from tty echo and again from cat). + 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']) + """ + + self.child_fd + attr = termios.tcgetattr(self.child_fd) + if state: + attr[3] = attr[3] | termios.ECHO + else: + attr[3] = attr[3] & ~termios.ECHO + # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent + # and blocked on some platforms. TCSADRAIN is probably ideal if it worked. + termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) + + 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 log file was set using + setlog() then all data will also be written to the log file. + + 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 was 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 effected 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. + + This is a wrapper around os.read(). It uses select.select() to + implement the timeout. """ + + if self.closed: + raise ValueError ('I/O operation on closed file in read_nonblocking().') + + if timeout == -1: + timeout = self.timeout + + # Note that some systems such as Solaris do not give an EOF when + # the child dies. In fact, you can still try to read + # from the child_fd -- it will block forever or until TIMEOUT. + # For this case, I test isalive() before doing any reading. + # If isalive() is false, then I pretend that this is the same as EOF. + if not self.isalive(): + r,w,e = self.__select([self.child_fd], [], [], 0) # timeout of 0 means "poll" + if not r: + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Braindead platform.') + elif self.__irix_hack: + # This is a hack for Irix. It seems that Irix requires a long delay before checking isalive. + # This adds a 2 second delay, but only when the child is terminated. + r, w, e = self.__select([self.child_fd], [], [], 2) + if not r and not self.isalive(): + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Pokey platform.') + + r,w,e = self.__select([self.child_fd], [], [], timeout) + + if not r: + if not self.isalive(): + # Some platforms, such as Irix, will claim that their processes are alive; + # then timeout on the select; and then finally admit that they are not alive. + self.flag_eof = True + raise EOF ('End of File (EOF) in read_nonblocking(). Very pokey platform.') + else: + raise TIMEOUT ('Timeout exceeded in read_nonblocking().') + + if self.child_fd in r: + try: + s = os.read(self.child_fd, size) + except OSError, e: # Linux does this + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Exception style platform.') + if s == '': # BSD style + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Empty string style platform.') + + if self.logfile is not None: + self.logfile.write (s) + self.logfile.flush() + if self.logfile_read is not None: + self.logfile_read.write (s) + self.logfile_read.flush() + + return s + + raise ExceptionPexpect ('Reached an unexpected state in read_nonblocking().') + + def read (self, size = -1): # File-like object. + + """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 '' + if size < 0: + self.expect (self.delimiter) # delimiter default is EOF + 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 consistant 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('.{%d}' % size, re.DOTALL) + index = self.expect ([cre, self.delimiter]) # delimiter default is EOF + if index == 0: + return self.after ### self.before should be ''. Should I assert this? + return self.before + + def readline (self, size = -1): # File-like object. + + """This reads and returns one entire line. A trailing newline is kept + in the string, but may be absent when a file ends with an incomplete + line. Note: This readline() looks for a \\r\\n pair even on UNIX + because this is what the pseudo tty device returns. So contrary to what + you may expect you will receive the newline as \\r\\n. An empty string + is returned when EOF is hit immediately. Currently, the size argument is + mostly ignored, so this behavior is not standard for a file-like + object. If size is 0 then an empty string is returned. """ + + if size == 0: + return '' + index = self.expect (['\r\n', self.delimiter]) # delimiter default is EOF + if index == 0: + return self.before + '\r\n' + else: + return self.before + + def __iter__ (self): # File-like object. + + """This is to support iterators over a file-like object. + """ + + return self + + def next (self): # File-like object. + + """This is to support iterators over a file-like object. + """ + + result = self.readline() + if result == "": + raise StopIteration + return result + + def readlines (self, sizehint = -1): # File-like object. + + """This reads until EOF using readline() and returns a list containing + the lines thus read. The optional "sizehint" argument is ignored. """ + + lines = [] + while True: + line = self.readline() + if not line: + break + lines.append(line) + return lines + + def write(self, s): # File-like object. + + """This is similar to send() except that there is no return value. + """ + + self.send (s) + + def writelines (self, sequence): # File-like object. + + """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): + + """This sends a string to the child process. This returns the number of + bytes written. If a log file was set then the data is also written to + the log. """ + + time.sleep(self.delaybeforesend) + if self.logfile is not None: + self.logfile.write (s) + self.logfile.flush() + if self.logfile_send is not None: + self.logfile_send.write (s) + self.logfile_send.flush() + c = os.write(self.child_fd, s) + return c + + def sendline(self, s=''): + + """This is like send(), but it adds a line feed (os.linesep). This + returns the number of bytes written. """ + + n = self.send(s) + n = n + self.send (os.linesep) + return n + + def sendcontrol(self, char): + + """This sends a control character to the child such as Ctrl-C or + Ctrl-D. For example, to send a Ctrl-G (ASCII 7):: + + child.sendcontrol('g') + + See also, sendintr() and sendeof(). + """ + + char = char.lower() + a = ord(char) + if a>=97 and a<=122: + a = a - ord('a') + 1 + return self.send (chr(a)) + d = {'@':0, '`':0, + '[':27, '{':27, + '\\':28, '|':28, + ']':29, '}': 29, + '^':30, '~':30, + '_':31, + '?':127} + if char not in d: + return 0 + return self.send (chr(d[char])) + + 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. """ + + ### Hmmm... how do I send an EOF? + ###C if ((m = write(pty, *buf, p - *buf)) < 0) + ###C return (errno == EWOULDBLOCK) ? n : -1; + #fd = sys.stdin.fileno() + #old = termios.tcgetattr(fd) # remember current state + #attr = termios.tcgetattr(fd) + #attr[3] = attr[3] | termios.ICANON # ICANON must be set to recognize EOF + #try: # use try/finally to ensure state gets restored + # termios.tcsetattr(fd, termios.TCSADRAIN, attr) + # if hasattr(termios, 'CEOF'): + # os.write (self.child_fd, '%c' % termios.CEOF) + # else: + # # Silly platform does not define CEOF so assume CTRL-D + # os.write (self.child_fd, '%c' % 4) + #finally: # restore state + # termios.tcsetattr(fd, termios.TCSADRAIN, old) + if hasattr(termios, 'VEOF'): + char = termios.tcgetattr(self.child_fd)[6][termios.VEOF] + else: + # platform does not define VEOF so assume CTRL-D + char = chr(4) + self.send(char) + + def sendintr(self): + + """This sends a SIGINT to the child. It does not require + the SIGINT to be the first character on a line. """ + + if hasattr(termios, 'VINTR'): + char = termios.tcgetattr(self.child_fd)[6][termios.VINTR] + else: + # platform does not define VINTR so assume CTRL-C + char = chr(3) + self.send (char) + + 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, e: + # 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, technically, the child + is still alive until its output is read. """ + + if self.isalive(): + pid, status = os.waitpid(self.pid, 0) + else: + raise ExceptionPexpect ('Cannot wait for dead child process.') + self.exitstatus = os.WEXITSTATUS(status) + if os.WIFEXITED (status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED (status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED (status): + raise ExceptionPexpect ('Wait was called for a child process that is stopped. This is not supported. Is some other process attempting job control with our child pid?') + return self.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. """ + + if self.terminated: + return False + + if self.flag_eof: + # This is for Linux, which requires the blocking form of waitpid to get + # status of a defunct process. This is super-lame. The flag_eof would have + # been set in read_nonblocking(), so this should be safe. + waitpid_options = 0 + else: + waitpid_options = os.WNOHANG + + try: + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError, e: # No child processes + if e[0] == errno.ECHILD: + raise ExceptionPexpect ('isalive() encountered condition where "terminated" is 0, but there was no child process. Did someone else call waitpid() on our process?') + else: + raise e + + # I have to do this twice for Solaris. I can't even believe that I figured this out... + # If waitpid() returns 0 it means that no child process wishes to + # report, and the value of status is undefined. + if pid == 0: + try: + pid, status = os.waitpid(self.pid, waitpid_options) ### os.WNOHANG) # Solaris! + except OSError, e: # This should never happen... + if e[0] == errno.ECHILD: + raise ExceptionPexpect ('isalive() encountered condition that should never happen. There was no child process. Did someone else call waitpid() on our process?') + else: + raise e + + # If pid is still 0 after two calls to waitpid() then + # the process really is alive. This seems to work on all platforms, except + # for Irix which seems to require a blocking call on waitpid or select, so I let read_nonblocking + # take care of this situation (unfortunately, this requires waiting through the timeout). + if pid == 0: + return True + + if pid == 0: + return True + + if os.WIFEXITED (status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED (status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED (status): + raise ExceptionPexpect ('isalive() encountered condition where child process is stopped. This is not supported. Is some other process attempting job control with our child pid?') + return False + + 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 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(clp, timeout) + ... + """ + + if patterns is None: + return [] + if type(patterns) is not types.ListType: + patterns = [patterns] + + compile_flags = re.DOTALL # Allow dot to match \n + if self.ignorecase: + compile_flags = compile_flags | re.IGNORECASE + compiled_pattern_list = [] + for p in patterns: + if type(p) in types.StringTypes: + 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 type(p) is type(re.compile('')): + compiled_pattern_list.append(p) + else: + raise TypeError ('Argument must be one of StringTypes, EOF, TIMEOUT, SRE_Pattern, or a list of those type. %s' % str(type(p))) + + return compiled_pattern_list + + def expect(self, pattern, timeout = -1, searchwindowsize=None): + + """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 returs 1 ('foo') if parts of the final 'bar' arrive late + + After a match is found the instance attributes 'before', 'after' and + 'match' will be set. You can see all the data read before the match in + 'before'. You can see the data that was matched in 'after'. The + re.MatchObject used in the re match will be in 'match'. If an error + occurred then 'before' will be set to all the data read so far and + 'after' and 'match' will be None. + + If timeout is -1 then timeout will be set to the self.timeout value. + + 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(). + """ + + compiled_pattern_list = self.compile_pattern_list(pattern) + return self.expect_list(compiled_pattern_list, timeout, searchwindowsize) + + def expect_list(self, pattern_list, timeout = -1, searchwindowsize = -1): + + """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(). If timeout==-1 then + the self.timeout value is used. If searchwindowsize==-1 then the + self.searchwindowsize value is used. """ + + return self.expect_loop(searcher_re(pattern_list), timeout, searchwindowsize) + + def expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1): + + """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.""" + + if type(pattern_list) in types.StringTypes or pattern_list in (TIMEOUT, EOF): + pattern_list = [pattern_list] + return self.expect_loop(searcher_string(pattern_list), timeout, searchwindowsize) + + 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. """ + + self.searcher = searcher + + if timeout == -1: + timeout = self.timeout + if timeout is not None: + end_time = time.time() + timeout + if searchwindowsize == -1: + searchwindowsize = self.searchwindowsize + + try: + incoming = self.buffer + freshlen = len(incoming) + while True: # Keep reading until exception or return. + index = searcher.search(incoming, freshlen, searchwindowsize) + if index >= 0: + self.buffer = incoming[searcher.end : ] + self.before = incoming[ : searcher.start] + self.after = incoming[searcher.start : searcher.end] + self.match = searcher.match + self.match_index = index + return self.match_index + # No match at this point + if timeout < 0 and timeout is not None: + raise TIMEOUT ('Timeout exceeded in expect_any().') + # Still have time left, so read more data + c = self.read_nonblocking (self.maxread, timeout) + freshlen = len(c) + time.sleep (0.0001) + incoming = incoming + c + if timeout is not None: + timeout = end_time - time.time() + except EOF, e: + self.buffer = '' + self.before = incoming + self.after = EOF + index = searcher.eof_index + if index >= 0: + self.match = EOF + self.match_index = index + return self.match_index + else: + self.match = None + self.match_index = None + raise EOF (str(e) + '\n' + str(self)) + except TIMEOUT, e: + self.buffer = incoming + self.before = incoming + self.after = TIMEOUT + index = searcher.timeout_index + if index >= 0: + self.match = TIMEOUT + self.match_index = index + return self.match_index + else: + self.match = None + self.match_index = None + raise TIMEOUT (str(e) + '\n' + str(self)) + except: + self.before = incoming + self.after = None + self.match = None + self.match_index = None + raise + + def getwinsize(self): + + """This returns the terminal window size of the child tty. The return + value is a tuple of (rows, cols). """ + + TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912L) + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(self.fileno(), TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] + + def setwinsize(self, r, c): + + """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. """ + + # Check for buggy platforms. Some Python versions on some platforms + # (notably OSF1 Alpha and RedHat 7.1) truncate the value for + # termios.TIOCSWINSZ. It is not clear why this happens. + # These platforms don't seem to handle the signed int very well; + # yet other platforms like OpenBSD have a large negative value for + # TIOCSWINSZ and they don't have a truncate problem. + # Newer versions of Linux have totally different values for TIOCSWINSZ. + # Note that this fix is a hack. + TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) + if TIOCSWINSZ == 2148037735L: # L is not required in Python >= 2.2. + TIOCSWINSZ = -2146929561 # Same bits, but with sign. + # Note, assume ws_xpixel and ws_ypixel are zero. + s = struct.pack('HHHH', r, c, 0, 0) + fcntl.ioctl(self.fileno(), TIOCSWINSZ, s) + + 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 stop. The default for + escape_character is ^]. This should not be confused with ASCII 27 -- + the ESC character. ASCII 29 was chosen for historical merit because + this is the character used by 'telnet' as the escape character. The + escape_character will not be sent to the child process. + + You may pass in optional input and output filter functions. These + functions should take a string and return a string. 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)) + global p + p.setwinsize(a[0],a[1]) + p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough. + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + p.interact() + """ + + # Flush the buffer. + self.stdout.write (self.buffer) + self.stdout.flush() + self.buffer = '' + mode = tty.tcgetattr(self.STDIN_FILENO) + tty.setraw(self.STDIN_FILENO) + 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 != '' 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(): + r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) + if self.child_fd in r: + data = self.__interact_read(self.child_fd) + if output_filter: data = output_filter(data) + if self.logfile is not None: + self.logfile.write (data) + self.logfile.flush() + 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 = data.rfind(escape_character) + if i != -1: + data = data[:i] + self.__interact_writen(self.child_fd, data) + break + self.__interact_writen(self.child_fd, data) + + def __select (self, iwtd, owtd, ewtd, timeout=None): + + """This is a wrapper around select.select() that ignores signals. If + select.select raises a select.error exception and errno is an EINTR + error then it is ignored. Mainly this is used to ignore sigwinch + (terminal resize). """ + + # if select() is interrupted by a signal (errno==EINTR) then + # we loop back and enter the select() again. + if timeout is not None: + end_time = time.time() + timeout + while True: + try: + return select.select (iwtd, owtd, ewtd, timeout) + except select.error, e: + if e[0] == errno.EINTR: + # if we loop back we have to subtract the amount of time we already waited. + if timeout is not None: + timeout = end_time - time.time() + if timeout < 0: + return ([],[],[]) + else: # something else caused the select.error, so this really is an exception + raise + +############################################################################## +# The following methods are no longer supported or allowed. + + def setmaxread (self, maxread): + + """This method is no longer supported or allowed. I don't like getters + and setters without a good reason. """ + + raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the maxread member variable.') + + def setlog (self, fileobject): + + """This method is no longer supported or allowed. + """ + + raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the logfile member variable.') + +############################################################################## +# End of spawn class +############################################################################## + +class searcher_string (object): + + """This is a plain string search helper for the spawn.expect_any() method. + + 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 = [] + for n, s in zip(range(len(strings)), strings): + if s is EOF: + self.eof_index = n + continue + if s is TIMEOUT: + self.timeout_index = n + continue + self._strings.append((n, s)) + + def __str__(self): + + """This returns a human-readable string that represents the state of + the object.""" + + ss = [ (ns[0],' %d: "%s"' % 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 = zip(*ss)[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): + + """This searches 'buffer' for the first occurence 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. """ + + absurd_match = len(buffer) + first_match = absurd_match + + # '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 n < first_match: + first_match = n + best_index, best_match = index, s + if first_match == absurd_match: + 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. + + 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 succesful 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 = [] + for n, s in zip(range(len(patterns)), 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,str(s.pattern))) for n,s in self._searches] + 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 = zip(*ss)[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): + + """This searches 'buffer' for the first occurence 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.""" + + absurd_match = len(buffer) + first_match = absurd_match + # '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 n < first_match: + first_match = n + the_match = match + best_index = index + if first_match == absurd_match: + return -1 + self.start = first_match + self.match = the_match + self.end = self.match.end() + return best_index + +def which (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 already contains a path. + if os.path.dirname(filename) != '': + if os.access (filename, os.X_OK): + return filename + + if not os.environ.has_key('PATH') or os.environ['PATH'] == '': + p = os.defpath + else: + p = os.environ['PATH'] + + # Oddly enough this was the one line that made Pexpect + # incompatible with Python 1.5.2. + #pathlist = p.split (os.pathsep) + pathlist = string.split (p, os.pathsep) + + for path in pathlist: + f = os.path.join(path, filename) + if os.access(f, os.X_OK): + return f + 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 + state_whitespace = 4 # The state of consuming whitespace between commands. + 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: + None # Do nothing. + 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 + +# vi:ts=4:sw=4:expandtab:ft=python: diff --git a/src/link/pexpect/pxssh.py b/src/link/pexpect/pxssh.py new file mode 100644 index 0000000..d3f46ab --- /dev/null +++ b/src/link/pexpect/pxssh.py @@ -0,0 +1,307 @@ +"""This class extends pexpect.spawn to specialize setting up SSH connections. +This adds methods for login, logout, and expecting the shell prompt. + +$Id: pxssh.py 487 2007-08-29 22:33:29Z noah $ +""" + +from pexpect import * +import pexpect +import time + +__all__ = ['ExceptionPxssh', 'pxssh'] + +# Exception classes used by this module. +class ExceptionPxssh(ExceptionPexpect): + """Raised for pxssh exceptions. + """ + +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:: + + 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, e: + print "pxssh failed on login." + print str(e) + + 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) + """ + + def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): + spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env) + + self.name = '' + + #SUBTLE HACK ALERT! Note that the command to set 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 = "\[PEXPECT\][\$\#] " + self.PROMPT = self.UNIQUE_PROMPT + + # used to set shell command-line prompt to UNIQUE_PROMPT. + self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '" + self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '" + self.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" + # 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.auto_prompt_reset = True + + 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 synch_original_prompt (self): + + """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. """ + + # 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. + # If latency is worse than these values then this will fail. + + self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + x = self.read_nonblocking(size=1000,timeout=1) + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + a = self.read_nonblocking(size=1000,timeout=1) + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + b = self.read_nonblocking(size=1000,timeout=1) + 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. + def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True): + + """This logs the user into the given server. It uses the + '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 "~/.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 ExceptionPxssh exception. + + In some situations it is not possible or desirable to reset the + original prompt. In this case, set 'auto_prompt_reset' to False to + inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh + uses a unique prompt in the prompt() method. If the original prompt is + not reset then this will disable the prompt() method unless you + manually set the PROMPT attribute. """ + + ssh_options = '-q' + if self.force_password: + ssh_options = ssh_options + ' ' + self.SSH_OPTS + if port is not None: + ssh_options = ssh_options + ' -p %s'%(str(port)) + cmd = "ssh %s -l %s %s" % (ssh_options, username, server) + + # This does not distinguish between a remote server 'password' prompt + # and a local ssh 'passphrase' prompt (for unlocking a private key). + spawn._spawn(self, cmd) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host"], 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") + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + if i==2: # password or passphrase + self.sendline(password) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + if i==4: + self.sendline(terminal_type) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + + # 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 not self.synch_original_prompt(): + 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\n'+self.before) + return True + + def logout (self): + + """This 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=20): + + """This matches the shell prompt. This is little more than a short-cut + to the expect() method. This returns True if the shell prompt was + matched. This returns False if there was a timeout. Note that if you + called login() with auto_prompt_reset set to False then you should have + manually set the PROMPT attribute to a regex pattern for matching the + prompt. """ + + 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 prompt() method to match the shell prompt + unambiguously. This method is called automatically by the 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. Just set the PROMPT + attribute to a regular expression that matches it. In this case you + should call login() with auto_prompt_reset=False; then set the PROMPT + attribute. After that the 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/src/link/pexpect/screen.py b/src/link/pexpect/screen.py new file mode 100644 index 0000000..13699f9 --- /dev/null +++ b/src/link/pexpect/screen.py @@ -0,0 +1,380 @@ +"""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 ANSI class +extends this class to add parsing of ANSI escape codes. + +$Id: screen.py 486 2007-07-13 01:04:16Z noah $ +""" + +import copy + +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 = chr(32) # Space or blank character. + +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 + rectangluar 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). """ + + def __init__ (self, r=24,c=80): + + """This initializes a blank scree of the given dimentions.""" + + self.rows = r + self.cols = c + 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 c in range(self.rows)] + + def __str__ (self): + + """This returns a printable representation of the screen. The end of + each screen line is terminated by a newline. """ + + return '\n'.join ([ ''.join(c) for c in self.w ]) + + def dump (self): + + """This returns a copy of the screen as a string. This is similar to + __str__ except that lines are not terminated with line feeds. """ + + return ''.join ([ ''.join(c) for c in self.w ]) + + def pretty (self): + + """This returns a copy of the screen as a string with an ASCII text box + around the screen border. This is similar to __str__ except that it + adds a box. """ + + top_bot = '+' + '-'*self.cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot + + def fill (self, ch=SPACE): + + self.fill_region (1,1,self.rows,self.cols, ch) + + def fill_region (self, rs,cs, re,ce, ch=SPACE): + + 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) + ch = str(ch)[0] + self.w[r-1][c-1] = ch + + def put (self, ch): + + """This puts a characters at the current cursor position. + """ + + 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. + """ + + 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): + + 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 = '' + 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): # [{ROW};{COLUMN}H + + self.cur_r = r + self.cur_c = c + self.cursor_constrain () + + def cursor_back (self,count=1): # [{COUNT}D (not confused with down) + + self.cur_c = self.cur_c - count + self.cursor_constrain () + + def cursor_down (self,count=1): # [{COUNT}B (not confused with back) + + self.cur_r = self.cur_r + count + self.cursor_constrain () + + def cursor_forward (self,count=1): # [{COUNT}C + + self.cur_c = self.cur_c + count + self.cursor_constrain () + + def cursor_up (self,count=1): # [{COUNT}A + + self.cur_r = self.cur_r - count + self.cursor_constrain () + + def cursor_up_reverse (self): # 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): # [{ROW};{COLUMN}f + + """Identical to Cursor Home.""" + + self.cursor_home (r, c) + + def cursor_save (self): # [s + + """Save current cursor position.""" + + self.cursor_save_attrs() + + def cursor_unsave (self): # [u + + """Restores cursor position after a Save Cursor.""" + + self.cursor_restore_attrs() + + def cursor_save_attrs (self): # 7 + + """Save current cursor position.""" + + self.cur_saved_r = self.cur_r + self.cur_saved_c = self.cur_c + + def cursor_restore_attrs (self): # 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): # [r + + """Enable scrolling for entire display.""" + + self.scroll_row_start = 1 + self.scroll_row_end = self.rows + + def scroll_screen_rows (self, rs, re): # [{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): # 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): # 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): # [0K -or- [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): # [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): # [2K + + """Erases the entire current line.""" + + self.fill_region (self.cur_r, 1, self.cur_r, self.cols) + + def erase_down (self): # [0J -or- [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): # [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): # [2J + + """Erases the screen with the background color.""" + + self.fill () + + def set_tab (self): # H + + """Sets a tab at the current position.""" + + pass + + def clear_tab (self): # [g + + """Clears tab at the current position.""" + + pass + + def clear_all_tabs (self): # [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/src/link/pexpect/setup.py b/src/link/pexpect/setup.py new file mode 100755 index 0000000..49a05c1 --- /dev/null +++ b/src/link/pexpect/setup.py @@ -0,0 +1,39 @@ +''' +$Revision: 485 $ +$Date: 2007-07-12 15:23:15 -0700 (Thu, 12 Jul 2007) $ +''' +from distutils.core import setup +setup (name='pexpect', + version='2.3', + py_modules=['pexpect', 'pxssh', 'fdpexpect', 'FSM', 'screen', 'ANSI'], + description='Pexpect is a pure Python Expect. It allows easy control of other applications.', + author='Noah Spurrier', + author_email='noah@noah.org', + url='http://pexpect.sourceforge.net/', + license='MIT license', + platforms='UNIX', +) + +# classifiers = [ +# 'Development Status :: 4 - Beta', +# 'Environment :: Console', +# 'Environment :: Console (Text Based)', +# 'Intended Audience :: Developers', +# 'Intended Audience :: System Administrators', +# 'Intended Audience :: Quality Engineers', +# 'License :: OSI Approved :: Python Software Foundation License', +# 'Operating System :: POSIX', +# 'Operating System :: MacOS :: MacOS X', +# 'Programming Language :: Python', +# 'Topic :: Software Development', +# 'Topic :: Software Development :: Libraries :: Python Modules', +# 'Topic :: Software Development :: Quality Assurance', +# 'Topic :: Software Development :: Testing', +# 'Topic :: System, System :: Archiving :: Packaging, System :: Installation/Setup', +# 'Topic :: System :: Shells', +# 'Topic :: System :: Software Distribution', +# 'Topic :: Terminals, Utilities', +# ], + + + diff --git a/src/rpsconst.pyc b/src/rpsconst.pyc deleted file mode 100644 index 6feecafa5bdf5250e7e77309747155d8fdaf5164..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 719 zcmZWm&2AGh5O#iBw)D=?#|Wvr6&Dbys+P2eicm#Osz`f@TzfV#@!BhoT`7;jk>}t6 zIPo;xcml?55r^*D-_G~V?07u$mb-=+MF|^_H_r3B`GTWvqY=qp z7Avnq0Lvftd=_`WSK1gYTI6TAZJpb}{vt|Q3bdt&=Q%~#0>7V5`P{pW7j3KUhEHAF zLD2b3ZTY*?(+_1)%(MiHpm?+jJQtRKxmliI*c;&H$@h^X08c4rH`D9e#WimnDftgRp&He+v zCtBSjc&yY{QC&G1TcF@d!DH1?A0_p#qLEg?N4rbbzu{sk|EzT?8aPjv!Z!rt{J8Y6iW!wHP&d>bJuCjO_>vTAJt8s<3U#o=Xw5r zqiU4PEA{I3Ty?KDRXfaI%+KbdM$Odk2T`@2FMFXM_+D)CTj;sf@@{^`!)$)F97I?U zP36|>K^&VffA=uI)Yvuo^`KG-yuBzt6YcC(tGnj#8s0JGMd6g!_)zzWcTE1y++3b7 zu6uiXLAaeK0VWROpc>|bF#pb_OIN3IuGwz{bra=R4&xn6yfbxqDwkUd$|j7ApKpXd zNb$n_t;MyAQG8f2`HG!;FCQ6`=X;hG=jNAJ=kr?uW_bwT#9mMV;lY3Lvs`e%gqYQv z!8@n}073#32@pM2dH9^ zYC)V|V`E1a9xCuA@uKdY;%(t(=;%7E`Hi$TRYD3WZj?h+oWFi&p>*qZaX!wVTPlU# zo+*{$tai#3FN)CXLvO*vi$!}>KJC@Fqt3y%3K){;Jyg?oB2$m1{d?YYv085InJ`Ar z+@G%Ds>-;c>3S`ij_ZwZclvSUC=0h==0=n+ar&KtqZ=zfTn@VZsO%Uyqhlw1El*c8 zcN^O;p%L+L;tZ#h1=qdE%x>cddKWr_>N-kvgvvmqAU2|6k)YUrfsrKTgN+pBoeju( zN*fUOK5ana)7r?0->;3V_!(_L0c5oSNwrctK&jgcs#qVz6UT9jnN@U{k zW6SCW(D55(uBhN#?INso zr~Lf-dsXr}_eu?FWwCb`OG|-^wUoZ!MQIXkmbQS9$IwjOz*F@?pP;~%7JO`NRgHr1 zZl$r+Lz(9Ji)PD&{)cF_u-9Vgvj}xx!~@n0QHOyxcL30$&l$>LR8lfF3L)WfF%Op>}76^-W?pG7KlIPRL-#t~QwHeeS!w@)y* zG##!I&hA>hVO*My)1aH$+ie??1@xMh{u`+B)1r+^?WPxX$orV&@!G$!B3BYS0#DaQ z&1dhhN@5}J9b%U;&>}X2HVb8Vj^iJp!riA}6(9D?l>}^$cHuQS?5aV6uL6kNOG$N5 z9Jm(*iwM0}1q_d~-0@o#FfKi3xG*BOW`9(gU6^;jjdp?WwpxT!&?3ulXX7(yKkyk$m`` z_#Ke~<-+CJRjNy+J@`Ns_~!2OoS=ZYbg0yHxsz>>iUp*SLN z8;T@sC)E%6gnP8ctJzR9$B~5W*r{Y|4Uz-YN<7dqnJ>z6J$J2IYg9bu zv9yaIH;mT~YbMgLAc#%<*yL*LA-tx7rMN)9!as|5qSI1mAzoy|;Y60d42OA+R#Bx! zH<|jvDr96MTIRgVXZ-+cTt+t?oU7ZfC+>jkgwm^wam_Y#ys4|@IlwDhE(?iHq4c9D z;T{?X8%^4ao44gCAqQ_V!N*Jq4w8`K=A;~{pTfi&8VM#_c*6a>tYZyU;&0n7NeK?c zC63JO=Vbl(y;IS-WQCr+?uFaNo#7Zcl{3%AG0aW7TVE9KQE$3`rB#SW?<>wwQ+yVhSQWK)7DYjBz zpY;R5;yInu)Mat=vh(8vv!UQU*creSg3*0VE^{g=M|Myxj-7(V#N{0IX>KbilQ5r? zN62(#XhkQjNrmB@($W{cWO+@Yv#>FE~8)>Y8`AV*P2GM$o=GmF}xt z&ey5msha9zYm0UoSa{AZx`>(wL(;-ZMEh-uvqR`di50GnIk5Ko1Vd=aLu9I zn(n)uacPcQemfKT>J{j_vB0Jzw1;PHuXi#*eSzSBBJp!Hqt{VE5b!$;zhJ<5!I^M| zob%2F{0_>fGlpV6)pXFllEZT-(JxS05rN&HYX^4^GQ7$V2qP(eEdWfZt^taQn*||; zRSW=Eh|nYo1E@0!Hel)O{#(#SZ^_RfLx6Hh7H-17qJ%&-!G8o&yIum-VZCZ=itr|2 zNA=PSTJ~;L5mBmXfKc`#RZA5U>a|;sfm&bvxYFluZ@RQds+A%&Pz<9g!F`)ik9v0% z+;=frK&W^7zN!Bfy@;6wflA6a^G+XlpRtOG9Oto=>-%UNyg|1C{LTzbEUsLF^6}Kovj5a#;ga zMRnbPWPB5);)k!pV&l8lutY6@V%LQD5`{3c;ocjZq?~mbgMM^9dI^g~mtPdQvz$k< z)d}sV`$KH>dz6S0EY3JsI0W7fID^ipwfsqpx6!qsb&IS>R41>fJW*&8jJ;zH z(;t68vB7&{+!=R9EK^8wn<+m*ho{zWU3qBb4vx`64d&GLsr9ffgI!K00fs8(9j36K zIL;)VxQj_vf;rgbPrSx%n@JrIS^iKhm3M-Q@7_hLcU&SS{)l4zg~R}Ol(U@TZBiul z&0O(wj9B=*1ic5*1e2iMm={v%#IYw}=2K1e2Z#YVDJVG@{)1V~AK7(DwY3)=2U-sv z1Ta`beu*75G@4v5UC@>pqs9%RvzKK)Z$^M^U-YM~Y-TR|s z_y455ziN!*ly&X@m4_ry>;ZAUkklh^hxowzQFTIJ=Sc=7l|dFXq#GVbo9-(ZR+_Ob zTqJA&hG5sNgnY{5I_o~e65_TCNmkr47_@qc7Y0YwuiC5`n~3Ax&$!?fRu@pIc|a_q zLbqHEjm4vy2-=al+4J!ITDWbaoTjZ`ENZu9?V-^=;f5}%-ocRx)<2^}3_T^0bMmm& zNf}F=hfW(4M)zmi?H8S)MB3r+ix?kra)~lQ+bVP1hZ@*|ww09Uw945jL}6+<5WcHF z_gB8323xF+W3z4JZlH7UQIoXd=DRJsW|Q(NCv+Y1Ks5fg6LQWfCZ_ClwiB^f<-%h@ ziM2GVMsOW%!gt$5%`BJbI#wsOK#SPpZ`_9m({MuyH1WqX6xTU+lND2L?j|c*FIS;U zRqav}xx9aOmlb#J(5bDH63Z{a_D~|)BcwBONPqTaNATG{+&`SnzK~6pF{3|Zssf0n z6`lwzCawcl0S))0N2ctBd?IO#s<&jvjO1LEX`7(?2l`E1X?U(WK>wNLsa^dq7ZPY^ zk99HLgbkNyGGD8%Gcd)ZzI>_VSIfwA>p|(;S*vZ}BxTUUzmJrwVHDeMk8Wc`bN?>4 zvH^&S-Ys_DW%Vj5eX`h$Al&bBn0wjGB$a^6TXQR@x=2Frq|jBjV8-U|KO}lJP)H{y bphj{{wUSPxQ$tWd&EG_}f9&n?b7SUzQtX79 -- 2.20.1