github.com/furusax0621/goa-v1@v1.4.3/goagen/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/goadesign/goa/goagen/codegen" 12 "github.com/goadesign/goa/goagen/meta" 13 "github.com/goadesign/goa/goagen/utils" 14 "github.com/goadesign/goa/version" 15 "github.com/spf13/cobra" 16 "github.com/spf13/pflag" 17 18 // These are packages required by the generated code but not by goagen. 19 // We list them here so that `go get` picks them up. 20 _ "gopkg.in/yaml.v2" 21 ) 22 23 func main() { 24 var ( 25 files []string 26 err error 27 terminatedByUser bool 28 ) 29 30 // rootCmd is the base command used when goagen is called with no argument. 31 rootCmd := &cobra.Command{ 32 Use: "goagen", 33 Short: "goa code generation tool", 34 Long: `The goagen tool generates artifacts from a goa service design package. 35 36 Each command supported by the tool produces a specific type of artifacts. For example 37 the "app" command generates the code that supports the service controllers. 38 39 The "bootstrap" command runs the "app", "main", "client" and "swagger" commands generating the 40 controllers supporting code and main skeleton code (if not already present) as well as a client 41 package and tool and the Swagger specification for the API. 42 `} 43 var ( 44 designPkg string 45 debug bool 46 ) 47 48 rootCmd.PersistentFlags().StringP("out", "o", ".", "output directory") 49 rootCmd.PersistentFlags().StringVarP(&designPkg, "design", "d", "", "design package import path") 50 rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug mode, does not cleanup temporary files.") 51 52 // versionCmd implements the "version" command 53 versionCmd := &cobra.Command{ 54 Use: "version", 55 Short: "Print the version number of goagen", 56 Run: func(cmd *cobra.Command, args []string) { 57 fmt.Println("goagen " + version.String() + "\nThe goa generation tool.") 58 }, 59 } 60 rootCmd.AddCommand(versionCmd) 61 62 // appCmd implements the "app" command. 63 var ( 64 pkg string 65 notest bool 66 ) 67 appCmd := &cobra.Command{ 68 Use: "app", 69 Short: "Generate application code", 70 Run: func(c *cobra.Command, _ []string) { files, err = run("genapp", c) }, 71 } 72 appCmd.Flags().StringVar(&pkg, "pkg", "app", "Name of generated Go package containing controllers supporting code (contexts, media types, user types etc.)") 73 appCmd.Flags().BoolVar(¬est, "notest", false, "Prevent generation of test helpers") 74 rootCmd.AddCommand(appCmd) 75 76 // mainCmd implements the "main" command. 77 var ( 78 force, regen bool 79 ) 80 mainCmd := &cobra.Command{ 81 Use: "main", 82 Short: "Generate application scaffolding", 83 Run: func(c *cobra.Command, _ []string) { files, err = run("genmain", c) }, 84 } 85 mainCmd.Flags().BoolVar(&force, "force", false, "overwrite existing files") 86 mainCmd.Flags().BoolVar(®en, "regen", false, "regenerate scaffolding, maintaining controller implementations") 87 rootCmd.AddCommand(mainCmd) 88 89 // clientCmd implements the "client" command. 90 var ( 91 toolDir, tool string 92 notool bool 93 ) 94 clientCmd := &cobra.Command{ 95 Use: "client", 96 Short: "Generate client package and tool", 97 Run: func(c *cobra.Command, _ []string) { files, err = run("genclient", c) }, 98 } 99 clientCmd.Flags().StringVar(&pkg, "pkg", "client", "Name of generated client Go package") 100 clientCmd.Flags().StringVar(&toolDir, "tooldir", "tool", "Name of generated tool directory") 101 clientCmd.Flags().StringVar(&tool, "tool", "[API-name]-cli", "Name of generated tool") 102 clientCmd.Flags().BoolVar(¬ool, "notool", false, "Prevent generation of cli tool") 103 rootCmd.AddCommand(clientCmd) 104 105 // swaggerCmd implements the "swagger" command. 106 swaggerCmd := &cobra.Command{ 107 Use: "swagger", 108 Short: "Generate Swagger", 109 Run: func(c *cobra.Command, _ []string) { files, err = run("genswagger", c) }, 110 } 111 rootCmd.AddCommand(swaggerCmd) 112 113 // jsCmd implements the "js" command. 114 var ( 115 timeout = time.Duration(20) * time.Second 116 scheme, host string 117 noexample bool 118 ) 119 jsCmd := &cobra.Command{ 120 Use: "js", 121 Short: "Generate JavaScript client", 122 Run: func(c *cobra.Command, _ []string) { files, err = run("genjs", c) }, 123 } 124 jsCmd.Flags().DurationVar(&timeout, "timeout", timeout, `the duration before the request times out.`) 125 jsCmd.Flags().StringVar(&scheme, "scheme", "", `the URL scheme used to make requests to the API, defaults to the scheme defined in the API design if any.`) 126 jsCmd.Flags().StringVar(&host, "host", "", `the API hostname, defaults to the hostname defined in the API design if any`) 127 jsCmd.Flags().BoolVar(&noexample, "noexample", false, `Skip generation of example HTML and controller`) 128 rootCmd.AddCommand(jsCmd) 129 130 // schemaCmd implements the "schema" command. 131 schemaCmd := &cobra.Command{ 132 Use: "schema", 133 Short: "Generate JSON Schema", 134 Run: func(c *cobra.Command, _ []string) { files, err = run("genschema", c) }, 135 } 136 rootCmd.AddCommand(schemaCmd) 137 138 // genCmd implements the "gen" command. 139 var ( 140 pkgPath string 141 ) 142 genCmd := &cobra.Command{ 143 Use: "gen", 144 Short: "Run third-party generator", 145 Run: func(c *cobra.Command, args []string) { files, err = runGen(c, args) }, 146 } 147 genCmd.Flags().StringVar(&pkgPath, "pkg-path", "", "Package import path of generator. The package must implement the Generate global function.") 148 // stop parsing arguments after -- to prevent an unknown flag error 149 // this also means custom arguments (after --) should be the last arguments 150 genCmd.Flags().SetInterspersed(false) 151 rootCmd.AddCommand(genCmd) 152 153 // boostrapCmd implements the "bootstrap" command. 154 bootCmd := &cobra.Command{ 155 Use: "bootstrap", 156 Short: `Equivalent to running the "app", "main", "client" and "swagger" commands.`, 157 Run: func(c *cobra.Command, a []string) { 158 appCmd.Run(c, a) 159 if err != nil { 160 return 161 } 162 prev := files 163 164 mainCmd.Run(c, a) 165 if err != nil { 166 return 167 } 168 prev = append(prev, files...) 169 170 clientCmd.Run(c, a) 171 if err != nil { 172 return 173 } 174 prev = append(prev, files...) 175 176 swaggerCmd.Run(c, a) 177 files = append(prev, files...) 178 }, 179 } 180 bootCmd.Flags().AddFlagSet(appCmd.Flags()) 181 bootCmd.Flags().AddFlagSet(mainCmd.Flags()) 182 bootCmd.Flags().AddFlagSet(clientCmd.Flags()) 183 bootCmd.Flags().AddFlagSet(swaggerCmd.Flags()) 184 rootCmd.AddCommand(bootCmd) 185 186 // controllerCmd implements the "controller" command. 187 var ( 188 res, appPkg string 189 ) 190 controllerCmd := &cobra.Command{ 191 Use: "controller", 192 Short: "Generate controller scaffolding", 193 Run: func(c *cobra.Command, _ []string) { files, err = run("gencontroller", c) }, 194 } 195 controllerCmd.Flags().BoolVar(&force, "force", false, "overwrite existing files") 196 controllerCmd.Flags().BoolVar(®en, "regen", false, "regenerate scaffolding, maintaining controller implementations") 197 controllerCmd.Flags().StringVar(&res, "res", "", "name of the `resource` to generate the controller for, generate all if not specified") 198 controllerCmd.Flags().StringVar(&pkg, "pkg", "main", "name of the generated controller `package`") 199 controllerCmd.Flags().StringVar(&appPkg, "app-pkg", "app", "`import path` of Go package generated with 'goagen app', may be relative to output") 200 rootCmd.AddCommand(controllerCmd) 201 202 // cmdsCmd implements the commands command 203 // It lists all the commands and flags in JSON to enable shell integrations. 204 cmdsCmd := &cobra.Command{ 205 Use: "commands", 206 Short: "Lists all commands and flags in JSON", 207 Run: func(c *cobra.Command, _ []string) { runCommands(rootCmd) }, 208 } 209 rootCmd.AddCommand(cmdsCmd) 210 211 // Now proceed with code generation 212 cleanup := func() { 213 for _, f := range files { 214 os.RemoveAll(f) 215 } 216 } 217 218 go utils.Catch(nil, func() { 219 terminatedByUser = true 220 }) 221 222 rootCmd.Execute() 223 224 if terminatedByUser { 225 cleanup() 226 return 227 } 228 229 if err != nil { 230 cleanup() 231 fmt.Fprintln(os.Stderr, err.Error()) 232 os.Exit(1) 233 } 234 235 rels := make([]string, len(files)) 236 cd, _ := os.Getwd() 237 for i, f := range files { 238 r, err := filepath.Rel(cd, f) 239 if err == nil { 240 rels[i] = r 241 } else { 242 rels[i] = f 243 } 244 } 245 fmt.Println(strings.Join(rels, "\n")) 246 } 247 248 func run(pkg string, c *cobra.Command) ([]string, error) { 249 pkgPath := fmt.Sprintf("github.com/goadesign/goa/goagen/gen_%s", pkg[3:]) 250 pkgSrcPath, err := codegen.PackageSourcePath(pkgPath) 251 if err != nil { 252 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 253 } 254 pkgName, err := codegen.PackageName(pkgSrcPath) 255 if err != nil { 256 return nil, fmt.Errorf("invalid package import path: %s", err) 257 } 258 return generate(pkgName, pkgPath, c, nil) 259 } 260 261 func runGen(c *cobra.Command, args []string) ([]string, error) { 262 pkgPath := c.Flag("pkg-path").Value.String() 263 pkgSrcPath, err := codegen.PackageSourcePath(pkgPath) 264 if err != nil { 265 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 266 } 267 pkgName, err := codegen.PackageName(pkgSrcPath) 268 if err != nil { 269 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 270 } 271 return generate(pkgName, pkgPath, c, args) 272 } 273 274 func generate(pkgName, pkgPath string, c *cobra.Command, args []string) ([]string, error) { 275 m := make(map[string]string) 276 c.Flags().Visit(func(f *pflag.Flag) { 277 if f.Name != "pkg-path" { 278 m[f.Name] = f.Value.String() 279 } 280 }) 281 if _, ok := m["out"]; !ok { 282 m["out"] = c.Flag("out").DefValue 283 } 284 // turn "out" into an absolute path 285 var err error 286 m["out"], err = filepath.Abs(m["out"]) 287 if err != nil { 288 return nil, err 289 } 290 291 gen, err := meta.NewGenerator( 292 pkgName+".Generate", 293 []*codegen.ImportSpec{codegen.SimpleImport(pkgPath)}, 294 m, 295 args, 296 ) 297 if err != nil { 298 return nil, err 299 } 300 return gen.Generate() 301 } 302 303 type ( 304 rootCommand struct { 305 Name string `json:"name"` 306 Commands []*command `json:"commands"` 307 Flags []*flag `json:"flags"` 308 } 309 310 flag struct { 311 Long string `json:"long,omitempty"` 312 Short string `json:"short,omitempty"` 313 Description string `json:"description,omitempty"` 314 Argument string `json:"argument,omitempty"` 315 Required bool `json:"required"` 316 } 317 318 command struct { 319 Name string `json:"name"` 320 Flags []*flag `json:"flags,omitempty"` 321 } 322 ) 323 324 func runCommands(root *cobra.Command) { 325 var ( 326 gblFlags []*flag 327 cmds []*command 328 ) 329 root.Flags().VisitAll(func(fl *pflag.Flag) { 330 gblFlags = append(gblFlags, flagJSON(fl)) 331 }) 332 cmds = make([]*command, len(root.Commands())-2) 333 j := 0 334 for _, cm := range root.Commands() { 335 if cm.Name() == "help" || cm.Name() == "commands" { 336 continue 337 } 338 cmds[j] = cmdJSON(cm, gblFlags) 339 j++ 340 } 341 rc := rootCommand{os.Args[0], cmds, gblFlags} 342 b, _ := json.MarshalIndent(rc, "", " ") 343 fmt.Println(string(b)) 344 } 345 346 // Lots of assumptions in here, it's OK for what we are doing 347 // Remember to update as goagen commands and flags evolve 348 // 349 // The flag argument values use variable names that cary semantic: 350 // $DIR for file system directories, $DESIGN_PKG for import path to Go goa design Go packages, $PKG 351 // for import path to any Go package. 352 func flagJSON(fl *pflag.Flag) *flag { 353 f := &flag{Long: fl.Name, Short: fl.Shorthand, Description: fl.Usage} 354 f.Required = fl.Name == "pkg-path" || fl.Name == "design" 355 switch fl.Name { 356 case "out": 357 f.Argument = "$DIR" 358 case "design": 359 f.Argument = "$DESIGN_PKG" 360 case "pkg-path": 361 f.Argument = "$PKG" 362 } 363 return f 364 } 365 366 func cmdJSON(cm *cobra.Command, flags []*flag) *command { 367 res := make([]*flag, len(flags)) 368 for i, fl := range flags { 369 f := *fl 370 res[i] = &f 371 } 372 cm.Flags().VisitAll(func(fl *pflag.Flag) { 373 res = append(res, flagJSON(fl)) 374 }) 375 return &command{cm.Name(), res} 376 }