github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/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 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 rootCmd.AddCommand(mainCmd) 87 88 // clientCmd implements the "client" command. 89 var ( 90 toolDir, tool string 91 notool bool 92 ) 93 clientCmd := &cobra.Command{ 94 Use: "client", 95 Short: "Generate client package and tool", 96 Run: func(c *cobra.Command, _ []string) { files, err = run("genclient", c) }, 97 } 98 clientCmd.Flags().StringVar(&pkg, "pkg", "client", "Name of generated client Go package") 99 clientCmd.Flags().StringVar(&toolDir, "tooldir", "tool", "Name of generated tool directory") 100 clientCmd.Flags().StringVar(&tool, "tool", "[API-name]-cli", "Name of generated tool") 101 clientCmd.Flags().BoolVar(¬ool, "notool", false, "Prevent generation of cli tool") 102 rootCmd.AddCommand(clientCmd) 103 104 // swaggerCmd implements the "swagger" command. 105 swaggerCmd := &cobra.Command{ 106 Use: "swagger", 107 Short: "Generate Swagger", 108 Run: func(c *cobra.Command, _ []string) { files, err = run("genswagger", c) }, 109 } 110 rootCmd.AddCommand(swaggerCmd) 111 112 // jsCmd implements the "js" command. 113 var ( 114 timeout = time.Duration(20) * time.Second 115 scheme, host string 116 noexample bool 117 ) 118 jsCmd := &cobra.Command{ 119 Use: "js", 120 Short: "Generate JavaScript client", 121 Run: func(c *cobra.Command, _ []string) { files, err = run("genjs", c) }, 122 } 123 jsCmd.Flags().DurationVar(&timeout, "timeout", timeout, `the duration before the request times out.`) 124 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.`) 125 jsCmd.Flags().StringVar(&host, "host", "", `the API hostname, defaults to the hostname defined in the API design if any`) 126 jsCmd.Flags().BoolVar(&noexample, "noexample", false, `Skip generation of example HTML and controller`) 127 rootCmd.AddCommand(jsCmd) 128 129 // schemaCmd implements the "schema" command. 130 schemaCmd := &cobra.Command{ 131 Use: "schema", 132 Short: "Generate JSON Schema", 133 Run: func(c *cobra.Command, _ []string) { files, err = run("genschema", c) }, 134 } 135 rootCmd.AddCommand(schemaCmd) 136 137 // genCmd implements the "gen" command. 138 var ( 139 pkgPath string 140 ) 141 genCmd := &cobra.Command{ 142 Use: "gen", 143 Short: "Run third-party generator", 144 Run: func(c *cobra.Command, args []string) { files, err = runGen(c, args) }, 145 } 146 genCmd.Flags().StringVar(&pkgPath, "pkg-path", "", "Package import path of generator. The package must implement the Generate global function.") 147 // stop parsing arguments after -- to prevent an unknown flag error 148 // this also means custom arguments (after --) should be the last arguments 149 genCmd.Flags().SetInterspersed(false) 150 rootCmd.AddCommand(genCmd) 151 152 // boostrapCmd implements the "bootstrap" command. 153 bootCmd := &cobra.Command{ 154 Use: "bootstrap", 155 Short: `Equivalent to running the "app", "main", "client" and "swagger" commands.`, 156 Run: func(c *cobra.Command, a []string) { 157 appCmd.Run(c, a) 158 if err != nil { 159 return 160 } 161 prev := files 162 163 mainCmd.Run(c, a) 164 if err != nil { 165 return 166 } 167 prev = append(prev, files...) 168 169 clientCmd.Run(c, a) 170 if err != nil { 171 return 172 } 173 prev = append(prev, files...) 174 175 swaggerCmd.Run(c, a) 176 files = append(prev, files...) 177 }, 178 } 179 bootCmd.Flags().AddFlagSet(appCmd.Flags()) 180 bootCmd.Flags().AddFlagSet(mainCmd.Flags()) 181 bootCmd.Flags().AddFlagSet(clientCmd.Flags()) 182 bootCmd.Flags().AddFlagSet(swaggerCmd.Flags()) 183 rootCmd.AddCommand(bootCmd) 184 185 // controllerCmd implements the "controller" command. 186 var ( 187 res, appPkg string 188 ) 189 controllerCmd := &cobra.Command{ 190 Use: "controller", 191 Short: "Generate controller scaffolding", 192 Run: func(c *cobra.Command, _ []string) { files, err = run("gencontroller", c) }, 193 } 194 controllerCmd.Flags().BoolVar(&force, "force", false, "overwrite existing files") 195 controllerCmd.Flags().StringVar(&res, "res", "", "name of the `resource` to generate the controller for, generate all if not specified") 196 controllerCmd.Flags().StringVar(&pkg, "pkg", "controller", "name of the generated controller `package`") 197 controllerCmd.Flags().StringVar(&appPkg, "app-pkg", "app", "`import path` of Go package generated with 'goagen app', may be relative to output") 198 rootCmd.AddCommand(controllerCmd) 199 200 // cmdsCmd implements the commands command 201 // It lists all the commands and flags in JSON to enable shell integrations. 202 cmdsCmd := &cobra.Command{ 203 Use: "commands", 204 Short: "Lists all commands and flags in JSON", 205 Run: func(c *cobra.Command, _ []string) { runCommands(rootCmd) }, 206 } 207 rootCmd.AddCommand(cmdsCmd) 208 209 // Now proceed with code generation 210 cleanup := func() { 211 for _, f := range files { 212 os.RemoveAll(f) 213 } 214 } 215 216 go utils.Catch(nil, func() { 217 terminatedByUser = true 218 }) 219 220 rootCmd.Execute() 221 222 if terminatedByUser { 223 cleanup() 224 return 225 } 226 227 if err != nil { 228 cleanup() 229 fmt.Fprintln(os.Stderr, err.Error()) 230 os.Exit(1) 231 } 232 233 rels := make([]string, len(files)) 234 cd, _ := os.Getwd() 235 for i, f := range files { 236 r, err := filepath.Rel(cd, f) 237 if err == nil { 238 rels[i] = r 239 } else { 240 rels[i] = f 241 } 242 } 243 fmt.Println(strings.Join(rels, "\n")) 244 } 245 246 func run(pkg string, c *cobra.Command) ([]string, error) { 247 pkgPath := fmt.Sprintf("github.com/goadesign/goa/goagen/gen_%s", pkg[3:]) 248 pkgSrcPath, err := codegen.PackageSourcePath(pkgPath) 249 if err != nil { 250 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 251 } 252 pkgName, err := codegen.PackageName(pkgSrcPath) 253 if err != nil { 254 return nil, fmt.Errorf("invalid package import path: %s", err) 255 } 256 return generate(pkgName, pkgPath, c, nil) 257 } 258 259 func runGen(c *cobra.Command, args []string) ([]string, error) { 260 pkgPath := c.Flag("pkg-path").Value.String() 261 pkgSrcPath, err := codegen.PackageSourcePath(pkgPath) 262 if err != nil { 263 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 264 } 265 pkgName, err := codegen.PackageName(pkgSrcPath) 266 if err != nil { 267 return nil, fmt.Errorf("invalid plugin package import path: %s", err) 268 } 269 return generate(pkgName, pkgPath, c, args) 270 } 271 272 func generate(pkgName, pkgPath string, c *cobra.Command, args []string) ([]string, error) { 273 m := make(map[string]string) 274 c.Flags().Visit(func(f *pflag.Flag) { 275 if f.Name != "pkg-path" { 276 m[f.Name] = f.Value.String() 277 } 278 }) 279 if _, ok := m["out"]; !ok { 280 m["out"] = c.Flag("out").DefValue 281 } 282 // turn "out" into an absolute path 283 var err error 284 m["out"], err = filepath.Abs(m["out"]) 285 if err != nil { 286 return nil, err 287 } 288 289 gen, err := meta.NewGenerator( 290 pkgName+".Generate", 291 []*codegen.ImportSpec{codegen.SimpleImport(pkgPath)}, 292 m, 293 args, 294 ) 295 if err != nil { 296 return nil, err 297 } 298 return gen.Generate() 299 } 300 301 type ( 302 rootCommand struct { 303 Name string `json:"name"` 304 Commands []*command `json:"commands"` 305 Flags []*flag `json:"flags"` 306 } 307 308 flag struct { 309 Long string `json:"long,omitempty"` 310 Short string `json:"short,omitempty"` 311 Description string `json:"description,omitempty"` 312 Argument string `json:"argument,omitempty"` 313 Required bool `json:"required"` 314 } 315 316 command struct { 317 Name string `json:"name"` 318 Flags []*flag `json:"flags,omitempty"` 319 } 320 ) 321 322 func runCommands(root *cobra.Command) { 323 var ( 324 gblFlags []*flag 325 cmds []*command 326 ) 327 root.Flags().VisitAll(func(fl *pflag.Flag) { 328 gblFlags = append(gblFlags, flagJSON(fl)) 329 }) 330 cmds = make([]*command, len(root.Commands())-2) 331 j := 0 332 for _, cm := range root.Commands() { 333 if cm.Name() == "help" || cm.Name() == "commands" { 334 continue 335 } 336 cmds[j] = cmdJSON(cm, gblFlags) 337 j++ 338 } 339 rc := rootCommand{os.Args[0], cmds, gblFlags} 340 b, _ := json.MarshalIndent(rc, "", " ") 341 fmt.Println(string(b)) 342 } 343 344 // Lots of assumptions in here, it's OK for what we are doing 345 // Remember to update as goagen commands and flags evolve 346 // 347 // The flag argument values use variable names that cary semantic: 348 // $DIR for file system directories, $DESIGN_PKG for import path to Go goa design Go packages, $PKG 349 // for import path to any Go package. 350 func flagJSON(fl *pflag.Flag) *flag { 351 f := &flag{Long: fl.Name, Short: fl.Shorthand, Description: fl.Usage} 352 f.Required = fl.Name == "pkg-path" || fl.Name == "design" 353 switch fl.Name { 354 case "out": 355 f.Argument = "$DIR" 356 case "design": 357 f.Argument = "$DESIGN_PKG" 358 case "pkg-path": 359 f.Argument = "$PKG" 360 } 361 return f 362 } 363 364 func cmdJSON(cm *cobra.Command, flags []*flag) *command { 365 res := make([]*flag, len(flags)) 366 for i, fl := range flags { 367 f := *fl 368 res[i] = &f 369 } 370 cm.Flags().VisitAll(func(fl *pflag.Flag) { 371 res = append(res, flagJSON(fl)) 372 }) 373 return &command{cm.Name(), res} 374 }