Add TextFSM rule tables as dual input to biz-state parsers.

Collectors apply RULE_KEYS (or index stems) into fsm_tables before normalize; parsers prefer FSM or hand-parse.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-17 22:18:21 +08:00
parent 24a1a853af
commit 02b8a7073b
25 changed files with 714 additions and 69 deletions

View file

@ -29,7 +29,7 @@ from ..models import (
from ..ne_netmiko import disable_target_paging, send_show_command
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
from .command_match import expand_from_bindings, match_command, normalize_command
from .parsers import get_parser
from .parsers import get_parser, run_parser
from .profiles import get_profile
_log = logging.getLogger("netx.biz_state.runner")
@ -470,8 +470,7 @@ def _run_collect_session(
merged = {**params, **hit.params}
cmd_row.params_json = merged
parser = get_parser(hit.profile.parser_id)
if not parser:
if not get_parser(hit.profile.parser_id):
any_fail = True
cmd_row.parse_status = "failed"
cmd_row.message = f"unknown parser {hit.profile.parser_id}"
@ -480,13 +479,22 @@ def _run_collect_session(
continue
try:
records = parser(
raw_text=cmd_row.raw_text,
records, fsm_tables, rule_keys = run_parser(
hit.profile.parser_id,
raw_text=cmd_row.raw_text or "",
vendor=vendor_eff,
device_type=device_type_eff,
command=hit.profile.textfsm_command or concrete,
params=merged,
textfsm_command=hit.profile.textfsm_command or "",
)
# Debug hint only — avoid persisting large FSM JSON.
if rule_keys:
nonempty = [k for k in rule_keys if fsm_tables.get(k)]
cmd_row.message = (
f"fsm_keys={','.join(rule_keys)}"
+ (f";hit={','.join(nonempty)}" if nonempty else ";hit=")
)[:1020]
except Exception as exc:
any_fail = True
cmd_row.parse_status = "failed"

View file

@ -14,7 +14,7 @@ from ..lldp_shared import resolve_vendor_key
from ..models import BizStateTask
from ..ne_netmiko import disable_target_paging, send_show_command
from ..ne_session_factory import close_netmiko_connection, open_netmiko_connection
from .parsers import get_parser
from .parsers import get_parser, run_parser
from .profiles import get_profile
@ -124,16 +124,17 @@ def discover_params(
"raw_preview": str(raw or "")[:4000],
}
parser = get_parser(disc.parser_id)
records: list[dict[str, Any]] = []
if parser:
if get_parser(disc.parser_id):
try:
records = parser(
records, _fsm_tables, _rule_keys = run_parser(
disc.parser_id,
raw_text=raw,
vendor=vendor,
device_type=device_type,
command=disc.textfsm_command or command,
params={},
textfsm_command=disc.textfsm_command or "",
)
except Exception as exc:
return {

View file

@ -3,7 +3,7 @@
Layout::
parsers/
common/ # cross-vendor (lldp, vrf_list, vrf_route_summary)
common/ # cross-vendor (lldp, vrf_list, vrf_route_summary, pipeline)
zte/ # implemented status tables
cisco/ # skeleton — add command modules here
huawei/
@ -13,12 +13,27 @@ Layout::
ericsson/
Each vendor package exports ``PARSERS: dict[parser_id, normalize_fn]``.
Adding a status metric
----------------------
1. (Recommended) ``cli_templates/<vendor>/<stem>.textfsm`` + ``index`` line
2. ``parsers/<vendor>/<metric>.py``: ``RULE_KEYS = ("<stem>",)`` +
``normalize(..., fsm_tables=...)`` (use ``common.pipeline.prefer_fsm``)
3. Register in the vendor ``PARSERS`` dict
4. ``profiles.py``: add ``ParseProfile`` (command match + ``FieldDef`` schema)
Collect runs TextFSM rules first → ``fsm_tables``, then calls ``normalize`` with
both ``raw_text`` and ``fsm_tables``. The parser decides: return mapped FSM rows,
post-process them, or ignore FSM and hand-parse ``raw_text``.
"""
from __future__ import annotations
from typing import Any, Callable
import inspect
from typing import Any, Callable, Sequence
from ...lldp_shared import resolve_vendor_key
from ...ntc_parse import apply_rules, resolve_cli_platform, rules_for_command
from .cisco import PARSERS as _CISCO_PARSERS
from .common.lldp_neighbors import normalize_lldp_neighbors
from .common.vrf_list import normalize_vrf_list
@ -59,3 +74,107 @@ def get_parser(parser_id: str) -> NormalizeFn | None:
def registered_parser_ids() -> list[str]:
return sorted(_REGISTRY.keys())
def _rule_keys_for_fn(fn: NormalizeFn) -> tuple[tuple[str, ...], bool]:
"""Return ``(keys, declared)``. ``declared`` is True when RULE_KEYS is set on fn/module."""
keys_raw = None
declared = False
if hasattr(fn, "RULE_KEYS"):
keys_raw = getattr(fn, "RULE_KEYS")
declared = True
else:
mod = inspect.getmodule(fn)
if mod is not None and hasattr(mod, "RULE_KEYS"):
keys_raw = getattr(mod, "RULE_KEYS")
declared = True
if not keys_raw:
return (), declared
out: list[str] = []
for k in keys_raw:
s = str(k or "").strip()
if s.lower().endswith(".textfsm"):
s = s[: -len(".textfsm")]
if s and s not in out:
out.append(s)
return tuple(out), declared
def get_parser_meta(parser_id: str) -> dict[str, Any] | None:
"""Return ``{fn, rule_keys, rule_keys_declared}`` for a registered parser, or None."""
fn = get_parser(parser_id)
if not fn:
return None
keys, declared = _rule_keys_for_fn(fn)
return {"fn": fn, "rule_keys": keys, "rule_keys_declared": declared}
def resolve_rule_keys(
*,
parser_id: str,
platform: str = "",
command: str = "",
textfsm_command: str = "",
) -> list[str]:
"""RULE_KEYS from parser when declared; else index stems for textfsm_command/command."""
meta = get_parser_meta(parser_id)
if not meta:
return []
if meta.get("rule_keys_declared"):
return list(meta["rule_keys"])
plat = str(platform or "").strip()
cmd = str(textfsm_command or command or "").strip()
if plat and cmd:
return rules_for_command(plat, cmd)
return []
def run_parser(
parser_id: str,
*,
raw_text: str,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
textfsm_command: str = "",
) -> tuple[list[dict[str, Any]], dict[str, list[dict[str, Any]]], list[str]]:
"""Apply TextFSM rules then normalize.
Returns ``(records, fsm_tables, rule_keys_used)``.
"""
meta = get_parser_meta(parser_id)
if not meta:
raise KeyError(f"unknown parser {parser_id}")
fn: NormalizeFn = meta["fn"]
platform = resolve_cli_platform(
vendor=vendor,
device_type=device_type,
vendor_key=resolve_vendor_key(vendor, device_type),
)
cmd_hint = str(textfsm_command or command or "").strip()
rule_keys = resolve_rule_keys(
parser_id=parser_id,
platform=platform,
command=command,
textfsm_command=textfsm_command,
)
fsm_tables: dict[str, list[dict[str, Any]]] = {}
if rule_keys and str(raw_text or "").strip():
fsm_tables = apply_rules(
platform=platform,
text=raw_text,
rule_keys=rule_keys,
command=cmd_hint,
)
records = fn(
raw_text=raw_text,
fsm_tables=fsm_tables,
vendor=vendor,
device_type=device_type,
command=cmd_hint or command,
params=params or {},
)
if not isinstance(records, list):
records = []
return records, fsm_tables, rule_keys

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_arp(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("cisco arp parser not implemented yet")
normalize_arp.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_bgp_peer(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("cisco bgp_peer parser not implemented yet")
normalize_bgp_peer.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_interface_brief(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("cisco interface_brief parser not implemented yet")
normalize_interface_brief.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_isis_adjacency(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("cisco isis_adjacency parser not implemented yet")
normalize_isis_adjacency.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_nd6_cache(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("cisco nd6_cache parser not implemented yet")
normalize_nd6_cache.RULE_KEYS = RULE_KEYS

View file

@ -6,10 +6,13 @@ from typing import Any
from ....lldp_shared import NeighborHit, parse_neighbor_output
RULE_KEYS: tuple[str, ...] = ()
def normalize_lldp_neighbors(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
@ -21,7 +24,7 @@ def normalize_lldp_neighbors(
device_type=device_type,
command=command,
)
_ = params
_ = (params, fsm_tables)
rows: list[dict[str, Any]] = []
for h in hits:
rows.append(
@ -34,3 +37,5 @@ def normalize_lldp_neighbors(
}
)
return rows
normalize_lldp_neighbors.RULE_KEYS = RULE_KEYS

View file

@ -0,0 +1,31 @@
"""Shared FSM-vs-hand helpers for biz_state parsers."""
from __future__ import annotations
from typing import Any, Callable, Mapping, Sequence
MapRowsFn = Callable[[list[dict[str, Any]]], list[dict[str, Any]]]
HandFn = Callable[..., list[dict[str, Any]]]
def prefer_fsm(
fsm_tables: Mapping[str, list[dict[str, Any]]] | None,
keys: Sequence[str],
map_rows: MapRowsFn,
hand_fn: HandFn,
*,
raw_text: str,
**kw: Any,
) -> list[dict[str, Any]]:
"""Use the first non-empty FSM table (mapped); otherwise hand-parse raw_text."""
tables = fsm_tables or {}
for key in keys or ():
k = str(key or "").strip()
if not k:
continue
rows = tables.get(k) or []
if rows:
mapped = map_rows(rows)
if mapped:
return mapped
return hand_fn(raw_text=raw_text, **kw)

View file

@ -3,28 +3,16 @@
from __future__ import annotations
import re
from typing import Any
from typing import Any, Mapping
from ....lldp_shared import resolve_vendor_key
from ....ntc_parse import parse_cli, resolve_cli_platform, row_get
from ....ntc_parse import apply_rules, resolve_cli_platform, row_get
from .pipeline import prefer_fsm
RULE_KEYS = ("zte_zxros_show_ip_vrf",)
def normalize_vrf_list(
*,
raw_text: str,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = params
platform = resolve_cli_platform(
vendor=vendor,
device_type=device_type,
vendor_key=resolve_vendor_key(vendor, device_type),
)
cmd = str(command or "").strip()
rows = parse_cli(platform=platform, command=cmd, text=raw_text) if platform and cmd else []
def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
seen: set[str] = set()
for r in rows:
@ -38,11 +26,15 @@ def normalize_vrf_list(
{
"vrf_name": name[:128],
"rd": row_get(r, "DEFAULT_RD", "RD", "rd")[:64],
"protocols": row_get(r, "PROTOCOLS", "ADDRESS_FAMILY", "protocols")[:64],
"protocols": row_get(r, "PROTOCOLS", "ADDRESS_FAMILY", "INTERFACES", "protocols")[:64],
}
)
if out:
return out
return out
def _hand_parse(*, raw_text: str, **_kw: Any) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
seen: set[str] = set()
for line in str(raw_text or "").splitlines():
m = re.match(r"^\s*([A-Za-z0-9_./:-]+)\s+(\d+:\d+|<not set>|\S+:\S+)\s*", line)
if not m:
@ -55,3 +47,37 @@ def normalize_vrf_list(
seen.add(name)
out.append({"vrf_name": name[:128], "rd": m.group(2)[:64], "protocols": ""})
return out
def normalize_vrf_list(
*,
raw_text: str,
fsm_tables: Mapping[str, list[dict[str, Any]]] | None = None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = params
tables = dict(fsm_tables or {})
if not any(tables.get(k) for k in RULE_KEYS):
platform = resolve_cli_platform(
vendor=vendor,
device_type=device_type,
vendor_key=resolve_vendor_key(vendor, device_type),
)
cmd = str(command or "show ip vrf").strip()
if platform and cmd:
tables = apply_rules(
platform=platform, text=raw_text, rule_keys=RULE_KEYS, command=cmd
)
return prefer_fsm(
tables,
RULE_KEYS,
_map_fsm_rows,
_hand_parse,
raw_text=raw_text,
)
normalize_vrf_list.RULE_KEYS = RULE_KEYS

View file

@ -16,16 +16,19 @@ _SOURCE_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
("total", re.compile(r"(?i)^\s*(?:total|totals?)\s+(?:routes?\s+)?(\d+)\s*$")),
]
RULE_KEYS: tuple[str, ...] = ()
def normalize_vrf_route_summary(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command)
_ = (vendor, device_type, command, fsm_tables)
vrf = str((params or {}).get("vrf") or (params or {}).get("vrf_name") or "").strip()
text = str(raw_text or "")
found: dict[str, int] = {}
@ -53,3 +56,5 @@ def normalize_vrf_route_summary(
if vrf and not rows:
rows.append({"vrf": vrf[:128], "source": "empty", "networks": 0})
return rows
normalize_vrf_route_summary.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_arp(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("huawei arp parser not implemented yet")
normalize_arp.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_bgp_peer(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("huawei bgp_peer parser not implemented yet")
normalize_bgp_peer.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_interface_brief(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("huawei interface_brief parser not implemented yet")
normalize_interface_brief.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_isis_adjacency(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("huawei isis_adjacency parser not implemented yet")
normalize_isis_adjacency.RULE_KEYS = RULE_KEYS

View file

@ -4,14 +4,19 @@ from __future__ import annotations
from typing import Any
RULE_KEYS: tuple[str, ...] = ()
def normalize_nd6_cache(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (raw_text, vendor, device_type, command, params)
_ = (raw_text, vendor, device_type, command, params, fsm_tables)
raise NotImplementedError("huawei nd6_cache parser not implemented yet")
normalize_nd6_cache.RULE_KEYS = RULE_KEYS

View file

@ -0,0 +1,8 @@
"""Compat shim — prefer ``parsers.common.vrf_list`` / ``vrf_route_summary``."""
from __future__ import annotations
from .common.vrf_list import normalize_vrf_list
from .common.vrf_route_summary import normalize_vrf_route_summary
__all__ = ["normalize_vrf_list", "normalize_vrf_route_summary"]

View file

@ -7,6 +7,8 @@ from typing import Any
_ARP_AGE_TIME_RE = re.compile(r"^\d{1,2}:\d{2}:\d{2}$")
RULE_KEYS: tuple[str, ...] = ()
def is_valid_arp_age(age: str) -> bool:
"""True when Age looks like a dynamic timer (HH:MM:SS), not static flags like H."""
@ -16,12 +18,13 @@ def is_valid_arp_age(age: str) -> bool:
def normalize_arp(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command, params)
_ = (vendor, device_type, command, params, fsm_tables)
out: list[dict[str, Any]] = []
seen: set[tuple[str, str]] = set()
ip_re = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
@ -61,3 +64,5 @@ def normalize_arp(
}
)
return out
normalize_arp.RULE_KEYS = RULE_KEYS

View file

@ -11,6 +11,8 @@ _BGP_PEER_RE = re.compile(
re.I,
)
RULE_KEYS: tuple[str, ...] = ()
def _detect_bgp_afi(command: str, params: dict[str, str] | None) -> str:
if params and params.get("afi"):
@ -30,12 +32,13 @@ def _detect_bgp_afi(command: str, params: dict[str, str] | None) -> str:
def normalize_bgp_peer(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type)
_ = (vendor, device_type, fsm_tables)
afi = _detect_bgp_afi(command, params)
out: list[dict[str, Any]] = []
seen: set[str] = set()
@ -77,3 +80,5 @@ def normalize_bgp_peer(
}
)
return out
normalize_bgp_peer.RULE_KEYS = RULE_KEYS

View file

@ -3,10 +3,13 @@
from __future__ import annotations
import re
from typing import Any
from typing import Any, Mapping
from ....lldp_shared import resolve_vendor_key
from ....ntc_parse import parse_cli, resolve_cli_platform, row_get
from ....ntc_parse import apply_rules, resolve_cli_platform, row_get
from ..common.pipeline import prefer_fsm
RULE_KEYS = ("zte_zxros_show_interface_brief",)
_IFACE_RE = re.compile(
r"^(?P<iface>\S+)\s+(?P<attr>\S+)\s+(?P<mode>\S+)"
@ -16,22 +19,7 @@ _IFACE_RE = re.compile(
)
def normalize_interface_brief(
*,
raw_text: str,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = params
platform = resolve_cli_platform(
vendor=vendor,
device_type=device_type,
vendor_key=resolve_vendor_key(vendor, device_type),
)
cmd = str(command or "show interface brief").strip() or "show interface brief"
rows = parse_cli(platform=platform, command=cmd, text=raw_text) if platform else []
def _map_fsm_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
seen: set[str] = set()
for r in rows:
@ -53,8 +41,20 @@ def normalize_interface_brief(
"description": row_get(r, "DESCRIPTION", "description")[:256],
}
)
if out:
return out
return out
def _hand_parse(
*,
raw_text: str,
vendor: str = "",
device_type: str = "",
command: str = "",
**_kw: Any,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command)
out: list[dict[str, Any]] = []
seen: set[str] = set()
for raw in str(raw_text or "").splitlines():
line = raw.strip()
if not line or line.lower().startswith("interface"):
@ -79,3 +79,40 @@ def normalize_interface_brief(
}
)
return out
def normalize_interface_brief(
*,
raw_text: str,
fsm_tables: Mapping[str, list[dict[str, Any]]] | None = None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = params
tables = dict(fsm_tables or {})
if not any(tables.get(k) for k in RULE_KEYS):
platform = resolve_cli_platform(
vendor=vendor,
device_type=device_type,
vendor_key=resolve_vendor_key(vendor, device_type),
)
cmd = str(command or "show interface brief").strip() or "show interface brief"
if platform:
tables = apply_rules(
platform=platform, text=raw_text, rule_keys=RULE_KEYS, command=cmd
)
return prefer_fsm(
tables,
RULE_KEYS,
_map_fsm_rows,
_hand_parse,
raw_text=raw_text,
vendor=vendor,
device_type=device_type,
command=command,
)
normalize_interface_brief.RULE_KEYS = RULE_KEYS

View file

@ -13,16 +13,19 @@ _ISIS_ROW_RE = re.compile(
)
_PROCESS_RE = re.compile(r"(?i)^\s*Process\s+ID\s*:\s*(\d+)\s*$")
RULE_KEYS: tuple[str, ...] = ()
def normalize_isis_adjacency(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command, params)
_ = (vendor, device_type, command, params, fsm_tables)
out: list[dict[str, Any]] = []
process_id = ""
for raw in str(raw_text or "").splitlines():
@ -54,3 +57,5 @@ def normalize_isis_adjacency(
}
)
return out
normalize_isis_adjacency.RULE_KEYS = RULE_KEYS

View file

@ -11,16 +11,19 @@ _ND6_ROW_RE = re.compile(
re.I,
)
RULE_KEYS: tuple[str, ...] = ()
def normalize_nd6_cache(
*,
raw_text: str,
fsm_tables=None,
vendor: str = "",
device_type: str = "",
command: str = "",
params: dict[str, str] | None = None,
) -> list[dict[str, Any]]:
_ = (vendor, device_type, command, params)
_ = (vendor, device_type, command, params, fsm_tables)
out: list[dict[str, Any]] = []
seen: set[tuple[str, str]] = set()
for raw in str(raw_text or "").splitlines():
@ -52,3 +55,5 @@ def normalize_nd6_cache(
}
)
return out
normalize_nd6_cache.RULE_KEYS = RULE_KEYS