mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
Merge branch 'master' into feature/recursive-validation
This commit is contained in:
@ -33,30 +33,38 @@ func Init(args ...string) {
|
||||
defaultParsedOptions = make(map[string]string)
|
||||
}
|
||||
// Parsing os.Args with default algorithm.
|
||||
defaultParsedArgs, defaultParsedOptions = ParseUsingDefaultAlgorithm(args...)
|
||||
}
|
||||
|
||||
// ParseUsingDefaultAlgorithm parses arguments using default algorithm.
|
||||
func ParseUsingDefaultAlgorithm(args ...string) (parsedArgs []string, parsedOptions map[string]string) {
|
||||
parsedArgs = make([]string, 0)
|
||||
parsedOptions = make(map[string]string)
|
||||
for i := 0; i < len(args); {
|
||||
array := argumentRegex.FindStringSubmatch(args[i])
|
||||
if len(array) > 2 {
|
||||
if array[2] == "=" {
|
||||
defaultParsedOptions[array[1]] = array[3]
|
||||
parsedOptions[array[1]] = array[3]
|
||||
} else if i < len(args)-1 {
|
||||
if len(args[i+1]) > 0 && args[i+1][0] == '-' {
|
||||
// Eg: gf gen -d -n 1
|
||||
defaultParsedOptions[array[1]] = array[3]
|
||||
parsedOptions[array[1]] = array[3]
|
||||
} else {
|
||||
// Eg: gf gen -n 2
|
||||
defaultParsedOptions[array[1]] = args[i+1]
|
||||
parsedOptions[array[1]] = args[i+1]
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Eg: gf gen -h
|
||||
defaultParsedOptions[array[1]] = array[3]
|
||||
parsedOptions[array[1]] = array[3]
|
||||
}
|
||||
} else {
|
||||
defaultParsedArgs = append(defaultParsedArgs, args[i])
|
||||
parsedArgs = append(parsedArgs, args[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOpt returns the option value named `name`.
|
||||
|
||||
@ -119,7 +119,7 @@ func TestCache_UpdateExpire(t *testing.T) {
|
||||
newExpire := 10 * time.Second
|
||||
oldExpire2, err := gcache.UpdateExpire(ctx, key, newExpire)
|
||||
t.AssertNil(err)
|
||||
t.Assert(oldExpire2, oldExpire)
|
||||
t.AssertIN(oldExpire2, g.Slice{oldExpire, `2.999s`})
|
||||
|
||||
e, _ := gcache.GetExpire(ctx, key)
|
||||
t.AssertNE(e, oldExpire)
|
||||
|
||||
@ -16,8 +16,10 @@ import (
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultCommandFuncMap = make(map[string]func())
|
||||
const (
|
||||
helpOptionName = "help"
|
||||
helpOptionNameShort = "h"
|
||||
maxLineChars = 120
|
||||
)
|
||||
|
||||
// Init does custom initialization.
|
||||
|
||||
68
os/gcmd/gcmd_command.go
Normal file
68
os/gcmd/gcmd_command.go
Normal file
@ -0,0 +1,68 @@
|
||||
// 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"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
// Function is a custom command callback function that is bound to a certain argument.
|
||||
type Function func(ctx context.Context, parser *Parser) (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.
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
// Add adds one or more sub-commands to current command.
|
||||
func (c *Command) Add(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 {
|
||||
return gerror.New("command function should not be empty")
|
||||
}
|
||||
cmd.parent = c
|
||||
c.commands = append(c.commands, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
131
os/gcmd/gcmd_command_help.go
Normal file
131
os/gcmd/gcmd_command_help.go
Normal file
@ -0,0 +1,131 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Print prints help info to stdout for current command.
|
||||
func (c *Command) Print() {
|
||||
prefix := gstr.Repeat(" ", 4)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
// Usage.
|
||||
if c.Usage != "" || c.Name != "" {
|
||||
buffer.WriteString("USAGE\n")
|
||||
buffer.WriteString(prefix)
|
||||
if c.Usage != "" {
|
||||
buffer.WriteString(c.Usage)
|
||||
} else {
|
||||
var (
|
||||
p = c
|
||||
name = c.Name
|
||||
)
|
||||
for p.parent != nil {
|
||||
name = p.parent.Name + " " + name
|
||||
p = p.parent
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(`%s ARGUMENT [OPTION]`, name))
|
||||
}
|
||||
buffer.WriteString("\n\n")
|
||||
}
|
||||
// Command.
|
||||
if len(c.commands) > 0 {
|
||||
buffer.WriteString("COMMAND\n")
|
||||
var (
|
||||
maxSpaceLength = 0
|
||||
)
|
||||
for _, cmd := range c.commands {
|
||||
if len(cmd.Name) > maxSpaceLength {
|
||||
maxSpaceLength = len(cmd.Name)
|
||||
}
|
||||
}
|
||||
for _, cmd := range c.commands {
|
||||
// Add "..." to brief for those commands that also have sub-commands.
|
||||
if len(cmd.commands) > 0 {
|
||||
cmd.Brief = gstr.TrimRight(cmd.Brief, ".") + "..."
|
||||
}
|
||||
var (
|
||||
spaceLength = maxSpaceLength - len(cmd.Name)
|
||||
lineStr = fmt.Sprintf("%s%s%s%s\n", prefix, cmd.Name, gstr.Repeat(" ", spaceLength+4), cmd.Brief)
|
||||
wordwrapPrefix = gstr.Repeat(" ", len(prefix+cmd.Name)+spaceLength+4)
|
||||
)
|
||||
lineStr = gstr.WordWrap(lineStr, maxLineChars, "\n"+wordwrapPrefix)
|
||||
buffer.WriteString(lineStr)
|
||||
}
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
// Option.
|
||||
if len(c.Options) > 0 {
|
||||
buffer.WriteString("OPTION\n")
|
||||
var (
|
||||
nameStr string
|
||||
maxSpaceLength = 0
|
||||
)
|
||||
for _, option := range c.Options {
|
||||
if option.Short != "" {
|
||||
nameStr = fmt.Sprintf("-%s,\t--%s", option.Short, option.Name)
|
||||
} else {
|
||||
nameStr = fmt.Sprintf("-/--%s", option.Name)
|
||||
}
|
||||
if len(nameStr) > maxSpaceLength {
|
||||
maxSpaceLength = len(nameStr)
|
||||
}
|
||||
}
|
||||
for _, option := range c.Options {
|
||||
if option.Short != "" {
|
||||
nameStr = fmt.Sprintf("-%s,\t--%s", option.Short, option.Name)
|
||||
} else {
|
||||
nameStr = fmt.Sprintf("-/--%s", option.Name)
|
||||
}
|
||||
var (
|
||||
spaceLength = maxSpaceLength - len(nameStr)
|
||||
lineStr = fmt.Sprintf("%s%s%s%s\n", prefix, nameStr, gstr.Repeat(" ", spaceLength+4), option.Brief)
|
||||
wordwrapPrefix = gstr.Repeat(" ", len(prefix+nameStr)+spaceLength+4)
|
||||
)
|
||||
lineStr = gstr.WordWrap(lineStr, maxLineChars, "\n"+wordwrapPrefix)
|
||||
buffer.WriteString(lineStr)
|
||||
}
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
// Example.
|
||||
if c.Examples != "" {
|
||||
buffer.WriteString("EXAMPLE\n")
|
||||
buffer.WriteString(prefix)
|
||||
buffer.WriteString(gstr.WordWrap(gstr.Trim(c.Examples), maxLineChars, "\n"+prefix))
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
// Description.
|
||||
if c.Description != "" {
|
||||
buffer.WriteString("DESCRIPTION\n")
|
||||
buffer.WriteString(prefix)
|
||||
buffer.WriteString(gstr.WordWrap(gstr.Trim(c.Description), maxLineChars, "\n"+prefix))
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
// Additional.
|
||||
if c.Additional != "" {
|
||||
lineStr := gstr.WordWrap(gstr.Trim(c.Additional), maxLineChars, "\n")
|
||||
buffer.WriteString(lineStr)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
fmt.Println(buffer.String())
|
||||
}
|
||||
|
||||
func (c *Command) defaultHelpFunc(ctx context.Context, parser *Parser) error {
|
||||
c.Print()
|
||||
return nil
|
||||
}
|
||||
107
os/gcmd/gcmd_command_run.go
Normal file
107
os/gcmd/gcmd_command_run.go
Normal file
@ -0,0 +1,107 @@
|
||||
// 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"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Run calls custom function that bound to this command.
|
||||
func (c *Command) Run(ctx context.Context) error {
|
||||
// Parse command arguments and options using default algorithm.
|
||||
parser, err := Parse(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := parser.GetArgAll()
|
||||
if len(args) == 1 {
|
||||
if c.HelpFunc != nil {
|
||||
return c.HelpFunc(ctx, parser)
|
||||
}
|
||||
return c.defaultHelpFunc(ctx, parser)
|
||||
}
|
||||
|
||||
// Exclude the root binary name.
|
||||
args = args[1:]
|
||||
|
||||
// Find the matched command and run it.
|
||||
if subCommand := c.searchCommand(args); subCommand != nil {
|
||||
return subCommand.doRun(ctx, parser)
|
||||
}
|
||||
|
||||
// Print error and help command if no command found.
|
||||
fmt.Printf(
|
||||
"ERROR: command \"%s\" not found for arguments \"%s\"\n",
|
||||
gstr.Join(args, " "),
|
||||
gstr.Join(os.Args, " "),
|
||||
)
|
||||
c.Print()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Command) doRun(ctx context.Context, parser *Parser) (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 c.defaultHelpFunc(ctx, parser)
|
||||
}
|
||||
// Reparse the arguments for current command configuration.
|
||||
parser, err = c.reParse(ctx, parser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Registered command function calling.
|
||||
return c.Func(ctx, parser)
|
||||
}
|
||||
|
||||
// reParse re-parses the arguments using option configuration of current command.
|
||||
func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error) {
|
||||
// It seems just has built-in help option, it so does nothing.
|
||||
if len(c.Options) == 1 {
|
||||
return parser, nil
|
||||
}
|
||||
var (
|
||||
optionKey string
|
||||
supportedOptions = make(map[string]bool)
|
||||
)
|
||||
for _, option := range c.Options {
|
||||
if option.Short != "" {
|
||||
optionKey = fmt.Sprintf(`%s.%s`, option.Name, option.Short)
|
||||
} else {
|
||||
optionKey = option.Name
|
||||
}
|
||||
supportedOptions[optionKey] = option.NeedValue
|
||||
}
|
||||
return Parse(supportedOptions)
|
||||
}
|
||||
|
||||
// searchCommand recursively searches the command according given arguments.
|
||||
func (c *Command) searchCommand(args []string) *Command {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, cmd := range c.commands {
|
||||
if cmd.Name == args[0] {
|
||||
leftArgs := args[1:]
|
||||
if len(leftArgs) == 0 {
|
||||
return &cmd
|
||||
}
|
||||
return cmd.searchCommand(leftArgs)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
// 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 (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// BindHandle registers callback function `f` with `cmd`.
|
||||
func BindHandle(cmd string, f func()) error {
|
||||
if _, ok := defaultCommandFuncMap[cmd]; ok {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "duplicated handle for command:"+cmd)
|
||||
} else {
|
||||
defaultCommandFuncMap[cmd] = f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindHandleMap registers callback function with map `m`.
|
||||
func BindHandleMap(m map[string]func()) error {
|
||||
var err error
|
||||
for k, v := range m {
|
||||
if err = BindHandle(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RunHandle executes the callback function registered by `cmd`.
|
||||
func RunHandle(cmd string) error {
|
||||
if handle, ok := defaultCommandFuncMap[cmd]; ok {
|
||||
handle()
|
||||
} else {
|
||||
return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoRun automatically recognizes and executes the callback function
|
||||
// by value of index 0 (the first console parameter).
|
||||
func AutoRun() error {
|
||||
if cmd := GetArg(1); !cmd.IsEmpty() {
|
||||
if handle, ok := defaultCommandFuncMap[cmd.String()]; ok {
|
||||
handle()
|
||||
} else {
|
||||
return gerror.NewCode(gcode.CodeMissingConfiguration, "no handle found for command:"+cmd.String())
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCode(gcode.CodeMissingParameter, "no command found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/command"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@ -25,7 +26,7 @@ type Parser struct {
|
||||
parsedArgs []string // As name described.
|
||||
parsedOptions map[string]string // As name described.
|
||||
passedOptions map[string]bool // User passed supported options.
|
||||
supportedOptions map[string]bool // Option [option name : need argument].
|
||||
supportedOptions map[string]bool // Option [OptionName:WhetherNeedArgument].
|
||||
commandFuncMap map[string]func() // Command function map for function handler.
|
||||
}
|
||||
|
||||
@ -36,6 +37,13 @@ type Parser struct {
|
||||
//
|
||||
// The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed.
|
||||
func Parse(supportedOptions map[string]bool, strict ...bool) (*Parser, error) {
|
||||
if supportedOptions == nil {
|
||||
command.Init(os.Args...)
|
||||
return &Parser{
|
||||
parsedArgs: GetArgAll(),
|
||||
parsedOptions: GetOptAll(),
|
||||
}, nil
|
||||
}
|
||||
return ParseWithArgs(os.Args, supportedOptions, strict...)
|
||||
}
|
||||
|
||||
@ -46,6 +54,13 @@ func Parse(supportedOptions map[string]bool, strict ...bool) (*Parser, error) {
|
||||
//
|
||||
// The optional parameter `strict` specifies whether stops parsing and returns error if invalid option passed.
|
||||
func ParseWithArgs(args []string, supportedOptions map[string]bool, strict ...bool) (*Parser, error) {
|
||||
if supportedOptions == nil {
|
||||
command.Init(args...)
|
||||
return &Parser{
|
||||
parsedArgs: GetArgAll(),
|
||||
parsedOptions: GetOptAll(),
|
||||
}, nil
|
||||
}
|
||||
strictParsing := false
|
||||
if len(strict) > 0 {
|
||||
strictParsing = strict[0]
|
||||
@ -86,7 +101,7 @@ func ParseWithArgs(args []string, supportedOptions map[string]bool, strict ...bo
|
||||
}
|
||||
} else {
|
||||
// Multiple options?
|
||||
if array := parser.parseMultiOption(option); len(array) > 0 {
|
||||
if array = parser.parseMultiOption(option); len(array) > 0 {
|
||||
for _, v := range array {
|
||||
parser.setOptionValue(v, "")
|
||||
}
|
||||
|
||||
@ -9,10 +9,13 @@
|
||||
package gcmd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/os/genv"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
@ -67,3 +70,151 @@ func Test_GetWithEnv(t *testing.T) {
|
||||
t.Assert(gcmd.GetOptWithEnv("test"), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Command(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
err error
|
||||
)
|
||||
commandRoot := &gcmd.Command{
|
||||
Name: "gf",
|
||||
}
|
||||
// env
|
||||
commandEnv := gcmd.Command{
|
||||
Name: "env",
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
fmt.Println("env")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
// test
|
||||
commandTest := gcmd.Command{
|
||||
Name: "test",
|
||||
Brief: "test brief",
|
||||
Description: "test description current Golang environment variables",
|
||||
Examples: `
|
||||
gf get github.com/gogf/gf
|
||||
gf get github.com/gogf/gf@latest
|
||||
gf get github.com/gogf/gf@master
|
||||
gf get golang.org/x/sys
|
||||
`,
|
||||
Options: []gcmd.Option{
|
||||
{
|
||||
Name: "my-option",
|
||||
Short: "o",
|
||||
Brief: "It's my custom option",
|
||||
NeedValue: false,
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
Short: "a",
|
||||
Brief: "It's my another custom option",
|
||||
NeedValue: false,
|
||||
},
|
||||
},
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
fmt.Println("test")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err = commandRoot.Add(
|
||||
commandEnv,
|
||||
commandTest,
|
||||
)
|
||||
if err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
|
||||
if err = commandRoot.Run(ctx); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Command_Print(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
err error
|
||||
)
|
||||
c := &gcmd.Command{
|
||||
Name: "gf",
|
||||
Description: `GoFrame Command Line Interface, which is your helpmate for building GoFrame application with convenience.`,
|
||||
Additional: `
|
||||
Use 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has '...' in the tail of their comments.`,
|
||||
}
|
||||
// env
|
||||
commandEnv := gcmd.Command{
|
||||
Name: "env",
|
||||
Brief: "show current Golang environment variables, long brief.long brief.long brief.long brief.long brief.long brief.long brief.long brief.",
|
||||
Description: "show current Golang environment variables",
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandEnv); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
// get
|
||||
commandGet := gcmd.Command{
|
||||
Name: "get",
|
||||
Brief: "install or update GF to system in default...",
|
||||
Description: "show current Golang environment variables",
|
||||
|
||||
Examples: `
|
||||
gf get github.com/gogf/gf
|
||||
gf get github.com/gogf/gf@latest
|
||||
gf get github.com/gogf/gf@master
|
||||
gf get golang.org/x/sys
|
||||
`,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandGet); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
// build
|
||||
//-n, --name output binary name
|
||||
//-v, --version output binary version
|
||||
//-a, --arch output binary architecture, multiple arch separated with ','
|
||||
//-s, --system output binary system, multiple os separated with ','
|
||||
//-o, --output output binary path, used when building single binary file
|
||||
//-p, --path output binary directory path, default is './bin'
|
||||
//-e, --extra extra custom "go build" options
|
||||
//-m, --mod like "-mod" option of "go build", use "-m none" to disable go module
|
||||
//-c, --cgo enable or disable cgo feature, it's disabled in default
|
||||
|
||||
commandBuild := gcmd.Command{
|
||||
Name: "build",
|
||||
Usage: "gf build FILE [OPTION]",
|
||||
Brief: "cross-building go project for lots of platforms...",
|
||||
Description: `
|
||||
The "build" command is most commonly used command, which is designed as a powerful wrapper for
|
||||
"go build" command for convenience cross-compiling usage.
|
||||
It provides much more features for building binary:
|
||||
1. Cross-Compiling for many platforms and architectures.
|
||||
2. Configuration file support for compiling.
|
||||
3. Build-In Variables.
|
||||
`,
|
||||
Examples: `
|
||||
gf build main.go
|
||||
gf build main.go --swagger
|
||||
gf build main.go --pack public,template
|
||||
gf build main.go --cgo
|
||||
gf build main.go -m none
|
||||
gf build main.go -n my-app -a all -s all
|
||||
gf build main.go -n my-app -a amd64,386 -s linux -p .
|
||||
gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin
|
||||
`,
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
if err = c.Add(commandBuild); err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
c.Run(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ func getHomePath() (string, error) {
|
||||
if nil == err {
|
||||
return u.HomeDir, nil
|
||||
}
|
||||
if "windows" == runtime.GOOS {
|
||||
if runtime.GOOS == "windows" {
|
||||
return homeWindows()
|
||||
}
|
||||
return homeUnix()
|
||||
|
||||
43
os/gfile/gfile_z_example_cache_test.go
Normal file
43
os/gfile/gfile_z_example_cache_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleGetContentsWithCache() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_cache")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// It reads the file content with cache duration of one minute,
|
||||
// which means it reads from cache after then without any IO operations within on minute.
|
||||
fmt.Println(gfile.GetContentsWithCache(tempFile, time.Minute))
|
||||
|
||||
// write new contents will clear its cache
|
||||
gfile.PutContents(tempFile, "new goframe example content")
|
||||
|
||||
// There's some delay for cache clearing after file content change.
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContentsWithCache(tempFile))
|
||||
|
||||
// May Output:
|
||||
// goframe example content
|
||||
// new goframe example content
|
||||
}
|
||||
240
os/gfile/gfile_z_example_contents_test.go
Normal file
240
os/gfile/gfile_z_example_contents_test.go
Normal file
@ -0,0 +1,240 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleGetContents() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// It reads and returns the file content as string.
|
||||
// It returns empty string if it fails reading, for example, with permission or IO error.
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
}
|
||||
|
||||
func ExampleGetBytes() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// It reads and returns the file content as []byte.
|
||||
// It returns nil if it fails reading, for example, with permission or IO error.
|
||||
fmt.Println(gfile.GetBytes(tempFile))
|
||||
|
||||
// Output:
|
||||
// [103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116]
|
||||
}
|
||||
|
||||
func ExamplePutContents() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// It creates and puts content string into specifies file path.
|
||||
// It automatically creates directory recursively if it does not exist.
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
}
|
||||
|
||||
func ExamplePutBytes() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutBytes(tempFile, []byte("goframe example content"))
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
}
|
||||
|
||||
func ExamplePutContentsAppend() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// It creates and append content string into specifies file path.
|
||||
// It automatically creates directory recursively if it does not exist.
|
||||
gfile.PutContentsAppend(tempFile, " append content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
// goframe example content append content
|
||||
}
|
||||
|
||||
func ExamplePutBytesAppend() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// write contents
|
||||
gfile.PutBytesAppend(tempFile, []byte(" append"))
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
// goframe example content append
|
||||
}
|
||||
|
||||
func ExampleGetNextCharOffsetByPath() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
index := gfile.GetNextCharOffsetByPath(tempFile, 'f', 0)
|
||||
fmt.Println(index)
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleGetBytesTilCharByPath() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetBytesTilCharByPath(tempFile, 'f', 0))
|
||||
|
||||
// Output:
|
||||
// [103 111 102] 2
|
||||
}
|
||||
|
||||
func ExampleGetBytesByTwoOffsetsByPath() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 7))
|
||||
|
||||
// Output:
|
||||
// [103 111 102 114 97 109 101]
|
||||
}
|
||||
|
||||
func ExampleReadLines() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content")
|
||||
|
||||
// read contents
|
||||
gfile.ReadLines(tempFile, func(text string) error {
|
||||
// Process each line
|
||||
fmt.Println(text)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Output:
|
||||
// L1 goframe example content
|
||||
// L2 goframe example content
|
||||
}
|
||||
|
||||
func ExampleReadLinesBytes() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_content")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content")
|
||||
|
||||
// read contents
|
||||
gfile.ReadLinesBytes(tempFile, func(bytes []byte) error {
|
||||
// Process each line
|
||||
fmt.Println(bytes)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Output:
|
||||
// [76 49 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116]
|
||||
// [76 50 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116]
|
||||
}
|
||||
52
os/gfile/gfile_z_example_copy_test.go
Normal file
52
os/gfile/gfile_z_example_copy_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleCopy() {
|
||||
// init
|
||||
var (
|
||||
srcFileName = "gflie_example.txt"
|
||||
srcTempDir = gfile.TempDir("gfile_example_copy_src")
|
||||
srcTempFile = gfile.Join(srcTempDir, srcFileName)
|
||||
|
||||
// copy file
|
||||
dstFileName = "gflie_example_copy.txt"
|
||||
dstTempFile = gfile.Join(srcTempDir, dstFileName)
|
||||
|
||||
// copy dir
|
||||
dstTempDir = gfile.TempDir("gfile_example_copy_dst")
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(srcTempFile, "goframe example copy")
|
||||
|
||||
// copy file
|
||||
gfile.Copy(srcTempFile, dstTempFile)
|
||||
|
||||
// read contents after copy file
|
||||
fmt.Println(gfile.GetContents(dstTempFile))
|
||||
|
||||
// copy dir
|
||||
gfile.Copy(srcTempDir, dstTempDir)
|
||||
|
||||
// list copy dir file
|
||||
fList, _ := gfile.ScanDir(dstTempDir, "*", false)
|
||||
for _, v := range fList {
|
||||
fmt.Println(gfile.Basename(v))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// goframe example copy
|
||||
// gflie_example.txt
|
||||
// gflie_example_copy.txt
|
||||
}
|
||||
22
os/gfile/gfile_z_example_home_test.go
Normal file
22
os/gfile/gfile_z_example_home_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleHome() {
|
||||
// user's home directory
|
||||
homePath, _ := gfile.Home()
|
||||
fmt.Println(homePath)
|
||||
|
||||
// May Output:
|
||||
// C:\Users\hailaz
|
||||
}
|
||||
120
os/gfile/gfile_z_example_replace_test.go
Normal file
120
os/gfile/gfile_z_example_replace_test.go
Normal file
@ -0,0 +1,120 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleReplaceFile() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_replace")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// It replaces content directly by file path.
|
||||
gfile.ReplaceFile("content", "replace word", tempFile)
|
||||
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
// goframe example replace word
|
||||
}
|
||||
|
||||
func ExampleReplaceFileFunc() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_replace")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example 123")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// It replaces content directly by file path and callback function.
|
||||
gfile.ReplaceFileFunc(func(path, content string) string {
|
||||
// Replace with regular match
|
||||
reg, _ := regexp.Compile(`\d{3}`)
|
||||
return reg.ReplaceAllString(content, "[num]")
|
||||
}, tempFile)
|
||||
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example 123
|
||||
// goframe example [num]
|
||||
}
|
||||
|
||||
func ExampleReplaceDir() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_replace")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// It replaces content of all files under specified directory recursively.
|
||||
gfile.ReplaceDir("content", "replace word", tempDir, "gflie_example.txt", true)
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example content
|
||||
// goframe example replace word
|
||||
}
|
||||
|
||||
func ExampleReplaceDirFunc() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_replace")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example 123")
|
||||
|
||||
// read contents
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// It replaces content of all files under specified directory with custom callback function recursively.
|
||||
gfile.ReplaceDirFunc(func(path, content string) string {
|
||||
// Replace with regular match
|
||||
reg, _ := regexp.Compile(`\d{3}`)
|
||||
return reg.ReplaceAllString(content, "[num]")
|
||||
}, tempDir, "gflie_example.txt", true)
|
||||
|
||||
fmt.Println(gfile.GetContents(tempFile))
|
||||
|
||||
// Output:
|
||||
// goframe example 123
|
||||
// goframe example [num]
|
||||
|
||||
}
|
||||
133
os/gfile/gfile_z_example_scan_test.go
Normal file
133
os/gfile/gfile_z_example_scan_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleScanDir() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_scan_dir")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
|
||||
tempSubDir = gfile.Join(tempDir, "sub_dir")
|
||||
tempSubFile = gfile.Join(tempSubDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
gfile.PutContents(tempSubFile, "goframe example content")
|
||||
|
||||
// scans directory recursively
|
||||
list, _ := gfile.ScanDir(tempDir, "*", true)
|
||||
for _, v := range list {
|
||||
fmt.Println(gfile.Basename(v))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// gflie_example.txt
|
||||
// sub_dir
|
||||
// gflie_example.txt
|
||||
}
|
||||
|
||||
func ExampleScanDirFile() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_scan_dir_file")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
|
||||
tempSubDir = gfile.Join(tempDir, "sub_dir")
|
||||
tempSubFile = gfile.Join(tempSubDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
gfile.PutContents(tempSubFile, "goframe example content")
|
||||
|
||||
// scans directory recursively exclusive of directories
|
||||
list, _ := gfile.ScanDirFile(tempDir, "*.txt", true)
|
||||
for _, v := range list {
|
||||
fmt.Println(gfile.Basename(v))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// gflie_example.txt
|
||||
// gflie_example.txt
|
||||
}
|
||||
|
||||
func ExampleScanDirFunc() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_scan_dir_func")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
|
||||
tempSubDir = gfile.Join(tempDir, "sub_dir")
|
||||
tempSubFile = gfile.Join(tempSubDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
gfile.PutContents(tempSubFile, "goframe example content")
|
||||
|
||||
// scans directory recursively
|
||||
list, _ := gfile.ScanDirFunc(tempDir, "*", true, func(path string) string {
|
||||
// ignores some files
|
||||
if gfile.Basename(path) == "gflie_example.txt" {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
})
|
||||
for _, v := range list {
|
||||
fmt.Println(gfile.Basename(v))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// sub_dir
|
||||
}
|
||||
|
||||
func ExampleScanDirFileFunc() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_scan_dir_file_func")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
|
||||
fileName1 = "gflie_example_ignores.txt"
|
||||
tempFile1 = gfile.Join(tempDir, fileName1)
|
||||
|
||||
tempSubDir = gfile.Join(tempDir, "sub_dir")
|
||||
tempSubFile = gfile.Join(tempSubDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
gfile.PutContents(tempFile1, "goframe example content")
|
||||
gfile.PutContents(tempSubFile, "goframe example content")
|
||||
|
||||
// scans directory recursively exclusive of directories
|
||||
list, _ := gfile.ScanDirFileFunc(tempDir, "*.txt", true, func(path string) string {
|
||||
// ignores some files
|
||||
if gfile.Basename(path) == "gflie_example_ignores.txt" {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
})
|
||||
for _, v := range list {
|
||||
fmt.Println(gfile.Basename(v))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// gflie_example.txt
|
||||
// gflie_example.txt
|
||||
}
|
||||
32
os/gfile/gfile_z_example_search_test.go
Normal file
32
os/gfile/gfile_z_example_search_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleSearch() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_search")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "goframe example content")
|
||||
|
||||
// search file
|
||||
realPath, _ := gfile.Search(fileName, tempDir)
|
||||
fmt.Println(gfile.Basename(realPath))
|
||||
|
||||
// Output:
|
||||
// gflie_example.txt
|
||||
}
|
||||
83
os/gfile/gfile_z_example_size_test.go
Normal file
83
os/gfile/gfile_z_example_size_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleSize() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_size")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "0123456789")
|
||||
fmt.Println(gfile.Size(tempFile))
|
||||
|
||||
// Output:
|
||||
// 10
|
||||
}
|
||||
|
||||
func ExampleSizeFormat() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_size")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "0123456789")
|
||||
fmt.Println(gfile.SizeFormat(tempFile))
|
||||
|
||||
// Output:
|
||||
// 10.00B
|
||||
}
|
||||
|
||||
func ExampleReadableSize() {
|
||||
// init
|
||||
var (
|
||||
fileName = "gflie_example.txt"
|
||||
tempDir = gfile.TempDir("gfile_example_size")
|
||||
tempFile = gfile.Join(tempDir, fileName)
|
||||
)
|
||||
|
||||
// write contents
|
||||
gfile.PutContents(tempFile, "01234567899876543210")
|
||||
fmt.Println(gfile.ReadableSize(tempFile))
|
||||
|
||||
// Output:
|
||||
// 20.00B
|
||||
}
|
||||
|
||||
func ExampleStrToSize() {
|
||||
size := gfile.StrToSize("100MB")
|
||||
fmt.Println(size)
|
||||
|
||||
// Output:
|
||||
// 104857600
|
||||
}
|
||||
|
||||
func ExampleFormatSize() {
|
||||
sizeStr := gfile.FormatSize(104857600)
|
||||
fmt.Println(sizeStr)
|
||||
sizeStr0 := gfile.FormatSize(1024)
|
||||
fmt.Println(sizeStr0)
|
||||
sizeStr1 := gfile.FormatSize(999999999999999999)
|
||||
fmt.Println(sizeStr1)
|
||||
|
||||
// Output:
|
||||
// 100.00M
|
||||
// 1.00K
|
||||
// 888.18P
|
||||
}
|
||||
32
os/gfile/gfile_z_example_sort_test.go
Normal file
32
os/gfile/gfile_z_example_sort_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleSortFiles() {
|
||||
files := []string{
|
||||
"/aaa/bbb/ccc.txt",
|
||||
"/aaa/bbb/",
|
||||
"/aaa/",
|
||||
"/aaa",
|
||||
"/aaa/ccc/ddd.txt",
|
||||
"/bbb",
|
||||
"/0123",
|
||||
"/ddd",
|
||||
"/ccc",
|
||||
}
|
||||
sortOut := gfile.SortFiles(files)
|
||||
fmt.Println(sortOut)
|
||||
|
||||
// Output:
|
||||
// [/0123 /aaa /aaa/ /aaa/bbb/ /aaa/bbb/ccc.txt /aaa/ccc/ddd.txt /bbb /ccc /ddd]
|
||||
}
|
||||
37
os/gfile/gfile_z_example_time_test.go
Normal file
37
os/gfile/gfile_z_example_time_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 gfile_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
)
|
||||
|
||||
func ExampleMTime() {
|
||||
t := gfile.MTime(gfile.TempDir())
|
||||
fmt.Println(t)
|
||||
|
||||
// May Output:
|
||||
// 2021-11-02 15:18:43.901141 +0800 CST
|
||||
}
|
||||
|
||||
func ExampleMTimestamp() {
|
||||
t := gfile.MTimestamp(gfile.TempDir())
|
||||
fmt.Println(t)
|
||||
|
||||
// May Output:
|
||||
// 1635838398
|
||||
}
|
||||
|
||||
func ExampleMTimestampMilli() {
|
||||
t := gfile.MTimestampMilli(gfile.TempDir())
|
||||
fmt.Println(t)
|
||||
|
||||
// May Output:
|
||||
// 1635838529330
|
||||
}
|
||||
@ -103,8 +103,8 @@ func Test_Truncate(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
|
||||
files, err = os.Open(testpath() + filepaths1)
|
||||
defer files.Close()
|
||||
t.Assert(err, nil)
|
||||
defer files.Close()
|
||||
fileinfo, err2 := files.Stat()
|
||||
t.Assert(err2, nil)
|
||||
t.Assert(fileinfo.Size(), 10)
|
||||
|
||||
@ -190,7 +190,7 @@ func Test_OpenWithFlagPerm(t *testing.T) {
|
||||
flags = append(flags, false)
|
||||
|
||||
for k, v := range files {
|
||||
fileobj, err = gfile.OpenWithFlagPerm(testpath()+v, os.O_RDWR, 666)
|
||||
fileobj, err = gfile.OpenWithFlagPerm(testpath()+v, os.O_RDWR, 0666)
|
||||
fileobj.Close()
|
||||
if flags[k] {
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -15,56 +15,6 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
// Refer to Laravel validation:
|
||||
// https://laravel.com/docs/5.5/validation#available-validation-rules
|
||||
// https://learnku.com/docs/laravel/5.4/validation
|
||||
//
|
||||
// All supported rules:
|
||||
// required format: required brief: Required.
|
||||
// required-if format: required-if:field,value,... brief: Required unless all given field and its value are equal.
|
||||
// required-unless format: required-unless:field,value,... brief: Required unless all given field and its value are not equal.
|
||||
// required-with format: required-with:field1,field2,... brief: Required if any of given fields are not empty.
|
||||
// required-with-all format: required-with-all:field1,field2,... brief: Required if all given fields are not empty.
|
||||
// required-without format: required-without:field1,field2,... brief: Required if any of given fields are empty.
|
||||
// required-without-all format: required-without-all:field1,field2,...brief: Required if all given fields are empty.
|
||||
// bail format: bail brief: Stop validating when this field's validation failed.
|
||||
// date format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02
|
||||
// datetime format: datetime brief: Standard datetime, like: 2006-01-02 12:00:00
|
||||
// date-format format: date-format:format brief: Custom date format.
|
||||
// email format: email brief: Email address.
|
||||
// phone format: phone brief: Phone number.
|
||||
// telephone format: telephone brief: Telephone number, like: "XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
// passport format: passport brief: Universal passport format rule: Starting with letter, containing only numbers or underscores, length between 6 and 18
|
||||
// password format: password brief: Universal password format rule1: Containing any visible chars, length between 6 and 18.
|
||||
// password2 format: password2 brief: Universal password format rule2: Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
// password3 format: password3 brief: Universal password format rule3: Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
// postcode format: postcode brief: Postcode number.
|
||||
// resident-id format: resident-id brief: Resident id number.
|
||||
// bank-card format: bank-card brief: Bank card nunber.
|
||||
// qq format: qq brief: Tencent QQ number.
|
||||
// ip format: ip brief: IPv4/IPv6.
|
||||
// ipv4 format: ipv4 brief: IPv4.
|
||||
// ipv6 format: ipv6 brief: IPv6.
|
||||
// mac format: mac brief: MAC.
|
||||
// url format: url brief: URL.
|
||||
// domain format: domain brief: Domain.
|
||||
// length format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// min-length format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// max-length format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// size format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
// between format: between:min,max brief: Range between :min and :max. It supports both integer and float.
|
||||
// min format: min:min brief: Equal or greater than :min. It supports both integer and float.
|
||||
// max format: max:max brief: Equal or lesser than :max. It supports both integer and float.
|
||||
// json format: json brief: JSON.
|
||||
// integer format: integer brief: Integer.
|
||||
// float format: float brief: Float. Note that an integer is actually a float number.
|
||||
// boolean format: boolean brief: Boolean(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
// same format: same:field brief: Value should be the same as value of field.
|
||||
// different format: different:field brief: Value should be different from value of field.
|
||||
// in format: in:value1,value2,... brief: Value should be in: value1,value2,...
|
||||
// not-in format: not-in:value1,value2,... brief: Value should not be in: value1,value2,...
|
||||
// regex format: regex:pattern brief: Value should match custom regular expression pattern.
|
||||
|
||||
// CustomMsg is the custom error message type,
|
||||
// like: map[field] => string|map[rule]string
|
||||
type CustomMsg = map[string]interface{}
|
||||
@ -89,100 +39,64 @@ const (
|
||||
internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule.
|
||||
ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content.
|
||||
noValidationTagName = "nv" // no validation tag name for struct attribute.
|
||||
bailRuleName = "bail" // the name for rule "bail"
|
||||
ruleNameBail = "bail" // the name for rule "bail"
|
||||
ruleNameCi = "ci" // the name for rule "ci"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultValidator = New() // defaultValidator is the default validator for package functions.
|
||||
structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array.
|
||||
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
||||
|
||||
// all internal error keys.
|
||||
internalErrKeyMap = map[string]string{
|
||||
internalRulesErrRuleName: internalRulesErrRuleName,
|
||||
internalParamsErrRuleName: internalParamsErrRuleName,
|
||||
internalObjectErrRuleName: internalObjectErrRuleName,
|
||||
}
|
||||
// regular expression object for single rule
|
||||
// which is compiled just once and of repeatable usage.
|
||||
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
||||
|
||||
// mustCheckRulesEvenValueEmpty specifies some rules that must be validated
|
||||
// even the value is empty (nil or empty).
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
//"same": {},
|
||||
//"different": {},
|
||||
//"in": {},
|
||||
//"not-in": {},
|
||||
//"regex": {},
|
||||
}
|
||||
// allSupportedRules defines all supported rules that is used for quick checks.
|
||||
// Refer to Laravel validation:
|
||||
// https://laravel.com/docs/5.5/validation#available-validation-rules
|
||||
// https://learnku.com/docs/laravel/5.4/validation
|
||||
allSupportedRules = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
"bail": {},
|
||||
"date": {},
|
||||
"datetime": {},
|
||||
"date-format": {},
|
||||
"email": {},
|
||||
"phone": {},
|
||||
"phone-loose": {},
|
||||
"telephone": {},
|
||||
"passport": {},
|
||||
"password": {},
|
||||
"password2": {},
|
||||
"password3": {},
|
||||
"postcode": {},
|
||||
"resident-id": {},
|
||||
"bank-card": {},
|
||||
"qq": {},
|
||||
"ip": {},
|
||||
"ipv4": {},
|
||||
"ipv6": {},
|
||||
"mac": {},
|
||||
"url": {},
|
||||
"domain": {},
|
||||
"length": {},
|
||||
"min-length": {},
|
||||
"max-length": {},
|
||||
"size": {},
|
||||
"between": {},
|
||||
"min": {},
|
||||
"max": {},
|
||||
"json": {},
|
||||
"integer": {},
|
||||
"float": {},
|
||||
"boolean": {},
|
||||
"same": {},
|
||||
"different": {},
|
||||
"in": {},
|
||||
"not-in": {},
|
||||
"regex": {},
|
||||
}
|
||||
// boolMap defines the boolean values.
|
||||
boolMap = map[string]struct{}{
|
||||
"1": {},
|
||||
"true": {},
|
||||
"on": {},
|
||||
"yes": {},
|
||||
"": {},
|
||||
"0": {},
|
||||
"false": {},
|
||||
"off": {},
|
||||
"no": {},
|
||||
"required": {}, // format: required brief: Required.
|
||||
"required-if": {}, // format: required-if:field,value,... brief: Required unless all given field and its value are equal.
|
||||
"required-unless": {}, // format: required-unless:field,value,... brief: Required unless all given field and its value are not equal.
|
||||
"required-with": {}, // format: required-with:field1,field2,... brief: Required if any of given fields are not empty.
|
||||
"required-with-all": {}, // format: required-with-all:field1,field2,... brief: Required if all given fields are not empty.
|
||||
"required-without": {}, // format: required-without:field1,field2,... brief: Required if any of given fields are empty.
|
||||
"required-without-all": {}, // format: required-without-all:field1,field2,...brief: Required if all given fields are empty.
|
||||
"bail": {}, // format: bail brief: Stop validating when this field's validation failed.
|
||||
"ci": {}, // format: ci brief: Case-Insensitive configuration for those rules that need value comparison like: same, different, in, not-in, etc.
|
||||
"date": {}, // format: date brief: Standard date, like: 2006-01-02, 20060102, 2006.01.02
|
||||
"datetime": {}, // format: datetime brief: Standard datetime, like: 2006-01-02 12:00:00
|
||||
"date-format": {}, // format: date-format:format brief: Custom date format.
|
||||
"email": {}, // format: email brief: Email address.
|
||||
"phone": {}, // format: phone brief: Phone number.
|
||||
"phone-loose": {}, // format: phone-loose brief: Loose phone number validation.
|
||||
"telephone": {}, // format: telephone brief: Telephone number, like: "XXXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"、"XXXXXXXX"
|
||||
"passport": {}, // format: passport brief: Universal passport format rule: Starting with letter, containing only numbers or underscores, length between 6 and 18
|
||||
"password": {}, // format: password brief: Universal password format rule1: Containing any visible chars, length between 6 and 18.
|
||||
"password2": {}, // format: password2 brief: Universal password format rule2: Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
"password3": {}, // format: password3 brief: Universal password format rule3: Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
"postcode": {}, // format: postcode brief: Postcode number.
|
||||
"resident-id": {}, // format: resident-id brief: Resident id number.
|
||||
"bank-card": {}, // format: bank-card brief: Bank card number.
|
||||
"qq": {}, // format: qq brief: Tencent QQ number.
|
||||
"ip": {}, // format: ip brief: IPv4/IPv6.
|
||||
"ipv4": {}, // format: ipv4 brief: IPv4.
|
||||
"ipv6": {}, // format: ipv6 brief: IPv6.
|
||||
"mac": {}, // format: mac brief: MAC.
|
||||
"url": {}, // format: url brief: URL.
|
||||
"domain": {}, // format: domain brief: Domain.
|
||||
"length": {}, // format: length:min,max brief: Length between :min and :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"min-length": {}, // format: min-length:min brief: Length is equal or greater than :min. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"max-length": {}, // format: max-length:max brief: Length is equal or lesser than :max. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"size": {}, // format: size:size brief: Length must be :size. The length is calculated using unicode string, which means one chinese character or letter both has the length of 1.
|
||||
"between": {}, // format: between:min,max brief: Range between :min and :max. It supports both integer and float.
|
||||
"min": {}, // format: min:min brief: Equal or greater than :min. It supports both integer and float.
|
||||
"max": {}, // format: max:max brief: Equal or lesser than :max. It supports both integer and float.
|
||||
"json": {}, // format: json brief: JSON.
|
||||
"integer": {}, // format: integer brief: Integer.
|
||||
"float": {}, // format: float brief: Float. Note that an integer is actually a float number.
|
||||
"boolean": {}, // format: boolean brief: Boolean(1,true,on,yes:true | 0,false,off,no,"":false)
|
||||
"same": {}, // format: same:field brief: Value should be the same as value of field.
|
||||
"different": {}, // format: different:field brief: Value should be different from value of field.
|
||||
"in": {}, // format: in:value1,value2,... brief: Value should be in: value1,value2,...
|
||||
"not-in": {}, // format: not-in:value1,value2,... brief: Value should not be in: value1,value2,...
|
||||
"regex": {}, // format: regex:pattern brief: Value should match custom regular expression pattern.
|
||||
}
|
||||
|
||||
// defaultMessages is the default error messages.
|
||||
// Note that these messages are synchronized from ./i18n/en/validation.toml .
|
||||
defaultMessages = map[string]string{
|
||||
@ -232,11 +146,56 @@ var (
|
||||
"regex": "The {attribute} value `{value}` must be in regex of: {pattern}",
|
||||
internalDefaultRuleName: "The {attribute} value `{value}` is invalid",
|
||||
}
|
||||
|
||||
// mustCheckRulesEvenValueEmpty specifies some rules that must be validated
|
||||
// even the value is empty (nil or empty).
|
||||
mustCheckRulesEvenValueEmpty = map[string]struct{}{
|
||||
"required": {},
|
||||
"required-if": {},
|
||||
"required-unless": {},
|
||||
"required-with": {},
|
||||
"required-with-all": {},
|
||||
"required-without": {},
|
||||
"required-without-all": {},
|
||||
//"same": {},
|
||||
//"different": {},
|
||||
//"in": {},
|
||||
//"not-in": {},
|
||||
//"regex": {},
|
||||
}
|
||||
|
||||
// boolMap defines the boolean values.
|
||||
boolMap = map[string]struct{}{
|
||||
"1": {},
|
||||
"true": {},
|
||||
"on": {},
|
||||
"yes": {},
|
||||
"": {},
|
||||
"0": {},
|
||||
"false": {},
|
||||
"off": {},
|
||||
"no": {},
|
||||
}
|
||||
|
||||
defaultValidator = New() // defaultValidator is the default validator for package functions.
|
||||
structTagPriority = []string{"gvalid", "valid", "v"} // structTagPriority specifies the validation tag priority array.
|
||||
aliasNameTagPriority = []string{"param", "params", "p"} // aliasNameTagPriority specifies the alias tag priority array.
|
||||
|
||||
// all internal error keys.
|
||||
internalErrKeyMap = map[string]string{
|
||||
internalRulesErrRuleName: internalRulesErrRuleName,
|
||||
internalParamsErrRuleName: internalParamsErrRuleName,
|
||||
internalObjectErrRuleName: internalObjectErrRuleName,
|
||||
}
|
||||
// regular expression object for single rule
|
||||
// which is compiled just once and of repeatable usage.
|
||||
ruleRegex, _ = regexp.Compile(singleRulePattern)
|
||||
|
||||
// markedRuleMap defines all rules that are just marked rules which have neither functional meaning
|
||||
// nor error messages.
|
||||
markedRuleMap = map[string]bool{
|
||||
bailRuleName: true,
|
||||
//"nullable": true,
|
||||
ruleNameBail: true,
|
||||
ruleNameCi: true,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ type Validator struct {
|
||||
ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
|
||||
useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`.
|
||||
bail bool // Stop validation after the first validation error.
|
||||
caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
|
||||
}
|
||||
|
||||
// New creates and returns a new Validator.
|
||||
@ -55,6 +56,13 @@ func (v *Validator) Bail() *Validator {
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// CaseInsensitive sets the mark for Case-Insensitive for those rules that need value comparison.
|
||||
func (v *Validator) CaseInsensitive() *Validator {
|
||||
newValidator := v.Clone()
|
||||
newValidator.caseInsensitive = true
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// Data is a chaining operation function, which sets validation data for current operation.
|
||||
// The parameter `data` is usually type of map, which specifies the parameter map used in validation.
|
||||
// Calling this function also sets `useDataInsteadOfObjectAttributes` true no mather the `data` is nil or not.
|
||||
|
||||
@ -54,9 +54,9 @@ type doCheckValueInput struct {
|
||||
}
|
||||
|
||||
// doCheckSingleValue does the really rules validation for single key-value.
|
||||
func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) Error {
|
||||
func (v *Validator) doCheckValue(ctx context.Context, in doCheckValueInput) Error {
|
||||
// If there's no validation rules, it does nothing and returns quickly.
|
||||
if input.Rule == "" {
|
||||
if in.Rule == "" {
|
||||
return nil
|
||||
}
|
||||
// It converts value to string and then does the validation.
|
||||
@ -69,17 +69,17 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
msgArray = make([]string, 0)
|
||||
customMsgMap = make(map[string]string)
|
||||
)
|
||||
switch messages := input.Messages.(type) {
|
||||
switch messages := in.Messages.(type) {
|
||||
case string:
|
||||
msgArray = strings.Split(messages, "|")
|
||||
default:
|
||||
for k, message := range gconv.Map(input.Messages) {
|
||||
for k, message := range gconv.Map(in.Messages) {
|
||||
customMsgMap[k] = gconv.String(message)
|
||||
}
|
||||
}
|
||||
// Handle the char '|' in the rule,
|
||||
// which makes this rule separated into multiple rules.
|
||||
ruleItems := strings.Split(strings.TrimSpace(input.Rule), "|")
|
||||
ruleItems := strings.Split(strings.TrimSpace(in.Rule), "|")
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
@ -90,7 +90,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
} else {
|
||||
return newValidationErrorByStr(
|
||||
internalRulesErrRuleName,
|
||||
errors.New(internalRulesErrRuleName+": "+input.Rule),
|
||||
errors.New(internalRulesErrRuleName+": "+in.Rule),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -101,7 +101,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
}
|
||||
}
|
||||
var (
|
||||
hasBailRule = false
|
||||
hasBailRule = v.bail
|
||||
hasCaseInsensitive = v.caseInsensitive
|
||||
)
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
@ -113,10 +114,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
customRuleFunc RuleFunc
|
||||
)
|
||||
|
||||
if !hasBailRule && ruleKey == bailRuleName {
|
||||
if !hasBailRule && ruleKey == ruleNameBail {
|
||||
hasBailRule = true
|
||||
}
|
||||
|
||||
if !hasCaseInsensitive && ruleKey == ruleNameCi {
|
||||
hasCaseInsensitive = true
|
||||
}
|
||||
|
||||
// Ignore logic executing for marked rules.
|
||||
if markedRuleMap[ruleKey] {
|
||||
index++
|
||||
@ -138,8 +143,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if err = customRuleFunc(ctx, RuleFuncInput{
|
||||
Rule: ruleItems[index],
|
||||
Message: message,
|
||||
Value: gvar.New(input.Value),
|
||||
Data: gvar.New(input.DataRaw),
|
||||
Value: gvar.New(in.Value),
|
||||
Data: gvar.New(in.DataRaw),
|
||||
}); err != nil {
|
||||
match = false
|
||||
// The error should have stack info to indicate the error position.
|
||||
@ -161,13 +166,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
match, err = v.doCheckSingleBuildInRules(
|
||||
ctx,
|
||||
doCheckBuildInRulesInput{
|
||||
Index: index,
|
||||
Value: input.Value,
|
||||
RuleKey: ruleKey,
|
||||
RulePattern: rulePattern,
|
||||
RuleItems: ruleItems,
|
||||
DataMap: input.DataMap,
|
||||
CustomMsgMap: customMsgMap,
|
||||
Index: index,
|
||||
Value: in.Value,
|
||||
RuleKey: ruleKey,
|
||||
RulePattern: rulePattern,
|
||||
RuleItems: ruleItems,
|
||||
DataMap: in.DataMap,
|
||||
CustomMsgMap: customMsgMap,
|
||||
CaseInsensitive: hasCaseInsensitive,
|
||||
},
|
||||
)
|
||||
if !match && err != nil {
|
||||
@ -187,9 +193,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if err = ruleErrorMap[ruleKey]; !gerror.HasStack(err) {
|
||||
var s string
|
||||
s = gstr.ReplaceByMap(err.Error(), map[string]string{
|
||||
"{value}": gconv.String(input.Value),
|
||||
"{value}": gconv.String(in.Value),
|
||||
"{pattern}": rulePattern,
|
||||
"{attribute}": input.Name,
|
||||
"{attribute}": in.Name,
|
||||
})
|
||||
s, _ = gregex.ReplaceString(`\s{2,}`, ` `, s)
|
||||
ruleErrorMap[ruleKey] = errors.New(s)
|
||||
@ -206,9 +212,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
if len(ruleErrorMap) > 0 {
|
||||
return newValidationError(
|
||||
gcode.CodeValidationFailed,
|
||||
[]fieldRule{{Name: input.Name, Rule: input.Rule}},
|
||||
[]fieldRule{{Name: in.Name, Rule: in.Rule}},
|
||||
map[string]map[string]error{
|
||||
input.Name: ruleErrorMap,
|
||||
in.Name: ruleErrorMap,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -216,18 +222,19 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
|
||||
}
|
||||
|
||||
type doCheckBuildInRulesInput struct {
|
||||
Index int // Index of RuleKey in RuleItems.
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
RuleItems []string // RuleItems are all the rules that should be validated on single field, like: []string{"required", "min:1"}
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CustomMsgMap map[string]string // Custom error message map.
|
||||
Index int // Index of RuleKey in RuleItems.
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
RuleItems []string // RuleItems are all the rules that should be validated on single field, like: []string{"required", "min:1"}
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CustomMsgMap map[string]string // Custom error message map.
|
||||
CaseInsensitive bool // Case-Insensitive comparison.
|
||||
}
|
||||
|
||||
func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheckBuildInRulesInput) (match bool, err error) {
|
||||
valueStr := gconv.String(input.Value)
|
||||
switch input.RuleKey {
|
||||
func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBuildInRulesInput) (match bool, err error) {
|
||||
valueStr := gconv.String(in.Value)
|
||||
switch in.RuleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
@ -237,7 +244,13 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = v.checkRequired(input.Value, input.RuleKey, input.RulePattern, input.DataMap)
|
||||
match = v.checkRequired(checkRequiredInput{
|
||||
Value: in.Value,
|
||||
RuleKey: in.RuleKey,
|
||||
RulePattern: in.RulePattern,
|
||||
DataMap: in.DataMap,
|
||||
CaseInsensitive: in.CaseInsensitive,
|
||||
})
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
@ -246,7 +259,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"min-length",
|
||||
"max-length",
|
||||
"size":
|
||||
if msg := v.checkLength(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
if msg := v.checkLength(ctx, valueStr, in.RuleKey, in.RulePattern, in.CustomMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
@ -257,7 +270,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := v.checkRange(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" {
|
||||
if msg := v.checkRange(ctx, valueStr, in.RuleKey, in.RulePattern, in.CustomMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
@ -266,27 +279,27 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := input.Index + 1; i < len(input.RuleItems); i++ {
|
||||
if !gregex.IsMatchString(singleRulePattern, input.RuleItems[i]) {
|
||||
input.RulePattern += "|" + input.RuleItems[i]
|
||||
input.Index++
|
||||
for i := in.Index + 1; i < len(in.RuleItems); i++ {
|
||||
if !gregex.IsMatchString(singleRulePattern, in.RuleItems[i]) {
|
||||
in.RulePattern += "|" + in.RuleItems[i]
|
||||
in.Index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(input.RulePattern, valueStr)
|
||||
match = gregex.IsMatchString(in.RulePattern, valueStr)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
match = gregex.IsMatchString(`\d{4}[\.\-\_/]{0,1}\d{2}[\.\-\_/]{0,1}\d{2}`, valueStr)
|
||||
|
||||
// Datetime rule.
|
||||
case "datetime":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, `Y-m-d H:i:s`); err == nil {
|
||||
match = true
|
||||
@ -295,54 +308,61 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
// support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time.
|
||||
if v, ok := input.Value.(iTime); ok {
|
||||
return !v.IsZero(), nil
|
||||
if value, ok := in.Value.(iTime); ok {
|
||||
return !value.IsZero(), nil
|
||||
}
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, input.RulePattern); err == nil {
|
||||
if _, err = gtime.StrToTimeFormat(valueStr, in.RulePattern); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var (
|
||||
msg string
|
||||
)
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
_, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern)
|
||||
_, foundValue := gutil.MapPossibleItemByKey(in.DataMap, in.RulePattern)
|
||||
if foundValue != nil {
|
||||
if strings.Compare(valueStr, gconv.String(foundValue)) == 0 {
|
||||
match = true
|
||||
if in.CaseInsensitive {
|
||||
match = strings.EqualFold(valueStr, gconv.String(foundValue))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, gconv.String(foundValue)) == 0
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
_, foundValue := gutil.MapPossibleItemByKey(input.DataMap, input.RulePattern)
|
||||
_, foundValue := gutil.MapPossibleItemByKey(in.DataMap, in.RulePattern)
|
||||
if foundValue != nil {
|
||||
if strings.Compare(valueStr, gconv.String(foundValue)) == 0 {
|
||||
match = false
|
||||
if in.CaseInsensitive {
|
||||
match = !strings.EqualFold(valueStr, gconv.String(foundValue))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, gconv.String(foundValue)) != 0
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap)
|
||||
msg = v.getErrorMessageByRule(ctx, in.RuleKey, in.CustomMsgMap)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := gstr.SplitAndTrim(input.RulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
for _, value := range gstr.SplitAndTrim(in.RulePattern, ",") {
|
||||
if in.CaseInsensitive {
|
||||
match = strings.EqualFold(valueStr, strings.TrimSpace(value))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, strings.TrimSpace(value)) == 0
|
||||
}
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -350,10 +370,13 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := gstr.SplitAndTrim(input.RulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
for _, value := range gstr.SplitAndTrim(in.RulePattern, ",") {
|
||||
if in.CaseInsensitive {
|
||||
match = !strings.EqualFold(valueStr, strings.TrimSpace(value))
|
||||
} else {
|
||||
match = strings.Compare(valueStr, strings.TrimSpace(value)) != 0
|
||||
}
|
||||
if !match {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -517,7 +540,7 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, input doCheck
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, errors.New("Invalid rule name: " + input.RuleKey)
|
||||
return match, errors.New("Invalid rule name: " + in.RuleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
||||
@ -15,11 +15,19 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
type checkRequiredInput struct {
|
||||
Value interface{} // Value to be validated.
|
||||
RuleKey string // RuleKey is like the "max" in rule "max: 6"
|
||||
RulePattern string // RulePattern is like "6" in rule:"max:6"
|
||||
DataMap map[string]interface{} // Parameter map.
|
||||
CaseInsensitive bool // Case-Insensitive comparison.
|
||||
}
|
||||
|
||||
// checkRequired checks `value` using required rules.
|
||||
// It also supports require checks for `value` of type: slice, map.
|
||||
func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string, dataMap map[string]interface{}) bool {
|
||||
func (v *Validator) checkRequired(in checkRequiredInput) bool {
|
||||
required := false
|
||||
switch ruleKey {
|
||||
switch in.RuleKey {
|
||||
// Required.
|
||||
case "required":
|
||||
required = true
|
||||
@ -29,7 +37,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-if":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
// It supports multiple field and value pairs.
|
||||
@ -37,9 +45,13 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, tk)
|
||||
if strings.Compare(tv, gconv.String(foundValue)) == 0 {
|
||||
required = true
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, tk)
|
||||
if in.CaseInsensitive {
|
||||
required = strings.EqualFold(tv, gconv.String(foundValue))
|
||||
} else {
|
||||
required = strings.Compare(tv, gconv.String(foundValue)) == 0
|
||||
}
|
||||
if required {
|
||||
break
|
||||
}
|
||||
i += 2
|
||||
@ -51,7 +63,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-unless":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
// It supports multiple field and value pairs.
|
||||
@ -59,12 +71,15 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
for i := 0; i < len(array); {
|
||||
tk := array[i]
|
||||
tv := array[i+1]
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, tk)
|
||||
if strings.Compare(tv, gconv.String(foundValue)) == 0 {
|
||||
required = false
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, tk)
|
||||
if in.CaseInsensitive {
|
||||
required = !strings.EqualFold(tv, gconv.String(foundValue))
|
||||
} else {
|
||||
required = strings.Compare(tv, gconv.String(foundValue)) != 0
|
||||
}
|
||||
if !required {
|
||||
break
|
||||
}
|
||||
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
@ -74,11 +89,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-with":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if !empty.IsEmpty(foundValue) {
|
||||
required = true
|
||||
break
|
||||
@ -90,11 +105,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-with-all":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if empty.IsEmpty(foundValue) {
|
||||
required = false
|
||||
break
|
||||
@ -106,11 +121,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-without":
|
||||
required = false
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if empty.IsEmpty(foundValue) {
|
||||
required = true
|
||||
break
|
||||
@ -122,11 +137,11 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case "required-without-all":
|
||||
required = true
|
||||
var (
|
||||
array = strings.Split(rulePattern, ",")
|
||||
array = strings.Split(in.RulePattern, ",")
|
||||
foundValue interface{}
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
_, foundValue = gutil.MapPossibleItemByKey(dataMap, array[i])
|
||||
_, foundValue = gutil.MapPossibleItemByKey(in.DataMap, array[i])
|
||||
if !empty.IsEmpty(foundValue) {
|
||||
required = false
|
||||
break
|
||||
@ -134,7 +149,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
}
|
||||
}
|
||||
if required {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
reflectValue := reflect.ValueOf(in.Value)
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
@ -142,7 +157,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
|
||||
case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
|
||||
return reflectValue.Len() != 0
|
||||
}
|
||||
return gconv.String(value) != ""
|
||||
return gconv.String(in.Value) != ""
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ func Test_Map_Bail(t *testing.T) {
|
||||
}
|
||||
err := g.Validator().Bail().Rules(rules).CheckMap(ctx, params)
|
||||
t.AssertNE(err, nil)
|
||||
t.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间")
|
||||
t.Assert(err.String(), "账号不能为空")
|
||||
})
|
||||
// global bail with rule bail
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
|
||||
30
util/gvalid/gvalid_z_unit_feature_ci_test.go
Normal file
30
util/gvalid/gvalid_z_unit_feature_ci_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
// 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 gvalid_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CI(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(ctx, "id", "in:Id,Name", nil)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gvalid.CheckValue(ctx, "id", "ci|in:Id,Name", nil)
|
||||
t.AssertNil(err)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := g.Validator().CaseInsensitive().Rules("in:Id,Name").CheckValue(ctx, "id")
|
||||
t.AssertNil(err)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user