github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/goagen/gen_main/generator.go (about) 1 package genmain 2 3 import ( 4 "flag" 5 "fmt" 6 "net" 7 "os" 8 "path" 9 "path/filepath" 10 "strings" 11 "text/template" 12 13 "github.com/goadesign/goa/design" 14 "github.com/goadesign/goa/goagen/codegen" 15 "github.com/goadesign/goa/goagen/utils" 16 ) 17 18 //NewGenerator returns an initialized instance of a JavaScript Client Generator 19 func NewGenerator(options ...Option) *Generator { 20 g := &Generator{} 21 22 for _, option := range options { 23 option(g) 24 } 25 26 return g 27 } 28 29 // Generator is the application code generator. 30 type Generator struct { 31 API *design.APIDefinition // The API definition 32 OutDir string // Path to output directory 33 DesignPkg string // Path to design package, only used to mark generated files. 34 Target string // Name of generated "app" package 35 Force bool // Whether to override existing files 36 genfiles []string // Generated files 37 } 38 39 // Generate is the generator entry point called by the meta generator. 40 func Generate() (files []string, err error) { 41 var ( 42 outDir, designPkg, target, ver string 43 force bool 44 ) 45 46 set := flag.NewFlagSet("main", flag.PanicOnError) 47 set.StringVar(&outDir, "out", "", "") 48 set.StringVar(&designPkg, "design", "", "") 49 set.StringVar(&target, "pkg", "app", "") 50 set.StringVar(&ver, "version", "", "") 51 set.BoolVar(&force, "force", false, "") 52 set.Bool("notest", false, "") 53 set.Parse(os.Args[1:]) 54 55 if err := codegen.CheckVersion(ver); err != nil { 56 return nil, err 57 } 58 59 target = codegen.Goify(target, false) 60 g := &Generator{OutDir: outDir, DesignPkg: designPkg, Target: target, Force: force, API: design.Design} 61 62 return g.Generate() 63 } 64 65 // GenerateController generates the controller corresponding to the given 66 // resource and returns the generated filename. 67 func GenerateController(force bool, appPkg, outDir, pkg, name string, r *design.ResourceDefinition) (string, error) { 68 filename := filepath.Join(outDir, codegen.SnakeCase(name)+".go") 69 if force { 70 os.Remove(filename) 71 } 72 if _, e := os.Stat(filename); e == nil { 73 return "", nil 74 } 75 if err := os.MkdirAll(outDir, 0755); err != nil { 76 return "", err 77 } 78 file, err := codegen.SourceFileFor(filename) 79 if err != nil { 80 return "", err 81 } 82 83 elems := strings.Split(appPkg, "/") 84 pkgName := elems[len(elems)-1] 85 var imp string 86 if _, err := codegen.PackageSourcePath(appPkg); err == nil { 87 imp = appPkg 88 } else { 89 imp, err = codegen.PackagePath(outDir) 90 if err != nil { 91 return "", err 92 } 93 imp = path.Join(filepath.ToSlash(imp), appPkg) 94 } 95 96 imports := []*codegen.ImportSpec{ 97 codegen.SimpleImport("io"), 98 codegen.SimpleImport("github.com/goadesign/goa"), 99 codegen.SimpleImport(imp), 100 codegen.SimpleImport("golang.org/x/net/websocket"), 101 } 102 103 file.WriteHeader("", pkg, imports) 104 if err = file.ExecuteTemplate("controller", ctrlT, funcMap(pkgName), r); err != nil { 105 return "", err 106 } 107 err = r.IterateActions(func(a *design.ActionDefinition) error { 108 if a.WebSocket() { 109 return file.ExecuteTemplate("actionWS", actionWST, funcMap(pkgName), a) 110 } 111 return file.ExecuteTemplate("action", actionT, funcMap(pkgName), a) 112 }) 113 if err != nil { 114 return "", err 115 } 116 if err = file.FormatCode(); err != nil { 117 return "", err 118 } 119 120 return filename, nil 121 } 122 123 // Generate produces the skeleton main. 124 func (g *Generator) Generate() (_ []string, err error) { 125 if g.API == nil { 126 return nil, fmt.Errorf("missing API definition, make sure design is properly initialized") 127 } 128 129 go utils.Catch(nil, func() { g.Cleanup() }) 130 131 defer func() { 132 if err != nil { 133 g.Cleanup() 134 } 135 }() 136 137 if g.Target == "" { 138 g.Target = "app" 139 } 140 141 codegen.Reserved[g.Target] = true 142 143 mainFile := filepath.Join(g.OutDir, "main.go") 144 if g.Force { 145 os.Remove(mainFile) 146 } 147 _, err = os.Stat(mainFile) 148 if err != nil { 149 // ensure that the output directory exists before creating a new main 150 if err := os.MkdirAll(g.OutDir, 0755); err != nil { 151 return nil, err 152 } 153 if err = g.createMainFile(mainFile, funcMap(g.Target)); err != nil { 154 return nil, err 155 } 156 } 157 158 err = g.API.IterateResources(func(r *design.ResourceDefinition) error { 159 filename, err := GenerateController(g.Force, g.Target, g.OutDir, "main", r.Name, r) 160 if err != nil { 161 return err 162 } 163 164 g.genfiles = append(g.genfiles, filename) 165 return nil 166 }) 167 if err != nil { 168 return 169 } 170 171 return g.genfiles, nil 172 } 173 174 // Cleanup removes all the files generated by this generator during the last invokation of Generate. 175 func (g *Generator) Cleanup() { 176 for _, f := range g.genfiles { 177 os.Remove(f) 178 } 179 g.genfiles = nil 180 } 181 182 func (g *Generator) createMainFile(mainFile string, funcs template.FuncMap) error { 183 g.genfiles = append(g.genfiles, mainFile) 184 file, err := codegen.SourceFileFor(mainFile) 185 if err != nil { 186 return err 187 } 188 funcs["getPort"] = func(hostport string) string { 189 _, port, err := net.SplitHostPort(hostport) 190 if err != nil { 191 return "8080" 192 } 193 return port 194 } 195 outPkg, err := codegen.PackagePath(g.OutDir) 196 if err != nil { 197 return err 198 } 199 appPkg := path.Join(outPkg, "app") 200 imports := []*codegen.ImportSpec{ 201 codegen.SimpleImport("time"), 202 codegen.SimpleImport("github.com/goadesign/goa"), 203 codegen.SimpleImport("github.com/goadesign/goa/middleware"), 204 codegen.SimpleImport(appPkg), 205 } 206 file.Write([]byte("//go:generate goagen bootstrap -d " + g.DesignPkg + "\n\n")) 207 file.WriteHeader("", "main", imports) 208 data := map[string]interface{}{ 209 "Name": g.API.Name, 210 "API": g.API, 211 } 212 if err = file.ExecuteTemplate("main", mainT, funcs, data); err != nil { 213 return err 214 } 215 return file.FormatCode() 216 } 217 218 // tempCount is the counter used to create unique temporary variable names. 219 var tempCount int 220 221 // tempvar generates a unique temp var name. 222 func tempvar() string { 223 tempCount++ 224 if tempCount == 1 { 225 return "c" 226 } 227 return fmt.Sprintf("c%d", tempCount) 228 } 229 230 func okResp(a *design.ActionDefinition, appPkg string) map[string]interface{} { 231 var ok *design.ResponseDefinition 232 for _, resp := range a.Responses { 233 if resp.Status == 200 { 234 ok = resp 235 break 236 } 237 } 238 if ok == nil { 239 return nil 240 } 241 var mt *design.MediaTypeDefinition 242 var ok2 bool 243 if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 { 244 return nil 245 } 246 view := ok.ViewName 247 if view == "" { 248 view = design.DefaultView 249 } 250 pmt, _, err := mt.Project(view) 251 if err != nil { 252 return nil 253 } 254 var typeref string 255 if pmt.IsError() { 256 typeref = `goa.ErrInternal("not implemented")` 257 } else { 258 name := codegen.GoTypeRef(pmt, pmt.AllRequired(), 1, false) 259 var pointer string 260 if strings.HasPrefix(name, "*") { 261 name = name[1:] 262 pointer = "*" 263 } 264 typeref = fmt.Sprintf("%s%s.%s", pointer, appPkg, name) 265 if strings.HasPrefix(typeref, "*") { 266 typeref = "&" + typeref[1:] 267 } 268 typeref += "{}" 269 } 270 var nameSuffix string 271 if view != "default" { 272 nameSuffix = codegen.Goify(view, true) 273 } 274 return map[string]interface{}{ 275 "Name": ok.Name + nameSuffix, 276 "GoType": codegen.GoNativeType(pmt), 277 "TypeRef": typeref, 278 } 279 } 280 281 // funcMap creates the funcMap used to render the controller code. 282 func funcMap(appPkg string) template.FuncMap { 283 return template.FuncMap{ 284 "tempvar": tempvar, 285 "okResp": okResp, 286 "targetPkg": func() string { return appPkg }, 287 } 288 } 289 290 const ctrlT = `// {{ $ctrlName := printf "%s%s" (goify .Name true) "Controller" }}{{ $ctrlName }} implements the {{ .Name }} resource. 291 type {{ $ctrlName }} struct { 292 *goa.Controller 293 } 294 295 // New{{ $ctrlName }} creates a {{ .Name }} controller. 296 func New{{ $ctrlName }}(service *goa.Service) *{{ $ctrlName }} { 297 return &{{ $ctrlName }}{Controller: service.NewController("{{ $ctrlName }}")} 298 } 299 ` 300 301 const actionT = `{{ $ctrlName := printf "%s%s" (goify .Parent.Name true) "Controller" }}// {{ goify .Name true }} runs the {{ .Name }} action. 302 func (c *{{ $ctrlName }}) {{ goify .Name true }}(ctx *{{ targetPkg }}.{{ goify .Name true }}{{ goify .Parent.Name true }}Context) error { 303 // {{ $ctrlName }}_{{ goify .Name true }}: start_implement 304 305 // Put your logic here 306 307 // {{ $ctrlName }}_{{ goify .Name true }}: end_implement 308 {{ $ok := okResp . targetPkg }}{{ if $ok }} res := {{ $ok.TypeRef }} 309 {{ end }} return {{ if $ok }}ctx.{{ $ok.Name }}(res){{ else }}nil{{ end }} 310 } 311 ` 312 313 const actionWST = `{{ $ctrlName := printf "%s%s" (goify .Parent.Name true) "Controller" }}// {{ goify .Name true }} runs the {{ .Name }} action. 314 func (c *{{ $ctrlName }}) {{ goify .Name true }}(ctx *{{ targetPkg }}.{{ goify .Name true }}{{ goify .Parent.Name true }}Context) error { 315 c.{{ goify .Name true }}WSHandler(ctx).ServeHTTP(ctx.ResponseWriter, ctx.Request) 316 return nil 317 } 318 319 // {{ goify .Name true }}WSHandler establishes a websocket connection to run the {{ .Name }} action. 320 func (c *{{ $ctrlName }}) {{ goify .Name true }}WSHandler(ctx *{{ targetPkg }}.{{ goify .Name true }}{{ goify .Parent.Name true }}Context) websocket.Handler { 321 return func(ws *websocket.Conn) { 322 // {{ $ctrlName }}_{{ goify .Name true }}: start_implement 323 324 // Put your logic here 325 326 // {{ $ctrlName }}_{{ goify .Name true }}: end_implement 327 ws.Write([]byte("{{ .Name }} {{ .Parent.Name }}")) 328 // Dummy echo websocket server 329 io.Copy(ws, ws) 330 } 331 }` 332 333 const mainT = ` 334 func main() { 335 // Create service 336 service := goa.New({{ printf "%q" .Name }}) 337 338 // Mount middleware 339 service.Use(middleware.RequestID()) 340 service.Use(middleware.LogRequest(true)) 341 service.Use(middleware.ErrorHandler(service, true)) 342 service.Use(middleware.Recover()) 343 {{ $api := .API }} 344 {{ range $name, $res := $api.Resources }}{{ $name := goify $res.Name true }} // Mount "{{$res.Name}}" controller 345 {{ $tmp := tempvar }}{{ $tmp }} := New{{ $name }}Controller(service) 346 {{ targetPkg }}.Mount{{ $name }}Controller(service, {{ $tmp }}) 347 {{ end }} 348 349 // Start service 350 if err := service.ListenAndServe(":{{ getPort .API.Host }}"); err != nil { 351 service.LogError("startup", "err", err) 352 } 353 } 354 `