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  `