490 lines
9.4 KiB
Go
490 lines
9.4 KiB
Go
package shelly
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
)
|
|
|
|
type Mode int
|
|
|
|
func (m Mode) String() string {
|
|
switch m {
|
|
case MODE_NORMAL:
|
|
return "NORMAL"
|
|
case MODE_STR:
|
|
return "STR"
|
|
case MODE_ESCAPE:
|
|
return "ESCAPE"
|
|
case MODE_ESCAPE_STR:
|
|
return "ESCAPE_STR"
|
|
case MODE_TAG:
|
|
return "MODE_TAG"
|
|
case MODE_ESCAPE_TAG:
|
|
return "MODE_ESCAPE_TAG"
|
|
}
|
|
|
|
return "UNKNOWN"
|
|
}
|
|
|
|
const (
|
|
MODE_NORMAL Mode = iota
|
|
MODE_STR
|
|
MODE_TAG
|
|
MODE_ESCAPE
|
|
MODE_ESCAPE_STR
|
|
MODE_ESCAPE_TAG
|
|
)
|
|
|
|
type LineType int
|
|
|
|
const (
|
|
LINETYPE_SERIAL LineType = iota
|
|
LINETYPE_PARALLEL
|
|
LINETYPE_COMMENT
|
|
LINETYPE_CHANGEWD
|
|
)
|
|
|
|
// Opts tells Exec how to behave
|
|
// Opts.Await will inform whether its call should wait for all parallel started jobs to finish
|
|
// Opts.SetupProc will customize the processes before execution - Eg.: setting out and err
|
|
type Opts struct {
|
|
Debug bool
|
|
Trace bool
|
|
Await bool
|
|
Wd string
|
|
SetupProc func(cmd *exec.Cmd)
|
|
}
|
|
|
|
// Line represents a line to be executed by the shell.
|
|
// Can be separated by \n our ; - in case of serial execution. & will be used to identify parallel execution
|
|
type Line struct {
|
|
Tokens []string
|
|
LType LineType
|
|
}
|
|
|
|
// Tokens will break the string into tokens - It can be later organized in command lines and alikes
|
|
func Tokens(str string) ([]string, error) {
|
|
params := make([]string, 0)
|
|
sb := strings.Builder{}
|
|
mode := MODE_NORMAL
|
|
for i, a := range str {
|
|
switch a {
|
|
case ' ', '\t', '\r':
|
|
switch mode {
|
|
case MODE_NORMAL:
|
|
if sb.Len() > 0 {
|
|
params = append(params, sb.String())
|
|
}
|
|
sb = strings.Builder{}
|
|
case MODE_STR, MODE_TAG:
|
|
sb.WriteRune(a)
|
|
default:
|
|
return nil, errors.New(fmt.Sprintf("Error at char %d - cant add space here. Mode: %s", i, mode.String()))
|
|
}
|
|
case '=':
|
|
switch mode {
|
|
case MODE_NORMAL:
|
|
if sb.Len() > 0 {
|
|
params = append(params, sb.String())
|
|
sb = strings.Builder{}
|
|
}
|
|
params = append(params, "=")
|
|
case MODE_STR, MODE_ESCAPE_STR:
|
|
sb.WriteRune(a)
|
|
}
|
|
|
|
case '\n':
|
|
switch mode {
|
|
case MODE_NORMAL:
|
|
if sb.Len() > 0 {
|
|
params = append(params, sb.String())
|
|
}
|
|
params = append(params, "\n")
|
|
sb = strings.Builder{}
|
|
case MODE_STR:
|
|
sb.WriteRune(a)
|
|
default:
|
|
return nil, errors.New(fmt.Sprintf("Error at char %d - cant add new line here. Mode: %s", i, mode.String()))
|
|
}
|
|
case '"':
|
|
switch mode {
|
|
case MODE_NORMAL:
|
|
if sb.Len() > 0 {
|
|
params = append(params, sb.String())
|
|
sb = strings.Builder{}
|
|
mode = MODE_STR
|
|
}
|
|
mode = MODE_STR
|
|
case MODE_TAG:
|
|
sb.WriteRune(a)
|
|
params = append(params, sb.String())
|
|
sb = strings.Builder{}
|
|
mode = MODE_NORMAL
|
|
case MODE_STR:
|
|
params = append(params, sb.String())
|
|
sb = strings.Builder{}
|
|
mode = MODE_NORMAL
|
|
case MODE_ESCAPE:
|
|
sb.WriteRune(a)
|
|
mode = MODE_NORMAL
|
|
case MODE_ESCAPE_STR:
|
|
sb.WriteRune(a)
|
|
mode = MODE_STR
|
|
}
|
|
|
|
case '\\':
|
|
switch mode {
|
|
case MODE_NORMAL:
|
|
mode = MODE_ESCAPE
|
|
case MODE_STR:
|
|
mode = MODE_ESCAPE_STR
|
|
case MODE_TAG:
|
|
mode = MODE_ESCAPE_TAG
|
|
case MODE_ESCAPE:
|
|
sb.WriteString("\\")
|
|
mode = MODE_NORMAL
|
|
case MODE_ESCAPE_STR:
|
|
sb.WriteString("\\")
|
|
mode = MODE_STR
|
|
}
|
|
default:
|
|
switch mode {
|
|
case MODE_NORMAL, MODE_TAG, MODE_STR:
|
|
sb.WriteRune(a)
|
|
|
|
case MODE_ESCAPE:
|
|
sb.WriteString("\\" + string(a))
|
|
mode = MODE_NORMAL
|
|
case MODE_ESCAPE_STR:
|
|
sb.WriteString("\\" + string(a))
|
|
mode = MODE_STR
|
|
case MODE_ESCAPE_TAG:
|
|
sb.WriteString("\\" + string(a))
|
|
mode = MODE_TAG
|
|
}
|
|
|
|
}
|
|
}
|
|
if mode != MODE_NORMAL {
|
|
return nil, errors.New("Unterminated String")
|
|
}
|
|
if sb.Len() > 0 {
|
|
params = append(params, sb.String())
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
// Lines will call Tokens first, and reorg them in executable lines
|
|
func Lines(str string) ([]Line, error) {
|
|
ret := make([]Line, 0)
|
|
tokens, err := Tokens(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
line := Line{
|
|
Tokens: make([]string, 0),
|
|
LType: LINETYPE_SERIAL,
|
|
}
|
|
|
|
addLine := func(line Line) {
|
|
if strings.HasPrefix(line.Tokens[0], "#") {
|
|
line.LType = LINETYPE_COMMENT
|
|
}
|
|
|
|
if line.Tokens[0] == "cd" {
|
|
line.LType = LINETYPE_CHANGEWD
|
|
}
|
|
|
|
if runtime.GOOS == "windows" && (line.LType == LINETYPE_SERIAL || line.LType == LINETYPE_PARALLEL) {
|
|
if !strings.HasSuffix(line.Tokens[0], ".exe") {
|
|
line.Tokens[0] = line.Tokens[0] + ".exe"
|
|
}
|
|
}
|
|
|
|
ret = append(ret, line)
|
|
}
|
|
|
|
for _, tk := range tokens {
|
|
switch tk {
|
|
|
|
case "\n", ";", "&&":
|
|
if len(line.Tokens) > 0 {
|
|
addLine(line)
|
|
}
|
|
line = Line{
|
|
Tokens: make([]string, 0),
|
|
}
|
|
case "&":
|
|
if len(line.Tokens) > 0 {
|
|
line.LType = LINETYPE_PARALLEL
|
|
addLine(line)
|
|
}
|
|
line = Line{
|
|
Tokens: make([]string, 0),
|
|
}
|
|
default:
|
|
if len(line.Tokens) > 2 && line.Tokens[len(line.Tokens)-1] == "=" {
|
|
line.Tokens[len(line.Tokens)-2] = line.Tokens[len(line.Tokens)-2] + "=" + tk
|
|
line.Tokens = line.Tokens[:len(line.Tokens)-1]
|
|
} else {
|
|
line.Tokens = append(line.Tokens, tk)
|
|
}
|
|
|
|
}
|
|
}
|
|
if len(line.Tokens) > 0 {
|
|
addLine(line)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// Exec will call Lines and execute one by one
|
|
func Exec(str string, opts ...*Opts) ([]*exec.Cmd, error) {
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := make([]*exec.Cmd, 0)
|
|
opt := &Opts{
|
|
Await: false,
|
|
Wd: wd,
|
|
SetupProc: func(cmd *exec.Cmd) {
|
|
|
|
},
|
|
}
|
|
if opts != nil && len(opts) > 0 {
|
|
opt = opts[0]
|
|
}
|
|
|
|
if opt.SetupProc == nil {
|
|
opt.SetupProc = func(cmd *exec.Cmd) {
|
|
|
|
}
|
|
}
|
|
|
|
if opt.Trace {
|
|
bs := debug.Stack()
|
|
log.Printf("Running: %s\n%s", str, string(bs))
|
|
}
|
|
|
|
cmdwd := opt.Wd
|
|
if !filepath.IsAbs(cmdwd) {
|
|
cmdwd = filepath.Join(wd, cmdwd)
|
|
}
|
|
prepCmd := func(l Line) *exec.Cmd {
|
|
fqncmd, err := exec.LookPath(l.Tokens[0])
|
|
if err != nil {
|
|
fqncmd = filepath.Join(cmdwd, l.Tokens[0])
|
|
}
|
|
if opt.Debug {
|
|
log.Printf("Will call CMD: %s", fqncmd)
|
|
}
|
|
cmd := exec.Command(fqncmd, l.Tokens[1:]...)
|
|
cmd.Stdout = log.Writer()
|
|
cmd.Stderr = log.Writer()
|
|
cmd.Dir = cmdwd
|
|
opt.SetupProc(cmd)
|
|
ret = append(ret, cmd)
|
|
return cmd
|
|
}
|
|
|
|
lines, err := Lines(str)
|
|
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
for _, l := range lines {
|
|
|
|
switch l.LType {
|
|
case LINETYPE_COMMENT:
|
|
continue
|
|
case LINETYPE_CHANGEWD:
|
|
|
|
if filepath.IsAbs(l.Tokens[1]) {
|
|
cmdwd = l.Tokens[1]
|
|
} else {
|
|
cmdwd, err = filepath.Abs(filepath.Join(cmdwd, l.Tokens[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if opt.Debug {
|
|
log.Printf("CMDWD NOW IS: %s", cmdwd)
|
|
}
|
|
continue
|
|
case LINETYPE_SERIAL:
|
|
cmd := prepCmd(l)
|
|
if opt.Debug {
|
|
log.Printf("Running %s from dir %s with params %s", cmd.Path, cmd.Dir, strings.Join(cmd.Args, ","))
|
|
}
|
|
err = cmd.Run()
|
|
if opt.Debug {
|
|
if err != nil {
|
|
log.Printf("Error running: %s: %s", cmd.Path, err.Error())
|
|
}
|
|
}
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
case LINETYPE_PARALLEL:
|
|
cmd := prepCmd(l)
|
|
|
|
if opt.Debug {
|
|
log.Printf("Running %s from dir %s with params %v", cmd.Path, cmd.Dir, cmd.Args)
|
|
}
|
|
err = cmd.Start()
|
|
if opt.Debug {
|
|
if err != nil {
|
|
log.Printf("Error running: %s: %s", cmd.Path, err.Error())
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return ret, err
|
|
|
|
}
|
|
|
|
func ExecStr(str string, opts ...*Opts) (string, error) {
|
|
sb := strings.Builder{}
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ret := make([]*exec.Cmd, 0)
|
|
opt := &Opts{
|
|
Await: false,
|
|
Wd: wd,
|
|
SetupProc: func(cmd *exec.Cmd) {
|
|
|
|
},
|
|
}
|
|
if opts != nil && len(opts) > 0 {
|
|
opt = opts[0]
|
|
}
|
|
|
|
if opt.SetupProc == nil {
|
|
opt.SetupProc = func(cmd *exec.Cmd) {
|
|
|
|
}
|
|
}
|
|
|
|
if opt.Trace {
|
|
bs := debug.Stack()
|
|
log.Printf("Running: %s\n%s", str, string(bs))
|
|
}
|
|
|
|
cmdwd := opt.Wd
|
|
|
|
prepCmd := func(l Line) (*exec.Cmd, error) {
|
|
str, err := exec.LookPath(l.Tokens[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cmd := exec.Command(str, l.Tokens[1:]...)
|
|
// cmd.Stdout = log.Writer()
|
|
// cmd.Stderr = log.Writer()
|
|
cmd.Dir = cmdwd
|
|
opt.SetupProc(cmd)
|
|
ret = append(ret, cmd)
|
|
return cmd, nil
|
|
}
|
|
|
|
lines, err := Lines(str)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, l := range lines {
|
|
|
|
switch l.LType {
|
|
case LINETYPE_COMMENT:
|
|
continue
|
|
case LINETYPE_CHANGEWD:
|
|
|
|
if filepath.IsAbs(l.Tokens[1]) {
|
|
cmdwd = l.Tokens[1]
|
|
} else {
|
|
cmdwd, err = filepath.Abs(filepath.Join(wd, l.Tokens[1]))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
log.Printf("CMDWD NOW IS: %s", cmdwd)
|
|
continue
|
|
case LINETYPE_SERIAL, LINETYPE_PARALLEL:
|
|
cmd, err := prepCmd(l)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if opt.Debug {
|
|
log.Printf("Running %s from dir %s with params %v", cmd.Path, cmd.Dir, cmd.Args)
|
|
}
|
|
bs, err := cmd.CombinedOutput()
|
|
sb.Write(bs)
|
|
sb.WriteString("\n")
|
|
if opt.Debug {
|
|
if err != nil {
|
|
log.Printf("Error running: %s: %s", cmd.Path, err.Error())
|
|
}
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return sb.String(), err
|
|
|
|
}
|
|
|
|
// Kill call OS to kill pid
|
|
func Kill(p int) error {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
_, err := Exec(fmt.Sprintf("taskkill /T /F /PID %d", p))
|
|
return err
|
|
default:
|
|
_, err := Exec(fmt.Sprintf("kill -9 %d", p))
|
|
return err
|
|
|
|
}
|
|
}
|
|
|
|
// Simple Runs a simple command line
|
|
func Simple(str string) (*exec.Cmd, error) {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines, err := Lines(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
l := lines[0]
|
|
fqncmd, err := exec.LookPath(l.Tokens[0])
|
|
if err != nil {
|
|
fqncmd = filepath.Join(wd, l.Tokens[0])
|
|
}
|
|
cmd := exec.Command(fqncmd, l.Tokens[1:]...)
|
|
return cmd, nil
|
|
|
|
}
|