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