CMS 3D CMS Logo

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