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 mode( 0-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
}