Skip to content

Commit c717cd3

Browse files
committed
feature Yaml + XPath search
1 parent 1426f5e commit c717cd3

10 files changed

Lines changed: 1314 additions & 822 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ This is a major release focused on modernizing the project infrastructure and to
2222
-**Type hints** - Improved IDE support and type safety
2323
-**py.typed** marker - PEP 561 compliance
2424
-**Modern CI/CD** - GitHub Actions with uv
25+
-**YAML output format** - Cleaner hierarchical structure with two-level split optimization
26+
-**XPath queries** - NSO-style queries for YAML-formatted configurations
27+
-**XPath context tracking** - Three context options (none/partial/full) to identify wildcard match sources
28+
-**XPath path tracking** - XPathResult.paths shows path components to each match
2529
-**MODERNIZATION_GUIDE.md** - Comprehensive migration guide
2630
-**BUSINESS_STANDARDS.md** - Enterprise compliance documentation
2731
-**PYTHON_COMPATIBILITY.md** - Version support documentation

README.md

Lines changed: 151 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ shconfparser is a vendor independent library where you can parse the following f
2828
- Table structure *`i.e. show ip interface`*
2929
- Data *`i.e. show version`*
3030

31+
YAML Format Output
32+
33+
![show run to YAML structure](https://raw.githubusercontent.com/kirankotari/shconfparser/master/asserts/img/sh_run_yaml.png)
34+
3135
Tree Structure
3236

3337
![show run to tree structure](https://raw.githubusercontent.com/kirankotari/shconfparser/master/asserts/img/sh_run.png)
@@ -43,7 +47,8 @@ Table Structure
4347
🔒 **Type Safe** - Full type hints and py.typed marker
4448
🎯 **Vendor Independent** - Works with any network device configuration
4549
📊 **Multiple Formats** - Parse trees, tables, and unstructured data
46-
🔍 **XPath Queries** - Search configs with XPath-style syntax (NEW!)
50+
📄 **Format Flexibility** - Output as JSON or YAML structures
51+
🔍 **XPath Queries** - NSO-style queries with context tracking (NEW!)
4752
🧪 **Well Tested** - 80%+ code coverage, tested on Python 3.8-3.13
4853

4954
## Quick Start
@@ -62,27 +67,30 @@ uv pip install shconfparser
6267

6368
### Basic Usage
6469

65-
**Single show command:**
70+
**Single show command with YAML format (recommended):**
6671
```python
6772
from shconfparser.parser import Parser
6873

69-
p = Parser()
74+
# Use YAML format for cleaner output and XPath support
75+
p = Parser(output_format='yaml')
7076
data = p.read('running_config.txt')
7177

7278
# Parse directly (no split needed for single show running command)
7379
tree = p.parse_tree(data)
7480
print(p.dump(tree, indent=2))
81+
82+
# Query with XPath
83+
result = p.xpath('/hostname')
84+
print(result.data) # 'R1'
7585
```
7686

7787
<details>
78-
<summary>Alternative: Access internal properties</summary>
88+
<summary>Alternative: JSON format (backward compatible)</summary>
7989

8090
```python
81-
p = Parser()
82-
p.read('running_config.txt')
83-
84-
# Access reader data directly
85-
tree = p.parse_tree(p.r.data)
91+
p = Parser() # Default is JSON format (OrderedDict)
92+
data = p.read('running_config.txt')
93+
tree = p.parse_tree(data)
8694
print(p.dump(tree, indent=4))
8795
```
8896
</details>
@@ -91,7 +99,7 @@ print(p.dump(tree, indent=4))
9199
```python
92100
from shconfparser.parser import Parser
93101

94-
p = Parser()
102+
p = Parser(output_format='yaml') # YAML format recommended
95103
data = p.read('multiple_commands.txt') # Contains multiple show outputs
96104
data = p.split(data) # Split into separate commands
97105
data.keys()
@@ -208,60 +216,157 @@ print(match)
208216
# {'Device ID': 'R2', 'Local Intrfce': 'Fas 0/0', ...}
209217
```
210218

211-
### XPath-Style Queries (New in 3.0!)
219+
### Output Format Selection (New in 3.0!)
212220

213-
Query parsed configuration trees using XPath-style syntax:
221+
Parse configurations to JSON (OrderedDict) or YAML-friendly dict structures:
214222

215223
```python
216224
from shconfparser.parser import Parser
217225

226+
# Default: JSON format (OrderedDict - backward compatible)
218227
p = Parser()
219-
data = p.read('multiple_commands.txt')
220-
data = p.split(data)
228+
data = p.read('running_config.txt')
229+
tree = p.parse_tree(data) # Returns OrderedDict
230+
print(type(tree)) # <class 'collections.OrderedDict'>
221231

222-
# Parse the running config into a tree
223-
data['running'] = p.parse_tree(data['running'])
232+
# YAML format: cleaner hierarchical structure
233+
p = Parser(output_format='yaml')
234+
data = p.read('running_config.txt')
235+
tree_yaml = p.parse_tree(data) # Returns dict with nested structure
236+
print(type(tree_yaml)) # <class 'dict'>
237+
238+
# Override format per call
239+
p = Parser() # Default is JSON
240+
tree_json = p.parse_tree(data) # OrderedDict
241+
tree_yaml = p.parse_tree(data, format='yaml') # dict
242+
243+
# YAML structure example:
244+
# Input: "interface FastEthernet0/0" with nested config
245+
# JSON: {"interface FastEthernet0/0": {...}}
246+
# YAML: {"interface": {"FastEthernet0/0": {...}}}
247+
```
224248

225-
# Now use XPath queries on the parsed tree
226-
# Find all IP addresses anywhere in config
227-
result = p.xpath('//ip/address', tree=data['running'])
228-
print(f"Found {result.count} IP addresses")
249+
**Format Comparison:**
229250

230-
# Get specific interface configuration
231-
result = p.xpath('/interface/FastEthernet0-0/ip/address', tree=data['running'])
232-
if result:
233-
print(f"IP: {result.data}")
251+
```python
252+
# JSON format (default) - preserves exact CLI structure
253+
{
254+
"interface FastEthernet0/0": {
255+
"ip address 1.1.1.1 255.255.255.0": "",
256+
"duplex auto": ""
257+
}
258+
}
259+
260+
# YAML format - hierarchical and human-readable
261+
{
262+
"interface": {
263+
"FastEthernet0/0": {
264+
"ip": {
265+
"address": "1.1.1.1 255.255.255.0"
266+
},
267+
"duplex": "auto"
268+
}
269+
}
270+
}
271+
```
234272

235-
# List all interface names using wildcard
236-
result = p.xpath('/interface/*', tree=data['running'])
237-
print(f"Interfaces: {', '.join(result.matches)}")
273+
**Benefits of YAML format:**
274+
- Cleaner hierarchy for nested configurations
275+
- Better for programmatic access
276+
- Easier to convert to actual YAML files
277+
- Natural structure for complex configs
278+
- Required for XPath queries
238279

239-
# Filter interfaces by pattern
240-
result = p.xpath('/interface[FastEthernet*]/description', tree=data['running'])
241-
for desc in result.matches:
242-
print(f" - {desc}")
243-
```
280+
### XPath Queries (New in 3.0!)
244281

245-
**Supported XPath Features:**
246-
- **Absolute paths**: `/interface/FastEthernet0-0/ip/address`
247-
- **Recursive search**: `//ip/address` (find anywhere)
248-
- **Wildcards**: `/interface/*/description` (all interfaces)
249-
- **Predicates**: `/interface[GigabitEthernet*]` (pattern matching)
250-
- **Root access**: `/` (entire tree)
282+
Query YAML-formatted configurations using NSO-style XPath with optional context tracking:
251283

252-
**Or use stored tree directly:**
253284
```python
254-
p = Parser()
285+
from shconfparser.parser import Parser
286+
287+
# XPath requires YAML format
288+
p = Parser(output_format='yaml')
255289
data = p.read('running_config.txt')
256-
p.parse_tree(data) # Stores in p.data
290+
tree = p.parse_tree(data)
291+
292+
# Simple queries
293+
result = p.xpath('/hostname')
294+
print(result.data) # 'R1'
257295

258-
# Query uses stored tree automatically
259-
result = p.xpath('//ip/address')
260-
for addr in result.matches:
261-
print(addr)
296+
# Wildcards - find all interface duplex settings
297+
result = p.xpath('/interface/*/duplex')
298+
print(result.matches) # ['auto', 'auto']
299+
print(result.count) # 2
300+
301+
# Predicates with slashes (network interface names)
302+
result = p.xpath('/interface[FastEthernet0/0]/duplex')
303+
print(result.data) # 'auto'
304+
305+
# Recursive search - find anywhere in tree
306+
result = p.xpath('//duplex')
307+
print(result.matches) # ['auto', 'auto']
308+
309+
# Predicate wildcards
310+
result = p.xpath('/interface[FastEthernet*]/ip/address')
311+
print(result.data) # '1.1.1.1 255.255.255.0'
312+
```
313+
314+
**Context Options** - Solve the "which interface?" problem:
315+
316+
```python
317+
# Problem: Can't identify source with wildcards
318+
result = p.xpath('/interface/*/duplex')
319+
print(result.matches) # ['auto', 'auto'] - Which interface?
320+
321+
# Solution 1: context='none' (default - just values)
322+
result = p.xpath('/interface/*/duplex', context='none')
323+
print(result.matches) # ['auto', 'auto']
324+
325+
# Solution 2: context='partial' (from wildcard match point)
326+
result = p.xpath('/interface/*/duplex', context='partial')
327+
print(result.matches)
328+
# [{'FastEthernet0/0': {'duplex': 'auto'}},
329+
# {'FastEthernet0/1': {'duplex': 'auto'}}]
330+
331+
# Solution 3: context='full' (complete tree hierarchy)
332+
result = p.xpath('/interface/*/duplex', context='full')
333+
print(result.matches)
334+
# [{'interface': {'FastEthernet0/0': {'duplex': 'auto'}}},
335+
# {'interface': {'FastEthernet0/1': {'duplex': 'auto'}}}]
336+
337+
# Path tracking (always available)
338+
result = p.xpath('/interface/*/speed')
339+
print(result.paths)
340+
# [['interface', 'FastEthernet0/0', 'speed'],
341+
# ['interface', 'FastEthernet0/1', 'speed']]
342+
```
343+
344+
**XPath Features:**
345+
- ✅ Absolute paths: `/interface/FastEthernet0/0/duplex`
346+
- ✅ Recursive search: `//duplex` (find anywhere)
347+
- ✅ Wildcards: `/interface/*/duplex`
348+
- ✅ Predicates: `/interface[FastEthernet0/0]`
349+
- ✅ Predicate wildcards: `/interface[FastEthernet*]`
350+
- ✅ Context tracking: See which match came from where
351+
- ✅ Path tracking: `result.paths` shows path components
352+
353+
**XPathResult Structure:**
354+
```python
355+
result = p.xpath('//duplex')
356+
print(result.success) # True
357+
print(result.data) # First match: 'auto'
358+
print(result.matches) # All matches: ['auto', 'auto']
359+
print(result.count) # Number of matches: 2
360+
print(result.query) # Original query: '//duplex'
361+
print(result.paths) # Path to each match
362+
print(result.error) # Error message if failed
363+
364+
# Boolean check
365+
if result:
366+
print(f"Found {result.count} matches")
262367
```
263368

264-
See [docs/XPATH_GUIDE.md](docs/XPATH_GUIDE.md) for complete XPath documentation.
369+
**Note:** XPath queries only work with `output_format='yaml'`. JSON format (OrderedDict) preserves exact CLI structure and should use traditional dict navigation.
265370

266371
### Alternative: Using Individual Components
267372

asserts/img/sh_run_yaml.png

339 KB
Loading

docs/QUICKSTART.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,24 @@ make check-all
9191
3. **Write tests** - For any new features or bug fixes
9292
4. **Keep it simple** - Small, focused commits are easier to review
9393

94+
## Quick Feature Test
95+
96+
Try the XPath feature:
97+
```python
98+
from shconfparser import Parser
99+
100+
p = Parser(output_format='yaml')
101+
data = p.read('data/shrun.txt')
102+
tree = p.parse_tree(data)
103+
104+
# Simple query
105+
result = p.xpath('/hostname')
106+
print(f"Hostname: {result.data}")
107+
108+
# Query with context
109+
result = p.xpath('/interface/*/duplex', context='partial')
110+
for match in result.matches:
111+
print(match) # Shows which interface
112+
```
113+
94114
Happy coding! 🚀

docs/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Welcome to the shconfparser documentation!
1414

1515
| Document | Description | Audience |
1616
|----------|-------------|----------|
17+
| [XPath Guide](XPATH_GUIDE.md) | Complete XPath query documentation | All users |
1718
| [Modernization Guide](MODERNIZATION_GUIDE.md) | Complete v2.x to v3.0 migration guide | Existing users, developers |
1819
| [Architecture](ARCHITECTURE.md) | Visual architecture diagrams and structure | Developers, architects |
1920
| [Business Standards](BUSINESS_STANDARDS.md) | Enterprise compliance documentation | Maintainers, enterprises |
@@ -31,6 +32,7 @@ Welcome to the shconfparser documentation!
3132
1. Start with the main [README.md](../README.md)
3233
2. Follow [Installation instructions](../README.md#installation-and-downloads)
3334
3. Check [Usage Examples](../README.md#usage-examples)
35+
4. Try [XPath Queries](../README.md#xpath-queries-new-in-30) for advanced querying
3436

3537
### For Contributors
3638
1. Read [QUICKSTART.md](QUICKSTART.md) (5 minutes)
@@ -95,6 +97,7 @@ Detailed summary of the modernization project:
9597
### I want to...
9698

9799
- **Get started contributing**[QUICKSTART.md](QUICKSTART.md)
100+
- **Learn XPath queries**[XPATH_GUIDE.md](XPATH_GUIDE.md)
98101
- **Understand v3.0 changes**[MODERNIZATION_GUIDE.md](MODERNIZATION_GUIDE.md)
99102
- **Migrate from v2.x**[MODERNIZATION_GUIDE.md](MODERNIZATION_GUIDE.md#migration-from-v2x-to-v30)
100103
- **Learn about the architecture**[ARCHITECTURE.md](ARCHITECTURE.md)

0 commit comments

Comments
 (0)