github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/actor/sharedaction/help.go (about) 1 package sharedaction 2 3 import ( 4 "reflect" 5 "sort" 6 "strings" 7 8 "code.cloudfoundry.org/cli/actor/actionerror" 9 "code.cloudfoundry.org/cli/util/sorting" 10 ) 11 12 const ( 13 CommonCommandsIndent string = " " 14 AllCommandsIndent string = " " 15 CommandIndent string = " " 16 ) 17 18 // CommandInfo contains the help details of a command 19 type CommandInfo struct { 20 // Name is the command name 21 Name string 22 23 // Description is the command description 24 Description string 25 26 // Alias is the command alias 27 Alias string 28 29 // Usage is the command usage string 30 Usage string 31 32 // Examples is the command examples string 33 Examples string 34 35 // Resources is the types of object that the command applies to 36 Resources string 37 38 // RelatedCommands is a list of commands related to the command 39 RelatedCommands []string 40 41 // Flags contains the list of flags for this command 42 Flags []CommandFlag 43 44 // Environment is a list of environment variables specific for this command 45 Environment []EnvironmentVariable 46 } 47 48 // CommandFlag contains the help details of a command's flag 49 type CommandFlag struct { 50 // Short is the short form of the flag 51 Short string 52 53 // Long is the long form of the flag 54 Long string 55 56 // Description is the description of the flag 57 Description string 58 59 // Default is the flag's default value 60 Default string 61 } 62 63 // EnvironmentVariable contains env vars specific for this command 64 type EnvironmentVariable struct { 65 Name string 66 Description string 67 DefaultValue string 68 } 69 70 // HasUsage is an interface that commands may implement if they want to define their usage 71 // text in a Usage() method, which gives them more flexibility than a struct tag. 72 type HasUsage interface { 73 Usage() string 74 } 75 76 // HasExamples is an interface that commands may implement if they want to define their examples 77 // text in a Examples() method, which gives them more flexibility than a struct tag. 78 type HasExamples interface { 79 Examples() string 80 } 81 82 // HasResources is an interface that commands may implement if they want to define their resources 83 // text in a Resources() method, which gives them more flexibility than a struct tag. 84 type HasResources interface { 85 Resources() string 86 } 87 88 // CommandInfoByName returns the help information for a particular commandName in 89 // the commandList. 90 func (Actor) CommandInfoByName(commandList interface{}, commandName string) (CommandInfo, error) { 91 field, found := reflect.TypeOf(commandList).FieldByNameFunc( 92 func(fieldName string) bool { 93 field, _ := reflect.TypeOf(commandList).FieldByName(fieldName) 94 return field.Tag.Get("command") == commandName || field.Tag.Get("alias") == commandName 95 }, 96 ) 97 98 if !found { 99 return CommandInfo{}, actionerror.InvalidCommandError{CommandName: commandName} 100 } 101 102 tag := field.Tag 103 cmd := CommandInfo{ 104 Name: tag.Get("command"), 105 Description: tag.Get("description"), 106 Alias: tag.Get("alias"), 107 Flags: []CommandFlag{}, 108 Environment: []EnvironmentVariable{}, 109 } 110 111 fieldValue := reflect.ValueOf(commandList).FieldByIndex(field.Index) 112 113 if commandWithUsage, hasUsage := fieldValue.Interface().(HasUsage); hasUsage { 114 cmd.Usage = strings.ReplaceAll( 115 strings.TrimSpace(commandWithUsage.Usage()), 116 "\n", 117 "\n"+CommandIndent, 118 ) 119 } 120 121 if commandWithExamples, hasExamples := fieldValue.Interface().(HasExamples); hasExamples { 122 cmd.Examples = strings.ReplaceAll( 123 strings.TrimSpace(commandWithExamples.Examples()), 124 "\n", 125 "\n"+CommandIndent, 126 ) 127 } 128 129 if commandWithResources, hasResources := fieldValue.Interface().(HasResources); hasResources { 130 cmd.Resources = strings.ReplaceAll( 131 strings.TrimSpace(commandWithResources.Resources()), 132 "\n", 133 "\n"+CommandIndent, 134 ) 135 } 136 137 command := field.Type 138 for i := 0; i < command.NumField(); i++ { 139 fieldTag := command.Field(i).Tag 140 141 if fieldTag.Get("hidden") != "" { 142 continue 143 } 144 145 if cmd.Usage == "" && fieldTag.Get("usage") != "" { 146 cmd.Usage = fieldTag.Get("usage") 147 continue 148 } 149 150 if cmd.Examples == "" && fieldTag.Get("examples") != "" { 151 cmd.Examples = fieldTag.Get("examples") 152 continue 153 } 154 155 if fieldTag.Get("related_commands") != "" { 156 relatedCommands := strings.Split(fieldTag.Get("related_commands"), ", ") 157 sort.Slice(relatedCommands, sorting.SortAlphabeticFunc(relatedCommands)) 158 cmd.RelatedCommands = relatedCommands 159 continue 160 } 161 162 if fieldTag.Get("description") != "" { 163 cmd.Flags = append(cmd.Flags, CommandFlag{ 164 Short: fieldTag.Get("short"), 165 Long: fieldTag.Get("long"), 166 Description: fieldTag.Get("description"), 167 Default: fieldTag.Get("default"), 168 }) 169 } 170 171 if fieldTag.Get("environmentName") != "" { 172 cmd.Environment = append(cmd.Environment, EnvironmentVariable{ 173 Name: fieldTag.Get("environmentName"), 174 DefaultValue: fieldTag.Get("environmentDefault"), 175 Description: fieldTag.Get("environmentDescription"), 176 }) 177 } 178 } 179 180 return cmd, nil 181 } 182 183 // CommandInfos returns a slice of CommandInfo that only fills in 184 // the Name and Description for all the commands in commandList 185 func (Actor) CommandInfos(commandList interface{}) map[string]CommandInfo { 186 handler := reflect.TypeOf(commandList) 187 188 infos := make(map[string]CommandInfo, handler.NumField()) 189 for i := 0; i < handler.NumField(); i++ { 190 fieldTag := handler.Field(i).Tag 191 commandName := fieldTag.Get("command") 192 infos[commandName] = CommandInfo{ 193 Name: commandName, 194 Description: fieldTag.Get("description"), 195 Alias: fieldTag.Get("alias"), 196 } 197 } 198 199 return infos 200 }