From a2a3fa99894f9d9ed8c5d4647116aca1e0e7e865 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Thu, 17 Nov 2016 05:30:47 -0200 Subject: [PATCH 1/2] Add assignment to setenv Signed-off-by: Tiago Natel de Moura --- ast/node.go | 20 +++++++++++++-- ast/node_fmt.go | 6 ++++- internal/sh/shell.go | 19 ++++++++++++++ internal/sh/shell_test.go | 41 ++++++++++++++++++++++++++++-- parser/parse.go | 35 +++++++++++++++++++++++--- parser/parse_test.go | 52 +++++++++++++++++++++++++++++++++++++-- spec.ebnf | 2 +- 7 files changed, 163 insertions(+), 12 deletions(-) diff --git a/ast/node.go b/ast/node.go index 7e481365..d5110999 100644 --- a/ast/node.go +++ b/ast/node.go @@ -68,6 +68,7 @@ type ( token.FileInfo varName string + assign Node } // AssignmentNode is a node for variable assignments @@ -455,18 +456,27 @@ func (n *ImportNode) IsEqual(other Node) bool { } // NewSetenvNode creates a new assignment node -func NewSetenvNode(info token.FileInfo, name string) *SetenvNode { +func NewSetenvNode(info token.FileInfo, name string, assign Node) (*SetenvNode, error) { + if assign != nil && assign.Type() != NodeAssignment && + assign.Type() != NodeExecAssign { + return nil, errors.New("Invalid assignment in setenv") + } + return &SetenvNode{ NodeType: NodeSetenv, FileInfo: info, varName: name, - } + assign: assign, + }, nil } // Identifier returns the environment name. func (n *SetenvNode) Identifier() string { return n.varName } +// Assignment returns the setenv assignment (if any) +func (n *SetenvNode) Assignment() Node { return n.assign } + // IsEqual returns if it is equal to the other node. func (n *SetenvNode) IsEqual(other Node) bool { if n == other { @@ -484,6 +494,12 @@ func (n *SetenvNode) IsEqual(other Node) bool { return false } + if n.assign != o.assign { + if !n.assign.IsEqual(o.assign) { + return false + } + } + return n.varName == o.varName } diff --git a/ast/node_fmt.go b/ast/node_fmt.go index f92b3444..d7ed978c 100644 --- a/ast/node_fmt.go +++ b/ast/node_fmt.go @@ -151,7 +151,11 @@ func (n *ImportNode) String() string { // String returns the string representation of assignment func (n *SetenvNode) String() string { - return "setenv " + n.varName + if n.assign == nil { + return "setenv " + n.varName + } + + return "setenv " + n.assign.String() } func (n *AssignmentNode) string() (string, bool) { diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 86e8e0ee..958e4a7a 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1474,8 +1474,27 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { var ( varValue sh.Obj ok bool + assign = v.Assignment() + err error ) + if assign != nil { + switch assign.Type() { + case ast.NodeAssignment: + err = shell.executeAssignment(assign.(*ast.AssignmentNode)) + case ast.NodeExecAssign: + err = shell.executeExecAssign(assign.(*ast.ExecAssignNode)) + default: + err = errors.NewEvalError(shell.filename, + v, "Failed to eval setenv, invalid assignment type: %+v", + assign) + } + + if err != nil { + return err + } + } + varName := v.Identifier() if varValue, ok = shell.Getvar(varName); !ok { diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 0196d799..94089af6 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -419,6 +419,44 @@ func TestExecuteRedirectionMap(t *testing.T) { } } +func TestExecuteSetenv(t *testing.T) { + for _, test := range []execTest{ + { + "test setenv basic", + `test = "hello" + setenv test + nash -c "echo $test"`, + "hello\n", "", "", + }, + { + "test setenv assignment", + `setenv test = "hello" + nash -c "echo $test"`, + "hello\n", "", "", + }, + { + "test setenv exec cmd", + `setenv test <= echo -n "hello" + nash -c "echo $test"`, + "hello\n", "", "", + }, + { + "test setenv semicolon", + `setenv a setenv b`, + "", "", + "test setenv semicolon:1:9: Unexpected token setenv, expected semicolon (;) or EOL", + }, + } { + testExec(t, + test.desc, + test.execStr, + test.expectedStdout, + test.expectedStderr, + test.expectedErr, + ) + } +} + func TestExecuteCd(t *testing.T) { for _, test := range []execTest{ { @@ -1689,8 +1727,7 @@ func TestExecuteSubShellDoesNotOverwriteparentEnv(t *testing.T) { return } - err = shell.Exec("set env", `SHELL = "bleh" -setenv SHELL`) + err = shell.Exec("set env", `setenv SHELL = "bleh"`) if err != nil { t.Error(err) diff --git a/parser/parse.go b/parser/parse.go index 2608ee52..4f3de971 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -454,19 +454,46 @@ func (p *Parser) parseImport(importToken scanner.Token) (ast.Node, error) { } func (p *Parser) parseSetenv(it scanner.Token) (ast.Node, error) { - fileInfo := it.FileInfo + var ( + setenv *ast.SetenvNode + assign ast.Node + err error + fileInfo = it.FileInfo + ) it = p.next() + next := p.peek() if it.Type() != token.Ident { - return nil, newParserError(it, p.name, "Unexpected token %v, expected VARIABLE", it) + return nil, newParserError(it, p.name, "Unexpected token %v, expected identifier", it) } - if p.peek().Type() == token.Semicolon { + if next.Type() == token.Assign || next.Type() == token.AssignCmd { + assign, err = p.parseAssignment(it) + + if err != nil { + return nil, err + } + + setenv, err = ast.NewSetenvNode(fileInfo, it.Value(), assign) + } else { + setenv, err = ast.NewSetenvNode(fileInfo, it.Value(), nil) + + if p.peek().Type() != token.Semicolon { + return nil, newParserError(p.peek(), + p.name, + "Unexpected token %v, expected semicolon (;) or EOL", + p.peek()) + } + p.ignore() } - return ast.NewSetenvNode(fileInfo, it.Value()), nil + if err != nil { + return nil, err + } + + return setenv, nil } func (p *Parser) getArgument(allowArg, allowConcat bool) (ast.Expr, error) { diff --git a/parser/parse_test.go b/parser/parse_test.go index 5bc35928..4347464d 100644 --- a/parser/parse_test.go +++ b/parser/parse_test.go @@ -111,12 +111,55 @@ func TestParsePipe(t *testing.T) { func TestBasicSetAssignment(t *testing.T) { expected := ast.NewTree("simple set assignment") ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) - set := ast.NewSetenvNode(token.NewFileInfo(1, 0), "test") + set, err := ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", nil) + + if err != nil { + t.Fatal(err) + } ln.Push(set) expected.Root = ln parserTestTable("simple set assignment", `setenv test`, expected, t, true) + + // setenv with assignment + expected = ast.NewTree("setenv with simple assignment") + ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) + assign := ast.NewAssignmentNode(token.NewFileInfo(1, 7), + "test", + ast.NewStringExpr(token.NewFileInfo(1, 15), "hello", true)) + set, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", assign) + + if err != nil { + t.Fatal(err) + } + + ln.Push(set) + expected.Root = ln + + parserTestTable("setenv with simple assignment", `setenv test = "hello"`, expected, t, true) + + expected = ast.NewTree("setenv with simple cmd assignment") + ln = ast.NewBlockNode(token.NewFileInfo(1, 0)) + + cmd := ast.NewCommandNode(token.NewFileInfo(1, 15), "ls", false) + + cmdAssign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 7), "test", cmd) + + if err != nil { + t.Fatal(err) + } + + set, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), "test", cmdAssign) + + if err != nil { + t.Fatal(err) + } + + ln.Push(set) + expected.Root = ln + + parserTestTable("simple assignment", `setenv test <= ls`, expected, t, true) } func TestBasicAssignment(t *testing.T) { @@ -436,7 +479,12 @@ func TestParseCd(t *testing.T) { assign := ast.NewAssignmentNode(token.NewFileInfo(1, 0), "HOME", ast.NewStringExpr(token.NewFileInfo(1, 8), "/", true)) - set := ast.NewSetenvNode(token.NewFileInfo(3, 0), "HOME") + set, err := ast.NewSetenvNode(token.NewFileInfo(3, 0), "HOME", nil) + + if err != nil { + t.Fatal(err) + } + cd = ast.NewCommandNode(token.NewFileInfo(5, 0), "cd", false) pwd := ast.NewCommandNode(token.NewFileInfo(6, 0), "pwd", false) diff --git a/spec.ebnf b/spec.ebnf index 6cbb5c82..9505728e 100644 --- a/spec.ebnf +++ b/spec.ebnf @@ -66,7 +66,7 @@ bindfn = "bindfn" identifier identifier . dump = "dump" [ filename ] . /* Set environment variable */ -setenvDecl = "setenv" identifier . +setenvDecl = "setenv" ( identifier | varDecl ) . /* Comment */ comment = "#" { unicode_char } . From e13fbdd1819b9938e917788da766de6f7f229047 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Thu, 17 Nov 2016 05:48:50 -0200 Subject: [PATCH 2/2] add absolute path to nash in tests Signed-off-by: Tiago Natel de Moura --- internal/sh/shell_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 94089af6..c12cfb11 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -425,19 +425,19 @@ func TestExecuteSetenv(t *testing.T) { "test setenv basic", `test = "hello" setenv test - nash -c "echo $test"`, + ` + nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, { "test setenv assignment", `setenv test = "hello" - nash -c "echo $test"`, + ` + nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, { "test setenv exec cmd", `setenv test <= echo -n "hello" - nash -c "echo $test"`, + ` + nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, {