File: InheritanceGraph.py

package info (click to toggle)
synopsis 0.8.0-5
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 10,112 kB
  • ctags: 12,996
  • sloc: cpp: 34,254; ansic: 33,620; python: 10,975; sh: 7,261; xml: 6,369; makefile: 773; asm: 445
file content (180 lines) | stat: -rw-r--r-- 6,784 bytes parent folder | download | duplicates (2)
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()