kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/cli/command_edges.go (about)

     1  /*
     2   * Copyright 2017 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cli
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"html"
    25  	"sort"
    26  	"strings"
    27  
    28  	"kythe.io/kythe/go/services/graph"
    29  	"kythe.io/kythe/go/util/log"
    30  	"kythe.io/kythe/go/util/schema/edges"
    31  	"kythe.io/kythe/go/util/schema/facts"
    32  
    33  	"bitbucket.org/creachadair/stringset"
    34  
    35  	gpb "kythe.io/kythe/proto/graph_go_proto"
    36  )
    37  
    38  type edgesCommand struct {
    39  	baseKytheCommand
    40  	dotGraph    bool
    41  	countOnly   bool
    42  	targetsOnly bool
    43  	edgeKinds   string
    44  	pageToken   string
    45  	pageSize    int
    46  }
    47  
    48  func (edgesCommand) Name() string     { return "edges" }
    49  func (edgesCommand) Synopsis() string { return "retrieve outward edges from a node" }
    50  func (c *edgesCommand) SetFlags(flag *flag.FlagSet) {
    51  	flag.BoolVar(&c.dotGraph, "graphviz", false, "Print resulting edges as a dot graph")
    52  	flag.BoolVar(&c.countOnly, "count_only", false, "Only print counts per edge kind")
    53  	flag.BoolVar(&c.targetsOnly, "targets_only", false, "Only display edge targets")
    54  	flag.StringVar(&c.edgeKinds, "kinds", "", "Comma-separated list of edge kinds to return (default returns all)")
    55  	flag.StringVar(&c.pageToken, "page_token", "", "Edges page token")
    56  	flag.IntVar(&c.pageSize, "page_size", 0, "Maximum number of edges returned (0 lets the service use a sensible default)")
    57  }
    58  func (c edgesCommand) Run(ctx context.Context, flag *flag.FlagSet, api API) error {
    59  	if c.countOnly && c.targetsOnly {
    60  		return errors.New("--count_only and --targets_only are mutually exclusive")
    61  	} else if c.countOnly && c.dotGraph {
    62  		return errors.New("--count_only and --graphviz are mutually exclusive")
    63  	} else if c.targetsOnly && c.dotGraph {
    64  		return errors.New("--targets_only and --graphviz are mutually exclusive")
    65  	}
    66  
    67  	req := &gpb.EdgesRequest{
    68  		Ticket:    flag.Args(),
    69  		PageToken: c.pageToken,
    70  		PageSize:  int32(c.pageSize),
    71  	}
    72  	if c.edgeKinds != "" {
    73  		for _, kind := range strings.Split(c.edgeKinds, ",") {
    74  			req.Kind = append(req.Kind, c.expandEdgeKind(kind))
    75  		}
    76  	}
    77  	if c.dotGraph {
    78  		req.Filter = []string{"**"}
    79  	}
    80  	LogRequest(req)
    81  	reply, err := api.GraphService.Edges(ctx, req)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	if reply.NextPageToken != "" {
    86  		defer log.InfoContextf(ctx, "Next page token: %s", reply.NextPageToken)
    87  	}
    88  	if c.countOnly {
    89  		return c.displayEdgeCounts(reply)
    90  	} else if c.targetsOnly {
    91  		return c.displayTargets(reply.EdgeSets)
    92  	} else if c.dotGraph {
    93  		return c.displayEdgeGraph(reply)
    94  	}
    95  	return c.displayEdges(reply)
    96  }
    97  
    98  func (c edgesCommand) displayEdges(reply *gpb.EdgesReply) error {
    99  	if DisplayJSON {
   100  		return PrintJSONMessage(reply)
   101  	}
   102  
   103  	for source, es := range reply.EdgeSets {
   104  		if _, err := fmt.Fprintln(out, "source:", source); err != nil {
   105  			return err
   106  		}
   107  		for kind, g := range es.Groups {
   108  			hasOrdinal := edges.OrdinalKind(kind)
   109  			for _, edge := range g.Edge {
   110  				var ordinal string
   111  				if hasOrdinal || edge.Ordinal != 0 {
   112  					ordinal = fmt.Sprintf(".%d", edge.Ordinal)
   113  				}
   114  				if _, err := fmt.Fprintf(out, "%s%s\t%s\n", kind, ordinal, edge.TargetTicket); err != nil {
   115  					return err
   116  				}
   117  			}
   118  		}
   119  	}
   120  	return nil
   121  }
   122  
   123  func (c edgesCommand) displayTargets(edges map[string]*gpb.EdgeSet) error {
   124  	var targets stringset.Set
   125  	for _, es := range edges {
   126  		for _, g := range es.Groups {
   127  			for _, e := range g.Edge {
   128  				targets.Add(e.TargetTicket)
   129  			}
   130  		}
   131  	}
   132  
   133  	if DisplayJSON {
   134  		return PrintJSON(targets.Elements())
   135  	}
   136  
   137  	for target := range targets {
   138  		if _, err := fmt.Fprintln(out, target); err != nil {
   139  			return err
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  func (c edgesCommand) displayEdgeGraph(reply *gpb.EdgesReply) error {
   146  	nodes := graph.NodesMap(reply.Nodes)
   147  	esets := make(map[string]map[string]stringset.Set)
   148  
   149  	for source, es := range reply.EdgeSets {
   150  		for gKind, g := range es.Groups {
   151  			hasOrdinal := edges.OrdinalKind(gKind)
   152  			for _, edge := range g.Edge {
   153  				tgt := edge.TargetTicket
   154  				src, kind := source, gKind
   155  				if edges.IsReverse(kind) {
   156  					src, kind, tgt = tgt, edges.Mirror(kind), src
   157  				}
   158  				if hasOrdinal || edge.Ordinal != 0 {
   159  					kind = fmt.Sprintf("%s.%d", kind, edge.Ordinal)
   160  				}
   161  				groups, ok := esets[src]
   162  				if !ok {
   163  					groups = make(map[string]stringset.Set)
   164  					esets[src] = groups
   165  				}
   166  				targets, ok := groups[kind]
   167  				if ok {
   168  					targets.Add(tgt)
   169  				} else {
   170  					groups[kind] = stringset.New(tgt)
   171  				}
   172  
   173  			}
   174  		}
   175  	}
   176  	if _, err := fmt.Println("digraph kythe {"); err != nil {
   177  		return err
   178  	}
   179  	for ticket, node := range nodes {
   180  		if _, err := fmt.Printf(`	%q [label=<<table><tr><td colspan="2">%s</td></tr>`, ticket, html.EscapeString(ticket)); err != nil {
   181  			return err
   182  		}
   183  		var factNames []string
   184  		for fact := range node {
   185  			if fact == facts.Code {
   186  				continue
   187  			}
   188  			factNames = append(factNames, fact)
   189  		}
   190  		sort.Strings(factNames)
   191  		for _, fact := range factNames {
   192  			if _, err := fmt.Printf("<tr><td>%s</td><td>%s</td></tr>", html.EscapeString(fact), html.EscapeString(string(node[fact]))); err != nil {
   193  				return err
   194  			}
   195  		}
   196  		if _, err := fmt.Println("</table>> shape=plaintext];"); err != nil {
   197  			return err
   198  		}
   199  	}
   200  	if _, err := fmt.Println(); err != nil {
   201  		return err
   202  	}
   203  
   204  	for src, groups := range esets {
   205  		for kind, targets := range groups {
   206  			for tgt := range targets {
   207  				if _, err := fmt.Printf("\t%q -> %q [label=%q];\n", src, tgt, kind); err != nil {
   208  					return err
   209  				}
   210  			}
   211  		}
   212  	}
   213  	if _, err := fmt.Println("}"); err != nil {
   214  		return err
   215  	}
   216  	return nil
   217  }
   218  
   219  func (c edgesCommand) displayEdgeCounts(edges *gpb.EdgesReply) error {
   220  	counts := make(map[string]int)
   221  	for _, es := range edges.EdgeSets {
   222  		for kind, g := range es.Groups {
   223  			counts[kind] += len(g.Edge)
   224  		}
   225  	}
   226  
   227  	if DisplayJSON {
   228  		return PrintJSON(counts)
   229  	}
   230  
   231  	for kind, cnt := range counts {
   232  		if _, err := fmt.Fprintf(out, "%s\t%d\n", kind, cnt); err != nil {
   233  			return err
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  // expandEdgeKind prefixes unrooted (not starting with "/") edge kinds with the
   240  // standard Kythe edge prefix ("/kythe/edge/").
   241  func (c edgesCommand) expandEdgeKind(kind string) string {
   242  	ck := edges.Canonical(kind)
   243  	if strings.HasPrefix(ck, "/") {
   244  		return kind
   245  	}
   246  
   247  	expansion := edges.Prefix + ck
   248  	if edges.IsReverse(kind) {
   249  		return edges.Mirror(expansion)
   250  	}
   251  	return expansion
   252  }