gr_hz/config/argument.go

398 lines
11 KiB
Go
Raw Normal View History

2024-04-30 19:30:09 +08:00
/*
* Copyright 2022 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
2024-04-30 20:22:44 +08:00
"gr_hz/meta"
"gr_hz/util"
"gr_hz/util/logs"
2024-04-30 19:30:09 +08:00
)
type Argument struct {
// Mode meta.Mode // operating mode0-compiler, 1-plugin)
CmdType string // command type
Verbose bool // print verbose log
Cwd string // execution path
OutDir string // output path
HandlerDir string // handler path
ModelDir string // model path
RouterDir string // router path
ClientDir string // client path
BaseDomain string // request domain
ForceClientDir string // client dir (not use namespace as a subpath)
IdlType string // idl type
IdlPaths []string // master idl path
RawOptPkg []string // user-specified package import path
OptPkgMap map[string]string
Includes []string
PkgPrefix string
Gopath string // $GOPATH
Gosrc string // $GOPATH/src
Gomod string
Gopkg string // $GOPATH/src/{{gopkg}}
ServiceName string // service name
Use string
NeedGoMod bool
JSONEnumStr bool
UnsetOmitempty bool
ProtobufCamelJSONTag bool
ProtocOptions []string // options to pass through to protoc
ThriftOptions []string // options to pass through to thriftgo for go flag
ProtobufPlugins []string
ThriftPlugins []string
SnakeName bool
RmTags []string
Excludes []string
NoRecurse bool
HandlerByMethod bool
ForceNew bool
SnakeStyleMiddleware bool
EnableExtends bool
CustomizeLayout string
CustomizeLayoutData string
CustomizePackage string
ModelBackend string
}
func NewArgument() *Argument {
return &Argument{
OptPkgMap: make(map[string]string),
Includes: make([]string, 0, 4),
Excludes: make([]string, 0, 4),
ProtocOptions: make([]string, 0, 4),
ThriftOptions: make([]string, 0, 4),
}
}
// Parse initializes a new argument based on its own information
func (arg *Argument) Parse(c *cli.Context, cmd string) (*Argument, error) {
// v2 cli cannot put the StringSlice flag to struct, so we need to parse it here
arg.parseStringSlice(c)
args := arg.Fork()
args.CmdType = cmd
err := args.checkPath()
if err != nil {
return nil, err
}
err = args.checkIDL()
if err != nil {
return nil, err
}
err = args.checkPackage()
if err != nil {
return nil, err
}
return args, nil
}
func (arg *Argument) parseStringSlice(c *cli.Context) {
arg.IdlPaths = c.StringSlice("idl")
arg.Includes = c.StringSlice("proto_path")
arg.Excludes = c.StringSlice("exclude_file")
arg.RawOptPkg = c.StringSlice("option_package")
arg.ThriftOptions = c.StringSlice("thriftgo")
arg.ProtocOptions = c.StringSlice("protoc")
arg.ThriftPlugins = c.StringSlice("thrift-plugins")
arg.ProtobufPlugins = c.StringSlice("protoc-plugins")
arg.RmTags = c.StringSlice("rm_tag")
}
func (arg *Argument) UpdateByManifest(m *meta.Manifest) {
if arg.HandlerDir == "" && m.HandlerDir != "" {
logs.Infof("use \"handler_dir\" in \".hz\" as the handler generated dir\n")
arg.HandlerDir = m.HandlerDir
}
if arg.ModelDir == "" && m.ModelDir != "" {
logs.Infof("use \"model_dir\" in \".hz\" as the model generated dir\n")
arg.ModelDir = m.ModelDir
}
if len(m.RouterDir) != 0 {
logs.Infof("use \"router_dir\" in \".hz\" as the router generated dir\n")
arg.RouterDir = m.RouterDir
}
}
// checkPath sets the project path and verifies that the model、handler、router and client path is compliant
func (arg *Argument) checkPath() error {
dir, err := os.Getwd()
if err != nil {
return fmt.Errorf("get current path failed: %s", err)
}
arg.Cwd = dir
if arg.OutDir == "" {
arg.OutDir = dir
}
if !filepath.IsAbs(arg.OutDir) {
ap := filepath.Join(arg.Cwd, arg.OutDir)
arg.OutDir = ap
}
if arg.ModelDir != "" && filepath.IsAbs(arg.ModelDir) {
return fmt.Errorf("model path %s must be relative to out_dir", arg.ModelDir)
}
if arg.HandlerDir != "" && filepath.IsAbs(arg.HandlerDir) {
return fmt.Errorf("handler path %s must be relative to out_dir", arg.HandlerDir)
}
if arg.RouterDir != "" && filepath.IsAbs(arg.RouterDir) {
return fmt.Errorf("router path %s must be relative to out_dir", arg.RouterDir)
}
if arg.ClientDir != "" && filepath.IsAbs(arg.ClientDir) {
return fmt.Errorf("router path %s must be relative to out_dir", arg.ClientDir)
}
return nil
}
// checkIDL check if the idl path exists, set and check the idl type
func (arg *Argument) checkIDL() error {
for i, path := range arg.IdlPaths {
abPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("idl path %s is not absolute", path)
}
ext := filepath.Ext(abPath)
if ext == "" || ext[0] != '.' {
return fmt.Errorf("idl path %s is not a valid file", path)
}
ext = ext[1:]
switch ext {
case meta.IdlThrift:
arg.IdlType = meta.IdlThrift
case meta.IdlProto:
arg.IdlType = meta.IdlProto
default:
return fmt.Errorf("IDL type %s is not supported", ext)
}
arg.IdlPaths[i] = abPath
}
return nil
}
func (arg *Argument) IsUpdate() bool {
return arg.CmdType == meta.CmdUpdate
}
func (arg *Argument) IsNew() bool {
return arg.CmdType == meta.CmdNew
}
// checkPackage check and set the gopath、 module and package name
func (arg *Argument) checkPackage() error {
gopath, err := util.GetGOPATH()
if err != nil {
return fmt.Errorf("get gopath failed: %s", err)
}
if gopath == "" {
return fmt.Errorf("GOPATH is not set")
}
arg.Gopath = gopath
arg.Gosrc = filepath.Join(gopath, "src")
// Generate the project under gopath, use the relative path as the package name
if strings.HasPrefix(arg.Cwd, arg.Gosrc) {
if gopkg, err := filepath.Rel(arg.Gosrc, arg.Cwd); err != nil {
return fmt.Errorf("get relative path to GOPATH/src failed: %s", err)
} else {
arg.Gopkg = gopkg
}
}
if len(arg.Gomod) == 0 { // not specified "go module"
// search go.mod recursively
module, path, ok := util.SearchGoMod(arg.Cwd, true)
if ok { // find go.mod in upper level, use it as project module, don't generate go.mod
rel, err := filepath.Rel(path, arg.Cwd)
if err != nil {
return fmt.Errorf("can not get relative path, err :%v", err)
}
arg.Gomod = filepath.Join(module, rel)
logs.Debugf("find module '%s' from '%s/go.mod', so use it as module name", module, path)
}
if len(arg.Gomod) == 0 { // don't find go.mod in upper level, use relative path as module name, generate go.mod
logs.Debugf("use gopath's relative path '%s' as the module name", arg.Gopkg)
// gopkg will be "" under non-gopath
arg.Gomod = arg.Gopkg
arg.NeedGoMod = true
}
arg.Gomod = util.PathToImport(arg.Gomod, "")
} else { // specified "go module"
// search go.mod in current path
module, path, ok := util.SearchGoMod(arg.Cwd, false)
if ok { // go.mod exists in current path, check module name, don't generate go.mod
if module != arg.Gomod {
return fmt.Errorf("module name given by the '-module/mod' option ('%s') is not consist with the name defined in go.mod ('%s' from %s), try to remove '-module/mod' option in your command\n", arg.Gomod, module, path)
}
} else { // go.mod don't exist in current path, generate go.mod
arg.NeedGoMod = true
}
}
if len(arg.Gomod) == 0 {
return fmt.Errorf("can not get go module, please specify a module name with the '-module/mod' flag")
}
if len(arg.RawOptPkg) > 0 {
arg.OptPkgMap = make(map[string]string, len(arg.RawOptPkg))
for _, op := range arg.RawOptPkg {
ps := strings.SplitN(op, "=", 2)
if len(ps) != 2 {
return fmt.Errorf("invalid option package: %s", op)
}
arg.OptPkgMap[ps[0]] = ps[1]
}
arg.RawOptPkg = nil
}
return nil
}
func (arg *Argument) Pack() ([]string, error) {
data, err := util.PackArgs(arg)
if err != nil {
return nil, fmt.Errorf("pack argument failed: %s", err)
}
return data, nil
}
func (arg *Argument) Unpack(data []string) error {
err := util.UnpackArgs(data, arg)
if err != nil {
return fmt.Errorf("unpack argument failed: %s", err)
}
return nil
}
// Fork can copy its own parameters to a new argument
func (arg *Argument) Fork() *Argument {
args := NewArgument()
*args = *arg
util.CopyString2StringMap(arg.OptPkgMap, args.OptPkgMap)
util.CopyStringSlice(&arg.Includes, &args.Includes)
util.CopyStringSlice(&arg.Excludes, &args.Excludes)
util.CopyStringSlice(&arg.ProtocOptions, &args.ProtocOptions)
util.CopyStringSlice(&arg.ThriftOptions, &args.ThriftOptions)
return args
}
func (arg *Argument) GetGoPackage() (string, error) {
if arg.Gomod != "" {
return arg.Gomod, nil
} else if arg.Gopkg != "" {
return arg.Gopkg, nil
}
return "", fmt.Errorf("project package name is not set")
}
func IdlTypeToCompiler(idlType string) (string, error) {
switch idlType {
case meta.IdlProto:
return meta.TpCompilerProto, nil
case meta.IdlThrift:
return meta.TpCompilerThrift, nil
default:
return "", fmt.Errorf("IDL type %s is not supported", idlType)
}
}
func (arg *Argument) ModelPackagePrefix() (string, error) {
ret := arg.Gomod
if arg.ModelDir == "" {
path, err := util.RelativePath(meta.ModelDir)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if err != nil {
return "", err
}
ret += path
} else {
path, err := util.RelativePath(arg.ModelDir)
if err != nil {
return "", err
}
ret += "/" + path
}
return strings.ReplaceAll(ret, string(filepath.Separator), "/"), nil
}
func (arg *Argument) ModelOutDir() string {
ret := arg.OutDir
if arg.ModelDir == "" {
ret = filepath.Join(ret, meta.ModelDir)
} else {
ret = filepath.Join(ret, arg.ModelDir)
}
return ret
}
func (arg *Argument) GetHandlerDir() (string, error) {
if arg.HandlerDir == "" {
return util.RelativePath(meta.HandlerDir)
}
return util.RelativePath(arg.HandlerDir)
}
func (arg *Argument) GetModelDir() (string, error) {
if arg.ModelDir == "" {
return util.RelativePath(meta.ModelDir)
}
return util.RelativePath(arg.ModelDir)
}
func (arg *Argument) GetRouterDir() (string, error) {
if arg.RouterDir == "" {
return util.RelativePath(meta.RouterDir)
}
return util.RelativePath(arg.RouterDir)
}
func (arg *Argument) GetClientDir() (string, error) {
if arg.ClientDir == "" {
return "", nil
}
return util.RelativePath(arg.ClientDir)
}
func (arg *Argument) InitManifest(m *meta.Manifest) {
m.Version = meta.Version
m.HandlerDir = arg.HandlerDir
m.ModelDir = arg.ModelDir
m.RouterDir = arg.RouterDir
}
func (arg *Argument) UpdateManifest(m *meta.Manifest) {
m.Version = meta.Version
if arg.HandlerDir != m.HandlerDir {
m.HandlerDir = arg.HandlerDir
}
if arg.HandlerDir != m.ModelDir {
m.ModelDir = arg.ModelDir
}
// "router_dir" must not be defined by "update" command
}