cosmossdk.io/client/v2@v2.0.0-beta.1/autocli/common.go (about) 1 package autocli 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/spf13/cobra" 8 "google.golang.org/protobuf/reflect/protoreflect" 9 "sigs.k8s.io/yaml" 10 11 autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" 12 "cosmossdk.io/client/v2/internal/flags" 13 "cosmossdk.io/client/v2/internal/util" 14 ) 15 16 type cmdType int 17 18 const ( 19 queryCmdType cmdType = iota 20 msgCmdType 21 ) 22 23 func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions, exec func(cmd *cobra.Command, input protoreflect.Message) error) (*cobra.Command, error) { 24 if options == nil { 25 // use the defaults 26 options = &autocliv1.RpcCommandOptions{} 27 } 28 29 short := options.Short 30 if short == "" { 31 short = fmt.Sprintf("Execute the %s RPC method", descriptor.Name()) 32 } 33 34 long := options.Long 35 if long == "" { 36 long = util.DescriptorDocs(descriptor) 37 } 38 39 inputDesc := descriptor.Input() 40 inputType := util.ResolveMessageType(b.TypeResolver, inputDesc) 41 42 use := options.Use 43 if use == "" { 44 use = protoNameToCliName(descriptor.Name()) 45 } 46 47 cmd := &cobra.Command{ 48 SilenceUsage: false, 49 Use: use, 50 Long: long, 51 Short: short, 52 Example: options.Example, 53 Aliases: options.Alias, 54 SuggestFor: options.SuggestFor, 55 Deprecated: options.Deprecated, 56 Version: options.Version, 57 } 58 59 cmd.SetContext(context.Background()) 60 binder, err := b.AddMessageFlags(cmd.Context(), cmd.Flags(), inputType, options) 61 if err != nil { 62 return nil, err 63 } 64 65 cmd.Args = binder.CobraArgs 66 67 cmd.RunE = func(cmd *cobra.Command, args []string) error { 68 input, err := binder.BuildMessage(args) 69 if err != nil { 70 return err 71 } 72 73 // signer related logic, triggers only when there is a signer defined 74 if binder.SignerInfo.FieldName != "" { 75 // mark the signer flag as required if defined 76 // TODO(@julienrbrt): UX improvement by only marking the flag as required when there is more than one key in the keyring; 77 // when there is only one key, use that key by default. 78 if binder.SignerInfo.IsFlag { 79 if err := cmd.MarkFlagRequired(binder.SignerInfo.FieldName); err != nil { 80 return err 81 } 82 83 // the client context uses the from flag to determine the signer. 84 // this sets the signer flags to the from flag value if a custom signer flag is set. 85 if binder.SignerInfo.FieldName != flags.FlagFrom { 86 signer, err := cmd.Flags().GetString(binder.SignerInfo.FieldName) 87 if err != nil { 88 return fmt.Errorf("failed to get signer flag: %w", err) 89 } 90 91 if err := cmd.Flags().Set(flags.FlagFrom, signer); err != nil { 92 return err 93 } 94 } 95 } else { 96 // if the signer is not a flag, it is a positional argument 97 // we need to get the correct positional arguments 98 if err := cmd.Flags().Set(flags.FlagFrom, args[binder.SignerInfo.PositionalArgIndex]); err != nil { 99 return err 100 } 101 } 102 } 103 104 return exec(cmd, input) 105 } 106 107 return cmd, nil 108 } 109 110 // enhanceCommandCommon enhances the provided query or msg command with either generated commands based on the provided module 111 // options or the provided custom commands for each module. If the provided query command already contains a command 112 // for a module, that command is not over-written by this method. This allows a graceful addition of autocli to 113 // automatically fill in missing commands. 114 func (b *Builder) enhanceCommandCommon( 115 cmd *cobra.Command, 116 cmdType cmdType, 117 appOptions AppOptions, 118 customCmds map[string]*cobra.Command, 119 ) error { 120 moduleOptions := appOptions.ModuleOptions 121 if len(moduleOptions) == 0 { 122 moduleOptions = make(map[string]*autocliv1.ModuleOptions) 123 } 124 for name, module := range appOptions.Modules { 125 if _, ok := moduleOptions[name]; !ok { 126 if module, ok := module.(HasAutoCLIConfig); ok { 127 moduleOptions[name] = module.AutoCLIOptions() 128 } else { 129 moduleOptions[name] = nil 130 } 131 } 132 } 133 134 for moduleName, modOpts := range moduleOptions { 135 hasModuleOptions := modOpts != nil 136 137 // if we have an existing command skip adding one here 138 if subCmd := findSubCommand(cmd, moduleName); subCmd != nil { 139 if hasModuleOptions { // check if we need to enhance the existing command 140 if err := enhanceCustomCmd(b, subCmd, cmdType, modOpts); err != nil { 141 return err 142 } 143 } 144 145 continue 146 } 147 148 // if we have a custom command use that instead of generating one 149 if custom, ok := customCmds[moduleName]; ok { 150 if hasModuleOptions { // check if we need to enhance the existing command 151 if err := enhanceCustomCmd(b, custom, cmdType, modOpts); err != nil { 152 return err 153 } 154 } 155 156 cmd.AddCommand(custom) 157 continue 158 } 159 160 // if we don't have module options, skip adding a command as we don't have anything to add 161 if !hasModuleOptions { 162 continue 163 } 164 165 switch cmdType { 166 case queryCmdType: 167 if err := enhanceQuery(b, moduleName, cmd, modOpts); err != nil { 168 return err 169 } 170 case msgCmdType: 171 if err := enhanceMsg(b, moduleName, cmd, modOpts); err != nil { 172 return err 173 } 174 } 175 } 176 177 return nil 178 } 179 180 // enhanceQuery enhances the provided query command with the autocli commands for a module. 181 func enhanceQuery(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error { 182 if queryCmdDesc := modOpts.Query; queryCmdDesc != nil { 183 subCmd := topLevelCmd(moduleName, fmt.Sprintf("Querying commands for the %s module", moduleName)) 184 if err := builder.AddQueryServiceCommands(subCmd, queryCmdDesc); err != nil { 185 return err 186 } 187 188 cmd.AddCommand(subCmd) 189 } 190 191 return nil 192 } 193 194 // enhanceMsg enhances the provided msg command with the autocli commands for a module. 195 func enhanceMsg(builder *Builder, moduleName string, cmd *cobra.Command, modOpts *autocliv1.ModuleOptions) error { 196 if txCmdDesc := modOpts.Tx; txCmdDesc != nil { 197 subCmd := topLevelCmd(moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName)) 198 if err := builder.AddMsgServiceCommands(subCmd, txCmdDesc); err != nil { 199 return err 200 } 201 202 cmd.AddCommand(subCmd) 203 } 204 205 return nil 206 } 207 208 // enhanceCustomCmd enhances the provided custom query or msg command autocli commands for a module. 209 func enhanceCustomCmd(builder *Builder, cmd *cobra.Command, cmdType cmdType, modOpts *autocliv1.ModuleOptions) error { 210 switch cmdType { 211 case queryCmdType: 212 if modOpts.Query != nil && modOpts.Query.EnhanceCustomCommand { 213 if err := builder.AddQueryServiceCommands(cmd, modOpts.Query); err != nil { 214 return err 215 } 216 } 217 case msgCmdType: 218 if modOpts.Tx != nil && modOpts.Tx.EnhanceCustomCommand { 219 if err := builder.AddMsgServiceCommands(cmd, modOpts.Tx); err != nil { 220 return err 221 } 222 } 223 } 224 225 return nil 226 } 227 228 // outOrStdoutFormat formats the output based on the output flag and writes it to the command's output stream. 229 func (b *Builder) outOrStdoutFormat(cmd *cobra.Command, out []byte) error { 230 var err error 231 outputType := cmd.Flag(flags.FlagOutput) 232 // if the output type is text, convert the json to yaml 233 // if output type is json or nil, default to json 234 if outputType != nil && outputType.Value.String() == flags.OutputFormatText { 235 out, err = yaml.JSONToYAML(out) 236 if err != nil { 237 return err 238 } 239 } 240 241 _, err = fmt.Fprintln(cmd.OutOrStdout(), string(out)) 242 return err 243 }