mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
Merge branch 'master' of https://github.com/gogf/gf into gtree_Example
This commit is contained in:
@ -281,7 +281,7 @@ type parseWithTagInFieldStructOutput struct {
|
||||
Order string
|
||||
}
|
||||
|
||||
func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) {
|
||||
func (m *Model) parseWithTagInFieldStruct(field structs.Field) (output parseWithTagInFieldStructOutput) {
|
||||
var (
|
||||
match []string
|
||||
ormTag = field.Tag(OrmTagForStruct)
|
||||
|
||||
@ -123,7 +123,7 @@ type FieldMapInput struct {
|
||||
Pointer interface{}
|
||||
|
||||
// PriorityTagArray specifies the priority tag array for retrieving from high to low.
|
||||
// If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name.
|
||||
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
|
||||
PriorityTagArray []string
|
||||
|
||||
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
|
||||
@ -132,12 +132,12 @@ type FieldMapInput struct {
|
||||
}
|
||||
|
||||
// Fields retrieves and returns the fields of `pointer` as slice.
|
||||
func Fields(in FieldsInput) ([]*Field, error) {
|
||||
func Fields(in FieldsInput) ([]Field, error) {
|
||||
var (
|
||||
ok bool
|
||||
fieldFilterMap = make(map[string]struct{})
|
||||
retrievedFields = make([]*Field, 0)
|
||||
currentLevelFieldMap = make(map[string]*Field)
|
||||
retrievedFields = make([]Field, 0)
|
||||
currentLevelFieldMap = make(map[string]Field)
|
||||
)
|
||||
rangeFields, err := getFieldValues(in.Pointer)
|
||||
if err != nil {
|
||||
@ -187,7 +187,7 @@ func Fields(in FieldsInput) ([]*Field, error) {
|
||||
continue
|
||||
}
|
||||
fieldFilterMap[fieldName] = struct{}{}
|
||||
if v := currentLevelFieldMap[fieldName]; v == nil {
|
||||
if v, ok := currentLevelFieldMap[fieldName]; !ok {
|
||||
retrievedFields = append(retrievedFields, structField)
|
||||
} else {
|
||||
retrievedFields = append(retrievedFields, v)
|
||||
@ -204,25 +204,25 @@ func Fields(in FieldsInput) ([]*Field, error) {
|
||||
return retrievedFields, nil
|
||||
}
|
||||
|
||||
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
|
||||
// FieldMap retrieves and returns struct field as map[name/tag]Field from `pointer`.
|
||||
//
|
||||
// The parameter `pointer` should be type of struct/*struct.
|
||||
//
|
||||
// The parameter `priority` specifies the priority tag array for retrieving from high to low.
|
||||
// If it's given `nil`, it returns map[name]*Field, of which the `name` is attribute name.
|
||||
// If it's given `nil`, it returns map[name]Field, of which the `name` is attribute name.
|
||||
//
|
||||
// The parameter `recursive` specifies the whether retrieving the fields recursively if the attribute
|
||||
// is an embedded struct.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func FieldMap(in FieldMapInput) (map[string]*Field, error) {
|
||||
func FieldMap(in FieldMapInput) (map[string]Field, error) {
|
||||
fields, err := getFieldValues(in.Pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
tagValue = ""
|
||||
mapField = make(map[string]*Field)
|
||||
mapField = make(map[string]Field)
|
||||
)
|
||||
for _, field := range fields {
|
||||
// Only retrieve exported attributes.
|
||||
|
||||
@ -65,14 +65,14 @@ func ParseTag(tag string) map[string]string {
|
||||
return data
|
||||
}
|
||||
|
||||
// TagFields retrieves and returns struct tags as []*Field from `pointer`.
|
||||
// TagFields retrieves and returns struct tags as []Field from `pointer`.
|
||||
//
|
||||
// The parameter `pointer` should be type of struct/*struct.
|
||||
//
|
||||
// Note that,
|
||||
// 1. It only retrieves the exported attributes with first letter up-case from struct.
|
||||
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
|
||||
func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
|
||||
func TagFields(pointer interface{}, priority []string) ([]Field, error) {
|
||||
return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
|
||||
}
|
||||
|
||||
@ -95,18 +95,18 @@ func TagMapName(pointer interface{}, priority []string) (map[string]string, erro
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
// TagMapField retrieves struct tags as map[tag]*Field from `pointer`, and returns it.
|
||||
// TagMapField retrieves struct tags as map[tag]Field from `pointer`, and returns it.
|
||||
// The parameter `object` should be either type of struct/*struct/[]struct/[]*struct.
|
||||
//
|
||||
// Note that,
|
||||
// 1. It only retrieves the exported attributes with first letter up-case from struct.
|
||||
// 2. The parameter `priority` should be given, it only retrieves fields that has given tag.
|
||||
func TagMapField(object interface{}, priority []string) (map[string]*Field, error) {
|
||||
func TagMapField(object interface{}, priority []string) (map[string]Field, error) {
|
||||
fields, err := TagFields(object, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagMap := make(map[string]*Field, len(fields))
|
||||
tagMap := make(map[string]Field, len(fields))
|
||||
for _, field := range fields {
|
||||
tagField := field
|
||||
tagMap[field.TagValue] = tagField
|
||||
@ -114,7 +114,7 @@ func TagMapField(object interface{}, priority []string) (map[string]*Field, erro
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
func getFieldValues(value interface{}) ([]*Field, error) {
|
||||
func getFieldValues(value interface{}) ([]Field, error) {
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
@ -156,10 +156,10 @@ exitLoop:
|
||||
var (
|
||||
structType = reflectValue.Type()
|
||||
length = reflectValue.NumField()
|
||||
fields = make([]*Field, length)
|
||||
fields = make([]Field, length)
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
fields[i] = &Field{
|
||||
fields[i] = Field{
|
||||
Value: reflectValue.Field(i),
|
||||
Field: structType.Field(i),
|
||||
}
|
||||
@ -167,14 +167,14 @@ exitLoop:
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]*Field, error) {
|
||||
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
tagValue = ""
|
||||
tagFields = make([]*Field, 0)
|
||||
tagFields = make([]Field, 0)
|
||||
)
|
||||
for _, field := range fields {
|
||||
// Only retrieve exported attributes.
|
||||
|
||||
@ -29,7 +29,6 @@ func Init(args ...string) {
|
||||
|
||||
// GetOpt returns the option value named `name` as gvar.Var.
|
||||
func GetOpt(name string, def ...string) *gvar.Var {
|
||||
Init()
|
||||
if v := command.GetOpt(name, def...); v != "" {
|
||||
return gvar.New(v)
|
||||
}
|
||||
@ -38,19 +37,16 @@ func GetOpt(name string, def ...string) *gvar.Var {
|
||||
|
||||
// GetOptAll returns all parsed options.
|
||||
func GetOptAll() map[string]string {
|
||||
Init()
|
||||
return command.GetOptAll()
|
||||
}
|
||||
|
||||
// ContainsOpt checks whether option named `name` exist in the arguments.
|
||||
func ContainsOpt(name string) bool {
|
||||
Init()
|
||||
return command.ContainsOpt(name)
|
||||
}
|
||||
|
||||
// GetArg returns the argument at `index` as gvar.Var.
|
||||
func GetArg(index int, def ...string) *gvar.Var {
|
||||
Init()
|
||||
if v := command.GetArg(index, def...); v != "" {
|
||||
return gvar.New(v)
|
||||
}
|
||||
@ -59,7 +55,6 @@ func GetArg(index int, def ...string) *gvar.Var {
|
||||
|
||||
// GetArgAll returns all parsed arguments.
|
||||
func GetArgAll() []string {
|
||||
Init()
|
||||
return command.GetArgAll()
|
||||
}
|
||||
|
||||
|
||||
@ -16,49 +16,53 @@ import (
|
||||
|
||||
// Command holds the info about an argument that can handle custom logic.
|
||||
type Command struct {
|
||||
Name string // Command name(case-sensitive).
|
||||
Usage string // A brief line description about its usage, eg: gf build main.go [OPTION]
|
||||
Brief string // A brief info that describes what this command will do.
|
||||
Description string // A detailed description.
|
||||
Options []Option // Option array, configuring how this command act.
|
||||
Func Function // Custom function.
|
||||
HelpFunc Function // Custom help function
|
||||
Examples string // Usage examples.
|
||||
Additional string // Additional custom info about this command.
|
||||
parent *Command // Parent command for internal usage.
|
||||
commands []Command // Sub commands of this command.
|
||||
Name string // Command name(case-sensitive).
|
||||
Usage string // A brief line description about its usage, eg: gf build main.go [OPTION]
|
||||
Brief string // A brief info that describes what this command will do.
|
||||
Description string // A detailed description.
|
||||
Options []Option // Option array, configuring how this command act.
|
||||
Func Function // Custom function.
|
||||
FuncWithValue FuncWithValue // Custom function with output parameters that can interact with command caller.
|
||||
HelpFunc Function // Custom help function
|
||||
Examples string // Usage examples.
|
||||
Additional string // Additional custom info about this command.
|
||||
parent *Command // Parent command for internal usage.
|
||||
commands []Command // Sub commands of this command.
|
||||
}
|
||||
|
||||
// Function is a custom command callback function that is bound to a certain argument.
|
||||
type Function func(ctx context.Context, parser *Parser) (err error)
|
||||
|
||||
// FuncWithValue is similar like Func but with output parameters that can interact with command caller.
|
||||
type FuncWithValue func(ctx context.Context, parser *Parser) (out interface{}, err error)
|
||||
|
||||
// Option is the command value that is specified by a name or shor name.
|
||||
// An Option can have or have no value bound to it.
|
||||
type Option struct {
|
||||
Name string // Option name.
|
||||
Short string // Option short.
|
||||
Brief string // Brief info about this Option, which is used in help info.
|
||||
NeedValue bool // Whether this Option having or having no value bound to it.
|
||||
Name string // Option name.
|
||||
Short string // Option short.
|
||||
Brief string // Brief info about this Option, which is used in help info.
|
||||
Orphan bool // Whether this Option having or having no value bound to it.
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultHelpOption is the default help option that will be automatically added to each command.
|
||||
defaultHelpOption = Option{
|
||||
Name: `help`,
|
||||
Short: `h`,
|
||||
Brief: `more information about this command`,
|
||||
NeedValue: false,
|
||||
Name: `help`,
|
||||
Short: `h`,
|
||||
Brief: `more information about this command`,
|
||||
Orphan: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Add adds one or more sub-commands to current command.
|
||||
func (c *Command) Add(commands ...Command) error {
|
||||
// AddCommand adds one or more sub-commands to current command.
|
||||
func (c *Command) AddCommand(commands ...Command) error {
|
||||
for _, cmd := range commands {
|
||||
cmd.Name = gstr.Trim(cmd.Name)
|
||||
if cmd.Name == "" {
|
||||
return gerror.New("command name should not be empty")
|
||||
}
|
||||
if cmd.Func == nil {
|
||||
if cmd.Func == nil && cmd.FuncWithValue == nil {
|
||||
return gerror.New("command function should not be empty")
|
||||
}
|
||||
cmd.parent = c
|
||||
@ -66,3 +70,18 @@ func (c *Command) Add(commands ...Command) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddObject adds one or more sub-commands to current command using struct object.
|
||||
func (c *Command) AddObject(objects ...interface{}) error {
|
||||
var (
|
||||
commands []Command
|
||||
)
|
||||
for _, object := range objects {
|
||||
tempCommand, err := NewFromObject(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commands = append(commands, *tempCommand)
|
||||
}
|
||||
return c.AddCommand(commands...)
|
||||
}
|
||||
|
||||
285
os/gcmd/gcmd_command_object.go
Normal file
285
os/gcmd/gcmd_command_object.go
Normal file
@ -0,0 +1,285 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/structs"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
const (
|
||||
tagNameDc = `dc`
|
||||
tagNameAd = `ad`
|
||||
tagNameRoot = `root`
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultValueTags is the struct tag names for default value storing.
|
||||
defaultValueTags = []string{"d", "default"}
|
||||
)
|
||||
|
||||
// NewFromObject creates and returns a root command object using given object.
|
||||
func NewFromObject(object interface{}) (rootCmd *Command, err error) {
|
||||
originValueAndKind := utils.OriginValueAndKind(object)
|
||||
if originValueAndKind.OriginKind != reflect.Struct {
|
||||
return nil, gerror.Newf(
|
||||
`input object should be type of struct, but got "%s"`,
|
||||
originValueAndKind.InputValue.Type().String(),
|
||||
)
|
||||
}
|
||||
var (
|
||||
nameSet = gset.NewStrSet()
|
||||
subCommands []Command
|
||||
)
|
||||
for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ {
|
||||
var (
|
||||
root bool
|
||||
method = originValueAndKind.InputValue.Method(i)
|
||||
methodCommand Command
|
||||
)
|
||||
methodCommand, root, err = newCommandFromMethod(object, method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nameSet.Contains(methodCommand.Name) {
|
||||
return nil, gerror.Newf(
|
||||
`command name should be unique, found duplicated command name in method "%s"`,
|
||||
method.Type().String(),
|
||||
)
|
||||
}
|
||||
if root {
|
||||
if rootCmd != nil {
|
||||
return nil, gerror.Newf(
|
||||
`there should be only one root command in object, found duplicated in method "%s"`,
|
||||
method.Type().String(),
|
||||
)
|
||||
}
|
||||
rootCmd = &methodCommand
|
||||
} else {
|
||||
subCommands = append(subCommands, methodCommand)
|
||||
}
|
||||
}
|
||||
if rootCmd == nil {
|
||||
return nil, gerror.Newf(
|
||||
`there should be one root command in object when creating command from object, but found none in object "%s"`,
|
||||
originValueAndKind.InputValue.Type().String(),
|
||||
)
|
||||
}
|
||||
if len(subCommands) > 0 {
|
||||
err = rootCmd.AddCommand(subCommands...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newCommandFromMethod(object interface{}, method reflect.Value) (command Command, root bool, err error) {
|
||||
var (
|
||||
reflectType = method.Type()
|
||||
)
|
||||
// Necessary validation for input/output parameters and naming.
|
||||
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
|
||||
if reflectType.PkgPath() != "" {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
||||
reflectType.PkgPath(), reflect.TypeOf(object).Name(), reflectType.Name(), reflectType.String(),
|
||||
)
|
||||
} else {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
||||
reflectType.String(),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
if reflectType.In(0).String() != "context.Context" {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`,
|
||||
reflectType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
if reflectType.Out(1).String() != "error" {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid command: defined as "%s", but the last output parameter should be type of "error"`,
|
||||
reflectType.String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
// The input struct should be named as `xxxInput`.
|
||||
if !gstr.HasSuffix(reflectType.In(1).String(), `Input`) {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`,
|
||||
reflectType.In(1).String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
// The output struct should be named as `xxxOutput`.
|
||||
if !gstr.HasSuffix(reflectType.Out(0).String(), `Output`) {
|
||||
err = gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`,
|
||||
reflectType.Out(0).String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
inputObject reflect.Value
|
||||
)
|
||||
if method.Type().In(1).Kind() == reflect.Ptr {
|
||||
inputObject = reflect.New(method.Type().In(1).Elem()).Elem()
|
||||
} else {
|
||||
inputObject = reflect.New(method.Type().In(1)).Elem()
|
||||
}
|
||||
|
||||
// Command creating.
|
||||
var (
|
||||
metaData = gmeta.Data(inputObject.Interface())
|
||||
)
|
||||
if err = gconv.Scan(metaData, &command); err != nil {
|
||||
return
|
||||
}
|
||||
root = gconv.Bool(metaData[tagNameRoot])
|
||||
// Name filed is necessary.
|
||||
if command.Name == "" {
|
||||
err = gerror.Newf(
|
||||
`command name cannot be empty, "name" tag not found in struct "%s"`,
|
||||
inputObject.Type().String(),
|
||||
)
|
||||
return
|
||||
}
|
||||
if command.Description == "" {
|
||||
command.Description = metaData[tagNameDc]
|
||||
}
|
||||
if command.Additional == "" {
|
||||
command.Additional = metaData[tagNameAd]
|
||||
}
|
||||
|
||||
if command.Options, err = newOptionsFromInput(inputObject.Interface()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create function that has value return.
|
||||
command.FuncWithValue = func(ctx context.Context, parser *Parser) (out interface{}, err error) {
|
||||
defer func() {
|
||||
if exception := recover(); exception != nil {
|
||||
if v, ok := exception.(error); ok && gerror.HasStack(v) {
|
||||
err = v
|
||||
} else {
|
||||
err = gerror.New(`exception recovered:` + gconv.String(exception))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
data = gconv.Map(parser.GetOptAll())
|
||||
inputValues = []reflect.Value{reflect.ValueOf(ctx)}
|
||||
)
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
err = mergeDefaultStructValue(data, inputObject.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Construct input parameters.
|
||||
if len(data) > 0 {
|
||||
|
||||
}
|
||||
if inputObject.Kind() == reflect.Ptr {
|
||||
err = gconv.Scan(data, inputObject.Interface())
|
||||
} else {
|
||||
err = gconv.Struct(data, inputObject.Addr().Interface())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Parameters validation.
|
||||
if err = gvalid.New().Bail().Data(inputObject.Interface()).Assoc(data).Run(ctx); err != nil {
|
||||
err = gerror.Current(err)
|
||||
return
|
||||
}
|
||||
inputValues = append(inputValues, inputObject)
|
||||
|
||||
// Call handler with dynamic created parameter values.
|
||||
results := method.Call(inputValues)
|
||||
out = results[0].Interface()
|
||||
if !results[1].IsNil() {
|
||||
if v, ok := results[1].Interface().(error); ok {
|
||||
err = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
|
||||
func mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
|
||||
tagFields, err := structs.TagFields(pointer, defaultValueTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tagFields) > 0 {
|
||||
var (
|
||||
foundKey string
|
||||
foundValue interface{}
|
||||
)
|
||||
for _, field := range tagFields {
|
||||
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
|
||||
if foundKey == "" {
|
||||
data[field.Name()] = field.TagValue
|
||||
} else {
|
||||
if utils.IsEmpty(foundValue) {
|
||||
data[foundKey] = field.TagValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOptionsFromInput(object interface{}) (options []Option, err error) {
|
||||
var (
|
||||
fields []structs.Field
|
||||
)
|
||||
fields, err = structs.Fields(structs.FieldsInput{
|
||||
Pointer: object,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
for _, field := range fields {
|
||||
var (
|
||||
option = Option{}
|
||||
metaData = field.TagMap()
|
||||
)
|
||||
if err = gconv.Scan(metaData, &option); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Name == "" {
|
||||
option.Name = field.Name()
|
||||
}
|
||||
options = append(options, option)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -12,22 +12,29 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Run calls custom function that bound to this command.
|
||||
func (c *Command) Run(ctx context.Context) error {
|
||||
_, err := c.RunWithValue(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// RunWithValue calls custom function that bound to this command with value output.
|
||||
func (c *Command) RunWithValue(ctx context.Context) (value interface{}, err error) {
|
||||
// Parse command arguments and options using default algorithm.
|
||||
parser, err := Parse(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
args := parser.GetArgAll()
|
||||
if len(args) == 1 {
|
||||
if c.HelpFunc != nil {
|
||||
return c.HelpFunc(ctx, parser)
|
||||
return nil, c.HelpFunc(ctx, parser)
|
||||
}
|
||||
return c.defaultHelpFunc(ctx, parser)
|
||||
return nil, c.defaultHelpFunc(ctx, parser)
|
||||
}
|
||||
|
||||
// Exclude the root binary name.
|
||||
@ -46,26 +53,32 @@ func (c *Command) Run(ctx context.Context) error {
|
||||
)
|
||||
c.Print()
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Command) doRun(ctx context.Context, parser *Parser) (err error) {
|
||||
func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{}, err error) {
|
||||
// Add built-in help option, just for info only.
|
||||
c.Options = append(c.Options, defaultHelpOption)
|
||||
// Check built-in help command.
|
||||
if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) {
|
||||
if c.HelpFunc != nil {
|
||||
return c.HelpFunc(ctx, parser)
|
||||
return nil, c.HelpFunc(ctx, parser)
|
||||
}
|
||||
return c.defaultHelpFunc(ctx, parser)
|
||||
return nil, c.defaultHelpFunc(ctx, parser)
|
||||
}
|
||||
// Reparse the arguments for current command configuration.
|
||||
parser, err = c.reParse(ctx, parser)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// Registered command function calling.
|
||||
return c.Func(ctx, parser)
|
||||
if c.Func != nil {
|
||||
return nil, c.Func(ctx, parser)
|
||||
}
|
||||
if c.FuncWithValue != nil {
|
||||
return c.FuncWithValue(ctx, parser)
|
||||
}
|
||||
return nil, gerror.New(`no function registered for current command`)
|
||||
}
|
||||
|
||||
// reParse re-parses the arguments using option configuration of current command.
|
||||
@ -80,11 +93,11 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error)
|
||||
)
|
||||
for _, option := range c.Options {
|
||||
if option.Short != "" {
|
||||
optionKey = fmt.Sprintf(`%s.%s`, option.Name, option.Short)
|
||||
optionKey = fmt.Sprintf(`%s,%s`, option.Name, option.Short)
|
||||
} else {
|
||||
optionKey = option.Name
|
||||
}
|
||||
supportedOptions[optionKey] = option.NeedValue
|
||||
supportedOptions[optionKey] = !option.Orphan
|
||||
}
|
||||
return Parse(supportedOptions)
|
||||
}
|
||||
|
||||
89
os/gcmd/gcmd_z_unit_feature_object_test.go
Normal file
89
os/gcmd/gcmd_z_unit_feature_object_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gcmd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
type TestCmdObject struct{}
|
||||
|
||||
type TestCmdObjectInput struct {
|
||||
g.Meta `root:"true" name:"root" usage:"root env/test" brief:"root env command" dc:"description" ad:"ad"`
|
||||
}
|
||||
type TestCmdObjectOutput struct{}
|
||||
|
||||
type TestCmdObjectEnvInput struct {
|
||||
g.Meta `name:"env" usage:"root env" brief:"root env command" dc:"root env command description" ad:"root env command ad"`
|
||||
}
|
||||
type TestCmdObjectEnvOutput struct{}
|
||||
|
||||
type TestCmdObjectTestInput struct {
|
||||
g.Meta `name:"test" usage:"root test" brief:"root test command" dc:"root test command description" ad:"root test command ad"`
|
||||
Name string `v:"required" short:"n" orphan:"false" brief:"name for test command"`
|
||||
}
|
||||
type TestCmdObjectTestOutput struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func (TestCmdObject) Root(ctx context.Context, in TestCmdObjectInput) (out *TestCmdObjectOutput, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (TestCmdObject) Env(ctx context.Context, in TestCmdObjectEnvInput) (out *TestCmdObjectEnvOutput, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (TestCmdObject) Test(ctx context.Context, in TestCmdObjectTestInput) (out *TestCmdObjectTestOutput, err error) {
|
||||
out = &TestCmdObjectTestOutput{
|
||||
Content: in.Name,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Test_Command_NewFromObject(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
cmd, err = gcmd.NewFromObject(&TestCmdObject{})
|
||||
)
|
||||
t.AssertNil(err)
|
||||
t.Assert(cmd.Name, "root")
|
||||
|
||||
os.Args = []string{"root", "test", "-n=john"}
|
||||
value, err := cmd.RunWithValue(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, `{"Content":"john"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Command_AddObject(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
command = gcmd.Command{
|
||||
Name: "start",
|
||||
}
|
||||
)
|
||||
err := command.AddObject(&TestCmdObject{})
|
||||
t.AssertNil(err)
|
||||
|
||||
os.Args = []string{"start", "root", "test", "-n=john"}
|
||||
value, err := command.RunWithValue(ctx)
|
||||
t.AssertNil(err)
|
||||
t.Assert(value, `{"Content":"john"}`)
|
||||
})
|
||||
}
|
||||
@ -101,16 +101,16 @@ gf get golang.org/x/sys
|
||||
`,
|
||||
Options: []gcmd.Option{
|
||||
{
|
||||
Name: "my-option",
|
||||
Short: "o",
|
||||
Brief: "It's my custom option",
|
||||
NeedValue: false,
|
||||
Name: "my-option",
|
||||
Short: "o",
|
||||
Brief: "It's my custom option",
|
||||
Orphan: true,
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
Short: "a",
|
||||
Brief: "It's my another custom option",
|
||||
NeedValue: false,
|
||||
Name: "another",
|
||||
Short: "a",
|
||||
Brief: "It's my another custom option",
|
||||
Orphan: true,
|
||||
},
|
||||
},
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
@ -118,7 +118,7 @@ gf get golang.org/x/sys
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err = commandRoot.Add(
|
||||
err = commandRoot.AddCommand(
|
||||
commandEnv,
|
||||
commandTest,
|
||||
)
|
||||
@ -153,7 +153,7 @@ Use 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has '
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandEnv); err != nil {
|
||||
if err = c.AddCommand(commandEnv); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
// get
|
||||
@ -172,7 +172,7 @@ gf get golang.org/x/sys
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandGet); err != nil {
|
||||
if err = c.AddCommand(commandGet); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
// build
|
||||
@ -212,7 +212,7 @@ gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./dock
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandBuild); err != nil {
|
||||
if err = c.AddCommand(commandBuild); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
c.Run(ctx)
|
||||
|
||||
@ -10,25 +10,33 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/command"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfsnotify"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheExpire = time.Minute // defaultCacheExpire is the expire time for file content caching in seconds.
|
||||
defaultCacheExpire = "1m" // defaultCacheExpire is the expire time for file content caching in seconds.
|
||||
commandEnvKeyForCache = "gf.gfile.cache" // commandEnvKeyForCache is the configuration key for command argument or environment configuring cache expire duration.
|
||||
)
|
||||
|
||||
var (
|
||||
// Default expire time for file content caching.
|
||||
cacheExpire = gcmd.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire).Duration()
|
||||
cacheExpire = getCacheExpire()
|
||||
|
||||
// internalCache is the memory cache for internal usage.
|
||||
internalCache = gcache.New()
|
||||
)
|
||||
|
||||
func getCacheExpire() time.Duration {
|
||||
d, err := time.ParseDuration(command.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// GetContentsWithCache returns string content of given file by `path` from cache.
|
||||
// If there's no content in the cache, it will read it from disk file specified by `path`.
|
||||
// The parameter `expire` specifies the caching time for this file content in seconds.
|
||||
|
||||
@ -20,11 +20,12 @@ package gtimer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/internal/command"
|
||||
)
|
||||
|
||||
// Timer is the timer manager, which uses ticks to calculate the timing interval.
|
||||
@ -47,15 +48,23 @@ const (
|
||||
StatusStopped = 2 // Job or Timer is stopped.
|
||||
StatusClosed = -1 // Job or Timer is closed and waiting to be deleted.
|
||||
panicExit = "exit" // panicExit is used for custom job exit with panic.
|
||||
defaultTimerInterval = 100 // defaultTimerInterval is the default timer interval in milliseconds.
|
||||
defaultTimerInterval = "100" // defaultTimerInterval is the default timer interval in milliseconds.
|
||||
commandEnvKeyForInterval = "gf.gtimer.interval" // commandEnvKeyForInterval is the key for command argument or environment configuring default interval duration for timer.
|
||||
)
|
||||
|
||||
var (
|
||||
defaultInterval = getDefaultInterval()
|
||||
defaultTimer = New()
|
||||
defaultInterval = gcmd.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval).Duration() * time.Millisecond
|
||||
)
|
||||
|
||||
func getDefaultInterval() time.Duration {
|
||||
n, err := strconv.Atoi(command.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return time.Duration(n) * time.Millisecond
|
||||
}
|
||||
|
||||
// DefaultOptions creates and returns a default options object for Timer creation.
|
||||
func DefaultOptions() TimerOptions {
|
||||
return TimerOptions{
|
||||
|
||||
@ -43,7 +43,7 @@ type ParameterRef struct {
|
||||
Value *Parameter
|
||||
}
|
||||
|
||||
func (oai *OpenApiV3) newParameterRefWithStructMethod(field *structs.Field, path, method string) (*ParameterRef, error) {
|
||||
func (oai *OpenApiV3) newParameterRefWithStructMethod(field structs.Field, path, method string) (*ParameterRef, error) {
|
||||
var (
|
||||
tagMap = field.TagMap()
|
||||
parameter = &Parameter{
|
||||
|
||||
@ -151,7 +151,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
}
|
||||
|
||||
// Normal unmarshalling interfaces checks.
|
||||
if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
// return v.UnmarshalValue(params)
|
||||
// }
|
||||
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
|
||||
if err, ok := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
|
||||
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
|
||||
return err
|
||||
}
|
||||
// Retrieve its element, may be struct at last.
|
||||
@ -177,7 +177,11 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
|
||||
// DO NOT use MapDeep here.
|
||||
paramsMap := Map(paramsInterface)
|
||||
if paramsMap == nil {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "convert params to map failed: %v", params)
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`convert params "%#v" to map failed`,
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
// It only performs one converting to the same attribute.
|
||||
|
||||
@ -142,10 +142,14 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
doDumpNumber(exportInternalInput)
|
||||
|
||||
case reflect.Chan:
|
||||
buffer.WriteString(`<chan>`)
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
|
||||
|
||||
case reflect.Func:
|
||||
buffer.WriteString(`<func>`)
|
||||
if reflectValue.IsNil() || !reflectValue.IsValid() {
|
||||
buffer.WriteString(`<nil>`)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
|
||||
}
|
||||
|
||||
default:
|
||||
doDumpDefault(exportInternalInput)
|
||||
|
||||
Reference in New Issue
Block a user