mirror of https://github.com/n1nj4sec/pupy.git
PupyArgumentParser.add_argument can now take a "completer" keywork argument to change the command line autocompletion for a specific argument ! useful to use with path_completer
This commit is contained in:
parent
57c3b82541
commit
9ddaf96ccf
|
@ -2,6 +2,7 @@
|
|||
pupy/data/
|
||||
pupy/.pupy_history
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
||||
|
||||
#do not redistribute microsoft visual C++ DLLs (LICENSE)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.PupyCompleter import *
|
||||
from rpyc.utils.classic import download
|
||||
import os
|
||||
import os.path
|
||||
|
@ -11,7 +12,7 @@ class DownloaderScript(PupyModule):
|
|||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog='download', description=self.__doc__)
|
||||
self.arg_parser.add_argument('remote_file', metavar='<remote_path>')
|
||||
self.arg_parser.add_argument('local_file', nargs='?', metavar='<local_path>')
|
||||
self.arg_parser.add_argument('local_file', nargs='?', metavar='<local_path>', completer=path_completer)
|
||||
def run(self, args):
|
||||
remote_file=self.client.conn.modules['os.path'].expandvars(args.remote_file)
|
||||
rep=os.path.join("data","downloads",self.client.short_name())
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|
||||
# --------------------------------------------------------------
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.PupyCompleter import *
|
||||
|
||||
__class_name__="MemoryExec"
|
||||
|
||||
|
@ -25,9 +26,9 @@ class MemoryExec(PupyModule):
|
|||
self.interrupted=False
|
||||
self.mp=None
|
||||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog="msgbox", description=self.__doc__)
|
||||
self.arg_parser = PupyArgumentParser(prog="memory_exec", description=self.__doc__)
|
||||
self.arg_parser.add_argument('-p', '--process', default='cmd.exe', help='process to start suspended')
|
||||
self.arg_parser.add_argument('--fork', action='store_true', help='fork and do not wait for the child program. stdout will not be retrieved')
|
||||
self.arg_parser.add_argument('--fork', action='store_true', help='fork and do not wait for the child program. stdout will not be retrieved', completer=path_completer)
|
||||
self.arg_parser.add_argument('--interactive', action='store_true', help='interactive with the new process stdin/stdout')
|
||||
self.arg_parser.add_argument('path', help='path to the exe')
|
||||
self.arg_parser.add_argument('args', nargs='*', help='optional arguments to pass to the exe')
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
# --------------------------------------------------------------
|
||||
# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|
||||
# --------------------------------------------------------------
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.PupyCompleter import *
|
||||
import random
|
||||
import pupygen
|
||||
import os.path
|
||||
|
@ -11,7 +26,7 @@ class PersistenceModule(PupyModule):
|
|||
""" Enables persistence via registry keys """
|
||||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog="persistence", description=self.__doc__)
|
||||
self.arg_parser.add_argument('-e','--exe', help='Use an alternative file and set persistency')
|
||||
self.arg_parser.add_argument('-e','--exe', help='Use an alternative file and set persistency', completer=path_completer)
|
||||
self.arg_parser.add_argument('-m','--method', choices=['registry'], required=True, help='persistence method')
|
||||
|
||||
@windows_only
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.PupyCompleter import *
|
||||
from rpyc.utils.classic import upload
|
||||
import os
|
||||
import os.path
|
||||
|
@ -10,7 +11,7 @@ class UploaderScript(PupyModule):
|
|||
""" upload a file/directory to a remote system """
|
||||
def init_argparse(self):
|
||||
self.arg_parser = PupyArgumentParser(prog='download', description=self.__doc__)
|
||||
self.arg_parser.add_argument('local_file', metavar='<local_path>')
|
||||
self.arg_parser.add_argument('local_file', metavar='<local_path>', completer=path_completer)
|
||||
self.arg_parser.add_argument('remote_file', metavar='<remote_path>')
|
||||
def run(self, args):
|
||||
upload(self.client.conn, args.local_file, args.remote_file)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
# --------------------------------------------------------------
|
||||
# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu)
|
||||
# All rights reserved.
|
||||
|
@ -38,10 +39,11 @@ import logging
|
|||
import traceback
|
||||
import rpyc
|
||||
import rpyc.utils.classic
|
||||
from .PythonCompleter import PupyCompleter
|
||||
from .PythonCompleter import PythonCompleter
|
||||
from .PupyErrors import PupyModuleExit, PupyModuleError
|
||||
from .PupyModule import PupyArgumentParser
|
||||
from .PupyJob import PupyJob
|
||||
from .PupyCompleter import PupyCompleter
|
||||
import argparse
|
||||
from pupysh import __version__
|
||||
import copy
|
||||
|
@ -169,7 +171,6 @@ class PupyCmd(cmd.Cmd):
|
|||
self.intro = color(BANNER, 'green')
|
||||
self.prompt = color('>> ','blue', prompt=True)
|
||||
self.doc_header = 'Available commands :\n'
|
||||
self.complete_space=['run']
|
||||
self.default_filter=None
|
||||
try:
|
||||
if not self.config.getboolean("cmdline","display_banner"):
|
||||
|
@ -183,6 +184,7 @@ class PupyCmd(cmd.Cmd):
|
|||
self.aliases[command]=alias
|
||||
except Exception as e:
|
||||
logging.warning("error while parsing aliases from pupy.conf ! %s"%str(traceback.format_exc()))
|
||||
self.pupy_completer=PupyCompleter(self.aliases, self.pupsrv)
|
||||
|
||||
@staticmethod
|
||||
def table_format(diclist, wl=[], bl=[]):
|
||||
|
@ -270,9 +272,7 @@ class PupyCmd(cmd.Cmd):
|
|||
|
||||
def completenames(self, text, *ignored):
|
||||
dotext = 'do_'+text
|
||||
if text in self.complete_space:
|
||||
return [a[3:]+" " for a in self.get_names() if a.startswith(dotext)]+[x+" " for x in self.aliases.iterkeys() if x.startswith(text)]
|
||||
return [a[3:] for a in self.get_names() if a.startswith(dotext)]+[x for x in self.aliases.iterkeys() if x.startswith(text)]
|
||||
return [a[3:]+' ' for a in self.get_names() if a.startswith(dotext)]+[x+' ' for x in self.aliases.iterkeys() if x.startswith(text)]
|
||||
|
||||
def pre_input_hook(self):
|
||||
#readline.redisplay()
|
||||
|
@ -495,7 +495,7 @@ class PupyCmd(cmd.Cmd):
|
|||
oldcompleter=readline.get_completer()
|
||||
try:
|
||||
local_ns={"pupsrv":self.pupsrv}
|
||||
readline.set_completer(PupyCompleter(local_ns=local_ns).complete)
|
||||
readline.set_completer(PythonCompleter(local_ns=local_ns).complete)
|
||||
readline.parse_and_bind('tab: complete')
|
||||
code.interact(local=local_ns)
|
||||
except Exception as e:
|
||||
|
@ -596,9 +596,41 @@ class PupyCmd(cmd.Cmd):
|
|||
if pj:
|
||||
del pj
|
||||
|
||||
def complete(self, text, state):
|
||||
if state == 0:
|
||||
import readline
|
||||
origline = readline.get_line_buffer()
|
||||
line = origline.lstrip()
|
||||
stripped = len(origline) - len(line)
|
||||
begidx = readline.get_begidx() - stripped
|
||||
endidx = readline.get_endidx() - stripped
|
||||
if begidx>0:
|
||||
cmd, args, foo = self.parseline(line)
|
||||
if cmd == '':
|
||||
compfunc = self.completedefault
|
||||
else:
|
||||
try:
|
||||
#compfunc = getattr(self, 'complete_' + cmd)
|
||||
compfunc = self.pupy_completer.complete
|
||||
except AttributeError:
|
||||
compfunc = self.completedefault
|
||||
else:
|
||||
compfunc = self.completenames
|
||||
self.completion_matches = compfunc(text, line, begidx, endidx)
|
||||
try:
|
||||
return self.completion_matches[state]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
#text : word match
|
||||
#line : complete line
|
||||
def complete_run(self, text, line, begidx, endidx):
|
||||
pmc=PupyModCompleter(None)
|
||||
try:
|
||||
res=pmc.complete(text, line, begidx, endidx)
|
||||
except Exception as e:
|
||||
print e
|
||||
return res
|
||||
mline = line.partition(' ')[2]
|
||||
|
||||
joker=1
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
# --------------------------------------------------------------
|
||||
# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu)
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|
||||
# --------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import shlex
|
||||
import re
|
||||
|
||||
def debug(msg):
|
||||
#with open("/tmp/debug.log","a+") as log:
|
||||
# log.write(str(msg)+"\n")
|
||||
pass
|
||||
|
||||
def list_completer(l):
|
||||
def func(text, line, begidx, endidx):
|
||||
return [x for x in l if text.startswith(x)]
|
||||
|
||||
def void_completer(text, line, begidx, endidx):
|
||||
return []
|
||||
|
||||
def path_completer(text, line, begidx, endidx):
|
||||
l=[]
|
||||
if not text:
|
||||
l=os.listdir(".")
|
||||
else:
|
||||
try:
|
||||
dirname=os.path.dirname(text)
|
||||
basename=os.path.basename(text)
|
||||
for f in os.listdir(dirname):
|
||||
if f.startswith(basename):
|
||||
if os.path.isdir(os.path.join(dirname,f)):
|
||||
l.append(os.path.join(dirname,f)+os.sep)
|
||||
else:
|
||||
l.append(os.path.join(dirname,f)+" ")
|
||||
except Exception as e:
|
||||
pass
|
||||
return l
|
||||
|
||||
class PupyCompleter(object):
|
||||
def __init__(self, aliases, pupysrv):
|
||||
self.aliases=aliases
|
||||
self.pupysrv=pupysrv
|
||||
|
||||
def get_module_completer(self, name):
|
||||
#TODO handle aliases
|
||||
return self.pupysrv.get_module_completer(name)
|
||||
|
||||
def complete(self, text, line, begidx, endidx):
|
||||
try:
|
||||
#debug("\"%s\" \"%s\" %s %s"%(text, line, begidx, endidx))
|
||||
if line.startswith("run "):
|
||||
res=self.complete_run(text, line, begidx, endidx)
|
||||
if res is not None:
|
||||
return res
|
||||
modname=line[4:].split()[0]
|
||||
completer_func=self.get_module_completer(modname).complete
|
||||
debug("%s"%completer_func)
|
||||
if completer_func:
|
||||
return completer_func(text, line, begidx, endidx)
|
||||
else:
|
||||
return []
|
||||
except Exception as e:
|
||||
#print e
|
||||
pass
|
||||
|
||||
def complete_run(self, text, line, begidx, endidx):
|
||||
mline = line.partition(' ')[2]
|
||||
joker=1
|
||||
found_module=False
|
||||
#handle autocompletion of modules with --filter argument
|
||||
for x in shlex.split(mline):
|
||||
if x in ("-f", "--filter"):#arguments with a param
|
||||
joker+=1
|
||||
elif x in ("--bg",):#arguments without parameter
|
||||
pass
|
||||
else:
|
||||
joker-=1
|
||||
if not x.startswith("-") and joker==0:
|
||||
found_module=True
|
||||
if joker<0:
|
||||
return
|
||||
if ((len(text)>0 and joker==0) or (len(text)==0 and not found_module and joker<=1)):
|
||||
return [re.sub(r"(.*)\.pyc?$",r"\1",x)+" " for x in os.listdir("modules") if x.startswith(text) and not x=="__init__.py" and not x=="__init__.pyc"]
|
||||
|
||||
|
||||
class PupyModCompleter(object):
|
||||
def __init__(self):
|
||||
self.conf= {
|
||||
"positional_args":[
|
||||
#TODO
|
||||
],
|
||||
"optional_args":[
|
||||
],
|
||||
}
|
||||
|
||||
def add_positional_arg(self, names, **kwargs):
|
||||
""" names can be a string or a list to pass args aliases at once """
|
||||
if not type(names) is list and not type(names) is tuple:
|
||||
names=[names]
|
||||
for name in names:
|
||||
self.conf["positional_args"].append((name, kwargs))
|
||||
|
||||
def add_optional_arg(self, names, **kwargs):
|
||||
""" names can be a string or a list to pass args aliases at once """
|
||||
if not type(names) is list and not type(names) is tuple:
|
||||
names=[names]
|
||||
for name in names:
|
||||
self.conf["optional_args"].append((name, kwargs))
|
||||
|
||||
def get_optional_nargs(self, name):
|
||||
if "action" in self.conf["optional_args"]:
|
||||
action=self.conf["optional_args"]["action"]
|
||||
if action=="store_true" or action=="store_false":
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def get_optional_args(self, nargs=None):
|
||||
if nargs is None:
|
||||
return [x[0] for x in self.conf["optional_args"]]
|
||||
else:
|
||||
return [x[0] for x in self.conf["optional_args"] if self.get_optional_nargs(x[0])==nargs]
|
||||
|
||||
def get_last_text(self, text, line, begidx, endidx):
|
||||
try:
|
||||
return line[0:begidx-1].rsplit(' ',1)[1].strip()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_positional_arg_index(self, text, line, begidx, endidx):
|
||||
#TODO
|
||||
tab=shlex.split(line)
|
||||
positional_index=-1
|
||||
for i in range(0, len(tab)):
|
||||
if tab[i] in self.get_optional_args(nargs=0):
|
||||
continue
|
||||
elif tab[i] in self.get_optional_args(nargs=1):
|
||||
i+=1
|
||||
continue
|
||||
else:
|
||||
positional_index+=1
|
||||
if len(text)==0:
|
||||
positional_index+=1
|
||||
return positional_index
|
||||
|
||||
def get_optional_args_completer(self, name):
|
||||
return [x[1]["completer"] for x in self.conf["optional_args"] if x[0]==name][0]
|
||||
def get_positional_args_completer(self, index):
|
||||
return self.conf["positional_args"][index][1]["completer"]
|
||||
|
||||
def complete(self, text, line, begidx, endidx):
|
||||
debug("\"%s\" \"%s\" %s %s"%(text, line, begidx, endidx))
|
||||
last_text=self.get_last_text(text, line, begidx, endidx)
|
||||
debug("last text: %s"%last_text)
|
||||
if last_text in self.get_optional_args(nargs=1):
|
||||
debug(self.get_optional_args_completer(last_text))
|
||||
return self.get_optional_args_completer(last_text)(text, line, begidx, endidx)
|
||||
if text.startswith("-"): #positional args completer
|
||||
return [x+" " for x in self.get_optional_args() if x.startswith(text)]
|
||||
else:
|
||||
try:
|
||||
debug("here")
|
||||
positional_index=self.get_positional_arg_index(text, line, begidx, endidx)-2 # -2 for "run" + "module_name"
|
||||
debug("positional index is %s"%positional_index)
|
||||
return self.get_positional_args_completer(positional_index)(text, line, begidx, endidx)
|
||||
except Exception as e:
|
||||
debug(e)
|
||||
|
||||
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
import argparse
|
||||
import sys
|
||||
from .PupyErrors import PupyModuleExit
|
||||
from .PupyCompleter import PupyModCompleter, void_completer
|
||||
import StringIO
|
||||
|
||||
class PupyArgumentParser(argparse.ArgumentParser):
|
||||
|
@ -24,6 +25,28 @@ class PupyArgumentParser(argparse.ArgumentParser):
|
|||
self._print_message(message, sys.stderr)
|
||||
raise PupyModuleExit("exit with status %s"%status)
|
||||
|
||||
def add_argument(self, *args, **kwargs):
|
||||
completer_func=None
|
||||
if "completer" in kwargs:
|
||||
completer_func=kwargs["completer"]
|
||||
del kwargs["completer"]
|
||||
else:
|
||||
completer_func=void_completer
|
||||
argparse.ArgumentParser.add_argument(self, *args, **kwargs)
|
||||
completer=self.get_completer()
|
||||
for a in args:
|
||||
if a.startswith("-"):
|
||||
completer.add_optional_arg(a, completer=completer_func)
|
||||
else:
|
||||
completer.add_positional_arg(a, completer=completer_func)
|
||||
|
||||
def get_completer(self):
|
||||
if hasattr(self,'pupy_mod_completer') and self.pupy_mod_completer is not None:
|
||||
return self.pupy_mod_completer
|
||||
else:
|
||||
self.pupy_mod_completer=PupyModCompleter()
|
||||
return self.pupy_mod_completer
|
||||
|
||||
class PupyModule(object):
|
||||
"""
|
||||
This is the class all the pupy scripts must inherit from
|
||||
|
@ -38,7 +61,6 @@ class PupyModule(object):
|
|||
""" client must be a PupyClient instance """
|
||||
self.client=client
|
||||
self.job=job
|
||||
self.init_argparse()
|
||||
if formatter is None:
|
||||
from .PupyCmd import PupyCmd
|
||||
self.formatter=PupyCmd
|
||||
|
@ -50,6 +72,7 @@ class PupyModule(object):
|
|||
else:
|
||||
self.stdout=stdout
|
||||
self.del_close=False
|
||||
self.init_argparse()
|
||||
|
||||
def __del__(self):
|
||||
if self.del_close:
|
||||
|
|
|
@ -204,6 +204,12 @@ class PupyServer(threading.Thread):
|
|||
l.append((module_name,module.__doc__))
|
||||
return l
|
||||
|
||||
def get_module_completer(self, module_name):
|
||||
""" return the module PupyCompleter if any is defined"""
|
||||
module=self.get_module(module_name)
|
||||
ps=module(None,None)
|
||||
return ps.arg_parser.get_completer()
|
||||
|
||||
def get_module(self, name):
|
||||
script_found=False
|
||||
for loader, module_name, is_pkg in pkgutil.iter_modules(modules.__path__):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import __builtin__
|
||||
|
||||
__all__ = ["PupyCompleter"]
|
||||
__all__ = ["PythonCompleter"]
|
||||
|
||||
class PupyCompleter:
|
||||
class PythonCompleter:
|
||||
def __init__(self, local_ns=None, global_ns=None):
|
||||
if local_ns is not None:
|
||||
self.local_ns=local_ns
|
||||
|
@ -100,7 +100,7 @@ def get_class_members(klass):
|
|||
if __name__=="__main__":
|
||||
import code
|
||||
import readline
|
||||
readline.set_completer(PupyCompleter().complete)
|
||||
readline.set_completer(PythonCompleter().complete)
|
||||
readline.parse_and_bind('tab: complete')
|
||||
code.interact()
|
||||
|
||||
|
|
Loading…
Reference in New Issue