feat(goctl): Support gateway sample generation (#3049)

This commit is contained in:
anqiansong
2023-03-29 17:06:23 +08:00
committed by GitHub
parent 95b85336d6
commit 1904af2323
32 changed files with 1399 additions and 513 deletions

View File

@@ -0,0 +1,171 @@
package cobrax
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/zeromicro/go-zero/tools/goctl/internal/flags"
)
type Option func(*cobra.Command)
func WithRunE(runE func(*cobra.Command, []string) error) Option {
return func(cmd *cobra.Command) {
cmd.RunE = runE
}
}
func WithRun(run func(*cobra.Command, []string)) Option {
return func(cmd *cobra.Command) {
cmd.Run = run
}
}
func WithArgs(arg cobra.PositionalArgs) Option {
return func(command *cobra.Command) {
command.Args = arg
}
}
func WithHidden() Option {
return func(command *cobra.Command) {
command.Hidden = true
}
}
type Command struct {
*cobra.Command
}
type FlagSet struct {
*pflag.FlagSet
}
func (f *FlagSet) StringVar(p *string, name string) {
f.StringVarWithDefaultValue(p, name, "")
}
func (f *FlagSet) StringVarWithDefaultValue(p *string, name string, value string) {
f.FlagSet.StringVar(p, name, value, "")
}
func (f *FlagSet) StringVarP(p *string, name, shorthand string) {
f.StringVarPWithDefaultValue(p, name, shorthand, "")
}
func (f *FlagSet) StringVarPWithDefaultValue(p *string, name, shorthand string, value string) {
f.FlagSet.StringVarP(p, name, shorthand, value, "")
}
func (f *FlagSet) BoolVar(p *bool, name string) {
f.BoolVarWithDefaultValue(p, name, false)
}
func (f *FlagSet) BoolVarWithDefaultValue(p *bool, name string, value bool) {
f.FlagSet.BoolVar(p, name, value, "")
}
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string) {
f.BoolVarPWithDefaultValue(p, name, shorthand, false)
}
func (f *FlagSet) BoolVarPWithDefaultValue(p *bool, name, shorthand string, value bool) {
f.FlagSet.BoolVarP(p, name, shorthand, value, "")
}
func (f *FlagSet) IntVar(p *int, name string) {
f.IntVarWithDefaultValue(p, name, 0)
}
func (f *FlagSet) IntVarWithDefaultValue(p *int, name string, value int) {
f.FlagSet.IntVar(p, name, value, "")
}
func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string) {
f.FlagSet.StringSliceVarP(p, name, shorthand, []string{}, "")
}
func (f *FlagSet) StringSliceVarPWithDefaultValue(p *[]string, name, shorthand string, value []string) {
f.FlagSet.StringSliceVarP(p, name, shorthand, value, "")
}
func (f *FlagSet) StringSliceVar(p *[]string, name string) {
f.StringSliceVarWithDefaultValue(p, name, []string{})
}
func (f *FlagSet) StringSliceVarWithDefaultValue(p *[]string, name string, value []string) {
f.FlagSet.StringSliceVar(p, name, value, "")
}
func NewCommand(use string, opts ...Option) *Command {
c := &Command{
Command: &cobra.Command{
Use: use,
},
}
for _, opt := range opts {
opt(c.Command)
}
return c
}
func (c *Command) AddCommand(cmds ...*Command) {
for _, cmd := range cmds {
c.Command.AddCommand(cmd.Command)
}
}
func (c *Command) Flags() *FlagSet {
set := c.Command.Flags()
return &FlagSet{
FlagSet: set,
}
}
func (c *Command) PersistentFlags() *FlagSet {
set := c.Command.PersistentFlags()
return &FlagSet{
FlagSet: set,
}
}
func (c *Command) MustInit() {
commands := append([]*cobra.Command{c.Command}, getCommandsRecursively(c.Command)...)
for _, command := range commands {
commandKey := getCommandName(command)
if len(command.Short) == 0 {
command.Short = flags.Get(commandKey + ".short")
}
if len(command.Long) == 0 {
command.Long = flags.Get(commandKey + ".long")
}
if len(command.Example) == 0 {
command.Example = flags.Get(commandKey + ".example")
}
command.Flags().VisitAll(func(flag *pflag.Flag) {
flag.Usage = flags.Get(fmt.Sprintf("%s.%s", commandKey, flag.Name))
})
command.PersistentFlags().VisitAll(func(flag *pflag.Flag) {
flag.Usage = flags.Get(fmt.Sprintf("%s.%s", commandKey, flag.Name))
})
}
}
func getCommandName(cmd *cobra.Command) string {
if cmd.HasParent() {
return getCommandName(cmd.Parent()) + "." + cmd.Name()
}
return cmd.Name()
}
func getCommandsRecursively(parent *cobra.Command) []*cobra.Command {
var commands []*cobra.Command
for _, cmd := range parent.Commands() {
commands = append(commands, cmd)
commands = append(commands, getCommandsRecursively(cmd)...)
}
return commands
}

