package shelly import ( "errors" "fmt" "log" "os/exec" "strings" ) type Mode int type Opts struct { SetupProc func(cmd *exec.Cmd) } 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" } return "UNKNOWN" } const ( MODE_NORMAL Mode = iota MODE_STR MODE_ESCAPE MODE_ESCAPE_STR ) // 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: sb.WriteRune(a) default: return nil, errors.New(fmt.Sprintf("Error at char %d - cant add space here. Mode: %s", i, mode.String())) } 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 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_ESCAPE: sb.WriteString("\\") mode = MODE_NORMAL case MODE_ESCAPE_STR: sb.WriteString("\\") mode = MODE_STR } default: switch mode { case MODE_NORMAL: sb.WriteRune(a) case 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 } } } 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) ([][]string, error) { ret := make([][]string, 0) tokens, err := Tokens(str) if err != nil { return nil, err } line := make([]string, 0) for _, tk := range tokens { switch tk { case "\n", "&&", ";": if len(line) > 0 { ret = append(ret, line) } line = make([]string, 0) default: line = append(line, tk) } } if len(line) > 0 { ret = append(ret, line) } return ret, nil } // Exec will call Lines and execute one by one func Exec(str string, opts ...*Opts) error { lines, err := Lines(str) if err != nil { return err } for _, l := range lines { if len(l) < 1 { continue } if strings.HasPrefix(l[0], "#") { continue } cmd := exec.Command(l[0], l[1:]...) if opts != nil && len(opts) > 0 { opts[0].SetupProc(cmd) } cmd.Stdout = log.Writer() cmd.Stderr = log.Writer() err = cmd.Run() if err != nil { return err } } return nil }