mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Use black with -l 120 param.
This commit is contained in:
1 parent
f928738bd5
commit
c021e21309
194 files changed
+6034
-4564
No files matched your search
@@ -50,7 +50,7 @@ async def cancellable_wait_run_in_executor(func, *args, **kwargs):
|
||||
:returns: Return the result of the function
|
||||
"""
|
||||
stopped_event = threading.Event()
|
||||
kwargs['stopped_event'] = stopped_event
|
||||
kwargs["stopped_event"] = stopped_event
|
||||
try:
|
||||
await wait_run_in_executor(func, *args, **kwargs)
|
||||
except asyncio.CancelledError:
|
||||
@@ -81,6 +81,7 @@ async def subprocess_check_output(*args, cwd=None, env=None, stderr=False):
|
||||
# and the code of VPCS, dynamips... Will detect it's not the correct binary
|
||||
return output.decode("utf-8", errors="ignore")
|
||||
|
||||
|
||||
async def wait_for_process_termination(process, timeout=10):
|
||||
"""
|
||||
Wait for a process terminate, and raise asyncio.TimeoutError in case of
|
||||
@@ -152,7 +153,6 @@ async def wait_for_named_pipe_creation(pipe_path, timeout=60):
|
||||
|
||||
|
||||
def locking(f):
|
||||
|
||||
@functools.wraps(f)
|
||||
async def wrapper(oself, *args, **kwargs):
|
||||
lock_name = "__" + f.__name__ + "_lock"
|
||||
@@ -160,4 +160,5 @@ def locking(f):
|
||||
setattr(oself, lock_name, asyncio.Lock())
|
||||
async with getattr(oself, lock_name):
|
||||
return await f(oself, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
@@ -32,10 +32,18 @@ import asyncio
|
||||
import aiofiles
|
||||
from concurrent import futures
|
||||
|
||||
from zipfile import (structCentralDir, structEndArchive64, structEndArchive, structEndArchive64Locator,
|
||||
stringCentralDir, stringEndArchive64, stringEndArchive, stringEndArchive64Locator)
|
||||
from zipfile import (
|
||||
structCentralDir,
|
||||
structEndArchive64,
|
||||
structEndArchive,
|
||||
structEndArchive64Locator,
|
||||
stringCentralDir,
|
||||
stringEndArchive64,
|
||||
stringEndArchive,
|
||||
stringEndArchive64Locator,
|
||||
)
|
||||
|
||||
stringDataDescriptor = b'PK\x07\x08' # magic number for data descriptor
|
||||
stringDataDescriptor = b"PK\x07\x08" # magic number for data descriptor
|
||||
|
||||
|
||||
def _get_compressor(compress_type):
|
||||
@@ -45,21 +53,23 @@ def _get_compressor(compress_type):
|
||||
|
||||
if compress_type == zipfile.ZIP_DEFLATED:
|
||||
from zipfile import zlib
|
||||
|
||||
return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
|
||||
elif compress_type == zipfile.ZIP_BZIP2:
|
||||
from zipfile import bz2
|
||||
|
||||
return bz2.BZ2Compressor()
|
||||
elif compress_type == zipfile.ZIP_LZMA:
|
||||
from zipfile import LZMACompressor
|
||||
|
||||
return LZMACompressor()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class PointerIO:
|
||||
|
||||
def __init__(self, mode='wb'):
|
||||
if mode not in ('wb', ):
|
||||
def __init__(self, mode="wb"):
|
||||
if mode not in ("wb",):
|
||||
raise RuntimeError('zipstream.ZipFile() requires mode "wb"')
|
||||
self.data_pointer = 0
|
||||
self.__mode = mode
|
||||
@@ -90,18 +100,17 @@ class PointerIO:
|
||||
|
||||
def write(self, data):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise ValueError("I/O operation on closed file")
|
||||
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
data = data.encode("utf-8")
|
||||
if not isinstance(data, bytes):
|
||||
raise TypeError('expected bytes')
|
||||
raise TypeError("expected bytes")
|
||||
self.data_pointer += len(data)
|
||||
return data
|
||||
|
||||
|
||||
class ZipInfo(zipfile.ZipInfo):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
zipfile.ZipInfo.__init__(self, *args, **kwargs)
|
||||
|
||||
@@ -113,23 +122,22 @@ class ZipInfo(zipfile.ZipInfo):
|
||||
"""
|
||||
|
||||
if self.compress_size > zipfile.ZIP64_LIMIT or self.file_size > zipfile.ZIP64_LIMIT:
|
||||
fmt = b'<4sLQQ'
|
||||
fmt = b"<4sLQQ"
|
||||
else:
|
||||
fmt = b'<4sLLL'
|
||||
fmt = b"<4sLLL"
|
||||
return struct.pack(fmt, stringDataDescriptor, self.CRC, self.compress_size, self.file_size)
|
||||
|
||||
|
||||
class ZipFile(zipfile.ZipFile):
|
||||
|
||||
def __init__(self, fileobj=None, mode='w', compression=zipfile.ZIP_STORED, allowZip64=True, chunksize=32768):
|
||||
def __init__(self, fileobj=None, mode="w", compression=zipfile.ZIP_STORED, allowZip64=True, chunksize=32768):
|
||||
"""Open the ZIP file with mode write "w"."""
|
||||
|
||||
if mode not in ('w', ):
|
||||
if mode not in ("w",):
|
||||
raise RuntimeError('aiozipstream.ZipFile() requires mode "w"')
|
||||
if fileobj is None:
|
||||
fileobj = PointerIO()
|
||||
|
||||
self._comment = b''
|
||||
self._comment = b""
|
||||
zipfile.ZipFile.__init__(self, fileobj, mode=mode, compression=compression, allowZip64=allowZip64)
|
||||
self._chunksize = chunksize
|
||||
self.paths_to_write = []
|
||||
@@ -156,8 +164,8 @@ class ZipFile(zipfile.ZipFile):
|
||||
# check for valid comment length
|
||||
if len(comment) >= zipfile.ZIP_MAX_COMMENT:
|
||||
if self.debug:
|
||||
print('Archive comment is too long; truncating to %d bytes' % zipfile.ZIP_MAX_COMMENT)
|
||||
comment = comment[:zipfile.ZIP_MAX_COMMENT]
|
||||
print("Archive comment is too long; truncating to %d bytes" % zipfile.ZIP_MAX_COMMENT)
|
||||
comment = comment[: zipfile.ZIP_MAX_COMMENT]
|
||||
self._comment = comment
|
||||
self._didModify = True
|
||||
|
||||
@@ -192,7 +200,7 @@ class ZipFile(zipfile.ZipFile):
|
||||
Write a file to the archive under the name `arcname`.
|
||||
"""
|
||||
|
||||
kwargs = {'filename': filename, 'arcname': arcname, 'compress_type': compress_type}
|
||||
kwargs = {"filename": filename, "arcname": arcname, "compress_type": compress_type}
|
||||
self.paths_to_write.append(kwargs)
|
||||
|
||||
def write_iter(self, arcname, iterable, compress_type=None):
|
||||
@@ -200,7 +208,7 @@ class ZipFile(zipfile.ZipFile):
|
||||
Write the bytes iterable `iterable` to the archive under the name `arcname`.
|
||||
"""
|
||||
|
||||
kwargs = {'arcname': arcname, 'iterable': iterable, 'compress_type': compress_type}
|
||||
kwargs = {"arcname": arcname, "iterable": iterable, "compress_type": compress_type}
|
||||
self.paths_to_write.append(kwargs)
|
||||
|
||||
def writestr(self, arcname, data, compress_type=None):
|
||||
@@ -210,6 +218,7 @@ class ZipFile(zipfile.ZipFile):
|
||||
|
||||
def _iterable():
|
||||
yield data
|
||||
|
||||
return self.write_iter(arcname, _iterable(), compress_type=compress_type)
|
||||
|
||||
async def _write(self, filename=None, iterable=None, arcname=None, compress_type=None):
|
||||
@@ -218,8 +227,7 @@ class ZipFile(zipfile.ZipFile):
|
||||
"""
|
||||
|
||||
if not self.fp:
|
||||
raise RuntimeError(
|
||||
"Attempt to write to ZIP archive that was already closed")
|
||||
raise RuntimeError("Attempt to write to ZIP archive that was already closed")
|
||||
if (filename is None and iterable is None) or (filename is not None and iterable is not None):
|
||||
raise ValueError("either (exclusively) filename or iterable shall be not None")
|
||||
|
||||
@@ -237,12 +245,12 @@ class ZipFile(zipfile.ZipFile):
|
||||
while arcname[0] in (os.sep, os.altsep):
|
||||
arcname = arcname[1:]
|
||||
if isdir:
|
||||
arcname += '/'
|
||||
arcname += "/"
|
||||
zinfo = ZipInfo(arcname, date_time)
|
||||
if st:
|
||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
|
||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
|
||||
else:
|
||||
zinfo.external_attr = 0o600 << 16 # ?rw-------
|
||||
zinfo.external_attr = 0o600 << 16 # ?rw-------
|
||||
if compress_type is None:
|
||||
zinfo.compress_type = self.compression
|
||||
else:
|
||||
@@ -253,8 +261,8 @@ class ZipFile(zipfile.ZipFile):
|
||||
else:
|
||||
zinfo.file_size = 0
|
||||
zinfo.flag_bits = 0x00
|
||||
zinfo.flag_bits |= 0x08 # ZIP flag bits, bit 3 indicates presence of data descriptor
|
||||
zinfo.header_offset = self.fp.tell() # Start of header bytes
|
||||
zinfo.flag_bits |= 0x08 # ZIP flag bits, bit 3 indicates presence of data descriptor
|
||||
zinfo.header_offset = self.fp.tell() # Start of header bytes
|
||||
if zinfo.compress_type == zipfile.ZIP_LZMA:
|
||||
# Compressed data includes an end-of-stream (EOS) marker
|
||||
zinfo.flag_bits |= 0x02
|
||||
@@ -284,15 +292,15 @@ class ZipFile(zipfile.ZipFile):
|
||||
if filename:
|
||||
async for buf in self.data_generator(filename):
|
||||
file_size = file_size + len(buf)
|
||||
CRC = zipfile.crc32(buf, CRC) & 0xffffffff
|
||||
CRC = zipfile.crc32(buf, CRC) & 0xFFFFFFFF
|
||||
if cmpr:
|
||||
buf = await self._run_in_executor(cmpr.compress, buf)
|
||||
compress_size = compress_size + len(buf)
|
||||
yield self.fp.write(buf)
|
||||
else: # we have an iterable
|
||||
else: # we have an iterable
|
||||
for buf in iterable:
|
||||
file_size = file_size + len(buf)
|
||||
CRC = zipfile.crc32(buf, CRC) & 0xffffffff
|
||||
CRC = zipfile.crc32(buf, CRC) & 0xFFFFFFFF
|
||||
if cmpr:
|
||||
buf = await self._run_in_executor(cmpr.compress, buf)
|
||||
compress_size = compress_size + len(buf)
|
||||
@@ -309,9 +317,9 @@ class ZipFile(zipfile.ZipFile):
|
||||
zinfo.file_size = file_size
|
||||
if not zip64 and self._allowZip64:
|
||||
if file_size > zipfile.ZIP64_LIMIT:
|
||||
raise RuntimeError('File size has increased during compressing')
|
||||
raise RuntimeError("File size has increased during compressing")
|
||||
if compress_size > zipfile.ZIP64_LIMIT:
|
||||
raise RuntimeError('Compressed size larger than uncompressed size')
|
||||
raise RuntimeError("Compressed size larger than uncompressed size")
|
||||
|
||||
yield self.fp.write(zinfo.DataDescriptor())
|
||||
self.filelist.append(zinfo)
|
||||
@@ -326,10 +334,10 @@ class ZipFile(zipfile.ZipFile):
|
||||
return
|
||||
|
||||
try:
|
||||
if self.mode in ('w', 'a') and self._didModify: # write ending records
|
||||
if self.mode in ("w", "a") and self._didModify: # write ending records
|
||||
count = 0
|
||||
pos1 = self.fp.tell()
|
||||
for zinfo in self.filelist: # write central directory
|
||||
for zinfo in self.filelist: # write central directory
|
||||
count = count + 1
|
||||
dt = zinfo.date_time
|
||||
dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
|
||||
@@ -338,15 +346,15 @@ class ZipFile(zipfile.ZipFile):
|
||||
if zinfo.file_size > zipfile.ZIP64_LIMIT or zinfo.compress_size > zipfile.ZIP64_LIMIT:
|
||||
extra.append(zinfo.file_size)
|
||||
extra.append(zinfo.compress_size)
|
||||
file_size = 0xffffffff
|
||||
compress_size = 0xffffffff
|
||||
file_size = 0xFFFFFFFF
|
||||
compress_size = 0xFFFFFFFF
|
||||
else:
|
||||
file_size = zinfo.file_size
|
||||
compress_size = zinfo.compress_size
|
||||
|
||||
if zinfo.header_offset > zipfile.ZIP64_LIMIT:
|
||||
extra.append(zinfo.header_offset)
|
||||
header_offset = 0xffffffff
|
||||
header_offset = 0xFFFFFFFF
|
||||
else:
|
||||
header_offset = zinfo.header_offset
|
||||
|
||||
@@ -354,9 +362,7 @@ class ZipFile(zipfile.ZipFile):
|
||||
min_version = 0
|
||||
if extra:
|
||||
# Append a ZIP64 field to the extra's
|
||||
extra_data = struct.pack(
|
||||
b'<HH' + b'Q'*len(extra),
|
||||
1, 8*len(extra), *extra) + extra_data
|
||||
extra_data = struct.pack(b"<HH" + b"Q" * len(extra), 1, 8 * len(extra), *extra) + extra_data
|
||||
min_version = zipfile.ZIP64_VERSION
|
||||
|
||||
if zinfo.compress_type == zipfile.ZIP_BZIP2:
|
||||
@@ -368,22 +374,54 @@ class ZipFile(zipfile.ZipFile):
|
||||
create_version = max(min_version, zinfo.create_version)
|
||||
try:
|
||||
filename, flag_bits = zinfo._encodeFilenameFlags()
|
||||
centdir = struct.pack(structCentralDir,
|
||||
stringCentralDir, create_version,
|
||||
zinfo.create_system, extract_version, zinfo.reserved,
|
||||
flag_bits, zinfo.compress_type, dostime, dosdate,
|
||||
zinfo.CRC, compress_size, file_size,
|
||||
len(filename), len(extra_data), len(zinfo.comment),
|
||||
0, zinfo.internal_attr, zinfo.external_attr,
|
||||
header_offset)
|
||||
centdir = struct.pack(
|
||||
structCentralDir,
|
||||
stringCentralDir,
|
||||
create_version,
|
||||
zinfo.create_system,
|
||||
extract_version,
|
||||
zinfo.reserved,
|
||||
flag_bits,
|
||||
zinfo.compress_type,
|
||||
dostime,
|
||||
dosdate,
|
||||
zinfo.CRC,
|
||||
compress_size,
|
||||
file_size,
|
||||
len(filename),
|
||||
len(extra_data),
|
||||
len(zinfo.comment),
|
||||
0,
|
||||
zinfo.internal_attr,
|
||||
zinfo.external_attr,
|
||||
header_offset,
|
||||
)
|
||||
except DeprecationWarning:
|
||||
print((structCentralDir, stringCentralDir, create_version,
|
||||
zinfo.create_system, extract_version, zinfo.reserved,
|
||||
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
|
||||
zinfo.CRC, compress_size, file_size,
|
||||
len(zinfo.filename), len(extra_data), len(zinfo.comment),
|
||||
0, zinfo.internal_attr, zinfo.external_attr,
|
||||
header_offset), file=sys.stderr)
|
||||
print(
|
||||
(
|
||||
structCentralDir,
|
||||
stringCentralDir,
|
||||
create_version,
|
||||
zinfo.create_system,
|
||||
extract_version,
|
||||
zinfo.reserved,
|
||||
zinfo.flag_bits,
|
||||
zinfo.compress_type,
|
||||
dostime,
|
||||
dosdate,
|
||||
zinfo.CRC,
|
||||
compress_size,
|
||||
file_size,
|
||||
len(zinfo.filename),
|
||||
len(extra_data),
|
||||
len(zinfo.comment),
|
||||
0,
|
||||
zinfo.internal_attr,
|
||||
zinfo.external_attr,
|
||||
header_offset,
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
yield self.fp.write(centdir)
|
||||
yield self.fp.write(filename)
|
||||
@@ -395,27 +433,44 @@ class ZipFile(zipfile.ZipFile):
|
||||
centDirCount = count
|
||||
centDirSize = pos2 - pos1
|
||||
centDirOffset = pos1
|
||||
if (centDirCount >= zipfile.ZIP_FILECOUNT_LIMIT or
|
||||
centDirOffset > zipfile.ZIP64_LIMIT or
|
||||
centDirSize > zipfile.ZIP64_LIMIT):
|
||||
if (
|
||||
centDirCount >= zipfile.ZIP_FILECOUNT_LIMIT
|
||||
or centDirOffset > zipfile.ZIP64_LIMIT
|
||||
or centDirSize > zipfile.ZIP64_LIMIT
|
||||
):
|
||||
# Need to write the ZIP64 end-of-archive records
|
||||
zip64endrec = struct.pack(
|
||||
structEndArchive64, stringEndArchive64,
|
||||
44, 45, 45, 0, 0, centDirCount, centDirCount,
|
||||
centDirSize, centDirOffset)
|
||||
structEndArchive64,
|
||||
stringEndArchive64,
|
||||
44,
|
||||
45,
|
||||
45,
|
||||
0,
|
||||
0,
|
||||
centDirCount,
|
||||
centDirCount,
|
||||
centDirSize,
|
||||
centDirOffset,
|
||||
)
|
||||
yield self.fp.write(zip64endrec)
|
||||
|
||||
zip64locrec = struct.pack(
|
||||
structEndArchive64Locator,
|
||||
stringEndArchive64Locator, 0, pos2, 1)
|
||||
zip64locrec = struct.pack(structEndArchive64Locator, stringEndArchive64Locator, 0, pos2, 1)
|
||||
yield self.fp.write(zip64locrec)
|
||||
centDirCount = min(centDirCount, 0xFFFF)
|
||||
centDirSize = min(centDirSize, 0xFFFFFFFF)
|
||||
centDirOffset = min(centDirOffset, 0xFFFFFFFF)
|
||||
|
||||
endrec = struct.pack(structEndArchive, stringEndArchive,
|
||||
0, 0, centDirCount, centDirCount,
|
||||
centDirSize, centDirOffset, len(self._comment))
|
||||
endrec = struct.pack(
|
||||
structEndArchive,
|
||||
stringEndArchive,
|
||||
0,
|
||||
0,
|
||||
centDirCount,
|
||||
centDirCount,
|
||||
centDirSize,
|
||||
centDirOffset,
|
||||
len(self._comment),
|
||||
)
|
||||
yield self.fp.write(endrec)
|
||||
yield self.fp.write(self._comment)
|
||||
self.fp.flush()
|
||||
|
||||
@@ -46,7 +46,7 @@ class EmbedShell:
|
||||
self._loop = loop
|
||||
self._reader = reader
|
||||
self._writer = writer
|
||||
self._prompt = '> '
|
||||
self._prompt = "> "
|
||||
self._welcome_message = welcome_message
|
||||
|
||||
@property
|
||||
@@ -85,30 +85,30 @@ class EmbedShell:
|
||||
"""
|
||||
Show help
|
||||
"""
|
||||
res = ''
|
||||
res = ""
|
||||
if len(args) == 0:
|
||||
res = 'Help:\n'
|
||||
res = "Help:\n"
|
||||
for name, value in inspect.getmembers(self):
|
||||
if not inspect.isgeneratorfunction(value):
|
||||
continue
|
||||
if name.startswith('_') or (len(args) and name != args[0]) or name == 'run':
|
||||
if name.startswith("_") or (len(args) and name != args[0]) or name == "run":
|
||||
continue
|
||||
doc = inspect.getdoc(value)
|
||||
res += name
|
||||
if len(args) and doc:
|
||||
res += ': ' + doc
|
||||
res += ": " + doc
|
||||
elif doc:
|
||||
res += ': ' + doc.split('\n')[0]
|
||||
res += '\n'
|
||||
res += ": " + doc.split("\n")[0]
|
||||
res += "\n"
|
||||
if len(args) == 0:
|
||||
res += '\nhelp command for details about a command\n'
|
||||
res += "\nhelp command for details about a command\n"
|
||||
return res
|
||||
|
||||
async def _parse_command(self, text):
|
||||
cmd = text.split(' ')
|
||||
cmd = text.split(" ")
|
||||
found = False
|
||||
if cmd[0] == '?':
|
||||
cmd[0] = 'help'
|
||||
if cmd[0] == "?":
|
||||
cmd[0] = "help"
|
||||
|
||||
# when there is no command specified just return empty result
|
||||
if not cmd[0].strip():
|
||||
@@ -121,7 +121,7 @@ class EmbedShell:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
res = (f'Command not found {cmd[0]}\n' + (await self.help()))
|
||||
res = f"Command not found {cmd[0]}\n" + (await self.help())
|
||||
return res
|
||||
|
||||
async def run(self):
|
||||
@@ -130,7 +130,7 @@ class EmbedShell:
|
||||
while True:
|
||||
self._writer.feed_data(self._prompt.encode())
|
||||
result = await self._reader.readline()
|
||||
result = result.decode().strip('\n')
|
||||
result = result.decode().strip("\n")
|
||||
res = await self._parse_command(result)
|
||||
self._writer.feed_data(res.encode())
|
||||
|
||||
@@ -143,7 +143,7 @@ class EmbedShell:
|
||||
for name, value in inspect.getmembers(self):
|
||||
if not inspect.isgeneratorfunction(value):
|
||||
continue
|
||||
if name.startswith('_') or name == 'run':
|
||||
if name.startswith("_") or name == "run":
|
||||
continue
|
||||
doc = inspect.getdoc(value)
|
||||
commands.append((name, doc))
|
||||
@@ -156,22 +156,23 @@ class PatchedStdinInput(StdinInput):
|
||||
Fixes issue when PyCharm runs own terminal without emulation.
|
||||
https://github.com/GNS3/gns3-server/issues/1172
|
||||
"""
|
||||
|
||||
def __init__(self, stdin=None):
|
||||
self.stdin = stdin or sys.stdin
|
||||
try:
|
||||
self.stdin.fileno()
|
||||
except io.UnsupportedOperation:
|
||||
if 'idlelib.run' in sys.modules:
|
||||
raise io.UnsupportedOperation(
|
||||
'Stdin is not a terminal. Running from Idle is not supported.')
|
||||
if "idlelib.run" in sys.modules:
|
||||
raise io.UnsupportedOperation("Stdin is not a terminal. Running from Idle is not supported.")
|
||||
else:
|
||||
raise io.UnsupportedOperation('Stdin is not a terminal.')
|
||||
raise io.UnsupportedOperation("Stdin is not a terminal.")
|
||||
|
||||
|
||||
class UnstoppableEventLoop(EventLoop):
|
||||
"""
|
||||
Partially fake event loop which cannot be stopped by CommandLineInterface
|
||||
"""
|
||||
|
||||
def __init__(self, loop):
|
||||
self._loop = loop
|
||||
|
||||
@@ -202,13 +203,13 @@ class ShellConnection(TelnetConnection):
|
||||
self._cli = None
|
||||
self._cb = None
|
||||
self._size = Size(rows=40, columns=79)
|
||||
self.encoding = 'utf-8'
|
||||
|
||||
self.encoding = "utf-8"
|
||||
|
||||
async def connected(self):
|
||||
# prompt_toolkit internally checks if it's on windows during output rendering but
|
||||
# we need to force that we use Vt100_Output not Win32_Output
|
||||
from prompt_toolkit import renderer
|
||||
|
||||
renderer.is_windows = lambda: False
|
||||
|
||||
def get_size():
|
||||
@@ -218,7 +219,8 @@ class ShellConnection(TelnetConnection):
|
||||
application=create_prompt_application(self._shell.prompt),
|
||||
eventloop=UnstoppableEventLoop(create_asyncio_eventloop(self._loop)),
|
||||
input=PatchedStdinInput(sys.stdin),
|
||||
output=Vt100_Output(self, get_size))
|
||||
output=Vt100_Output(self, get_size),
|
||||
)
|
||||
|
||||
self._cb = self._cli.create_eventloop_callbacks()
|
||||
self._inputstream = InputStream(self._cb.feed_key)
|
||||
@@ -302,19 +304,21 @@ def create_stdin_shell(shell, loop=None):
|
||||
:param loop: The event loop
|
||||
:returns: Telnet server
|
||||
"""
|
||||
|
||||
async def feed_stdin(loop, reader, shell):
|
||||
history = InMemoryHistory()
|
||||
completer = WordCompleter([name for name, _ in shell.get_commands()], ignore_case=True)
|
||||
while True:
|
||||
line = await prompt(
|
||||
">", patch_stdout=True, return_asyncio_coroutine=True, history=history, completer=completer)
|
||||
line += '\n'
|
||||
">", patch_stdout=True, return_asyncio_coroutine=True, history=history, completer=completer
|
||||
)
|
||||
line += "\n"
|
||||
reader.feed_data(line.encode())
|
||||
|
||||
async def read_stdout(writer):
|
||||
while True:
|
||||
c = await writer.read(1)
|
||||
print(c.decode(), end='')
|
||||
print(c.decode(), end="")
|
||||
sys.stdout.flush()
|
||||
|
||||
reader = asyncio.StreamReader()
|
||||
@@ -329,30 +333,31 @@ def create_stdin_shell(shell, loop=None):
|
||||
shell_task = loop.create_task(shell.run())
|
||||
return asyncio.gather(shell_task, writer_task, reader_task)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
class Demo(EmbedShell):
|
||||
|
||||
async def hello(self, *args):
|
||||
"""
|
||||
Hello world
|
||||
|
||||
This command accept arguments: hello tutu will display tutu
|
||||
"""
|
||||
|
||||
async def world():
|
||||
await asyncio.sleep(2)
|
||||
if len(args):
|
||||
return ' '.join(args)
|
||||
return " ".join(args)
|
||||
else:
|
||||
return 'world\n'
|
||||
return "world\n"
|
||||
|
||||
return await world()
|
||||
|
||||
# Demo using telnet
|
||||
shell = Demo(welcome_message="Welcome!\n")
|
||||
server = create_telnet_shell(shell, loop=loop)
|
||||
coro = asyncio.start_server(server.run, '127.0.0.1', 4444, loop=loop)
|
||||
coro = asyncio.start_server(server.run, "127.0.0.1", 4444, loop=loop)
|
||||
s = loop.run_until_complete(coro)
|
||||
try:
|
||||
loop.run_forever()
|
||||
|
||||
@@ -19,160 +19,150 @@ from prompt_toolkit.key_binding.input_processor import KeyPress
|
||||
# )
|
||||
|
||||
_DEBUG_RENDERER_INPUT = False
|
||||
_DEBUG_RENDERER_INPUT_FILENAME = 'prompt-toolkit-render-input.log'
|
||||
_DEBUG_RENDERER_INPUT_FILENAME = "prompt-toolkit-render-input.log"
|
||||
|
||||
|
||||
# Regex matching any CPR response
|
||||
# (Note that we use '\Z' instead of '$', because '$' could include a trailing
|
||||
# newline.)
|
||||
_cpr_response_re = re.compile('^' + re.escape('\x1b[') + r'\d+;\d+R\Z')
|
||||
_cpr_response_re = re.compile("^" + re.escape("\x1b[") + r"\d+;\d+R\Z")
|
||||
|
||||
# Mouse events:
|
||||
# Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M"
|
||||
_mouse_event_re = re.compile('^' + re.escape('\x1b[') + r'(<?[\d;]+[mM]|M...)\Z')
|
||||
_mouse_event_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]+[mM]|M...)\Z")
|
||||
|
||||
# Regex matching any valid prefix of a CPR response.
|
||||
# (Note that it doesn't contain the last character, the 'R'. The prefix has to
|
||||
# be shorter.)
|
||||
_cpr_response_prefix_re = re.compile('^' + re.escape('\x1b[') + r'[\d;]*\Z')
|
||||
_cpr_response_prefix_re = re.compile("^" + re.escape("\x1b[") + r"[\d;]*\Z")
|
||||
|
||||
_mouse_event_prefix_re = re.compile('^' + re.escape('\x1b[') + r'(<?[\d;]*|M.{0,2})\Z')
|
||||
_mouse_event_prefix_re = re.compile("^" + re.escape("\x1b[") + r"(<?[\d;]*|M.{0,2})\Z")
|
||||
|
||||
|
||||
class _Flush:
|
||||
""" Helper object to indicate flush operation to the parser. """
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# Mapping of vt100 escape codes to Keys.
|
||||
ANSI_SEQUENCES = {
|
||||
'\x1b': Keys.Escape,
|
||||
|
||||
'\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@)
|
||||
'\x01': Keys.ControlA, # Control-A (home)
|
||||
'\x02': Keys.ControlB, # Control-B (emacs cursor left)
|
||||
'\x03': Keys.ControlC, # Control-C (interrupt)
|
||||
'\x04': Keys.ControlD, # Control-D (exit)
|
||||
'\x05': Keys.ControlE, # Contrel-E (end)
|
||||
'\x06': Keys.ControlF, # Control-F (cursor forward)
|
||||
'\x07': Keys.ControlG, # Control-G
|
||||
'\x08': Keys.ControlH, # Control-H (8) (Identical to '\b')
|
||||
'\x09': Keys.ControlI, # Control-I (9) (Identical to '\t')
|
||||
'\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n')
|
||||
'\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab)
|
||||
'\x0c': Keys.ControlL, # Control-L (clear; form feed)
|
||||
'\x0d': Keys.ControlM, # Control-M (13) (Identical to '\r')
|
||||
'\x0e': Keys.ControlN, # Control-N (14) (history forward)
|
||||
'\x0f': Keys.ControlO, # Control-O (15)
|
||||
'\x10': Keys.ControlP, # Control-P (16) (history back)
|
||||
'\x11': Keys.ControlQ, # Control-Q
|
||||
'\x12': Keys.ControlR, # Control-R (18) (reverse search)
|
||||
'\x13': Keys.ControlS, # Control-S (19) (forward search)
|
||||
'\x14': Keys.ControlT, # Control-T
|
||||
'\x15': Keys.ControlU, # Control-U
|
||||
'\x16': Keys.ControlV, # Control-V
|
||||
'\x17': Keys.ControlW, # Control-W
|
||||
'\x18': Keys.ControlX, # Control-X
|
||||
'\x19': Keys.ControlY, # Control-Y (25)
|
||||
'\x1a': Keys.ControlZ, # Control-Z
|
||||
|
||||
'\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-|
|
||||
'\x1d': Keys.ControlSquareClose, # Control-]
|
||||
'\x1e': Keys.ControlCircumflex, # Control-^
|
||||
'\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.)
|
||||
'\x7f': Keys.Backspace, # (127) Backspace
|
||||
'\x1b[A': Keys.Up,
|
||||
'\x1b[B': Keys.Down,
|
||||
'\x1b[C': Keys.Right,
|
||||
'\x1b[D': Keys.Left,
|
||||
'\x1b[H': Keys.Home,
|
||||
'\x1bOH': Keys.Home,
|
||||
'\x1b[F': Keys.End,
|
||||
'\x1bOF': Keys.End,
|
||||
'\x1b[3~': Keys.Delete,
|
||||
'\x1b[3;2~': Keys.ShiftDelete, # xterm, gnome-terminal.
|
||||
'\x1b[3;5~': Keys.ControlDelete, # xterm, gnome-terminal.
|
||||
'\x1b[1~': Keys.Home, # tmux
|
||||
'\x1b[4~': Keys.End, # tmux
|
||||
'\x1b[5~': Keys.PageUp,
|
||||
'\x1b[6~': Keys.PageDown,
|
||||
'\x1b[7~': Keys.Home, # xrvt
|
||||
'\x1b[8~': Keys.End, # xrvt
|
||||
'\x1b[Z': Keys.BackTab, # shift + tab
|
||||
'\x1b[2~': Keys.Insert,
|
||||
|
||||
'\x1bOP': Keys.F1,
|
||||
'\x1bOQ': Keys.F2,
|
||||
'\x1bOR': Keys.F3,
|
||||
'\x1bOS': Keys.F4,
|
||||
'\x1b[[A': Keys.F1, # Linux console.
|
||||
'\x1b[[B': Keys.F2, # Linux console.
|
||||
'\x1b[[C': Keys.F3, # Linux console.
|
||||
'\x1b[[D': Keys.F4, # Linux console.
|
||||
'\x1b[[E': Keys.F5, # Linux console.
|
||||
'\x1b[11~': Keys.F1, # rxvt-unicode
|
||||
'\x1b[12~': Keys.F2, # rxvt-unicode
|
||||
'\x1b[13~': Keys.F3, # rxvt-unicode
|
||||
'\x1b[14~': Keys.F4, # rxvt-unicode
|
||||
'\x1b[15~': Keys.F5,
|
||||
'\x1b[17~': Keys.F6,
|
||||
'\x1b[18~': Keys.F7,
|
||||
'\x1b[19~': Keys.F8,
|
||||
'\x1b[20~': Keys.F9,
|
||||
'\x1b[21~': Keys.F10,
|
||||
'\x1b[23~': Keys.F11,
|
||||
'\x1b[24~': Keys.F12,
|
||||
'\x1b[25~': Keys.F13,
|
||||
'\x1b[26~': Keys.F14,
|
||||
'\x1b[28~': Keys.F15,
|
||||
'\x1b[29~': Keys.F16,
|
||||
'\x1b[31~': Keys.F17,
|
||||
'\x1b[32~': Keys.F18,
|
||||
'\x1b[33~': Keys.F19,
|
||||
'\x1b[34~': Keys.F20,
|
||||
|
||||
"\x1b": Keys.Escape,
|
||||
"\x00": Keys.ControlSpace, # Control-Space (Also for Ctrl-@)
|
||||
"\x01": Keys.ControlA, # Control-A (home)
|
||||
"\x02": Keys.ControlB, # Control-B (emacs cursor left)
|
||||
"\x03": Keys.ControlC, # Control-C (interrupt)
|
||||
"\x04": Keys.ControlD, # Control-D (exit)
|
||||
"\x05": Keys.ControlE, # Contrel-E (end)
|
||||
"\x06": Keys.ControlF, # Control-F (cursor forward)
|
||||
"\x07": Keys.ControlG, # Control-G
|
||||
"\x08": Keys.ControlH, # Control-H (8) (Identical to '\b')
|
||||
"\x09": Keys.ControlI, # Control-I (9) (Identical to '\t')
|
||||
"\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n')
|
||||
"\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab)
|
||||
"\x0c": Keys.ControlL, # Control-L (clear; form feed)
|
||||
"\x0d": Keys.ControlM, # Control-M (13) (Identical to '\r')
|
||||
"\x0e": Keys.ControlN, # Control-N (14) (history forward)
|
||||
"\x0f": Keys.ControlO, # Control-O (15)
|
||||
"\x10": Keys.ControlP, # Control-P (16) (history back)
|
||||
"\x11": Keys.ControlQ, # Control-Q
|
||||
"\x12": Keys.ControlR, # Control-R (18) (reverse search)
|
||||
"\x13": Keys.ControlS, # Control-S (19) (forward search)
|
||||
"\x14": Keys.ControlT, # Control-T
|
||||
"\x15": Keys.ControlU, # Control-U
|
||||
"\x16": Keys.ControlV, # Control-V
|
||||
"\x17": Keys.ControlW, # Control-W
|
||||
"\x18": Keys.ControlX, # Control-X
|
||||
"\x19": Keys.ControlY, # Control-Y (25)
|
||||
"\x1a": Keys.ControlZ, # Control-Z
|
||||
"\x1c": Keys.ControlBackslash, # Both Control-\ and Ctrl-|
|
||||
"\x1d": Keys.ControlSquareClose, # Control-]
|
||||
"\x1e": Keys.ControlCircumflex, # Control-^
|
||||
"\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.)
|
||||
"\x7f": Keys.Backspace, # (127) Backspace
|
||||
"\x1b[A": Keys.Up,
|
||||
"\x1b[B": Keys.Down,
|
||||
"\x1b[C": Keys.Right,
|
||||
"\x1b[D": Keys.Left,
|
||||
"\x1b[H": Keys.Home,
|
||||
"\x1bOH": Keys.Home,
|
||||
"\x1b[F": Keys.End,
|
||||
"\x1bOF": Keys.End,
|
||||
"\x1b[3~": Keys.Delete,
|
||||
"\x1b[3;2~": Keys.ShiftDelete, # xterm, gnome-terminal.
|
||||
"\x1b[3;5~": Keys.ControlDelete, # xterm, gnome-terminal.
|
||||
"\x1b[1~": Keys.Home, # tmux
|
||||
"\x1b[4~": Keys.End, # tmux
|
||||
"\x1b[5~": Keys.PageUp,
|
||||
"\x1b[6~": Keys.PageDown,
|
||||
"\x1b[7~": Keys.Home, # xrvt
|
||||
"\x1b[8~": Keys.End, # xrvt
|
||||
"\x1b[Z": Keys.BackTab, # shift + tab
|
||||
"\x1b[2~": Keys.Insert,
|
||||
"\x1bOP": Keys.F1,
|
||||
"\x1bOQ": Keys.F2,
|
||||
"\x1bOR": Keys.F3,
|
||||
"\x1bOS": Keys.F4,
|
||||
"\x1b[[A": Keys.F1, # Linux console.
|
||||
"\x1b[[B": Keys.F2, # Linux console.
|
||||
"\x1b[[C": Keys.F3, # Linux console.
|
||||
"\x1b[[D": Keys.F4, # Linux console.
|
||||
"\x1b[[E": Keys.F5, # Linux console.
|
||||
"\x1b[11~": Keys.F1, # rxvt-unicode
|
||||
"\x1b[12~": Keys.F2, # rxvt-unicode
|
||||
"\x1b[13~": Keys.F3, # rxvt-unicode
|
||||
"\x1b[14~": Keys.F4, # rxvt-unicode
|
||||
"\x1b[15~": Keys.F5,
|
||||
"\x1b[17~": Keys.F6,
|
||||
"\x1b[18~": Keys.F7,
|
||||
"\x1b[19~": Keys.F8,
|
||||
"\x1b[20~": Keys.F9,
|
||||
"\x1b[21~": Keys.F10,
|
||||
"\x1b[23~": Keys.F11,
|
||||
"\x1b[24~": Keys.F12,
|
||||
"\x1b[25~": Keys.F13,
|
||||
"\x1b[26~": Keys.F14,
|
||||
"\x1b[28~": Keys.F15,
|
||||
"\x1b[29~": Keys.F16,
|
||||
"\x1b[31~": Keys.F17,
|
||||
"\x1b[32~": Keys.F18,
|
||||
"\x1b[33~": Keys.F19,
|
||||
"\x1b[34~": Keys.F20,
|
||||
# Xterm
|
||||
'\x1b[1;2P': Keys.F13,
|
||||
'\x1b[1;2Q': Keys.F14,
|
||||
"\x1b[1;2P": Keys.F13,
|
||||
"\x1b[1;2Q": Keys.F14,
|
||||
# '\x1b[1;2R': Keys.F15, # Conflicts with CPR response.
|
||||
'\x1b[1;2S': Keys.F16,
|
||||
'\x1b[15;2~': Keys.F17,
|
||||
'\x1b[17;2~': Keys.F18,
|
||||
'\x1b[18;2~': Keys.F19,
|
||||
'\x1b[19;2~': Keys.F20,
|
||||
'\x1b[20;2~': Keys.F21,
|
||||
'\x1b[21;2~': Keys.F22,
|
||||
'\x1b[23;2~': Keys.F23,
|
||||
'\x1b[24;2~': Keys.F24,
|
||||
|
||||
'\x1b[1;5A': Keys.ControlUp, # Cursor Mode
|
||||
'\x1b[1;5B': Keys.ControlDown, # Cursor Mode
|
||||
'\x1b[1;5C': Keys.ControlRight, # Cursor Mode
|
||||
'\x1b[1;5D': Keys.ControlLeft, # Cursor Mode
|
||||
|
||||
'\x1b[1;2A': Keys.ShiftUp,
|
||||
'\x1b[1;2B': Keys.ShiftDown,
|
||||
'\x1b[1;2C': Keys.ShiftRight,
|
||||
'\x1b[1;2D': Keys.ShiftLeft,
|
||||
|
||||
"\x1b[1;2S": Keys.F16,
|
||||
"\x1b[15;2~": Keys.F17,
|
||||
"\x1b[17;2~": Keys.F18,
|
||||
"\x1b[18;2~": Keys.F19,
|
||||
"\x1b[19;2~": Keys.F20,
|
||||
"\x1b[20;2~": Keys.F21,
|
||||
"\x1b[21;2~": Keys.F22,
|
||||
"\x1b[23;2~": Keys.F23,
|
||||
"\x1b[24;2~": Keys.F24,
|
||||
"\x1b[1;5A": Keys.ControlUp, # Cursor Mode
|
||||
"\x1b[1;5B": Keys.ControlDown, # Cursor Mode
|
||||
"\x1b[1;5C": Keys.ControlRight, # Cursor Mode
|
||||
"\x1b[1;5D": Keys.ControlLeft, # Cursor Mode
|
||||
"\x1b[1;2A": Keys.ShiftUp,
|
||||
"\x1b[1;2B": Keys.ShiftDown,
|
||||
"\x1b[1;2C": Keys.ShiftRight,
|
||||
"\x1b[1;2D": Keys.ShiftLeft,
|
||||
# Tmux sends following keystrokes when control+arrow is pressed, but for
|
||||
# Emacs ansi-term sends the same sequences for normal arrow keys. Consider
|
||||
# it a normal arrow press, because that's more important.
|
||||
'\x1bOA': Keys.Up,
|
||||
'\x1bOB': Keys.Down,
|
||||
'\x1bOC': Keys.Right,
|
||||
'\x1bOD': Keys.Left,
|
||||
|
||||
'\x1b[5A': Keys.ControlUp,
|
||||
'\x1b[5B': Keys.ControlDown,
|
||||
'\x1b[5C': Keys.ControlRight,
|
||||
'\x1b[5D': Keys.ControlLeft,
|
||||
|
||||
'\x1bOc': Keys.ControlRight, # rxvt
|
||||
'\x1bOd': Keys.ControlLeft, # rxvt
|
||||
|
||||
'\x1b[200~': Keys.BracketedPaste, # Start of bracketed paste.
|
||||
|
||||
"\x1bOA": Keys.Up,
|
||||
"\x1bOB": Keys.Down,
|
||||
"\x1bOC": Keys.Right,
|
||||
"\x1bOD": Keys.Left,
|
||||
"\x1b[5A": Keys.ControlUp,
|
||||
"\x1b[5B": Keys.ControlDown,
|
||||
"\x1b[5C": Keys.ControlRight,
|
||||
"\x1b[5D": Keys.ControlLeft,
|
||||
"\x1bOc": Keys.ControlRight, # rxvt
|
||||
"\x1bOd": Keys.ControlLeft, # rxvt
|
||||
"\x1b[200~": Keys.BracketedPaste, # Start of bracketed paste.
|
||||
# Meta + arrow keys. Several terminals handle this differently.
|
||||
# The following sequences are for xterm and gnome-terminal.
|
||||
# (Iterm sends ESC followed by the normal arrow_up/down/left/right
|
||||
@@ -182,15 +172,14 @@ ANSI_SEQUENCES = {
|
||||
# pressing ESC (to go to Vi navigation mode), followed by just the
|
||||
# 'b' or 'f' key. These combinations are handled in
|
||||
# the input processor.)
|
||||
'\x1b[1;3D': (Keys.Escape, Keys.Left),
|
||||
'\x1b[1;3C': (Keys.Escape, Keys.Right),
|
||||
'\x1b[1;3A': (Keys.Escape, Keys.Up),
|
||||
'\x1b[1;3B': (Keys.Escape, Keys.Down),
|
||||
|
||||
"\x1b[1;3D": (Keys.Escape, Keys.Left),
|
||||
"\x1b[1;3C": (Keys.Escape, Keys.Right),
|
||||
"\x1b[1;3A": (Keys.Escape, Keys.Up),
|
||||
"\x1b[1;3B": (Keys.Escape, Keys.Down),
|
||||
# Sequences generated by numpad 5. Not sure what it means. (It doesn't
|
||||
# appear in 'infocmp'. Just ignore.
|
||||
'\x1b[E': Keys.Ignore, # Xterm.
|
||||
'\x1b[G': Keys.Ignore, # Linux console.
|
||||
"\x1b[E": Keys.Ignore, # Xterm.
|
||||
"\x1b[G": Keys.Ignore, # Linux console.
|
||||
}
|
||||
|
||||
|
||||
@@ -199,10 +188,11 @@ class _IsPrefixOfLongerMatchCache(dict):
|
||||
Dictiory that maps input sequences to a boolean indicating whether there is
|
||||
any key that start with this characters.
|
||||
"""
|
||||
|
||||
def __missing__(self, prefix):
|
||||
# (hard coded) If this could be a prefix of a CPR response, return
|
||||
# True.
|
||||
if (_cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match(prefix)):
|
||||
if _cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match(prefix):
|
||||
result = True
|
||||
else:
|
||||
# If this could be a prefix of anything else, also return True.
|
||||
@@ -231,6 +221,7 @@ class InputStream:
|
||||
|
||||
:attr input_processor: :class:`~prompt_toolkit.key_binding.InputProcessor` instance.
|
||||
"""
|
||||
|
||||
# Lookup table of ANSI escape sequences for a VT100 terminal
|
||||
# Hint: in order to know what sequences your terminal writes to stdin, run
|
||||
# "od -c" and start typing.
|
||||
@@ -241,7 +232,7 @@ class InputStream:
|
||||
self.reset()
|
||||
|
||||
if _DEBUG_RENDERER_INPUT:
|
||||
self.LOG = open(_DEBUG_RENDERER_INPUT_FILENAME, 'ab')
|
||||
self.LOG = open(_DEBUG_RENDERER_INPUT_FILENAME, "ab")
|
||||
|
||||
def reset(self, request=False):
|
||||
self._in_bracketed_paste = False
|
||||
@@ -277,7 +268,7 @@ class InputStream:
|
||||
"""
|
||||
Coroutine (state machine) for the input parser.
|
||||
"""
|
||||
prefix = ''
|
||||
prefix = ""
|
||||
retry = False
|
||||
flush = False
|
||||
|
||||
@@ -303,7 +294,7 @@ class InputStream:
|
||||
# Exact matches found, call handlers..
|
||||
if (flush or not is_prefix_of_longer_match) and match:
|
||||
self._call_handler(match, prefix)
|
||||
prefix = ''
|
||||
prefix = ""
|
||||
|
||||
# No exact match found.
|
||||
elif (flush or not is_prefix_of_longer_match) and not match:
|
||||
@@ -313,7 +304,7 @@ class InputStream:
|
||||
# Loop over the input, try the longest match first and
|
||||
# shift.
|
||||
for i in range(len(prefix), 0, -1):
|
||||
match= self._get_match(prefix[:i])
|
||||
match = self._get_match(prefix[:i])
|
||||
if match:
|
||||
self._call_handler(match, prefix[:i])
|
||||
prefix = prefix[i:]
|
||||
@@ -333,7 +324,7 @@ class InputStream:
|
||||
else:
|
||||
if key == Keys.BracketedPaste:
|
||||
self._in_bracketed_paste = True
|
||||
self._paste_buffer = ''
|
||||
self._paste_buffer = ""
|
||||
else:
|
||||
self.feed_key_callback(KeyPress(key, insert_text))
|
||||
|
||||
@@ -346,7 +337,7 @@ class InputStream:
|
||||
assert isinstance(data, str)
|
||||
|
||||
if _DEBUG_RENDERER_INPUT:
|
||||
self.LOG.write(repr(data).encode('utf-8') + b'\n')
|
||||
self.LOG.write(repr(data).encode("utf-8") + b"\n")
|
||||
self.LOG.flush()
|
||||
|
||||
# Handle bracketed paste. (We bypass the parser that matches all other
|
||||
@@ -354,7 +345,7 @@ class InputStream:
|
||||
# This is much faster then parsing character by character.
|
||||
if self._in_bracketed_paste:
|
||||
self._paste_buffer += data
|
||||
end_mark = '\x1b[201~'
|
||||
end_mark = "\x1b[201~"
|
||||
|
||||
if end_mark in self._paste_buffer:
|
||||
end_index = self._paste_buffer.index(end_mark)
|
||||
@@ -365,8 +356,8 @@ class InputStream:
|
||||
|
||||
# Quit bracketed paste mode and handle remaining input.
|
||||
self._in_bracketed_paste = False
|
||||
remaining = self._paste_buffer[end_index + len(end_mark):]
|
||||
self._paste_buffer = ''
|
||||
remaining = self._paste_buffer[end_index + len(end_mark) :]
|
||||
self._paste_buffer = ""
|
||||
|
||||
self.feed(remaining)
|
||||
|
||||
@@ -391,8 +382,8 @@ class InputStream:
|
||||
|
||||
# When this is removed, replace Enter=ControlJ by
|
||||
# Enter=ControlM in keys.py.
|
||||
if c == '\r':
|
||||
c = '\n'
|
||||
if c == "\r":
|
||||
c = "\n"
|
||||
self._input_parser.send(c)
|
||||
|
||||
def flush(self):
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import asyncio
|
||||
|
||||
|
||||
class Pool():
|
||||
class Pool:
|
||||
"""
|
||||
Limit concurrency for running parallel tasks
|
||||
"""
|
||||
@@ -60,5 +60,5 @@ def main():
|
||||
loop.run_until_complete(pool.join())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -20,6 +20,7 @@ import asyncio
|
||||
import asyncio.subprocess
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
READ_SIZE = 4096
|
||||
@@ -43,10 +44,12 @@ class AsyncioRawCommandServer:
|
||||
|
||||
async def run(self, network_reader, network_writer):
|
||||
await self._lock.acquire()
|
||||
process = await asyncio.subprocess.create_subprocess_exec(*self._command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE)
|
||||
process = await asyncio.subprocess.create_subprocess_exec(
|
||||
*self._command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE
|
||||
)
|
||||
try:
|
||||
await self._process(network_reader, network_writer, process.stdout, process.stdin)
|
||||
except ConnectionResetError:
|
||||
@@ -61,10 +64,20 @@ class AsyncioRawCommandServer:
|
||||
# Server host from the client point of view
|
||||
host = network_writer.transport.get_extra_info("sockname")[0]
|
||||
for replace in self._replaces:
|
||||
if b'{{HOST}}' in replace[1]:
|
||||
replaces.append((replace[0], replace[1].replace(b'{{HOST}}', host.encode()), ))
|
||||
if b"{{HOST}}" in replace[1]:
|
||||
replaces.append(
|
||||
(
|
||||
replace[0],
|
||||
replace[1].replace(b"{{HOST}}", host.encode()),
|
||||
)
|
||||
)
|
||||
else:
|
||||
replaces.append((replace[0], replace[1], ))
|
||||
replaces.append(
|
||||
(
|
||||
replace[0],
|
||||
replace[1],
|
||||
)
|
||||
)
|
||||
|
||||
network_read = asyncio.ensure_future(network_reader.read(READ_SIZE))
|
||||
reader_read = asyncio.ensure_future(process_reader.read(READ_SIZE))
|
||||
@@ -72,12 +85,8 @@ class AsyncioRawCommandServer:
|
||||
|
||||
while True:
|
||||
done, pending = await asyncio.wait(
|
||||
[
|
||||
network_read,
|
||||
reader_read
|
||||
],
|
||||
timeout=timeout,
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
[network_read, reader_read], timeout=timeout, return_when=asyncio.FIRST_COMPLETED
|
||||
)
|
||||
if len(done) == 0:
|
||||
raise ConnectionResetError()
|
||||
for coro in done:
|
||||
@@ -104,13 +113,21 @@ class AsyncioRawCommandServer:
|
||||
await network_writer.drain()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
command = ["nc", "localhost", "80"]
|
||||
server = AsyncioRawCommandServer(command, replaces=[(b"work", b"{{HOST}}", )])
|
||||
coro = asyncio.start_server(server.run, '0.0.0.0', 4444, loop=loop)
|
||||
server = AsyncioRawCommandServer(
|
||||
command,
|
||||
replaces=[
|
||||
(
|
||||
b"work",
|
||||
b"{{HOST}}",
|
||||
)
|
||||
],
|
||||
)
|
||||
coro = asyncio.start_server(server.run, "0.0.0.0", 4444, loop=loop)
|
||||
s = loop.run_until_complete(coro)
|
||||
|
||||
try:
|
||||
|
||||
@@ -31,7 +31,6 @@ if sys.platform.startswith("win"):
|
||||
|
||||
|
||||
class SerialReaderWriterProtocol(asyncio.Protocol):
|
||||
|
||||
def __init__(self):
|
||||
self._output = asyncio.StreamReader()
|
||||
self._closed = False
|
||||
@@ -135,6 +134,6 @@ async def asyncio_open_serial(path):
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
return (await _asyncio_open_serial_windows(path))
|
||||
return await _asyncio_open_serial_windows(path)
|
||||
else:
|
||||
return (await _asyncio_open_serial_unix(path))
|
||||
return await _asyncio_open_serial_unix(path)
|
||||
@@ -20,45 +20,47 @@ import asyncio.subprocess
|
||||
import struct
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Mostly from https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py
|
||||
|
||||
# Telnet Commands
|
||||
SE = 240 # End of sub-negotiation parameters
|
||||
NOP = 241 # No operation
|
||||
DATMK = 242 # Data stream portion of a sync.
|
||||
BREAK = 243 # NVT Character BRK
|
||||
IP = 244 # Interrupt Process
|
||||
AO = 245 # Abort Output
|
||||
AYT = 246 # Are you there
|
||||
EC = 247 # Erase Character
|
||||
EL = 248 # Erase Line
|
||||
GA = 249 # The Go Ahead Signal
|
||||
SB = 250 # Sub-option to follow
|
||||
WILL = 251 # Will; request or confirm option begin
|
||||
WONT = 252 # Wont; deny option request
|
||||
DO = 253 # Do = Request or confirm remote option
|
||||
DONT = 254 # Don't = Demand or confirm option halt
|
||||
IAC = 255 # Interpret as Command
|
||||
SEND = 1 # Sub-process negotiation SEND command
|
||||
IS = 0 # Sub-process negotiation IS command
|
||||
SE = 240 # End of sub-negotiation parameters
|
||||
NOP = 241 # No operation
|
||||
DATMK = 242 # Data stream portion of a sync.
|
||||
BREAK = 243 # NVT Character BRK
|
||||
IP = 244 # Interrupt Process
|
||||
AO = 245 # Abort Output
|
||||
AYT = 246 # Are you there
|
||||
EC = 247 # Erase Character
|
||||
EL = 248 # Erase Line
|
||||
GA = 249 # The Go Ahead Signal
|
||||
SB = 250 # Sub-option to follow
|
||||
WILL = 251 # Will; request or confirm option begin
|
||||
WONT = 252 # Wont; deny option request
|
||||
DO = 253 # Do = Request or confirm remote option
|
||||
DONT = 254 # Don't = Demand or confirm option halt
|
||||
IAC = 255 # Interpret as Command
|
||||
SEND = 1 # Sub-process negotiation SEND command
|
||||
IS = 0 # Sub-process negotiation IS command
|
||||
|
||||
# Telnet Options
|
||||
BINARY = 0 # Transmit Binary
|
||||
ECHO = 1 # Echo characters back to sender
|
||||
RECON = 2 # Reconnection
|
||||
SGA = 3 # Suppress Go-Ahead
|
||||
TMARK = 6 # Timing Mark
|
||||
TTYPE = 24 # Terminal Type
|
||||
NAWS = 31 # Negotiate About Window Size
|
||||
LINEMO = 34 # Line Mode
|
||||
BINARY = 0 # Transmit Binary
|
||||
ECHO = 1 # Echo characters back to sender
|
||||
RECON = 2 # Reconnection
|
||||
SGA = 3 # Suppress Go-Ahead
|
||||
TMARK = 6 # Timing Mark
|
||||
TTYPE = 24 # Terminal Type
|
||||
NAWS = 31 # Negotiate About Window Size
|
||||
LINEMO = 34 # Line Mode
|
||||
|
||||
READ_SIZE = 1024
|
||||
|
||||
|
||||
class TelnetConnection:
|
||||
"""Default implementation of telnet connection which may but may not be used."""
|
||||
|
||||
def __init__(self, reader, writer, window_size_changed_callback=None):
|
||||
self.is_closing = False
|
||||
self._reader = reader
|
||||
@@ -83,7 +85,7 @@ class TelnetConnection:
|
||||
|
||||
async def window_size_changed(self, columns, rows):
|
||||
"""Method called when window size changed, only can occur when
|
||||
`naws` flag is enable in server configuration."""
|
||||
`naws` flag is enable in server configuration."""
|
||||
|
||||
if self._window_size_changed_callback:
|
||||
await self._window_size_changed_callback(columns, rows)
|
||||
@@ -113,14 +115,24 @@ class TelnetConnection:
|
||||
class AsyncioTelnetServer:
|
||||
MAX_NEGOTIATION_READ = 10
|
||||
|
||||
def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None):
|
||||
def __init__(
|
||||
self,
|
||||
reader=None,
|
||||
writer=None,
|
||||
binary=True,
|
||||
echo=False,
|
||||
naws=False,
|
||||
window_size_changed_callback=None,
|
||||
connection_factory=None,
|
||||
):
|
||||
"""
|
||||
Initializes telnet server
|
||||
:param naws when True make a window size negotiation
|
||||
:param connection_factory: when set it's possible to inject own implementation of connection
|
||||
"""
|
||||
assert connection_factory is None or (connection_factory is not None and reader is None and writer is None), \
|
||||
"Please use either reader and writer either connection_factory, otherwise duplicate data may be produced."
|
||||
assert connection_factory is None or (
|
||||
connection_factory is not None and reader is None and writer is None
|
||||
), "Please use either reader and writer either connection_factory, otherwise duplicate data may be produced."
|
||||
|
||||
self._reader = reader
|
||||
self._writer = writer
|
||||
@@ -151,9 +163,7 @@ class AsyncioTelnetServer:
|
||||
if echo:
|
||||
writer.write(bytes([IAC, WILL, ECHO]))
|
||||
else:
|
||||
writer.write(bytes([
|
||||
IAC, WONT, ECHO,
|
||||
IAC, DONT, ECHO]))
|
||||
writer.write(bytes([IAC, WONT, ECHO, IAC, DONT, ECHO]))
|
||||
await writer.drain()
|
||||
|
||||
async def _write_intro(self, writer, binary=False, echo=False, naws=False):
|
||||
@@ -161,26 +171,15 @@ class AsyncioTelnetServer:
|
||||
if echo:
|
||||
writer.write(bytes([IAC, WILL, ECHO]))
|
||||
else:
|
||||
writer.write(bytes([
|
||||
IAC, WONT, ECHO,
|
||||
IAC, DONT, ECHO]))
|
||||
writer.write(bytes([IAC, WONT, ECHO, IAC, DONT, ECHO]))
|
||||
|
||||
if binary:
|
||||
writer.write(bytes([
|
||||
IAC, WILL, SGA,
|
||||
IAC, WILL, BINARY,
|
||||
IAC, DO, BINARY]))
|
||||
writer.write(bytes([IAC, WILL, SGA, IAC, WILL, BINARY, IAC, DO, BINARY]))
|
||||
else:
|
||||
writer.write(bytes([
|
||||
IAC, WONT, SGA,
|
||||
IAC, DONT, SGA,
|
||||
IAC, WONT, BINARY,
|
||||
IAC, DONT, BINARY]))
|
||||
writer.write(bytes([IAC, WONT, SGA, IAC, DONT, SGA, IAC, WONT, BINARY, IAC, DONT, BINARY]))
|
||||
|
||||
if naws:
|
||||
writer.write(bytes([
|
||||
IAC, DO, NAWS
|
||||
]))
|
||||
writer.write(bytes([IAC, DO, NAWS]))
|
||||
await writer.drain()
|
||||
|
||||
async def run(self, network_reader, network_writer):
|
||||
@@ -188,7 +187,7 @@ class AsyncioTelnetServer:
|
||||
sock = network_writer.get_extra_info("socket")
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
#log.debug("New connection from {}".format(sock.getpeername()))
|
||||
# log.debug("New connection from {}".format(sock.getpeername()))
|
||||
|
||||
# Keep track of connected clients
|
||||
connection = self._connection_factory(network_reader, network_writer, self._window_size_changed_callback)
|
||||
@@ -247,14 +246,10 @@ class AsyncioTelnetServer:
|
||||
network_read,
|
||||
],
|
||||
timeout=1,
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
else:
|
||||
done, pending = await asyncio.wait(
|
||||
[
|
||||
network_read,
|
||||
reader_read
|
||||
],
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
done, pending = await asyncio.wait([network_read, reader_read], return_when=asyncio.FIRST_COMPLETED)
|
||||
for coro in done:
|
||||
data = coro.result()
|
||||
if coro == network_read:
|
||||
@@ -309,10 +304,10 @@ class AsyncioTelnetServer:
|
||||
command, payload = data[0], data[1:]
|
||||
if command == NAWS:
|
||||
if len(payload) == 4:
|
||||
columns, rows = struct.unpack('!HH', bytes(payload))
|
||||
columns, rows = struct.unpack("!HH", bytes(payload))
|
||||
await connection.window_size_changed(columns, rows)
|
||||
else:
|
||||
log.warning('Wrong number of NAWS bytes')
|
||||
log.warning("Wrong number of NAWS bytes")
|
||||
else:
|
||||
log.debug("Not supported negotiation sequence, received {} bytes", len(data))
|
||||
|
||||
@@ -324,7 +319,6 @@ class AsyncioTelnetServer:
|
||||
:returns: buffer minus Telnet commands
|
||||
"""
|
||||
|
||||
|
||||
skip_to = 0
|
||||
while True:
|
||||
# Locate an IAC to process
|
||||
@@ -345,7 +339,7 @@ class AsyncioTelnetServer:
|
||||
if iac_cmd[1] not in [WILL, WONT, DO, DONT, SB]:
|
||||
if iac_cmd[1] == AYT:
|
||||
log.debug("Telnet server received Are-You-There (AYT)")
|
||||
network_writer.write(b'\r\nYour Are-You-There received. I am here.\r\n')
|
||||
network_writer.write(b"\r\nYour Are-You-There received. I am here.\r\n")
|
||||
elif iac_cmd[1] == IAC:
|
||||
# It's data, not an IAC
|
||||
iac_cmd.pop()
|
||||
@@ -356,8 +350,7 @@ class AsyncioTelnetServer:
|
||||
elif iac_cmd[1] == NOP:
|
||||
pass
|
||||
else:
|
||||
log.debug("Unhandled telnet command: "
|
||||
"{:#x} {:#x}".format(*iac_cmd))
|
||||
log.debug("Unhandled telnet command: " "{:#x} {:#x}".format(*iac_cmd))
|
||||
elif iac_cmd[1] == SB: # starts negotiation commands
|
||||
negotiation = []
|
||||
for pos in range(2, self.MAX_NEGOTIATION_READ):
|
||||
@@ -392,38 +385,42 @@ class AsyncioTelnetServer:
|
||||
log.debug(f"Telnet WON'T {iac_cmd[2]:#x}")
|
||||
|
||||
elif iac_cmd[1] == DONT:
|
||||
log.debug("Unhandled DONT telnet command: "
|
||||
"{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
log.debug("Unhandled DONT telnet command: " "{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
elif iac_cmd[1] == WILL:
|
||||
if iac_cmd[2] not in [BINARY, NAWS]:
|
||||
log.debug("Unhandled WILL telnet command: "
|
||||
"{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
log.debug("Unhandled WILL telnet command: " "{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
elif iac_cmd[1] == WONT:
|
||||
log.debug("Unhandled WONT telnet command: "
|
||||
"{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
log.debug("Unhandled WONT telnet command: " "{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
else:
|
||||
log.debug("Unhandled telnet command: "
|
||||
"{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
log.debug("Unhandled telnet command: " "{:#x} {:#x} {:#x}".format(*iac_cmd))
|
||||
|
||||
# Remove the entire TELNET command from the buffer
|
||||
buf = buf.replace(iac_cmd, b'', 1)
|
||||
buf = buf.replace(iac_cmd, b"", 1)
|
||||
|
||||
await network_writer.drain()
|
||||
|
||||
# Return the new copy of the buffer, minus telnet commands
|
||||
return buf
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
process = loop.run_until_complete(asyncio.ensure_future(asyncio.subprocess.create_subprocess_exec("/bin/sh", "-i",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE)))
|
||||
process = loop.run_until_complete(
|
||||
asyncio.ensure_future(
|
||||
asyncio.subprocess.create_subprocess_exec(
|
||||
"/bin/sh",
|
||||
"-i",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
)
|
||||
)
|
||||
)
|
||||
server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=False, echo=False)
|
||||
|
||||
coro = asyncio.start_server(server.run, '127.0.0.1', 4444, loop=loop)
|
||||
coro = asyncio.start_server(server.run, "127.0.0.1", 4444, loop=loop)
|
||||
s = loop.run_until_complete(coro)
|
||||
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user