2013-05-29 22:58:46 +04:00
|
|
|
package cmd
|
2013-04-29 00:41:45 +04:00
|
|
|
|
|
|
|
import (
|
2013-06-18 00:35:02 +04:00
|
|
|
"fmt"
|
2013-04-29 05:47:08 +04:00
|
|
|
"os"
|
2013-04-29 00:41:45 +04:00
|
|
|
"os/exec"
|
2014-11-30 23:14:18 +03:00
|
|
|
"runtime"
|
2013-07-02 22:28:50 +04:00
|
|
|
"strings"
|
2014-11-30 23:14:18 +03:00
|
|
|
"syscall"
|
|
|
|
|
2020-04-17 02:02:37 +03:00
|
|
|
"github.com/github/hub/v2/ui"
|
2013-04-29 00:41:45 +04:00
|
|
|
)
|
|
|
|
|
2020-03-21 01:11:47 +03:00
|
|
|
// Cmd is a project-wide struct that represents a command to be run in the console.
|
2013-05-29 22:58:46 +04:00
|
|
|
type Cmd struct {
|
2017-06-21 23:55:03 +03:00
|
|
|
Name string
|
|
|
|
Args []string
|
|
|
|
Stdin *os.File
|
|
|
|
Stdout *os.File
|
|
|
|
Stderr *os.File
|
2013-04-29 00:41:45 +04:00
|
|
|
}
|
|
|
|
|
2013-07-02 22:28:50 +04:00
|
|
|
func (cmd Cmd) String() string {
|
2020-05-26 00:44:26 +03:00
|
|
|
args := make([]string, len(cmd.Args))
|
|
|
|
for i, a := range cmd.Args {
|
|
|
|
if strings.ContainsRune(a, '"') {
|
|
|
|
args[i] = fmt.Sprintf(`'%s'`, a)
|
|
|
|
} else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') {
|
|
|
|
args[i] = fmt.Sprintf(`"%s"`, a)
|
|
|
|
} else {
|
|
|
|
args[i] = a
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s %s", cmd.Name, strings.Join(args, " "))
|
2013-07-02 22:28:50 +04:00
|
|
|
}
|
|
|
|
|
2020-03-21 01:30:49 +03:00
|
|
|
// WithArg returns the current argument
|
2013-05-29 22:58:46 +04:00
|
|
|
func (cmd *Cmd) WithArg(arg string) *Cmd {
|
2014-12-09 19:42:37 +03:00
|
|
|
cmd.Args = append(cmd.Args, arg)
|
2013-04-29 00:41:45 +04:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2013-06-29 01:21:48 +04:00
|
|
|
func (cmd *Cmd) WithArgs(args ...string) *Cmd {
|
2013-07-05 22:10:24 +04:00
|
|
|
for _, arg := range args {
|
|
|
|
cmd.WithArg(arg)
|
|
|
|
}
|
2013-06-29 01:21:48 +04:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2019-05-08 15:59:56 +03:00
|
|
|
func (cmd *Cmd) Output() (string, error) {
|
|
|
|
verboseLog(cmd)
|
|
|
|
c := exec.Command(cmd.Name, cmd.Args...)
|
|
|
|
c.Stderr = cmd.Stderr
|
|
|
|
output, err := c.Output()
|
|
|
|
|
|
|
|
return string(output), err
|
|
|
|
}
|
|
|
|
|
2014-11-30 23:16:53 +03:00
|
|
|
func (cmd *Cmd) CombinedOutput() (string, error) {
|
2015-10-31 02:53:41 +03:00
|
|
|
verboseLog(cmd)
|
2013-06-22 05:02:29 +04:00
|
|
|
output, err := exec.Command(cmd.Name, cmd.Args...).CombinedOutput()
|
2013-04-29 00:41:45 +04:00
|
|
|
|
2013-06-22 05:02:29 +04:00
|
|
|
return string(output), err
|
2013-04-29 00:41:45 +04:00
|
|
|
}
|
|
|
|
|
2016-01-22 12:47:51 +03:00
|
|
|
func (cmd *Cmd) Success() bool {
|
|
|
|
verboseLog(cmd)
|
|
|
|
err := exec.Command(cmd.Name, cmd.Args...).Run()
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2014-11-30 23:14:18 +03:00
|
|
|
// Run runs command with `Exec` on platforms except Windows
|
|
|
|
// which only supports `Spawn`
|
|
|
|
func (cmd *Cmd) Run() error {
|
2019-01-28 02:38:24 +03:00
|
|
|
if isWindows() {
|
2014-11-30 23:14:18 +03:00
|
|
|
return cmd.Spawn()
|
|
|
|
}
|
2020-03-21 01:11:47 +03:00
|
|
|
return cmd.Exec()
|
2014-11-30 23:14:18 +03:00
|
|
|
}
|
|
|
|
|
2019-01-28 02:38:24 +03:00
|
|
|
func isWindows() bool {
|
|
|
|
return runtime.GOOS == "windows" || detectWSL()
|
|
|
|
}
|
|
|
|
|
|
|
|
var detectedWSL bool
|
|
|
|
var detectedWSLContents string
|
|
|
|
|
|
|
|
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
|
|
|
|
func detectWSL() bool {
|
|
|
|
if !detectedWSL {
|
|
|
|
b := make([]byte, 1024)
|
|
|
|
f, err := os.Open("/proc/version")
|
|
|
|
if err == nil {
|
|
|
|
f.Read(b)
|
|
|
|
f.Close()
|
|
|
|
detectedWSLContents = string(b)
|
|
|
|
}
|
|
|
|
detectedWSL = true
|
|
|
|
}
|
|
|
|
return strings.Contains(detectedWSLContents, "Microsoft")
|
|
|
|
}
|
|
|
|
|
2014-11-30 23:14:18 +03:00
|
|
|
// Spawn runs command with spawn(3)
|
2014-11-30 22:15:13 +03:00
|
|
|
func (cmd *Cmd) Spawn() error {
|
2015-10-31 02:53:41 +03:00
|
|
|
verboseLog(cmd)
|
2014-11-30 22:15:13 +03:00
|
|
|
c := exec.Command(cmd.Name, cmd.Args...)
|
2017-06-21 23:55:03 +03:00
|
|
|
c.Stdin = cmd.Stdin
|
|
|
|
c.Stdout = cmd.Stdout
|
|
|
|
c.Stderr = cmd.Stderr
|
2013-04-29 05:47:08 +04:00
|
|
|
|
|
|
|
return c.Run()
|
|
|
|
}
|
|
|
|
|
2014-11-30 23:14:18 +03:00
|
|
|
// 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 (cmd *Cmd) Exec() error {
|
2016-10-03 19:54:35 +03:00
|
|
|
verboseLog(cmd)
|
|
|
|
|
2014-11-30 23:14:18 +03:00
|
|
|
binary, err := exec.LookPath(cmd.Name)
|
|
|
|
if err != nil {
|
2016-10-03 20:09:41 +03:00
|
|
|
return &exec.Error{
|
|
|
|
Name: cmd.Name,
|
|
|
|
Err: fmt.Errorf("command not found"),
|
|
|
|
}
|
2014-11-30 23:14:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{binary}
|
|
|
|
args = append(args, cmd.Args...)
|
|
|
|
|
|
|
|
return syscall.Exec(binary, args, os.Environ())
|
|
|
|
}
|
|
|
|
|
2019-06-28 23:15:23 +03:00
|
|
|
func New(name string) *Cmd {
|
|
|
|
return &Cmd{
|
|
|
|
Name: name,
|
|
|
|
Args: []string{},
|
|
|
|
Stdin: os.Stdin,
|
|
|
|
Stdout: os.Stdout,
|
|
|
|
Stderr: os.Stderr,
|
|
|
|
}
|
2013-04-29 00:41:45 +04:00
|
|
|
}
|
2013-06-01 03:32:30 +04:00
|
|
|
|
|
|
|
func NewWithArray(cmd []string) *Cmd {
|
2017-08-05 02:16:16 +03:00
|
|
|
return &Cmd{Name: cmd[0], Args: cmd[1:], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}
|
2013-06-01 03:32:30 +04:00
|
|
|
}
|
2015-10-31 02:53:41 +03:00
|
|
|
|
|
|
|
func verboseLog(cmd *Cmd) {
|
|
|
|
if os.Getenv("HUB_VERBOSE") != "" {
|
2020-05-26 00:44:26 +03:00
|
|
|
msg := fmt.Sprintf("$ %s", cmd.String())
|
2015-10-31 02:53:41 +03:00
|
|
|
if ui.IsTerminal(os.Stderr) {
|
|
|
|
msg = fmt.Sprintf("\033[35m%s\033[0m", msg)
|
|
|
|
}
|
|
|
|
ui.Errorln(msg)
|
|
|
|
}
|
|
|
|
}
|