View File

@@ -0,0 +1,283 @@
{
"goctl": {
"short": "A cli tool to generate go-zero code",
"long": "A cli tool to generate api, zrpc, model code\n\nGitHub: https://github.com/zeromicro/go-zero\nSite: https://go-zero.dev",
"api": {
"short": "Generate api related files",
"o": "Output a sample api file",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"api": "The api file",
"dir": "The target dir",
"dart": {
"short": "Generate dart files for provided api in api file",
"dir": "{{.goctl.api.dir}}",
"api": "{{.goctl.api.api}}",
"legacy": "Legacy generator for flutter v1",
"hostname": "hostname of the server",
"scheme": "scheme of the server"
},
"doc": {
"short": "Generate doc files",
"dir": "{{.goctl.api.dir}}",
"o": "The output markdown directory"
},
"format": {
"short": "Format api files",
"dir": "{{.goctl.api.dir}}",
"iu": "Ignore update",
"stdin": "Use stdin to input api doc content, press \"ctrl + d\" to send EOF",
"declare": "Use to skip check api types already declare"
},
"go": {
"short": "Generate go files for provided api in api file",
"dir": "{{.goctl.api.dir}}",
"api": "{{.goctl.api.api}}",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"style": "{{.global.style}}"
},
"new": {
"short": "Fast create api service",
"Example": "goctl api new [options] service-name",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"style": "{{.global.style}}"
},
"validate": {
"short": "Validate api file",
"api": "{{.goctl.api.api}}"
},
"kt": {
"short": "Generate kotlin code for provided api file",
"dir": "{{.goctl.api.dir}}",
"api": "{{.goctl.api.api}}",
"pkg": "Define package name for kotlin file"
},
"plugin": {
"short": "Custom file generator",
"plugin": "The plugin file",
"dir": "{{.goctl.api.dir}}",
"api": "{{.goctl.api.api}}",
"style": "{{.global.style}}"
},
"ts": {
"short": "Generate ts files for provided api in api file",
"dir": "{{.goctl.api.dir}}",
"api": "{{.goctl.api.api}}",
"caller": "The web api caller",
"unwrap": "Unwrap the webapi caller for import"
}
},
"bug": {
"short": "Report a bug"
},
"docker": {
"short": "Generate Dockerfile",
"exe": "The executable name in the built image",
"go": "The file that contains main function",
"base": "The base image to build the docker image, default scratch",
"port": "The port to expose, default none",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"version": "The goctl builder golang image version",
"tz": "The timezone of the container"
},
"kube": {
"short": "Generate kubernetes files",
"deploy": {
"short": "Generate deployment yaml file",
"name": "The name of deployment (required)",
"namespace": "The namespace of deployment (required)",
"image": "The docker image of deployment (required)",
"secret": "The secret to image pull from registry",
"requestCpu": "The request cpu to deploy",
"requestMem": "The request memory to deploy",
"limitCpu": "The limit cpu to deploy",
"limitMem": "The limit memory to deploy",
"o": "The output yaml file (required)",
"replicas": "The number of replicas to deploy",
"revisions": "The number of revision history to limit",
"port": "The port of the deployment to listen on pod (required)",
"nodePort": "The nodePort of the deployment to expose",
"targetPort": "The targetPort of the deployment, default to port",
"minReplicas": "The min replicas to deploy",
"maxReplicas": "The max replicas to deploy",
"imagePullPolicy": "The image pull policy of the deployment, default to IfNotPresent",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"serviceAccount": "TheServiceAccount for the deployment"
}
},
"env": {
"short": "Check or edit goctl environment",
"write": "Edit goctl environment",
"force": "Silent installation of non-existent dependencies",
"verbose": "Enable log output",
"install": {
"short": "Goctl env installation"
},
"check": {
"short": "Detect goctl env and dependency tools",
"install": "Install dependencies if not found"
}
},
"gateway": {
"short": "gateway is a tool to generate gateway code",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"dir": "The output dir",
"protoc": {
"short": "generate gateway code from proto file"
},
"protoset": {
"short": "generate gateway code from protoset file"
},
"server": {
"short": "generate gateway code from grpc server"
}
},
"model": {
"short": "Generate model code",
"dir": "The target dir",
"mysql": {
"short": "Generate mysql model",
"strict": "Generate model in strict mode",
"ignore-columns": "Ignore columns while creating or updating rows",
"datasource": {
"short": "Generate model from datasource",
"url": "The data source of database,like \"root:password@tcp(127.0.0.1:3306)/database",
"table": "The table or table globbing patterns in the database",
"cache": "Generate code with cache [optional]",
"dir": "{{.goctl.model.dir}}",
"style": "{{.global.style}}",
"idea": "For idea plugin [optional]",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}"
},
"ddl": {
"short": "Generate mysql model from ddl",
"src": "The path or path globbing patterns of the ddl",
"dir": "{{.goctl.model.dir}}",
"style": "{{.global.style}}",
"cache": "Generate code with cache [optional]",
"idea": "For idea plugin [optional]",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}"
}
},
"pg": {
"short": "Generate postgresql model",
"datasource": {
"short": "Generate model from datasource",
"url": "The data source of database,like \"root:password@tcp(127.0.0.1:3306)/database",
"table": "The table or table globbing patterns in the database",
"schema": "The schema or schema globbing patterns in the database",
"cache": "Generate code with cache [optional]",
"dir": "{{.goctl.model.dir}}",
"style": "{{.global.style}}",
"idea": "For idea plugin [optional]",
"strict": "Generate model in strict mode",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}"
}
},
"mongo": {
"short": "Generate mongo model",
"type": "Specified model type name",
"cache": "Generate code with cache [optional]",
"easy": "Generate code with auto generated CollectionName for easy declare [optional]",
"dir": "{{.goctl.model.dir}}",
"style": "{{.global.style}}",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}"
}
},
"migrate": {
"short": "Migrate from tal-tech to zeromicro",
"long": "Migrate is a transition command to help users migrate their projects from tal-tech to zeromicro version",
"verbose": "Verbose enables extra logging",
"version": "The target release version of github.com/zeromicro/go-zero to migrate"
},
"quickstart": {
"short": "quickly start a project",
"service-type": "specify the service type, supported values: [mono, micro]"
},
"rpc": {
"short": "Generate rpc code",
"o": "Output a sample proto file",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"new": {
"short": "Generate rpc demo service",
"style": "{{.global.style}}",
"idea": "For idea plugin [optional]",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"verbose": "Enable log output"
},
"template": {
"short": "Generate proto template",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}"
},
"protoc": {
"short": "Generate grpc code",
"example": "goctl rpc protoc xx.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.",
"multiple": "Generated in multiple rpc service mode",
"zrpc_out": "The zrpc output directory",
"style": "{{.global.style}}",
"home": "{{.global.home}}",
"remote": "{{.global.remote}}",
"branch": "{{.global.branch}}",
"verbose": "Enable log output"
}
},
"template": {
"short": "Template operation",
"home": "The goctl home path of the template",
"init": {
"short": "Initialize the all templates(force update)",
"home": "{{.goctl.template.home}}",
"category": "The category of template, enum [api,rpc,model,docker,kube]"
},
"clean": {
"short": "Clean the all cache templates",
"home": "{{.goctl.template.home}}"
},
"update": {
"short": "Update template of the target category to the latest",
"home": "{{.goctl.template.home}}",
"category": "{{.goctl.template.category}}"
},
"revert": {
"short": "Revert the target template to the latest",
"home": "{{.goctl.template.home}}",
"category": "{{.goctl.template.category}}",
"name": "The target file name of template"
}
},
"upgrade": {
"short": "Upgrade goctl to latest version"
}
},
"global": {
"home": "The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority",
"remote": "The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority\nThe git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure",
"branch": "The branch of the remote repo, it does work with --remote",
"style": "The file naming format, see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]"
}
}

