#!/usr/bin/python

import os
import sets
from kjbuckets import kjGraph

# TODO: colour edge according to owner trust of node

# the 'validity' is the calculated trust; how certain i can be that
# a key is valid
keytrust_colours = {'u': '0.555 0.25 1',    # ultimate
                    'f': '0.333 0.25 1',    # full
                    'm': '0.166 0.25 1',    # marginal
                    'n': '0.038 0.19 0.94', # never
                    'q': '0 0 1',           # undefined
                    '-': '0 0 1',           # unknown
                    'e': '',                # expired
                    'r': '',                # revoked
                    'd': '0 0 0.95',        # disabled
                    'o': '0 0 1',           # unknown
                    'i': ''}                # invalid

# the 'ownertrust' is how much i can trust the key owner to certify
# others correctly
sigtrust_colours = {'u': '0.555 0.6 0.65',
                    'f': '0.333 0.6 0.65',
                    'm': '0.166 0.6 0.65',
                    'n': '0.038 0.5 0.65',
                    'q': '0 0.0 0.7',
                    '-': '0 0.0 0.7'}

class_styles = {'x': 'solid',
                'l': 'dashed'}

# retrieve public keys

pubkeys = {}
current_key = None
my_key = None

sigs = kjGraph ()
rsigs = kjGraph ()

# holds data about edges. currently whether an adge represents a Local or
# an eXportable signature
classes = {}

def seek_pub (line):
	global my_key, pubkeys, current_key
	if line[0] == 'pub':
		if not line[1] in ['r', 'e', 'i']:
			current_key = line[4]
			if my_key == None:
				my_key = current_key
			pubkeys[current_key] = {'uid': line[9], 'validity': line[1], 'otrust': line[8]}
			return seek_sig

	return seek_pub

def seek_sig (line):
	global current_key, sigs, rsigs
	if line[0] == 'pub':
		return seek_pub (line)
	elif line[0] in ['sig', 'rev']:
		if current_key == line[4]:
			return seek_sig
		elif line[9] == '[User id not found]':
			return seek_sig

		if line[0] == 'sig' and line[1] == '!': # ! indicates a good sig
			sigs.add (line[4], current_key)
			sigclass = line[10][2]
			if not (sigclass == 'l' and classes.get ('%s -> %s' % (line[4], current_key)) == 'x'):
				classes['%s -> %s' % (line[4], current_key)] = sigclass
		elif line[0] == 'rev':
			rsigs.add (line[4], current_key)

	return seek_sig
	
data = os.popen ('gpg --with-colons --check-sigs', 'r')

state = seek_pub
for line in data:
	line = line.split (':')
	state = state (line)

if data.close () != None:
	raise Exception, 'gpg failed to list keys'

# remove revocations

sigs = sigs - rsigs

# print graph

print 'digraph "Personal PGP web of trust" {'
print 'graph [root="%s",overlap="compress",splines="true",sep="0.1"]' % my_key
print 'node [style="filled",shape="box",fontsize="9"]'

# print nodes

for id in pubkeys:
	pk = pubkeys[id]

	label = pk['uid']
	i = label.find ('(')
	if i == -1:
		i = label.find ('<')
	if i != -1:
		label = label[:i - 1]

	print '"%s" [label="%s",fillcolor="%s",fontname="Helvetica"]' % (id, label, keytrust_colours [pk['validity']])

# print edges

cross_sigs = sets.Set ()

for signer in pubkeys:
	#print '// Processing %s' % pubkeys[signer]['uid']
	for signee in pubkeys:
	#print '// \tchecking %s' % pubkeys[signee]['uid']
		if signee in sigs.neighbors (signer):
			# the second check is a workaround for the case where there
			# exists a cross-signature where one signature is local and
			# the other is exportable
			if signer in sigs.neighbors(signee) and classes['%s -> %s' % (signer, signee)] == classes['%s -> %s' % (signee, signer)]:
				#print '// \t\tcross-signed'
				if signer < signee:
					key = '%s<->%s' % (signer, signee)
				else:
					key = '%s<->%s' % (signee, signer)

				if not key in cross_sigs:
					cross_sigs.add (key)
					c1 = sigtrust_colours[pubkeys[signer]['otrust']]
					c2 = sigtrust_colours[pubkeys[signee]['otrust']]
					if c1 != c2:
						colour = '%s:%s' % (c1, c2)
					else:
						colour = c1

					print '"%s" -> "%s" [dir="both",color="%s",style="%s"]' % (signer, signee, colour, class_styles[classes['%s -> %s' % (signer, signee)]])
			else:
				#print '// \t\tsigned'
				print '"%s" -> "%s" [dir="forward",color="%s",style="%s"]' % (signer, signee, sigtrust_colours[pubkeys[signer]['otrust']], class_styles[classes['%s -> %s' % (signer, signee)]])

print '}'
