func filesInTable(ctx context.Context, esr esearch.Service) []string { req := &spb.SearchRequest{ Fact: []*spb.SearchRequest_Fact{ &spb.SearchRequest_Fact{ Name: "/kythe/node/kind", Value: []byte("file"), Prefix: false, }, }, } repl, err := esr.Search(ctx, req) if err != nil { log.Fatalf("File search in serving tables failed: %v", err) } return repl.Ticket }
var ( ctx = context.Background() xs xrefs.Service ft filetree.Service idx search.Service // ls flags lsURIs bool filesOnly bool dirsOnly bool // node flags nodeFilters string factSizeThreshold int // edges flags countOnly bool targetsOnly bool edgeKinds string pageToken string pageSize int // source/decor flags decorSpan string // decor flags dirtyFile string refFormat string // xrefs flags defKind, refKind, docKind string relatedNodes bool // search flags suffixWildcard string corpus string root string path string language string signature string spanHelp = `Limit results to this span (e.g. "10-30", "b1462-b1847", "3:5-3:10") Formats: b\d+-b\d+ -- Byte-offsets \d+(:\d+)?-\d+(:\d+)? -- Line offsets with optional column offsets` cmdLS = newCommand("ls", "[--uris] [directory-uri]", "List a directory's contents", func(flag *flag.FlagSet) { flag.BoolVar(&lsURIs, "uris", false, "Display files/directories as Kythe URIs") flag.BoolVar(&filesOnly, "files", false, "Display only files") flag.BoolVar(&dirsOnly, "dirs", false, "Display only directories") }, func(flag *flag.FlagSet) error { if filesOnly && dirsOnly { return errors.New("--files and --dirs are mutually exclusive") } if len(flag.Args()) == 0 { req := &ftpb.CorpusRootsRequest{} logRequest(req) cr, err := ft.CorpusRoots(ctx, req) if err != nil { return err } return displayCorpusRoots(cr) } var corpus, root, path string switch len(flag.Args()) { case 1: uri, err := kytheuri.Parse(flag.Arg(0)) if err != nil { log.Fatalf("invalid uri %q: %v", flag.Arg(0), err) } corpus = uri.Corpus root = uri.Root path = uri.Path default: flag.Usage() os.Exit(1) } path = filepath.Join("/", path) req := &ftpb.DirectoryRequest{ Corpus: corpus, Root: root, Path: path, } logRequest(req) dir, err := ft.Directory(ctx, req) if err != nil { return err } else if dir == nil { return fmt.Errorf("no such directory: %q in corpus %q (root %q)", path, corpus, root) } if filesOnly { dir.Subdirectory = nil } else if dirsOnly { dir.File = nil } return displayDirectory(dir) }) cmdEdges = newCommand("edges", "[--count_only] [--kinds edgeKind1,edgeKind2,...] [--page_token token] [--page_size num] <ticket>", "Retrieve outward edges from a node", func(flag *flag.FlagSet) { flag.BoolVar(&countOnly, "count_only", false, "Only print counts per edge kind") flag.BoolVar(&targetsOnly, "targets_only", false, "Only display edge targets") flag.StringVar(&edgeKinds, "kinds", "", "Comma-separated list of edge kinds to return (default returns all)") flag.StringVar(&pageToken, "page_token", "", "Edges page token") flag.IntVar(&pageSize, "page_size", 0, "Maximum number of edges returned (0 lets the service use a sensible default)") }, func(flag *flag.FlagSet) error { req := &xpb.EdgesRequest{ Ticket: flag.Args(), PageToken: pageToken, PageSize: int32(pageSize), } if edgeKinds != "" { req.Kind = strings.Split(edgeKinds, ",") } logRequest(req) reply, err := xs.Edges(ctx, req) if err != nil { return err } if reply.NextPageToken != "" { defer log.Printf("Next page token: %s", reply.NextPageToken) } if countOnly { return displayEdgeCounts(reply) } else if targetsOnly { return displayTargets(reply.EdgeSet) } return displayEdges(reply) }) cmdXRefs = newCommand("xrefs", "[--definitions kind] [--references kind] [--documentation kind] [--related_nodes] [--page_token token] [--page_size num] <ticket>", "Retrieve the global cross-references of the given node", func(flag *flag.FlagSet) { flag.StringVar(&defKind, "definitions", "all", "Kind of definitions to return (kinds: all, binding, full, or none)") flag.StringVar(&refKind, "references", "all", "Kind of references to return (kinds: all or none)") flag.StringVar(&docKind, "documentation", "all", "Kind of documentation to return (kinds: all or none)") flag.BoolVar(&relatedNodes, "related_nodes", false, "Whether to request related nodes") flag.StringVar(&pageToken, "page_token", "", "CrossReferences page token") flag.IntVar(&pageSize, "page_size", 0, "Maximum number of cross-references returned (0 lets the service use a sensible default)") }, func(flag *flag.FlagSet) error { log.Println("WARNING: this API is currently experimental") req := &xpb.CrossReferencesRequest{ Ticket: flag.Args(), PageToken: pageToken, PageSize: int32(pageSize), } if relatedNodes { req.Filter = []string{schema.NodeKindFact} } switch defKind { case "all": req.DefinitionKind = xpb.CrossReferencesRequest_ALL_DEFINITIONS case "none": req.DefinitionKind = xpb.CrossReferencesRequest_NO_DEFINITIONS case "binding": req.DefinitionKind = xpb.CrossReferencesRequest_BINDING_DEFINITIONS case "full": req.DefinitionKind = xpb.CrossReferencesRequest_FULL_DEFINITIONS default: return fmt.Errorf("unknown definition kind: %q", defKind) } switch refKind { case "all": req.ReferenceKind = xpb.CrossReferencesRequest_ALL_REFERENCES case "none": req.ReferenceKind = xpb.CrossReferencesRequest_NO_REFERENCES default: return fmt.Errorf("unknown reference kind: %q", refKind) } switch docKind { case "all": req.DocumentationKind = xpb.CrossReferencesRequest_ALL_DOCUMENTATION case "none": req.DocumentationKind = xpb.CrossReferencesRequest_NO_DOCUMENTATION default: return fmt.Errorf("unknown documentation kind: %q", docKind) } logRequest(req) reply, err := xs.CrossReferences(ctx, req) if err != nil { return err } if reply.NextPageToken != "" { defer log.Printf("Next page token: %s", reply.NextPageToken) } return displayXRefs(reply) }) cmdNode = newCommand("node", "[--filters factFilter1,factFilter2,...] [--max_fact_size] <ticket>", "Retrieve a node's facts", func(flag *flag.FlagSet) { flag.StringVar(&nodeFilters, "filters", "", "Comma-separated list of node fact filters (default returns all)") flag.IntVar(&factSizeThreshold, "max_fact_size", 64, "Maximum size of fact values to display. Facts with byte lengths longer than this value will only have their fact names displayed.") }, func(flag *flag.FlagSet) error { if factSizeThreshold < 0 { return fmt.Errorf("invalid --max_fact_size value (must be non-negative): %d", factSizeThreshold) } req := &xpb.NodesRequest{ Ticket: flag.Args(), } if nodeFilters != "" { req.Filter = strings.Split(nodeFilters, ",") } logRequest(req) reply, err := xs.Nodes(ctx, req) if err != nil { return err } return displayNodes(reply.Node) }) cmdSource = newCommand("source", "[--span span] <file-ticket>", "Retrieve a file's source text", func(flag *flag.FlagSet) { flag.StringVar(&decorSpan, "span", "", spanHelp) }, func(flag *flag.FlagSet) error { req := &xpb.DecorationsRequest{ Location: &xpb.Location{ Ticket: flag.Arg(0), }, SourceText: true, } if decorSpan != "" { start, end, err := parseSpan(decorSpan) if err != nil { return fmt.Errorf("invalid --span %q: %v", decorSpan, err) } req.Location.Kind = xpb.Location_SPAN req.Location.Start = start req.Location.End = end } logRequest(req) reply, err := xs.Decorations(ctx, req) if err != nil { return err } return displaySource(reply) }) cmdDecor = newCommand("decor", "[--format spec] [--dirty file] [--span span] <file-ticket>", "List a file's anchor decorations", func(flag *flag.FlagSet) { // TODO(schroederc): add option to look for dirty files based on file-ticket path and a directory root flag.StringVar(&dirtyFile, "dirty", "", "Send the given file as the dirty buffer for patching references") flag.StringVar(&refFormat, "format", "@edgeKind@\t@^line@:@^col@-@$line@:@$col@\t@nodeKind@\t@target@", `Format for each decoration result. Format Markers: @source@ -- ticket of anchor source node @target@ -- ticket of referenced target node @edgeKind@ -- edge kind from anchor node to its referenced target @nodeKind@ -- node kind of referenced target @subkind@ -- subkind of referenced target @^offset@ -- anchor source's starting byte-offset @^line@ -- anchor source's starting line @^col@ -- anchor source's starting column offset @$offset@ -- anchor source's ending byte-offset @$line@ -- anchor source's ending line @$col@ -- anchor source's ending column offset`) flag.StringVar(&decorSpan, "span", "", spanHelp) }, func(flag *flag.FlagSet) error { req := &xpb.DecorationsRequest{ Location: &xpb.Location{ Ticket: flag.Arg(0), }, References: true, Filter: []string{ schema.NodeKindFact, schema.SubkindFact, schema.AnchorLocFilter, // TODO(schroederc): remove this backwards-compatibility fix }, } if dirtyFile != "" { f, err := vfs.Open(ctx, dirtyFile) if err != nil { return fmt.Errorf("error opening dirty buffer file at %q: %v", dirtyFile, err) } buf, err := ioutil.ReadAll(f) if err != nil { f.Close() return fmt.Errorf("error reading dirty buffer file: %v", err) } else if err := f.Close(); err != nil { return fmt.Errorf("error closing dirty buffer file: %v", err) } req.DirtyBuffer = buf } if decorSpan != "" { start, end, err := parseSpan(decorSpan) if err != nil { return fmt.Errorf("invalid --span %q: %v", decorSpan, err) } req.Location.Kind = xpb.Location_SPAN req.Location.Start = start req.Location.End = end } else { req.SourceText = true // TODO(schroederc): remove need for this } logRequest(req) reply, err := xs.Decorations(ctx, req) if err != nil { return err } if !req.SourceText { // We need to grab the full SourceText to normalize each anchor's // location, but when given a --span, we don't receive the full text and // we require a separate Nodes call. // TODO(schroederc): add Locations for each DecorationsReply_Reference nodesReq := &xpb.NodesRequest{ Ticket: []string{req.Location.Ticket}, Filter: []string{schema.TextFact, schema.TextEncodingFact}, } logRequest(nodesReq) fileNodeReply, err := xs.Nodes(ctx, nodesReq) if err != nil { return err } for _, n := range fileNodeReply.Node { if n.Ticket != req.Location.Ticket { log.Printf("WARNING: received unrequested node: %q", n.Ticket) continue } for _, f := range n.Fact { switch f.Name { case schema.TextFact: reply.SourceText = f.Value case schema.TextEncodingFact: reply.Encoding = string(f.Value) } } } } return displayDecorations(req.DirtyBuffer, reply) }) cmdSearch = newCommand("search", "[--corpus c] [--sig s] [--root r] [--lang l] [--path p] [factName factValue]...", "Search for nodes based on partial components and fact values.", func(flag *flag.FlagSet) { flag.StringVar(&suffixWildcard, "suffix_wildcard", "%", "Suffix wildcard for search values (optional)") flag.StringVar(&corpus, "corpus", "", "Limit results to nodes with the given corpus (optional)") flag.StringVar(&root, "root", "", "Limit results to nodes with the given root (optional)") flag.StringVar(&path, "path", "", "Limit results to nodes with the given path (optional)") flag.StringVar(&signature, "sig", "", "Limit results to nodes with the given signature (optional)") flag.StringVar(&language, "lang", "", "Limit results to nodes with the given language (optional)") }, func(flag *flag.FlagSet) error { if len(flag.Args())%2 != 0 { return fmt.Errorf("given odd number of arguments (%d): %v", len(flag.Args()), flag.Args()) } req := &spb.SearchRequest{ Partial: &spb.VName{ Corpus: strings.TrimSuffix(corpus, suffixWildcard), Signature: strings.TrimSuffix(signature, suffixWildcard), Root: strings.TrimSuffix(root, suffixWildcard), Path: strings.TrimSuffix(path, suffixWildcard), Language: strings.TrimSuffix(language, suffixWildcard), }, } req.PartialPrefix = &spb.VNameMask{ Corpus: req.Partial.Corpus != corpus, Signature: req.Partial.Signature != signature, Root: req.Partial.Root != root, Path: req.Partial.Path != path, Language: req.Partial.Language != language, } for i := 0; i < len(flag.Args()); i = i + 2 { if flag.Arg(i) == schema.TextFact { log.Printf("WARNING: Large facts such as %s are not likely to be indexed", schema.TextFact) } v := strings.TrimSuffix(flag.Arg(i+1), suffixWildcard) req.Fact = append(req.Fact, &spb.SearchRequest_Fact{ Name: flag.Arg(i), Value: []byte(v), Prefix: v != flag.Arg(i+1), }) } logRequest(req) reply, err := idx.Search(ctx, req) if err != nil { return err } return displaySearch(reply) }) )