~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to pastegraph.cgi

  • Committer: Aaron Bentley
  • Date: 2005-08-30 15:02:18 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050830150218-2e6f98bdca4b186e
Added pastegraph

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python2.4
 
2
import sys
 
3
from subprocess import Popen, PIPE
 
4
import cgi
 
5
import cgitb; cgitb.enable()
 
6
from urllib import urlencode
 
7
from xml.sax.saxutils import escape
 
8
import os.path
 
9
 
 
10
REQ_DIR = '/home/abentley/pastegraph'
 
11
 
 
12
class Node(object):
 
13
    def __init__(self, name, color=None):
 
14
        self.name = name
 
15
        self.color = color
 
16
 
 
17
    def define(self):
 
18
        if self.color is not None:
 
19
            return '%s[fillcolor="%s" style="filled"]' % \
 
20
                (self.name, self.color)
 
21
 
 
22
    def __str__(self):
 
23
        return self.name
 
24
 
 
25
def save_request(req):
 
26
    files = [int(f) for f in os.listdir(REQ_DIR)]
 
27
    files.sort()
 
28
    if len(files) == 0:
 
29
        stem = '0' 
 
30
    else:
 
31
        stem = str(files[-1]+1)
 
32
    filename = os.path.join(REQ_DIR, stem)
 
33
    file(filename, 'wb').write(req)
 
34
    return stem
 
35
 
 
36
 
 
37
def load_request(num):
 
38
    return file(os.path.join(REQ_DIR, num), 'rb').read()
 
39
 
 
40
 
 
41
def parsed_link(link):
 
42
    suffixes = {'$': "#ffee99",
 
43
                '!': "#ff0000",
 
44
                '\'': "#eeeeee",
 
45
                '?': "#7799ff",
 
46
    }
 
47
    if link[-1] in suffixes:
 
48
        return link[:-1], suffixes[link[-1]]
 
49
    else:
 
50
        return link, None
 
51
 
 
52
def parse(input):
 
53
    input = input.replace('\n', ' ')
 
54
    input = input.replace('\r', ' ')
 
55
    statements = input.split(' ')
 
56
    relations = []
 
57
    nodes = {}
 
58
    for statement in statements:
 
59
        if len(statement.strip(' ')) == 0:
 
60
            continue
 
61
        links = statement.split('->')
 
62
        new_links = []
 
63
        for link, color in (parsed_link(l) for l in links):
 
64
            if link not in nodes:
 
65
                nodes[link] = Node(link, color)
 
66
            elif color is not None:
 
67
                nodes[link].color = color
 
68
            new_links.append(link)
 
69
        for i in range(len(links)-1):
 
70
            relations.append((nodes[new_links[i]], nodes[new_links[i+1]]))
 
71
    return relations
 
72
 
 
73
def dot_output(my_file, relations):
 
74
    defined = set()
 
75
    yield "digraph G\n"
 
76
    yield "{\n"
 
77
    for (start, end) in relations:
 
78
        if start.name not in defined:
 
79
            defined.add(start.name)
 
80
            my_def = start.define()
 
81
            if my_def is not None:
 
82
                yield "    %s;\n" % my_def
 
83
        if end.name not in defined:
 
84
            defined.add(end.name)
 
85
            my_def = end.define()
 
86
            if my_def is not None:
 
87
                yield "    %s;\n" % my_def
 
88
 
 
89
        yield "    %s->%s;\n" % (start.name, end.name)
 
90
    yield "}\n"
 
91
 
 
92
def invoke_dot(input, out_file=None):
 
93
    cmdline = ['dot', '-Tgif']
 
94
    if out_file is not None:
 
95
        cmdline.extend(('-o', out_file))
 
96
    dot_proc = Popen(cmdline, stdin=PIPE)
 
97
    for line in input:
 
98
        dot_proc.stdin.write(line)
 
99
    dot_proc.stdin.close()
 
100
    return dot_proc.wait()
 
101
 
 
102
def do_form(seqnum):
 
103
    if seqnum is not None:
 
104
        input = load_request(seqnum)
 
105
    else:
 
106
        input = "A->B B->C  A->C$"
 
107
    print "Content-type: text/html;charset=utf-8"
 
108
    print ""
 
109
    if input is not None:
 
110
        img_src = "?" + urlencode({"graphtext": input, "type": "img"})
 
111
        img = '<img src="%s" alt="result graph"/>' % escape(img_src)
 
112
    dotcode = ''.join(list(dot_output(sys.stdout, parse(input))))
 
113
    print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
 
114
<html>
 
115
<head>
 
116
<title>Pastegraph</title>
 
117
</head>
 
118
<body>
 
119
    <p>
 
120
    This is a quikie tool for directed acyclic graphs.
 
121
    It uses a simple 'A->B' notation to describe the graphs.
 
122
    </p>
 
123
    <p>
 
124
       You can use the suffixes !, ', $ and ? to emphasize nodes.  E.g. A$->B
 
125
       will cause A to be yellow.
 
126
    </p>
 
127
    <p>
 
128
    Yes, that's Dot under the covers.
 
129
    </p>
 
130
    <form method="post" action="">
 
131
        <p><textarea name="graphtext" rows="5" cols="40">%s</textarea></p>
 
132
        <p><input type="submit" value="Draw graph"/></p>
 
133
        <p><input type="hidden" name="type" value="save"/></p>
 
134
    </form>
 
135
<div>%s</div>
 
136
<pre>%s</pre>
 
137
</body>
 
138
</html>""" % (input, img, escape(dotcode))
 
139
 
 
140
def save_redirect(input):
 
141
    seqnum = save_request(input)
 
142
    print "Status: 302 Found"
 
143
    print "Location: ?%s" %  urlencode({"n": seqnum})
 
144
 
 
145
 
 
146
def main():
 
147
    form = cgi.FieldStorage()
 
148
    type = form.getfirst("type", "form")
 
149
    input = form.getfirst("graphtext", "A->B B->C  A->C$")
 
150
    seqnum = form.getfirst("n", None)
 
151
    if type == "form":
 
152
        do_form(seqnum)
 
153
    elif type == "save":
 
154
        save_redirect(input)
 
155
    else:
 
156
      print "Content-type: image/gif\n"
 
157
      sys.stdout.flush()
 
158
      invoke_dot(dot_output(sys.stdout, parse(input)))
 
159
main()