Download scripts/parse_arista.py from gesh75/multivendor-cli-configurator: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/gesh75/multivendor-cli-configurator/resolve/main/scripts/parse_arista.py
- Command line
-
hf download hf://spaces/gesh75/multivendor-cli-configurator/scripts/parse_arista.py
-
curl -L -o parse_arista.py https://huggingface.co/spaces/gesh75/multivendor-cli-configurator/resolve/main/scripts/parse_arista.py
10.4 kB
| #!/usr/bin/env python3 | |
| """ | |
| Arista EOS User Guide parser. | |
| Format: | |
| ## Chapter N: TOPIC | |
| ### Subsection title | |
| **`command name`** *`mode`* | |
| ``` | |
| command syntax line(s) | |
| ``` | |
| > Description starting with the command name... | |
| """ | |
| import re, json, pathlib | |
| from collections import Counter | |
| SRC = pathlib.Path(__file__).parent / "sources" / "arista-eos-user-guide.md" | |
| OUT = pathlib.Path(__file__).parent / "arista.json" | |
| # Chapter → default category | |
| CHAPTER_CAT = { | |
| 1: "System", # Overview | |
| 2: "System", # Booting the Switch | |
| 3: "System", # Initial Configuration and Recovery | |
| 4: "System", # Administering the Switch | |
| 5: "Interfaces", # 7130 Platform Layer 1 | |
| 6: "NTP", # Timing Protocols | |
| 7: "System", # Switch Environment Control | |
| 8: "System", # Upgrades and Downgrades | |
| 9: "Security", # Security (multi-topic; refined per command) | |
| 10: "QoS", # Quality of Service and Traffic Management | |
| 11: "Interfaces", # Interface Configuration | |
| 12: "Interfaces", # Layer 1 Configuration | |
| 13: "VLAN", # Layer 2 Configuration | |
| 14: "Routing", # Layer 3 Configuration (multi-topic; refined) | |
| 15: "DHCP", # IP Services (DHCP/DNS/NAT mostly) | |
| 16: "Routing", # Routing Protocols (multi-topic: BGP/OSPF/etc) | |
| 17: "Multicast", # Multicast | |
| 18: "VLAN", # VXLANs | |
| 19: "BGP", # EVPN (rides on BGP) | |
| 20: "MPLS", # MPLS | |
| 21: "MPLS", # RSVP-TE | |
| 22: "Troubleshooting", # Visibility and Monitoring | |
| 23: "Troubleshooting", | |
| 24: "Troubleshooting", | |
| } | |
| # Section / command-keyword overrides (lowercase substring → category) | |
| RECAT = [ | |
| # routing protocols | |
| ("bgp", "BGP"), | |
| ("evpn", "BGP"), | |
| ("ospf", "OSPF"), | |
| ("ospfv3", "OSPF"), | |
| ("isis", "Routing"), | |
| ("is-is", "Routing"), | |
| ("rip ", "Routing"), | |
| ("eigrp", "EIGRP"), | |
| # MPLS family | |
| ("mpls", "MPLS"), | |
| ("ldp", "MPLS"), | |
| ("rsvp", "MPLS"), | |
| ("traffic-engineering", "MPLS"), | |
| ("te tunnel", "MPLS"), | |
| # multicast | |
| ("pim ", "Multicast"), | |
| ("igmp", "Multicast"), | |
| ("msdp", "Multicast"), | |
| ("multicast", "Multicast"), | |
| # L2 | |
| ("spanning-tree", "Spanning-Tree"), | |
| ("rstp", "Spanning-Tree"), | |
| ("mstp", "Spanning-Tree"), | |
| ("portfast", "Spanning-Tree"), | |
| ("bpdu", "Spanning-Tree"), | |
| ("mlag", "EtherChannel"), | |
| ("port-channel", "EtherChannel"), | |
| ("port channel", "EtherChannel"), | |
| ("channel-group", "EtherChannel"), | |
| ("lacp", "EtherChannel"), | |
| ("lag ", "EtherChannel"), | |
| ("vlan", "VLAN"), | |
| ("vxlan", "VLAN"), | |
| ("trunk", "VLAN"), | |
| ("dot1q", "VLAN"), | |
| ("storm-control", "VLAN"), | |
| # interfaces | |
| ("interface ethernet", "Interfaces"), | |
| ("interface port-channel", "Interfaces"), | |
| ("interface loopback", "Interfaces"), | |
| ("interface management", "Interfaces"), | |
| ("interface vlan", "Interfaces"), | |
| ("ip address", "Interfaces"), | |
| ("ipv6 address", "Interfaces"), | |
| ("mtu", "Interfaces"), | |
| ("speed ", "Interfaces"), | |
| ("duplex ", "Interfaces"), | |
| ("link-flap", "Interfaces"), | |
| # IP services | |
| ("dhcp", "DHCP"), | |
| ("dns", "System"), | |
| ("nat", "NAT"), | |
| ("ntp", "NTP"), | |
| ("snmp", "SNMP"), | |
| ("sflow", "Troubleshooting"), | |
| ("netflow", "Troubleshooting"), | |
| ("syslog", "Logging"), | |
| ("logging", "Logging"), | |
| # AAA | |
| ("aaa", "AAA"), | |
| ("tacacs", "AAA"), | |
| ("radius", "AAA"), | |
| ("authentication", "AAA"), | |
| ("role ", "AAA"), | |
| ("username", "AAA"), | |
| # ACL/Security | |
| ("access-list", "ACL"), | |
| ("access-group", "ACL"), | |
| ("ip access", "ACL"), | |
| ("mac access", "ACL"), | |
| ("ipv6 access", "ACL"), | |
| ("control-plane", "Security"), | |
| ("dhcp snooping", "Security"), | |
| ("arp inspection", "Security"), | |
| ("urpf", "Security"), | |
| ("port-security", "Security"), | |
| ("storm control", "VLAN"), | |
| # HA / FHRP | |
| ("vrrp", "HA"), | |
| ("varp", "HA"), | |
| ("ip virtual-router", "HA"), | |
| # routing | |
| ("ip route", "Static"), | |
| ("ipv6 route", "Static"), | |
| ("static route", "Static"), | |
| ("route-map", "Routing"), | |
| ("prefix-list", "Routing"), | |
| ("ip prefix-list", "Routing"), | |
| # diagnostics / monitoring | |
| ("show ", "System"), # generic show fallback (overridden by per-protocol matches above when relevant) | |
| ("debug ", "Troubleshooting"), | |
| ("monitor", "Troubleshooting"), | |
| ("ping ", "Troubleshooting"), | |
| ("traceroute", "Troubleshooting"), | |
| ("tcpdump", "Troubleshooting"), | |
| ("ethanalyzer", "Troubleshooting"), | |
| ("packet-capture", "Troubleshooting"), | |
| ("event-monitor", "Troubleshooting"), | |
| ("event-handler", "Troubleshooting"), | |
| ("event handler", "Troubleshooting"), | |
| ("schedule ", "System"), | |
| # cluster / chassis | |
| ("chassis", "System"), | |
| ("environment", "System"), | |
| ("power", "System"), | |
| ("ztp", "System"), | |
| ("aboot", "System"), | |
| ("reload", "System"), | |
| ("boot", "System"), | |
| ("clock", "NTP"), | |
| ("ptp", "NTP"), | |
| # QoS | |
| ("qos", "QoS"), | |
| ("class-map", "QoS"), | |
| ("policy-map", "QoS"), | |
| ("service-policy", "QoS"), | |
| ("policer", "QoS"), | |
| ("dscp", "QoS"), | |
| ("cos ", "QoS"), | |
| ("queue", "QoS"), | |
| ] | |
| def categorize(default_cat: str, title: str, cmd: str, section_hdr: str) -> str: | |
| hay = (title + " " + cmd + " " + section_hdr).lower() | |
| # First, prefer exact protocol match (BGP/OSPF/etc) | |
| for k, c in RECAT: | |
| if k == "show " or k == "debug ": # generic - skip on first pass | |
| continue | |
| if k in hay: | |
| return c | |
| # Then fall back to generic show/debug | |
| for k, c in RECAT: | |
| if k in ("show ", "debug ") and k in hay: | |
| return c | |
| return default_cat | |
| def role_of(title: str, cmd: str, section_hdr: str) -> str: | |
| hay = (title + " " + cmd + " " + section_hdr).lower() | |
| # Arista is primarily switching, but EOS does L3, VXLAN, EVPN, MPLS — keep mostly switch/router split | |
| if any(k in hay for k in ["spanning-tree","vlan","trunk","mlag","port-channel","switchport","storm-control","stp","vxlan"]): | |
| return "switch" | |
| if any(k in hay for k in ["zone","security policy","firewall policy"]): | |
| return "firewall" | |
| # routing/L3 protocols → router | |
| if any(k in hay for k in ["ospf","bgp","isis","mpls","ldp","rsvp","router","redistribute","ip route","ipv6 route"]): | |
| return "router" | |
| # everything else (system, AAA, NTP, SNMP, etc) → switch (Arista default) | |
| return "switch" | |
| # Title-line regex: `**`command name`** *`mode`*` (mode optional) | |
| TITLE_RE = re.compile(r"^\*\*`([^`]+)`\*\*\s*(?:\*`([^`]+)`\*)?\s*$") | |
| def parse(): | |
| text = SRC.read_text(encoding="utf-8", errors="ignore") | |
| lines = text.split("\n") | |
| out = [] | |
| chapter = 0 | |
| section_hdr = "" | |
| pending_title = None # (name, mode) | |
| i, n = 0, len(lines) | |
| while i < n: | |
| line = lines[i] | |
| m = re.match(r"^##\s+Chapter\s+(\d+):\s*(.+?)\s*$", line) | |
| if m: | |
| chapter = int(m.group(1)) | |
| section_hdr = m.group(2).strip() | |
| pending_title = None | |
| i += 1; continue | |
| m = re.match(r"^#{2,3}\s+(.+?)\s*$", line) | |
| if m: | |
| section_hdr = m.group(1).strip() | |
| pending_title = None | |
| i += 1; continue | |
| # Bold-backtick command title line | |
| m = TITLE_RE.match(line) | |
| if m: | |
| pending_title = (m.group(1).strip(), (m.group(2) or "").strip()) | |
| i += 1; continue | |
| # Fenced code block | |
| if line.strip().startswith("```"): | |
| block = [] | |
| i += 1 | |
| while i < n and not lines[i].strip().startswith("```"): | |
| block.append(lines[i]) | |
| i += 1 | |
| i += 1 | |
| # Look for description (markdown blockquote starting with >) | |
| desc = "" | |
| j = i | |
| while j < n and j < i + 6: | |
| ln = lines[j].strip() | |
| if not ln: j += 1; continue | |
| qm = re.match(r"^>\s*(.+?)\s*$", lines[j]) | |
| if qm: | |
| # gather contiguous blockquote lines | |
| parts = [qm.group(1)] | |
| k = j + 1 | |
| while k < n: | |
| qq = re.match(r"^>\s*(.+?)\s*$", lines[k]) | |
| if qq: | |
| parts.append(qq.group(1)); k += 1 | |
| else: | |
| break | |
| desc = " ".join(parts).strip() | |
| break | |
| else: | |
| break | |
| # Process block | |
| cleaned = [l for l in block if l.strip()] | |
| if not cleaned: | |
| pending_title = None | |
| continue | |
| cmd = "\n".join(cleaned).rstrip() | |
| # Skip blocks that are clearly output samples (start with prompt/login/status text) | |
| first = cleaned[0].strip() | |
| if re.match(r"^(Active|Inactive|Standby|Up|Down|Login:|Username:|Password:|switch>|switch#|switch\(|--More--|VERSION|Last login|MAC)", first): | |
| pending_title = None | |
| continue | |
| if not pending_title: | |
| # no preceding title — try to use first line as title (and skip if it looks like output) | |
| title = first[:80] | |
| pending_title = (title, "") | |
| title, mode = pending_title | |
| default = CHAPTER_CAT.get(chapter, "System") | |
| cat = categorize(default, title, cmd, section_hdr) | |
| out.append({ | |
| "os":"eos","role":role_of(title, cmd, section_hdr),"vendor":"Arista","cat":cat, | |
| "title": title[:80], | |
| "cmd": cmd, | |
| "desc": (desc or section_hdr)[:280], | |
| }) | |
| pending_title = None | |
| continue | |
| i += 1 | |
| return out | |
| def main(): | |
| items = parse() | |
| # dedupe | |
| seen = set(); deduped = [] | |
| for it in items: | |
| key = re.sub(r"\s+", " ", it["cmd"]).lower() | |
| if key in seen: continue | |
| seen.add(key); deduped.append(it) | |
| OUT.write_text(json.dumps(deduped, indent=1, ensure_ascii=False)) | |
| print(f"wrote {len(deduped)} Arista entries -> {OUT}") | |
| print("by cat:", dict(Counter(c["cat"] for c in deduped))) | |
| print("by role:", dict(Counter(c["role"] for c in deduped))) | |
| if __name__ == "__main__": | |
| main() | |