"""Pure functions: device output in, typed values out. No I/O here.""" from __future__ import annotations import re from typing import TypedDict #: "Key : value" lines of `show version`. _FIELD = re.compile(r"^(?P[^:\n]+?)\s*:\s*(?P.*)$", re.MULTILINE) #: "3 days, 4:05:06" or "4:05:06". _UPTIME = re.compile(r"^(?:(?P\d+) days?, )?(?P\d+):(?P\d\d):(?P\d\d)$") _SECONDS_PER_DAY = 86400 class VersionDict(TypedDict): model: str hostname: str os_version: str serial_number: str uptime: float def parse_uptime(text: str) -> float: """Seconds from an uptime like ``3 days, 4:05:06``.""" match = _UPTIME.match(text.strip()) if not match: raise ValueError(f"unreadable uptime: {text!r}") days = int(match.group("days") or 0) hours, minutes, seconds = (int(match.group(k)) for k in ("h", "m", "s")) return float(days * _SECONDS_PER_DAY + hours * 3600 + minutes * 60 + seconds) def parse_version(text: str) -> VersionDict: """Facts from ``show version``; a field the device did not print is an error.""" fields = {m.group("key").strip(): m.group("value").strip() for m in _FIELD.finditer(text)} try: return { "model": text.splitlines()[0].removesuffix(" Switch").strip(), "hostname": fields["Hostname"], "os_version": fields["Firmware"], "serial_number": fields["Serial number"], "uptime": parse_uptime(fields["Uptime"]), } except KeyError as exc: raise ValueError(f"show version lacks {exc.args[0]}") from exc