Module devshell.ptcmd

This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() with calls to the input_method attribute

Expand source code
"""
This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() with calls to the input_method attribute
"""
import ast, inspect, cmd, shlex
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit import PromptSession


class PTCmd_Completer(Completer):
    def __init__(self,ptcmd):
        self.ptcmd = ptcmd
    def get_completions(self,document,complete_event):
        for suggestion in self.ptcmd.pt_complete(document,complete_event):
            #yield Completion(suggestion,start_position=0)
            try:
                yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
            except:
                pass
class PTCmd(cmd.Cmd):
    def __init__(self, completekey='tab', stdin=None, stdout=None,**psession_kwargs):
        super().__init__(completekey,stdin,stdout)
        psession_kwargs['completer'] = PTCmd_Completer(self)
        psession_kwargs['complete_while_typing'] = True
        self.psession = PromptSession(**psession_kwargs)
        self.input_method = self.psession.prompt
    def pt_complete(self, document,complete_event):
        origline = document.text
        line = origline.lstrip()
        stripped = len(origline) - len(line)
        begidx = document.cursor_position_col - stripped
        endidx = len(document.text) - stripped
        if begidx>0:
            cmd, args, foo = self.parseline(line)
            if cmd == '':
                compfunc = self.completedefault
            else:
                try:
                    compfunc = getattr(self, 'complete_' + cmd)
                except AttributeError:
                    compfunc = self.completedefault
        else:
            compfunc = self.completenames
        self.completion_matches = compfunc(document.text, line, begidx, endidx)
        yield from self.completion_matches


class SwitchInput(ast.NodeTransformer):
    def visit_Call(self,node):
        if isinstance(node.func,ast.Name) and node.func.id == 'input':
            load = ast.Load()
            return ast.Call(
                    func = ast.Attribute(
                            value=ast.Name(
                                id='self',
                                ctx=load,
                                ),
                            attr='input_method',
                            ctx=load,
                        ),
                    args=node.args,
                    keywords=node.keywords,
                    )
        else:
            return node

ptcmd_tree = ast.parse(inspect.getsource(PTCmd))
cmd_tree = ast.fix_missing_locations(SwitchInput().visit(ast.parse(inspect.getsource(cmd.Cmd)))) #get a version that swaps all input(...) calls with self.input_method(...)

#find the cmdloop function
found = False
for node in ast.walk(cmd_tree):
    if isinstance(node,ast.FunctionDef) and node.name == 'cmdloop':
        cmdloop_node = node
        found = True
        break
assert(found)

#find the PTCmd class
found = False
for node in ast.walk(ptcmd_tree):
    if isinstance(node,ast.ClassDef) and node.name == 'PTCmd':
        found = True
        ptcmd_node = node
        break
assert(found)
#add the cmdloop function to the class definition (overwrite inherited version)
ptcmd_node.body.append(cmdloop_node)
ptcmd_tree = ast.fix_missing_locations(ptcmd_tree)

#Redefine the new class
exec(compile(ptcmd_tree,'<ast>','exec'))

Classes

class PTCmd (completekey='tab', stdin=None, stdout=None, **psession_kwargs)

A simple framework for writing line-oriented command interpreters.

These are often useful for test harnesses, administrative tools, and prototypes that will later be wrapped in a more sophisticated interface.

A Cmd instance or subclass instance is a line-oriented interpreter framework. There is no good reason to instantiate Cmd itself; rather, it's useful as a superclass of an interpreter class you define yourself in order to inherit Cmd's methods and encapsulate action methods.

Instantiate a line-oriented interpreter framework.

The optional argument 'completekey' is the readline name of a completion key; it defaults to the Tab key. If completekey is not None and the readline module is available, command completion is done automatically. The optional arguments stdin and stdout specify alternate input and output file objects; if not specified, sys.stdin and sys.stdout are used.

