You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
8.7 KiB
Go
450 lines
8.7 KiB
Go
1 year ago
|
package cmdr
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"git.noahlan.cn/noahlan/ntool/narr"
|
||
|
"git.noahlan.cn/noahlan/ntool/ncli/cmdline"
|
||
|
"io"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"path/filepath"
|
||
|
)
|
||
|
|
||
|
// Cmd struct
|
||
|
type Cmd struct {
|
||
|
*exec.Cmd
|
||
|
// Name of the command
|
||
|
Name string
|
||
|
// DryRun if True, not real execute command
|
||
|
DryRun bool
|
||
|
// Vars mapping
|
||
|
Vars map[string]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
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
panic(fmt.Sprintf("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, narr.SliceToStrings(args)...)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
// AddArg add args and returns the current object
|
||
|
func (c *Cmd) AddArg(args ...string) *Cmd { return c.WithArg(args...) }
|
||
|
|
||
|
// WithArg add args and returns 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 returns 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 returns 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 returns 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 returns 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 returns 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 returns 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 returns 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 cmdline.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
|
||
|
}
|
||
|
|
||
|
output, err := c.Cmd.Output()
|
||
|
|
||
|
if c.AfterRun != nil {
|
||
|
c.AfterRun(c, err)
|
||
|
}
|
||
|
return string(output), err
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
output, err := c.Cmd.CombinedOutput()
|
||
|
|
||
|
if c.AfterRun != nil {
|
||
|
c.AfterRun(c, err)
|
||
|
}
|
||
|
return string(output), 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
|
||
|
}
|