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  }