Initial QSfera import
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
# System Utils
|
||||
|
||||
Provide some system util functions. eg: sysenv, exec, user, process
|
||||
|
||||
- quick exec a command line string.
|
||||
- quick build a command for run
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/sysutil
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
out, err := sysutil.ExecCmd("ls", []string{"-al"})
|
||||
```
|
||||
|
||||
## Clipboard
|
||||
|
||||
Package `clipboard` provide a simple clipboard read and write operations.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/sysutil/clipboard
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
src := "hello, this is clipboard"
|
||||
err = clipboard.WriteString(src)
|
||||
assert.NoErr(t, err)
|
||||
|
||||
// str: "hello, this is clipboard"
|
||||
str, err = clipboard.ReadString()
|
||||
assert.NoErr(t, err)
|
||||
assert.NotEmpty(t, str)
|
||||
assert.Eq(t, src, str)
|
||||
```
|
||||
|
||||
## Cmd run
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/sysutil/cmdr
|
||||
```
|
||||
### Usage
|
||||
|
||||
**Cmd Builder**:
|
||||
|
||||
```go
|
||||
c := cmdr.NewCmd("ls").
|
||||
WithArg("-l").
|
||||
WithArgs([]string{"-h"}).
|
||||
AddArg("-a").
|
||||
AddArgf("%s", "./")
|
||||
|
||||
c.OnBefore(func(c *cmdr.Cmd) {
|
||||
assert.Eq(t, "ls -l -h -a ./", c.Cmdline())
|
||||
})
|
||||
|
||||
out := c.SafeOutput()
|
||||
fmt.Println(out)
|
||||
```
|
||||
|
||||
**Batch Cmd Tasks**:
|
||||
|
||||
Can use `cmdr.Runner` run multi cmd tasks at once.
|
||||
|
||||
```go
|
||||
buf := new(bytes.Buffer)
|
||||
rr := cmdr.NewRunner()
|
||||
|
||||
rr.Add(&cmdr.Task{
|
||||
ID: "task1",
|
||||
Cmd: cmdr.NewCmd("id", "-F").WithOutput(buf, buf),
|
||||
})
|
||||
rr.AddCmd(cmdr.NewCmd("ls").AddArgs([]string{"-l", "-h"}).WithOutput(buf, buf))
|
||||
|
||||
err = rr.Run()
|
||||
```
|
||||
|
||||
### Functions API
|
||||
|
||||
> generate by: `go doc ./sysutil`
|
||||
|
||||
```go
|
||||
func BinDir() string
|
||||
func BinFile() string
|
||||
func ChangeUserByName(newUname string) (err error)
|
||||
func ChangeUserUidGid(newUid int, newGid int) (err error)
|
||||
func CurrentShell(onlyName bool) (path string)
|
||||
func CurrentUser() *user.User
|
||||
func EnvPaths() []string
|
||||
func ExecCmd(binName string, args []string, workDir ...string) (string, error)
|
||||
func ExecLine(cmdLine string, workDir ...string) (string, error)
|
||||
func Executable(binName string) (string, error)
|
||||
func ExpandPath(path string) string
|
||||
func FindExecutable(binName string) (string, error)
|
||||
func FlushExec(bin string, args ...string) error
|
||||
func GoVersion() string
|
||||
func HasExecutable(binName string) bool
|
||||
func HasShellEnv(shell string) bool
|
||||
func HomeDir() string
|
||||
func Hostname() string
|
||||
func IsConsole(out io.Writer) bool
|
||||
func IsDarwin() bool
|
||||
func IsLinux() bool
|
||||
func IsMSys() bool
|
||||
func IsMac() bool
|
||||
func IsShellSpecialVar(c uint8) bool
|
||||
func IsTerminal(fd uintptr) bool
|
||||
func IsWin() bool
|
||||
func IsWindows() bool
|
||||
func Kill(pid int, signal syscall.Signal) error
|
||||
func LoginUser() *user.User
|
||||
func MustFindUser(uname string) *user.User
|
||||
func NewCmd(bin string, args ...string) *cmdr.Cmd
|
||||
func OpenBrowser(URL string) error
|
||||
func ProcessExists(pid int) bool
|
||||
func QuickExec(cmdLine string, workDir ...string) (string, error)
|
||||
func SearchPath(keywords string) []string
|
||||
func ShellExec(cmdLine string, shells ...string) (string, error)
|
||||
func StdIsTerminal() bool
|
||||
func UHomeDir() string
|
||||
func UserCacheDir(subPath string) string
|
||||
func UserConfigDir(subPath string) string
|
||||
func UserDir(subPath string) string
|
||||
func UserHomeDir() string
|
||||
func Workdir() string
|
||||
type CallerInfo struct{ ... }
|
||||
func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo
|
||||
type GoInfo struct{ ... }
|
||||
func OsGoInfo() (*GoInfo, error)
|
||||
func ParseGoVersion(line string) (*GoInfo, error)
|
||||
```
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
package cmdr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gookit/goutil"
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/cliutil/cmdline"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// Cmd struct
|
||||
type Cmd struct {
|
||||
*exec.Cmd
|
||||
// Name of the command
|
||||
Name string
|
||||
// DryRun setting. if True, not really execute command
|
||||
DryRun bool
|
||||
// Vars mapping TODO
|
||||
Vars map[string]string
|
||||
// PrintLine placeholder for print command cline.
|
||||
PrintLine string
|
||||
|
||||
// BeforeRun hook
|
||||
BeforeRun func(c *Cmd)
|
||||
// AfterRun hook
|
||||
AfterRun func(c *Cmd, err error)
|
||||
}
|
||||
|
||||
// NewGitCmd instance
|
||||
func NewGitCmd(subCmd string, args ...string) *Cmd {
|
||||
return NewCmd("git", subCmd).AddArgs(args)
|
||||
}
|
||||
|
||||
// NewCmdline instance
|
||||
//
|
||||
// see exec.Command
|
||||
func NewCmdline(line string) *Cmd {
|
||||
bin, args := cmdline.NewParser(line).WithParseEnv().BinAndArgs()
|
||||
return NewCmd(bin, args...)
|
||||
}
|
||||
|
||||
// NewCmd instance
|
||||
//
|
||||
// see exec.Command
|
||||
func NewCmd(bin string, args ...string) *Cmd {
|
||||
return WrapGoCmd(exec.Command(bin, args...))
|
||||
}
|
||||
|
||||
// CmdWithCtx create new instance with context.
|
||||
//
|
||||
// see exec.CommandContext
|
||||
func CmdWithCtx(ctx context.Context, bin string, args ...string) *Cmd {
|
||||
return WrapGoCmd(exec.CommandContext(ctx, bin, args...))
|
||||
}
|
||||
|
||||
// WrapGoCmd instance
|
||||
func WrapGoCmd(cmd *exec.Cmd) *Cmd {
|
||||
return &Cmd{
|
||||
Cmd: cmd,
|
||||
Vars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// config the command
|
||||
// -------------------------------------------------
|
||||
|
||||
// Config the command
|
||||
func (c *Cmd) Config(fn func(c *Cmd)) *Cmd {
|
||||
fn(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDryRun on exec command
|
||||
func (c *Cmd) WithDryRun(dryRun bool) *Cmd {
|
||||
c.DryRun = dryRun
|
||||
return c
|
||||
}
|
||||
|
||||
// PrintCmdline on exec command
|
||||
func (c *Cmd) PrintCmdline() *Cmd {
|
||||
c.BeforeRun = PrintCmdline
|
||||
return c
|
||||
}
|
||||
|
||||
// PrintCmdline2 on exec command
|
||||
func (c *Cmd) PrintCmdline2() *Cmd {
|
||||
c.BeforeRun = PrintCmdline2
|
||||
return c
|
||||
}
|
||||
|
||||
// OnBefore exec add hook
|
||||
func (c *Cmd) OnBefore(fn func(c *Cmd)) *Cmd {
|
||||
c.BeforeRun = fn
|
||||
return c
|
||||
}
|
||||
|
||||
// OnAfter exec add hook
|
||||
func (c *Cmd) OnAfter(fn func(c *Cmd, err error)) *Cmd {
|
||||
c.AfterRun = fn
|
||||
return c
|
||||
}
|
||||
|
||||
// WithBin name returns the current object
|
||||
func (c *Cmd) WithBin(name string) *Cmd {
|
||||
c.Args[0] = name
|
||||
c.lookPath(name)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Cmd) lookPath(name string) {
|
||||
if filepath.Base(name) == name {
|
||||
lp, err := exec.LookPath(name)
|
||||
if lp != "" {
|
||||
// Update cmd.Path even if err is non-nil.
|
||||
// If err is ErrDot (especially on Windows), lp may include a resolved
|
||||
// extension (like .exe or .bat) that should be preserved.
|
||||
c.Path = lp
|
||||
}
|
||||
if err != nil {
|
||||
goutil.Panicf("cmdr: look %q path error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithGoCmd and returns the current instance.
|
||||
func (c *Cmd) WithGoCmd(ec *exec.Cmd) *Cmd {
|
||||
c.Cmd = ec
|
||||
return c
|
||||
}
|
||||
|
||||
// WithWorkDir returns the current object
|
||||
func (c *Cmd) WithWorkDir(dir string) *Cmd {
|
||||
c.Dir = dir
|
||||
return c
|
||||
}
|
||||
|
||||
// WorkDirOnNE set workdir on input is not empty
|
||||
func (c *Cmd) WorkDirOnNE(dir string) *Cmd {
|
||||
if dir != "" {
|
||||
c.Dir = dir
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEnvMap override set new ENV for run
|
||||
func (c *Cmd) WithEnvMap(mp map[string]string) *Cmd {
|
||||
if ln := len(mp); ln > 0 {
|
||||
c.Env = make([]string, 0, ln)
|
||||
for key, val := range mp {
|
||||
c.Env = append(c.Env, key+"="+val)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// AppendEnv to the os ENV for run command
|
||||
func (c *Cmd) AppendEnv(mp map[string]string) *Cmd {
|
||||
if len(mp) > 0 {
|
||||
// init env data
|
||||
if c.Env == nil {
|
||||
c.Env = os.Environ()
|
||||
}
|
||||
|
||||
for name, val := range mp {
|
||||
c.Env = append(c.Env, name+"="+val)
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// OutputToOS output to OS stdout and error
|
||||
func (c *Cmd) OutputToOS() *Cmd { return c.ToOSStdoutStderr() }
|
||||
|
||||
// ToOSStdoutStderr output to OS stdout and error
|
||||
func (c *Cmd) ToOSStdoutStderr() *Cmd {
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return c
|
||||
}
|
||||
|
||||
// ToOSStdout output to OS stdout
|
||||
func (c *Cmd) ToOSStdout() *Cmd {
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stdout
|
||||
return c
|
||||
}
|
||||
|
||||
// WithStdin returns the current argument
|
||||
func (c *Cmd) WithStdin(in io.Reader) *Cmd {
|
||||
c.Stdin = in
|
||||
return c
|
||||
}
|
||||
|
||||
// WithOutput returns the current instance
|
||||
func (c *Cmd) WithOutput(out, errOut io.Writer) *Cmd {
|
||||
c.Stdout = out
|
||||
if errOut != nil {
|
||||
c.Stderr = errOut
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithAnyArgs add args and returns the current object.
|
||||
func (c *Cmd) WithAnyArgs(args ...any) *Cmd {
|
||||
c.Args = append(c.Args, arrutil.SliceToStrings(args)...)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddArg add args and return the current object
|
||||
func (c *Cmd) AddArg(args ...string) *Cmd { return c.WithArg(args...) }
|
||||
|
||||
// WithArg add args and return the current object. alias of the WithArg()
|
||||
func (c *Cmd) WithArg(args ...string) *Cmd {
|
||||
c.Args = append(c.Args, args...)
|
||||
return c
|
||||
}
|
||||
|
||||
// AddArgf add args and return the current object. alias of the WithArgf()
|
||||
func (c *Cmd) AddArgf(format string, args ...any) *Cmd { return c.WithArgf(format, args...) }
|
||||
|
||||
// WithArgf add arg and return the current object
|
||||
func (c *Cmd) WithArgf(format string, args ...any) *Cmd {
|
||||
c.Args = append(c.Args, fmt.Sprintf(format, args...))
|
||||
return c
|
||||
}
|
||||
|
||||
// ArgIf add arg and return the current object
|
||||
func (c *Cmd) ArgIf(arg string, exprOk bool) *Cmd {
|
||||
if exprOk {
|
||||
c.Args = append(c.Args, arg)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithArgIf add arg and return the current object
|
||||
func (c *Cmd) WithArgIf(arg string, exprOk bool) *Cmd {
|
||||
return c.ArgIf(arg, exprOk)
|
||||
}
|
||||
|
||||
// AddArgs for the git. alias of WithArgs()
|
||||
func (c *Cmd) AddArgs(args []string) *Cmd { return c.WithArgs(args) }
|
||||
|
||||
// WithArgs for the git
|
||||
func (c *Cmd) WithArgs(args []string) *Cmd {
|
||||
if len(args) > 0 {
|
||||
c.Args = append(c.Args, args...)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithArgsIf add arg and return the current object
|
||||
func (c *Cmd) WithArgsIf(args []string, exprOk bool) *Cmd {
|
||||
if exprOk && len(args) > 0 {
|
||||
c.Args = append(c.Args, args...)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithVars add vars and return the current object
|
||||
func (c *Cmd) WithVars(vs map[string]string) *Cmd {
|
||||
if len(vs) > 0 {
|
||||
c.Vars = vs
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// SetVar add var and return the current object
|
||||
func (c *Cmd) SetVar(name, val string) *Cmd {
|
||||
c.Vars[name] = val
|
||||
return c
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
// helper command
|
||||
// -------------------------------------------------
|
||||
|
||||
// IDString of the command
|
||||
func (c *Cmd) IDString() string {
|
||||
if c.Name != "" {
|
||||
return c.Name
|
||||
}
|
||||
return c.BinOrPath()
|
||||
}
|
||||
|
||||
// BinName of the command
|
||||
func (c *Cmd) BinName() string {
|
||||
if len(c.Args) > 0 {
|
||||
return c.Args[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BinOrPath of the command
|
||||
func (c *Cmd) BinOrPath() string {
|
||||
if len(c.Args) > 0 {
|
||||
return c.Args[0]
|
||||
}
|
||||
return c.Path
|
||||
}
|
||||
|
||||
// OnlyArgs of the command, not contains bin name.
|
||||
func (c *Cmd) OnlyArgs() (ss []string) {
|
||||
if len(c.Args) > 1 {
|
||||
return c.Args[1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResetArgs for command, but will keep bin name.
|
||||
func (c *Cmd) ResetArgs() {
|
||||
if len(c.Args) > 0 {
|
||||
c.Args = c.Args[0:1]
|
||||
} else {
|
||||
c.Args = c.Args[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Workdir of the command
|
||||
func (c *Cmd) Workdir() string { return c.Dir }
|
||||
|
||||
// Cmdline to command line
|
||||
func (c *Cmd) Cmdline() string { return comfunc.Cmdline(c.Args) }
|
||||
|
||||
// RawLine raw command line for print show.
|
||||
func (c *Cmd) RawLine() string {
|
||||
if c.PrintLine != "" {
|
||||
return c.PrintLine
|
||||
}
|
||||
return comfunc.Cmdline(c.Args)
|
||||
}
|
||||
|
||||
// Copy new instance from current command, with new args.
|
||||
func (c *Cmd) Copy(args ...string) *Cmd {
|
||||
nc := *c
|
||||
|
||||
// copy bin name.
|
||||
if len(c.Args) > 0 {
|
||||
nc.Args = append([]string{c.Args[0]}, args...)
|
||||
} else {
|
||||
nc.Args = args
|
||||
}
|
||||
|
||||
return &nc
|
||||
}
|
||||
|
||||
// GoCmd get exec.Cmd
|
||||
func (c *Cmd) GoCmd() *exec.Cmd { return c.Cmd }
|
||||
|
||||
// -------------------------------------------------
|
||||
// run command
|
||||
// -------------------------------------------------
|
||||
|
||||
// Success run and return whether success
|
||||
func (c *Cmd) Success() bool {
|
||||
return c.Run() == nil
|
||||
}
|
||||
|
||||
// HasStdout output setting.
|
||||
func (c *Cmd) HasStdout() bool {
|
||||
return c.Stdout != nil
|
||||
}
|
||||
|
||||
// SafeLines run and return output as lines
|
||||
func (c *Cmd) SafeLines() []string {
|
||||
ss, _ := c.OutputLines()
|
||||
return ss
|
||||
}
|
||||
|
||||
// OutputLines run and return output as lines
|
||||
func (c *Cmd) OutputLines() ([]string, error) {
|
||||
out, err := c.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return OutputLines(out), err
|
||||
}
|
||||
|
||||
// SafeOutput run and return output
|
||||
func (c *Cmd) SafeOutput() string {
|
||||
out, err := c.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Output run and return output
|
||||
func (c *Cmd) Output() (string, error) {
|
||||
if c.BeforeRun != nil {
|
||||
c.BeforeRun(c)
|
||||
}
|
||||
|
||||
if c.DryRun {
|
||||
return "DRY-RUN: ok", nil
|
||||
}
|
||||
|
||||
bs, err := c.Cmd.Output()
|
||||
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// AllOutput run and return output, will combine stderr and stdout output
|
||||
func (c *Cmd) AllOutput() (string, error) {
|
||||
return c.CombinedOutput()
|
||||
}
|
||||
|
||||
// CombinedOutput run and return output, will combine stderr and stdout output
|
||||
func (c *Cmd) CombinedOutput() (string, error) {
|
||||
if c.BeforeRun != nil {
|
||||
c.BeforeRun(c)
|
||||
}
|
||||
|
||||
if c.DryRun {
|
||||
return "DRY-RUN: ok", nil
|
||||
}
|
||||
|
||||
bs, err := c.Cmd.CombinedOutput()
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// MustRun a command. will panic on error
|
||||
func (c *Cmd) MustRun() {
|
||||
if err := c.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// FlushRun runs command and flush output to stdout
|
||||
func (c *Cmd) FlushRun() error {
|
||||
return c.ToOSStdoutStderr().Run()
|
||||
}
|
||||
|
||||
// Run runs command
|
||||
func (c *Cmd) Run() error {
|
||||
if c.BeforeRun != nil {
|
||||
c.BeforeRun(c)
|
||||
}
|
||||
|
||||
if c.DryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
// do running
|
||||
err := c.Cmd.Run()
|
||||
if c.AfterRun != nil {
|
||||
c.AfterRun(c, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// if IsWindows() {
|
||||
// return c.Spawn()
|
||||
// }
|
||||
// return c.Exec()
|
||||
|
||||
// Spawn runs command with spawn(3)
|
||||
// func (c *Cmd) Spawn() error {
|
||||
// return c.Cmd.Run()
|
||||
// }
|
||||
//
|
||||
// // Exec runs command with exec(3)
|
||||
// // Note that Windows doesn't support exec(3): http://golang.org/src/pkg/syscall/exec_windows.go#L339
|
||||
// func (c *Cmd) Exec() error {
|
||||
// binary, err := exec.LookPath(c.Path)
|
||||
// if err != nil {
|
||||
// return &exec.Error{
|
||||
// Name: c.Path,
|
||||
// Err: errorx.Newf("%s not found in the system", c.Path),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// args := []string{binary}
|
||||
// args = append(args, c.Args...)
|
||||
//
|
||||
// return syscall.Exec(binary, args, os.Environ())
|
||||
// }
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Package cmdr Provide for quick build and run a cmd, batch run multi cmd tasks
|
||||
package cmdr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/x/ccolor"
|
||||
)
|
||||
|
||||
// PrintCmdline on before exec
|
||||
func PrintCmdline(c *Cmd) {
|
||||
if c.DryRun {
|
||||
ccolor.Yellowln("DRY-RUN>", c.RawLine())
|
||||
} else {
|
||||
ccolor.Yellowln(">", c.RawLine())
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCmdline2 on before exec
|
||||
func PrintCmdline2(c *Cmd) {
|
||||
if c.Dir != "" {
|
||||
ccolor.Magentaln("> Workdir:", c.Dir)
|
||||
}
|
||||
if c.DryRun {
|
||||
ccolor.Yellowln("DRY-RUN>", c.RawLine())
|
||||
} else {
|
||||
ccolor.Yellowln(">", c.RawLine())
|
||||
}
|
||||
}
|
||||
|
||||
// OutputLines split output to lines
|
||||
func OutputLines(output string) []string {
|
||||
output = strings.TrimSuffix(output, "\n")
|
||||
if output == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(output, "\n")
|
||||
}
|
||||
|
||||
// FirstLine from command output
|
||||
func FirstLine(output string) string {
|
||||
if i := strings.Index(output, "\n"); i >= 0 {
|
||||
return output[0:i]
|
||||
}
|
||||
return output
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
package cmdr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/arrutil"
|
||||
"github.com/gookit/goutil/cliutil/cmdline"
|
||||
"github.com/gookit/goutil/errorx"
|
||||
"github.com/gookit/goutil/maputil"
|
||||
"github.com/gookit/goutil/mathutil"
|
||||
"github.com/gookit/goutil/strutil/textutil"
|
||||
"github.com/gookit/goutil/x/ccolor"
|
||||
)
|
||||
|
||||
// Task struct
|
||||
type Task struct {
|
||||
err error
|
||||
index int
|
||||
|
||||
// ID for task
|
||||
ID string
|
||||
Cmd *Cmd
|
||||
|
||||
// BeforeRun hook
|
||||
BeforeRun func(t *Task)
|
||||
PrevCond func(prev *Task) bool
|
||||
}
|
||||
|
||||
// NewTask instance
|
||||
func NewTask(cmd *Cmd) *Task {
|
||||
return &Task{
|
||||
Cmd: cmd,
|
||||
}
|
||||
}
|
||||
|
||||
// get task id by cmd.Name
|
||||
func (t *Task) ensureID(idx int) {
|
||||
t.index = idx
|
||||
if t.ID != "" {
|
||||
return
|
||||
}
|
||||
|
||||
id := t.Cmd.IDString()
|
||||
if t.Cmd.Name == "" {
|
||||
id += mathutil.String(idx)
|
||||
}
|
||||
t.ID = id
|
||||
}
|
||||
|
||||
var rpl = textutil.NewVarReplacer("$").DisableFlatten()
|
||||
|
||||
// RunWith command
|
||||
func (t *Task) RunWith(ctx maputil.Data) error {
|
||||
cmdVars := ctx.StringMap("cmdVars")
|
||||
|
||||
if len(cmdVars) > 0 {
|
||||
// rpl := strutil.NewReplacer(cmdVars)
|
||||
for i, val := range t.Cmd.Args {
|
||||
if strings.ContainsRune(val, '$') {
|
||||
t.Cmd.Args[i] = rpl.RenderSimple(val, cmdVars)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t.Run()
|
||||
}
|
||||
|
||||
// Run command
|
||||
func (t *Task) Run() error {
|
||||
if t.BeforeRun != nil {
|
||||
t.BeforeRun(t)
|
||||
}
|
||||
|
||||
t.err = t.Cmd.Run()
|
||||
return t.err
|
||||
}
|
||||
|
||||
// Err get
|
||||
func (t *Task) Err() error {
|
||||
return t.err
|
||||
}
|
||||
|
||||
// Index get
|
||||
func (t *Task) Index() int {
|
||||
return t.index
|
||||
}
|
||||
|
||||
// Cmdline get
|
||||
func (t *Task) Cmdline() string {
|
||||
return t.Cmd.Cmdline()
|
||||
}
|
||||
|
||||
// IsSuccess of task
|
||||
func (t *Task) IsSuccess() bool {
|
||||
return t.err == nil
|
||||
}
|
||||
|
||||
// RunnerHookFn func
|
||||
type RunnerHookFn func(r *Runner, t *Task) bool
|
||||
|
||||
// Runner use for batch run multi task commands
|
||||
type Runner struct {
|
||||
prev *Task
|
||||
// task name to index
|
||||
idMap map[string]int
|
||||
tasks []*Task
|
||||
// Errs on run tasks, key is Task.ID
|
||||
Errs errorx.ErrMap
|
||||
|
||||
// TODO Concurrent run
|
||||
|
||||
// Workdir common workdir
|
||||
Workdir string
|
||||
// EnvMap will append to task.Cmd on run
|
||||
EnvMap map[string]string
|
||||
|
||||
// Params for add custom params
|
||||
Params maputil.Map
|
||||
|
||||
// DryRun dry run all commands
|
||||
DryRun bool
|
||||
// OutToStd stdout and stderr
|
||||
OutToStd bool
|
||||
// IgnoreErr continue on error
|
||||
IgnoreErr bool
|
||||
// BeforeRun hooks on each task. return false to skip current task.
|
||||
BeforeRun func(r *Runner, t *Task) bool
|
||||
// AfterRun hook on each task. return false to stop running.
|
||||
AfterRun func(r *Runner, t *Task) bool
|
||||
}
|
||||
|
||||
// NewRunner instance with config func
|
||||
func NewRunner(fns ...func(rr *Runner)) *Runner {
|
||||
rr := &Runner{
|
||||
idMap: make(map[string]int, 0),
|
||||
tasks: make([]*Task, 0),
|
||||
Errs: make(errorx.ErrMap),
|
||||
Params: make(maputil.Map),
|
||||
}
|
||||
|
||||
rr.OutToStd = true
|
||||
for _, fn := range fns {
|
||||
fn(rr)
|
||||
}
|
||||
return rr
|
||||
}
|
||||
|
||||
// WithOutToStd set
|
||||
func (r *Runner) WithOutToStd() *Runner {
|
||||
r.OutToStd = true
|
||||
return r
|
||||
}
|
||||
|
||||
// Add multitask at once
|
||||
func (r *Runner) Add(tasks ...*Task) *Runner {
|
||||
for _, task := range tasks {
|
||||
r.AddTask(task)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// AddTask add one task
|
||||
func (r *Runner) AddTask(task *Task) *Runner {
|
||||
if task.Cmd == nil {
|
||||
panic("task command cannot be empty")
|
||||
}
|
||||
|
||||
idx := len(r.tasks)
|
||||
task.ensureID(idx)
|
||||
|
||||
// TODO check id repeat
|
||||
r.idMap[task.ID] = idx
|
||||
r.tasks = append(r.tasks, task)
|
||||
return r
|
||||
}
|
||||
|
||||
// AddCmd commands
|
||||
func (r *Runner) AddCmd(cmds ...*Cmd) *Runner {
|
||||
for _, cmd := range cmds {
|
||||
r.AddTask(&Task{Cmd: cmd})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GitCmd quick a git command task
|
||||
func (r *Runner) GitCmd(subCmd string, args ...string) *Runner {
|
||||
return r.AddTask(&Task{
|
||||
Cmd: NewGitCmd(subCmd, args...),
|
||||
})
|
||||
}
|
||||
|
||||
// CmdWithArgs a command task
|
||||
func (r *Runner) CmdWithArgs(cmdName string, args ...string) *Runner {
|
||||
return r.AddTask(&Task{
|
||||
Cmd: NewCmd(cmdName, args...),
|
||||
})
|
||||
}
|
||||
|
||||
// CmdWithAnys a command task
|
||||
func (r *Runner) CmdWithAnys(cmdName string, args ...any) *Runner {
|
||||
return r.AddTask(&Task{
|
||||
Cmd: NewCmd(cmdName, arrutil.SliceToStrings(args)...),
|
||||
})
|
||||
}
|
||||
|
||||
// AddCmdline as a command task
|
||||
func (r *Runner) AddCmdline(line string) *Runner {
|
||||
bin, args := cmdline.NewParser(line).BinAndArgs()
|
||||
|
||||
return r.AddTask(&Task{
|
||||
Cmd: NewCmd(bin, args...),
|
||||
})
|
||||
}
|
||||
|
||||
// Run all tasks
|
||||
func (r *Runner) Run() error {
|
||||
// do run tasks
|
||||
for i, task := range r.tasks {
|
||||
if r.BeforeRun != nil && !r.BeforeRun(r, task) {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.prev != nil && task.PrevCond != nil && !task.PrevCond(r.prev) {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.DryRun {
|
||||
ccolor.Infof("DRY-RUN: task#%d execute completed\n\n", i+1)
|
||||
continue
|
||||
}
|
||||
|
||||
if !r.RunTask(task) {
|
||||
break
|
||||
}
|
||||
fmt.Println() // with newline.
|
||||
}
|
||||
|
||||
if len(r.Errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.Errs
|
||||
}
|
||||
|
||||
// StepRun one command
|
||||
func (r *Runner) StepRun() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// RunTask command
|
||||
func (r *Runner) RunTask(task *Task) (goon bool) {
|
||||
if len(r.EnvMap) > 0 {
|
||||
task.Cmd.AppendEnv(r.EnvMap)
|
||||
}
|
||||
|
||||
if r.OutToStd && !task.Cmd.HasStdout() {
|
||||
task.Cmd.ToOSStdoutStderr()
|
||||
}
|
||||
|
||||
// common workdir
|
||||
if r.Workdir != "" && task.Cmd.Dir == "" {
|
||||
task.Cmd.WithWorkDir(r.Workdir)
|
||||
}
|
||||
|
||||
// do running
|
||||
if err := task.RunWith(r.Params); err != nil {
|
||||
r.Errs[task.ID] = err
|
||||
ccolor.Errorf("Task#%d run error: %s\n", task.Index()+1, err)
|
||||
|
||||
// not ignore error, stop.
|
||||
if !r.IgnoreErr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if r.AfterRun != nil && !r.AfterRun(r, task) {
|
||||
return false
|
||||
}
|
||||
|
||||
// store prev
|
||||
r.prev = task
|
||||
return true
|
||||
}
|
||||
|
||||
// Len of tasks
|
||||
func (r *Runner) Len() int {
|
||||
return len(r.tasks)
|
||||
}
|
||||
|
||||
// Reset instance
|
||||
func (r *Runner) Reset() *Runner {
|
||||
r.prev = nil
|
||||
r.tasks = make([]*Task, 0)
|
||||
r.idMap = make(map[string]int, 0)
|
||||
return r
|
||||
}
|
||||
|
||||
// TaskIDs get
|
||||
func (r *Runner) TaskIDs() []string {
|
||||
ss := make([]string, 0, len(r.idMap))
|
||||
for id := range r.idMap {
|
||||
ss = append(ss, id)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
// Prev task instance after running
|
||||
func (r *Runner) Prev() *Task {
|
||||
return r.prev
|
||||
}
|
||||
|
||||
// Task get by id name
|
||||
func (r *Runner) Task(id string) (*Task, error) {
|
||||
if idx, ok := r.idMap[id]; ok {
|
||||
return r.tasks[idx], nil
|
||||
}
|
||||
return nil, fmt.Errorf("task %q is not exists", id)
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/gookit/goutil/cliutil/cmdline"
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
"github.com/gookit/goutil/sysutil/cmdr"
|
||||
)
|
||||
|
||||
// NewCmd instance
|
||||
func NewCmd(bin string, args ...string) *cmdr.Cmd {
|
||||
return cmdr.NewCmd(bin, args...)
|
||||
}
|
||||
|
||||
// FlushExec command, will flush output to stdout,stderr
|
||||
func FlushExec(bin string, args ...string) error {
|
||||
return cmdr.NewCmd(bin, args...).FlushRun()
|
||||
}
|
||||
|
||||
// QuickExec quick exec a simple command line, return combined output.
|
||||
func QuickExec(cmdLine string, workDir ...string) (string, error) {
|
||||
return ExecLine(cmdLine, workDir...)
|
||||
}
|
||||
|
||||
// ExecLine quick exec a command line string, return combined output.
|
||||
//
|
||||
// NOTE: not support | or ; in cmdLine
|
||||
func ExecLine(cmdLine string, workDir ...string) (string, error) {
|
||||
p := cmdline.NewParser(cmdLine)
|
||||
|
||||
// create a new Cmd instance
|
||||
cmd := p.NewExecCmd()
|
||||
if len(workDir) > 0 {
|
||||
cmd.Dir = workDir[0]
|
||||
}
|
||||
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// ExecCmd a command and return combined output.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ExecCmd("ls", []string{"-al"})
|
||||
func ExecCmd(binName string, args []string, workDir ...string) (string, error) {
|
||||
// create a new Cmd instance
|
||||
cmd := exec.Command(binName, args...)
|
||||
if len(workDir) > 0 {
|
||||
cmd.Dir = workDir[0]
|
||||
}
|
||||
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// ShellExec exec command by shell cmdLine, return combined output.
|
||||
//
|
||||
// shells e.g. "/bin/sh", "bash", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh", "pwsh.exe"
|
||||
//
|
||||
// eg: ShellExec("ls -al")
|
||||
func ShellExec(cmdLine string, shells ...string) (string, error) {
|
||||
// shell := "/bin/sh"
|
||||
shell := "sh"
|
||||
if len(shells) > 0 {
|
||||
shell = shells[0]
|
||||
}
|
||||
|
||||
// "-c" for bash,sh,zsh shell
|
||||
mark := "-c"
|
||||
|
||||
// special for Windows shell
|
||||
if IsWindows() {
|
||||
// use cmd.exe, mark is "/c"
|
||||
if checkfn.StringsContains([]string{"cmd", "cmd.exe"}, shell) {
|
||||
mark = "/c"
|
||||
} else if checkfn.StringsContains([]string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}, shell) {
|
||||
// "-Command" for powershell
|
||||
mark = "-Command"
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(shell, mark, cmdLine)
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/errorx"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// OSVersionInfo 结构体用于存储操作系统版本信息
|
||||
//
|
||||
// NOTE: Windows 10 和 Windows 11 在主版本号和次版本号上是相同的,因此需要通过构建号(win11: DwBuildNumber>=22000)来进一步区分
|
||||
type OSVersionInfo struct {
|
||||
// 主版本号
|
||||
MajorVersion uint16
|
||||
// 次版本号
|
||||
MinorVersion uint16
|
||||
// 构建号
|
||||
BuildNumber uint32
|
||||
// 修订号
|
||||
RevisionNumber uint32
|
||||
}
|
||||
|
||||
// cache: 全局变量
|
||||
var stdOv = VersionInfoBySys()
|
||||
|
||||
// OsVersion Get operating system version information
|
||||
func OsVersion() *OSVersionInfo { return stdOv }
|
||||
|
||||
// VersionInfoBySys Get Windows system version information by sys/windows
|
||||
func VersionInfoBySys() *OSVersionInfo {
|
||||
// Get the Windows Version and Build Number
|
||||
var majorVersion, minorVersion, buildNumber = windows.RtlGetNtVersionNumbers()
|
||||
|
||||
return &OSVersionInfo{
|
||||
MajorVersion: uint16(majorVersion),
|
||||
MinorVersion: uint16(minorVersion),
|
||||
BuildNumber: buildNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// OsVersionByParse Get Windows system version information by parse string
|
||||
//
|
||||
// cmdOut eg: "Microsoft Windows [Version 10.0.22631.4391]"
|
||||
func OsVersionByParse(cmdOut string) (*OSVersionInfo, error) {
|
||||
return parseOsVersionString(cmdOut)
|
||||
}
|
||||
|
||||
// OsVersionByVerCmd Get Windows system version information
|
||||
//
|
||||
// 还可用使用dll获取:
|
||||
//
|
||||
// 通过 GetVersion, GetVersionEx 函数获取的信息不准确. win11获取到 6.2.9200, 实际是 10.0.22631
|
||||
func OsVersionByVerCmd() (*OSVersionInfo, error) {
|
||||
// Windows cmd 执行 ver 命令
|
||||
out, err := ShellExec("ver", "cmd")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseOsVersionString(out)
|
||||
}
|
||||
|
||||
// IsLtWindows7 判断是否小于 Windows 7
|
||||
func (ov *OSVersionInfo) IsLtWindows7() bool {
|
||||
return ov.MajorVersion < 6 || (ov.MajorVersion == 6 && ov.MinorVersion < 1)
|
||||
}
|
||||
|
||||
// IsWindows7 判断是否为 Windows 7
|
||||
func (ov *OSVersionInfo) IsWindows7() bool {
|
||||
return ov.MajorVersion == 6 && ov.MinorVersion == 1
|
||||
}
|
||||
|
||||
// IsWindows8 判断是否为 Windows 8/8.1
|
||||
func (ov *OSVersionInfo) IsWindows8() bool {
|
||||
return ov.MajorVersion == 6 && (ov.MinorVersion == 2 || ov.MinorVersion == 3)
|
||||
}
|
||||
|
||||
// IsWindows10 判断是否为 Windows 10
|
||||
func (ov *OSVersionInfo) IsWindows10() bool {
|
||||
return ov.MajorVersion == 10 && ov.MinorVersion == 0 && ov.BuildNumber < 22000
|
||||
}
|
||||
|
||||
// IsWindows11 判断是否为 Windows 11
|
||||
func (ov *OSVersionInfo) IsWindows11() bool {
|
||||
return ov.MajorVersion == 10 && ov.MinorVersion == 0 && ov.BuildNumber >= 22000
|
||||
}
|
||||
|
||||
// Name 获取 Windows 通用的版本名称. eg: xp, win7, win8, win10, win11, unknown
|
||||
func (ov *OSVersionInfo) Name() string {
|
||||
switch ov.MajorVersion {
|
||||
case 10:
|
||||
if ov.BuildNumber < 22000 {
|
||||
return "win10"
|
||||
} else {
|
||||
return "win11"
|
||||
}
|
||||
case 6:
|
||||
switch ov.MinorVersion {
|
||||
case 0:
|
||||
return "vista"
|
||||
case 1:
|
||||
return "win7"
|
||||
case 2:
|
||||
return "win8"
|
||||
case 3:
|
||||
return "win8.1"
|
||||
}
|
||||
case 5:
|
||||
switch ov.MinorVersion {
|
||||
case 1:
|
||||
return "xp"
|
||||
case 2:
|
||||
return "ws2003" // win server 2003
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// String format
|
||||
func (ov *OSVersionInfo) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", ov.MajorVersion, ov.MinorVersion, ov.BuildNumber)
|
||||
}
|
||||
|
||||
func parseOsVersionString(out string) (*OSVersionInfo, error) {
|
||||
// out eg: Microsoft Windows [Version 10.0.22631.4391] => 10.0.22631.4391
|
||||
// 部分系统会输出中文 eg: Microsoft Windows [版本 10.0.22631.4391]
|
||||
out = strings.TrimSpace(out)
|
||||
ns := strings.SplitN(out, "[", 2)
|
||||
if len(ns) < 2 {
|
||||
return nil, errorx.Rawf("cannot parse version info: %s", out)
|
||||
}
|
||||
|
||||
ns = strutil.SplitByWhitespace(strings.Trim(ns[1], "]"))
|
||||
if len(ns) < 2 {
|
||||
return nil, errorx.Rawf("cannot parse version info2: %s", out)
|
||||
}
|
||||
|
||||
var err error
|
||||
var ovi OSVersionInfo
|
||||
// get like: 10.0.22631.4391 or 10.0.22631
|
||||
verStr := ns[1]
|
||||
|
||||
if strings.Count(verStr, ".") >= 3 {
|
||||
_, err = fmt.Sscanf(verStr, "%d.%d.%d.%d", &ovi.MajorVersion, &ovi.MinorVersion, &ovi.BuildNumber, &ovi.RevisionNumber)
|
||||
} else {
|
||||
_, err = fmt.Sscanf(verStr, "%d.%d.%d", &ovi.MajorVersion, &ovi.MinorVersion, &ovi.BuildNumber)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errorx.Rawf("parse version info %q error: %v", verStr, err)
|
||||
}
|
||||
return &ovi, nil
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// os names
|
||||
const (
|
||||
Windows = "windows"
|
||||
Linux = "linux"
|
||||
Darwin = "darwin"
|
||||
FreeBSD = "freebsd"
|
||||
)
|
||||
|
||||
// IsMSys msys(MINGW64) env,不一定支持颜色
|
||||
func IsMSys() bool {
|
||||
// "MSYSTEM=MINGW64"
|
||||
return len(os.Getenv("MSYSTEM")) > 0
|
||||
}
|
||||
|
||||
// IsWSL system env
|
||||
func IsWSL() bool { return checkfn.IsWSL() }
|
||||
|
||||
// IsConsole check out is in stderr/stdout/stdin
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// sysutil.IsConsole(os.Stdout)
|
||||
func IsConsole(out io.Writer) bool {
|
||||
o, ok := out.(*os.File)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
fd := o.Fd()
|
||||
|
||||
// fix: cannot use 'o == os.Stdout' to compare
|
||||
return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr)
|
||||
}
|
||||
|
||||
// IsTerminal isatty check
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// sysutil.IsTerminal(os.Stdout.Fd())
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
// return isatty.IsTerminal(fd) // "github.com/mattn/go-isatty"
|
||||
return term.IsTerminal(int(fd))
|
||||
}
|
||||
|
||||
// StdIsTerminal os.Stdout is terminal
|
||||
func StdIsTerminal() bool {
|
||||
return IsTerminal(os.Stdout.Fd())
|
||||
}
|
||||
|
||||
// Hostname is alias of os.Hostname, but ignore error
|
||||
func Hostname() string {
|
||||
name, _ := os.Hostname()
|
||||
return name
|
||||
}
|
||||
|
||||
// CurrentShell get current used shell env file.
|
||||
//
|
||||
// eg "/bin/zsh" "/bin/bash".
|
||||
// if onlyName=true, will return "zsh", "bash"
|
||||
func CurrentShell(onlyName bool, fallbackShell ...string) string {
|
||||
return comfunc.CurrentShell(onlyName, fallbackShell...)
|
||||
}
|
||||
|
||||
// HasShellEnv has shell env check.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// HasShellEnv("sh")
|
||||
// HasShellEnv("bash")
|
||||
func HasShellEnv(shell string) bool {
|
||||
// can also use: "echo $0"
|
||||
out, err := ShellExec("echo OK", shell)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.TrimSpace(out) == "OK"
|
||||
}
|
||||
|
||||
// IsShellSpecialVar reports whether the character identifies a special
|
||||
// shell variable such as $*.
|
||||
func IsShellSpecialVar(c uint8) bool {
|
||||
switch c {
|
||||
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FindExecutable in the system
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// sysutil.FindExecutable("bash")
|
||||
func FindExecutable(binName string) (string, error) {
|
||||
return exec.LookPath(binName)
|
||||
}
|
||||
|
||||
// Executable find in the system, alias of FindExecutable()
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// sysutil.Executable("bash")
|
||||
func Executable(binName string) (string, error) {
|
||||
return exec.LookPath(binName)
|
||||
}
|
||||
|
||||
// HasExecutable in the system
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// HasExecutable("bash")
|
||||
func HasExecutable(binName string) bool {
|
||||
_, err := exec.LookPath(binName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Getenv get ENV value by key name, can with default value
|
||||
func Getenv(name string, def ...string) string {
|
||||
val := os.Getenv(name)
|
||||
if val == "" && len(def) > 0 {
|
||||
val = def[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Environ like os.Environ, but will returns key-value map[string]string data.
|
||||
func Environ() map[string]string { return comfunc.Environ() }
|
||||
|
||||
// EnvMapWith like os.Environ, but will return key-value map[string]string data.
|
||||
func EnvMapWith(newEnv map[string]string) map[string]string {
|
||||
envMp := comfunc.Environ()
|
||||
for name, value := range newEnv {
|
||||
envMp[name] = value
|
||||
}
|
||||
return envMp
|
||||
}
|
||||
|
||||
// EnvPaths get and split $PATH to []string
|
||||
func EnvPaths() []string {
|
||||
return filepath.SplitList(os.Getenv("PATH"))
|
||||
}
|
||||
|
||||
// ToEnvPATH convert []string to $PATH string.
|
||||
func ToEnvPATH(paths []string) string {
|
||||
return strings.Join(paths, string(filepath.ListSeparator))
|
||||
}
|
||||
|
||||
// SearchPathOption settings for SearchPath
|
||||
type SearchPathOption struct {
|
||||
// 限制查找的扩展名(Windows) such as ".exe", ".bat", ".cmd"
|
||||
LimitExt []string
|
||||
}
|
||||
|
||||
// SearchPath search executable files in the system $PATH
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// sysutil.SearchPath("go")
|
||||
func SearchPath(keywords string, limit int, opts ...SearchPathOption) []string {
|
||||
path := os.Getenv("PATH")
|
||||
ptn := "*" + keywords + "*"
|
||||
list := make([]string, 0)
|
||||
|
||||
opt := SearchPathOption{LimitExt: []string{".exe", ".bat", ".cmd"}}
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
|
||||
// if windows, will limit with .exe, .bat, .cmd
|
||||
isWindows := IsWindows()
|
||||
checked := make(map[string]bool)
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
// mark dir is checked
|
||||
if _, ok := checked[dir]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
checked[dir] = true
|
||||
matches, err := filepath.Glob(filepath.Join(dir, ptn))
|
||||
if err == nil && len(matches) > 0 {
|
||||
if isWindows {
|
||||
// if windows, will limit with .exe, .bat, .cmd
|
||||
for _, fPath := range matches {
|
||||
fExt := filepath.Ext(fPath)
|
||||
if !checkfn.StringsContains(opt.LimitExt, fExt) {
|
||||
continue
|
||||
}
|
||||
list = append(list, fPath)
|
||||
}
|
||||
} else {
|
||||
list = append(list, matches...)
|
||||
}
|
||||
|
||||
// limit result size
|
||||
size := len(list)
|
||||
if limit > 0 && size >= limit {
|
||||
list = list[:limit]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/gookit/goutil/x/goinfo"
|
||||
)
|
||||
|
||||
// GoVersion get go runtime version. eg: "1.18.2"
|
||||
func GoVersion() string {
|
||||
return runtime.Version()[2:]
|
||||
}
|
||||
|
||||
// GoInfo define. alias of goinfo.GoInfo
|
||||
type GoInfo = goinfo.GoInfo
|
||||
|
||||
// CallerInfo define. alias of goinfo.CallerInfo
|
||||
type CallerInfo = goinfo.CallerInfo
|
||||
|
||||
// ParseGoVersion get info by parse `go version` results. alias of goinfo.ParseGoVersion()
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// line, err := sysutil.ExecLine("go version")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// info, err := sysutil.ParseGoVersion()
|
||||
// dump.P(info)
|
||||
func ParseGoVersion(line string) (*GoInfo, error) {
|
||||
return goinfo.ParseGoVersion(line)
|
||||
}
|
||||
|
||||
// OsGoInfo fetch and parse. alias of goinfo.OsGoInfo()
|
||||
func OsGoInfo() (*GoInfo, error) {
|
||||
return goinfo.OsGoInfo()
|
||||
}
|
||||
|
||||
// CallersInfos returns an array of the CallerInfo. can with filters
|
||||
func CallersInfos(skip, num int, filters ...goinfo.CallerFilterFunc) []*CallerInfo {
|
||||
return goinfo.CallersInfos(skip+1, num, filters...)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Package sysutil provide some system util functions. eg: sysenv, exec, user, process
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Workdir get
|
||||
func Workdir() string {
|
||||
dir, _ := os.Getwd()
|
||||
return dir
|
||||
}
|
||||
|
||||
// BinDir get
|
||||
func BinDir() string {
|
||||
return filepath.Dir(os.Args[0])
|
||||
}
|
||||
|
||||
// BinName get
|
||||
func BinName() string {
|
||||
return filepath.Base(os.Args[0])
|
||||
}
|
||||
|
||||
// BinFile get
|
||||
func BinFile() string {
|
||||
return os.Args[0]
|
||||
}
|
||||
|
||||
// Open file or url address
|
||||
func Open(fileOrURL string) error { return OpenURL(fileOrURL) }
|
||||
|
||||
// OpenBrowser file or url address
|
||||
func OpenBrowser(fileOrURL string) error { return OpenURL(fileOrURL) }
|
||||
|
||||
// OpenFile open files browser window for the file path.
|
||||
func OpenFile(path string) error {
|
||||
fpath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OpenURL("file://" + fpath)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
//go:build darwin
|
||||
|
||||
package sysutil
|
||||
|
||||
import "os/exec"
|
||||
|
||||
// OsName system name. like runtime.GOOS. allow: linux, windows, darwin
|
||||
const OsName = Darwin
|
||||
|
||||
// IsWin system. linux windows darwin
|
||||
func IsWin() bool { return false }
|
||||
|
||||
// IsWindows system. linux windows darwin
|
||||
func IsWindows() bool { return false }
|
||||
|
||||
// IsMac system
|
||||
func IsMac() bool { return true }
|
||||
|
||||
// IsDarwin system
|
||||
func IsDarwin() bool { return true }
|
||||
|
||||
// IsLinux system
|
||||
func IsLinux() bool { return false }
|
||||
|
||||
// OpenURL Open browser URL
|
||||
//
|
||||
// Mac:
|
||||
//
|
||||
// open 'https://github.com/inhere'
|
||||
//
|
||||
// Linux:
|
||||
//
|
||||
// xdg-open URL
|
||||
// x-www-browser 'https://github.com/inhere'
|
||||
//
|
||||
// Windows:
|
||||
//
|
||||
// cmd /c start https://github.com/inhere
|
||||
func OpenURL(URL string) error {
|
||||
return exec.Command("open", URL).Run()
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsName system name. like runtime.GOOS. allow: linux, windows, darwin
|
||||
const OsName = Linux
|
||||
|
||||
// IsWin system. linux windows darwin
|
||||
func IsWin() bool { return false }
|
||||
|
||||
// IsWindows system. linux windows darwin
|
||||
func IsWindows() bool { return false }
|
||||
|
||||
// IsMac system
|
||||
func IsMac() bool { return false }
|
||||
|
||||
// IsDarwin system
|
||||
func IsDarwin() bool { return false }
|
||||
|
||||
// IsLinux system
|
||||
func IsLinux() bool { return true }
|
||||
|
||||
// There are multiple possible providers to open a browser on linux
|
||||
// One of them is xdg-open, another is x-www-browser, then there's www-browser, etc.
|
||||
// Look for one that exists and run it
|
||||
var openBins = []string{"xdg-open", "x-www-browser", "www-browser"}
|
||||
|
||||
// OpenURL Open file or browser URL
|
||||
//
|
||||
// Mac:
|
||||
//
|
||||
// open 'https://github.com/inhere'
|
||||
//
|
||||
// Linux:
|
||||
//
|
||||
// xdg-open URL
|
||||
// x-www-browser 'https://github.com/inhere'
|
||||
//
|
||||
// Windows:
|
||||
//
|
||||
// cmd /c start https://github.com/inhere
|
||||
func OpenURL(URL string) error {
|
||||
for _, bin := range openBins {
|
||||
if _, err := exec.LookPath(bin); err == nil {
|
||||
return exec.Command(bin, URL).Run()
|
||||
}
|
||||
}
|
||||
|
||||
return &exec.Error{Name: strings.Join(openBins, ","), Err: exec.ErrNotFound}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//go:build !windows
|
||||
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Kill a process by pid
|
||||
func Kill(pid int, signal syscall.Signal) error {
|
||||
return syscall.Kill(pid, signal)
|
||||
}
|
||||
|
||||
// ProcessExists check process exists by pid
|
||||
func ProcessExists(pid int) bool {
|
||||
return nil == syscall.Kill(pid, 0)
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsName system name. like runtime.GOOS.
|
||||
// For Unix systems, this will be the actual GOOS value (freebsd, openbsd, netbsd, dragonfly)
|
||||
var OsName = runtime.GOOS
|
||||
|
||||
// IsWin system. linux windows darwin
|
||||
func IsWin() bool { return false }
|
||||
|
||||
// IsWindows system. linux windows darwin
|
||||
func IsWindows() bool { return false }
|
||||
|
||||
// IsMac system
|
||||
func IsMac() bool { return false }
|
||||
|
||||
// IsDarwin system
|
||||
func IsDarwin() bool { return false }
|
||||
|
||||
// IsLinux system
|
||||
func IsLinux() bool { return false }
|
||||
|
||||
// There are multiple possible providers to open a browser on Unix systems
|
||||
// Similar to Linux, try xdg-open and other common browser launchers
|
||||
var openBins = []string{"xdg-open", "x-www-browser", "www-browser", "firefox", "chrome", "chromium"}
|
||||
|
||||
// OpenURL Open file or browser URL
|
||||
//
|
||||
// Mac:
|
||||
//
|
||||
// open 'https://github.com/inhere'
|
||||
//
|
||||
// Linux:
|
||||
//
|
||||
// xdg-open URL
|
||||
// x-www-browser 'https://github.com/inhere'
|
||||
//
|
||||
// Windows:
|
||||
//
|
||||
// cmd /c start https://github.com/inhere
|
||||
//
|
||||
// Unix (FreeBSD, OpenBSD, etc.):
|
||||
//
|
||||
// Try xdg-open, x-www-browser, or fallback browsers
|
||||
func OpenURL(URL string) error {
|
||||
for _, bin := range openBins {
|
||||
if _, err := exec.LookPath(bin); err == nil {
|
||||
return exec.Command(bin, URL).Run()
|
||||
}
|
||||
}
|
||||
|
||||
return &exec.Error{Name: strings.Join(openBins, ","), Err: exec.ErrNotFound}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// OsName system name. like runtime.GOOS. only allow: linux, windows, darwin
|
||||
const OsName = Windows
|
||||
|
||||
// IsWin system. linux windows darwin
|
||||
func IsWin() bool { return true }
|
||||
|
||||
// IsWindows system. linux windows darwin
|
||||
func IsWindows() bool { return true }
|
||||
|
||||
// IsMac system
|
||||
func IsMac() bool { return false }
|
||||
|
||||
// IsDarwin system
|
||||
func IsDarwin() bool { return false }
|
||||
|
||||
// IsLinux system
|
||||
func IsLinux() bool { return false }
|
||||
|
||||
// Kill a process by pid
|
||||
func Kill(pid int, signal syscall.Signal) error {
|
||||
return errors.New("not support on Windows")
|
||||
}
|
||||
|
||||
// ProcessExists check process exists by pid
|
||||
func ProcessExists(pid int) bool {
|
||||
panic("TIP: please use sysutil/process.Exists()")
|
||||
}
|
||||
|
||||
// OpenURL Open file or browser URL
|
||||
//
|
||||
// - refers https://github.com/pkg/browser
|
||||
//
|
||||
// Mac:
|
||||
//
|
||||
// open 'https://github.com/inhere'
|
||||
//
|
||||
// Linux:
|
||||
//
|
||||
// xdg-open URL
|
||||
// x-www-browser 'https://github.com/inhere'
|
||||
//
|
||||
// Windows:
|
||||
//
|
||||
// cmd /c start https://github.com/inhere
|
||||
func OpenURL(url string) error {
|
||||
// return exec.Command("cmd", "/C", "start", URL).Run()
|
||||
return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL)
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// MustFindUser must find a system user by name
|
||||
func MustFindUser(uname string) *user.User {
|
||||
u, err := user.Lookup(uname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// LoginUser must get current user, will panic if error
|
||||
func LoginUser() *user.User { return CurrentUser() }
|
||||
|
||||
// CurrentUser must get current user, will panic if error
|
||||
func CurrentUser() *user.User {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// UHomeDir get user home dir path, ignore error. (by user.Current)
|
||||
func UHomeDir() string {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return u.HomeDir
|
||||
}
|
||||
|
||||
// homeDir cache
|
||||
var _homeDir string
|
||||
|
||||
// UserHomeDir is alias of os.UserHomeDir, but ignore error.(by os.UserHomeDir)
|
||||
func UserHomeDir() string {
|
||||
if _homeDir == "" {
|
||||
_homeDir, _ = os.UserHomeDir()
|
||||
}
|
||||
return _homeDir
|
||||
}
|
||||
|
||||
// HomeDir get user home dir path.
|
||||
func HomeDir() string { return UserHomeDir() }
|
||||
|
||||
// UserDir will prepend user home dir to subPaths
|
||||
func UserDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths2(UserHomeDir(), subPaths)
|
||||
}
|
||||
|
||||
// UserCacheDir will prepend user `$HOME/.cache` to subPaths
|
||||
func UserCacheDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths3(UserHomeDir(), ".cache", subPaths)
|
||||
}
|
||||
|
||||
// UserConfigDir will prepend user `$HOME/.config` to subPath
|
||||
func UserConfigDir(subPaths ...string) string {
|
||||
return comfunc.JoinPaths3(UserHomeDir(), ".config", subPaths)
|
||||
}
|
||||
|
||||
// ExpandPath will parse `~` as user home dir path.
|
||||
func ExpandPath(path string) string { return comfunc.ExpandHome(path) }
|
||||
|
||||
// ExpandHome will parse `~` as user home dir path.
|
||||
func ExpandHome(path string) string { return comfunc.ExpandHome(path) }
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
//go:build !windows
|
||||
|
||||
package sysutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// IsAdmin Determine whether the current user is an administrator(root)
|
||||
func IsAdmin() bool {
|
||||
return os.Getuid() == 0
|
||||
}
|
||||
|
||||
// ChangeUserByName change work user by new username.
|
||||
func ChangeUserByName(newUname string) error {
|
||||
u := MustFindUser(newUname)
|
||||
// syscall.Setlogin(newUname)
|
||||
return ChangeUserUIDGid(strutil.MustInt(u.Uid), strutil.MustInt(u.Gid))
|
||||
}
|
||||
|
||||
// ChangeUserUidGid change work user by new username uid,gid
|
||||
//
|
||||
// Deprecated: use ChangeUserUIDGid instead
|
||||
func ChangeUserUidGid(newUID int, newGid int) error {
|
||||
return ChangeUserUIDGid(newUID, newGid)
|
||||
}
|
||||
|
||||
// ChangeUserUIDGid change work user by new username uid,gid
|
||||
func ChangeUserUIDGid(newUID int, newGid int) (err error) {
|
||||
if newUID > 0 {
|
||||
err = syscall.Setuid(newUID)
|
||||
|
||||
// update group id
|
||||
if err == nil && newGid > 0 {
|
||||
err = syscall.Setgid(newGid)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//go:build windows
|
||||
|
||||
package sysutil
|
||||
|
||||
// ChangeUserByName change work user by new username.
|
||||
func ChangeUserByName(newUname string) error {
|
||||
return ChangeUserUIDGid(0, 0)
|
||||
}
|
||||
|
||||
// ChangeUserUidGid change work user by new username uid,gid
|
||||
//
|
||||
// Deprecated: use ChangeUserUIDGid instead
|
||||
func ChangeUserUidGid(newUid int, newGid int) error {
|
||||
return ChangeUserUIDGid(newUid, newGid)
|
||||
}
|
||||
|
||||
// ChangeUserUIDGid change work user by new username uid,gid
|
||||
func ChangeUserUIDGid(newUid int, newGid int) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAdmin Determine whether the current user is an administrator
|
||||
func IsAdmin() bool {
|
||||
// 执行 net session 判断
|
||||
_, err := ExecCmd("net", []string{"session"})
|
||||
return err == nil
|
||||
}
|
||||
Reference in New Issue
Block a user