github.com/symfony-cli/symfony-cli@v0.0.0-20240514161054-ece2df437dfa/local/platformsh/generator/commands.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "os/exec" 9 "sort" 10 "strings" 11 "text/template" 12 13 "github.com/mitchellh/go-homedir" 14 "github.com/pkg/errors" 15 "github.com/symfony-cli/console" 16 "github.com/symfony-cli/symfony-cli/local/platformsh" 17 ) 18 19 type application struct { 20 Namespaces []namespace 21 Commands []command 22 } 23 24 type namespace struct { 25 ID string 26 Commands []string 27 } 28 29 type command struct { 30 Name string 31 Usage []string 32 Description string 33 Help string 34 Definition definition 35 Hidden bool 36 Aliases []string 37 } 38 39 type definition struct { 40 Arguments map[string]argument 41 Options map[string]option 42 } 43 44 type argument struct { 45 } 46 47 type option struct { 48 Shortcut string 49 Default interface{} 50 } 51 52 var commandsTemplate = template.Must(template.New("output").Parse(`// Code generated by platformsh/generator/main.go 53 // DO NOT EDIT 54 55 /* 56 * Copyright (c) 2021-present Fabien Potencier <fabien@symfony.com> 57 * 58 * This file is part of Symfony CLI project 59 * 60 * This program is free software: you can redistribute it and/or modify 61 * it under the terms of the GNU Affero General Public License as 62 * published by the Free Software Foundation, either version 3 of the 63 * License, or (at your option) any later version. 64 * 65 * This program is distributed in the hope that it will be useful, 66 * but WITHOUT ANY WARRANTY; without even the implied warranty of 67 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 68 * GNU Affero General Public License for more details. 69 * 70 * You should have received a copy of the GNU Affero General Public License 71 * along with this program. If not, see <http://www.gnu.org/licenses/>. 72 */ 73 74 package platformsh 75 76 import ( 77 "github.com/symfony-cli/console" 78 ) 79 80 var Commands = []*console.Command{ 81 {{ .Definition -}} 82 } 83 `)) 84 85 func generateCommands() { 86 home, err := homedir.Dir() 87 if err != nil { 88 panic(err) 89 } 90 // as platform.sh and upsun have the same commands, we can use either one 91 cloudPath, err := platformsh.Install(home, platformsh.PlatformshBrand) 92 if err != nil { 93 panic(err.Error()) 94 } 95 definitionAsString, err := parseCommands(cloudPath) 96 if err != nil { 97 panic(err.Error()) 98 } 99 data := map[string]interface{}{ 100 "Definition": definitionAsString, 101 } 102 var buf bytes.Buffer 103 if err := commandsTemplate.Execute(&buf, data); err != nil { 104 panic(err) 105 } 106 f, err := os.Create("local/platformsh/commands.go") 107 if err != nil { 108 panic(err) 109 } 110 f.Write(buf.Bytes()) 111 112 } 113 114 func parseCommands(cloudPath string) (string, error) { 115 var buf bytes.Buffer 116 var bufErr bytes.Buffer 117 cmd := exec.Command(cloudPath, "list", "--format=json", "--all") 118 cmd.Stdout = &buf 119 cmd.Stderr = &bufErr 120 if err := cmd.Run(); err != nil { 121 return "", errors.Errorf("unable to list commands: %s\n%s\n%s", err, bufErr.String(), buf.String()) 122 } 123 124 // Fix PHP types 125 cleanOutput := bytes.ReplaceAll(buf.Bytes(), []byte(`"arguments":[]`), []byte(`"arguments":{}`)) 126 127 var definition application 128 if err := json.Unmarshal(cleanOutput, &definition); err != nil { 129 return "", err 130 } 131 132 excludedCommands := map[string]bool{ 133 "list": true, 134 "help": true, 135 "self:stats": true, 136 "decode": true, 137 "environment:drush": true, 138 "project:init": true, 139 } 140 141 excludedOptions := console.AnsiFlag.Names() 142 excludedOptions = append(excludedOptions, console.NoAnsiFlag.Names()...) 143 excludedOptions = append(excludedOptions, console.NoInteractionFlag.Names()...) 144 excludedOptions = append(excludedOptions, console.QuietFlag.Names()...) 145 excludedOptions = append(excludedOptions, console.LogLevelFlag.Names()...) 146 excludedOptions = append(excludedOptions, console.HelpFlag.Names()...) 147 excludedOptions = append(excludedOptions, console.VersionFlag.Names()...) 148 149 definitionAsString := "" 150 for _, command := range definition.Commands { 151 if strings.Contains(command.Description, "deprecated") || strings.Contains(command.Description, "DEPRECATED") { 152 continue 153 } 154 if _, ok := excludedCommands[command.Name]; ok { 155 continue 156 } 157 if strings.HasPrefix(command.Name, "local:") { 158 continue 159 } 160 if strings.HasPrefix(command.Name, "self:") { 161 command.Hidden = true 162 } 163 namespace := "cloud" 164 loop: 165 for _, n := range definition.Namespaces { 166 for _, name := range n.Commands { 167 if name == command.Name { 168 if n.ID != "_global" { 169 namespace += ":" + n.ID 170 } 171 break loop 172 } 173 } 174 } 175 name := strings.TrimPrefix("cloud:"+command.Name, namespace+":") 176 aliases := []string{} 177 if namespace != "cloud" && !strings.HasPrefix(command.Name, "self:") { 178 aliases = append(aliases, fmt.Sprintf("{Name: \"%s\", Hidden: true}", command.Name)) 179 } 180 181 cmdAliases, err := getCommandAliases(command.Name, cloudPath) 182 if err != nil { 183 return "", err 184 } 185 aliases = append(aliases, fmt.Sprintf("{Name: \"upsun:%s\", Hidden: true}", command.Name)) 186 for _, alias := range cmdAliases { 187 aliases = append(aliases, fmt.Sprintf("{Name: \"cloud:%s\"}", alias)) 188 aliases = append(aliases, fmt.Sprintf("{Name: \"upsun:%s\", Hidden: true}", alias)) 189 if namespace != "cloud" && !strings.HasPrefix(command.Name, "self:") { 190 aliases = append(aliases, fmt.Sprintf("{Name: \"%s\", Hidden: true}", alias)) 191 } 192 } 193 if command.Name == "environment:push" { 194 aliases = append(aliases, "{Name: \"deploy\"}") 195 aliases = append(aliases, "{Name: \"cloud:deploy\"}") 196 aliases = append(aliases, "{Name: \"upsun:deploy\", Hidden: true}") 197 } 198 aliasesAsString := "" 199 if len(aliases) > 0 { 200 aliasesAsString += "\n\t\tAliases: []*console.Alias{\n" 201 for _, alias := range aliases { 202 aliasesAsString += "\t\t\t" + alias + ",\n" 203 } 204 aliasesAsString += "\t\t}," 205 } 206 hide := "" 207 if command.Hidden { 208 hide = "\n\t\tHidden: console.Hide," 209 } 210 211 optionNames := make([]string, 0, len(command.Definition.Options)) 212 213 optionsLoop: 214 for name := range command.Definition.Options { 215 if name == "yes" || name == "no" || name == "version" { 216 continue 217 } 218 for _, excludedOption := range excludedOptions { 219 if excludedOption == name { 220 continue optionsLoop 221 } 222 } 223 224 optionNames = append(optionNames, name) 225 } 226 sort.Strings(optionNames) 227 flags := []string{} 228 for _, name := range optionNames { 229 option := command.Definition.Options[name] 230 optionAliasesAsString := "" 231 if option.Shortcut != "" { 232 optionAliasesAsString += " Aliases: []string{\"" 233 optionAliasesAsString += strings.Join(strings.Split(strings.ReplaceAll(option.Shortcut, "-", ""), "|"), "\", \"") 234 optionAliasesAsString += "\"}," 235 } 236 flagType := "String" 237 defaultValue := "" 238 if value, ok := option.Default.(bool); ok { 239 flagType = "Bool" 240 if value { 241 defaultValue = "true" 242 } 243 } else if value, ok := option.Default.(string); ok { 244 defaultValue = fmt.Sprintf("%#v", value) 245 } 246 defaultValueAsString := "" 247 if defaultValue != "" { 248 defaultValueAsString = fmt.Sprintf(" DefaultValue: %s,", defaultValue) 249 } 250 flags = append(flags, fmt.Sprintf(`&console.%sFlag{Name: "%s",%s%s}`, flagType, name, optionAliasesAsString, defaultValueAsString)) 251 } 252 flagsAsString := "" 253 if len(flags) > 0 { 254 flagsAsString += "\n\t\tFlags: []console.Flag{\n" 255 for _, flag := range flags { 256 flagsAsString += "\t\t\t" + flag + ",\n" 257 } 258 flagsAsString += "\t\t}," 259 } 260 261 command.Description = strings.ReplaceAll(command.Description, "Platform.sh", "Platform.sh/Upsun") 262 definitionAsString += fmt.Sprintf(` { 263 Category: "%s", 264 Name: "%s",%s 265 Usage: %#v,%s%s 266 }, 267 `, namespace, name, aliasesAsString, command.Description, hide, flagsAsString) 268 } 269 270 return definitionAsString, nil 271 } 272 273 func getCommandAliases(name, cloudPath string) ([]string, error) { 274 var buf bytes.Buffer 275 var bufErr bytes.Buffer 276 c := exec.Command(cloudPath, name, "--help", "--format=json") 277 c.Stdout = &buf 278 c.Stderr = &bufErr 279 if err := c.Run(); err != nil { 280 // Can currently happen for commands implemented in Go upstream (like app:config-validate) 281 // FIXME: to be removed once upstream implements --help --format=json for all commands 282 return []string{}, nil 283 //return nil, errors.Errorf("unable to get definition for command %s: %s\n%s\n%s", name, err, bufErr.String(), buf.String()) 284 } 285 var cmd command 286 if err := json.Unmarshal(buf.Bytes(), &cmd); err != nil { 287 return nil, err 288 } 289 return cmd.Aliases, nil 290 }