This commit is contained in:
huangqian
2021-12-09 21:29:15 +08:00
20 changed files with 315 additions and 67 deletions

View File

@ -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()
}

View File

@ -13,9 +13,9 @@ import (
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/genv"
)
@ -166,8 +166,8 @@ func (c *Config) GetWithCmd(ctx context.Context, pattern string, def ...interfac
return nil, err
}
if value == nil {
if v := gcmd.GetOpt(utils.FormatCmdKey(pattern)); v != nil {
return v, nil
if v := command.GetOpt(utils.FormatCmdKey(pattern)); v != "" {
return gvar.New(v), nil
}
if len(def) > 0 {
return gvar.New(def[0]), nil

View File

@ -15,8 +15,8 @@ import (
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gfsnotify"
"github.com/gogf/gf/v2/os/gres"
@ -62,7 +62,7 @@ func NewAdapterFile(file ...string) (*AdapterFile, error) {
name = file[0]
} else {
// Custom default configuration file name from command line or environment.
if customFile := gcmd.GetOptWithEnv(commandEnvKeyForFile).String(); customFile != "" {
if customFile := command.GetOptWithEnv(commandEnvKeyForFile); customFile != "" {
name = customFile
}
}
@ -72,7 +72,7 @@ func NewAdapterFile(file ...string) (*AdapterFile, error) {
jsonMap: gmap.NewStrAnyMap(true),
}
// Customized dir path from env/cmd.
if customPath := gcmd.GetOptWithEnv(commandEnvKeyForPath).String(); customPath != "" {
if customPath := command.GetOptWithEnv(commandEnvKeyForPath); customPath != "" {
if gfile.Exists(customPath) {
if err = c.SetPath(customPath); err != nil {
return nil, err

View File

@ -18,7 +18,8 @@ import (
)
const (
CtxKeyParser gctx.StrKey = `GoFrameCommandParser`
CtxKeyParser gctx.StrKey = `CtxKeyParser`
CtxKeyCommand gctx.StrKey = `CtxKeyCommand`
)
const (

View File

@ -28,8 +28,9 @@ type Command struct {
Additional string // Additional info about this command, which will be appended to the end of help info.
NeedArgs bool // NeedArgs specifies this command needs arguments.
Strict bool // Strict parsing options, which means it returns error if invalid option given.
Config string // Config node name, which also retrieves the values from config component along with command line.
parent *Command // Parent command for internal usage.
commands []Command // Sub commands of this command.
commands []*Command // Sub commands of this command.
}
// Function is a custom command callback function that is bound to a certain argument.
@ -57,8 +58,18 @@ var (
}
)
// CommandFromCtx retrieves and returns Command from context.
func CommandFromCtx(ctx context.Context) *Command {
if v := ctx.Value(CtxKeyCommand); v != nil {
if p, ok := v.(*Command); ok {
return p
}
}
return nil
}
// AddCommand adds one or more sub-commands to current command.
func (c *Command) AddCommand(commands ...Command) error {
func (c *Command) AddCommand(commands ...*Command) error {
for _, cmd := range commands {
cmd.Name = gstr.Trim(cmd.Name)
if cmd.Name == "" {
@ -73,7 +84,7 @@ func (c *Command) AddCommand(commands ...Command) error {
// AddObject adds one or more sub-commands to current command using struct object.
func (c *Command) AddObject(objects ...interface{}) error {
var (
commands []Command
commands []*Command
)
for _, object := range objects {
rootCommand, err := NewFromObject(object)

View File

@ -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")
}

View File

@ -37,7 +37,7 @@ var (
)
// NewFromObject creates and returns a root command object using given object.
func NewFromObject(object interface{}) (rootCmd Command, err error) {
func NewFromObject(object interface{}) (rootCmd *Command, err error) {
originValueAndKind := utils.OriginValueAndKind(object)
if originValueAndKind.OriginKind != reflect.Struct {
err = gerror.Newf(
@ -55,12 +55,12 @@ func NewFromObject(object interface{}) (rootCmd Command, err error) {
var (
nameSet = gset.NewStrSet()
rootCommandName = gmeta.Get(object, tagNameRoot).String()
subCommands []Command
subCommands []*Command
)
for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ {
var (
method = originValueAndKind.InputValue.Method(i)
methodCommand Command
methodCommand *Command
)
methodCommand, err = newCommandFromMethod(object, method)
if err != nil {
@ -93,7 +93,7 @@ func NewFromObject(object interface{}) (rootCmd Command, err error) {
return
}
func newCommandFromObjectMeta(object interface{}) (command Command, err error) {
func newCommandFromObjectMeta(object interface{}) (command *Command, err error) {
var (
metaData = gmeta.Data(object)
)
@ -130,7 +130,7 @@ func newCommandFromObjectMeta(object interface{}) (command Command, err error) {
return
}
func newCommandFromMethod(object interface{}, method reflect.Value) (command Command, err error) {
func newCommandFromMethod(object interface{}, method reflect.Value) (command *Command, err error) {
var (
reflectType = method.Type()
)

View File

@ -12,7 +12,10 @@ import (
"fmt"
"os"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
// Run calls custom function that bound to this command.
@ -53,6 +56,8 @@ func (c *Command) RunWithValue(ctx context.Context) (value interface{}, err erro
}
func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{}, err error) {
ctx = context.WithValue(ctx, CtxKeyCommand, c)
// Check built-in help command.
if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) {
if c.HelpFunc != nil {
@ -84,7 +89,6 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error)
if len(c.Options) == 0 {
return parser, nil
}
var (
optionKey string
supportedOptions = make(map[string]bool)
@ -97,7 +101,30 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error)
}
supportedOptions[optionKey] = !option.Orphan
}
return Parse(supportedOptions, c.Strict)
parser, err := Parse(supportedOptions, c.Strict)
if err != nil {
return nil, err
}
// Retrieve option values from config component if it has "config" tag.
if c.Config != "" && gcfg.Instance().Available(ctx) {
value, err := gcfg.Instance().Get(ctx, c.Config)
if err != nil {
return nil, err
}
configMap := value.Map()
for optionName, _ := range parser.passedOptions {
// The command line has the high priority.
if parser.ContainsOpt(optionName) {
continue
}
// Merge the config value into parser.
foundKey, foundValue := gutil.MapPossibleItemByKey(configMap, optionName)
if foundKey != "" {
parser.parsedOptions[optionName] = gconv.String(foundValue)
}
}
}
return parser, nil
}
// searchCommand recursively searches the command according given arguments.
@ -109,13 +136,13 @@ func (c *Command) searchCommand(args []string) *Command {
// If this command needs argument,
// it then gives all its left arguments to it.
if cmd.NeedArgs {
return &cmd
return cmd
}
// Recursively searching the command.
if cmd.Name == args[0] {
leftArgs := args[1:]
if len(leftArgs) == 0 {
return &cmd
return cmd
}
return cmd.searchCommand(leftArgs)
}

View File

@ -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)
})
}

View File

@ -81,7 +81,7 @@ func Test_Command(t *testing.T) {
Name: "gf",
}
// env
commandEnv := gcmd.Command{
commandEnv := &gcmd.Command{
Name: "env",
Func: func(ctx context.Context, parser *gcmd.Parser) error {
fmt.Println("env")
@ -89,7 +89,7 @@ func Test_Command(t *testing.T) {
},
}
// test
commandTest := gcmd.Command{
commandTest := &gcmd.Command{
Name: "test",
Brief: "test brief",
Description: "test description current Golang environment variables",
@ -145,7 +145,7 @@ func Test_Command_Print(t *testing.T) {
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{
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",
@ -157,7 +157,7 @@ Use 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has '
g.Log().Fatal(ctx, err)
}
// get
commandGet := gcmd.Command{
commandGet := &gcmd.Command{
Name: "get",
Brief: "install or update GF to system in default...",
Description: "show current Golang environment variables",
@ -212,7 +212,7 @@ gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./dock
return nil
},
}
if err = c.AddCommand(commandBuild); err != nil {
if err = c.AddCommand(&commandBuild); err != nil {
g.Log().Fatal(ctx, err)
}
c.Run(ctx)

View File

@ -12,8 +12,8 @@ import (
"strings"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gcmd"
)
// All returns a copy of strings representing the environment,
@ -94,8 +94,8 @@ func GetWithCmd(key string, def ...interface{}) *gvar.Var {
return gvar.New(v)
}
cmdKey := utils.FormatCmdKey(key)
if v := gcmd.GetOpt(cmdKey); !v.IsEmpty() {
return v
if v := command.GetOpt(cmdKey); v != "" {
return gvar.New(v)
}
if len(def) > 0 {
return gvar.New(def[0])

21
os/genv/genv_must.go Normal file
View File

@ -0,0 +1,21 @@
// 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 genv
// MustSet performs as Set, but it panics if any error occurs.
func MustSet(key, value string) {
if err := Set(key, value); err != nil {
panic(err)
}
}
// MustRemove performs as Remove, but it panics if any error occurs.
func MustRemove(key ...string) {
if err := Remove(key...); err != nil {
panic(err)
}
}

View File

@ -84,7 +84,10 @@ func Uptime() time.Duration {
// The command `cmd` reads the input parameters from input pipe `in`, and writes its output automatically
// to output pipe `out`.
func Shell(cmd string, out io.Writer, in io.Reader) error {
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
p := NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
)
p.Stdin = in
p.Stdout = out
return p.Run()
@ -92,18 +95,26 @@ func Shell(cmd string, out io.Writer, in io.Reader) error {
// ShellRun executes given command `cmd` synchronously and outputs the command result to the stdout.
func ShellRun(cmd string) error {
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
p := NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
)
return p.Run()
}
// ShellExec executes given command `cmd` synchronously and returns the command result.
func ShellExec(cmd string, environment ...[]string) (string, error) {
buf := bytes.NewBuffer(nil)
p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
var (
buf = bytes.NewBuffer(nil)
p = NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
environment...,
)
)
p.Stdout = buf
p.Stderr = buf
err := p.Run()
return buf.String(), err
return buf.String(), p.Run()
}
// parseCommand parses command `cmd` into slice arguments.
@ -147,7 +158,7 @@ func parseCommand(cmd string) (args []string) {
return
}
// getShell returns the shell command depending on current working operation system.
// getShell returns the shell command depending on current working operating system.
// It returns "cmd.exe" for windows, and "bash" or "sh" for others.
func getShell() string {
switch runtime.GOOS {
@ -183,7 +194,7 @@ func getShellOption() string {
// SearchBinary searches the binary `file` in current working folder and PATH environment.
func SearchBinary(file string) string {
// Check if it's absolute path of exists at current working directory.
// Check if it is absolute path of exists at current working directory.
if gfile.Exists(file) {
return file
}
@ -204,6 +215,7 @@ func SearchBinaryPath(file string) string {
if gfile.Ext(file) != ".exe" {
file += ".exe"
}
default:
array = gstr.SplitAndTrim(genv.Get("PATH").String(), ":")
}

34
os/gproc/gproc_must.go Normal file
View File

@ -0,0 +1,34 @@
// 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 gproc
import (
"io"
)
// MustShell performs as Shell, but it panics if any error occurs.
func MustShell(cmd string, out io.Writer, in io.Reader) {
if err := Shell(cmd, out, in); err != nil {
panic(err)
}
}
// MustShellRun performs as ShellRun, but it panics if any error occurs.
func MustShellRun(cmd string) {
if err := ShellRun(cmd); err != nil {
panic(err)
}
}
// MustShellExec performs as ShellExec, but it panics if any error occurs.
func MustShellExec(cmd string, environment ...[]string) string {
result, err := ShellExec(cmd, environment...)
if err != nil {
panic(err)
}
return result
}

View File

@ -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
}

View File

@ -96,6 +96,8 @@ var (
"sm": "summary",
"des": "description",
"dc": "description",
"eg": "example",
"egs": "examples",
}
)

View File

@ -34,7 +34,7 @@ type Path struct {
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// Paths is specified by OpenAPI/Swagger standard version 3.0.
// Paths are specified by OpenAPI/Swagger standard version 3.0.
type Paths map[string]Path
const (

View File

@ -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 (\)

View File

@ -11,7 +11,7 @@ package gmode
import (
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/os/gfile"
)
@ -58,7 +58,7 @@ func SetProduct() {
func Mode() string {
// If current mode is not set, do this auto check.
if currentMode == NOT_SET {
if v := gcmd.GetOptWithEnv(commandEnvKey).String(); v != "" {
if v := command.GetOptWithEnv(commandEnvKey); v != "" {
// Mode configured from command argument of environment.
currentMode = v
} else {