Skip to content

Commit

Permalink
Allow VarPath-based assignment for shell cmds
Browse files Browse the repository at this point in the history
Assignments for shell cmds can currently only be done with raw
identifiers e.g.

  a, b = $!`...`

The past few commits have been improving the flexibility here by allow
arbitrary collection nesting via key-lookups in assignments. This commit
extends that to shell cmd output assignments, so you can now do e.g.

  a[1][0], b["key"] = $!`...`

and it will correctly assign items in the respective collections.

This is mostly for consistency around assignment rules.
  • Loading branch information
amterp committed Dec 15, 2024
1 parent 7a62d33 commit a6af521
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 42 deletions.
4 changes: 2 additions & 2 deletions core/gen_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (e SwitchAssignment) String() string {
}

type ShellCmd struct {
Identifiers []Token
Paths []VarPath
Unsafe *Token
Quiet *Token
Dollar Token
Expand All @@ -188,7 +188,7 @@ func (e ShellCmd) Accept(visitor StmtVisitor) {
}
func (e ShellCmd) String() string {
var parts []string
parts = append(parts, fmt.Sprintf("Identifiers: %v", e.Identifiers))
parts = append(parts, fmt.Sprintf("Paths: %v", e.Paths))
parts = append(parts, fmt.Sprintf("Unsafe: %v", e.Unsafe))
parts = append(parts, fmt.Sprintf("Quiet: %v", e.Quiet))
parts = append(parts, fmt.Sprintf("Dollar: %v", e.Dollar))
Expand Down
3 changes: 1 addition & 2 deletions core/generators/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ func main() {
"JsonPathAssign : Token Identifier, JsonPath Path",
"SwitchBlockStmt : SwitchBlock Block",
"SwitchAssignment : []VarPath Paths, SwitchBlock Block",
// todo shell cmd should also use varpaths
"ShellCmd : []Token Identifiers, *Token Unsafe, *Token Quiet, Token Dollar, *Token Bang, Expr CmdExpr, *Block FailBlock, *Block RecoverBlock",
"ShellCmd : []VarPath Paths, *Token Unsafe, *Token Quiet, Token Dollar, *Token Bang, Expr CmdExpr, *Block FailBlock, *Block RecoverBlock",
"Block : []Stmt Stmts",
"IfStmt : []IfCase Cases, *Block ElseBlock",
"ForStmt : Token ForToken, Token Identifier1, *Token Identifier2, Expr Range, Block Body",
Expand Down
46 changes: 27 additions & 19 deletions core/interpreter_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import (
// - silent keyword to suppress output?

func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
identifiers := shellCmd.Identifiers
paths := shellCmd.Paths
token := shellCmd.Dollar
if len(identifiers) > 3 {
i.error(token, "At most 3 identifiers allowed for assignment with shell commands")
if len(paths) > 3 {
i.error(token, "At most 3 assignments allowed with shell commands")
}

cmdValue := shellCmd.CmdExpr.Accept(i)
Expand All @@ -33,16 +33,16 @@ func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
cmd := resolveCmd(i, token, cmdStr.Plain())
var stdout, stderr bytes.Buffer

captureStdout := len(identifiers) >= 2
captureStderr := len(identifiers) >= 3
captureStdout := len(paths) >= 2
captureStderr := len(paths) >= 3

var stdoutPipe, stderrPipe io.ReadCloser
var err error

if captureStdout {
stdoutPipe, err = cmd.StdoutPipe()
if err != nil {
handleError(i, identifiers, stdout, stderr, 1, fmt.Sprintf("Error creating stdout pipe: %v", err), shellCmd)
handleError(i, token, paths, stdout, stderr, 1, fmt.Sprintf("Error creating stdout pipe: %v", err), shellCmd)
}
} else {
cmd.Stdout = RIo.StdOut
Expand All @@ -51,7 +51,7 @@ func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
if captureStderr {
stderrPipe, err = cmd.StderrPipe()
if err != nil {
handleError(i, identifiers, stdout, stderr, 1, fmt.Sprintf("Error creating stderr pipe: %v", err), shellCmd)
handleError(i, token, paths, stdout, stderr, 1, fmt.Sprintf("Error creating stderr pipe: %v", err), shellCmd)
}
} else {
cmd.Stderr = RIo.StdErr
Expand All @@ -61,7 +61,7 @@ func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
RP.RadInfo(fmt.Sprintf("⚡️ Running: %s\n", cmdStr.String()))
}
if err := cmd.Start(); err != nil {
handleError(i, identifiers, stdout, stderr, 1, fmt.Sprintf("Error starting command: %v", err), shellCmd)
handleError(i, token, paths, stdout, stderr, 1, fmt.Sprintf("Error starting command: %v", err), shellCmd)
}

if captureStdout || captureStderr {
Expand All @@ -86,7 +86,7 @@ func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
err = cmd.Wait()
if pipeErr := <-errCh; pipeErr != nil {
RP.RadDebug("pipe error")
handleError(i, identifiers, stdout, stderr, 1, fmt.Sprintf("Failed to run command:\n%s", pipeErr.Error()), shellCmd)
handleError(i, token, paths, stdout, stderr, 1, fmt.Sprintf("Failed to run command:\n%s", pipeErr.Error()), shellCmd)
return
}
} else {
Expand All @@ -97,27 +97,28 @@ func (i *MainInterpreter) VisitShellCmdStmt(shellCmd ShellCmd) {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
RP.RadDebug("exit error with error code")
handleError(i, identifiers, stdout, stderr, exitErr.ExitCode(), fmt.Sprintf("Failed to run command: %v\nStderr: %s", err, stderr.String()), shellCmd)
handleError(i, token, paths, stdout, stderr, exitErr.ExitCode(), fmt.Sprintf("Failed to run command: %v\nStderr: %s", err, stderr.String()), shellCmd)
} else {
RP.RadDebug("exit error without error code")
handleError(i, identifiers, stdout, stderr, 1, fmt.Sprintf("Failed to run command: %v\nStderr: %s", err, stderr.String()), shellCmd)
handleError(i, token, paths, stdout, stderr, 1, fmt.Sprintf("Failed to run command: %v\nStderr: %s", err, stderr.String()), shellCmd)
}
return
}

defineIdentifiers(i, identifiers, stdout, stderr, 0)
doAssignment(i, token, paths, stdout, stderr, 0)
}

func handleError(
i *MainInterpreter,
identifiers []Token,
tkn Token,
paths []VarPath,
stdout bytes.Buffer,
stderr bytes.Buffer,
errorCode int,
err string,
cmd ShellCmd,
) {
defineIdentifiers(i, identifiers, stdout, stderr, errorCode)
doAssignment(i, tkn, paths, stdout, stderr, errorCode)

if cmd.Bang != nil {
// Critical command, exit
Expand Down Expand Up @@ -169,15 +170,22 @@ func buildCmd(shellStr string, cmdStr string) *exec.Cmd {
return cmd
}

func defineIdentifiers(i *MainInterpreter, identifiers []Token, stdout bytes.Buffer, stderr bytes.Buffer, errorCode int) {
for j, identifier := range identifiers {
func doAssignment(
i *MainInterpreter,
tkn Token,
paths []VarPath,
stdout bytes.Buffer,
stderr bytes.Buffer,
errorCode int,
) {
for j, path := range paths {
switch j {
case 0:
i.env.SetAndImplyType(identifier, int64(errorCode))
i.setValForPath(tkn, path, int64(errorCode))
case 1:
i.env.SetAndImplyType(identifier, stdout.String())
i.setValForPath(tkn, path, stdout.String())
case 2:
i.env.SetAndImplyType(identifier, stderr.String())
i.setValForPath(tkn, path, stderr.String())
}
}
}
10 changes: 4 additions & 6 deletions core/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func (p *Parser) statement() Stmt {
}

if p.isShellCmdNext() {
return p.shellCmd([]Token{})
return p.shellCmd([]VarPath{})
}

if p.peekTypeSeries(IDENTIFIER, LEFT_PAREN) {
Expand Down Expand Up @@ -647,9 +647,7 @@ func (p *Parser) assignment() Stmt {
}

if p.isShellCmdNext() {
// todo: don't require identifiers here RAD-56
identifiers := p.GetIdentifiers(paths)
return p.shellCmd(identifiers)
return p.shellCmd(paths)
}

return p.basicAssignment(equal, paths)
Expand Down Expand Up @@ -1355,7 +1353,7 @@ func (p *Parser) isShellCmdNext() bool {
return p.peekKeyword(GLOBAL_KEYWORDS, UNSAFE) || p.peekKeyword(GLOBAL_KEYWORDS, QUIET) || p.peekType(DOLLAR)
}

func (p *Parser) shellCmd(identifiers []Token) Stmt {
func (p *Parser) shellCmd(paths []VarPath) Stmt {
var unsafeToken *Token
var quietToken *Token
for p.peekKeyword(GLOBAL_KEYWORDS, UNSAFE) || p.peekKeyword(GLOBAL_KEYWORDS, QUIET) {
Expand Down Expand Up @@ -1428,7 +1426,7 @@ func (p *Parser) shellCmd(identifiers []Token) Stmt {
}

return &ShellCmd{
Identifiers: identifiers,
Paths: paths,
Unsafe: unsafeToken,
Quiet: quietToken,
Dollar: dollarToken,
Expand Down
13 changes: 0 additions & 13 deletions core/parser_utils.go

This file was deleted.

0 comments on commit a6af521

Please sign in to comment.