View File

@@ -0,0 +1,101 @@
package flags
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"io"
"log"
"strings"
"testing"
"github.com/zeromicro/go-zero/tools/goctl/util"
)
//go:embed default_en.json
var defaultEnFlagConfig []byte
type ConfigLoader struct {
conf map[string]any
}
func (cl *ConfigLoader) ReadConfig(in io.Reader) error {
return json.NewDecoder(in).Decode(&cl.conf)
}
func (cl *ConfigLoader) GetString(key string) string {
keyList := strings.FieldsFunc(key, func(r rune) bool {
return r == '.'
})
var conf = cl.conf
for idx, k := range keyList {
val, ok := conf[k]
if !ok {
return ""
}
if idx < len(keyList)-1 {
conf, ok = val.(map[string]any)
if !ok {
return ""
}
continue
}
return fmt.Sprint(val)
}
return ""
}
type Flags struct {
loader *ConfigLoader
}
func MustLoad() *Flags {
loader := &ConfigLoader{
conf: map[string]any{},
}
if err := loader.ReadConfig(bytes.NewBuffer(defaultEnFlagConfig)); err != nil {
log.Fatal(err)
}
return &Flags{
loader: loader,
}
}
func setTestData(t *testing.T, data []byte) {
origin := defaultEnFlagConfig
defaultEnFlagConfig = data
t.Cleanup(func() {
defaultEnFlagConfig = origin
})
}
func (f *Flags) Get(key string) (string, error) {
value := f.loader.GetString(key)
for util.IsTemplateVariable(value) {
value = util.TemplateVariable(value)
if value == key {
return "", fmt.Errorf("the variable can not be self: %q", key)
}
return f.Get(value)
}
return value, nil
}
var flags *Flags
func Get(key string) string {
if flags == nil {
flags = MustLoad()
}
v, err := flags.Get(key)
if err != nil {
log.Fatal(err)
return ""
}
return v
}

