view cmd2.py @ 23:07aae987b754

load great except for prompt markers
author devlinjs@FA7CZA6N1254998.wrightpatterson.afmc.ds.af.mil
date Wed, 19 Dec 2007 14:40:58 -0500
parents 221095f3a4af
children 7a89805a47b1
line wrap: on
line source

"""Variant on standard library's cmd with extra features:

Searchable command history
Multi-line commands
Case-insensitive commands
Special-character shortcut commands

still to do:
environment (maxrows, etc.)
edit

"""
import cmd, re, os

class Cmd(cmd.Cmd):
    caseInsensitive = True
    multilineCommands = []
    continuationPrompt = '> '    
    shortcuts = {'?': 'help', '!': 'shell', '@': 'load'}   
    excludeFromHistory = '''run r list l history hi ed li eof'''.split()   
    defaultExtension = 'txt'
    def __init__(self, *args, **kwargs):	
        cmd.Cmd.__init__(self, *args, **kwargs)
        self.history = History()
	
    def precmd(self, line):
        """Hook method executed just before the command line is
        interpreted, but after the input prompt is generated and issued.
        
        Makes commands case-insensitive (but unfortunately does not alter command completion).
        """
        try:
            (command, args) = line.split(None,1)
	except ValueError:
	    (command, args) = line, ''
	if self.caseInsensitive:
	    command = command.lower()
	statement = ' '.join([command, args])
	if (not self.multilineCommands) or (command not in self.multilineCommands):
	    return statement
	return self.finishStatement(statement)

    def postcmd(self, stop, line):
        """Hook method executed just after a command dispatch is finished."""
	try:
	    command = line.split(None,1)[0].lower()
	    if command not in self.excludeFromHistory:
		self.history.append(line)
	finally:
	    return stop
	
    def finishStatement(self, firstline):
	statement = firstline
	while not self.statementHasEnded(statement):
	    statement = '%s\n%s' % (statement, self.pseudo_raw_input(self.continuationPrompt))
	return statement
	# assembling a list of lines and joining them at the end would be faster, 
	# but statementHasEnded needs a string arg; anyway, we're getting
	# user input and users are slow.
	
    def pseudo_raw_input(self, prompt):
	"""copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout"""
	
	if self.use_rawinput:
	    try:
		line = raw_input(prompt)
	    except EOFError:
		line = 'EOF'
	else:
	    self.stdout.write(prompt)
	    self.stdout.flush()
	    line = self.stdin.readline()
	    if not len(line):
		line = 'EOF'
	    else:
		line = line[:-1] # chop \n
	return line
			    
    def do_EOF(self, arg):
	return True
    do_eof = do_EOF
    
    statementEndPattern = re.compile(r'[\n;]\s*$')	
    def statementHasEnded(self, lines):
	"""This version lets statements end with ; or with a blank line.
	Override for your own needs."""
	return bool(self.statementEndPattern.search(lines))
	       
    def parseline(self, line):
        """Parse the line into a command name and a string containing
        the arguments.  Returns a tuple containing (command, args, line).
        'command' and 'args' may be None if the line couldn't be parsed.
        """
        line = line.strip()
        if not line:
            return None, None, line
	shortcut = self.shortcuts.get(line[0])
	if shortcut and hasattr(self, 'do_%s' % shortcut):
	    line = '%s %s' % (shortcut, line[1:])
        i, n = 0, len(line)
        while i < n and line[i] in self.identchars: i = i+1
        cmd, arg = line[:i], line[i:].strip()
        return cmd, arg, line
    
    def do_shell(self, arg):
        'execute a command as if at the OS prompt.'
        os.system(arg)
	
    def do_history(self, arg):
        """history [arg]: lists past commands issued
        
        no arg -> list all
        arg is integer -> list one history item, by index
        arg is string -> string search
        arg is /enclosed in forward-slashes/ -> regular expression search
        """
        if arg:
            history = self.history.get(arg)
        else:
            history = self.history
        for hi in history:
            self.stdout.write(hi.pr())
    def last_matching(self, arg):
        try:
            if arg:
                return self.history.get(arg)[-1]
            else:
                return self.history[-1]
        except:
            return None        
    def do_list(self, arg):
        """list [arg]: lists last command issued
        
        no arg -> list absolute last
        arg is integer -> list one history item, by index
        - arg, arg - (integer) -> list up to or after #arg
        arg is string -> list last command matching string search
        arg is /enclosed in forward-slashes/ -> regular expression search
        """
        try:
            self.stdout.write(self.last_matching(arg).pr())
        except:
            pass
    do_hi = do_history
    do_l = do_list
    do_li = do_list
    
    def breakupStatements(self, txt):
	"""takes text that may include multiple statements and 
	breaks it into a list of individual statements."""
	result = ['']
	for line in txt.splitlines():
	    result[-1] += line
	    if self.statementHasEnded(result[-1]):
		result.append('')
	
    def do_load(self, fname):
        """Runs command(s) from a file."""
	stdin = self.stdin
	try:
	    self.stdin = open(fname, 'r')
        except IOError, e:
            try:
                self.stdin = open('%s.%s' % (fname, self.defaultExtension), 'r')
            except:
                print 'Problem opening file %s: \n%s' % (fname, e)
		self.stdin = stdin
                return 	    
	use_rawinput = self.use_rawinput
	self.use_rawinput = False
	self.cmdloop()
	self.stdin.close()
	self.stdin = stdin
	self.use_rawinput = use_rawinput
	self.stdin.flush()
	self.lastcmd = ''

class HistoryItem(str):
    def __init__(self, instr):
        str.__init__(self, instr)
        self.lowercase = self.lower()
        self.idx = None
    def pr(self):
	return '-------------------------[%d]\n%s\n' % (self.idx, str(self))
        
class History(list):
    rangeFrom = re.compile(r'^([\d])+\s*\-$')
    def append(self, new):
        new = HistoryItem(new)
        list.append(self, new)
        new.idx = len(self)
    def extend(self, new):
        for n in new:
            self.append(n)
    def get(self, getme):
        try:
            getme = int(getme)
            if getme < 0:
                return self[:(-1 * getme)]
            else:
                return [self[getme-1]]
        except IndexError:
            return []
        except (ValueError, TypeError):
            getme = getme.strip()
            mtch = self.rangeFrom.search(getme)
            if mtch:
                return self[(int(mtch.group(1))-1):]
            if getme.startswith(r'/') and getme.endswith(r'/'):
                finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
                def isin(hi):
                    return finder.search(hi)
            else:
                def isin(hi):
                    return (getme.lower() in hi.lowercase)
            return [itm for itm in self if isin(itm)]