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(&notest, "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(&notool, "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  }