github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/cmd/digraph/digraph.go (about) 1 // The digraph command performs queries over unlabelled directed graphs 2 // represented in text form. It is intended to integrate nicely with 3 // typical UNIX command pipelines. 4 // 5 // Since directed graphs (import graphs, reference graphs, call graphs, 6 // etc) often arise during software tool development and debugging, this 7 // command is included in the go.tools repository. 8 // 9 // TODO(adonovan): 10 // - support input files other than stdin 11 // - suport alternative formats (AT&T GraphViz, CSV, etc), 12 // a comment syntax, etc. 13 // - allow queries to nest, like Blaze query language. 14 // 15 package main // import "golang.org/x/tools/cmd/digraph" 16 17 import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "sort" 26 "strconv" 27 "unicode" 28 "unicode/utf8" 29 ) 30 31 const Usage = `digraph: queries over directed graphs in text form. 32 33 Graph format: 34 35 Each line contains zero or more words. Words are separated by 36 unquoted whitespace; words may contain Go-style double-quoted portions, 37 allowing spaces and other characters to be expressed. 38 39 Each field declares a node, and if there are more than one, 40 an edge from the first to each subsequent one. 41 The graph is provided on the standard input. 42 43 For instance, the following (acyclic) graph specifies a partial order 44 among the subtasks of getting dressed: 45 46 % cat clothes.txt 47 socks shoes 48 "boxer shorts" pants 49 pants belt shoes 50 shirt tie sweater 51 sweater jacket 52 hat 53 54 The line "shirt tie sweater" indicates the two edges shirt -> tie and 55 shirt -> sweater, not shirt -> tie -> sweater. 56 57 Supported queries: 58 59 nodes 60 the set of all nodes 61 degree 62 the in-degree and out-degree of each node. 63 preds <label> ... 64 the set of immediate predecessors of the specified nodes 65 succs <label> ... 66 the set of immediate successors of the specified nodes 67 forward <label> ... 68 the set of nodes transitively reachable from the specified nodes 69 reverse <label> ... 70 the set of nodes that transitively reach the specified nodes 71 somepath <label> <label> 72 the list of nodes on some arbitrary path from the first node to the second 73 allpaths <label> <label> 74 the set of nodes on all paths from the first node to the second 75 sccs 76 all strongly connected components (one per line) 77 scc <label> 78 the set of nodes nodes strongly connected to the specified one 79 80 Example usage: 81 82 Show the transitive closure of imports of the digraph tool itself: 83 % go list -f '{{.ImportPath}}{{.Imports}}' ... | tr '[]' ' ' | 84 digraph forward golang.org/x/tools/cmd/digraph 85 86 Show which clothes (see above) must be donned before a jacket: 87 % digraph reverse jacket <clothes.txt 88 89 ` 90 91 func main() { 92 flag.Parse() 93 94 args := flag.Args() 95 if len(args) == 0 { 96 fmt.Println(Usage) 97 return 98 } 99 100 if err := digraph(args[0], args[1:]); err != nil { 101 fmt.Fprintf(os.Stderr, "digraph: %s\n", err) 102 os.Exit(1) 103 } 104 } 105 106 type nodelist []string 107 108 func (l nodelist) println(sep string) { 109 for i, label := range l { 110 if i > 0 { 111 fmt.Fprint(stdout, sep) 112 } 113 fmt.Fprint(stdout, label) 114 } 115 fmt.Fprintln(stdout) 116 } 117 118 type nodeset map[string]bool 119 120 func (s nodeset) sort() nodelist { 121 labels := make(nodelist, len(s)) 122 var i int 123 for label := range s { 124 labels[i] = label 125 i++ 126 } 127 sort.Strings(labels) 128 return labels 129 } 130 131 func (s nodeset) addAll(x nodeset) { 132 for label := range x { 133 s[label] = true 134 } 135 } 136 137 // A graph maps nodes to the non-nil set of their immediate successors. 138 type graph map[string]nodeset 139 140 func (g graph) addNode(label string) nodeset { 141 edges := g[label] 142 if edges == nil { 143 edges = make(nodeset) 144 g[label] = edges 145 } 146 return edges 147 } 148 149 func (g graph) addEdges(from string, to ...string) { 150 edges := g.addNode(from) 151 for _, to := range to { 152 g.addNode(to) 153 edges[to] = true 154 } 155 } 156 157 func (g graph) reachableFrom(roots nodeset) nodeset { 158 seen := make(nodeset) 159 var visit func(label string) 160 visit = func(label string) { 161 if !seen[label] { 162 seen[label] = true 163 for e := range g[label] { 164 visit(e) 165 } 166 } 167 } 168 for root := range roots { 169 visit(root) 170 } 171 return seen 172 } 173 174 func (g graph) transpose() graph { 175 rev := make(graph) 176 for label, edges := range g { 177 rev.addNode(label) 178 for succ := range edges { 179 rev.addEdges(succ, label) 180 } 181 } 182 return rev 183 } 184 185 func (g graph) sccs() []nodeset { 186 // Kosaraju's algorithm---Tarjan is overkill here. 187 188 // Forward pass. 189 S := make(nodelist, 0, len(g)) // postorder stack 190 seen := make(nodeset) 191 var visit func(label string) 192 visit = func(label string) { 193 if !seen[label] { 194 seen[label] = true 195 for e := range g[label] { 196 visit(e) 197 } 198 S = append(S, label) 199 } 200 } 201 for label := range g { 202 visit(label) 203 } 204 205 // Reverse pass. 206 rev := g.transpose() 207 var scc nodeset 208 seen = make(nodeset) 209 var rvisit func(label string) 210 rvisit = func(label string) { 211 if !seen[label] { 212 seen[label] = true 213 scc[label] = true 214 for e := range rev[label] { 215 rvisit(e) 216 } 217 } 218 } 219 var sccs []nodeset 220 for len(S) > 0 { 221 top := S[len(S)-1] 222 S = S[:len(S)-1] // pop 223 if !seen[top] { 224 scc = make(nodeset) 225 rvisit(top) 226 sccs = append(sccs, scc) 227 } 228 } 229 return sccs 230 } 231 232 func parse(rd io.Reader) (graph, error) { 233 g := make(graph) 234 235 var linenum int 236 in := bufio.NewScanner(rd) 237 for in.Scan() { 238 linenum++ 239 // Split into words, honoring double-quotes per Go spec. 240 words, err := split(in.Text()) 241 if err != nil { 242 return nil, fmt.Errorf("at line %d: %v", linenum, err) 243 } 244 if len(words) > 0 { 245 g.addEdges(words[0], words[1:]...) 246 } 247 } 248 if err := in.Err(); err != nil { 249 return nil, err 250 } 251 return g, nil 252 } 253 254 var stdin io.Reader = os.Stdin 255 var stdout io.Writer = os.Stdout 256 257 func digraph(cmd string, args []string) error { 258 // Parse the input graph. 259 g, err := parse(stdin) 260 if err != nil { 261 return err 262 } 263 264 // Parse the command line. 265 switch cmd { 266 case "nodes": 267 if len(args) != 0 { 268 return fmt.Errorf("usage: digraph nodes") 269 } 270 nodes := make(nodeset) 271 for label := range g { 272 nodes[label] = true 273 } 274 nodes.sort().println("\n") 275 276 case "degree": 277 if len(args) != 0 { 278 return fmt.Errorf("usage: digraph degree") 279 } 280 nodes := make(nodeset) 281 for label := range g { 282 nodes[label] = true 283 } 284 rev := g.transpose() 285 for _, label := range nodes.sort() { 286 fmt.Fprintf(stdout, "%d\t%d\t%s\n", len(rev[label]), len(g[label]), label) 287 } 288 289 case "succs", "preds": 290 if len(args) == 0 { 291 return fmt.Errorf("usage: digraph %s <label> ...", cmd) 292 } 293 g := g 294 if cmd == "preds" { 295 g = g.transpose() 296 } 297 result := make(nodeset) 298 for _, root := range args { 299 edges := g[root] 300 if edges == nil { 301 return fmt.Errorf("no such node %q", root) 302 } 303 result.addAll(edges) 304 } 305 result.sort().println("\n") 306 307 case "forward", "reverse": 308 if len(args) == 0 { 309 return fmt.Errorf("usage: digraph %s <label> ...", cmd) 310 } 311 roots := make(nodeset) 312 for _, root := range args { 313 if g[root] == nil { 314 return fmt.Errorf("no such node %q", root) 315 } 316 roots[root] = true 317 } 318 g := g 319 if cmd == "reverse" { 320 g = g.transpose() 321 } 322 g.reachableFrom(roots).sort().println("\n") 323 324 case "somepath": 325 if len(args) != 2 { 326 return fmt.Errorf("usage: digraph somepath <from> <to>") 327 } 328 from, to := args[0], args[1] 329 if g[from] == nil { 330 return fmt.Errorf("no such 'from' node %q", from) 331 } 332 if g[to] == nil { 333 return fmt.Errorf("no such 'to' node %q", to) 334 } 335 336 seen := make(nodeset) 337 var visit func(path nodelist, label string) bool 338 visit = func(path nodelist, label string) bool { 339 if !seen[label] { 340 seen[label] = true 341 if label == to { 342 append(path, label).println("\n") 343 return true // unwind 344 } 345 for e := range g[label] { 346 if visit(append(path, label), e) { 347 return true 348 } 349 } 350 } 351 return false 352 } 353 if !visit(make(nodelist, 0, 100), from) { 354 return fmt.Errorf("no path from %q to %q", args[0], args[1]) 355 } 356 357 case "allpaths": 358 if len(args) != 2 { 359 return fmt.Errorf("usage: digraph allpaths <from> <to>") 360 } 361 from, to := args[0], args[1] 362 if g[from] == nil { 363 return fmt.Errorf("no such 'from' node %q", from) 364 } 365 if g[to] == nil { 366 return fmt.Errorf("no such 'to' node %q", to) 367 } 368 369 seen := make(nodeset) // value of seen[x] indicates whether x is on some path to 'to' 370 var visit func(label string) bool 371 visit = func(label string) bool { 372 reachesTo, ok := seen[label] 373 if !ok { 374 reachesTo = label == to 375 376 seen[label] = reachesTo 377 for e := range g[label] { 378 if visit(e) { 379 reachesTo = true 380 } 381 } 382 seen[label] = reachesTo 383 } 384 return reachesTo 385 } 386 if !visit(from) { 387 return fmt.Errorf("no path from %q to %q", from, to) 388 } 389 for label, reachesTo := range seen { 390 if !reachesTo { 391 delete(seen, label) 392 } 393 } 394 seen.sort().println("\n") 395 396 case "sccs": 397 if len(args) != 0 { 398 return fmt.Errorf("usage: digraph sccs") 399 } 400 for _, scc := range g.sccs() { 401 scc.sort().println(" ") 402 } 403 404 case "scc": 405 if len(args) != 1 { 406 return fmt.Errorf("usage: digraph scc <label>") 407 } 408 label := args[0] 409 if g[label] == nil { 410 return fmt.Errorf("no such node %q", label) 411 } 412 for _, scc := range g.sccs() { 413 if scc[label] { 414 scc.sort().println("\n") 415 break 416 } 417 } 418 419 default: 420 return fmt.Errorf("no such command %q", cmd) 421 } 422 423 return nil 424 } 425 426 // -- Utilities -------------------------------------------------------- 427 428 // split splits a line into words, which are generally separated by 429 // spaces, but Go-style double-quoted string literals are also supported. 430 // (This approximates the behaviour of the Bourne shell.) 431 // 432 // `one "two three"` -> ["one" "two three"] 433 // `a"\n"b` -> ["a\nb"] 434 // 435 func split(line string) ([]string, error) { 436 var ( 437 words []string 438 inWord bool 439 current bytes.Buffer 440 ) 441 442 for len(line) > 0 { 443 r, size := utf8.DecodeRuneInString(line) 444 if unicode.IsSpace(r) { 445 if inWord { 446 words = append(words, current.String()) 447 current.Reset() 448 inWord = false 449 } 450 } else if r == '"' { 451 var ok bool 452 size, ok = quotedLength(line) 453 if !ok { 454 return nil, errors.New("invalid quotation") 455 } 456 s, err := strconv.Unquote(line[:size]) 457 if err != nil { 458 return nil, err 459 } 460 current.WriteString(s) 461 inWord = true 462 } else { 463 current.WriteRune(r) 464 inWord = true 465 } 466 line = line[size:] 467 } 468 if inWord { 469 words = append(words, current.String()) 470 } 471 return words, nil 472 } 473 474 // quotedLength returns the length in bytes of the prefix of input that 475 // contain a possibly-valid double-quoted Go string literal. 476 // 477 // On success, n is at least two (""); input[:n] may be passed to 478 // strconv.Unquote to interpret its value, and input[n:] contains the 479 // rest of the input. 480 // 481 // On failure, quotedLength returns false, and the entire input can be 482 // passed to strconv.Unquote if an informative error message is desired. 483 // 484 // quotedLength does not and need not detect all errors, such as 485 // invalid hex or octal escape sequences, since it assumes 486 // strconv.Unquote will be applied to the prefix. It guarantees only 487 // that if there is a prefix of input containing a valid string literal, 488 // its length is returned. 489 // 490 // TODO(adonovan): move this into a strconv-like utility package. 491 // 492 func quotedLength(input string) (n int, ok bool) { 493 var offset int 494 495 // next returns the rune at offset, or -1 on EOF. 496 // offset advances to just after that rune. 497 next := func() rune { 498 if offset < len(input) { 499 r, size := utf8.DecodeRuneInString(input[offset:]) 500 offset += size 501 return r 502 } 503 return -1 504 } 505 506 if next() != '"' { 507 return // error: not a quotation 508 } 509 510 for { 511 r := next() 512 if r == '\n' || r < 0 { 513 return // error: string literal not terminated 514 } 515 if r == '"' { 516 return offset, true // success 517 } 518 if r == '\\' { 519 var skip int 520 switch next() { 521 case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"': 522 skip = 0 523 case '0', '1', '2', '3', '4', '5', '6', '7': 524 skip = 2 525 case 'x': 526 skip = 2 527 case 'u': 528 skip = 4 529 case 'U': 530 skip = 8 531 default: 532 return // error: invalid escape 533 } 534 535 for i := 0; i < skip; i++ { 536 next() 537 } 538 } 539 } 540 }