Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+142
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}