#!/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()