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