Files
gf/os/gcmd/gcmd_command_object.go
hailaz ee24da4e72 refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395)
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.

**Type alias migration to `any`:**

* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)

**Generated code and templates:**

* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)

**Container and utility updates:**

* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)

These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00

448 lines
13 KiB
Go

// 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"
"fmt"
"reflect"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
"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/gtag"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/util/gvalid"
)
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 any) (rootCmd *Command, err error) {
switch c := object.(type) {
case Command:
return &c, nil
case *Command:
return c, nil
}
originValueAndKind := reflection.OriginValueAndKind(object)
if originValueAndKind.OriginKind != reflect.Struct {
err = gerror.Newf(
`input object should be type of struct, but got "%s"`,
originValueAndKind.InputValue.Type().String(),
)
return
}
var reflectValue = originValueAndKind.InputValue
// If given `object` is not pointer, it then creates a temporary one,
// of which the value is `reflectValue`.
// It then can retrieve all the methods both of struct/*struct.
if reflectValue.Kind() == reflect.Struct {
newValue := reflect.New(reflectValue.Type())
newValue.Elem().Set(reflectValue)
reflectValue = newValue
}
// Root command creating.
rootCmd, err = newCommandFromObjectMeta(object, "")
if err != nil {
return
}
// Sub command creating.
var (
nameSet = gset.NewStrSet()
rootCommandName = gmeta.Get(object, gtag.Root).String()
subCommands []*Command
)
if rootCommandName == "" {
rootCommandName = rootCmd.Name
}
for i := 0; i < reflectValue.NumMethod(); i++ {
var (
method = reflectValue.Type().Method(i)
methodValue = reflectValue.Method(i)
methodType = methodValue.Type()
methodCmd *Command
)
methodCmd, err = newCommandFromMethod(object, method, methodValue, methodType)
if err != nil {
return
}
if nameSet.Contains(methodCmd.Name) {
err = gerror.Newf(
`command name should be unique, found duplicated command name in method "%s"`,
methodType.String(),
)
return
}
if rootCommandName == methodCmd.Name {
methodToRootCmdWhenNameEqual(rootCmd, methodCmd)
} else {
subCommands = append(subCommands, methodCmd)
}
}
if len(subCommands) > 0 {
err = rootCmd.AddCommand(subCommands...)
}
return
}
func methodToRootCmdWhenNameEqual(rootCmd *Command, methodCmd *Command) {
if rootCmd.Usage == "" {
rootCmd.Usage = methodCmd.Usage
}
if rootCmd.Brief == "" {
rootCmd.Brief = methodCmd.Brief
}
if rootCmd.Description == "" {
rootCmd.Description = methodCmd.Description
}
if rootCmd.Examples == "" {
rootCmd.Examples = methodCmd.Examples
}
if rootCmd.Func == nil {
rootCmd.Func = methodCmd.Func
}
if rootCmd.FuncWithValue == nil {
rootCmd.FuncWithValue = methodCmd.FuncWithValue
}
if rootCmd.HelpFunc == nil {
rootCmd.HelpFunc = methodCmd.HelpFunc
}
if len(rootCmd.Arguments) == 0 {
rootCmd.Arguments = methodCmd.Arguments
}
if !rootCmd.Strict {
rootCmd.Strict = methodCmd.Strict
}
if rootCmd.Config == "" {
rootCmd.Config = methodCmd.Config
}
}
// The `object` is the Meta attribute from business object, and the `name` is the command name,
// commonly from method name, which is used when no name tag is defined in Meta.
func newCommandFromObjectMeta(object any, name string) (command *Command, err error) {
var metaData = gmeta.Data(object)
if err = gconv.Scan(metaData, &command); err != nil {
return
}
// Name field is necessary.
if command.Name == "" {
if name == "" {
err = gerror.Newf(
`command name cannot be empty, "name" tag not found in meta of struct "%s"`,
reflect.TypeOf(object).String(),
)
return
}
command.Name = name
}
if command.Brief == "" {
for _, tag := range []string{gtag.Summary, gtag.SummaryShort, gtag.SummaryShort2} {
command.Brief = metaData[tag]
if command.Brief != "" {
break
}
}
}
if command.Description == "" {
command.Description = metaData[gtag.DescriptionShort]
}
if command.Brief == "" && command.Description != "" {
command.Brief = command.Description
command.Description = ""
}
if command.Examples == "" {
command.Examples = metaData[gtag.ExampleShort]
}
if command.Additional == "" {
command.Additional = metaData[gtag.AdditionalShort]
}
return
}
func newCommandFromMethod(
object any, method reflect.Method, methodValue reflect.Value, methodType reflect.Type,
) (command *Command, err error) {
// Necessary validation for input/output parameters and naming.
if methodType.NumIn() != 2 || methodType.NumOut() != 2 {
if methodType.PkgPath() != "" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
methodType.PkgPath(), reflect.TypeOf(object).Name(), method.Name, methodType.String(),
)
} else {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: %s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
reflect.TypeOf(object).Name(), method.Name, methodType.String(),
)
}
return
}
if !methodType.In(0).Implements(reflect.TypeOf((*context.Context)(nil)).Elem()) {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: %s.%s defined as "%s", but the first input parameter should be type of "context.Context"`,
reflect.TypeOf(object).Name(), method.Name, methodType.String(),
)
return
}
if !methodType.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: %s.%s defined as "%s", but the last output parameter should be type of "error"`,
reflect.TypeOf(object).Name(), method.Name, methodType.String(),
)
return
}
// The input struct should be named as `xxxInput`.
if !gstr.HasSuffix(methodType.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"`,
methodType.In(1).String(),
)
return
}
// The output struct should be named as `xxxOutput`.
if !gstr.HasSuffix(methodType.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"`,
methodType.Out(0).String(),
)
return
}
var inputObject reflect.Value
if methodType.In(1).Kind() == reflect.Pointer {
inputObject = reflect.New(methodType.In(1).Elem()).Elem()
} else {
inputObject = reflect.New(methodType.In(1)).Elem()
}
// Command creating.
if command, err = newCommandFromObjectMeta(inputObject.Interface(), method.Name); err != nil {
return
}
// Options creating.
if command.Arguments, err = newArgumentsFromInput(inputObject.Interface()); err != nil {
return
}
// For input struct converting using priority tag.
var priorityTag = gstr.Join([]string{tagNameName, tagNameShort}, ",")
// =============================================================================================
// Create function that has value return.
// =============================================================================================
command.FuncWithValue = func(ctx context.Context, parser *Parser) (out any, err error) {
ctx = context.WithValue(ctx, CtxKeyParser, parser)
var (
data = gconv.Map(parser.GetOptAll())
argIndex = 0
arguments = parser.GetArgAll()
inputValues = []reflect.Value{reflect.ValueOf(ctx)}
)
if value := ctx.Value(CtxKeyArgumentsIndex); value != nil {
argIndex = value.(int)
}
if data == nil {
data = map[string]any{}
}
// Handle orphan options.
for _, arg := range command.Arguments {
if arg.IsArg {
// Read argument from command line index.
if argIndex < len(arguments) {
data[arg.Name] = arguments[argIndex]
argIndex++
}
} else {
// Read argument from command line option name.
if arg.Orphan {
if orphanValue := parser.GetOpt(arg.Name); orphanValue != nil {
if orphanValue.String() == "" {
// Example: gf -f
data[arg.Name] = "true"
if arg.Short != "" {
data[arg.Short] = "true"
}
} else {
// Adapter with common user habits.
// Eg:
// `gf -f=0`: which parameter `f` is parsed as false
// `gf -f=1`: which parameter `f` is parsed as true
data[arg.Name] = orphanValue.Bool()
}
}
}
}
}
// Default values from struct tag.
if err = mergeDefaultStructValue(data, inputObject.Interface()); err != nil {
return nil, err
}
// Construct input parameters.
if len(data) > 0 {
intlog.PrintFunc(ctx, func() string {
return fmt.Sprintf(`input command data map: %s`, gjson.MustEncode(data))
})
if inputObject.Kind() == reflect.Pointer {
err = gconv.StructTag(data, inputObject.Interface(), priorityTag)
} else {
err = gconv.StructTag(data, inputObject.Addr().Interface(), priorityTag)
}
intlog.PrintFunc(ctx, func() string {
return fmt.Sprintf(`input object assigned data: %s`, gjson.MustEncode(inputObject.Interface()))
})
if err != nil {
return
}
}
// Parameters validation.
if err = gvalid.New().Bail().Data(inputObject.Interface()).Assoc(data).Run(ctx); err != nil {
err = gerror.Wrapf(gerror.Current(err), `arguments validation failed for command "%s"`, command.Name)
return
}
inputValues = append(inputValues, inputObject)
// Call handler with dynamic created parameter values.
results := methodValue.Call(inputValues)
out = results[0].Interface()
if !results[1].IsNil() {
if v, ok := results[1].Interface().(error); ok {
err = v
}
}
return
}
return
}
func newArgumentsFromInput(object any) (args []Argument, err error) {
var (
fields []gstructs.Field
nameSet = gset.NewStrSet()
shortSet = gset.NewStrSet()
)
fields, err = gstructs.Fields(gstructs.FieldsInput{
Pointer: object,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
})
for _, field := range fields {
var (
arg = Argument{}
metaData = field.TagMap()
)
if err = gconv.Scan(metaData, &arg); err != nil {
return nil, err
}
if arg.Name == "" {
arg.Name = field.Name()
}
if arg.Name == helpOptionName {
return nil, gerror.Newf(
`argument name "%s" defined in "%s.%s" is already token by built-in arguments`,
arg.Name, reflect.TypeOf(object).String(), field.Name(),
)
}
if arg.Short == helpOptionNameShort {
return nil, gerror.Newf(
`short argument name "%s" defined in "%s.%s" is already token by built-in arguments`,
arg.Short, reflect.TypeOf(object).String(), field.Name(),
)
}
if arg.Brief == "" {
arg.Brief = field.TagDescription()
}
if arg.Default == "" {
arg.Default = field.TagDefault()
}
if v, ok := metaData[gtag.Arg]; ok {
arg.IsArg = gconv.Bool(v)
}
if nameSet.Contains(arg.Name) {
return nil, gerror.Newf(
`argument name "%s" defined in "%s.%s" is already token by other argument`,
arg.Name, reflect.TypeOf(object).String(), field.Name(),
)
}
nameSet.Add(arg.Name)
if arg.Short != "" {
if shortSet.Contains(arg.Short) {
return nil, gerror.Newf(
`short argument name "%s" defined in "%s.%s" is already token by other argument`,
arg.Short, reflect.TypeOf(object).String(), field.Name(),
)
}
shortSet.Add(arg.Short)
}
args = append(args, arg)
}
return
}
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
func mergeDefaultStructValue(data map[string]any, pointer any) error {
tagFields, err := gstructs.TagFields(pointer, defaultValueTags)
if err != nil {
return err
}
if len(tagFields) > 0 {
var (
foundKey string
foundValue any
)
for _, field := range tagFields {
var (
nameValue = field.Tag(tagNameName)
shortValue = field.Tag(tagNameShort)
)
// If it already has value, it then ignores the default value.
if value, ok := data[nameValue]; ok {
data[field.Name()] = value
continue
}
if value, ok := data[shortValue]; ok {
data[field.Name()] = value
continue
}
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
}