#!/usr/bin/env python2.4
import sys
from subprocess import Popen, PIPE
import cgi
import cgitb; cgitb.enable()
from urllib import urlencode
from xml.sax.saxutils import escape
import os.path
import re
REQ_DIR = '/home/abentley/pastegraph'
class Node(object):
def __init__(self, name, color=None):
self.name = name
self.color = color
def define(self):
if self.color is not None:
return '%s[fillcolor="%s" style="filled"]' % \
(self.name, self.color)
def __str__(self):
return self.name
def save_request(req):
files = [int(f) for f in os.listdir(REQ_DIR)]
files.sort()
if len(files) == 0:
stem = '0'
else:
stem = str(files[-1]+1)
filename = os.path.join(REQ_DIR, stem)
file(filename, 'wb').write(req)
return stem
def load_request(num):
return file(os.path.join(REQ_DIR, num), 'rb').read()
def parsed_link(link):
suffixes = {'$': "#ffee99",
'!': "#ff0000",
'\'': "#eeeeee",
'?': "#7799ff",
}
if link[-1] in suffixes:
return link[:-1], suffixes[link[-1]]
else:
return link, None
def parse(input):
input = input.replace('\n', ' ')
input = input.replace('\r', ' ')
input = re.sub(' *-> *', '->', input)
statements = input.split(' ')
relations = []
nodes = {}
for statement in statements:
if len(statement.strip(' ')) == 0:
continue
links = statement.split('->')
new_links = []
for link, color in (parsed_link(l) for l in links):
if link not in nodes:
nodes[link] = Node(link, color)
elif color is not None:
nodes[link].color = color
new_links.append(link)
for i in range(len(links)-1):
relations.append((nodes[new_links[i]], nodes[new_links[i+1]]))
return relations
def dot_output(my_file, relations):
defined = set()
yield "digraph G\n"
yield "{\n"
for (start, end) in relations:
if start.name not in defined:
defined.add(start.name)
my_def = start.define()
if my_def is not None:
yield " %s;\n" % my_def
if end.name not in defined:
defined.add(end.name)
my_def = end.define()
if my_def is not None:
yield " %s;\n" % my_def
yield " %s->%s;\n" % (start.name, end.name)
yield "}\n"
def invoke_dot(input, out_file=None):
cmdline = ['dot', '-Tgif']
if out_file is not None:
cmdline.extend(('-o', out_file))
dot_proc = Popen(cmdline, stdin=PIPE)
for line in input:
dot_proc.stdin.write(line)
dot_proc.stdin.close()
return dot_proc.wait()
def do_form(seqnum):
if seqnum is not None and '/' not in seqnum:
input = load_request(seqnum)
else:
input = "A->B B->C A->C$"
print "Content-type: text/html;charset=utf-8"
print ""
if input is not None:
img_src = "?" + urlencode({"graphtext": input, "type": "img"})
img = '' % escape(img_src)
dotcode = ''.join(list(dot_output(sys.stdout, parse(input))))
print """
This is a quikie tool for directed acyclic graphs. It uses a simple 'A->B' notation to describe the graphs.
You can use the suffixes !, ', $ and ? to emphasize nodes. E.g. A$->B will cause A to be yellow.
Yes, that's Dot under the covers.
%s""" % (input, img, escape(dotcode)) def save_redirect(input): seqnum = save_request(input) print "Status: 302 Found" print "Location: ?%s" % urlencode({"n": seqnum}) print def main(): form = cgi.FieldStorage() type = form.getfirst("type", "form") input = form.getfirst("graphtext", "A->B B->C A->C$") seqnum = form.getfirst("n", None) if type == "form": do_form(seqnum) elif type == "save": save_redirect(input) else: print "Content-type: image/gif\n" sys.stdout.flush() invoke_dot(dot_output(sys.stdout, parse(input))) main()