fix sentinel collision, backslash escape, and timeout cleanup
This commit is contained in:
parent
57fba7acb0
commit
c7730bcaa6
2
parse.go
2
parse.go
|
|
@ -12,6 +12,8 @@ func stripComment(line string) string {
|
||||||
for i := 0; i < len(line); i++ {
|
for i := 0; i < len(line); i++ {
|
||||||
ch := line[i]
|
ch := line[i]
|
||||||
switch {
|
switch {
|
||||||
|
case ch == '\\' && !inSingle:
|
||||||
|
i++ // skip escaped character (backslash escapes in double-quoted and unquoted contexts)
|
||||||
case ch == '\'' && !inDouble:
|
case ch == '\'' && !inDouble:
|
||||||
inSingle = !inSingle
|
inSingle = !inSingle
|
||||||
case ch == '"' && !inSingle:
|
case ch == '"' && !inSingle:
|
||||||
|
|
|
||||||
29
run.go
29
run.go
|
|
@ -1,6 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
@ -11,7 +13,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sentinelPrefix = "__SHOUT_SENTINEL_"
|
func newSentinelPrefix() string {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
_, _ = rand.Read(b)
|
||||||
|
return fmt.Sprintf("__SHOUT_%x_", b)
|
||||||
|
}
|
||||||
|
|
||||||
type RunOptions struct {
|
type RunOptions struct {
|
||||||
CleanEnv bool
|
CleanEnv bool
|
||||||
|
|
@ -22,7 +28,7 @@ type RunOptions struct {
|
||||||
OnCommand func(Command)
|
OnCommand func(Command)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildScript(commands []Command) string {
|
func buildScript(commands []Command, sentinelPrefix string) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("exec 2>&1\n")
|
b.WriteString("exec 2>&1\n")
|
||||||
for i, cmd := range commands {
|
for i, cmd := range commands {
|
||||||
|
|
@ -33,7 +39,7 @@ func buildScript(commands []Command) string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSentinelOutput(raw string, commandCount int) (outputs [][]string, exitCodes []int) {
|
func parseSentinelOutput(raw string, commandCount int, sentinelPrefix string) (outputs [][]string, exitCodes []int) {
|
||||||
re := regexp.MustCompile(regexp.QuoteMeta(sentinelPrefix) + `(\d+)_(\d+)__`)
|
re := regexp.MustCompile(regexp.QuoteMeta(sentinelPrefix) + `(\d+)_(\d+)__`)
|
||||||
|
|
||||||
remaining := raw
|
remaining := raw
|
||||||
|
|
@ -91,7 +97,8 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
||||||
return FileResult{File: file, TmpDir: tmpDir}
|
return FileResult{File: file, TmpDir: tmpDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
script := buildScript(file.Commands)
|
sentinel := newSentinelPrefix()
|
||||||
|
script := buildScript(file.Commands, sentinel)
|
||||||
|
|
||||||
// Build environment
|
// Build environment
|
||||||
var envMap map[string]string
|
var envMap map[string]string
|
||||||
|
|
@ -115,7 +122,12 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
||||||
|
|
||||||
if len(opts.PathDirs) > 0 {
|
if len(opts.PathDirs) > 0 {
|
||||||
existing := envMap["PATH"]
|
existing := envMap["PATH"]
|
||||||
envMap["PATH"] = strings.Join(opts.PathDirs, ":") + ":" + existing
|
prepend := strings.Join(opts.PathDirs, ":")
|
||||||
|
if existing != "" {
|
||||||
|
envMap["PATH"] = prepend + ":" + existing
|
||||||
|
} else {
|
||||||
|
envMap["PATH"] = prepend
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
envSlice := make([]string, 0, len(envMap))
|
envSlice := make([]string, 0, len(envMap))
|
||||||
|
|
@ -172,12 +184,17 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
||||||
}
|
}
|
||||||
output = r.data
|
output = r.data
|
||||||
case <-time.After(totalTimeout):
|
case <-time.After(totalTimeout):
|
||||||
|
if cmd.Process != nil {
|
||||||
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
}
|
||||||
|
<-ch // drain the reader goroutine so pipe FDs are released
|
||||||
|
_ = cmd.Wait()
|
||||||
return FileResult{File: file, TmpDir: tmpDir, Error: "Timeout reading output"}
|
return FileResult{File: file, TmpDir: tmpDir, Error: "Timeout reading output"}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
|
|
||||||
outputs, exitCodesList := parseSentinelOutput(string(output), len(file.Commands))
|
outputs, exitCodesList := parseSentinelOutput(string(output), len(file.Commands), sentinel)
|
||||||
|
|
||||||
results := make([]CommandResult, len(file.Commands))
|
results := make([]CommandResult, len(file.Commands))
|
||||||
for i, c := range file.Commands {
|
for i, c := range file.Commands {
|
||||||
|
|
|
||||||
12
update.go
12
update.go
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -20,6 +21,13 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
|
||||||
output = append(output, line)
|
output = append(output, line)
|
||||||
|
|
||||||
if cmdIdx >= len(file.Commands) || cmdIdx >= len(results) {
|
if cmdIdx >= len(file.Commands) || cmdIdx >= len(results) {
|
||||||
|
// Out of bounds — skip past expected output, preserving original lines
|
||||||
|
j := i + 1
|
||||||
|
for j < len(lines) && !strings.HasPrefix(lines[j], "$ ") {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
output = append(output, lines[i+1:j]...)
|
||||||
|
i = j - 1
|
||||||
cmdIdx++
|
cmdIdx++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +70,10 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
|
||||||
output = append(output, result.Actual...)
|
output = append(output, result.Actual...)
|
||||||
if oldExitMarker != "" {
|
if oldExitMarker != "" {
|
||||||
output = append(output, oldExitMarker)
|
output = append(output, oldExitMarker)
|
||||||
|
} else if len(result.Actual) > 0 && exitCodeMarkerRe.MatchString(result.Actual[len(result.Actual)-1]) {
|
||||||
|
// Actual output's last line looks like an exit code marker.
|
||||||
|
// Add an explicit marker to prevent parser from consuming it as one.
|
||||||
|
output = append(output, fmt.Sprintf("[%d]", result.ExitCode))
|
||||||
}
|
}
|
||||||
for k := 0; k < trailingBlanks; k++ {
|
for k := 0; k < trailingBlanks; k++ {
|
||||||
output = append(output, "")
|
output = append(output, "")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user