From 114dcd565324156d108b3362d7222d1f4864e0ce Mon Sep 17 00:00:00 2001
From: Brandon Heller <brandonh@stanford.edu>
Date: Fri, 29 Jan 2010 14:42:53 -0800
Subject: [PATCH] Improve CLI line editing capabilities

Convert CLI to use Python Cmd object, which provides line editing.
---
 mininet/cli.py | 129 ++++++++++++++++++++++++-------------------------
 1 file changed, 62 insertions(+), 67 deletions(-)

diff --git a/mininet/cli.py b/mininet/cli.py
index 20d843b5..d99fc281 100644
--- a/mininet/cli.py
+++ b/mininet/cli.py
@@ -33,21 +33,18 @@
   For now, we recommend limiting CLI use to non-interactive
   commands which terminate in a reasonable amount of time.
 
-- We don't (yet) support command line history editing. This is
-  coming soon.
-
 """
 
 from subprocess import call
 import sys
+from cmd import Cmd
 
 from mininet.log import lg
 
-class CLI( object ):
+class CLI( Cmd ):
     "Simple command-line interface to talk to nodes."
 
-    cmds = [ '?', 'help', 'nodes', 'net', 'sh', 'pingAll', 'exit',
-            'pingPair', 'iperf', 'iperfUdp', 'intfs', 'dump' ]
+    prompt = 'mininet> '
 
     def __init__( self, mininet ):
         self.mn = mininet
@@ -57,24 +54,27 @@ def __init__( self, mininet ):
         for cname, cnode in self.mn.controllers.iteritems():
             self.nodemap[ cname ] = cnode
         self.nodelist = self.nodemap.values()
-        self.run()
+        Cmd.__init__( self )
+
+        lg.warn( '*** Starting CLI:\n' )
+        self.cmdloop()
 
     # Disable pylint "Unused argument: 'arg's'" messages.
     # Each CLI function needs the same interface.
     # pylint: disable-msg=W0613
 
-    # Commands
-    def help( self, args ):
-        "Semi-useful help for CLI."
-        helpStr = ( 'Available commands are:' + str( self.cmds ) + '\n'
-                   'You may also send a command to a node using:\n'
+    def do_help(self, arg):
+        "Describe available CLI commands."
+        Cmd.do_help(self, arg)
+        helpStr = ( 'You may also send a command to a node using:\n'
                    '  <node> command {args}\n'
                    'For example:\n'
                    '  mininet> h0 ifconfig\n'
                    '\n'
                    'The interpreter automatically substitutes IP '
                    'addresses\n'
-                   'for node names, so commands like\n'
+                   'for node names when a node is the first arg, so command'
+                   ' like\n'
                    '  mininet> h0 ping -c1 h1\n'
                    'should work.\n'
                    '\n\n'
@@ -82,14 +82,14 @@ def help( self, args ):
                    'so please limit commands to ones that do not\n'
                    'require user interaction and will terminate\n'
                    'after a reasonable amount of time.\n' )
-        print( helpStr )
+        self.stdout.write(helpStr)
 
-    def nodes( self, args ):
+    def do_nodes( self, args ):
         "List all nodes."
         nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
         lg.info( 'available nodes are: \n%s\n' % nodes )
 
-    def net( self, args ):
+    def do_net( self, args ):
         "List network connections."
         for switchDpid in self.mn.topo.switches():
             switch = self.mn.nodes[ switchDpid ]
@@ -99,81 +99,76 @@ def net( self, args ):
                 lg.info( ' %s' % node.name )
             lg.info( '\n' )
 
-    def sh( self, args ):
+    def do_sh( self, args ):
         "Run an external shell command"
         call( [ 'sh', '-c' ] + args )
 
-    def pingAll( self, args ):
+    def do_pingAll( self, args ):
         "Ping between all hosts."
         self.mn.pingAll()
 
-    def pingPair( self, args ):
+    def do_pingPair( self, args ):
         "Ping between first two hosts, useful for testing."
         self.mn.pingPair()
 
-    def iperf( self, args ):
+    def do_iperf( self, args ):
         "Simple iperf TCP test between two hosts."
         self.mn.iperf()
 
-    def iperfUdp( self, args ):
+    def do_iperfUdp( self, args ):
         "Simple iperf UDP test between two hosts."
         udpBw = args[ 0 ] if len( args ) else '10M'
         self.mn.iperfUdp( udpBw )
 
-    def intfs( self, args ):
+    def do_intfs( self, args ):
         "List interfaces."
         for node in self.mn.nodes.values():
             lg.info( '%s: %s\n' % ( node.name, ' '.join( node.intfs ) ) )
 
-    def dump( self, args ):
+    def do_dump( self, args ):
         "Dump node info."
         for node in self.mn.nodes.values():
             lg.info( '%s\n' % node )
 
-    # Re-enable pylint "Unused argument: 'arg's'" messages.
-    # pylint: enable-msg=W0613
+    def do_exit( self, args ):
+        "Exit"
+        return 'exited by user command'
 
-    def run( self ):
-        "Read and execute commands."
-        lg.warn( '*** Starting CLI:\n' )
-        while True:
-            lg.warn( 'mininet> ' )
-            inputLine = sys.stdin.readline()
-            if inputLine == '':
-                break
-            if inputLine[ -1 ] == '\n':
-                inputLine = inputLine[ :-1 ]
-            cmd = inputLine.split( ' ' )
-            first = cmd[ 0 ]
-            rest = cmd[ 1: ]
-            if first in self.cmds and hasattr( self, first ):
-                getattr( self, first )( rest )
-            elif first in self.nodemap and rest != []:
-                node = self.nodemap[ first ]
-                # Substitute IP addresses for node names in command
-                rest = [ self.nodemap[ arg ].IP()
+    def do_quit( self, args ):
+        "Exit"
+        self.do_exit()
+
+    def default( self, line ):
+        """Called on an input line when the command prefix is not recognized.
+
+        Overridden to run shell commands when a node is the first CLI argument.
+        Past the first CLI argument, node names are automatically replaced with
+        corresponding IP addrs.
+        """
+        first, args, line = self.parseline( line )
+        if len(args) > 0 and args[ -1 ] == '\n':
+                args = args[ :-1 ]
+        rest = args.split( ' ' )
+
+        if first in self.nodemap:
+            node = self.nodemap[ first ]
+            # Substitute IP addresses for node names in command
+            rest = [ self.nodemap[ arg ].IP()
                     if arg in self.nodemap else arg
                     for arg in rest ]
-                rest = ' '.join( rest )
-                # Interactive commands don't work yet, and
-                # there are still issues with control-c
-                lg.warn( '*** %s: running %s\n' % ( node.name, rest ) )
-                node.sendCmd( rest )
-                while True:
-                    try:
-                        done, data = node.monitor()
-                        lg.info( '%s\n' % data )
-                        if done:
-                            break
-                    except KeyboardInterrupt:
-                        node.sendInt()
-            elif first == '':
-                pass
-            elif first in [ 'exit', 'quit' ]:
-                break
-            elif first == '?':
-                self.help( rest )
-            else:
-                lg.error( 'CLI: unknown node or command: < %s >\n' % first )
-            #lg.info( '*** CLI: command complete\n' )
-        return 'exited by user command'
+            rest = ' '.join( rest )
+            # Run cmd on node:
+            node.sendCmd( rest )
+            while True:
+                try:
+                    done, data = node.monitor()
+                    lg.info( '%s\n' % data )
+                    if done:
+                        break
+                except KeyboardInterrupt:
+                    node.sendInt()
+        else:
+            self.stdout.write('*** Unknown syntax: %s\n'%line)
+
+    # Re-enable pylint "Unused argument: 'arg's'" messages.
+    # pylint: enable-msg=W0613
-- 
GitLab