1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
#
# Copyright (C) 2000 Stephen Davies
# Copyright (C) 2000 Stefan Seefeld
# All rights reserved.
# Licensed to the public under the terms of the GNU LGPL (>= 2),
# see the file COPYING for details.
#
from Synopsis.Processor import Parameter
from Synopsis import AST, Type, Util
from Synopsis.Formatters.HTML.View import View
from Synopsis.Formatters.HTML.Tags import *
import os
class DeclarationFinder(Type.Visitor):
def __init__(self, types, verbose):
self.types = types
self.verbose = verbose
def __call__(self, name):
try:
typeobj = self.types[name]
except KeyError:
# Eg: Unknown parent which has been NameMapped
if self.verbose: print "Warning: %s not found in type dict."%(name,)
return None
self.__decl = None
typeobj.accept(self)
#if self.__decl is None:
# return None
return self.__decl
def visitBaseType(self, type): return
def visitUnknown(self, type): return
def visitDeclared(self, type): self.__decl = type.declaration()
def visitModifier(self, type): type.alias().accept(self)
def visitArray(self, type): type.alias().accept(self)
def visitTemplate(self, type): self.__decl = type.declaration()
def visitParametrized(self, type): type.template().accept(self)
def visitFunctionType(self, type): return
def find_common_name(graph):
common_name = list(graph[0])
for decl_name in graph[1:]:
if len(common_name) > len(decl_name):
common_name = common_name[:len(decl_name)]
for i in range(min(len(decl_name), len(common_name))):
if decl_name[i] != common_name[i]:
common_name = common_name[:i]
break
return string.join(common_name, '::')
class InheritanceGraph(View):
min_size = Parameter(2, 'minimum number of nodes for a graph to be displayed')
min_group_size = Parameter(5, 'how many nodes to put into a group')
direction = Parameter('vertical', 'layout of the graph')
def register(self, processor):
View.register(self, processor)
self.decl_finder = DeclarationFinder(processor.ast.types(), processor.verbose)
self.processor.add_root_view(self.filename(), 'Inheritance Graph', 'main', 1)
def filename(self): return self.processor.file_layout.special('InheritanceGraph')
def title(self): return 'Synopsis - Class Hierarchy'
def consolidate(self, graphs):
"""Consolidates small graphs into larger ones"""
# Weed out the small graphs and group by common base name
common = {}
for graph in graphs:
len_graph = len(graph)
if len_graph < self.min_size:
# Ignore the graph
continue
common.setdefault(find_common_name(graph), []).append(graph)
# Consolidate each group
for name, graphs in common.items():
conned = []
pending = []
for graph in graphs:
# Try adding to an already pending graph
for pend in pending:
if len_graph + len(pend) <= self.min_group_size:
pend.extend(graph)
graph = None
if len(pend) == self.min_group_size:
conned.append(pend)
pending.remove(pend)
break
if graph:
if len_graph >= self.min_group_size:
# Add to final list
conned.append(graph)
else:
# Add to pending list
pending.append(graph)
graphs[:] = conned + pending
return common
def process(self, start):
"""Creates a file with the inheritance graph"""
filename = self.filename()
self.start_file()
self.write(self.processor.navigation_bar(filename))
self.write(entity('h1', "Inheritance Graph"))
try:
from Synopsis.Formatters import Dot
except:
print "InheritanceGraph: Can't load the Dot formatter"
self.end_file()
return
# Create a toc file for Dot to use
toc_file = filename + "-dot.toc"
self.processor.toc.store(toc_file)
graphs = self.processor.class_tree.graphs()
count = 0
# Consolidate the graphs, and sort to make the largest appear first
lensorter = lambda a, b: cmp(len(b),len(a))
common_graphs = self.consolidate(graphs)
names = common_graphs.keys()
names.sort()
for name in names:
graphs = common_graphs[name]
graphs.sort(lensorter)
if name:
self.write('<div class="inheritance-group">')
scoped_name = string.split(name,'::')
type_str = ''
types = self.processor.ast.types()
type = types.get(scoped_name, None)
if isinstance(type, Type.Declared):
type_str = type.declaration().type() + ' '
self.write('Graphs in '+type_str+name+':<br/>')
for graph in graphs:
try:
if self.processor.verbose: print "Creating graph #%s - %s classes"%(count,len(graph))
# Find declarations
declarations = map(self.decl_finder, graph)
declarations = filter(lambda x: x is not None, declarations)
# Call Dot formatter
output = os.path.join(self.processor.output,
os.path.splitext(self.filename())[0]) + '-%s'%count
dot = Dot.Formatter()
ast = AST.AST({}, declarations, self.processor.ast.types())
dot.process(ast,
output=output,
format='html',
toc_in=[toc_file],
base_url=self.filename(),
title='Synopsis %s'%count,
#-n, FIXME : what does the 'no_descend' option do ?
# do we need to expose that through a parameter ?
layout=self.direction)
#args = ('-i', '-f', 'html', '-o', output, '-r', toc_file,
# '-R', self.filename(), '-t', 'Synopsis %s'%count, '-n',
# '-p', name, '-d', self.direction)
#Dot.format(args, temp_ast, None)
dot_file = open(output + '.html', 'r')
self.write(dot_file.read())
dot_file.close()
os.remove(output + ".html")
except:
import traceback
traceback.print_exc()
print "Graph:",graph
print "Declarations:",declarations
count = count + 1
if name:
self.write('</div>')
os.remove(toc_file)
self.end_file()
|