00001 import sys
00002 import os
00003 import logging
00004 import random
00005 import subprocess
00006 import re
00007 import struct
00008
00009 import FWCore.ParameterSet.SequenceTypes as sqt
00010 import FWCore.ParameterSet.Config as cms
00011 import FWCore.ParameterSet.Modules as mod
00012 import FWCore.ParameterSet.Types as typ
00013 import FWCore.ParameterSet.Mixins as mix
00014
00015 from Vispa.Plugins.ConfigEditor.ConfigDataAccessor import ConfigDataAccessor
00016 from FWCore.GuiBrowsers.FileExportPlugin import FileExportPlugin
00017
00018 class DotProducer(object):
00019 def __init__(self,data,options,shapes):
00020 self.data = data
00021 self.options = options
00022 self.shapes = shapes
00023 self.nodes={}
00024
00025 self.pathstarts=[]
00026 self.pathends=[]
00027 self.endstarts=[]
00028 self.toplevel = self.getTopLevel()
00029
00030 def getTopLevel(self):
00031
00032
00033 all_toplevel={}
00034 if self.data.process():
00035 for tlo in self.data.children(self.data.topLevelObjects()[0]):
00036 children = self.data.children(tlo)
00037 if children:
00038 all_toplevel[tlo._label]=children
00039 else:
00040
00041
00042 for tlo in self.data.topLevelObjects():
00043 if self.data.type(tlo)=='Sequence':
00044 if 'sequences' in all_toplevel:
00045 all_toplevel['sequences']+=[tlo]
00046 else:
00047 all_toplevel['sequences']=[tlo]
00048 if self.data.type(tlo)=='Path':
00049 if 'paths' in all_toplevel:
00050 all_toplevel['paths']+=[tlo]
00051 else:
00052 all_toplevel['paths']=[tlo]
00053 if self.data.type(tlo) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule'):
00054 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}
00055 if self.options['services'] and self.data.type(tlo)=='Service':
00056 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}
00057 if self.options['es'] and self.data.type(tlo) in ('ESSource','ESProducer'):
00058 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}
00059 return all_toplevel
00060
00061 def seqRecurseChildren(self,obj):
00062 children = self.data.children(obj)
00063 if children:
00064 seqlabel = self.data.label(obj)
00065 if self.options['file']:
00066 seqlabel += '\\n%s:%s' % (self.data.pypackage(obj),self.data.lineNumber(obj))
00067 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'])
00068 for c in children:
00069 result += self.seqRecurseChildren(c)
00070 result+='}\n'
00071 return result
00072 else:
00073 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}
00074 return '%s\n'%self.data.label(obj)
00075
00076 def recurseChildren(self,obj):
00077 result=[]
00078 children=self.data.children(obj)
00079 if children:
00080 for c in children:
00081 result += self.recurseChildren(c)
00082 else:
00083 result.append(obj)
00084 return result
00085
00086
00087 def nodeLabel(self,obj):
00088 result = self.data.label(obj)
00089 if self.options['class']:
00090 result += '\\n%s'%self.data.classname(obj)
00091 if self.options['file']:
00092 result += '\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
00093 return result
00094
00095
00096 def nodeURL(self,obj):
00097 classname = self.data.classname(obj)
00098 pypath = self.data.pypath(obj)
00099 pyline = self.data.lineNumber(obj)
00100 url = self.options['urlbase'].replace('$classname',classname).replace('$pypath',pypath).replace('$pyline',pyline)
00101 return url
00102
00103 def makePath(self,path,endpath=False):
00104 children = self.recurseChildren(path)
00105 pathlabel = self.data.label(path)
00106 if self.options['file']:
00107 pathlabel += '\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
00108 if endpath:
00109 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'])
00110 else:
00111 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'])
00112 if self.options['seqconnect']:
00113 if endpath:
00114 self.endstarts.append('endstart_%s'%self.data.label(path))
00115 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}
00116 else:
00117 self.pathstarts.append('start_%s'%self.data.label(path))
00118 self.pathends.append('end_%s'%self.data.label(path))
00119 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}
00120 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}
00121 labels=[]
00122 for c in children:
00123
00124
00125 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}
00126 labels.append(self.data.label(c))
00127 if self.options['seqconnect']:
00128 pathresult += '->'.join(labels)+'\n'
00129 else:
00130 if not self.options['seq']:
00131 pathresult += '\n'.join(labels)+'\n'
00132 if self.options['seq']:
00133 if self.data.children(path):
00134 for path_child in self.data.children(path):
00135 pathresult += self.seqRecurseChildren(path_child)
00136 pathresult += '}\n'
00137 if len(labels)>0 and self.options['seqconnect']:
00138 if endpath:
00139 pathresult += 'endstart_%s->%s\n' % (self.data.label(path),labels[0])
00140 else:
00141 pathresult += 'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
00142
00143 return pathresult
00144
00145 def producePaths(self):
00146 result=''
00147 if 'paths' in self.toplevel:
00148 for path in self.toplevel['paths']:
00149 result += self.makePath(path)
00150 if self.options['endpath']:
00151 if 'endpaths' in self.toplevel:
00152 for path in self.toplevel['endpaths']:
00153 result += self.makePath(path,True)
00154 if 'sequences' in self.toplevel:
00155 for seq in self.toplevel['sequences']:
00156 result += self.seqRecurseChildren(seq)
00157 return result
00158
00159 def connectPaths(self):
00160 result=''
00161 for p in self.pathends:
00162 for p2 in self.endstarts:
00163 result+="%s->%s\n" % (p,p2)
00164 return result
00165
00166 def connectTags(self):
00167
00168
00169 result=''
00170 allobjects = [self.nodes[n]['obj'] for n in self.nodes if self.nodes[n]['inpath']]
00171 self.data.readConnections(allobjects)
00172 connections = self.data.connections()
00173 for objects,names in connections.items():
00174 if self.options['taglabel']:
00175 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'])
00176 else:
00177 result += '%s->%s[color="%s"]\n' % (objects[0],objects[1],self.options['color_inputtag'])
00178 return result
00179
00180
00181 def produceSource(self):
00182
00183
00184
00185 result=''
00186 if 'source' in self.toplevel:
00187 for s in self.toplevel['source']:
00188 self.nodes['source']={'obj':s,'n_label':self.data.classname(s),'n_shape':self.shapes['Source']}
00189 if self.options['seqconnect']:
00190 for p in self.pathstarts:
00191 result += 'source->%s\n' % (p)
00192 return result
00193
00194 def produceServices(self):
00195
00196
00197 servicenodes=[]
00198 result=''
00199 if self.options['es']:
00200 if 'essources' in self.toplevel:
00201 for e in self.toplevel['essources']:
00202 servicenodes.append(self.data.label(e))
00203 self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESSource'],'inpath':False}
00204 if 'esproducers' in self.toplevel:
00205 for e in self.toplevel['esproducers']:
00206 servicenodes.append(self.data.label(e))
00207 self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESProducer'],'inpath':False}
00208 if self.options['services']:
00209 if 'services' in self.toplevel:
00210 for s in self.toplevel['services']:
00211 self.servicenodes.append(self.data.label(s))
00212 self.nodes[self.data.label(s)]={'obj':s,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['Service'],'inpath':False}
00213
00214 maxpath=max([len(recurseChildren(path) for path in self.toplevel.get('paths',(0,)))])
00215 maxendpath=max([len(recurseChildren(path) for path in self.toplevel.get('endpaths',(0,)))])
00216
00217
00218
00219 for i,s in enumerate(servicenodes[:-1]):
00220 if not i%(maxpath+maxendpath)==(maxpath+maxendpath)-1:
00221 result+='%s->%s[style=invis]\n' % (s,servicenodes[i+1])
00222 return result
00223
00224 def produceNodes(self):
00225 result=''
00226 for n in self.nodes:
00227 self.nodes[n]['n_fontname']=self.options['font_name']
00228 self.nodes[n]['n_fontsize']=self.options['font_size']
00229 if self.options['url']:
00230 self.nodes[n]['n_URL']=self.nodeURL(self.nodes[n]['obj'])
00231 result += "%s[%s]\n" % (n,','.join(['%s="%s"' % (k[2:],v) for k,v in self.nodes[n].items() if k[0:2]=='n_']))
00232 return result
00233
00234 def produceLegend(self):
00235 """
00236 Return a legend subgraph using current shape and colour preferences.
00237 """
00238 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'])
00239
00240 def __call__(self):
00241 blocks=[]
00242 if self.options['legend']:
00243 blocks += [self.produceLegend()]
00244 blocks += [self.producePaths()]
00245 if self.options['seqconnect']:
00246 blocks += [self.connectPaths()]
00247 if self.options['tagconnect']:
00248 blocks += [self.connectTags()]
00249 if self.options['source']:
00250 blocks += [self.produceSource()]
00251 if self.options['es'] or self.options['services']:
00252 blocks += [self.produceServices()]
00253 blocks += [self.produceNodes()]
00254 if self.data.process():
00255 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))
00256 else:
00257 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))
00258
00259
00260
00261 class DotExport(FileExportPlugin):
00262 """
00263 Export a CMSSW config file to DOT (https://www.graphviz.org) markup, either as raw markup or by invoking the dot program, as an image.
00264 """
00265 option_types={
00266 'legend':('Show Legend','boolean',True),
00267 'source':('Show Source','boolean',True),
00268 'es':('Show Event Setup','boolean',False),
00269 'tagconnect':('Connect InputTags','boolean',True),
00270 'seqconnect':('Connect Module Sequence','boolean',False),
00271 'services':('Show Services','boolean',False),
00272 'endpath':('Show EndPaths','boolean',True),
00273 'seq':('Group Sequences','boolean',True),
00274 'class':('Show Class','boolean',True),
00275 'file':('Show File','boolean',True),
00276 'schedule':('Show Schedule','boolean',False),
00277 'taglabel':('Show Tag Labels','boolean',True),
00278 'color_path':('Path Color','color','#ff00ff'),
00279 'color_endpath':('EndPath Color','color','#ff0000'),
00280 'color_sequence':('Sequence Color','color','#00ff00'),
00281 'color_inputtag':('InputTag Color','color','#0000ff'),
00282 'color_schedule':('Schedule Color','color','#00ffff'),
00283 'url':('Include URLs','boolean',False),
00284 'urlprocess':('Postprocess URL (for client-side imagemaps)','boolean',False),
00285 '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':'https://cmslxr.fnal.gov/lxr/ident/?i=$classname','html_alt':'LXR','html_class':'LXR'},{'top':1,'left':0,'width':1,'height':1,'html_href':'https://cmssw.cvs.cern.ch/cgi-bin/cmssw.cgi/CMSSW/$pypath?view=markup#$pyline','html_alt':'CVS','html_class':'CVS'}]}"),
00286 'node_graphs':('Produce individual graphs focussing on each node','boolean',False),
00287 'node_graphs_restrict':('Select which nodes to make node graphs for','string',''),
00288 'node_depth':('Search depth for individual node graphs','int',1),
00289 'font_name':('Font name','string','Times-Roman'),
00290 'font_size':('Font size','int',8),
00291 'png_max_size':('Maximum edge for png image','int',16768)
00292 }
00293 plugin_name='DOT Export'
00294 file_types=('bmp','dot','eps','gif','jpg','pdf','png','ps','svg','tif','png+map','stdout')
00295 def __init__(self):
00296 FileExportPlugin.__init__(self)
00297
00298
00299 self.shapes={}
00300 self.shapes['EDProducer']='box'
00301 self.shapes['EDFilter']='invhouse'
00302 self.shapes['EDAnalyzer']='house'
00303 self.shapes['OutputModule']='invtrapezium'
00304 self.shapes['ESSource']='Mdiamond'
00305 self.shapes['ESProducer']='Msquare'
00306 self.shapes['Source']='ellipse'
00307 self.shapes['Service']='diamond'
00308
00309 def dotIndenter(self,dot):
00310 """
00311 Simple indenter for dot output, mainly to prettify it for human reading.
00312 """
00313 spaces = lambda d: ''.join([space]*d)
00314 newdot = ""
00315 depth = 0
00316 space = ' '
00317 for line in dot.splitlines():
00318 if '{' in line:
00319 newdot += spaces(depth)+line+'\n'
00320 depth += 1
00321 elif '}' in line:
00322 depth -= 1
00323 newdot += spaces(depth)+line+'\n'
00324 else:
00325 newdot += spaces(depth)+line+'\n'
00326 return newdot
00327
00328 def selectNode(self,dotdata,node,depth_s):
00329 depth = int(depth_s)
00330 backtrace=False
00331 if depth<0:
00332 depth = abs(depth)
00333 backtrace=True
00334 re_link = re.compile(r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00335 re_nodedef = re.compile(r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00336 re_title = re.compile(r'^label=\"(.*?)\"$',re.MULTILINE)
00337 re_nodeprops = re.compile(r'^\s*?('+node+r')\[(.*?)\]$',re.MULTILINE)
00338
00339 nodes = re_nodedef.findall(dotdata)
00340 if not node in nodes:
00341 raise Exception, "Selected node (%s) not found" % (node)
00342 links_l = re_link.findall(dotdata)
00343 links = {}
00344 for link in links_l:
00345 if not backtrace:
00346 if link[0] in links:
00347 links[link[0]] += [link[1]]
00348 else:
00349 links[link[0]] = [link[1]]
00350 if link[1] in links:
00351 links[link[1]] += [link[0]]
00352 else:
00353 links[link[1]] = [link[0]]
00354
00355 def node_recursor(links,depthleft,start):
00356 if start in links:
00357 if depthleft==0:
00358 return links[start]+[start]
00359 else:
00360 result = [start]
00361 for l in links[start]:
00362 result.extend(node_recursor(links,depthleft-1,l))
00363 return result
00364 else:
00365 return [start]
00366
00367
00368 include_nodes = set(node_recursor(links,depth-1,node))
00369 include_nodes.add(node)
00370
00371 class link_replacer:
00372 def __init__(self,include_nodes):
00373 self.include_nodes=include_nodes
00374 def __call__(self,match):
00375 if match.group(1) in self.include_nodes and match.group(2) in self.include_nodes:
00376 return match.group(0)
00377 return ''
00378 class node_replacer:
00379 def __init__(self,include_nodes):
00380 self.include_nodes=include_nodes
00381 def __call__(self,match):
00382 if match.group(1) in self.include_nodes:
00383 return match.group(0)
00384 return ''
00385
00386 dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
00387 dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
00388 dotdata = re_title.sub(r'label="\g<1>\\nDepth '+str(depth_s)+r' from node ' +node+r'"',dotdata,1)
00389 dotdata = re_nodeprops.sub('\\g<1>[\\g<2>,color="red"]',dotdata,1)
00390
00391 return dotdata
00392
00393 def processMap(self,mapdata):
00394 """
00395 Re-process the client-side image-map produces when png+map is selected.
00396 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.
00397
00398 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.
00399
00400 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.
00401
00402 The dictionary should have structure:
00403 {
00404 split_x:#,
00405 split_y:#,
00406 scale_x:#,
00407 scale_y:#,
00408 cells:[
00409 {
00410 top:#,
00411 left:#,
00412 width:#,
00413 height:#,
00414 html_attribute1:"...",
00415 html_attribute2:"..."
00416 ]
00417 }
00418 The imagemap is first scaled in size by scale_x and scale_y.
00419 It is then split into split_x*split_y rectangular cells.
00420 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.
00421 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.
00422
00423 This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
00424
00425 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.
00426 """
00427 new_areas=[]
00428 area_default = {'split_x':1,'scale_x':1.,'split_y':1,'scale_y':1.,'cells':[]}
00429 cell_default = {'top':0,'left':0,'width':1,'height':1,'html_href':'#'}
00430 re_area = re.compile('<area.*?/>',re.DOTALL)
00431
00432 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)
00433 re_htmlunquote = re.compile('&#([0-9]{1,3});')
00434 mapdata = re_htmlunquote.sub(lambda x: chr(int(x.group(1))),mapdata)
00435 areas = re_area.findall(mapdata)
00436 for area in areas:
00437
00438 data = re_content.search(area)
00439 baseurl = data.group(1)
00440 x1,y1,x2,y2 = map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
00441 rad_x,rad_y = int((x2-x1)*0.5),int((y2-y1)*0.5)
00442 centre_x,centre_y = x1+rad_x,y1+rad_y
00443 basedict = eval(baseurl)
00444 for ad in area_default:
00445 if not ad in basedict:
00446 basedict[ad]=area_default[ad]
00447 rad_x = int(rad_x*basedict['scale_x'])
00448 rad_y = int(rad_y*basedict['scale_y'])
00449 top_x,top_y = centre_x-rad_x,centre_y-rad_y
00450 split_x,split_y = int((2*rad_x)/basedict['split_x']),int((2*rad_y)/basedict['split_y'])
00451
00452 for cell in basedict['cells']:
00453 for cd in cell_default:
00454 if not cd in cell:
00455 cell[cd]=cell_default[cd]
00456 x1,y1 = top_x+split_x*cell['left'],top_y+split_y*cell['top']
00457 x2,y2 = x1+split_x*cell['width'],y1+split_y*cell['height']
00458 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_')]))
00459 new_areas.append(area_html)
00460 return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%('\n'.join(new_areas))
00461
00462
00463 def export(self,data,filename,filetype):
00464
00465
00466
00467
00468 dot_producer = DotProducer(data,self.options,self.shapes)
00469 dot = dot_producer()
00470
00471 if len(dot_producer.nodes)>0:
00472
00473 if self.options['node_graphs']:
00474 nodes = [n for n in dot_producer.nodes if data.type(dot_producer.nodes[n]['obj']) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule')]
00475 for n in nodes:
00476 if self.options['node_graphs_restrict'] in n:
00477 try:
00478 node_dot = self.selectNode(dot,n,self.options['node_depth'])
00479 self.write_output(node_dot,filename.replace('.','_%s.'%n),filetype)
00480 except:
00481 pass
00482 else:
00483 dot = self.dotIndenter(dot)
00484 self.write_output(dot,filename,filetype)
00485 else:
00486 print "WARNING: Empty image. Not saved."
00487
00488
00489 def get_png_size(self,filename):
00490 png_header = '\x89PNG\x0d\x0a\x1a\x0a'
00491 ihdr = 'IHDR'
00492 filedata = open(filename,'r').read(24)
00493 png_data = struct.unpack('>8s4s4sII',filedata)
00494 if not (png_data[0]==png_header and png_data[2]==ihdr):
00495 raise 'PNG header or IHDR not found'
00496 return png_data[3],png_data[4]
00497
00498 def write_output(self,dot,filename,filetype):
00499
00500 if filetype=='dot':
00501 dotfile = open(filename,'w')
00502 dotfile.write(dot)
00503 dotfile.close()
00504 elif filetype=='stdout':
00505 print result
00506 elif filetype=='pdf':
00507 dot_p = subprocess.Popen(['dot','-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00508 ps2 = dot_p.communicate(dot)[0]
00509 if not dot_p.returncode==0:
00510 raise "dot returned non-zero exit code: %s"%dot_p.returncode
00511 pdf_p = subprocess.Popen(['ps2pdf','-',filename],stdin=subprocess.PIPE)
00512 pdf_p.communicate(ps2)
00513 if not pdf_p.returncode==0:
00514 raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
00515 elif filetype=='png+map':
00516 if '.' in filename:
00517 filename = filename.split('.')[0]
00518 dot_p = subprocess.Popen(['dot','-Tpng','-o','%s.png'%filename,'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00519 mapdata = dot_p.communicate(dot)[0]
00520 if not dot_p.returncode==0:
00521 raise "dot returned non-zero exit code: %s"%dot_p.returncode
00522 if self.options['urlprocess']:
00523 mapdata = self.processMap(mapdata)
00524 mapfile = open('%s.map'%filename,'w')
00525 mapfile.write(mapdata)
00526 mapfile.close()
00527 filesize = self.get_png_size('%s.png'%filename)
00528 if max(filesize) > self.options['png_max_size']:
00529 print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00530 os.remove('%s.png'%filename)
00531 os.remove('%s.map'%filename)
00532 elif filetype=='png':
00533 dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00534 dot_p.communicate(dot)
00535 if not dot_p.returncode==0:
00536 raise "dot returned non-zero exit code: %s"%dot_p.returncode
00537 filesize = self.get_png_size(filename)
00538 if max(filesize) > self.options['png_max_size']:
00539 print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00540 os.remove(filename)
00541 else:
00542 dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00543 dot_p.communicate(dot)
00544 if not dot_p.returncode==0:
00545 raise "dot returned non-zero exit code: %s"%dot_p.returncode
00546
00547
00548