5941781 [rkeene@sledge /home/rkeene/tmp/graphviz]$ cat -n graph-tool.tcl
   1: #! /usr/bin/env tclsh
   2: 
   3: # Handle options
   4: ## Set defaults
   5: set options(DrawPorts) 1
   6: set options(DrawPortsSideways) 0
   7: set options(RankDir) RL
   8: set options(PostScript) 0
   9: set options(ClusterConnected) 1
  10: set options(RankSep) 2.0
  11: set options(DrawSameNodeEdges) 0
  12: 
  13: # Procedures
  14: proc __to_dot_node {str} {
  15: 	if {[regexp {^[0-9]} $str]} {
  16: 		set str "__$str"
  17: 	}
  18: 
  19: 	return [string map [list - _ / _ " " _ * _ . _] $str]
  20: }
  21: 
  22: proc __to_dot_port {str} {
  23: 	set work [split $str /]
  24: 	set subpart [lindex $work 1]
  25: 	if {[string index $subpart 0] == "g" && [llength $work] == 2} {
  26: 		set work [lreplace $work 1 1 [expr [string range $subpart 1 end] + 16384]]
  27: 		set str [join $work /]
  28: 	}
  29: 
  30: 	set str [string map [list - _ / _ " " _ . _] $str]
  31: 	return "port$str"
  32: }
  33: 
  34: proc __str_to_dot {str} {
  35: 	return [string map [list "&" "&" "-" "-"] $str]
  36: }
  37: 
  38: proc __ports_to_color {sport dport} {
  39: 	binary scan $sport H* snum
  40: 	binary scan $dport H* dnum
  41: 
  42: 	if {$::tcl_version == "8.4"} {
  43: 		set snum [string range $snum 0 6]
  44: 		set dnum [string range $dnum 0 6]
  45: 	}
  46: 	if {$snum == ""} {
  47: 		set snum "0"
  48: 	}
  49: 	if {$dnum == ""} {
  50: 		set dnum "0"
  51: 	}
  52: 	set snum "0x$snum"
  53: 	set dnum "0x$dnum"
  54: 	
  55: 	expr srand($snum + $dnum)
  56: 
  57: 	set edgecolor_part(r) [expr int(rand() * 192) + 32]
  58: 	set edgecolor_part(g) [expr int(rand() * 192) + 32]
  59: 	set edgecolor_part(b) [expr int(rand() * 192) + 32]
  60: 
  61: 	return [format "#%02x%02x%02x" $edgecolor_part(r) $edgecolor_part(g) $edgecolor_part(b)	]
  62: }
  63: 
  64: proc get_all_nodes_connected_to {node nodemapping {retval ""}} {
  65: 	array set node_mapping $nodemapping
  66: 
  67: 	lappend retval $node
  68: 
  69: 	if {[info exists node_mapping($node)]} {
  70: 		foreach target [lsort -dictionary $node_mapping($node)] {
  71: 			set dhost [lindex $target 1]
  72: 
  73: 			# Exclude hosts we have already visited
  74: 			if {[lsearch -exact $retval $dhost] != -1} {
  75: 				continue
  76: 			}
  77: 
  78: 			lappend retval $dhost
  79: 
  80: 			foreach childnode [get_all_nodes_connected_to $dhost $nodemapping $retval] {
  81: 				if {[lsearch -exact $retval $childnode] != -1} {
  82: 					continue
  83: 				}
  84: 
  85: 				lappend retval $childnode
  86: 			}
  87: 		}
  88: 
  89: 		unset node_mapping($node)
  90: 	}
  91: 
  92: 	foreach tmpnode [array names node_mapping] {
  93: 		foreach target [lsort -dictionary $node_mapping($tmpnode)] {
  94: 			set dhost [lindex $target 1]
  95: 
  96: 			if {$dhost == $node} {
  97: 				if {[lsearch -exact $retval $tmpnode] != -1} {
  98: 					continue
  99: 				}
 100: 
 101: 				lappend retval $tmpnode
 102: 
 103: 				foreach childnode [get_all_nodes_connected_to $tmpnode $nodemapping $retval] {
 104: 					if {[lsearch -exact $retval $childnode] != -1} {
 105: 						continue
 106: 					}
 107: 
 108: 					lappend retval $childnode
 109: 				}
 110: 			}
 111: 		}
 112: 
 113: 		unset node_mapping($tmpnode)
 114: 	}
 115: 
 116: 	return $retval
 117: }
 118: 
 119: ## Process user actions
 120: for {set idx 0} {$idx < [llength $argv]} {incr idx} {
 121: 	set arg [lindex $argv $idx]
 122: 
 123: 	if {[string index $arg 0] != "-"} {
 124: 		break
 125: 	}
 126: 
 127: 	switch -- $arg {
 128: 		"-p" - "--drawports" {
 129: 			set options(DrawPorts) 1
 130: 		}
 131: 		"-n" - "--nodrawports" {
 132: 			set options(DrawPorts) 0
 133: 		}
 134: 		"-r" - "--righttoleft" {
 135: 			set options(RankDir) RL
 136: 		}
 137: 		"-t" - "--toptobottom" {
 138: 			set options(RankDir) BT
 139: 		}
 140: 		"-R" {
 141: 			set options(RankDir) [string reverse $options(RankDir)]
 142: 		}
 143: 		"-w" - "--widenodes" {
 144: 			set options(DrawPortsSideways) 1
 145: 		}
 146: 		"-P" - "--postscript" {
 147: 			set options(PostScript) 1
 148: 		}
 149: 		"-u" - "--unclusterconnected" {
 150: 			set options(ClusterConnected) 0
 151: 		}
 152: 		default {
 153: 			if {$arg == "-h" || $arg == "--help"} {
 154: 				set outfd stdout
 155: 				set retval 0
 156: 			} else {
 157: 				puts stderr "Unknown argument: \"$arg\""
 158: 				set outfd stderr
 159: 				set retval 1
 160: 			}
 161: 
 162: 			puts $outfd "Usage: $argv0 \[-pnrtwPu\] \[--\[no\]drawports\] \[--righttoleft\] \[--toptobottom\] \[--widenodes\] \[--postscript\] \[--unclusterconnected\]"
 163: 
 164: 			exit $retval
 165: 		}
 166: 	}
 167: }
 168: set argv [lrange $argv $idx end]
 169: 
 170: # Set the title
 171: set options(Title) [lindex $argv 0]
 172: if {$options(Title) == ""} {
 173: 	set options(Title) "Unnamed Graph [clock format [clock seconds]] [info host]"
 174: }
 175: 
 176: # Hard-coded definitions
 177: set attr_info([list cluster *]) {cluster=$VALUE}
 178: 
 179: # Read edges from standard input
 180: set data [read stdin]
 181: foreach line [split $data \n] {
 182: 	# Check for pre-processor directives
 183: 	if {[regexp {^# *define  *} $line]} {
 184: 		set preproc [split [regsub {^# *define  *} $line {}]]
 185: 		set attr_name [lindex $preproc 0]
 186: 		set attr_val [lindex $preproc 1]
 187: 		set newattrs [join [lrange $preproc 2 end]]
 188: 
 189: 		lappend attr_info([list $attr_name $attr_val]) $newattrs
 190: 		continue
 191: 	}
 192: 
 193: 	if {[regexp {^# *setdot  *} $line]} {
 194: 		set preproc [split [regsub {^# *setdot  *} $line {}]]
 195: 		set set_node [lindex $preproc 0]
 196: 		set set_dot_attr [lindex $preproc 1]
 197: 		set set_dot_attrval  [join [lrange $preproc 2 end]]
 198: 		lappend user_attr($set_node) $set_dot_attr $set_dot_attrval
 199: 	}
 200: 
 201: 	
 202: 	if {[regexp {^# *set  *} $line]} {
 203: 		set preproc [split [regsub {^# *set  *} $line {}]]
 204: 		set set_node [lindex $preproc 0]
 205: 		set set_dot_attr [lindex $preproc 1]
 206: 		set set_dot_attrval  [join [lrange $preproc 2 end]]
 207: 
 208: 		set info [join [list "$set_node.$set_dot_attr" "$set_dot_attrval"] =]
 209: 		set line [join [list "" "" $info] "|"]
 210: 	}
 211: 
 212: 	if {[regexp {^# *option  *} $line]} {
 213: 		set preproc [split [regsub {^# *option  *} $line {}]]
 214: 		set set_option_name [lindex $preproc 0]
 215: 		set set_option_val [join [lrange $preproc 1 end]]
 216: 
 217: 		set options($set_option_name) $set_option_val
 218: 	}
 219: 
 220: 	# Remove comments and blank lines
 221: 	set line [regsub {#.*$} $line {}]
 222: 	set line [string trim $line]
 223: 	if {$line == ""} {
 224: 		continue
 225: 	}
 226: 
 227: 	# Tokenize into source, destination, and informational sections
 228: 	set work [split $line |]
 229: 
 230: 	if {[string match "*=*" [lindex $work end]]} {
 231: 		set info [string trim [lindex $work end]]
 232: 		set work [lrange $work 0 end-1]
 233: 	} else {
 234: 		set info ""
 235: 	}
 236: 
 237: 	set pathnodes [list]
 238: 	set pathedges [list]
 239: 	for {set dstidx 1} {$dstidx < [llength $work]} {incr dstidx} {
 240: 		set source [string trim [lindex $work [expr $dstidx - 1]]]
 241: 		set dest [string trim [lindex $work $dstidx]]
 242: 
 243: 		# Tokenize source and destination into node and port
 244: 		set source_work [split $source :]
 245: 		set dest_work [split $dest :]
 246: 
 247: 		set source_node [lindex $source_work 0]
 248: 		set source_port [lindex $source_work 1]
 249: 
 250: 		set dest_node [lindex $dest_work 0]
 251: 		set dest_port [lindex $dest_work 1]
 252: 
 253: 		# Skip entries with no source or destination node
 254: 		if {$source_node == "" || $dest_node == ""} {
 255: 			continue
 256: 		}
 257: 
 258: 		lappend node_mapping($source_node) [list $source_port $dest_node $dest_port]
 259: 
 260: 		lappend pathnodes $source_node
 261: 
 262: 		if {$source_port != ""} {
 263: 			lappend pathedges $source_node:$source_port
 264: 		}
 265: 	}
 266: 	if {[info exists dest_node]} {
 267: 		lappend pathnodes $dest_node
 268: 
 269: 		if {$dest_port != ""} {
 270: 			lappend pathedges $dest_node:$dest_port
 271: 		}
 272: 	}
 273: 
 274: 	# Process informational section
 275: 	foreach infoentry [split $info ,] {
 276: 		set infoentry [string map [list "\\=" "||||"] $infoentry]
 277: 		set infoentrywork [split $infoentry =]
 278: 		set infoentrywork [string map [list "||||" "="] $infoentrywork]
 279: 		set infoentry_targetattr [lindex $infoentrywork 0]
 280: 		set infoentry_attrval [lindex $infoentrywork 1]
 281: 
 282: 		set infoentry_targetattrwork [split $infoentry_targetattr .]
 283: 		set infoentry_target [lindex $infoentry_targetattrwork 0]
 284: 		set infoentry_attr [lindex $infoentry_targetattrwork 1]
 285: 
 286: 		foreach entrychk [array names attr_info] {
 287: 			set attr_name [lindex $entrychk 0]
 288: 			set attr_val [lindex $entrychk 1]
 289: 
 290: 			if {$infoentry_attr != $attr_name} {
 291: 				continue
 292: 			}
 293: 
 294: 			if {[string match $attr_val $infoentry_attrval]} {
 295: 				foreach dotattr $attr_info($entrychk) {
 296: 					set VALUE $infoentry_attrval
 297: 
 298: 					set dotattrwork [split $dotattr =]
 299: 					set dotattr_name [lindex $dotattrwork 0]
 300: 					set dotattr_val [subst [lindex $dotattrwork 1]]
 301: 
 302: 					set targets [list]
 303: 					switch -- $infoentry_target {
 304: 						"__PATHNODES__" {
 305: 							foreach pathnode $pathnodes {
 306: 								lappend targets $pathnode
 307: 							}
 308: 						}
 309: 						"__PATHEDGES__" {
 310: 							foreach pathedge $pathedges {
 311: 								lappend targets $pathedge
 312: 							}
 313: 						}
 314: 						default {
 315: 							lappend targets $infoentry_target
 316: 						}
 317: 					}
 318: 
 319: 					foreach target $targets {
 320: 						lappend user_attr($target) $dotattr_name $dotattr_val
 321: 					}
 322: 				}
 323: 			}
 324: 		}
 325: 	}
 326: }
 327: 
 328: # Emit DOT header.
 329: puts "digraph \"$options(Title)\" {"
 330: puts "  label = \"$options(Title)\";"
 331: puts {  normalize = true;}
 332: puts "  rankdir = $options(RankDir);"
 333: puts "  ranksep = $options(RankSep);"
 334: puts {  overlap = false;}
 335: puts {  remincross = true;}
 336: puts {  pack = true;}
 337: puts {  compound = true;}
 338: puts {  center = true;}
 339: if {$options(PostScript)} {
 340: 	puts {  page = "8.5, 11.0";}
 341: }
 342: puts {  outputorder = "nodesfirst";}
 343: puts {  node [shape=box,fontname="Helvetica",style="rounded,filled"];}
 344: puts {  edge [arrowhead=normal,arrowtail=normal,decorate=true];}
 345: if {[info exists options(Splines)]} {
 346: 	puts "  splines = \"$options(Splines)\";"
 347: }
 348: 
 349: # Get list of all nodes
 350: set allnodes [list]
 351: foreach node [array names node_mapping] {
 352: 	foreach target $node_mapping($node) {
 353: 		set dhost [lindex $target 1]
 354: 
 355: 		if {[lsearch -exact $allnodes $dhost] == -1} {
 356: 			lappend allnodes $dhost
 357: 		}
 358: 	}
 359: 
 360: 	if {[lsearch -exact $allnodes $node] == -1} {
 361: 		lappend allnodes $node
 362: 	}
 363: }
 364: 
 365: # Apply wildcard attributes to all nodes
 366: foreach node $allnodes {
 367: 	foreach attr_name [array names user_attr] {
 368: 		if {[string match "$attr_name" $node] && $attr_name != $node} {
 369: 			foreach attr_val $user_attr($attr_name) {
 370: 				set NODENAME $node
 371: 				lappend user_attr($node) [subst $attr_val]
 372: 			}
 373: 		}
 374: 	}
 375: }
 376: 
 377: # Set group on all node from "cluster" Dot attribute
 378: set node_clusters [list]
 379: foreach node $allnodes {
 380: 	unset -nocomplain node_attrs
 381: 	if {[info exists user_attr($node)]} {
 382: 		array set node_attrs $user_attr($node)
 383: 	}
 384: 
 385: 	if {[info exists node_attrs(cluster)]} {
 386: 		set cluster $node_attrs(cluster)
 387: 
 388: 		if {$options(ClusterConnected)} {
 389: 			set connnodes [get_all_nodes_connected_to $node [array get node_mapping]]
 390: 		} else {
 391: 			set connnodes [list $node]
 392: 		}
 393: 
 394: 		foreach connnode $connnodes {
 395: 			unset -nocomplain tmp_node_attrs
 396: 
 397: 			array set tmp_node_attrs [list]
 398: 			if {[info exists user_attr($connnode)]} {
 399: 				array set tmp_node_attrs $user_attr($connnode)
 400: 			}
 401: 
 402: 			unset -nocomplain tmp_node_attrs(cluster)
 403: 			set tmp_node_attrs(group) [__to_dot_node cluster_$cluster]
 404: 			if {[lsearch -exact $node_clusters $cluster] == -1} {
 405: 				lappend node_clusters $cluster
 406: 			}
 407: 
 408: 			set user_attr($connnode) [array get tmp_node_attrs]
 409: 		}
 410: 	}
 411: }
 412: unset -nocomplain node_attrs tmp_node_attrs connnode node cluster
 413: lappend node_clusters __DEFAULT__
 414: 
 415: # Draw all nodes
 416: foreach node [array names node_mapping] {
 417: 	foreach target $node_mapping($node) {
 418: 		set sport [lindex $target 0]
 419: 		set dhost [lindex $target 1]
 420: 		set dport [lindex $target 2]
 421: 
 422: 		if {$sport != ""} {
 423: 			lappend node_ports($node) $sport
 424: 		}
 425: 		if {$dport != "" && $dhost != ""} {
 426: 			lappend node_ports($dhost) $dport
 427: 		}
 428: 
 429: 	}
 430: }
 431: 
 432: foreach cluster $node_clusters {
 433: 	if {$cluster != "__DEFAULT__"} {
 434: 		puts "  subgraph \"[__to_dot_node cluster_$cluster]\" \{"
 435: 		puts "    label = \"$cluster\";"
 436: 		set indent "    "
 437: 	} else {
 438: 		set indent "  "
 439: 	}
 440: 
 441: 	foreach node $allnodes {
 442: 		unset -nocomplain node_attrs
 443: 		if {[info exists user_attr($node)]} {
 444: 			array set node_attrs $user_attr($node)
 445: 		}
 446: 
 447: 		if {$cluster == "__DEFAULT__"} {
 448: 			if {[info exists node_attrs(group)]} {
 449: 				continue
 450: 			}
 451: 		} else {
 452: 			if {![info exists node_attrs(group)]} {
 453: 				continue
 454: 			}
 455: 
 456: 			if {$node_attrs(group) != [__to_dot_node cluster_${cluster}]} {
 457: 				continue
 458: 			}
 459: 		}
 460: 
 461: 		set node_dot_attrs_list [list]
 462: 		set node_label {<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="1">}
 463: 		append node_label "<TR><TD TITLE=\"[__str_to_dot $node]\" BORDER=\"0\">$node</TD></TR>"
 464: 		if {[info exists node_ports($node)]} {
 465: 
 466: 			if {$options(DrawPorts)} {
 467: 				if {$options(DrawPortsSideways)} {
 468: 					append node_label "<TR>"
 469: 					set portheader ""
 470: 					set porttrailer ""
 471: 				} else {
 472: 					set portheader "<TR>"
 473: 					set porttrailer "</TR>"
 474: 				}
 475: 
 476: 				set foundPorts [list]
 477: 				foreach port [lsort -dictionary $node_ports($node)] {
 478: 					if {[lsearch -exact $foundPorts $port] != -1} {
 479: 						continue
 480: 					}
 481: 
 482: 					lappend foundPorts $port
 483: 
 484: 					append node_label "${portheader}<TD PORT=\"[__to_dot_port $port]\" TITLE=\"[__str_to_dot $node:$port]\">$port</TD>${porttrailer}"
 485: 				}
 486: 
 487: 				if {$options(DrawPortsSideways)} {
 488: 					append node_label "</TR>"
 489: 				}
 490: 
 491: 			}
 492: 
 493: 		}
 494: 		append node_label {</TABLE>}
 495: 
 496: 		set node_attrs(label) "<$node_label>"
 497: 
 498: 		foreach attrlookupnode [list $node __NODE__] {
 499: 			if {[info exists user_attr($attrlookupnode)]} {
 500: 				foreach {attr_name attr_val} $user_attr($attrlookupnode) {
 501: 					set node_attrs($attr_name) $attr_val
 502: 				}
 503: 			}
 504: 		}
 505: 
 506: 		foreach {node_dot_attr_name node_dot_attr_val} [array get node_attrs] {
 507: 			if {$node_dot_attr_name == "cluster"} {
 508: 				continue
 509: 			}
 510: 
 511: 			lappend node_dot_attrs_list "$node_dot_attr_name=$node_dot_attr_val"
 512: 		}
 513: 
 514: 		puts "${indent}[__to_dot_node $node] \[[join $node_dot_attrs_list ,]\];"
 515: 	}
 516: 
 517: 	if {$cluster != "__DEFAULT__"} {
 518: 		puts "  \}"
 519: 	}
 520: }
 521: 
 522: # Draw all edges
 523: if {$options(DrawPortsSideways)} {
 524: 	set compasssrc _
 525: 	set compassdst _
 526: } else {
 527: 	set compasssrc _
 528: 	set compassdst _
 529: }
 530: 
 531: foreach node [array names node_mapping] {
 532: 	# Create edges from node
 533: 	foreach target [lsort -dictionary $node_mapping($node)] {
 534: 		set sport [lindex $target 0]
 535: 		set dhost [lindex $target 1]
 536: 		set dport [lindex $target 2]
 537: 
 538: 		if {!$options(DrawSameNodeEdges)} {
 539: 			if {$node == $dhost} {
 540: 				continue
 541: 			}
 542: 		}
 543: 
 544: 		unset -nocomplain edge_attrs
 545: 		set edge_attrs(splines) "false"
 546: 		set edge_attrs(color) [__ports_to_color $sport $dport]
 547: 
 548: 		foreach attrlookupedge [list __EDGE__ $node-$dhost $node:$sport $dhost:$dport] {
 549: 			if {[info exists user_attr($attrlookupedge)]} {
 550: 				foreach {attr_name attr_val} $user_attr($attrlookupedge) {
 551: 					set edge_attrs($attr_name) $attr_val
 552: 				}
 553: 			}
 554: 		}
 555: 
 556: 		set edge_attrs_list [list]
 557: 		foreach attr [array names edge_attrs] {
 558: 			lappend edge_attrs_list "$attr=\"[string trim $edge_attrs($attr) "\" "]\""
 559: 		}
 560: 
 561: 		set edge_attrs_str [join $edge_attrs_list ,]
 562: 
 563: 		puts "  [__to_dot_node $node]:[__to_dot_port $sport]:${compasssrc} -> [__to_dot_node $dhost]:[__to_dot_port $dport]:${compassdst} \[$edge_attrs_str\];"
 564: 	}
 565: }
 566: 
 567: 
 568: # Emit footer
 569: puts "}"
5941782 [rkeene@sledge /home/rkeene/tmp/graphviz]$

Click here to go back to the directory listing.
Click here to download this file.
last modified: 2018-03-06 17:18:45