9 import FWCore.ParameterSet.SequenceTypes
as sqt
11 import FWCore.ParameterSet.Modules
as mod
12 import FWCore.ParameterSet.Types
as typ
15 from Vispa.Plugins.ConfigEditor.ConfigDataAccessor
import ConfigDataAccessor
16 from FWCore.GuiBrowsers.FileExportPlugin
import FileExportPlugin
34 if self.data.process():
35 for tlo
in self.data.children(self.data.topLevelObjects()[0]):
36 children = self.data.children(tlo)
38 all_toplevel[tlo._label]=children
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]
47 all_toplevel[
'sequences']=[tlo]
48 if self.data.type(tlo)==
'Path':
49 if 'paths' in all_toplevel:
50 all_toplevel[
'paths']+=[tlo]
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}
62 children = self.data.children(obj)
64 seqlabel = self.data.label(obj)
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'])
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)
78 children=self.data.children(obj)
88 result = self.data.label(obj)
90 result +=
'\\n%s'%self.data.classname(obj)
92 result +=
'\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
97 classname = self.data.classname(obj)
98 pypath = self.data.pypath(obj)
99 pyline = self.data.lineNumber(obj)
105 pathlabel = self.data.label(path)
107 pathlabel +=
'\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
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'])
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'])
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}
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}
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))
128 pathresult +=
'->'.
join(labels)+
'\n'
131 pathresult +=
'\n'.
join(labels)+
'\n'
133 if self.data.children(path):
134 for path_child
in self.data.children(path):
137 if len(labels)>0
and self.
options[
'seqconnect']:
139 pathresult +=
'endstart_%s->%s\n' % (self.data.label(path),labels[0])
141 pathresult +=
'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
152 for path
in self.
toplevel[
'endpaths']:
155 for seq
in self.
toplevel[
'sequences']:
163 result+=
"%s->%s\n" % (p,p2)
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():
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'])
177 result +=
'%s->%s[color="%s"]\n' % (objects[0],objects[1],self.
options[
'color_inputtag'])
188 self.
nodes[
'source']={
'obj':s,
'n_label':self.data.classname(s),
'n_shape':self.
shapes[
'Source']}
191 result +=
'source->%s\n' % (p)
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}
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}
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}
215 maxendpath=
max([len(
recurseChildren(path)
for path
in self.toplevel.get(
'endpaths',(0,)))])
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])
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_']))
236 Return a legend subgraph using current shape and colour preferences.
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'])
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))
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))
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.
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),
284 'urlprocess':(
'Postprocess URL (for client-side imagemaps)',
'boolean',
False),
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'}]}"),
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)
293 plugin_name=
'DOT Export'
294 file_types=(
'bmp',
'dot',
'eps',
'gif',
'jpg',
'pdf',
'png',
'ps',
'svg',
'tif',
'png+map',
'stdout')
296 FileExportPlugin.__init__(self)
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'
311 Simple indenter for dot output, mainly to prettify it for human reading.
313 spaces =
lambda d:
''.
join([space]*d)
317 for line
in dot.splitlines():
319 newdot += spaces(depth)+line+
'\n'
323 newdot += spaces(depth)+line+
'\n'
325 newdot += spaces(depth)+line+
'\n'
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)
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)
347 links[link[0]] += [link[1]]
349 links[link[0]] = [link[1]]
351 links[link[1]] += [link[0]]
353 links[link[1]] = [link[0]]
355 def node_recursor(links,depthleft,start):
358 return links[start]+[start]
361 for l
in links[start]:
362 result.extend(node_recursor(links,depthleft-1,l))
368 include_nodes = set(node_recursor(links,depth-1,node))
369 include_nodes.add(node)
374 def __call__(self,match):
376 return match.group(0)
381 def __call__(self,match):
383 return match.group(0)
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)
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.
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.
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.
402 The dictionary should have structure:
414 html_attribute1:"...",
415 html_attribute2:"..."
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.
423 This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
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.
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)
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)
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'])
452 for cell
in basedict[
'cells']:
453 for cd
in cell_default:
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))
471 if len(dot_producer.nodes)>0:
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')]
476 if self.options[
'node_graphs_restrict']
in n:
478 node_dot = self.
selectNode(dot,n,self.options[
'node_depth'])
479 self.
write_output(node_dot,filename.replace(
'.',
'_%s.'%n),filetype)
486 print "WARNING: Empty image. Not saved."
490 png_header =
'\x89PNG\x0d\x0a\x1a\x0a'
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]
501 dotfile = open(filename,
'w')
504 elif filetype==
'stdout':
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':
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']:
524 mapfile = open(
'%s.map'%filename,
'w')
525 mapfile.write(mapdata)
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
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'])
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
const T & max(const T &a, const T &b)
static std::string join(char **cmd)