From e33230a88f4476221122c94467683cde4985125e Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Dec 2019 17:01:27 +0800 Subject: [PATCH] add package gbuild --- .example/os/gbuild/config.toml | 9 ++++ .example/os/gbuild/gbuild.go | 11 ++++ .example/other/test.go | 24 ++------- .example/other/test2.go | 17 ------ container/gvar/gvar.go | 20 +------ debug/gdebug/gdebug.go | 16 ------ encoding/gjson/gjson_api.go | 12 ++++- os/gbuild/gbuild.go | 72 ++++++++++++++++++++++++++ os/gcfg/gcfg_api.go | 7 +++ os/gproc/gproc.go | 86 ++++++++++++++++++++++--------- os/gproc/gproc_process.go | 17 +++--- os/gproc/gproc_unit_basic_test.go | 26 ++++++++++ os/gtime/gtime_time.go | 8 +++ util/gconv/gconv_map.go | 34 ++++++++++++ 14 files changed, 255 insertions(+), 104 deletions(-) create mode 100644 .example/os/gbuild/config.toml create mode 100644 .example/os/gbuild/gbuild.go delete mode 100644 .example/other/test2.go create mode 100644 os/gbuild/gbuild.go create mode 100644 os/gproc/gproc_unit_basic_test.go diff --git a/.example/os/gbuild/config.toml b/.example/os/gbuild/config.toml new file mode 100644 index 000000000..db7e02a1c --- /dev/null +++ b/.example/os/gbuild/config.toml @@ -0,0 +1,9 @@ + + +# custom gf build setting. +[compiler] + name = "app" + [compiler.varmap] + name = "GoFrame" + version = "1.10.1" + home-site = "https://goframe.org" \ No newline at end of file diff --git a/.example/os/gbuild/gbuild.go b/.example/os/gbuild/gbuild.go new file mode 100644 index 000000000..486371b31 --- /dev/null +++ b/.example/os/gbuild/gbuild.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gbuild" +) + +func main() { + g.Dump(gbuild.Info()) + g.Dump(gbuild.Map()) +} diff --git a/.example/other/test.go b/.example/other/test.go index 00e469ea8..96a2c7be2 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -2,28 +2,12 @@ package main import ( "fmt" + "github.com/gogf/gf/debug/gdebug" "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/util/gconv" - "reflect" + "os" ) -// 结构体 -type Data struct { - Name string `json:"name"` - Age int `json:"age"` -} - func main() { - data := Data{Name: "abcdefg"} - data1 := Data{} - data2 := Data{} - - g.Redis().Do("SET", "goods:id", data) - v, _ := g.Redis().DoVar("GET", "goods:id") - - v.Struct(&data1) - gconv.Struct(v, &data2) - - fmt.Println(v, data1, data2) - fmt.Println(reflect.TypeOf(v), reflect.TypeOf(data1)) + g.Dump(os.Args) + fmt.Println(gdebug.BuildInfo()) } diff --git a/.example/other/test2.go b/.example/other/test2.go deleted file mode 100644 index e37f7ca63..000000000 --- a/.example/other/test2.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "fmt" - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/util/gconv" -) - -func main() { - type User struct { - Scores []int64 - } - user := new(User) - err := gconv.Struct(g.Map{"scores": []interface{}{1, 2, 3}}, user) - fmt.Println(err) - fmt.Println(user) -} diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index 61acf30c9..7dc739c8b 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -233,15 +233,7 @@ func (v *Var) Map(tags ...string) map[string]interface{} { // MapStrStr converts to map[string]string. func (v *Var) MapStrStr(tags ...string) map[string]string { - m := v.Map(tags...) - if len(m) > 0 { - vMap := make(map[string]string) - for k, v := range m { - vMap[k] = gconv.String(v) - } - return vMap - } - return nil + return gconv.MapStrStr(v.Val(), tags...) } // MapStrVar converts to map[string]*Var. @@ -264,15 +256,7 @@ func (v *Var) MapDeep(tags ...string) map[string]interface{} { // MapDeep converts to map[string]string recursively. func (v *Var) MapStrStrDeep(tags ...string) map[string]string { - m := v.MapDeep(tags...) - if len(m) > 0 { - vMap := make(map[string]string) - for k, v := range m { - vMap[k] = gconv.String(v) - } - return vMap - } - return nil + return gconv.MapStrStrDeep(v.Val(), tags...) } // MapStrVarDeep converts to map[string]*Var recursively. diff --git a/debug/gdebug/gdebug.go b/debug/gdebug/gdebug.go index 4162f9233..6809ccfa2 100644 --- a/debug/gdebug/gdebug.go +++ b/debug/gdebug/gdebug.go @@ -10,7 +10,6 @@ package gdebug import ( "bytes" "fmt" - "github.com/gogf/gf" "path/filepath" "reflect" "runtime" @@ -29,9 +28,6 @@ const ( ) var ( - buildTime = "" // Binary time string, which is injected from "go build -ldflags '-X importpath.name=value'". - buildGoVersion = "" // Binary go version, which is injected from "go build -ldflags '-X importpath.name=value'". - buildGitCommit = "" // Binary git commit, which is injected from "go build -ldflags '-X importpath.name=value'". goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose. binaryVersion = "" // The version of current running binary(uint64 hex). binaryVersionMd5 = "" // The version of current running binary(MD5). @@ -43,18 +39,6 @@ func init() { } } -// BuildInfo returns the built information of the binary. -// Note that it should be used with gf-cli tool: gf build, -// which injects necessary information into the binary. -func BuildInfo() map[string]string { - return map[string]string{ - "gf": gf.VERSION, - "go": buildGoVersion, - "git": buildGitCommit, - "time": buildTime, - } -} - // BinVersion returns the version of current running binary. // It uses ghash.BKDRHash+BASE36 algorithm to calculate the unique version of the binary. func BinVersion() string { diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index 4a95d86a9..0a9954b7a 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -78,7 +78,7 @@ func (j *Json) GetVars(pattern string, def ...interface{}) []*gvar.Var { return gvar.New(j.Get(pattern, def...)).Vars() } -// GetMap gets the value by specified , +// GetMap retrieves the value by specified , // and converts it to map[string]interface{}. func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{} { result := j.Get(pattern, def...) @@ -88,6 +88,16 @@ func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{} return nil } +// GetMapStrStr retrieves the value by specified , +// and converts it to map[string]string. +func (j *Json) GetMapStrStr(pattern string, def ...interface{}) map[string]string { + result := j.Get(pattern, def...) + if result != nil { + return gconv.MapStrStr(result) + } + return nil +} + // GetJson gets the value by specified , // and converts it to a un-concurrent-safe Json object. func (j *Json) GetJson(pattern string, def ...interface{}) *Json { diff --git a/os/gbuild/gbuild.go b/os/gbuild/gbuild.go new file mode 100644 index 000000000..c0c1f0f81 --- /dev/null +++ b/os/gbuild/gbuild.go @@ -0,0 +1,72 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 gbuild manages the build-in variables from "gf build". +package gbuild + +import ( + "encoding/json" + "github.com/gogf/gf" + "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/encoding/gbase64" + "github.com/gogf/gf/internal/intlog" + "github.com/gogf/gf/util/gconv" + "runtime" +) + +var ( + builtInVarStr = "" // Raw variable base64 string. + builtInVarMap = map[string]interface{}{} // Binary custom variable map decoded. +) + +func init() { + if builtInVarStr != "" { + err := json.Unmarshal(gbase64.MustDecodeString(builtInVarStr), &builtInVarMap) + if err != nil { + intlog.Error(err) + } + builtInVarMap["gfVersion"] = gf.VERSION + builtInVarMap["goVersion"] = runtime.Version() + } +} + +// Info returns the basic built information of the binary as map. +// Note that it should be used with gf-cli tool "gf build", +// which injects necessary information into the binary. +func Info() map[string]string { + return map[string]string{ + "gf": GetString("gfVersion"), + "go": GetString("goVersion"), + "git": GetString("builtGit"), + "time": GetString("builtTime"), + } +} + +// Get retrieves and returns the build-in binary variable with given name. +func Get(name string, def ...interface{}) interface{} { + if v, ok := builtInVarMap[name]; ok { + return v + } + if len(def) > 0 { + return def[0] + } + return nil +} + +// Get retrieves and returns the build-in binary variable of given name as *gvar.Var. +func GetVar(name string, def ...interface{}) *gvar.Var { + return gvar.New(Get(name, def...)) +} + +// GetString retrieves and returns the build-in binary variable of given name as string. +func GetString(name string, def ...interface{}) string { + return gconv.String(Get(name, def...)) +} + +// Map returns the custom build-in variable map. +func Map() map[string]interface{} { + return builtInVarMap +} diff --git a/os/gcfg/gcfg_api.go b/os/gcfg/gcfg_api.go index c57e098d3..9956f751f 100644 --- a/os/gcfg/gcfg_api.go +++ b/os/gcfg/gcfg_api.go @@ -44,6 +44,13 @@ func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface return nil } +func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]string { + if j := c.getJson(); j != nil { + return j.GetMapStrStr(pattern, def...) + } + return nil +} + func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} { if j := c.getJson(); j != nil { return j.GetArray(pattern, def...) diff --git a/os/gproc/gproc.go b/os/gproc/gproc.go index bdc7c2665..75087cd38 100644 --- a/os/gproc/gproc.go +++ b/os/gproc/gproc.go @@ -13,7 +13,6 @@ import ( "io" "os" "runtime" - "strings" "time" "github.com/gogf/gf/os/gfile" @@ -36,7 +35,7 @@ func Pid() int { return processPid } -// 获取父进程ID(gproc父进程,如果当前进程本身就是父进程,那么返回自身的pid,不存在时则使用系统父进程) +// PPid returns the custom parent pid if exists, or else it returns the system parent pid. func PPid() int { if !IsChild() { return Pid() @@ -48,18 +47,22 @@ func PPid() int { return PPidOS() } -// 获取父进程ID(系统父进程) +// PPidOS returns the system parent pid of current process. +// Note that the difference between PPidOS and PPid function is that the PPidOS returns +// the system ppid, but the PPid functions may return the custom pid by gproc if the custom +// ppid exists. func PPidOS() int { return os.Getppid() } -// 判断当前进程是否为gproc创建的子进程 +// IsChild checks and returns whether current process is a child process. +// A child process is forked by another gproc process. func IsChild() bool { ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) return ppidValue != "" && ppidValue != "0" } -// 设置gproc父进程ID,当ppid为0时表示该进程为gproc主进程,否则为gproc子进程 +// SetPPid sets custom parent pid for current process. func SetPPid(ppid int) error { if ppid > 0 { return os.Setenv(gPROC_ENV_KEY_PPID_KEY, gconv.String(ppid)) @@ -68,50 +71,84 @@ func SetPPid(ppid int) error { } } -// 进程开始执行时间 +// StartTime returns the start time of current process. func StartTime() time.Time { return processStartTime } -// 进程已经运行的时间(毫秒) -func Uptime() int { - return int(time.Now().UnixNano()/1e6 - processStartTime.UnixNano()/1e6) +// Uptime returns the duration which current process has been running +func Uptime() time.Duration { + return time.Now().Sub(processStartTime) } -// 阻塞执行shell指令,并给定输入输出对象 +// Shell executes command synchronizingly with given input pipe and output pipe . +// The command reads the input parameters from input pipe , and writes its output automatically +// to output pipe . func Shell(cmd string, out io.Writer, in io.Reader) error { - p := NewProcess(getShell(), []string{getShellOption(), cmd}) + p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...)) p.Stdin = in p.Stdout = out return p.Run() } -// 阻塞执行shell指令,并输出结果当终端(如果需要异步,请使用goroutine) +// ShellRun executes given command synchronizingly and outputs the command result to the stdout. func ShellRun(cmd string) error { - p := NewProcess(getShell(), []string{getShellOption(), cmd}) + p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...)) return p.Run() } -// 阻塞执行shell指令,并返回输出结果(如果需要异步,请使用goroutine) +// ShellExec executes given command synchronizingly and returns the command result. func ShellExec(cmd string, environment ...[]string) (string, error) { buf := bytes.NewBuffer(nil) - p := NewProcess(getShell(), []string{getShellOption(), cmd}, environment...) + p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...) p.Stdout = buf err := p.Run() return buf.String(), err } -// 检测环境变量中是否已经存在指定键名 -func checkEnvKey(env []string, key string) bool { - for _, v := range env { - if len(v) >= len(key) && strings.EqualFold(v[0:len(key)], key) { - return true +// parseCommand parses command into slice arguments. +// +// Note that it just parses the for "cmd.exe" binary in windows, but it is not necessary +// parsing the for other systems using "bash"/"sh" binary. +func parseCommand(cmd string) (args []string) { + if runtime.GOOS != "windows" { + return []string{cmd} + } + // Just for "cmd.exe" in windows. + var argStr string + var firstChar, prevChar, lastChar1, lastChar2 byte + array := gstr.SplitAndTrim(cmd, " ") + for _, v := range array { + if len(argStr) > 0 { + argStr += " " + } + firstChar = v[0] + lastChar1 = v[len(v)-1] + lastChar2 = 0 + if len(v) > 1 { + lastChar2 = v[len(v)-2] + } + if prevChar == 0 && (firstChar == '"' || firstChar == '\'') { + // It should remove the first quote char. + argStr += v[1:] + prevChar = firstChar + } else if prevChar != 0 && lastChar2 != '\\' && lastChar1 == prevChar { + // It should remove the last quote char. + argStr += v[:len(v)-1] + args = append(args, argStr) + argStr = "" + prevChar = 0 + } else if len(argStr) > 0 { + argStr += v + } else { + args = append(args, v) } } - return false + return } -// 获取当前系统下的shell路径 +// getShell returns the shell command depending on current working operation system. +// It returns "cmd.exe" for windows, and "bash" or "sh" for others. func getShell() string { switch runtime.GOOS { case "windows": @@ -125,7 +162,8 @@ func getShell() string { } } -// 获取当前系统默认shell执行指令的option参数 +// getShellOption returns the shell option depending on current working operation system. +// It returns "/c" for windows, and "-c" for others. func getShellOption() string { switch runtime.GOOS { case "windows": @@ -135,7 +173,7 @@ func getShellOption() string { } } -// 从环境变量PATH中搜索可执行文件 +// SearchBinary searches the binary in current working folder and PATH environment. func SearchBinary(file string) string { // Check if it's absolute path of exists at current working directory. if gfile.Exists(file) { diff --git a/os/gproc/gproc_process.go b/os/gproc/gproc_process.go index a9c0abc54..48eb92394 100644 --- a/os/gproc/gproc_process.go +++ b/os/gproc/gproc_process.go @@ -14,14 +14,14 @@ import ( "strings" ) -// 子进程 +// Process is the struct for a single process. type Process struct { exec.Cmd - Manager *Manager // 所属进程管理器 - PPid int // 自定义关联的父进程ID + Manager *Manager + PPid int } -// 创建一个进程(不执行) +// NewProcess creates and returns a new Process. func NewProcess(path string, args []string, environment ...[]string) *Process { var env []string if len(environment) > 0 { @@ -57,10 +57,11 @@ func NewProcess(path string, args []string, environment ...[]string) *Process { // NewProcessCmd creates and returns a process with given command and optional environment variable array. func NewProcessCmd(cmd string, environment ...[]string) *Process { - return NewProcess(getShell(), []string{getShellOption(), cmd}, environment...) + return NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...) } -// 开始执行(非阻塞) +// Start starts executing the process in non-blocking way. +// It returns the pid if success, or else it returns an error. func (p *Process) Start() (int, error) { if p.Process != nil { return p.Pid(), nil @@ -76,7 +77,7 @@ func (p *Process) Start() (int, error) { } } -// 运行进程(阻塞等待执行完毕) +// Run executes the process in blocking way. func (p *Process) Run() error { if _, err := p.Start(); err == nil { return p.Wait() @@ -93,7 +94,7 @@ func (p *Process) Pid() int { return 0 } -// 向进程发送消息 +// Send send custom data to the process. func (p *Process) Send(data []byte) error { if p.Process != nil { return Send(p.Process.Pid, data) diff --git a/os/gproc/gproc_unit_basic_test.go b/os/gproc/gproc_unit_basic_test.go new file mode 100644 index 000000000..32c594048 --- /dev/null +++ b/os/gproc/gproc_unit_basic_test.go @@ -0,0 +1,26 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 gproc + +import ( + "github.com/gogf/gf/test/gtest" + "testing" +) + +func Test_parseCommand(t *testing.T) { + gtest.Case(t, func() { + commandMap := map[string]interface{}{ + `cmd`: []string{`cmd`}, + `cmd /c`: []string{`cmd`, `/c`}, + `cmd /c go build`: []string{`cmd`, `/c`, `go`, `build`}, + `cmd /c go build -ldflags "-X 'a=123' -X 'b=456'" test.go`: []string{`cmd`, `/c`, `go`, `build`, `-ldflags`, `"-X 'a=123' -X 'b=456'"`, `test.go`}, + } + for k, v := range commandMap { + gtest.Assert(parseCommand(k), v) + } + }) +} diff --git a/os/gtime/gtime_time.go b/os/gtime/gtime_time.go index 5571db3f7..a04fb222a 100644 --- a/os/gtime/gtime_time.go +++ b/os/gtime/gtime_time.go @@ -196,6 +196,14 @@ func (t *Time) Truncate(d time.Duration) *Time { return t } +// Sub returns the duration t-u. If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, the maximum (or minimum) duration +// will be returned. +// To compute t-d for a duration d, use t.Add(-d). +func (t *Time) Sub(u *Time) time.Duration { + return t.Time.Sub(u.Time) +} + // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (t *Time) MarshalJSON() ([]byte, error) { return []byte(`"` + t.String() + `"`), nil diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 19a310938..9f9e3e073 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -190,6 +190,40 @@ func MapDeep(value interface{}, tags ...string) map[string]interface{} { return data } +// MapStrStr converts to map[string]string. +// Note that there might be data copy for this map type converting. +func MapStrStr(value interface{}, tags ...string) map[string]string { + if r, ok := value.(map[string]string); ok { + return r + } + m := Map(value, tags...) + if len(m) > 0 { + vMap := make(map[string]string) + for k, v := range m { + vMap[k] = String(v) + } + return vMap + } + return nil +} + +// MapStrStrDeep converts to map[string]string recursively. +// Note that there might be data copy for this map type converting. +func MapStrStrDeep(value interface{}, tags ...string) map[string]string { + if r, ok := value.(map[string]string); ok { + return r + } + m := MapDeep(value, tags...) + if len(m) > 0 { + vMap := make(map[string]string) + for k, v := range m { + vMap[k] = String(v) + } + return vMap + } + return nil +} + // MapToMap converts map type variable to another map type variable using reflect. // The elements of should be type of *map. func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {