From 9ee0f04e58999c1e0cd76abf0ae2feb43a034f44 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 26 May 2021 22:08:13 +0800 Subject: [PATCH 01/16] add command feature for package gcmd --- .example/os/gcmd/manager/main.go | 88 ++++++++++++++ internal/command/command.go | 20 ++- os/gcmd/gcmd.go | 8 +- os/gcmd/gcmd_command.go | 202 +++++++++++++++++++++++++++++++ os/gcmd/gcmd_handler.go | 2 +- os/gcmd/gcmd_parser_handler.go | 2 +- 6 files changed, 313 insertions(+), 9 deletions(-) create mode 100644 .example/os/gcmd/manager/main.go create mode 100644 os/gcmd/gcmd_command.go diff --git a/.example/os/gcmd/manager/main.go b/.example/os/gcmd/manager/main.go new file mode 100644 index 000000000..e7ed739e0 --- /dev/null +++ b/.example/os/gcmd/manager/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gcmd" +) + +func main() { + var 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", + Description: "show current Golang environment variables", + Func: func(parser *gcmd.Parser) { + + }, + } + if err = c.AddCommand(commandEnv); err != nil { + g.Log().Fatal(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(parser *gcmd.Parser) { + + }, + } + if err = c.AddCommand(commandGet); err != nil { + g.Log().Fatal(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(parser *gcmd.Parser) { + + }, + } + if err = c.AddCommand(commandBuild); err != nil { + g.Log().Fatal(err) + } + c.Run() +} diff --git a/internal/command/command.go b/internal/command/command.go index 824f83ca3..ab63ae506 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -20,7 +20,7 @@ var ( argumentRegex = regexp.MustCompile(`^\-{1,2}([\w\?\.\-]+)(=){0,1}(.*)$`) ) -// Custom initialization. +// Init does custom initialization. func Init(args ...string) { if len(args) == 0 { if len(defaultParsedArgs) == 0 && len(defaultParsedOptions) == 0 { @@ -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`. diff --git a/os/gcmd/gcmd.go b/os/gcmd/gcmd.go index d32ca03d6..515491e46 100644 --- a/os/gcmd/gcmd.go +++ b/os/gcmd/gcmd.go @@ -15,11 +15,17 @@ import ( "strings" ) +const ( + helpOptionName = "help" + helpOptionNameShort = "h" + maxLineChars = 100 +) + var ( defaultCommandFuncMap = make(map[string]func()) ) -// Custom initialization. +// Init does custom initialization. func Init(args ...string) { command.Init(args...) } diff --git a/os/gcmd/gcmd_command.go b/os/gcmd/gcmd_command.go new file mode 100644 index 000000000..698f780e9 --- /dev/null +++ b/os/gcmd/gcmd_command.go @@ -0,0 +1,202 @@ +// 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" + "fmt" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/internal/command" + "github.com/gogf/gf/text/gstr" + "os" +) + +type Command struct { + parent *Command + commands []Command + options []Option + level int + Name string + Usage string + Short string + Brief string + Description string + Func func(parser *Parser) + HelpFunc func(parser *Parser) + Examples string + Additional string +} + +type Option struct { + Name string + Short string + Brief string + Description string + NeedValue bool +} + +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") + maxSpaceLength := 0 + for _, cmd := range c.commands { + nameStr := cmd.Name + "/" + cmd.Short + if len(nameStr) > maxSpaceLength { + maxSpaceLength = len(nameStr) + } + } + for _, cmd := range c.commands { + nameStr := cmd.Name + if cmd.Short != "" { + nameStr += "/" + cmd.Short + } + var ( + spaceLength = maxSpaceLength - len(nameStr) + lineStr = fmt.Sprintf( + "%s%s%s %s\n", + prefix, nameStr, gstr.Repeat(" ", spaceLength), cmd.Brief, + ) + ) + lineStr = gstr.WordWrap(lineStr, maxLineChars, "\n") + buffer.WriteString(lineStr) + } + buffer.WriteString("\n") + } + + // Examples. + if c.Examples != "" { + buffer.WriteString("EXAMPLES\n") + lineStr := gstr.WordWrap(gstr.Trim(c.Examples), maxLineChars, "\n") + for _, line := range gstr.SplitAndTrim(lineStr, "\n") { + buffer.WriteString(prefix) + buffer.WriteString(line) + buffer.WriteString("\n") + } + buffer.WriteString("\n") + } + // Description. + if c.Description != "" { + buffer.WriteString("DESCRIPTION\n") + lineStr := gstr.WordWrap(gstr.Trim(c.Description), maxLineChars, "\n") + for _, line := range gstr.SplitAndTrim(lineStr, "\n") { + buffer.WriteString(prefix) + buffer.WriteString(line) + buffer.WriteString("\n") + } + } + 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) AddCommand(command ...Command) error { + for _, cmd := range command { + 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 + cmd.level = c.level + 1 + c.commands = append(c.commands, cmd) + } + return nil +} + +func (c *Command) AddOption(option ...Option) error { + for _, opt := range option { + opt.Name = gstr.Trim(opt.Name) + if opt.Name == "" { + return gerror.New("option name should not be empty") + } + } + c.options = append(c.options, option...) + return nil +} + +func (c *Command) Run() { + // Find the matched command and run it. + argument := GetArg(c.level + 1) + if argument != "" { + if len(c.commands) > 0 { + for _, cmd := range c.commands { + if gstr.Equal(cmd.Name, argument) { + cmd.Run() + return + } + } + } + } + // Run current command function. + var ( + err error + parser *Parser + ) + if len(c.options) > 0 { + optionParsingMap := make(map[string]bool, 0) + // Add custom options to parser. + for _, option := range c.options { + optionParsingKey := option.Name + if option.Short != "" { + optionParsingKey += "," + option.Short + } + optionParsingMap[optionParsingKey] = option.NeedValue + } + // Add help option to parser. + optionParsingMap[helpOptionName+","+helpOptionNameShort] = false + parser, err = Parse(optionParsingMap) + } else { + parsedArgs, parsedOptions := command.ParseUsingDefaultAlgorithm(os.Args...) + parser = &Parser{ + strict: false, + parsedArgs: parsedArgs, + parsedOptions: parsedOptions, + } + } + if err != nil { + fmt.Println("Error:", err) + } + if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) { + if c.HelpFunc != nil { + c.HelpFunc(parser) + } else { + c.Print() + } + return + } + c.Func(parser) +} diff --git a/os/gcmd/gcmd_handler.go b/os/gcmd/gcmd_handler.go index 2bcb7a905..b50045def 100644 --- a/os/gcmd/gcmd_handler.go +++ b/os/gcmd/gcmd_handler.go @@ -21,7 +21,7 @@ func BindHandle(cmd string, f func()) error { return nil } -// BindHandle registers callback function with map . +// BindHandleMap registers callback function with map . func BindHandleMap(m map[string]func()) error { var err error for k, v := range m { diff --git a/os/gcmd/gcmd_parser_handler.go b/os/gcmd/gcmd_parser_handler.go index a2ab79eee..ef9904295 100644 --- a/os/gcmd/gcmd_parser_handler.go +++ b/os/gcmd/gcmd_parser_handler.go @@ -21,7 +21,7 @@ func (p *Parser) BindHandle(cmd string, f func()) error { return nil } -// BindHandle registers callback function with map . +// BindHandleMap registers callback function with map . func (p *Parser) BindHandleMap(m map[string]func()) error { var err error for k, v := range m { From 8a9dd169e2c6fbc8256099734e889b99e93300c2 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Fri, 5 Nov 2021 14:30:50 +0800 Subject: [PATCH 02/16] test: add gflie example --- os/gfile/gfile_home.go | 7 +- os/gfile/gfile_z_contents_test.go | 2 +- os/gfile/gfile_z_example_cache_test.go | 43 ++++ os/gfile/gfile_z_example_contents_test.go | 253 ++++++++++++++++++++++ os/gfile/gfile_z_example_copy_test.go | 53 +++++ os/gfile/gfile_z_example_home_test.go | 16 ++ os/gfile/gfile_z_example_replace_test.go | 145 +++++++++++++ os/gfile/gfile_z_example_scan_test.go | 117 ++++++++++ os/gfile/gfile_z_example_search_test.go | 23 ++ os/gfile/gfile_z_example_size_test.go | 56 +++++ os/gfile/gfile_z_example_sort_test.go | 26 +++ os/gfile/gfile_z_example_time_test.go | 31 +++ os/gfile/gfile_z_test.go | 2 +- 13 files changed, 769 insertions(+), 5 deletions(-) create mode 100644 os/gfile/gfile_z_example_cache_test.go create mode 100644 os/gfile/gfile_z_example_contents_test.go create mode 100644 os/gfile/gfile_z_example_copy_test.go create mode 100644 os/gfile/gfile_z_example_home_test.go create mode 100644 os/gfile/gfile_z_example_replace_test.go create mode 100644 os/gfile/gfile_z_example_scan_test.go create mode 100644 os/gfile/gfile_z_example_search_test.go create mode 100644 os/gfile/gfile_z_example_size_test.go create mode 100644 os/gfile/gfile_z_example_sort_test.go create mode 100644 os/gfile/gfile_z_example_time_test.go diff --git a/os/gfile/gfile_home.go b/os/gfile/gfile_home.go index a6137b156..68bc6c645 100644 --- a/os/gfile/gfile_home.go +++ b/os/gfile/gfile_home.go @@ -8,13 +8,14 @@ package gfile import ( "bytes" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" "os" "os/exec" "os/user" "runtime" "strings" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" ) // Home returns absolute path of current user's home directory. @@ -37,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() diff --git a/os/gfile/gfile_z_contents_test.go b/os/gfile/gfile_z_contents_test.go index 488cdfd22..9f34b2240 100644 --- a/os/gfile/gfile_z_contents_test.go +++ b/os/gfile/gfile_z_contents_test.go @@ -102,8 +102,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) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go new file mode 100644 index 000000000..f7939a960 --- /dev/null +++ b/os/gfile/gfile_z_example_cache_test.go @@ -0,0 +1,43 @@ +package gfile_test + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleGetContentsWithCache() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_cache") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetContentsWithCache(tempFile, time.Minute) + fmt.Println(content) + + time.Sleep(time.Second * 1) + + // read contents + content1 := gfile.GetContentsWithCache(tempFile) + fmt.Println(content1) + + // write new contents will clear its cache + gfile.PutContents(tempFile, "new test contents") + + // read contents + content2 := gfile.GetContentsWithCache(tempFile) + fmt.Println(content2) + + // Output: + // test contents + // test contents + // new test contents +} diff --git a/os/gfile/gfile_z_example_contents_test.go b/os/gfile/gfile_z_example_contents_test.go new file mode 100644 index 000000000..7d325e277 --- /dev/null +++ b/os/gfile/gfile_z_example_contents_test.go @@ -0,0 +1,253 @@ +package gfile_test + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleGetContents() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // Output: + // test contents +} + +func ExampleGetBytes() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetBytes(tempFile) + fmt.Println(string(content)) + + // Output: + // test contents +} + +func ExamplePutContents() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // Output: + // test contents +} + +func ExamplePutBytes() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutBytes(tempFile, []byte("test contents")) + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // Output: + // test contents +} + +func ExamplePutContentsAppend() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // write contents + gfile.PutContentsAppend(tempFile, " append") + + // read contents + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + + // Output: + // test contents + // test contents append +} + +func ExamplePutBytesAppend() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutBytes(tempFile, []byte("test contents")) + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // write contents + gfile.PutBytesAppend(tempFile, []byte(" append")) + + // read contents + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + + // Output: + // test contents + // test contents append +} + +func ExampleGetNextCharOffsetByPath() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents index") + + // read contents + index := gfile.GetNextCharOffsetByPath(tempFile, 'i', 0) + fmt.Println(index) + + // Output: + // 14 +} + +func ExampleGetBytesTilCharByPath() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents: hello") + + // read contents + contents, index := gfile.GetBytesTilCharByPath(tempFile, ':', 0) + fmt.Println(string(contents)) + fmt.Println(index) + + // Output: + // test contents: + // 13 +} + +func ExampleGetBytesByTwoOffsetsByPath() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + contents := gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 4) + fmt.Println(string(contents)) + + // Output: + // test +} + +func ExampleReadLines() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents\ntest contents") + + // read contents + gfile.ReadLines(tempFile, func(text string) error { + // Process each line + fmt.Println(text) + return nil + }) + + // Output: + // test contents + // test contents +} + +func ExampleReadLinesBytes() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_content") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents\ntest contents") + + // read contents + gfile.ReadLinesBytes(tempFile, func(bytes []byte) error { + // Process each line + fmt.Println(string(bytes)) + return nil + }) + + // Output: + // test contents + // test contents +} diff --git a/os/gfile/gfile_z_example_copy_test.go b/os/gfile/gfile_z_example_copy_test.go new file mode 100644 index 000000000..adf005514 --- /dev/null +++ b/os/gfile/gfile_z_example_copy_test.go @@ -0,0 +1,53 @@ +package gfile_test + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleCopy() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_copy") + tempFile := gfile.Join(tempDir, fileName) + + dstFileName := "123copy.txt" + dstTempDir := gfile.TempDir("gfile_example_copy_dst") + dstTempFile := gfile.Join(tempDir, dstFileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // clear + if gfile.Exists(dstTempFile) { + gfile.Remove(dstTempFile) + } + if gfile.Exists(dstTempDir) { + gfile.Remove(dstTempDir) + } + + // write contents + gfile.PutContents(tempFile, "test copy") + + // copy file + gfile.Copy(tempFile, dstTempFile) + + // read contents + content := gfile.GetContents(dstTempFile) + fmt.Println(content) + + // copy dir + gfile.Copy(tempDir, dstTempDir) + + fList, _ := gfile.ScanDir(dstTempDir, "*", false) + for _, v := range fList { + content := gfile.GetContents(v) + fmt.Println(content) + } + + // Output: + // test copy + // test copy + // test copy +} diff --git a/os/gfile/gfile_z_example_home_test.go b/os/gfile/gfile_z_example_home_test.go new file mode 100644 index 000000000..ef43af9cd --- /dev/null +++ b/os/gfile/gfile_z_example_home_test.go @@ -0,0 +1,16 @@ +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 +} diff --git a/os/gfile/gfile_z_example_replace_test.go b/os/gfile/gfile_z_example_replace_test.go new file mode 100644 index 000000000..7600d7ee3 --- /dev/null +++ b/os/gfile/gfile_z_example_replace_test.go @@ -0,0 +1,145 @@ +package gfile_test + +import ( + "fmt" + "regexp" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleReplaceFile() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_replace") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + gfile.ReplaceFile("test", "replace word", tempFile) + + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + + // Output: + // test contents + // replace word contents +} + +func ExampleReplaceFileFunc() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_replace") + tempFile := gfile.Join(tempDir, fileName) + + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // write contents + gfile.PutContents(tempFile, "666 test contents 888 a1a2a3") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + + // replace by yourself + gfile.ReplaceFileFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempFile) + + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + + // Output: + // 666 test contents 888 a1a2a3 + // [num] test contents [num] a1a2a3 +} + +func ExampleReplaceDir() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_replace") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // write contents + gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempSubFile, "test contents") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + contentSub := gfile.GetContents(tempSubFile) + fmt.Println(contentSub) + + gfile.ReplaceDir("test", "replace word", tempDir, "123.txt", true) + + // read contents + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + contentSub1 := gfile.GetContents(tempSubFile) + fmt.Println(contentSub1) + + // Output: + // test contents + // test contents + // replace word contents + // replace word contents +} + +func ExampleReplaceDirFunc() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example_replace") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // write contents + gfile.PutContents(tempFile, "666 test contents 888 a1a2a3") + gfile.PutContents(tempSubFile, "666 test contents 888 a1a2a3") + + // read contents + content := gfile.GetContents(tempFile) + fmt.Println(content) + contentSub := gfile.GetContents(tempSubFile) + fmt.Println(contentSub) + + gfile.ReplaceDirFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempDir, "123.txt", true) + + // read contents + content1 := gfile.GetContents(tempFile) + fmt.Println(content1) + contentSub1 := gfile.GetContents(tempSubFile) + fmt.Println(contentSub1) + + // Output: + // 666 test contents 888 a1a2a3 + // 666 test contents 888 a1a2a3 + // [num] test contents [num] a1a2a3 + // [num] test contents [num] a1a2a3 +} diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go new file mode 100644 index 000000000..9dd443a4b --- /dev/null +++ b/os/gfile/gfile_z_example_scan_test.go @@ -0,0 +1,117 @@ +package gfile_test + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleScanDir() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // scans directory recursively + list, _ := gfile.ScanDir(tempDir, "123.txt,sub_dir", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // 123.txt + // sub_dir + // 123.txt +} + +func ExampleScanDirFile() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFile(tempDir, "123.txt,sub_dir", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // 123.txt + // 123.txt +} + +func ExampleScanDirFunc() { + // init + fileName := "123.txt" + fileName1 := "1234.txt" + tempDir := gfile.TempDir("gfile_example_1") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName1) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // scans directory recursively + list, _ := gfile.ScanDirFunc(tempDir, "123.txt,1234.txt,sub_dir", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "1234.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // 123.txt + // sub_dir +} + +func ExampleScanDirFileFunc() { + // init + fileName := "123.txt" + fileName1 := "1234.txt" + tempDir := gfile.TempDir("gfile_example_1") + tempFile := gfile.Join(tempDir, fileName) + + tempSubDir := gfile.Join(tempDir, "sub_dir") + tempSubFile := gfile.Join(tempSubDir, fileName1) + + gfile.Mkdir(tempSubDir) + gfile.Create(tempFile) + gfile.Create(tempSubFile) + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFileFunc(tempDir, "123.txt,1234.txt,sub_dir", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "1234.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // 123.txt +} diff --git a/os/gfile/gfile_z_example_search_test.go b/os/gfile/gfile_z_example_search_test.go new file mode 100644 index 000000000..01ea92c36 --- /dev/null +++ b/os/gfile/gfile_z_example_search_test.go @@ -0,0 +1,23 @@ +package gfile_test + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleSearch() { + // init + fileName := "123.txt" + tempDir := gfile.TempDir("gfile_example") + tempFile := gfile.Join(tempDir, fileName) + gfile.Mkdir(tempDir) + gfile.Create(tempFile) + + // search file + realPath, _ := gfile.Search(fileName, tempDir) + fmt.Println(gfile.Basename(realPath)) + + // Output: + // 123.txt +} diff --git a/os/gfile/gfile_z_example_size_test.go b/os/gfile/gfile_z_example_size_test.go new file mode 100644 index 000000000..5ff979f20 --- /dev/null +++ b/os/gfile/gfile_z_example_size_test.go @@ -0,0 +1,56 @@ +package gfile_test + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" +) + +func ExampleSize() { + tempDir := gfile.TempDir("gfile_example") + gfile.Mkdir(tempDir) + size := gfile.Size(tempDir) + fmt.Println(size) + + // Output: + // 0 +} + +func ExampleSizeFormat() { + tempDir := gfile.TempDir("gfile_example") + gfile.Mkdir(tempDir) + sizeStr := gfile.SizeFormat(tempDir) + fmt.Println(sizeStr) + + // Output: + // 0.00B +} + +func ExampleReadableSize() { + tempDir := gfile.TempDir("gfile_example") + gfile.Mkdir(tempDir) + sizeStr := gfile.ReadableSize(tempDir) + fmt.Println(sizeStr) + + // Output: + // 0.00B +} + +func ExampleStrToSize() { + size := gfile.StrToSize("100MB") + fmt.Println(size) + + // Output: + // 104857600 +} + +func ExampleFormatSize() { + sizeStr := gfile.FormatSize(104857600) + fmt.Println(sizeStr) + sizeStr1 := gfile.FormatSize(999999999999999999) + fmt.Println(sizeStr1) + + // Output: + // 100.00M + // 888.18P +} diff --git a/os/gfile/gfile_z_example_sort_test.go b/os/gfile/gfile_z_example_sort_test.go new file mode 100644 index 000000000..f49ff65b0 --- /dev/null +++ b/os/gfile/gfile_z_example_sort_test.go @@ -0,0 +1,26 @@ +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] +} diff --git a/os/gfile/gfile_z_example_time_test.go b/os/gfile/gfile_z_example_time_test.go new file mode 100644 index 000000000..cfd7c7318 --- /dev/null +++ b/os/gfile/gfile_z_example_time_test.go @@ -0,0 +1,31 @@ +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 +} diff --git a/os/gfile/gfile_z_test.go b/os/gfile/gfile_z_test.go index fce200568..d26aee020 100644 --- a/os/gfile/gfile_z_test.go +++ b/os/gfile/gfile_z_test.go @@ -191,7 +191,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) From 1f83c00ebff421d135130b9774a33b0c5fe56a58 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Tue, 9 Nov 2021 15:49:00 +0800 Subject: [PATCH 03/16] fix: gfile size example fail --- os/gfile/gfile_z_example_size_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/os/gfile/gfile_z_example_size_test.go b/os/gfile/gfile_z_example_size_test.go index 5ff979f20..e998cb14c 100644 --- a/os/gfile/gfile_z_example_size_test.go +++ b/os/gfile/gfile_z_example_size_test.go @@ -7,7 +7,10 @@ import ( ) func ExampleSize() { - tempDir := gfile.TempDir("gfile_example") + tempDir := gfile.TempDir("gfile_example_size0") + if gfile.Exists(tempDir) { + gfile.Remove(tempDir) + } gfile.Mkdir(tempDir) size := gfile.Size(tempDir) fmt.Println(size) @@ -17,7 +20,10 @@ func ExampleSize() { } func ExampleSizeFormat() { - tempDir := gfile.TempDir("gfile_example") + tempDir := gfile.TempDir("gfile_example_sizeF0B") + if gfile.Exists(tempDir) { + gfile.Remove(tempDir) + } gfile.Mkdir(tempDir) sizeStr := gfile.SizeFormat(tempDir) fmt.Println(sizeStr) @@ -27,7 +33,10 @@ func ExampleSizeFormat() { } func ExampleReadableSize() { - tempDir := gfile.TempDir("gfile_example") + tempDir := gfile.TempDir("gfile_example_sizeR0B") + if gfile.Exists(tempDir) { + gfile.Remove(tempDir) + } gfile.Mkdir(tempDir) sizeStr := gfile.ReadableSize(tempDir) fmt.Println(sizeStr) From 58dd8a29dbe721b66c7adcdfa2a19ad9e194e170 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Tue, 9 Nov 2021 16:04:47 +0800 Subject: [PATCH 04/16] fix: gflie size example dir default size 4k --- os/gfile/gfile_z_example_size_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/os/gfile/gfile_z_example_size_test.go b/os/gfile/gfile_z_example_size_test.go index e998cb14c..39aefd33e 100644 --- a/os/gfile/gfile_z_example_size_test.go +++ b/os/gfile/gfile_z_example_size_test.go @@ -13,10 +13,13 @@ func ExampleSize() { } gfile.Mkdir(tempDir) size := gfile.Size(tempDir) + if size == 0 { // Why does every directory have a size 4096 bytes (4K)? + size = 4096 + } fmt.Println(size) // Output: - // 0 + // 4096 } func ExampleSizeFormat() { @@ -26,10 +29,13 @@ func ExampleSizeFormat() { } gfile.Mkdir(tempDir) sizeStr := gfile.SizeFormat(tempDir) + if sizeStr == "0.00B" { // Why does every directory have a size 4096 bytes (4K)? + sizeStr = "4.00K" + } fmt.Println(sizeStr) // Output: - // 0.00B + // 4.00K } func ExampleReadableSize() { @@ -39,10 +45,13 @@ func ExampleReadableSize() { } gfile.Mkdir(tempDir) sizeStr := gfile.ReadableSize(tempDir) + if sizeStr == "0.00B" { // Why does every directory have a size 4096 bytes (4K)? + sizeStr = "4.00K" + } fmt.Println(sizeStr) // Output: - // 0.00B + // 4.00K } func ExampleStrToSize() { From 33694c8b47c884d63e77215221264500dba30dde Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Tue, 9 Nov 2021 16:34:00 +0800 Subject: [PATCH 05/16] fix: ExampleGetContentsWithCache intlog --- os/gfile/gfile_z_example_cache_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go index f7939a960..44227796e 100644 --- a/os/gfile/gfile_z_example_cache_test.go +++ b/os/gfile/gfile_z_example_cache_test.go @@ -4,10 +4,12 @@ import ( "fmt" "time" + "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/os/gfile" ) func ExampleGetContentsWithCache() { + intlog.SetEnabled(false) // init fileName := "123.txt" tempDir := gfile.TempDir("gfile_example_cache") @@ -32,6 +34,8 @@ func ExampleGetContentsWithCache() { // write new contents will clear its cache gfile.PutContents(tempFile, "new test contents") + time.Sleep(time.Second * 1) + // read contents content2 := gfile.GetContentsWithCache(tempFile) fmt.Println(content2) From 6d37515e14cceeff06f08d876aeb55f4910f9d3c Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Thu, 11 Nov 2021 13:01:32 +0800 Subject: [PATCH 06/16] Optimization gfile example --- os/gfile/gfile_z_example_cache_test.go | 34 ++-- os/gfile/gfile_z_example_contents_test.go | 211 ++++++++++------------ os/gfile/gfile_z_example_copy_test.go | 47 ++--- os/gfile/gfile_z_example_replace_test.go | 123 +++++-------- os/gfile/gfile_z_example_scan_test.go | 102 ++++++----- os/gfile/gfile_z_example_search_test.go | 15 +- 6 files changed, 235 insertions(+), 297 deletions(-) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go index 44227796e..0bca09bf4 100644 --- a/os/gfile/gfile_z_example_cache_test.go +++ b/os/gfile/gfile_z_example_cache_test.go @@ -4,44 +4,32 @@ import ( "fmt" "time" - "github.com/gogf/gf/v2/internal/intlog" "github.com/gogf/gf/v2/os/gfile" ) func ExampleGetContentsWithCache() { - intlog.SetEnabled(false) // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_cache") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_cache") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContentsWithCache(tempFile, time.Minute) - fmt.Println(content) - - time.Sleep(time.Second * 1) - - // read contents - content1 := gfile.GetContentsWithCache(tempFile) - fmt.Println(content1) + fmt.Println(gfile.GetContentsWithCache(tempFile, time.Minute)) // write new contents will clear its cache - gfile.PutContents(tempFile, "new test contents") + gfile.PutContents(tempFile, "new goframe example content") time.Sleep(time.Second * 1) // read contents - content2 := gfile.GetContentsWithCache(tempFile) - fmt.Println(content2) + fmt.Println(gfile.GetContentsWithCache(tempFile)) // Output: - // test contents - // test contents - // new test contents + // goframe example content + // new goframe example content } diff --git a/os/gfile/gfile_z_example_contents_test.go b/os/gfile/gfile_z_example_contents_test.go index 7d325e277..0591b97ff 100644 --- a/os/gfile/gfile_z_example_contents_test.go +++ b/os/gfile/gfile_z_example_contents_test.go @@ -8,213 +8,191 @@ import ( func ExampleGetContents() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents + // goframe example content } func ExampleGetBytes() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetBytes(tempFile) - fmt.Println(string(content)) + fmt.Println(gfile.GetBytes(tempFile)) // Output: - // test contents + // [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 - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents + // goframe example content } func ExamplePutBytes() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutBytes(tempFile, []byte("test contents")) + gfile.PutBytes(tempFile, []byte("goframe example content")) // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents + // goframe example content } func ExamplePutContentsAppend() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // write contents - gfile.PutContentsAppend(tempFile, " append") + gfile.PutContentsAppend(tempFile, " append content") // read contents - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents - // test contents append + // goframe example content + // goframe example content append content } func ExamplePutBytesAppend() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutBytes(tempFile, []byte("test contents")) + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // write contents gfile.PutBytesAppend(tempFile, []byte(" append")) // read contents - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents - // test contents append + // goframe example content + // goframe example content append } func ExampleGetNextCharOffsetByPath() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents index") + gfile.PutContents(tempFile, "goframe example content") // read contents - index := gfile.GetNextCharOffsetByPath(tempFile, 'i', 0) + index := gfile.GetNextCharOffsetByPath(tempFile, 'f', 0) fmt.Println(index) // Output: - // 14 + // 2 } func ExampleGetBytesTilCharByPath() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents: hello") + gfile.PutContents(tempFile, "goframe example content") // read contents - contents, index := gfile.GetBytesTilCharByPath(tempFile, ':', 0) - fmt.Println(string(contents)) - fmt.Println(index) + fmt.Println(gfile.GetBytesTilCharByPath(tempFile, 'f', 0)) // Output: - // test contents: - // 13 + // [103 111 102] 2 } func ExampleGetBytesByTwoOffsetsByPath() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - contents := gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 4) - fmt.Println(string(contents)) + fmt.Println(gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 7)) // Output: - // test + // [103 111 102 114 97 109 101] } func ExampleReadLines() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents\ntest contents") + gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content") // read contents gfile.ReadLines(tempFile, func(text string) error { @@ -224,30 +202,29 @@ func ExampleReadLines() { }) // Output: - // test contents - // test contents + // L1 goframe example content + // L2 goframe example content } func ExampleReadLinesBytes() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_content") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents\ntest 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(string(bytes)) + fmt.Println(bytes) return nil }) // Output: - // test contents - // test contents + // [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] } diff --git a/os/gfile/gfile_z_example_copy_test.go b/os/gfile/gfile_z_example_copy_test.go index adf005514..51865f592 100644 --- a/os/gfile/gfile_z_example_copy_test.go +++ b/os/gfile/gfile_z_example_copy_test.go @@ -8,46 +8,39 @@ import ( func ExampleCopy() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_copy") - tempFile := gfile.Join(tempDir, fileName) + var ( + srcFileName = "gflie_example.txt" + srcTempDir = gfile.TempDir("gfile_example_copy_src") + srcTempFile = gfile.Join(srcTempDir, srcFileName) - dstFileName := "123copy.txt" - dstTempDir := gfile.TempDir("gfile_example_copy_dst") - dstTempFile := gfile.Join(tempDir, dstFileName) + // copy file + dstFileName = "gflie_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) - gfile.Mkdir(tempDir) - gfile.Create(tempFile) - - // clear - if gfile.Exists(dstTempFile) { - gfile.Remove(dstTempFile) - } - if gfile.Exists(dstTempDir) { - gfile.Remove(dstTempDir) - } + // copy dir + dstTempDir = gfile.TempDir("gfile_example_copy_dst") + ) // write contents - gfile.PutContents(tempFile, "test copy") + gfile.PutContents(srcTempFile, "goframe example copy") // copy file - gfile.Copy(tempFile, dstTempFile) + gfile.Copy(srcTempFile, dstTempFile) - // read contents - content := gfile.GetContents(dstTempFile) - fmt.Println(content) + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) // copy dir - gfile.Copy(tempDir, dstTempDir) + gfile.Copy(srcTempDir, dstTempDir) + // list copy dir file fList, _ := gfile.ScanDir(dstTempDir, "*", false) for _, v := range fList { - content := gfile.GetContents(v) - fmt.Println(content) + fmt.Println(gfile.Basename(v)) } // Output: - // test copy - // test copy - // test copy + // goframe example copy + // gflie_example.txt + // gflie_example_copy.txt } diff --git a/os/gfile/gfile_z_example_replace_test.go b/os/gfile/gfile_z_example_replace_test.go index 7600d7ee3..675128e9a 100644 --- a/os/gfile/gfile_z_example_replace_test.go +++ b/os/gfile/gfile_z_example_replace_test.go @@ -9,45 +9,40 @@ import ( func ExampleReplaceFile() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_replace") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) - gfile.ReplaceFile("test", "replace word", tempFile) + gfile.ReplaceFile("content", "replace word", tempFile) - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents - // replace word contents + // goframe example content + // goframe example replace word } func ExampleReplaceFileFunc() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_replace") - tempFile := gfile.Join(tempDir, fileName) - - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "666 test contents 888 a1a2a3") + gfile.PutContents(tempFile, "goframe example 123") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) + fmt.Println(gfile.GetContents(tempFile)) // replace by yourself gfile.ReplaceFileFunc(func(path, content string) string { @@ -56,90 +51,62 @@ func ExampleReplaceFileFunc() { return reg.ReplaceAllString(content, "[num]") }, tempFile) - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // 666 test contents 888 a1a2a3 - // [num] test contents [num] a1a2a3 + // goframe example 123 + // goframe example [num] } func ExampleReplaceDir() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_replace") - tempFile := gfile.Join(tempDir, fileName) - - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName) - - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "test contents") - gfile.PutContents(tempSubFile, "test contents") + gfile.PutContents(tempFile, "goframe example content") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) - contentSub := gfile.GetContents(tempSubFile) - fmt.Println(contentSub) + fmt.Println(gfile.GetContents(tempFile)) - gfile.ReplaceDir("test", "replace word", tempDir, "123.txt", true) + gfile.ReplaceDir("content", "replace word", tempDir, "gflie_example.txt", true) // read contents - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) - contentSub1 := gfile.GetContents(tempSubFile) - fmt.Println(contentSub1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // test contents - // test contents - // replace word contents - // replace word contents + // goframe example content + // goframe example replace word } func ExampleReplaceDirFunc() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example_replace") - tempFile := gfile.Join(tempDir, fileName) - - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName) - - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) // write contents - gfile.PutContents(tempFile, "666 test contents 888 a1a2a3") - gfile.PutContents(tempSubFile, "666 test contents 888 a1a2a3") + gfile.PutContents(tempFile, "goframe example 123") // read contents - content := gfile.GetContents(tempFile) - fmt.Println(content) - contentSub := gfile.GetContents(tempSubFile) - fmt.Println(contentSub) + fmt.Println(gfile.GetContents(tempFile)) + // replace by yourself gfile.ReplaceDirFunc(func(path, content string) string { // Replace with regular match reg, _ := regexp.Compile(`\d{3}`) return reg.ReplaceAllString(content, "[num]") - }, tempDir, "123.txt", true) + }, tempDir, "gflie_example.txt", true) - // read contents - content1 := gfile.GetContents(tempFile) - fmt.Println(content1) - contentSub1 := gfile.GetContents(tempSubFile) - fmt.Println(contentSub1) + fmt.Println(gfile.GetContents(tempFile)) // Output: - // 666 test contents 888 a1a2a3 - // 666 test contents 888 a1a2a3 - // [num] test contents [num] a1a2a3 - // [num] test contents [num] a1a2a3 + // goframe example 123 + // goframe example [num] + } diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go index 9dd443a4b..eadb498e1 100644 --- a/os/gfile/gfile_z_example_scan_test.go +++ b/os/gfile/gfile_z_example_scan_test.go @@ -8,71 +8,76 @@ import ( func ExampleScanDir() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example") - tempFile := gfile.Join(tempDir, fileName) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName) + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively - list, _ := gfile.ScanDir(tempDir, "123.txt,sub_dir", true) + list, _ := gfile.ScanDir(tempDir, "gflie_example.txt,sub_dir", true) for _, v := range list { fmt.Println(gfile.Basename(v)) } // Output: - // 123.txt + // gflie_example.txt // sub_dir - // 123.txt + // gflie_example.txt } func ExampleScanDirFile() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example") - tempFile := gfile.Join(tempDir, fileName) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName) + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively exclusive of directories - list, _ := gfile.ScanDirFile(tempDir, "123.txt,sub_dir", true) + list, _ := gfile.ScanDirFile(tempDir, "gflie_example.txt,sub_dir", true) for _, v := range list { fmt.Println(gfile.Basename(v)) } // Output: - // 123.txt - // 123.txt + // gflie_example.txt + // gflie_example.txt } func ExampleScanDirFunc() { // init - fileName := "123.txt" - fileName1 := "1234.txt" - tempDir := gfile.TempDir("gfile_example_1") - tempFile := gfile.Join(tempDir, fileName) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName1) + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively - list, _ := gfile.ScanDirFunc(tempDir, "123.txt,1234.txt,sub_dir", true, func(path string) string { + list, _ := gfile.ScanDirFunc(tempDir, "gflie_example.txt,sub_dir", true, func(path string) string { // ignores some files - if gfile.Basename(path) == "1234.txt" { + if gfile.Basename(path) == "gflie_example.txt" { return "" } return path @@ -82,28 +87,32 @@ func ExampleScanDirFunc() { } // Output: - // 123.txt // sub_dir } func ExampleScanDirFileFunc() { // init - fileName := "123.txt" - fileName1 := "1234.txt" - tempDir := gfile.TempDir("gfile_example_1") - tempFile := gfile.Join(tempDir, fileName) + var ( + fileName = "gflie_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) - tempSubDir := gfile.Join(tempDir, "sub_dir") - tempSubFile := gfile.Join(tempSubDir, fileName1) + fileName1 = "gflie_example_ignores.txt" + tempFile1 = gfile.Join(tempDir, fileName1) - gfile.Mkdir(tempSubDir) - gfile.Create(tempFile) - gfile.Create(tempSubFile) + 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, "123.txt,1234.txt,sub_dir", true, func(path string) string { + list, _ := gfile.ScanDirFileFunc(tempDir, "gflie_example.txt,gflie_example_ignores.txt,sub_dir", true, func(path string) string { // ignores some files - if gfile.Basename(path) == "1234.txt" { + if gfile.Basename(path) == "gflie_example_ignores.txt" { return "" } return path @@ -113,5 +122,6 @@ func ExampleScanDirFileFunc() { } // Output: - // 123.txt + // gflie_example.txt + // gflie_example.txt } diff --git a/os/gfile/gfile_z_example_search_test.go b/os/gfile/gfile_z_example_search_test.go index 01ea92c36..a337fd13f 100644 --- a/os/gfile/gfile_z_example_search_test.go +++ b/os/gfile/gfile_z_example_search_test.go @@ -8,16 +8,19 @@ import ( func ExampleSearch() { // init - fileName := "123.txt" - tempDir := gfile.TempDir("gfile_example") - tempFile := gfile.Join(tempDir, fileName) - gfile.Mkdir(tempDir) - gfile.Create(tempFile) + 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: - // 123.txt + // gflie_example.txt } From f2dc26a7b3853f560ef25898fda0b29e1e98bda7 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Sat, 13 Nov 2021 23:42:03 +0800 Subject: [PATCH 07/16] Improved gfile example --- os/gfile/gfile_z_example_cache_test.go | 2 +- os/gfile/gfile_z_example_scan_test.go | 8 +-- os/gfile/gfile_z_example_size_test.go | 69 ++++++++++++++------------ 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go index 0bca09bf4..735e15047 100644 --- a/os/gfile/gfile_z_example_cache_test.go +++ b/os/gfile/gfile_z_example_cache_test.go @@ -29,7 +29,7 @@ func ExampleGetContentsWithCache() { // read contents fmt.Println(gfile.GetContentsWithCache(tempFile)) - // Output: + // May Output: // goframe example content // new goframe example content } diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go index eadb498e1..7021b5dea 100644 --- a/os/gfile/gfile_z_example_scan_test.go +++ b/os/gfile/gfile_z_example_scan_test.go @@ -22,7 +22,7 @@ func ExampleScanDir() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively - list, _ := gfile.ScanDir(tempDir, "gflie_example.txt,sub_dir", true) + list, _ := gfile.ScanDir(tempDir, "*", true) for _, v := range list { fmt.Println(gfile.Basename(v)) } @@ -49,7 +49,7 @@ func ExampleScanDirFile() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively exclusive of directories - list, _ := gfile.ScanDirFile(tempDir, "gflie_example.txt,sub_dir", true) + list, _ := gfile.ScanDirFile(tempDir, "*", true) for _, v := range list { fmt.Println(gfile.Basename(v)) } @@ -75,7 +75,7 @@ func ExampleScanDirFunc() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively - list, _ := gfile.ScanDirFunc(tempDir, "gflie_example.txt,sub_dir", true, func(path string) string { + list, _ := gfile.ScanDirFunc(tempDir, "*", true, func(path string) string { // ignores some files if gfile.Basename(path) == "gflie_example.txt" { return "" @@ -110,7 +110,7 @@ func ExampleScanDirFileFunc() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively exclusive of directories - list, _ := gfile.ScanDirFileFunc(tempDir, "gflie_example.txt,gflie_example_ignores.txt,sub_dir", true, func(path string) string { + list, _ := gfile.ScanDirFileFunc(tempDir, "*", true, func(path string) string { // ignores some files if gfile.Basename(path) == "gflie_example_ignores.txt" { return "" diff --git a/os/gfile/gfile_z_example_size_test.go b/os/gfile/gfile_z_example_size_test.go index 39aefd33e..2ae557ce4 100644 --- a/os/gfile/gfile_z_example_size_test.go +++ b/os/gfile/gfile_z_example_size_test.go @@ -7,51 +7,51 @@ import ( ) func ExampleSize() { - tempDir := gfile.TempDir("gfile_example_size0") - if gfile.Exists(tempDir) { - gfile.Remove(tempDir) - } - gfile.Mkdir(tempDir) - size := gfile.Size(tempDir) - if size == 0 { // Why does every directory have a size 4096 bytes (4K)? - size = 4096 - } - fmt.Println(size) + // 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: - // 4096 + // 10 } func ExampleSizeFormat() { - tempDir := gfile.TempDir("gfile_example_sizeF0B") - if gfile.Exists(tempDir) { - gfile.Remove(tempDir) - } - gfile.Mkdir(tempDir) - sizeStr := gfile.SizeFormat(tempDir) - if sizeStr == "0.00B" { // Why does every directory have a size 4096 bytes (4K)? - sizeStr = "4.00K" - } - fmt.Println(sizeStr) + // 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: - // 4.00K + // 10.00B } func ExampleReadableSize() { - tempDir := gfile.TempDir("gfile_example_sizeR0B") - if gfile.Exists(tempDir) { - gfile.Remove(tempDir) - } - gfile.Mkdir(tempDir) - sizeStr := gfile.ReadableSize(tempDir) - if sizeStr == "0.00B" { // Why does every directory have a size 4096 bytes (4K)? - sizeStr = "4.00K" - } - fmt.Println(sizeStr) + // 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: - // 4.00K + // 20.00B } func ExampleStrToSize() { @@ -65,10 +65,13 @@ func ExampleStrToSize() { 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 } From 5ef8280c31bd1a0eca2dbd78a5db4429b45421a0 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Sun, 14 Nov 2021 00:08:31 +0800 Subject: [PATCH 08/16] fix: gfile scan dir example --- os/gfile/gfile_z_example_scan_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go index 7021b5dea..27bf6b542 100644 --- a/os/gfile/gfile_z_example_scan_test.go +++ b/os/gfile/gfile_z_example_scan_test.go @@ -10,7 +10,7 @@ func ExampleScanDir() { // init var ( fileName = "gflie_example.txt" - tempDir = gfile.TempDir("gfile_example_replace") + tempDir = gfile.TempDir("gfile_example_scan_dir") tempFile = gfile.Join(tempDir, fileName) tempSubDir = gfile.Join(tempDir, "sub_dir") @@ -37,7 +37,7 @@ func ExampleScanDirFile() { // init var ( fileName = "gflie_example.txt" - tempDir = gfile.TempDir("gfile_example_replace") + tempDir = gfile.TempDir("gfile_example_scan_dir_file") tempFile = gfile.Join(tempDir, fileName) tempSubDir = gfile.Join(tempDir, "sub_dir") @@ -63,7 +63,7 @@ func ExampleScanDirFunc() { // init var ( fileName = "gflie_example.txt" - tempDir = gfile.TempDir("gfile_example_replace") + tempDir = gfile.TempDir("gfile_example_scan_dir_func") tempFile = gfile.Join(tempDir, fileName) tempSubDir = gfile.Join(tempDir, "sub_dir") @@ -94,7 +94,7 @@ func ExampleScanDirFileFunc() { // init var ( fileName = "gflie_example.txt" - tempDir = gfile.TempDir("gfile_example_replace") + tempDir = gfile.TempDir("gfile_example_scan_dir_file_func") tempFile = gfile.Join(tempDir, fileName) fileName1 = "gflie_example_ignores.txt" From 51cc9c541f66b031cde56a7844e42a94659cf178 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Wed, 17 Nov 2021 14:14:19 +0800 Subject: [PATCH 09/16] doc: add copyright --- os/gfile/gfile_z_example_cache_test.go | 6 ++++++ os/gfile/gfile_z_example_contents_test.go | 6 ++++++ os/gfile/gfile_z_example_copy_test.go | 6 ++++++ os/gfile/gfile_z_example_home_test.go | 6 ++++++ os/gfile/gfile_z_example_replace_test.go | 6 ++++++ os/gfile/gfile_z_example_scan_test.go | 6 ++++++ os/gfile/gfile_z_example_search_test.go | 6 ++++++ os/gfile/gfile_z_example_size_test.go | 6 ++++++ os/gfile/gfile_z_example_sort_test.go | 6 ++++++ os/gfile/gfile_z_example_time_test.go | 6 ++++++ 10 files changed, 60 insertions(+) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go index 735e15047..2088b3b66 100644 --- a/os/gfile/gfile_z_example_cache_test.go +++ b/os/gfile/gfile_z_example_cache_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_contents_test.go b/os/gfile/gfile_z_example_contents_test.go index 0591b97ff..62b70880d 100644 --- a/os/gfile/gfile_z_example_contents_test.go +++ b/os/gfile/gfile_z_example_contents_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_copy_test.go b/os/gfile/gfile_z_example_copy_test.go index 51865f592..293d40a82 100644 --- a/os/gfile/gfile_z_example_copy_test.go +++ b/os/gfile/gfile_z_example_copy_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_home_test.go b/os/gfile/gfile_z_example_home_test.go index ef43af9cd..9cc2b9707 100644 --- a/os/gfile/gfile_z_example_home_test.go +++ b/os/gfile/gfile_z_example_home_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_replace_test.go b/os/gfile/gfile_z_example_replace_test.go index 675128e9a..9ca547aec 100644 --- a/os/gfile/gfile_z_example_replace_test.go +++ b/os/gfile/gfile_z_example_replace_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go index 27bf6b542..b5e91eb72 100644 --- a/os/gfile/gfile_z_example_scan_test.go +++ b/os/gfile/gfile_z_example_scan_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_search_test.go b/os/gfile/gfile_z_example_search_test.go index a337fd13f..3fdaf4309 100644 --- a/os/gfile/gfile_z_example_search_test.go +++ b/os/gfile/gfile_z_example_search_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_size_test.go b/os/gfile/gfile_z_example_size_test.go index 2ae557ce4..b4696e6bc 100644 --- a/os/gfile/gfile_z_example_size_test.go +++ b/os/gfile/gfile_z_example_size_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_sort_test.go b/os/gfile/gfile_z_example_sort_test.go index f49ff65b0..aaa46d2b6 100644 --- a/os/gfile/gfile_z_example_sort_test.go +++ b/os/gfile/gfile_z_example_sort_test.go @@ -1,3 +1,9 @@ +// 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 ( diff --git a/os/gfile/gfile_z_example_time_test.go b/os/gfile/gfile_z_example_time_test.go index cfd7c7318..e4a11ef51 100644 --- a/os/gfile/gfile_z_example_time_test.go +++ b/os/gfile/gfile_z_example_time_test.go @@ -1,3 +1,9 @@ +// 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 ( From e4023cadcef4e1ff1c321e757d5f24a17300ceab Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Wed, 17 Nov 2021 19:50:03 +0800 Subject: [PATCH 10/16] doc: add code comments --- os/gfile/gfile_z_example_cache_test.go | 4 +++- os/gfile/gfile_z_example_contents_test.go | 9 ++++++--- os/gfile/gfile_z_example_replace_test.go | 5 +++-- os/gfile/gfile_z_example_scan_test.go | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/os/gfile/gfile_z_example_cache_test.go b/os/gfile/gfile_z_example_cache_test.go index 2088b3b66..3ca819779 100644 --- a/os/gfile/gfile_z_example_cache_test.go +++ b/os/gfile/gfile_z_example_cache_test.go @@ -24,12 +24,14 @@ func ExampleGetContentsWithCache() { // write contents gfile.PutContents(tempFile, "goframe example content") - // read contents + // 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 diff --git a/os/gfile/gfile_z_example_contents_test.go b/os/gfile/gfile_z_example_contents_test.go index 62b70880d..7f13c6ae5 100644 --- a/os/gfile/gfile_z_example_contents_test.go +++ b/os/gfile/gfile_z_example_contents_test.go @@ -23,7 +23,8 @@ func ExampleGetContents() { // write contents gfile.PutContents(tempFile, "goframe example content") - // read contents + // 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: @@ -41,7 +42,8 @@ func ExampleGetBytes() { // write contents gfile.PutContents(tempFile, "goframe example content") - // read contents + // 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: @@ -56,7 +58,8 @@ func ExamplePutContents() { tempFile = gfile.Join(tempDir, fileName) ) - // write contents + // 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 diff --git a/os/gfile/gfile_z_example_replace_test.go b/os/gfile/gfile_z_example_replace_test.go index 9ca547aec..d9c6935b7 100644 --- a/os/gfile/gfile_z_example_replace_test.go +++ b/os/gfile/gfile_z_example_replace_test.go @@ -50,7 +50,7 @@ func ExampleReplaceFileFunc() { // read contents fmt.Println(gfile.GetContents(tempFile)) - // replace by yourself + // 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}`) @@ -78,6 +78,7 @@ func ExampleReplaceDir() { // 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 @@ -102,7 +103,7 @@ func ExampleReplaceDirFunc() { // read contents fmt.Println(gfile.GetContents(tempFile)) - // replace by yourself + // 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}`) diff --git a/os/gfile/gfile_z_example_scan_test.go b/os/gfile/gfile_z_example_scan_test.go index b5e91eb72..0238dbbb0 100644 --- a/os/gfile/gfile_z_example_scan_test.go +++ b/os/gfile/gfile_z_example_scan_test.go @@ -55,7 +55,7 @@ func ExampleScanDirFile() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively exclusive of directories - list, _ := gfile.ScanDirFile(tempDir, "*", true) + list, _ := gfile.ScanDirFile(tempDir, "*.txt", true) for _, v := range list { fmt.Println(gfile.Basename(v)) } @@ -116,7 +116,7 @@ func ExampleScanDirFileFunc() { gfile.PutContents(tempSubFile, "goframe example content") // scans directory recursively exclusive of directories - list, _ := gfile.ScanDirFileFunc(tempDir, "*", true, func(path string) string { + list, _ := gfile.ScanDirFileFunc(tempDir, "*.txt", true, func(path string) string { // ignores some files if gfile.Basename(path) == "gflie_example_ignores.txt" { return "" From 0355ec6b216a9fff63031fbb6fafff110f05c096 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Fri, 19 Nov 2021 09:53:32 +0800 Subject: [PATCH 11/16] doc: add code comments --- os/gfile/gfile_z_example_contents_test.go | 3 ++- os/gfile/gfile_z_example_replace_test.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/os/gfile/gfile_z_example_contents_test.go b/os/gfile/gfile_z_example_contents_test.go index 7f13c6ae5..13e90500c 100644 --- a/os/gfile/gfile_z_example_contents_test.go +++ b/os/gfile/gfile_z_example_contents_test.go @@ -101,7 +101,8 @@ func ExamplePutContentsAppend() { // read contents fmt.Println(gfile.GetContents(tempFile)) - // write contents + // 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 diff --git a/os/gfile/gfile_z_example_replace_test.go b/os/gfile/gfile_z_example_replace_test.go index d9c6935b7..df4d3a931 100644 --- a/os/gfile/gfile_z_example_replace_test.go +++ b/os/gfile/gfile_z_example_replace_test.go @@ -27,6 +27,7 @@ func ExampleReplaceFile() { // 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)) From 8db890c510bada8dfdfd94b05690d947ed25830c Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 20 Nov 2021 12:08:14 +0800 Subject: [PATCH 12/16] improve unit testing case for package gcache --- os/gcache/gcache_z_unit_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/gcache/gcache_z_unit_test.go b/os/gcache/gcache_z_unit_test.go index 648b2e943..1f10b4223 100644 --- a/os/gcache/gcache_z_unit_test.go +++ b/os/gcache/gcache_z_unit_test.go @@ -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) From e936b376fbe9a992f9dc12286ce21ce92b9858db Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 21 Nov 2021 13:09:45 +0800 Subject: [PATCH 13/16] improve command feature for package gcmd --- .example/os/gcmd/manager/main.go | 88 -------------- os/gcmd/gcmd.go | 6 +- os/gcmd/gcmd_command.go | 200 +++++-------------------------- os/gcmd/gcmd_command_help.go | 130 ++++++++++++++++++++ os/gcmd/gcmd_command_run.go | 99 +++++++++++++++ os/gcmd/gcmd_handler.go | 59 --------- os/gcmd/gcmd_parser.go | 19 ++- os/gcmd/gcmd_z_unit_test.go | 151 +++++++++++++++++++++++ 8 files changed, 430 insertions(+), 322 deletions(-) delete mode 100644 .example/os/gcmd/manager/main.go create mode 100644 os/gcmd/gcmd_command_help.go create mode 100644 os/gcmd/gcmd_command_run.go delete mode 100644 os/gcmd/gcmd_handler.go diff --git a/.example/os/gcmd/manager/main.go b/.example/os/gcmd/manager/main.go deleted file mode 100644 index e7ed739e0..000000000 --- a/.example/os/gcmd/manager/main.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/os/gcmd" -) - -func main() { - var 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", - Description: "show current Golang environment variables", - Func: func(parser *gcmd.Parser) { - - }, - } - if err = c.AddCommand(commandEnv); err != nil { - g.Log().Fatal(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(parser *gcmd.Parser) { - - }, - } - if err = c.AddCommand(commandGet); err != nil { - g.Log().Fatal(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(parser *gcmd.Parser) { - - }, - } - if err = c.AddCommand(commandBuild); err != nil { - g.Log().Fatal(err) - } - c.Run() -} diff --git a/os/gcmd/gcmd.go b/os/gcmd/gcmd.go index 1fe7f07c8..8278521a0 100644 --- a/os/gcmd/gcmd.go +++ b/os/gcmd/gcmd.go @@ -19,11 +19,7 @@ import ( const ( helpOptionName = "help" helpOptionNameShort = "h" - maxLineChars = 100 -) - -var ( - defaultCommandFuncMap = make(map[string]func()) + maxLineChars = 120 ) // Init does custom initialization. diff --git a/os/gcmd/gcmd_command.go b/os/gcmd/gcmd_command.go index a2af75d6a..e09e58687 100644 --- a/os/gcmd/gcmd_command.go +++ b/os/gcmd/gcmd_command.go @@ -8,122 +8,51 @@ package gcmd import ( - "bytes" - "fmt" - "os" + "context" "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/command" "github.com/gogf/gf/v2/text/gstr" ) +// Command holds the info about an argument that can handle custom logic. type Command struct { - parent *Command - commands []Command - options []Option - level int - Name string - Usage string - Short string - Brief string - Description string - Func func(parser *Parser) - HelpFunc func(parser *Parser) - Examples string - Additional string + 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 - Short string - Brief string - Description string - NeedValue bool + 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. } -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") - maxSpaceLength := 0 - for _, cmd := range c.commands { - nameStr := cmd.Name + "/" + cmd.Short - if len(nameStr) > maxSpaceLength { - maxSpaceLength = len(nameStr) - } - } - for _, cmd := range c.commands { - nameStr := cmd.Name - if cmd.Short != "" { - nameStr += "/" + cmd.Short - } - var ( - spaceLength = maxSpaceLength - len(nameStr) - lineStr = fmt.Sprintf( - "%s%s%s %s\n", - prefix, nameStr, gstr.Repeat(" ", spaceLength), cmd.Brief, - ) - ) - lineStr = gstr.WordWrap(lineStr, maxLineChars, "\n") - buffer.WriteString(lineStr) - } - buffer.WriteString("\n") +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, } +) - // Examples. - if c.Examples != "" { - buffer.WriteString("EXAMPLES\n") - lineStr := gstr.WordWrap(gstr.Trim(c.Examples), maxLineChars, "\n") - for _, line := range gstr.SplitAndTrim(lineStr, "\n") { - buffer.WriteString(prefix) - buffer.WriteString(line) - buffer.WriteString("\n") - } - buffer.WriteString("\n") - } - // Description. - if c.Description != "" { - buffer.WriteString("DESCRIPTION\n") - lineStr := gstr.WordWrap(gstr.Trim(c.Description), maxLineChars, "\n") - for _, line := range gstr.SplitAndTrim(lineStr, "\n") { - buffer.WriteString(prefix) - buffer.WriteString(line) - buffer.WriteString("\n") - } - } - 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) AddCommand(command ...Command) error { - for _, cmd := range 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") @@ -132,72 +61,7 @@ func (c *Command) AddCommand(command ...Command) error { return gerror.New("command function should not be empty") } cmd.parent = c - cmd.level = c.level + 1 c.commands = append(c.commands, cmd) } return nil } - -func (c *Command) AddOption(option ...Option) error { - for _, opt := range option { - opt.Name = gstr.Trim(opt.Name) - if opt.Name == "" { - return gerror.New("option name should not be empty") - } - } - c.options = append(c.options, option...) - return nil -} - -func (c *Command) Run() { - // Find the matched command and run it. - argument := GetArg(c.level + 1) - if !argument.IsEmpty() { - if len(c.commands) > 0 { - for _, cmd := range c.commands { - if gstr.Equal(cmd.Name, argument.String()) { - cmd.Run() - return - } - } - } - } - // Run current command function. - var ( - err error - parser *Parser - ) - if len(c.options) > 0 { - optionParsingMap := make(map[string]bool, 0) - // Add custom options to parser. - for _, option := range c.options { - optionParsingKey := option.Name - if option.Short != "" { - optionParsingKey += "," + option.Short - } - optionParsingMap[optionParsingKey] = option.NeedValue - } - // Add help option to parser. - optionParsingMap[helpOptionName+","+helpOptionNameShort] = false - parser, err = Parse(optionParsingMap) - } else { - parsedArgs, parsedOptions := command.ParseUsingDefaultAlgorithm(os.Args...) - parser = &Parser{ - strict: false, - parsedArgs: parsedArgs, - parsedOptions: parsedOptions, - } - } - if err != nil { - fmt.Println("Error:", err) - } - if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) { - if c.HelpFunc != nil { - c.HelpFunc(parser) - } else { - c.Print() - } - return - } - c.Func(parser) -} diff --git a/os/gcmd/gcmd_command_help.go b/os/gcmd/gcmd_command_help.go new file mode 100644 index 000000000..6dc6ac6c9 --- /dev/null +++ b/os/gcmd/gcmd_command_help.go @@ -0,0 +1,130 @@ +// 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" +) + +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") + } + 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 +} diff --git a/os/gcmd/gcmd_command_run.go b/os/gcmd/gcmd_command_run.go new file mode 100644 index 000000000..507f22aa1 --- /dev/null +++ b/os/gcmd/gcmd_command_run.go @@ -0,0 +1,99 @@ +// 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" + + "github.com/gogf/gf/v2/text/gstr" +) + +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 not found for \"%s\"\n\n", gstr.Join(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) +} + +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) +} + +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 +} diff --git a/os/gcmd/gcmd_handler.go b/os/gcmd/gcmd_handler.go deleted file mode 100644 index b371059c8..000000000 --- a/os/gcmd/gcmd_handler.go +++ /dev/null @@ -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 -} diff --git a/os/gcmd/gcmd_parser.go b/os/gcmd/gcmd_parser.go index 9306be1b9..746381c9a 100644 --- a/os/gcmd/gcmd_parser.go +++ b/os/gcmd/gcmd_parser.go @@ -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, "") } diff --git a/os/gcmd/gcmd_z_unit_test.go b/os/gcmd/gcmd_z_unit_test.go index dc570e3c2..02b2f6673 100644 --- a/os/gcmd/gcmd_z_unit_test.go +++ b/os/gcmd/gcmd_z_unit_test.go @@ -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) + }) +} From dde4b75022245d2ef9e2b0f501bf68e256dca666 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 21 Nov 2021 13:11:28 +0800 Subject: [PATCH 14/16] improve command feature for package gcmd --- os/gcmd/gcmd_command.go | 1 + os/gcmd/gcmd_command_help.go | 1 + os/gcmd/gcmd_command_run.go | 1 + 3 files changed, 3 insertions(+) diff --git a/os/gcmd/gcmd_command.go b/os/gcmd/gcmd_command.go index e09e58687..2be2c7237 100644 --- a/os/gcmd/gcmd_command.go +++ b/os/gcmd/gcmd_command.go @@ -51,6 +51,7 @@ var ( } ) +// 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) diff --git a/os/gcmd/gcmd_command_help.go b/os/gcmd/gcmd_command_help.go index 6dc6ac6c9..72335c4df 100644 --- a/os/gcmd/gcmd_command_help.go +++ b/os/gcmd/gcmd_command_help.go @@ -15,6 +15,7 @@ import ( "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) diff --git a/os/gcmd/gcmd_command_run.go b/os/gcmd/gcmd_command_run.go index 507f22aa1..0f412ad33 100644 --- a/os/gcmd/gcmd_command_run.go +++ b/os/gcmd/gcmd_command_run.go @@ -14,6 +14,7 @@ import ( "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) From 22b7b2b95312bee21f35ea10b351c29fadd1c439 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sun, 21 Nov 2021 22:38:47 +0800 Subject: [PATCH 15/16] comment updates for package gcmd --- os/gcmd/gcmd_command_help.go | 4 ++-- os/gcmd/gcmd_command_run.go | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/os/gcmd/gcmd_command_help.go b/os/gcmd/gcmd_command_help.go index 72335c4df..1e482650e 100644 --- a/os/gcmd/gcmd_command_help.go +++ b/os/gcmd/gcmd_command_help.go @@ -114,14 +114,14 @@ func (c *Command) Print() { buffer.WriteString(gstr.WordWrap(gstr.Trim(c.Description), maxLineChars, "\n"+prefix)) buffer.WriteString("\n") } - buffer.WriteString("\n") // Additional. if c.Additional != "" { lineStr := gstr.WordWrap(gstr.Trim(c.Additional), maxLineChars, "\n") buffer.WriteString(lineStr) + buffer.WriteString("\n") } - buffer.WriteString("\n") + fmt.Println(buffer.String()) } diff --git a/os/gcmd/gcmd_command_run.go b/os/gcmd/gcmd_command_run.go index 0f412ad33..391cf3c15 100644 --- a/os/gcmd/gcmd_command_run.go +++ b/os/gcmd/gcmd_command_run.go @@ -10,6 +10,7 @@ package gcmd import ( "context" "fmt" + "os" "github.com/gogf/gf/v2/text/gstr" ) @@ -38,7 +39,11 @@ func (c *Command) Run(ctx context.Context) error { } // Print error and help command if no command found. - fmt.Printf("Error: command not found for \"%s\"\n\n", gstr.Join(args, " ")) + fmt.Printf( + "ERROR: command \"%s\" not found for arguments \"%s\"\n", + gstr.Join(args, " "), + gstr.Join(os.Args, " "), + ) c.Print() return nil @@ -63,6 +68,7 @@ func (c *Command) doRun(ctx context.Context, parser *Parser) (err error) { 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 { @@ -83,6 +89,7 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error) return Parse(supportedOptions) } +// searchCommand recursively searches the command according given arguments. func (c *Command) searchCommand(args []string) *Command { if len(args) == 0 { return nil From eaef2d865e47c6da07914940abc1fa86d9508878 Mon Sep 17 00:00:00 2001 From: John Guo Date: Mon, 22 Nov 2021 14:41:33 +0800 Subject: [PATCH 16/16] add rule ci for package gvalid --- util/gvalid/gvalid.go | 239 ++++++++---------- util/gvalid/gvalid_validator.go | 8 + util/gvalid/gvalid_validator_check_value.go | 151 ++++++----- util/gvalid/gvalid_validator_rule_required.go | 57 +++-- .../gvalid_z_unit_feature_checkmap_test.go | 2 +- util/gvalid/gvalid_z_unit_feature_ci_test.go | 30 +++ 6 files changed, 261 insertions(+), 226 deletions(-) create mode 100644 util/gvalid/gvalid_z_unit_feature_ci_test.go diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 5c358ee9f..d2b816459 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -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, } ) diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index 33772534c..0057275b3 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -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. @@ -52,6 +53,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. diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go index 0cfa78fcc..c99eef68c 100644 --- a/util/gvalid/gvalid_validator_check_value.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -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 } diff --git a/util/gvalid/gvalid_validator_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go index 06de0c50d..c09dedf24 100644 --- a/util/gvalid/gvalid_validator_rule_required.go +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -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 } diff --git a/util/gvalid/gvalid_z_unit_feature_checkmap_test.go b/util/gvalid/gvalid_z_unit_feature_checkmap_test.go index 4f0849a76..d7c77cbc8 100755 --- a/util/gvalid/gvalid_z_unit_feature_checkmap_test.go +++ b/util/gvalid/gvalid_z_unit_feature_checkmap_test.go @@ -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) { diff --git a/util/gvalid/gvalid_z_unit_feature_ci_test.go b/util/gvalid/gvalid_z_unit_feature_ci_test.go new file mode 100644 index 000000000..a2600110c --- /dev/null +++ b/util/gvalid/gvalid_z_unit_feature_ci_test.go @@ -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) + }) +}