#! /usr/bin/env python # # $YahooCvsId: bug2dot.py,v 1.6 2008/11/24 17:02:56 Exp $ # $Source: /CVSROOT/.../bug2dot/src/bug2dot.py,v $ # # Copyright (c) 2008 Yahoo! Inc. # # Originally written by Jan Schaumann in August 2008. # # This program emits a dot language description of the dependency tree for # the given bugs. It is very crudely hacked together, as it's supposed to # be a temporary solution until # http:///show_bug.cgi?id=2003070 is addressed. # # Yes, I know that there's nothing as permanent as a temporary solution. # # Please file bugs and feature requests at: # http:///enter_bug.cgi?product=&component=bug2dot import getopt from os.path import basename import re import string import sys import time import urllib2 ### ### Globals ### EXIT_ERROR = 1 EXIT_SUCCESS = 0 # "private" __BASEURL = 'http:///showdependencytree.cgi?id=' __NODE2COLOR = { 'resolved':'green', 'top-level':'black', 'open':'red' } __NUMBERS_ONLY = False __RESOLVED_BUGS = {} __OPEN_ONLY = False __VERSION = "0.1" ### ### Subroutines ### def dehtmlify(input): """crudely remove html tags""" output = re.sub('<.*?>', '', input.strip()) output = re.sub('"', '\\\"', output) return output def dotFooter(): """print the end of the dot file""" print '}' print '// %s' % time.ctime() print '// end dot description generated by bug2dot version %s' % __VERSION def dotHeader(): """print the beginning for the dot file""" print '// begin dot description generated by bug2dot version %s' % __VERSION print '// %s' % " ".join(sys.argv) print '// %s' % time.ctime() print 'digraph "bugs" {' def dotify(bug): """fetch the dependency tree for the given bug and generate a dot language description on stdout""" global __NUMBERS_ONLY, __RESOLVED_BUGS current = bug inLink = 0 nextSummary = False resolved = False reverse = False parent = bug grandparent = bug bugs = [ bug ] edgesSeen = [] nodesSeen = [] print '// --- Begin %s ---' % bug if __NUMBERS_ONLY: printNode(bug, 'top-level', 'Bug ' + bug) nodesSeen.append(bug) url = urllib2.urlopen(__BASEURL + bug) for line in url: if not __NUMBERS_ONLY: if (line.find('

Dependency tree for ') != -1): nextSummary = True continue if nextSummary: desc = "Bug " + bug + ": " + dehtmlify(line) printNode(bug, 'top-level', desc) nodesSeen.append(bug) nextSummary = False if (line.find('blocks

') != -1): reverse = True if (line.find('
    ') != -1): grandparent = parent parent = current if (line.find('
') != -1): parent = grandparent grandparent = bug if not bugs: parent = bug break if (line.find('') != -1): resolved = True if (line.find('[0-9]+)">[0-9]+') rem = bp.match(line) if (rem): current = rem.group('child') bugs.append(current) inLink = 1 continue if (inLink == 1): inLink += 1 continue if (line.find('') != -1): if len(bugs) > 1: previous = bugs.pop() last = bugs[len(bugs) - 1] else: continue if reverse: printEdge(bug, last, previous, edgesSeen) else: printEdge(bug, previous, last, edgesSeen) if (inLink == 2): type = "open" if (resolved): type = "resolved" __RESOLVED_BUGS[current] = 1 if not current in nodesSeen: nodesSeen.append(current) desc = "Bug " + current if not __NUMBERS_ONLY: desc = desc + ": " + dehtmlify(line) printNode(current, type, desc) if current != parent: if reverse: if len(bugs) != 2: printEdge(bug, parent, current, edgesSeen) else: printEdge(bug, current, parent, edgesSeen) resolved = False inLink = 0 url.close() print '// --- End %s ---' % bug def handleStdin(): """read items from stdin, split by whitespace, then traverse""" rl = sys.stdin.readlines lines = rl(1024) while lines: for line in lines: for word in line.split(): dotify(word) lines = rl(1024) def main(): """parse command-line options and act on them""" args = parseOptions() if not args: usage() sys.exit(EXIT_ERROR) # NOTREACHED dotHeader() for a in args: if a == '-': handleStdin() else: dotify(a) dotFooter() return EXIT_SUCCESS def parseOptions(): """parse command-line options via getopt, return remaining args""" global __NUMBERS_ONLY, __OPEN_ONLY try: opts, args = getopt.getopt(sys.argv[1:], "hno") except getopt.GetoptError: usage() sys.exit(EXIT_ERROR) # NOTREACHED for o, a in opts: if o in ("-h"): usage() sys.exit(EXIT_SUCCESS) # NOTREACHED if o in ("-n"): __NUMBERS_ONLY = True if o in ("-o"): __OPEN_ONLY = True return args def printEdge(top, a, b, seen): """print an edge entry from a to b iff not in seen; append tuple to seen""" global __OPEN_ONLY, __RESOLVED_BUGS style = '' if __OPEN_ONLY: if a in __RESOLVED_BUGS: return if b in __RESOLVED_BUGS: style = ',style="dotted"' b = top t = (a, b) if not t in seen: seen.append(t) print '"%s" -> "%s" [color="black"%s]; // EDGE' % (a, b, style) def printNode(a, type, desc): """print a node entry""" global __NODE2COLOR, __OPEN_ONLY if (type == "resolved") and __OPEN_ONLY: return print '"%s" [color="%s",label="%s"]; // NODE' % (a, __NODE2COLOR[type], desc) def usage(): """print short usage""" print 'Usage: %s [-hno] bug [bug [...]]' % basename(sys.argv[0]) print '\t-h Print a usage statement and exit.' print '\t-n Only use numbers for all bugs.' print '\t-o Only display open bugs.' ### ### Main ### if __name__ == "__main__": sys.exit(main())