Created
October 27, 2015 17:22
-
-
Save azimut/07bd3e0968ddb6b4c214 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# A simple script to convert an LDIF file to DOT format for drawing graphs. | |
# Copyright 2009 Marcin Owsiany <[email protected]> | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation; either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License along | |
# with this program; if not, write to the Free Software Foundation, Inc., | |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
"""A simple script to convert an LDIF file to DOT format for drawing graphs. | |
So far it only supports the most basic form of entry records: "attrdesc: value". | |
In particular line continuations, BASE64 or other encodings, change records, | |
include statements, etc... are not supported. | |
Example usage, assuming your DIT's base is dc=nodomain: | |
ldapsearch -x -b 'dc=nodomain' | \\ | |
ldif2dot | \\ | |
dot -o nodomain.png -Nshape=box -Tpng /dev/stdin | |
""" | |
import sys | |
class Element(object): | |
"""Represents an LDIF entry.""" | |
def __init__(self): | |
"""Initializes an object.""" | |
self.attributes = [] | |
def __repr__(self): | |
"""Returns a basic state dump.""" | |
return 'Element' + str(self.index) + str(self.attributes) | |
def add(self, line): | |
"""Adds a line of input to the object. | |
Args: | |
- line: a string with trailing newline stripped | |
Returns: True if this object is ready for processing (i.e. a separator | |
line was passed). Otherwise returns False. Behaviour is undefined if | |
this method is called after a previous invocation has returned True. | |
""" | |
def _valid(line): | |
return line and not line.startswith('#') | |
def _interesting(line): | |
return line != 'objectClass: top' | |
if self.is_valid() and not _valid(line): | |
return True | |
if _valid(line) and _interesting(line): | |
self.attributes.append(line) | |
return False | |
def is_valid(self): | |
"""Indicates whether a valid entry has been read.""" | |
return len(self.attributes) != 0 and self.attributes[0].startswith('dn: ') | |
def dn(self): | |
"""Returns the DN for this entry.""" | |
if self.attributes[0].startswith('dn: '): | |
return self.attributes[0][4:] | |
else: | |
return None | |
def edge(self, dnmap): | |
"""Returns a text represenation of a grapsh edge. | |
Finds its parent in provided dnmap (dictionary mapping dn names to | |
Element objects) and returns a string which declares a DOT edge, or an | |
empty string, if no parent was found. | |
""" | |
dn_components = self.dn().split(',') | |
for i in range(1, len(dn_components) + 1): | |
parent = ','.join(dn_components[i:]) | |
if parent in dnmap: | |
return ' n%d->n%d\n' % (dnmap[parent].index, self.index) | |
return '' | |
def dot(self, dnmap): | |
"""Returns a text representation of the node and perhaps its parent edge. | |
Args: | |
- dnmap: dictionary mapping dn names to Element objects | |
""" | |
return ' n%d [label="%s\\l"]\n%s' % (self.index, '\\l'.join(self.attributes), self.edge(dnmap)) | |
class Converter(object): | |
"""An LDIF to DOT converter.""" | |
def __init__(self): | |
"""Initializes the object.""" | |
self.elements = [] | |
self.dnmap = {} | |
def _append(self, e): | |
"""Adds an element to internal list and map. | |
First sets it up with an index in the list, for node naming. | |
""" | |
index = len(self.elements) | |
e.index = index | |
self.elements.append(e) | |
self.dnmap[e.dn()] = e | |
def parse(self, file, name): | |
"""Reads the given file into memory. | |
Args: | |
- file: an object which yields text lines on iteration. | |
- name: a name for the graph | |
Returns a string containing the graph in DOT format. | |
""" | |
e = Element() | |
for line in file: | |
line = line.rstrip() | |
if e.add(line): | |
self._append(e) | |
e = Element() | |
if e.is_valid(): | |
self._append(e) | |
return ('strict digraph "%s" {\n rankdir=LR\n%s}\n' | |
% (name, ''.join([e.dot(self.dnmap) for e in self.elements]))) | |
if __name__ == '__main__': | |
if len(sys.argv) > 2: | |
raise 'Expected at most one argument.' | |
elif len(sys.argv) == 2: | |
name = sys.argv[1] | |
file = open(sys.argv[1], 'r') | |
else: | |
name = '<stdin>' | |
file = sys.stdin | |
print Converter().parse(file, name) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment