CMS 3D CMS Logo

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