You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
birdvisu/birdvisu/ospffile.py

91 lines
2.6 KiB
Python

"""
A simple implementation of parsing and creating BIRD's OSPF state descriptions
The code-facing data structure is list of directives, which are tuples of string
(directive) and list of sub-directives. For example, the following input:
```
BIRD 2.0.9 ready.
area 0.0.0.0
router 172.23.100.1
distance 30
network [172.23.100.4-3] metric 10
network [172.23.100.10-2] metric 10
```
would be parsed into:
```python3
[('BIRD 2.0.9 ready.', []),
('area 0.0.0.0', [
('router 172.23.100.1', [
('distance 30', []),
('network [172.23.100.4-3] metric 10', []),
('network [172.23.100.10-2] metric 10', []),
]),
]), # end of area directive
] # end of the whole representation
```
We expect that users will read the representation sequentially, so we do not
provide any means of accessing individual sub-directives directly.
Here we only deal with syntax. What the directives mean and how to act on them
is up to modules using this. Therefore, we only handle strings and do not try
to understand them here. We only strip comments and whitespace. (We are
guessing here that the '#' symbol will not appear in real BIRD output.)
We use lists instead of tuples just for convenience. When encoding, we SHOULD
support other sequences as well. Our users likewise SHOULD not depend on this
being a list. Supplying a string instead of a tuple with an empty sequence is
supported and equivalent to specifying the tuple, both representing a directive
without children. Using empty list is currently considered canonical.
We provide functions dump, dumps, load and loads, similar to the API of json,
marshal, pickle and possibly others. Note however that unlike those, we cannot
represent arbitrary data, just the tuple-list trees described above.
"""
import re
def _tree_lines(tree: list, depth):
result = []
for child in tree:
if isinstance(child, str):
node = child
subtree = []
else:
node, subtree = child
result.append('\t'*depth + node)
result.extend(_tree_lines(subtree, depth+1))
return result
def dumps(tree):
return '\n'.join(_tree_lines(tree, 0))
def dump(tree, fp):
fp.write(dumps(tree))
def loads(s):
line_regex = re.compile(r'(\t*)([^#]*)(#.*)?')
result = []
open_directives = [result]
for line in s.splitlines():
indent, directive, comment = line_regex.match(line).groups()
directive = directive.strip()
indent = len(indent)
if directive == '': continue
new_node = (directive, [])
open_directives = open_directives[:indent+1]
open_directives[indent].append(new_node)
open_directives.append(new_node[1])
return result
def load(fp):
return loads(fp.read())