commit 9edd3dea0299197cf666fa26a4cada93afa786b8 Author: Pavel 'LEdoian' Turinsky Date: Wed Sep 28 08:23:28 2022 +0200 Add module for parsing BIRD's OSPF dumps diff --git a/birdvisu/ospffile.py b/birdvisu/ospffile.py new file mode 100644 index 0000000..abc66d7 --- /dev/null +++ b/birdvisu/ospffile.py @@ -0,0 +1,90 @@ +""" +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()) + +