clijson 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clijson-0.2.0/LICENSE +21 -0
- clijson-0.2.0/PKG-INFO +390 -0
- clijson-0.2.0/README.md +340 -0
- clijson-0.2.0/pyproject.toml +194 -0
- clijson-0.2.0/pyproject.toml.orig +139 -0
- clijson-0.2.0/src/clijson/__init__.py +50 -0
- clijson-0.2.0/src/clijson/__main__.py +5 -0
- clijson-0.2.0/src/clijson/api.py +455 -0
- clijson-0.2.0/src/clijson/cli.py +417 -0
- clijson-0.2.0/src/clijson/commands.py +312 -0
- clijson-0.2.0/src/clijson/diff.py +177 -0
- clijson-0.2.0/src/clijson/engines/__init__.py +1 -0
- clijson-0.2.0/src/clijson/engines/config.py +247 -0
- clijson-0.2.0/src/clijson/engines/external.py +77 -0
- clijson-0.2.0/src/clijson/engines/generic.py +339 -0
- clijson-0.2.0/src/clijson/engines/structured.py +143 -0
- clijson-0.2.0/src/clijson/exceptions.py +47 -0
- clijson-0.2.0/src/clijson/live.py +114 -0
- clijson-0.2.0/src/clijson/mcp_server.py +273 -0
- clijson-0.2.0/src/clijson/models.py +476 -0
- clijson-0.2.0/src/clijson/parsers/__init__.py +1 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/__init__.py +1 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/bgp_rib.py +269 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/config.py +45 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/extra.py +547 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/interfaces.py +452 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/routing.py +872 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/services.py +878 -0
- clijson-0.2.0/src/clijson/parsers/iosxr/system.py +440 -0
- clijson-0.2.0/src/clijson/parsers/junos/__init__.py +1 -0
- clijson-0.2.0/src/clijson/parsers/junos/common.py +35 -0
- clijson-0.2.0/src/clijson/parsers/junos/config.py +16 -0
- clijson-0.2.0/src/clijson/parsers/junos/extra.py +333 -0
- clijson-0.2.0/src/clijson/parsers/junos/interfaces.py +447 -0
- clijson-0.2.0/src/clijson/parsers/junos/routing.py +939 -0
- clijson-0.2.0/src/clijson/parsers/junos/services.py +346 -0
- clijson-0.2.0/src/clijson/parsers/junos/system.py +583 -0
- clijson-0.2.0/src/clijson/parsers/vrp/__init__.py +1 -0
- clijson-0.2.0/src/clijson/parsers/vrp/config.py +23 -0
- clijson-0.2.0/src/clijson/parsers/vrp/extra.py +312 -0
- clijson-0.2.0/src/clijson/parsers/vrp/interfaces.py +570 -0
- clijson-0.2.0/src/clijson/parsers/vrp/routing.py +686 -0
- clijson-0.2.0/src/clijson/parsers/vrp/services.py +325 -0
- clijson-0.2.0/src/clijson/parsers/vrp/system.py +457 -0
- clijson-0.2.0/src/clijson/platforms.py +278 -0
- clijson-0.2.0/src/clijson/py.typed +0 -0
- clijson-0.2.0/src/clijson/registry.py +204 -0
- clijson-0.2.0/src/clijson/result.py +147 -0
- clijson-0.2.0/src/clijson/server.py +88 -0
- clijson-0.2.0/src/clijson/textutils.py +378 -0
clijson-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shady Magdy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
clijson-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clijson
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Turn show/display command output from Cisco IOS XR, Juniper Junos and Huawei VRP routers into JSON.
|
|
5
|
+
Keywords: network,automation,parser,cli,json,cisco,iosxr,juniper,junos,huawei,vrp,netdevops
|
|
6
|
+
Author: Shady Magdy
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Telecommunications Industry
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: System :: Networking
|
|
22
|
+
Classifier: Topic :: Text Processing
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Dist: pyyaml>=5.4 ; extra == 'all'
|
|
25
|
+
Requires-Dist: rich>=12 ; extra == 'all'
|
|
26
|
+
Requires-Dist: ntc-templates>=4 ; extra == 'all'
|
|
27
|
+
Requires-Dist: mcp>=2 ; extra == 'all'
|
|
28
|
+
Requires-Dist: pyats-topology ; extra == 'genie'
|
|
29
|
+
Requires-Dist: genie-libs-parser ; extra == 'genie'
|
|
30
|
+
Requires-Dist: mcp>=2 ; extra == 'mcp'
|
|
31
|
+
Requires-Dist: netmiko>=4 ; extra == 'netmiko'
|
|
32
|
+
Requires-Dist: ntc-templates>=4 ; extra == 'ntc'
|
|
33
|
+
Requires-Dist: rich>=12 ; extra == 'pretty'
|
|
34
|
+
Requires-Dist: scrapli>=2023.7.30 ; extra == 'scrapli'
|
|
35
|
+
Requires-Dist: pyyaml>=5.4 ; extra == 'yaml'
|
|
36
|
+
Requires-Python: >=3.10
|
|
37
|
+
Project-URL: Homepage, https://github.com/shadymagdy/network-cli-parser
|
|
38
|
+
Project-URL: Source, https://github.com/shadymagdy/network-cli-parser
|
|
39
|
+
Project-URL: Issues, https://github.com/shadymagdy/network-cli-parser/issues
|
|
40
|
+
Project-URL: Changelog, https://github.com/shadymagdy/network-cli-parser/blob/main/CHANGELOG.md
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Provides-Extra: genie
|
|
43
|
+
Provides-Extra: mcp
|
|
44
|
+
Provides-Extra: netmiko
|
|
45
|
+
Provides-Extra: ntc
|
|
46
|
+
Provides-Extra: pretty
|
|
47
|
+
Provides-Extra: scrapli
|
|
48
|
+
Provides-Extra: yaml
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# clijson — router CLI output → JSON
|
|
52
|
+
|
|
53
|
+
[](https://github.com/shadymagdy/network-cli-parser/actions/workflows/ci.yml)
|
|
54
|
+
[](https://shadymagdy.github.io/network-cli-parser/)
|
|
55
|
+
[](pyproject.toml)
|
|
56
|
+
[](pyproject.toml)
|
|
57
|
+
[](https://github.com/astral-sh/uv)
|
|
58
|
+
[](https://github.com/astral-sh/ruff)
|
|
59
|
+
[](LICENSE)
|
|
60
|
+
|
|
61
|
+
📖 **Documentation: <https://shadymagdy.github.io/network-cli-parser/>**
|
|
62
|
+
|
|
63
|
+
**clijson** (repo: `network-cli-parser`) turns the output of `show` / `display` commands from
|
|
64
|
+
**Cisco IOS XR**, **Juniper Junos** and **Huawei VRP** routers into clean, predictable JSON.
|
|
65
|
+
It has **no runtime dependencies**.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
>>> import clijson
|
|
69
|
+
>>> r = clijson.parse(open("pe1.log").read()) # platform and command are auto-detected
|
|
70
|
+
>>> r.parser, r.platform
|
|
71
|
+
('iosxr.show_bgp_summary', 'iosxr')
|
|
72
|
+
>>> r.data["neighbors"][0]
|
|
73
|
+
{'neighbor': '10.255.0.2', 'instance': 'default', 'vrf': 'default', 'address_family': 'ipv4 unicast',
|
|
74
|
+
'remote_as': 65000, 'messages_received': 12011, ..., 'up_down': '1w2d', 'state': 'Established',
|
|
75
|
+
'prefixes_received': 512}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Why clijson
|
|
79
|
+
|
|
80
|
+
| | |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **Works on any command** | 161 dedicated parsers (237 command patterns). Anything else goes through a **generic engine** that finds tables, `key: value` pairs and indented sections in *any* output, so you always get JSON back. |
|
|
83
|
+
| **Understands you like a router does** | `sh ip int br`, `dis int br` and `show interfaces brief` all resolve. Huawei accepts `show` as well as `display`. Parameters such as a VRF or interface are captured. |
|
|
84
|
+
| **Zero configuration** | The platform is detected from the prompt, the command verb or fingerprints in the output. If those don't settle it, **trial parsing** lets each vendor's parser try and keeps the one that understands the output. Echoed prompts, `--More--` pagers, ANSI codes, timestamps and `{master}` lines are removed. |
|
|
85
|
+
| **One model for all vendors** | `normalize=True` adds a **vendor-neutral view** for 18 common concepts (BGP peers, interfaces, routes, LLDP, OSPF/IS-IS/LDP, ARP/ND, BFD, LAG, VRFs, …), so one script can handle all three vendors. |
|
|
86
|
+
| **Structured output is native** | Junos `| display json` / `| display xml` output is recognised and flattened into clean snake_case JSON. |
|
|
87
|
+
| **Config as data** | `show running-config`, `show configuration` (curly braces or `| display set`) and `display current-configuration` become nested trees. |
|
|
88
|
+
| **Whole sessions** | Paste a terminal log with 20 commands and get 20 results (`parse_session`). |
|
|
89
|
+
| **Pre/post change checks** | `clijson.diff(before, after)` (or `clijson diff pre.txt post.txt`) matches records by their natural key (neighbor, interface, prefix, …) and ignores counters and timers by default, so only real changes are reported. |
|
|
90
|
+
| **Clear about failures** | Device errors (`% Invalid input`, `syntax error`, `Error: Unrecognized command`) come back as `engine="device-error"` with the message, instead of garbage data. |
|
|
91
|
+
| **Stands on giants' shoulders** | If you have [ntc-templates](https://github.com/networktocode/ntc-templates) or [Cisco Genie](https://github.com/CiscoTestAutomation/genieparser) installed, their templates become extra fallback engines automatically. |
|
|
92
|
+
| **Tells you how it got the answer** | Every result carries `engine`, `parser`, `confidence`, `warnings` (for example "output was filtered by `| include`") and metadata such as the hostname and timestamp. |
|
|
93
|
+
| **Tested on real output** | 238 regression fixtures: 217 captured from real ASR9K, NCS5500, 8000, CRS, XRv, MX, PTX, QFX, EX, SRX, NE40E, CX600, ATN, CE, S and AR devices, plus 21 written from vendor documentation formats. |
|
|
94
|
+
| **Easy to use from any language** | Python API, a full CLI (`clijson`), and a zero-dependency HTTP API (`clijson serve`). |
|
|
95
|
+
| **Built for AI assistants** | `clijson mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server. Claude, Cursor, VS Code and any agent can call clijson as a tool and reason over exact, schema'd data. |
|
|
96
|
+
|
|
97
|
+
## What it looks like
|
|
98
|
+
|
|
99
|
+
Input: a Huawei capture pasted straight from the terminal, prompt included.
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
<PE1>display interface brief
|
|
103
|
+
PHY: Physical
|
|
104
|
+
*down: administratively down
|
|
105
|
+
Interface PHY Protocol InUti OutUti inErrors outErrors
|
|
106
|
+
Eth-Trunk1 up up 0.01% 0.38% 0 0
|
|
107
|
+
GigabitEthernet0/0/1 up up 0.01% 0.40% 0 0
|
|
108
|
+
GigabitEthernet0/0/2 up up 0% 0.36% 0 0
|
|
109
|
+
GigabitEthernet0/0/3 *down down 0% 0% 0 0
|
|
110
|
+
LoopBack0 up up(s) 0% 0% 0 0
|
|
111
|
+
<PE1>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`clijson pe1.txt` detects Huawei VRP and `display interface brief`, nests the trunk members and decodes the
|
|
115
|
+
flags. Output is abbreviated here:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
[
|
|
119
|
+
{"interface": "Eth-Trunk1", "physical": "up", "protocol": "up", "input_utilization": 0.01, "output_utilization": 0.38,
|
|
120
|
+
"input_errors": 0, "output_errors": 0,
|
|
121
|
+
"members": [{"interface": "GigabitEthernet0/0/1", "physical": "up", "protocol": "up", ...},
|
|
122
|
+
{"interface": "GigabitEthernet0/0/2", ...}]},
|
|
123
|
+
{"interface": "GigabitEthernet0/0/3", "physical": "down", "protocol": "down", "admin_down": true, ...},
|
|
124
|
+
{"interface": "LoopBack0", "physical": "up", "protocol": "up", "flags": ["spoofing"], ...}
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`clijson pe1.txt -n` gives the vendor-neutral view. It has the same shape for IOS XR and Junos:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
[{"name": "Eth-Trunk1", "admin_status": "up", "oper_status": "up", "ip_address": null, "vrf": null, "description": null},
|
|
132
|
+
{"name": "GigabitEthernet0/0/3", "admin_status": "admin-down", "oper_status": "down", ...}, ...]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Install
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pip install clijson # core, no dependencies
|
|
139
|
+
pip install "clijson[all]" # + YAML output, pretty tables, ntc-templates fallback
|
|
140
|
+
pip install "clijson[netmiko]" # + collect from live devices (or [scrapli])
|
|
141
|
+
pip install "clijson[mcp]" # + MCP server for AI assistants
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
With [uv](https://docs.astral.sh/uv/):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv add clijson # add to your project
|
|
148
|
+
uvx clijson parse show_bgp.txt # run the CLI without installing anything
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Requires Python 3.10 or newer.
|
|
152
|
+
|
|
153
|
+
## Quick start
|
|
154
|
+
|
|
155
|
+
### Python
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import clijson
|
|
159
|
+
|
|
160
|
+
# 1. Tell it everything...
|
|
161
|
+
r = clijson.parse(output, "show ipv4 interface brief", platform="iosxr")
|
|
162
|
+
|
|
163
|
+
# 2. ...or nothing: prompt lines like "RP/0/RP0/CPU0:PE1#show ipv4 int br" are recognised
|
|
164
|
+
r = clijson.parse(output)
|
|
165
|
+
|
|
166
|
+
r.data # structured data (dict / list)
|
|
167
|
+
r.to_json() # JSON string
|
|
168
|
+
r.to_yaml() # needs PyYAML
|
|
169
|
+
r.to_dict() # data + provenance (engine, parser, confidence, warnings, metadata)
|
|
170
|
+
r.records() # the most table-like view as flat rows
|
|
171
|
+
r.to_dataframe() # the same rows as a pandas DataFrame (needs pandas)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Pre/post maintenance check**:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
before = clijson.parse(pre_capture, "show bgp summary", "iosxr", normalize=True)
|
|
178
|
+
after = clijson.parse(post_capture, "show bgp summary", "iosxr", normalize=True)
|
|
179
|
+
for change in clijson.diff(before, after):
|
|
180
|
+
print(change)
|
|
181
|
+
# ~ [neighbor=10.255.0.2].state: 'Established' -> 'Idle'
|
|
182
|
+
# - [neighbor=10.255.0.4]: {...}
|
|
183
|
+
# + [neighbor=10.255.0.5]: {...}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Same code for every vendor** with the normalized view:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
jobs = [("pe1-xr.txt", "show bgp summary", "iosxr"),
|
|
190
|
+
("pe2-mx.txt", "show bgp summary", "junos"),
|
|
191
|
+
("pe3-ne.txt", "display bgp peer", "vrp")]
|
|
192
|
+
|
|
193
|
+
for path, cmd, platform in jobs:
|
|
194
|
+
r = clijson.parse(open(path).read(), cmd, platform, normalize=True)
|
|
195
|
+
for peer in r.normalized:
|
|
196
|
+
if not peer["established"]:
|
|
197
|
+
print(f"{path}: {peer['neighbor']} AS{peer['remote_as']} is {peer['state']}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**A whole terminal session** (PuTTY/SecureCRT/`script` log):
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
for r in clijson.parse_session(open("maintenance-window.log").read()):
|
|
204
|
+
print(r.metadata.get("hostname"), r.command, r.parser)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Live devices** (via scrapli or netmiko). You can try it against containerlab XRd / cRPD / vJunos / VRP images:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from clijson.live import collect
|
|
211
|
+
results = collect("10.0.0.1", "junos", ["show version", "show bgp summary"],
|
|
212
|
+
username="lab", password="lab123", normalize=True)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Command line
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
clijson parse show_bgp.txt -p iosxr -c "show bgp summary"
|
|
219
|
+
ssh mx1 "show interfaces terse" | clijson parse -p junos -c "show interfaces terse"
|
|
220
|
+
clijson session.log # every command in a log (shorthand for `parse`)
|
|
221
|
+
clijson parse out.txt -c "dis bgp peer" -n -f table # normalized, as a table
|
|
222
|
+
clijson parse out.txt -m # include engine/parser/confidence/warnings
|
|
223
|
+
clijson diff pre.txt post.txt -c "show bgp summary" # what changed? (exit code 1 if anything did)
|
|
224
|
+
clijson commands -p vrp --search lldp # what is supported?
|
|
225
|
+
clijson detect mystery.txt # which OS produced this?
|
|
226
|
+
clijson schema bgp.summary # JSON Schema of a normalized model
|
|
227
|
+
clijson run 10.0.0.1 -p iosxr -c "show version" -c "show bgp summary" -u admin
|
|
228
|
+
clijson serve --port 8080 # HTTP API for other languages/tools
|
|
229
|
+
clijson mcp # MCP server for AI assistants
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### HTTP API
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
clijson serve --port 8080 &
|
|
236
|
+
curl -s localhost:8080/parse -d '{"platform":"vrp","command":"display interface brief","output":"...","normalize":true}'
|
|
237
|
+
curl -s "localhost:8080/commands?platform=junos"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### AI assistants (MCP)
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
claude mcp add clijson -- uvx --from "clijson[mcp]" clijson mcp # Claude Code
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For Claude Desktop, Cursor or VS Code, add the same command (`uvx --from clijson[mcp] clijson mcp`) to the
|
|
247
|
+
client's MCP config. The assistant gets read-only tools: `parse_output`, `parse_session`, `detect_platform`,
|
|
248
|
+
`diff_outputs`, `list_commands` and `get_model_schema`. Setup for each client and the HTTP transport are
|
|
249
|
+
covered in **[docs/mcp.md](docs/mcp.md)**.
|
|
250
|
+
|
|
251
|
+
## How it works
|
|
252
|
+
|
|
253
|
+
```mermaid
|
|
254
|
+
flowchart LR
|
|
255
|
+
A[raw text] --> B[clean<br/>ANSI, pagers, CRLF,<br/>indentation]
|
|
256
|
+
B --> C[prompt & echo<br/>extraction<br/>host, command, timestamp]
|
|
257
|
+
C --> D{platform?}
|
|
258
|
+
D -- given / prompt / verb / fingerprints --> E
|
|
259
|
+
D -- still unknown --> T[trial parse<br/>every vendor]
|
|
260
|
+
T --> E{output format}
|
|
261
|
+
E -- JSON / XML --> S[structured engine]
|
|
262
|
+
E -- text --> R[command grammar<br/>abbreviation-aware<br/>resolution]
|
|
263
|
+
R --> N[native parser]
|
|
264
|
+
N -. failed / missing .-> X[ntc-templates / Genie<br/>if installed]
|
|
265
|
+
X -. missing .-> G[generic engine<br/>tables · key/value · sections]
|
|
266
|
+
N --> M[normalize<br/>vendor-neutral model]
|
|
267
|
+
S & N & X & G --> O[ParseResult<br/>data · engine · parser ·<br/>confidence · warnings]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
* **Command grammar.** Parsers declare what they understand using the notation from vendor documentation:
|
|
271
|
+
`show bgp [instance <instance>] [vrf (all|<vrf>)] [<afi> [<safi>]] summary`.
|
|
272
|
+
Typed commands are scored against every pattern. Exact keywords beat abbreviations and abbreviations beat
|
|
273
|
+
parameters, so `show interfaces brief` never gets mistaken for `show interfaces <interface>`.
|
|
274
|
+
* **Engines** are tried in order: `native → ntc → genie → generic`. Optional engines are skipped if they
|
|
275
|
+
aren't installed. Use `engines=[...]` to choose your own order, or `strict=True` to accept only a dedicated parser.
|
|
276
|
+
* **Provenance.** Each result has a `confidence` score: 1.0 for native and structured output, 0.9 for
|
|
277
|
+
ntc/Genie, 0.4–0.6 for generic. Automation can decide how much to trust a result.
|
|
278
|
+
|
|
279
|
+
More detail: [docs/architecture.md](docs/architecture.md).
|
|
280
|
+
|
|
281
|
+
## Supported platforms and commands
|
|
282
|
+
|
|
283
|
+
| Platform | Aliases (any of these work) | Dedicated parsers |
|
|
284
|
+
|---|---|---:|
|
|
285
|
+
| Cisco IOS XR (ASR9K, NCS 540/5500/5700, 8000, CRS, XRv 9000, XRd) | `iosxr`, `xr`, `cisco_xr`, `ios-xr`, … | 65 |
|
|
286
|
+
| Juniper Junos / Junos Evolved (MX, PTX, ACX, QFX, EX, SRX, vMX, cRPD) | `junos`, `juniper`, `juniper_junos`, `evo`, … | 48 |
|
|
287
|
+
| Huawei VRP (NE40E/NE8000, CX600, ATN, CE, S, AR) | `vrp`, `huawei`, `huawei_vrp`, `vrpv8`, … | 48 |
|
|
288
|
+
|
|
289
|
+
They cover the commands you run every day: version and inventory, platform and RE/FPC state, CPU, memory,
|
|
290
|
+
power, fans and temperature, interfaces (brief, detail, description, counters, optics/DOM), LAG/LACP, IPv4/IPv6
|
|
291
|
+
addressing, ARP/ND and MAC tables, VLANs, LLDP/CDP, RIB (brief and detail/extensive), BGP (summary, neighbor detail,
|
|
292
|
+
advertised/received routes and BGP RIB, including VRF, instance and address family), OSPF/OSPFv3, IS-IS, MPLS LDP,
|
|
293
|
+
RSVP-TE tunnels and LSPs, LFIB, BFD, HSRP/VRRP, PIM, VRFs and VPN instances, L2VPN xconnects and bridge domains,
|
|
294
|
+
EVPN, firewall filters, ACLs, SRX cluster and policies, NTP, users, alarms, logging, licenses, file systems,
|
|
295
|
+
commit history, startup/patch info and running configuration.
|
|
296
|
+
|
|
297
|
+
The full, generated list is in **[docs/commands.md](docs/commands.md)**. You can also run `clijson commands`.
|
|
298
|
+
|
|
299
|
+
## Normalized models
|
|
300
|
+
|
|
301
|
+
With `normalize=True`, commands that map to a common concept return records with **exactly** these fields on
|
|
302
|
+
every vendor:
|
|
303
|
+
|
|
304
|
+
| Model | Fields |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `system.version` | hostname, vendor, os, version, model, serial_number, uptime, uptime_seconds |
|
|
307
|
+
| `interfaces.brief` | name, admin_status, oper_status, ip_address, vrf, description |
|
|
308
|
+
| `interfaces.detail` | name, admin_status, oper_status, description, mac_address, mtu, bandwidth_kbps, ipv4_addresses, input/output rate & packets & errors |
|
|
309
|
+
| `bgp.summary` | neighbor, remote_as, state, established, uptime, uptime_seconds, prefixes_received, vrf, address_family |
|
|
310
|
+
| `routes` | prefix, protocol, next_hops, distance, metric, vrf, age |
|
|
311
|
+
| `lldp.neighbors` | local_interface, neighbor, neighbor_interface, chassis_id, capabilities, ttl |
|
|
312
|
+
| `ospf.neighbors`, `isis.adjacency`, `ldp.neighbors`, `bfd.sessions`, `arp`, `ipv6.neighbors`, `mac.table`, `lag`, `vrfs`, `inventory`, `cpu`, `interfaces.description` | see [docs/models.md](docs/models.md) |
|
|
313
|
+
|
|
314
|
+
Normalized values are standardised too. Statuses become `up` / `down` / `admin-down`, MACs become
|
|
315
|
+
`aa:bb:cc:dd:ee:ff`, and uptimes, ages and timers are also given in seconds.
|
|
316
|
+
|
|
317
|
+
**Typed and schema'd.** Every model is a `TypedDict` (`clijson.models.BgpNeighbor`, `Route`, `Interface`, ...), so
|
|
318
|
+
editors autocomplete fields and mypy/pyright check your code. Every model also has a JSON Schema (draft 2020-12)
|
|
319
|
+
for consumers in other languages, API contracts or data pipelines:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from typing import cast
|
|
323
|
+
from clijson.models import BgpNeighbor, json_schema, validate
|
|
324
|
+
|
|
325
|
+
peers = cast(list[BgpNeighbor], clijson.parse(text, "show bgp summary", "iosxr", normalize=True).normalized)
|
|
326
|
+
json_schema("bgp.summary") # dict, ready for json.dump / OpenAPI / pydantic / jsonschema
|
|
327
|
+
validate("bgp.summary", peers) # [] when the data matches the model
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
clijson schema # list the models
|
|
332
|
+
clijson schema routes > routes.schema.json # print one
|
|
333
|
+
clijson schema --out schemas/ # export all of them
|
|
334
|
+
clijson parse out.txt -c "show arp" -n | clijson schema arp --check - # validate output in CI
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The generated schemas are also committed under [`schemas/`](schemas/).
|
|
338
|
+
|
|
339
|
+
## Extending
|
|
340
|
+
|
|
341
|
+
Adding a parser takes a decorator and a method. Run `python scripts/fixture.py add` to add a regression test for it:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from typing import Any
|
|
345
|
+
|
|
346
|
+
from clijson import Parser, register
|
|
347
|
+
from clijson.textutils import match_lines
|
|
348
|
+
|
|
349
|
+
@register("iosxr", "show hsrp [<interface>] brief", intent=None)
|
|
350
|
+
class ShowHsrpBrief(Parser):
|
|
351
|
+
"""HSRP groups, state and virtual IP."""
|
|
352
|
+
|
|
353
|
+
def parse(self, text: str) -> list[dict[str, Any]]:
|
|
354
|
+
return [m.groupdict() for m in match_lines(
|
|
355
|
+
r"^\s*(?P<interface>\S+)\s+(?P<group>\d+)\s+(?P<priority>\d+)\s+(?P<state>\w+)\s+(?P<vip>\S+)", text)]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Third-party packages can ship parsers through the `clijson.parsers` entry point. See
|
|
359
|
+
[docs/writing-parsers.md](docs/writing-parsers.md).
|
|
360
|
+
|
|
361
|
+
## Testing against real routers
|
|
362
|
+
|
|
363
|
+
`lab/` has a [containerlab](https://containerlab.dev) topology with Cisco XRd, Juniper vJunos/cRPD and Huawei
|
|
364
|
+
VRP. `scripts/harvest.py` runs every supported command on each device (live or emulated), saves the raw output
|
|
365
|
+
and reports how much of it parsed natively. This is how new OS releases get checked. See
|
|
366
|
+
[lab/README.md](lab/README.md).
|
|
367
|
+
|
|
368
|
+
## Development
|
|
369
|
+
|
|
370
|
+
The project is managed with [uv](https://docs.astral.sh/uv/) (`uv.lock` pins every tool):
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
uv sync # create .venv with the dev dependency group
|
|
374
|
+
uv run pre-commit install # ruff lint + format on every commit
|
|
375
|
+
uv run pytest # ~1100 tests incl. 238 fixtures
|
|
376
|
+
uv run ruff check . && uv run ruff format .
|
|
377
|
+
uv run mypy # strict type check
|
|
378
|
+
uv run scripts/fixture.py check # or `update` after an intentional parser change
|
|
379
|
+
uv run scripts/gen_docs.py # refresh docs/commands.md, docs/models.md and schemas/
|
|
380
|
+
uv run --group docs zensical serve # preview the documentation site
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
384
|
+
|
|
385
|
+
## License
|
|
386
|
+
|
|
387
|
+
MIT. Some regression fixtures were taken from the Apache-2.0 licensed
|
|
388
|
+
[ntc-templates](https://github.com/networktocode/ntc-templates) and
|
|
389
|
+
[genieparser](https://github.com/CiscoTestAutomation/genieparser) projects. See
|
|
390
|
+
[tests/fixtures/NOTICE.md](tests/fixtures/NOTICE.md).
|