Expand source code
class PTCmd(cmd.Cmd):
    def __init__(self, completekey='tab', stdin=None, stdout=None,**psession_kwargs):
        super().__init__(completekey,stdin,stdout)
        psession_kwargs['completer'] = PTCmd_Completer(self)
        psession_kwargs['complete_while_typing'] = True
        self.psession = PromptSession(**psession_kwargs)
        self.input_method = self.psession.prompt
    def pt_complete(self, document,complete_event):
        origline = document.text
        line = origline.lstrip()
        stripped = len(origline) - len(line)
        begidx = document.cursor_position_col - stripped
        endidx = len(document.text) - stripped
        if begidx>0:
            cmd, args, foo = self.parseline(line)
            if cmd == '':
                compfunc = self.completedefault
            else:
                try:
                    compfunc = getattr(self, 'complete_' + cmd)
                except AttributeError:
                    compfunc = self.completedefault
        else:
            compfunc = self.completenames
        self.completion_matches = compfunc(document.text, line, begidx, endidx)
        yield from self.completion_matches

Ancestors

  • cmd.Cmd

Subclasses

Methods

def cmdloop(self, intro=None)

Repeatedly issue a prompt, accept input, parse an initial prefix off the received input, and dispatch to action methods, passing them the remainder of the line as argument.

def pt_complete(self, document, complete_event)
class PTCmd_Completer (ptcmd)

Base class for completer implementations.

Expand source code
class PTCmd_Completer(Completer):
    def __init__(self,ptcmd):
        self.ptcmd = ptcmd
    def get_completions(self,document,complete_event):
        for suggestion in self.ptcmd.pt_complete(document,complete_event):
            #yield Completion(suggestion,start_position=0)
            try:
                yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
            except:
                pass

Ancestors

  • prompt_toolkit.completion.base.Completer

Methods

def get_completions(self, document, complete_event)

This should be a generator that yields :class:.Completion instances.

If the generation of completions is something expensive (that takes a lot of time), consider wrapping this Completer class in a ThreadedCompleter. In that case, the completer algorithm runs in a background thread and completions will be displayed as soon as they arrive.

:param document: :class:~prompt_toolkit.document.Document instance. :param complete_event: :class:.CompleteEvent instance.

Expand source code
def get_completions(self,document,complete_event):
    for suggestion in self.ptcmd.pt_complete(document,complete_event):
        #yield Completion(suggestion,start_position=0)
        try:
            yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
        except:
            pass
class SwitchInput

A :class:NodeVisitor subclass that walks the abstract syntax tree and allows modification of nodes.

The NodeTransformer will walk the AST and use the return value of the visitor methods to replace or remove the old node. If the return value of the visitor method is None, the node will be removed from its location, otherwise it is replaced with the return value. The return value may be the original node in which case no replacement takes place.

Here is an example transformer that rewrites all occurrences of name lookups (foo) to data['foo']::

class RewriteName(NodeTransformer):

   def visit_Name(self, node):
       return Subscript(
           value=Name(id='data', ctx=Load()),
           slice=Index(value=Str(s=node.id)),
           ctx=node.ctx
       )

Keep in mind that if the node you're operating on has child nodes you must either transform the child nodes yourself or call the :meth:generic_visit method for the node first.

For nodes that were part of a collection of statements (that applies to all statement nodes), the visitor may also return a list of nodes rather than just a single node.

Usually you use the transformer like this::

node = YourTransformer().visit(node)

Expand source code
class SwitchInput(ast.NodeTransformer):
    def visit_Call(self,node):
        if isinstance(node.func,ast.Name) and node.func.id == 'input':
            load = ast.Load()
            return ast.Call(
                    func = ast.Attribute(
                            value=ast.Name(
                                id='self',
                                ctx=load,
                                ),
                            attr='input_method',
                            ctx=load,
                        ),
                    args=node.args,
                    keywords=node.keywords,
                    )
        else:
            return node

Ancestors

  • ast.NodeTransformer
  • ast.NodeVisitor

Methods

def visit_Call(self, node)
Expand source code
def visit_Call(self,node):
    if isinstance(node.func,ast.Name) and node.func.id == 'input':
        load = ast.Load()
        return ast.Call(
                func = ast.Attribute(
                        value=ast.Name(
                            id='self',
                            ctx=load,
                            ),
                        attr='input_method',
                        ctx=load,
                    ),
                args=node.args,
                keywords=node.keywords,
                )
    else:
        return node