CMS 3D CMS Logo

DOTExport.py
Go to the documentation of this file.
1 import sys
2 import os
3 import logging
4 import random
5 import subprocess
6 import re
7 import struct
8 
10 import FWCore.ParameterSet.Config as cms
11 import FWCore.ParameterSet.Modules as mod
12 import FWCore.ParameterSet.Types as typ
13 import FWCore.ParameterSet.Mixins as mix
14 
15 from Vispa.Plugins.ConfigEditor.ConfigDataAccessor import ConfigDataAccessor
16 from FWCore.GuiBrowsers.FileExportPlugin import FileExportPlugin
17 
19  def __init__(self,data,options,shapes):
20  self.data = data
21  self.options = options
22  self.shapes = shapes
23  self.nodes={}
24  #lists of starts, ends of paths for path-endpath and source-path connections
25  self.pathstarts=[]
26  self.pathends=[]
27  self.endstarts=[]
28  self.toplevel = self.getTopLevel()
29 
30  def getTopLevel(self):
31 
32  #build a dictionary of available top-level objects
33  all_toplevel={}
34  if self.data.process():
35  for tlo in self.data.children(self.data.topLevelObjects()[0]):
36  children = self.data.children(tlo)
37  if children:
38  all_toplevel[tlo._label]=children
39  else:
40  #case if we have only an anonymous (non-process) file
41  #pick up (modules, sequences, paths)
42  for tlo in self.data.topLevelObjects():
43  if self.data.type(tlo)=='Sequence':
44  if 'sequences' in all_toplevel:
45  all_toplevel['sequences']+=[tlo]
46  else:
47  all_toplevel['sequences']=[tlo]
48  if self.data.type(tlo)=='Path':
49  if 'paths' in all_toplevel:
50  all_toplevel['paths']+=[tlo]
51  else:
52  all_toplevel['paths']=[tlo]
53  if self.data.type(tlo) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule'):
54  self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':True}
55  if self.options['services'] and self.data.type(tlo)=='Service':
56  self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':False}
57  if self.options['es'] and self.data.type(tlo) in ('ESSource','ESProducer'):
58  self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':False}
59  return all_toplevel
60 
61  def seqRecurseChildren(self,obj):
62  children = self.data.children(obj)
63  if children:
64  seqlabel = self.data.label(obj)
65  if self.options['file']:
66  seqlabel += '\\n%s:%s' % (self.data.pypackage(obj),self.data.lineNumber(obj))
67  result='subgraph clusterSeq%s {\nlabel="Sequence %s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(obj),seqlabel,self.options['color_sequence'],self.options['color_sequence'],self.options['font_name'],self.options['font_size'])
68  for c in children:
69  result += self.seqRecurseChildren(c)
70  result+='}\n'
71  return result
72  else:
73  self.nodes[self.data.label(obj)]={'obj':obj,'n_label':self.nodeLabel(obj),'n_shape':self.shapes.get(self.data.type(obj),'plaintext'),'inpath':True}
74  return '%s\n'%self.data.label(obj)
75 
76  def recurseChildren(self,obj):
77  result=[]
78  children=self.data.children(obj)
79  if children:
80  for c in children:
81  result += self.recurseChildren(c)
82  else:
83  result.append(obj)
84  return result
85 
86  #write out an appropriate node label
87  def nodeLabel(self,obj):
88  result = self.data.label(obj)
89  if self.options['class']:
90  result += '\\n%s'%self.data.classname(obj)
91  if self.options['file']:
92  result += '\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
93  return result
94 
95  #generate an appropriate URL by replacing placeholders in baseurl
96  def nodeURL(self,obj):
97  classname = self.data.classname(obj)
98  pypath = self.data.pypath(obj)
99  pyline = self.data.lineNumber(obj)
100  url = self.options['urlbase'].replace('$classname',classname).replace('$pypath',pypath).replace('$pyline',pyline)
101  return url
102 
103  def makePath(self,path,endpath=False):
104  children = self.recurseChildren(path)
105  pathlabel = self.data.label(path)
106  if self.options['file']:
107  pathlabel += '\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
108  if endpath:
109  pathresult = 'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.options['color_endpath'],self.options['color_endpath'],self.options['font_name'],self.options['font_size'])
110  else:
111  pathresult = 'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.options['color_path'],self.options['color_path'],self.options['font_name'],self.options['font_size'])
112  if self.options['seqconnect']:
113  if endpath:
114  self.endstarts.append('endstart_%s'%self.data.label(path))
115  self.nodes['endstart_%s'%self.data.label(path)]={'obj':path,'n_label':'Start %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
116  else:
117  self.pathstarts.append('start_%s'%self.data.label(path))
118  self.pathends.append('end_%s'%self.data.label(path))
119  self.nodes['start_%s'%self.data.label(path)]={'obj':path,'n_label':'Start %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
120  self.nodes['end_%s'%self.data.label(path)]={'obj':path,'n_label':'End %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
121  labels=[]
122  for c in children:
123  #this is also done in seqRecurseChildren, so will be duplicated
124  #unncessary, but relatively cheap and saves more cff/cfg conditionals
125  self.nodes[self.data.label(c)]={'obj':c,'n_label':self.nodeLabel(c),'n_shape':self.shapes.get(self.data.type(c),'plaintext'),'inpath':True}
126  labels.append(self.data.label(c))
127  if self.options['seqconnect']:
128  pathresult += '->'.join(labels)+'\n'
129  else:
130  if not self.options['seq']:
131  pathresult += '\n'.join(labels)+'\n'
132  if self.options['seq']:
133  if self.data.children(path):
134  for path_child in self.data.children(path):
135  pathresult += self.seqRecurseChildren(path_child)
136  pathresult += '}\n'
137  if len(labels)>0 and self.options['seqconnect']:
138  if endpath:
139  pathresult += 'endstart_%s->%s\n' % (self.data.label(path),labels[0])
140  else:
141  pathresult += 'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
142 
143  return pathresult
144 
145  def producePaths(self):
146  result=''
147  if 'paths' in self.toplevel:
148  for path in self.toplevel['paths']:
149  result += self.makePath(path)
150  if self.options['endpath']:
151  if 'endpaths' in self.toplevel:
152  for path in self.toplevel['endpaths']:
153  result += self.makePath(path,True)
154  if 'sequences' in self.toplevel:
155  for seq in self.toplevel['sequences']:
156  result += self.seqRecurseChildren(seq)
157  return result
158 
159  def connectPaths(self):
160  result=''
161  for p in self.pathends:
162  for p2 in self.endstarts:
163  result+="%s->%s\n" % (p,p2)
164  return result
165 
166  def connectTags(self):
167  #if we are connecting by tag, add labelled tag joining lines
168  #this doesn't have to be exclusive with sequence connection, by stylistically probably should be
169  result=''
170  allobjects = [self.nodes[n]['obj'] for n in self.nodes if self.nodes[n]['inpath']]
171  self.data.readConnections(allobjects)
172  connections = self.data.connections()
173  for objects,names in connections.items():
174  if self.options['taglabel']:
175  result += '%s->%s[label="%s",color="%s",fontcolor="%s",fontsize=%s,fontname="%s"]\n' % (objects[0],objects[1],names[1],self.options['color_inputtag'],self.options['color_inputtag'],self.options['font_size'],self.options['font_name'])
176  else:
177  result += '%s->%s[color="%s"]\n' % (objects[0],objects[1],self.options['color_inputtag'])
178  return result
179 
180 
181  def produceSource(self):
182  #add the source
183  #if we are connecting sequences, connect it to all the path starts
184  #if we are connecting sequences and have a schedule, connect it to path #0
185  result=''
186  if 'source' in self.toplevel:
187  for s in self.toplevel['source']:
188  self.nodes['source']={'obj':s,'n_label':self.data.classname(s),'n_shape':self.shapes['Source']}
189  if self.options['seqconnect']:
190  for p in self.pathstarts:
191  result += 'source->%s\n' % (p)
192  return result
193 
194  def produceServices(self):
195  # add service, eventsetup nodes
196  # this will usually result in thousands and isn't that interesting
197  servicenodes=[]
198  result=''
199  if self.options['es']:
200  if 'essources' in self.toplevel:
201  for e in self.toplevel['essources']:
202  servicenodes.append(self.data.label(e))
203  self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESSource'],'inpath':False}
204  if 'esproducers' in self.toplevel:
205  for e in self.toplevel['esproducers']:
206  servicenodes.append(self.data.label(e))
207  self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESProducer'],'inpath':False}
208  if self.options['services']:
209  if 'services' in self.toplevel:
210  for s in self.toplevel['services']:
211  self.servicenodes.append(self.data.label(s))
212  self.nodes[self.data.label(s)]={'obj':s,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['Service'],'inpath':False}
213  #find the maximum path and endpath lengths for servicenode layout
214  maxpath=max([len(recurseChildren(path) for path in self.toplevel.get('paths',(0,)))])
215  maxendpath=max([len(recurseChildren(path) for path in self.toplevel.get('endpaths',(0,)))])
216 
217  #add invisible links between service nodes where necessary to ensure they only fill to the same height as the longest path+endpath
218  #this constraint should only apply for link view
219  for i,s in enumerate(servicenodes[:-1]):
220  if not i%(maxpath+maxendpath)==(maxpath+maxendpath)-1:
221  result+='%s->%s[style=invis]\n' % (s,servicenodes[i+1])
222  return result
223 
224  def produceNodes(self):
225  result=''
226  for n in self.nodes:
227  self.nodes[n]['n_fontname']=self.options['font_name']
228  self.nodes[n]['n_fontsize']=self.options['font_size']
229  if self.options['url']:
230  self.nodes[n]['n_URL']=self.nodeURL(self.nodes[n]['obj'])
231  result += "%s[%s]\n" % (n,','.join(['%s="%s"' % (k[2:],v) for k,v in self.nodes[n].items() if k[0:2]=='n_']))
232  return result
233 
234  def produceLegend(self):
235  """
236  Return a legend subgraph using current shape and colour preferences.
237  """
238  return 'subgraph clusterLegend {\nlabel="legend"\ncolor=red\nSource->Producer->Filter->Analyzer\nService->ESSource[style=invis]\nESSource->ESProducer[style=invis]\nProducer->Filter[color="%s",label="InputTag",fontcolor="%s"]\nProducer[shape=%s]\nFilter[shape=%s]\nAnalyzer[shape=%s]\nESSource[shape=%s]\nESProducer[shape=%s]\nSource[shape=%s]\nService[shape=%s]\nsubgraph clusterLegendSequence {\nlabel="Sequence"\ncolor="%s"\nfontcolor="%s"\nProducer\nFilter\n}\n}\n' % (self.options['color_inputtag'],self.options['color_inputtag'],self.shapes['EDProducer'],self.shapes['EDFilter'],self.shapes['EDAnalyzer'],self.shapes['ESSource'],self.shapes['ESProducer'],self.shapes['Source'],self.shapes['Service'],self.options['color_sequence'],self.options['color_sequence'])
239 
240  def __call__(self):
241  blocks=[]
242  if self.options['legend']:
243  blocks += [self.produceLegend()]
244  blocks += [self.producePaths()]
245  if self.options['seqconnect']:
246  blocks += [self.connectPaths()]
247  if self.options['tagconnect']:
248  blocks += [self.connectTags()]
249  if self.options['source']:
250  blocks += [self.produceSource()]
251  if self.options['es'] or self.options['services']:
252  blocks += [self.produceServices()]
253  blocks += [self.produceNodes()]
254  if self.data.process():
255  return 'digraph configbrowse {\nsubgraph clusterProcess {\nlabel="%s\\n%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data.process().name_(),self.data._filename,self.options['font_size'],self.options['font_name'],'\n'.join(blocks))
256  else:
257  return 'digraph configbrowse {\nsubgraph clusterCFF {\nlabel="%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data._filename,self.options['font_size'],self.options['font_name'],'\n'.join(blocks))
258 
259 
260 
262  """
263  Export a CMSSW config file to DOT (http://www.graphviz.org) markup, either as raw markup or by invoking the dot program, as an image.
264  """
265  option_types={
266  'legend':('Show Legend','boolean',True),
267  'source':('Show Source','boolean',True),
268  'es':('Show Event Setup','boolean',False),
269  'tagconnect':('Connect InputTags','boolean',True),
270  'seqconnect':('Connect Module Sequence','boolean',False),
271  'services':('Show Services','boolean',False),
272  'endpath':('Show EndPaths','boolean',True),
273  'seq':('Group Sequences','boolean',True),
274  'class':('Show Class','boolean',True),
275  'file':('Show File','boolean',True),
276  'schedule':('Show Schedule','boolean',False),
277  'taglabel':('Show Tag Labels','boolean',True),
278  'color_path':('Path Color','color','#ff00ff'),
279  'color_endpath':('EndPath Color','color','#ff0000'),
280  'color_sequence':('Sequence Color','color','#00ff00'),
281  'color_inputtag':('InputTag Color','color','#0000ff'),
282  'color_schedule':('Schedule Color','color','#00ffff'),
283  'url':('Include URLs','boolean',False), #this is only purposeful for png+map mode
284  'urlprocess':('Postprocess URL (for client-side imagemaps)','boolean',False), #see processMap documentation; determines whether to treat 'urlbase' as a dictionary for building a more complex imagemap or a simple URL
285  'urlbase':('URL to generate','string',"{'split_x':1,'split_y':2,'scale_x':1.,'scale_y':1.,'cells':[{'top':0,'left':0,'width':1,'height':1,'html_href':'http://cmslxr.fnal.gov/lxr/ident/?i=$classname','html_alt':'LXR','html_class':'LXR'},{'top':1,'left':0,'width':1,'height':1,'html_href':'http://cmssw.cvs.cern.ch/cgi-bin/cmssw.cgi/CMSSW/$pypath?view=markup#$pyline','html_alt':'CVS','html_class':'CVS'}]}"), #CVS markup view doesn't allow line number links, only annotate view (which doesn't then highlight the code...)
286  'node_graphs':('Produce individual graphs focussing on each node','boolean',False),
287  'node_graphs_restrict':('Select which nodes to make node graphs for','string',''),
288  'node_depth':('Search depth for individual node graphs','int',1),
289  'font_name':('Font name','string','Times-Roman'),
290  'font_size':('Font size','int',8),
291  'png_max_size':('Maximum edge for png image','int',16768)
292  }
293  plugin_name='DOT Export'
294  file_types=('bmp','dot','eps','gif','jpg','pdf','png','ps','svg','tif','png+map','stdout')
295  def __init__(self):
296  FileExportPlugin.__init__(self)
297 
298  #could make these customizeable in the general options dict
299  self.shapes={}
300  self.shapes['EDProducer']='box'
301  self.shapes['EDFilter']='invhouse'
302  self.shapes['EDAnalyzer']='house'
303  self.shapes['OutputModule']='invtrapezium'
304  self.shapes['ESSource']='Mdiamond'
305  self.shapes['ESProducer']='Msquare'
306  self.shapes['Source']='ellipse'
307  self.shapes['Service']='diamond'
308 
309  def dotIndenter(self,dot):
310  """
311  Simple indenter for dot output, mainly to prettify it for human reading.
312  """
313  spaces = lambda d: ''.join([space]*d)
314  newdot = ""
315  depth = 0
316  space = ' '
317  for line in dot.splitlines():
318  if '{' in line:
319  newdot += spaces(depth)+line+'\n'
320  depth += 1
321  elif '}' in line:
322  depth -= 1
323  newdot += spaces(depth)+line+'\n'
324  else:
325  newdot += spaces(depth)+line+'\n'
326  return newdot
327 
328  def selectNode(self,dotdata,node,depth_s):
329  depth = int(depth_s)
330  backtrace=False
331  if depth<0:
332  depth = abs(depth)
333  backtrace=True
334  re_link = re.compile(r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
335  re_nodedef = re.compile(r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
336  re_title = re.compile(r'^label=\"(.*?)\"$',re.MULTILINE)
337  re_nodeprops = re.compile(r'^\s*?('+node+r')\[(.*?)\]$',re.MULTILINE)
338 
339  nodes = re_nodedef.findall(dotdata)
340  if not node in nodes:
341  raise Exception("Selected node (%s) not found" % (node))
342  links_l = re_link.findall(dotdata)
343  links = {}
344  for link in links_l:
345  if not backtrace:
346  if link[0] in links:
347  links[link[0]] += [link[1]]
348  else:
349  links[link[0]] = [link[1]]
350  if link[1] in links:
351  links[link[1]] += [link[0]]
352  else:
353  links[link[1]] = [link[0]]
354 
355  def node_recursor(links,depthleft,start):
356  if start in links:
357  if depthleft==0:
358  return links[start]+[start]
359  else:
360  result = [start]
361  for l in links[start]:
362  result.extend(node_recursor(links,depthleft-1,l))
363  return result
364  else:
365  return [start]
366 
367 
368  include_nodes = set(node_recursor(links,depth-1,node))
369  include_nodes.add(node)
370 
371  class link_replacer:
372  def __init__(self,include_nodes):
373  self.include_nodes=include_nodes
374  def __call__(self,match):
375  if match.group(1) in self.include_nodes and match.group(2) in self.include_nodes:
376  return match.group(0)
377  return ''
378  class node_replacer:
379  def __init__(self,include_nodes):
380  self.include_nodes=include_nodes
381  def __call__(self,match):
382  if match.group(1) in self.include_nodes:
383  return match.group(0)
384  return ''
385 
386  dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
387  dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
388  dotdata = re_title.sub(r'label="\g<1>\\nDepth '+str(depth_s)+r' from node ' +node+r'"',dotdata,1)
389  dotdata = re_nodeprops.sub('\\g<1>[\\g<2>,color="red"]',dotdata,1)
390 
391  return dotdata
392 
393  def processMap(self,mapdata):
394  """
395  Re-process the client-side image-map produces when png+map is selected.
396  DOT will only ever put a single URL in the imagemap corresponding to a node, with the 'url' parameter (after html encoding) as the url, and the 'title' parameter as the title. This isn't useful behaviour for our purposes. We want probably several link areas, or a javascript link to make a menu appear, or other more complex behaviour.
397 
398  If the option 'urlprocess' is turned on, this function is called, and it expects to find a dictionary it can eval in the url parameter. I can't think of a less messy way of passing data to this function without having inner access to DOT at the moment.
399 
400  This function iterates through all the areas in the mapfile, replacing each one with one or more areas according to the rules in the dictionary stored in the URL parameter.
401 
402  The dictionary should have structure:
403  {
404  split_x:#,
405  split_y:#,
406  scale_x:#,
407  scale_y:#,
408  cells:[
409  {
410  top:#,
411  left:#,
412  width:#,
413  height:#,
414  html_attribute1:"...",
415  html_attribute2:"..."
416  ]
417  }
418  The imagemap is first scaled in size by scale_x and scale_y.
419  It is then split into split_x*split_y rectangular cells.
420  New areas are created for each defined cell with the defined top,left location and width,height. This will not check you aren't making new areas that overlap if you define them as such.
421  The areas then get attributes defined by html_attribute fields - eg, 'html_href':'mypage.htm' becomes 'href'='mypage.htm' in the area. Probably you want as a minimum to define html_href and html_alt. It would also be useful to set html_class to allow highlighting of different link types, or html_onclick/onmouseover for more exotic behaviour.
422 
423  This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
424 
425  This may be somewhat replaceable with the <html_label> and cut-down table format that graphviz provides, but I haven't had much of an experiment with that.
426  """
427  new_areas=[]
428  area_default = {'split_x':1,'scale_x':1.,'split_y':1,'scale_y':1.,'cells':[]}
429  cell_default = {'top':0,'left':0,'width':1,'height':1,'html_href':'#'}
430  re_area = re.compile('<area.*?/>',re.DOTALL)
431  #sometimes DOT comes up with negative coordinates, so we need to deal with them here (so all the other links will work at least)
432  re_content = re.compile('href="(.*?)" title=".*?" alt="" coords="(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6})"',re.DOTALL)
433  re_htmlunquote = re.compile('&#([0-9]{1,3});')
434  mapdata = re_htmlunquote.sub(lambda x: chr(int(x.group(1))),mapdata)
435  areas = re_area.findall(mapdata)
436  for area in areas:
437  #print area
438  data = re_content.search(area)
439  baseurl = data.group(1)
440  x1,y1,x2,y2 = map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
441  rad_x,rad_y = int((x2-x1)*0.5),int((y2-y1)*0.5)
442  centre_x,centre_y = x1+rad_x,y1+rad_y
443  basedict = eval(baseurl)
444  for ad in area_default:
445  if not ad in basedict:
446  basedict[ad]=area_default[ad]
447  rad_x = int(rad_x*basedict['scale_x'])
448  rad_y = int(rad_y*basedict['scale_y'])
449  top_x,top_y = centre_x-rad_x,centre_y-rad_y
450  split_x,split_y = int((2*rad_x)/basedict['split_x']),int((2*rad_y)/basedict['split_y'])
451 
452  for cell in basedict['cells']:
453  for cd in cell_default:
454  if not cd in cell:
455  cell[cd]=cell_default[cd]
456  x1,y1 = top_x+split_x*cell['left'],top_y+split_y*cell['top']
457  x2,y2 = x1+split_x*cell['width'],y1+split_y*cell['height']
458  area_html = '<area shape="rect" coords="%s,%s,%s,%s" %s />' % (x1,y1,x2,y2,' '.join(['%s="%s"'%(key.split('_',1)[1],value) for key, value in cell.items() if key.startswith('html_')]))
459  new_areas.append(area_html)
460  return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%('\n'.join(new_areas))
461 
462 
463  def export(self,data,filename,filetype):
464  #if not data.process():
465  # raise "DOTExport requires a cms.Process object"
466 
467  #dot = self.produceDOT(data)
468  dot_producer = DotProducer(data,self.options,self.shapes)
469  dot = dot_producer()
470 
471  if len(dot_producer.nodes)>0:
472 
473  if self.options['node_graphs']:
474  nodes = [n for n in dot_producer.nodes if data.type(dot_producer.nodes[n]['obj']) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule')]
475  for n in nodes:
476  if self.options['node_graphs_restrict'] in n:
477  try:
478  node_dot = self.selectNode(dot,n,self.options['node_depth'])
479  self.write_output(node_dot,filename.replace('.','_%s.'%n),filetype)
480  except:
481  pass
482  else:
483  dot = self.dotIndenter(dot)
484  self.write_output(dot,filename,filetype)
485  else:
486  print "WARNING: Empty image. Not saved."
487 
488 
489  def get_png_size(self,filename):
490  png_header = '\x89PNG\x0d\x0a\x1a\x0a'
491  ihdr = 'IHDR'
492  filedata = open(filename,'r').read(24)
493  png_data = struct.unpack('>8s4s4sII',filedata)
494  if not (png_data[0]==png_header and png_data[2]==ihdr):
495  raise 'PNG header or IHDR not found'
496  return png_data[3],png_data[4]
497 
498  def write_output(self,dot,filename,filetype):
499  #don't use try-except-finally here, we want any errors passed on so the enclosing program can decide how to handle them
500  if filetype=='dot':
501  dotfile = open(filename,'w')
502  dotfile.write(dot)
503  dotfile.close()
504  elif filetype=='stdout':
505  print result
506  elif filetype=='pdf':
507  dot_p = subprocess.Popen(['dot','-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
508  ps2 = dot_p.communicate(dot)[0]
509  if not dot_p.returncode==0:
510  raise "dot returned non-zero exit code: %s"%dot_p.returncode
511  pdf_p = subprocess.Popen(['ps2pdf','-',filename],stdin=subprocess.PIPE)
512  pdf_p.communicate(ps2)
513  if not pdf_p.returncode==0:
514  raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
515  elif filetype=='png+map':
516  if '.' in filename:
517  filename = filename.split('.')[0]
518  dot_p = subprocess.Popen(['dot','-Tpng','-o','%s.png'%filename,'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
519  mapdata = dot_p.communicate(dot)[0]
520  if not dot_p.returncode==0:
521  raise "dot returned non-zero exit code: %s"%dot_p.returncode
522  if self.options['urlprocess']:
523  mapdata = self.processMap(mapdata)
524  mapfile = open('%s.map'%filename,'w')
525  mapfile.write(mapdata)
526  mapfile.close()
527  filesize = self.get_png_size('%s.png'%filename)
528  if max(filesize) > self.options['png_max_size']:
529  print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
530  os.remove('%s.png'%filename)
531  os.remove('%s.map'%filename)
532  elif filetype=='png':
533  dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
534  dot_p.communicate(dot)
535  if not dot_p.returncode==0:
536  raise "dot returned non-zero exit code: %s"%dot_p.returncode
537  filesize = self.get_png_size(filename)
538  if max(filesize) > self.options['png_max_size']:
539  print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
540  os.remove(filename)
541  else:
542  dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
543  dot_p.communicate(dot)
544  if not dot_p.returncode==0:
545  raise "dot returned non-zero exit code: %s"%dot_p.returncode
546 
547 
548 
def produceSource(self)
Definition: DOTExport.py:181
def getTopLevel(self)
Definition: DOTExport.py:30
def makePath(self, path, endpath=False)
Definition: DOTExport.py:103
def replace(string, replacements)
def nodeURL(self, obj)
Definition: DOTExport.py:96
def write_output(self, dot, filename, filetype)
Definition: DOTExport.py:498
def __init__(self)
Definition: DOTExport.py:295
def recurseChildren(self, obj)
Definition: DOTExport.py:76
def produceLegend(self)
Definition: DOTExport.py:234
def producePaths(self)
Definition: DOTExport.py:145
def connectPaths(self)
Definition: DOTExport.py:159
def seqRecurseChildren(self, obj)
Definition: DOTExport.py:61
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def produceNodes(self)
Definition: DOTExport.py:224
def processMap(self, mapdata)
Definition: DOTExport.py:393
def selectNode(self, dotdata, node, depth_s)
Definition: DOTExport.py:328
def export(self, data, filename, filetype)
Definition: DOTExport.py:463
def dotIndenter(self, dot)
Definition: DOTExport.py:309
def produceServices(self)
Definition: DOTExport.py:194
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def get_png_size(self, filename)
Definition: DOTExport.py:489
def nodeLabel(self, obj)
Definition: DOTExport.py:87
def connectTags(self)
Definition: DOTExport.py:166
def __init__(self, data, options, shapes)
Definition: DOTExport.py:19