diff --git a/internal/utils/utils_str.go b/internal/utils/utils_str.go index 73e2eac39..f5891b03d 100644 --- a/internal/utils/utils_str.go +++ b/internal/utils/utils_str.go @@ -7,6 +7,7 @@ package utils import ( + "bytes" "strings" ) @@ -141,3 +142,21 @@ func FormatCmdKey(s string) string { func FormatEnvKey(s string) string { return strings.ToUpper(strings.Replace(s, ".", "_", -1)) } + +// StripSlashes un-quotes a quoted string by AddSlashes. +func StripSlashes(str string) string { + var buf bytes.Buffer + l, skip := len(str), false + for i, char := range str { + if skip { + skip = false + } else if char == '\\' { + if i+1 < l && str[i+1] == '\\' { + skip = true + } + continue + } + buf.WriteRune(char) + } + return buf.String() +} diff --git a/os/gcmd/gcmd_command_help.go b/os/gcmd/gcmd_command_help.go index 0f6af76fe..d1a983d97 100644 --- a/os/gcmd/gcmd_command_help.go +++ b/os/gcmd/gcmd_command_help.go @@ -75,7 +75,7 @@ func (c *Command) Print() { // Option. if len(options) > 0 { - buffer.WriteString("OPTIONS\n") + buffer.WriteString("OPTION\n") var ( nameStr string maxSpaceLength = 0 @@ -110,16 +110,22 @@ func (c *Command) Print() { // Example. if c.Examples != "" { buffer.WriteString("EXAMPLE\n") - buffer.WriteString(prefix) - buffer.WriteString(gstr.WordWrap(gstr.Trim(c.Examples), maxLineChars, "\n"+prefix)) + for _, line := range gstr.SplitAndTrim(c.Examples, "\n") { + buffer.WriteString(prefix) + buffer.WriteString(gstr.WordWrap(gstr.Trim(line), maxLineChars, "\n"+prefix)) + buffer.WriteString("\n") + } 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)) + for _, line := range gstr.SplitAndTrim(c.Description, "\n") { + buffer.WriteString(prefix) + buffer.WriteString(gstr.WordWrap(gstr.Trim(line), maxLineChars, "\n"+prefix)) + buffer.WriteString("\n") + } buffer.WriteString("\n") } diff --git a/os/gcmd/gcmd_z_unit_feature_object_test.go b/os/gcmd/gcmd_z_unit_feature_object1_test.go similarity index 100% rename from os/gcmd/gcmd_z_unit_feature_object_test.go rename to os/gcmd/gcmd_z_unit_feature_object1_test.go diff --git a/os/gcmd/gcmd_z_unit_feature_object2_test.go b/os/gcmd/gcmd_z_unit_feature_object2_test.go new file mode 100644 index 000000000..2d16b6f09 --- /dev/null +++ b/os/gcmd/gcmd_z_unit_feature_object2_test.go @@ -0,0 +1,135 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" -benchmem + +package gcmd_test + +import ( + "context" + "os" + "testing" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gtag" +) + +type commandBuild struct { + g.Meta `name:"build" root:"build" args:"true" brief:"{commandBuildBrief}" dc:"{commandBuildDc}" eg:"{commandBuildEg}" ad:"{commandBuildAd}"` + nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file. + packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file. +} + +const ( + commandBuildBrief = `cross-building go project for lots of platforms` + commandBuildEg = ` +gf build main.go +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 +` + commandBuildDc = ` +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. +` + commandBuildAd = ` +PLATFORMS + darwin amd64,arm64 + freebsd 386,amd64,arm + linux 386,amd64,arm,arm64,ppc64,ppc64le,mips,mipsle,mips64,mips64le + netbsd 386,amd64,arm + openbsd 386,amd64,arm + windows 386,amd64 +` + // https://golang.google.cn/doc/install/source + commandBuildPlatforms = ` + darwin amd64 + darwin arm64 + ios amd64 + ios arm64 + freebsd 386 + freebsd amd64 + freebsd arm + linux 386 + linux amd64 + linux arm + linux arm64 + linux ppc64 + linux ppc64le + linux mips + linux mipsle + linux mips64 + linux mips64le + netbsd 386 + netbsd amd64 + netbsd arm + openbsd 386 + openbsd amd64 + openbsd arm + windows 386 + windows amd64 + android arm + dragonfly amd64 + plan9 386 + plan9 amd64 + solaris amd64 +` +) + +func init() { + gtag.Sets(map[string]string{ + `commandBuildBrief`: commandBuildBrief, + `commandBuildDc`: commandBuildDc, + `commandBuildEg`: commandBuildEg, + `commandBuildAd`: commandBuildAd, + }) +} + +type commandBuildInput struct { + g.Meta `name:"build" config:"gfcli.build"` + Name string `short:"n" name:"name" brief:"output binary name"` + Version string `short:"v" name:"version" brief:"output binary version"` + Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"` + System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"` + Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"` + Path string `short:"p" name:"path" brief:"output binary directory path, default is './bin'" d:"./bin"` + Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"` + Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"` + Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"` + Pack string `name:"pack" brief:"pack specified folder into temporary go file before building and removes it after built"` +} +type commandBuildOutput struct{} + +func (c commandBuild) Index(ctx context.Context, in commandBuildInput) (out *commandBuildOutput, err error) { + return +} + +func TestNewFromObject(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + ctx = gctx.New() + ) + cmd, err := gcmd.NewFromObject(commandBuild{ + nodeNameInConfigFile: "gfcli.build", + packedGoFileName: "build_pack_data.go", + }) + t.AssertNil(err) + + os.Args = []string{"build", "-h"} + err = cmd.Run(ctx) + t.AssertNil(err) + }) +} diff --git a/os/gstructs/gstructs_field.go b/os/gstructs/gstructs_field.go index 359a3a590..8bc0aa234 100644 --- a/os/gstructs/gstructs_field.go +++ b/os/gstructs/gstructs_field.go @@ -8,9 +8,9 @@ package gstructs import ( "reflect" - "regexp" "strings" + "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/util/gtag" ) @@ -18,10 +18,6 @@ const ( jsonTagName = `json` ) -var ( - tagMapRegex = regexp.MustCompile(`([\w\-]+):"(.+?)"`) -) - // Tag returns the value associated with key in the tag string. If there is no // such key in the tag, Tag returns the empty string. func (f *Field) Tag(key string) string { @@ -67,13 +63,10 @@ func (f *Field) TagStr() string { // TagMap returns all the tag of the field along with its value string as map. func (f *Field) TagMap() map[string]string { var ( - data = map[string]string{} - match = tagMapRegex.FindAllStringSubmatch(f.TagStr(), -1) + data = ParseTag(f.TagStr()) ) - for _, m := range match { - if len(m) == 3 { - data[m[1]] = gtag.Parse(m[2]) - } + for k, v := range data { + data[k] = utils.StripSlashes(gtag.Parse(v)) } return data } diff --git a/text/gstr/gstr.go b/text/gstr/gstr.go index e783a9a0b..277e5ff85 100644 --- a/text/gstr/gstr.go +++ b/text/gstr/gstr.go @@ -442,20 +442,7 @@ func AddSlashes(str string) string { // StripSlashes un-quotes a quoted string by AddSlashes. func StripSlashes(str string) string { - var buf bytes.Buffer - l, skip := len(str), false - for i, char := range str { - if skip { - skip = false - } else if char == '\\' { - if i+1 < l && str[i+1] == '\\' { - skip = true - } - continue - } - buf.WriteRune(char) - } - return buf.String() + return utils.StripSlashes(str) } // QuoteMeta returns a version of str with a backslash character (\)