kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/cli/cli.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 exposes a CLI interface to the Kythe services. 18 package cli // import "kythe.io/kythe/go/services/cli" 19 20 import ( 21 "context" 22 "encoding/json" 23 "flag" 24 "fmt" 25 "os" 26 "strings" 27 28 "kythe.io/kythe/go/services/filetree" 29 "kythe.io/kythe/go/services/graph" 30 "kythe.io/kythe/go/services/web" 31 "kythe.io/kythe/go/services/xrefs" 32 "kythe.io/kythe/go/serving/identifiers" 33 "kythe.io/kythe/go/util/log" 34 35 "github.com/google/subcommands" 36 "google.golang.org/protobuf/proto" 37 ) 38 39 // DisplayJSON is true if the user wants all service responses to be displayed 40 // as JSON (using the PrintJSON and PrintJSONMessage functions). 41 var DisplayJSON bool 42 43 var ( 44 logRequests = flag.Bool("log_requests", false, "Log all requests to stderr as JSON") 45 out = os.Stdout 46 ) 47 48 var jsonMarshaler = web.JSONMarshaler 49 50 func init() { 51 jsonMarshaler.Options.Indent = " " 52 flag.BoolVar(&DisplayJSON, "json", DisplayJSON, "Display results as JSON") 53 } 54 55 // API contains access points the CLI's backend services. 56 type API struct { 57 XRefService xrefs.Service 58 GraphService graph.Service 59 FileTreeService filetree.Service 60 IdentifierService identifiers.Service 61 } 62 63 // Execute registers all Kythe CLI commands to subcommands.DefaultCommander and 64 // executes it with the given API. 65 func Execute(ctx context.Context, api API) subcommands.ExitStatus { 66 subcommands.ImportantFlag("json") 67 subcommands.ImportantFlag("log_requests") 68 subcommands.Register(subcommands.HelpCommand(), "usage") 69 subcommands.Register(subcommands.FlagsCommand(), "usage") 70 subcommands.Register(subcommands.CommandsCommand(), "usage") 71 72 RegisterCommand(&nodesCommand{}, "graph") 73 RegisterCommand(&edgesCommand{}, "graph") 74 75 RegisterCommand(&identCommand{}, "") 76 RegisterCommand(&lsCommand{}, "") 77 78 RegisterCommand(&decorCommand{}, "xrefs") 79 RegisterCommand(&diagnosticsCommand{}, "xrefs") 80 RegisterCommand(&docsCommand{}, "xrefs") 81 RegisterCommand(&sourceCommand{}, "xrefs") 82 RegisterCommand(&xrefsCommand{}, "xrefs") 83 84 return subcommands.Execute(ctx, api) 85 } 86 87 // RegisterCommand adds a KytheCommand to the list of subcommands for the 88 // specified group. 89 func RegisterCommand(c KytheCommand, group string) { 90 cmd := &commandWrapper{c} 91 subcommands.Register(cmd, group) 92 for _, a := range c.Aliases() { 93 subcommands.Alias(a, cmd) 94 } 95 } 96 97 // TODO(schroederc): more documentation per command 98 // TODO(schroederc): split commands into separate packages 99 100 type commandWrapper struct{ KytheCommand } 101 102 func (w *commandWrapper) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 103 if len(args) != 1 { 104 return subcommands.ExitUsageError 105 } 106 api, ok := args[0].(API) 107 if !ok { 108 return subcommands.ExitUsageError 109 } 110 if err := w.Run(ctx, f, api); err != nil { 111 log.ErrorContextf(ctx, "%v", err) 112 return subcommands.ExitFailure 113 } 114 return subcommands.ExitSuccess 115 } 116 117 // A KytheCommand is a type-safe version of the subcommands.Command interface. 118 type KytheCommand interface { 119 Name() string 120 Aliases() []string 121 Synopsis() string 122 Usage() string 123 SetFlags(*flag.FlagSet) 124 Run(context.Context, *flag.FlagSet, API) error 125 } 126 127 type baseKytheCommand struct{} 128 129 func (kc *baseKytheCommand) Aliases() []string { return nil } 130 func (kc *baseKytheCommand) Usage() string { return "" } 131 func (kc *baseKytheCommand) SetFlags(*flag.FlagSet) {} 132 133 // LogRequest should be passed all proto request messages for logging. 134 func LogRequest(req proto.Message) { 135 if *logRequests { 136 str, err := jsonMarshaler.MarshalToString(req) 137 if err != nil { 138 log.Fatalf("Failed to encode request for logging %v: %v", req, err) 139 } 140 log.Infof("%s: %s", baseTypeName(req), string(str)) 141 } 142 } 143 144 // PrintJSONMessage prints the given proto message to the console. This should 145 // be called whenever the DisplayJSON flag is true. 146 func PrintJSONMessage(resp proto.Message) error { return jsonMarshaler.Marshal(out, resp) } 147 148 // PrintJSON prints the given value to the console. This should be called 149 // whenever the DisplayJSON flag is true. PrintJSONMessage should be preferred 150 // when possible. 151 func PrintJSON(val any) error { return json.NewEncoder(out).Encode(val) } 152 153 func baseTypeName(x any) string { 154 ss := strings.SplitN(fmt.Sprintf("%T", x), ".", 2) 155 if len(ss) == 2 { 156 return ss[1] 157 } 158 return ss[0] 159 }