diff --git a/docs/supported-features.md b/docs/supported-features.md index d11446af..0524a46a 100644 --- a/docs/supported-features.md +++ b/docs/supported-features.md @@ -39,3 +39,8 @@ key=value key2="value" command ```shell command | command2 | command3 ``` + +## Conditional Execution +```shell +command || command2 && command3 +``` diff --git a/generator/generator.go b/generator/generator.go index 25338132..2661f246 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -44,9 +44,60 @@ func (g *generator) generate(buf *InstructionBuffer, statement ast.Statement, pc *buf = append(*buf, ir.Closure{ Body: cmdbuf, }) + case ast.List: + g.handleList(buf, v) } } +func (g *generator) handleList(buf *InstructionBuffer, l ast.List) { + g.generate(buf, l.Left, nil) + + var bodybuf InstructionBuffer + g.generate(&bodybuf, l.Right, nil) + + buf.add(ir.IfLastExitCode{ + Zero: l.Operator == "&&", + Body: bodybuf, + }) +} + +func (g *generator) handlePipeline(buf *InstructionBuffer, p ast.Pipeline) { + buf.add(ir.NewPipelineWaitgroup("pipelineWaitgroup")) + + for i, cmd := range p { + if i < (len(p) - 1) { //last command doesn't need a pipe + buf.add(ir.NewPipe{ + Writer: fmt.Sprintf("pipeWriter%d", i+1), + Reader: fmt.Sprintf("pipeReader%d", i+1), + }) + } + + var pc pipeContext + if i == 0 { + pc = pipeContext{ + writer: fmt.Sprintf("pipeWriter%d", i+1), + stderr: cmd.Stderr, + } + } else if i == (len(p) - 1) { + pc = pipeContext{ + reader: fmt.Sprintf("pipeReader%d", i), + } + } else { + pc = pipeContext{ + writer: fmt.Sprintf("pipeWriter%d", i+1), + reader: fmt.Sprintf("pipeReader%d", i), + stderr: cmd.Stderr, + } + } + + pc.waitgroup = "pipelineWaitgroup" + g.generate(buf, cmd.Command, &pc) + } + + buf.add(ir.WaitPipelineWaitgroup("pipelineWaitgroup")) + +} + func (g *generator) handleSimpleCommand(buf *InstructionBuffer, cmd ast.Command, pc *pipeContext) { buf.add(ir.Declare{Name: "commandName", Value: g.handleExpression(cmd.Name)}) buf.add(ir.DeclareSlice{Name: "arguments"}) @@ -258,40 +309,3 @@ type pipeContext struct { waitgroup string stderr bool } - -func (g *generator) handlePipeline(buf *InstructionBuffer, p ast.Pipeline) { - buf.add(ir.NewPipelineWaitgroup("pipelineWaitgroup")) - - for i, cmd := range p { - if i < (len(p) - 1) { //last command doesn't need a pipe - buf.add(ir.NewPipe{ - Writer: fmt.Sprintf("pipeWriter%d", i+1), - Reader: fmt.Sprintf("pipeReader%d", i+1), - }) - } - - var pc pipeContext - if i == 0 { - pc = pipeContext{ - writer: fmt.Sprintf("pipeWriter%d", i+1), - stderr: cmd.Stderr, - } - } else if i == (len(p) - 1) { - pc = pipeContext{ - reader: fmt.Sprintf("pipeReader%d", i), - } - } else { - pc = pipeContext{ - writer: fmt.Sprintf("pipeWriter%d", i+1), - reader: fmt.Sprintf("pipeReader%d", i), - stderr: cmd.Stderr, - } - } - - pc.waitgroup = "pipelineWaitgroup" - g.generate(buf, cmd.Command, &pc) - } - - buf.add(ir.WaitPipelineWaitgroup("pipelineWaitgroup")) - -} diff --git a/generator/tests/04-lists.test b/generator/tests/04-lists.test new file mode 100644 index 00000000..1ddd6a38 --- /dev/null +++ b/generator/tests/04-lists.test @@ -0,0 +1,454 @@ +#(TEST: list with "||" operator) + +command || command2 || command3 + +#(RESULT) +package main + +import "bunster-build/runtime" + +func Main(shell *runtime.Shell) { + func() { + var commandName = `command` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + if shell.ExitCode != 0 { + func() { + var commandName = `command2` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } + if shell.ExitCode != 0 { + func() { + var commandName = `command3` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } +} + +#(ENDTEST) + + + +#(TEST: list with "&&" operator) + +command && command2 && command3 + +#(RESULT) +package main + +import "bunster-build/runtime" + +func Main(shell *runtime.Shell) { + func() { + var commandName = `command` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + if shell.ExitCode == 0 { + func() { + var commandName = `command2` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } + if shell.ExitCode == 0 { + func() { + var commandName = `command3` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } +} + +#(ENDTEST) + + + + +#(TEST: list with mixed "&&" and "||" operators) + +command && command2 || command3 + +#(RESULT) +package main + +import "bunster-build/runtime" + +func Main(shell *runtime.Shell) { + func() { + var commandName = `command` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + if shell.ExitCode == 0 { + func() { + var commandName = `command2` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } + if shell.ExitCode != 0 { + func() { + var commandName = `command3` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Run(); err != nil { + shell.HandleError(err) + return + } + shell.ExitCode = command.ProcessState.ExitCode() + + }() + + } +} + +#(ENDTEST) + + + + +#(TEST: lists of piplines) + +command | command2 || + command3 |& command4 && + command5 | command6 + + +#(RESULT) +package main + +import "bunster-build/runtime" + +func Main(shell *runtime.Shell) { + func() { + var pipelineWaitgroup runtime.PiplineWaitgroup + pipeReader1, pipeWriter1, err := runtime.NewPipe() + if err != nil { + shell.HandleError(err) + return + } + func() { + var commandName = `command` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`1`, pipeWriter1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + func() { + var commandName = `command2` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`0`, pipeReader1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + for i, item := range pipelineWaitgroup { + if err := item.Wait(); err != nil { + shell.HandleError(err) + } + if i < (len(pipelineWaitgroup) - 1) { + shell.ExitCode = 0 + } + } + + }() + if shell.ExitCode != 0 { + func() { + var pipelineWaitgroup runtime.PiplineWaitgroup + pipeReader1, pipeWriter1, err := runtime.NewPipe() + if err != nil { + shell.HandleError(err) + return + } + func() { + var commandName = `command3` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`1`, pipeWriter1) + commandFDT.Add(`2`, pipeWriter1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + func() { + var commandName = `command4` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`0`, pipeReader1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + for i, item := range pipelineWaitgroup { + if err := item.Wait(); err != nil { + shell.HandleError(err) + } + if i < (len(pipelineWaitgroup) - 1) { + shell.ExitCode = 0 + } + } + + }() + + } + if shell.ExitCode == 0 { + func() { + var pipelineWaitgroup runtime.PiplineWaitgroup + pipeReader1, pipeWriter1, err := runtime.NewPipe() + if err != nil { + shell.HandleError(err) + return + } + func() { + var commandName = `command5` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`1`, pipeWriter1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + func() { + var commandName = `command6` + var arguments []string + var command = shell.Command(commandName, arguments...) + commandFDT, err := shell.CloneFDT() + if err != nil { + shell.HandleError(err) + return + } + defer commandFDT.Destroy() + commandFDT.Add(`0`, pipeReader1) + command.Stdin = commandFDT.Get(`0`) + command.Stdout = commandFDT.Get(`1`) + command.Stderr = commandFDT.Get(`2`) + if err := command.Start(); err != nil { + shell.HandleError(err) + return + } + pipelineWaitgroup = append(pipelineWaitgroup, runtime.PiplineWaitgroupItem{ + Wait: command.Wait, + }) + }() + for i, item := range pipelineWaitgroup { + if err := item.Wait(); err != nil { + shell.HandleError(err) + } + if i < (len(pipelineWaitgroup) - 1) { + shell.ExitCode = 0 + } + } + + }() + + } +} + +#(ENDTEST) diff --git a/ir/ir.go b/ir/ir.go index 8f292326..470a5c88 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -154,3 +154,26 @@ type SetCmdEnv struct { func (s SetCmdEnv) togo() string { return fmt.Sprintf("%s.Env = append(%s.Env,`%s=` + %s)\n", s.Command, s.Command, s.Key, s.Value.togo()) } + +type IfLastExitCode struct { + Zero bool + Body []Instruction +} + +func (i IfLastExitCode) togo() string { + var condition = "shell.ExitCode == 0" + if !i.Zero { + condition = "shell.ExitCode != 0" + } + + var body string + for _, ins := range i.Body { + body += ins.togo() + } + + return fmt.Sprintf( + `if %s { + %s + } + `, condition, body) +}