View File

@@ -0,0 +1,78 @@
package flags
import (
"fmt"
"testing"
"github.com/zeromicro/go-zero/tools/goctl/test"
)
func TestFlags_Get(t *testing.T) {
setTestData(t, []byte(`{"host":"0.0.0.0","port":8888,"service":{"host":"{{.host}}","port":"{{.port}}","invalid":"{{.service.invalid}}"}}`))
f := MustLoad()
executor := test.NewExecutor[string, string]()
executor.Add([]test.Data[string, string]{
{
Name: "key_host",
Input: "host",
Want: "0.0.0.0",
},
{
Name: "key_port",
Input: "port",
Want: "8888",
},
{
Name: "key_service.host",
Input: "service.host",
Want: "0.0.0.0",
},
{
Name: "key_service.port",
Input: "service.port",
Want: "8888",
},
{
Name: "key_not_exists",
Input: "service.port.invalid",
},
{
Name: "key_service.invalid",
Input: "service.invalid",
E: fmt.Errorf("the variable can not be self: %q", "service.invalid"),
},
}...)
executor.RunE(t, f.Get)
}
func Test_Get(t *testing.T) {
setTestData(t, []byte(`{"host":"0.0.0.0","port":8888,"service":{"host":"{{.host}}","port":"{{.port}}","invalid":"{{.service.invalid}}"}}`))
executor := test.NewExecutor[string, string]()
executor.Add([]test.Data[string, string]{
{
Name: "key_host",
Input: "host",
Want: "0.0.0.0",
},
{
Name: "key_port",
Input: "port",
Want: "8888",
},
{
Name: "key_service.host",
Input: "service.host",
Want: "0.0.0.0",
},
{
Name: "key_service.port",
Input: "service.port",
Want: "8888",
},
{
Name: "key_not_exists",
Input: "service.port.invalid",
},
}...)
executor.Run(t, Get)
}