mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-11-08 12:18:42 +00:00
Stub of Python package for CLI parsing
This commit is contained in:
parent
ca0f239c72
commit
5e4ab092f3
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@
|
||||
/sysdep/autoconf.h.in~
|
||||
/cscope.*
|
||||
*.tar.gz
|
||||
__pycache__
|
||||
|
25
python/BIRD/Basic.py
Normal file
25
python/BIRD/Basic.py
Normal file
@ -0,0 +1,25 @@
|
||||
import asyncio
|
||||
|
||||
class BIRDException(Exception):
|
||||
pass
|
||||
|
||||
class Basic:
|
||||
def __init__(self, bird):
|
||||
self.bird = bird
|
||||
self.data = None
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.data is None:
|
||||
raise BIRDException(f"Call update() to get data")
|
||||
|
||||
if name not in self.data:
|
||||
raise BIRDException(f"Unknown key {name} in {type(self)}")
|
||||
|
||||
return self.data[name]
|
||||
|
||||
def __repr__(self):
|
||||
return f"{type(self).__name__}({self.data})"
|
||||
|
||||
async def load(self):
|
||||
if self.data is None:
|
||||
await self.update()
|
84
python/BIRD/Socket.py
Normal file
84
python/BIRD/Socket.py
Normal file
@ -0,0 +1,84 @@
|
||||
import asyncio
|
||||
|
||||
class SocketException(Exception):
|
||||
def __init__(self, socket, msg):
|
||||
Exception.__init__(self, f"Failed to {msg} BIRD Control Socket at {socket.path}")
|
||||
|
||||
class ReadException(Exception):
|
||||
def __init__(self, socket, line, msg):
|
||||
Exception.__init__(self, f"Invalid input on line {line}: {msg}")
|
||||
|
||||
class Socket:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
|
||||
async def open(self):
|
||||
assert(self.reader is None)
|
||||
assert(self.writer is None)
|
||||
|
||||
try:
|
||||
self.reader, self.writer = await asyncio.open_unix_connection(path=self.path)
|
||||
except Exception as e:
|
||||
raise SocketException(self, "connect to") from e
|
||||
|
||||
try:
|
||||
return await self.read_from_socket()
|
||||
except ReadException as e:
|
||||
raise SocketException(self, "read hello from") from e
|
||||
|
||||
async def close(self):
|
||||
assert(self.reader is not None)
|
||||
assert(self.writer is not None)
|
||||
|
||||
try:
|
||||
self.writer.close()
|
||||
await self.writer.wait_closed()
|
||||
except Exception as e:
|
||||
raise SocketException(self, "close") from e
|
||||
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
|
||||
async def read_from_socket(self):
|
||||
current_code = None
|
||||
lines = []
|
||||
|
||||
while True:
|
||||
line = (await self.reader.readline()).decode()
|
||||
|
||||
if len(line) == 0:
|
||||
raise ReadException(self, len(lines)+1, "Connection closed")
|
||||
|
||||
if line[-1] != "\n":
|
||||
raise ReadException(self, len(lines)+1, "Received partial data")
|
||||
|
||||
if line[0] == " ":
|
||||
if current_code is None:
|
||||
raise ReadException(self, len(lines)+1, "First line can't be unnumbered continuation")
|
||||
lines.append({"code": current_code, "data": line[1:-1]})
|
||||
|
||||
elif line[4] == "-" or line[4] == " ":
|
||||
try:
|
||||
current_code = int(line[:4])
|
||||
except ValueError as e:
|
||||
raise ReadException(self, len(lines)+1, f"Invalid line code: {line[:4]}") from e
|
||||
|
||||
lines.append({"code": current_code, "data": line[5:-1]})
|
||||
|
||||
if line[4] == " ":
|
||||
return lines
|
||||
|
||||
async def command(self, cmd):
|
||||
try:
|
||||
self.writer.write(f"{cmd}\n".encode())
|
||||
await self.writer.drain()
|
||||
except Exception as e:
|
||||
raise SocketException(self, f"write command {cmd} to") from e
|
||||
|
||||
try:
|
||||
return await self.read_from_socket()
|
||||
except Exception as e:
|
||||
raise SocketException(self, f"read response for command {cmd} from") from e
|
||||
|
48
python/BIRD/Status.py
Normal file
48
python/BIRD/Status.py
Normal file
@ -0,0 +1,48 @@
|
||||
import asyncio
|
||||
from BIRD.Basic import Basic
|
||||
|
||||
class StatusException(Exception):
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self, "Failed to parse status: " + msg)
|
||||
|
||||
class Status(Basic):
|
||||
async def update(self):
|
||||
self.data = {}
|
||||
|
||||
await self.bird.cli.open()
|
||||
data = await self.bird.cli.socket.command("show status")
|
||||
|
||||
if data[0]["code"] != 1000:
|
||||
raise StatusException(f"BIRD version not on the first line, got {data[0]['code']}")
|
||||
|
||||
self.data["version"] = data[0]["data"]
|
||||
|
||||
if data[-1]["code"] != 13:
|
||||
raise StatusException(f"BIRD status not on the last line, got {data[-1]['code']}")
|
||||
|
||||
self.data["status"] = data[-1]["data"]
|
||||
|
||||
# for d in data[1:-1]:
|
||||
|
||||
|
||||
|
||||
class VersionException(Exception):
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self, "Failed to parse version from socket hello: " + msg)
|
||||
|
||||
class Version(Basic):
|
||||
async def update(self):
|
||||
await self.bird.cli.open()
|
||||
hello = self.bird.cli.hello
|
||||
|
||||
if hello["code"] != 1:
|
||||
raise VersionException(f"code is {hello['code']}, should be 1")
|
||||
|
||||
s = hello["data"].split(" ")
|
||||
if len(s) != 3 or s[2] != "ready.":
|
||||
raise VersionException(f"malformed hello: {hello['data']}")
|
||||
|
||||
self.data = {
|
||||
"name": s[0],
|
||||
"version": s[1],
|
||||
}
|
48
python/BIRD/__init__.py
Normal file
48
python/BIRD/__init__.py
Normal file
@ -0,0 +1,48 @@
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from BIRD.Basic import BIRDException
|
||||
from BIRD.Socket import Socket
|
||||
from BIRD.Status import Status, Version
|
||||
|
||||
class CLI:
|
||||
def __init__(self, name):
|
||||
self.socket = Socket(name)
|
||||
self.connected = False
|
||||
self.hello = None
|
||||
|
||||
async def open(self):
|
||||
if self.hello is not None:
|
||||
return
|
||||
|
||||
h = await self.socket.open()
|
||||
if len(h) != 1:
|
||||
raise BIRDException("CLI hello should have 1 line, has {len(h)} lines: {h}")
|
||||
|
||||
self.hello = h[0]
|
||||
|
||||
async def close(self):
|
||||
if self.hello is None:
|
||||
return
|
||||
|
||||
await self.socket.close()
|
||||
self.hello = None
|
||||
|
||||
class BIRD:
|
||||
def __init__(self, socket=Path("bird.ctl")):
|
||||
self.cli = CLI(socket)
|
||||
self.version = Version(self)
|
||||
self.status = Status(self)
|
||||
|
||||
self.within = False
|
||||
|
||||
async def __aenter__(self):
|
||||
if self.within:
|
||||
raise BIRDException("Tried to enter BIRD context (async with) more than once")
|
||||
|
||||
self.within = True
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args):
|
||||
await self.cli.close()
|
||||
self.within = False
|
12
python/test.py
Normal file
12
python/test.py
Normal file
@ -0,0 +1,12 @@
|
||||
import asyncio
|
||||
from BIRD import BIRD
|
||||
|
||||
async def main():
|
||||
async with BIRD("/run/bird/bird.ctl") as b:
|
||||
await b.version.update()
|
||||
print(b.version)
|
||||
|
||||
await b.status.update()
|
||||
print(b.status)
|
||||
|
||||
asyncio.run(main())
|
Loading…
Reference in New Issue
Block a user