Implement backtick expansion

master
Hugo Thunnissen 2 years ago
parent f6f3210165
commit b45c47d32d

@ -12,6 +12,9 @@ func (p *QuoteParameters) SubParsers() []Parser {
&BaseParser{ &BaseParser{
parameters: &DollarParameters{}, parameters: &DollarParameters{},
}, },
&BaseParser{
parameters: &BacktickParameters{},
},
&BaseParser{ &BaseParser{
parameters: &CharParserParameters{}, parameters: &CharParserParameters{},
}, },

@ -27,7 +27,8 @@ func (t *QuotesTest) TestParse() {
t.NoError(err) t.NoError(err)
expanded, _ := token.(Expandable).Expand(nil, nil, nil) expanded, _ := token.(Expandable).Expand(nil, nil, nil)
t.Equal("word1 word2 word3\n word4", strings.Join(expanded, " ")) t.Equal(1, len(expanded))
t.Equal("word1 word2 word3\n word4", expanded[0])
} }
func (t *QuotesTest) TestExpandVariables() { func (t *QuotesTest) TestExpandVariables() {
@ -72,6 +73,52 @@ func (t *QuotesTest) TestExpandSubshell() {
t.Equal("This is a subshell", expanded[0]) t.Equal("This is a subshell", expanded[0])
} }
func (t *QuotesTest) TestExpandBacktick() {
parser := &BaseParser{&QuoteParameters{}}
reader := bufio.NewReader(
strings.NewReader("\"This is `echo a backtick`\""),
)
state := NewShellState()
echo := &builtins.Echo{}
state.SetCommand(echo.Name(), echo)
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.NoError(err)
expanded, _ := token.(Expandable).Expand(state, nil, nil)
t.Equal(1, len(expanded))
t.Equal("This is a backtick", expanded[0])
}
// The only way backticks can be nested at the moment is by wrapping them in
// quotes.
func (t *QuotesTest) TestExpandNestedBacktick() {
parser := &BaseParser{&QuoteParameters{}}
reader := bufio.NewReader(
strings.NewReader("\"This is `echo a \"`echo backtick`\"`\""),
)
state := NewShellState()
echo := &builtins.Echo{}
state.SetCommand(echo.Name(), echo)
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.NoError(err)
expanded, _ := token.(Expandable).Expand(state, nil, nil)
t.Equal(1, len(expanded))
t.Equal("This is a backtick", expanded[0])
}
func TestQuotesTest(t *testing.T) { func TestQuotesTest(t *testing.T) {
suite.Run(t, new(QuotesTest)) suite.Run(t, new(QuotesTest))
} }

@ -1,6 +1,7 @@
package shell package shell
import ( import (
"bytes"
"io" "io"
"log" "log"
) )
@ -64,11 +65,17 @@ func (s *Subshell) Evaluate(state State, stdin io.Reader, stdout io.Writer, stde
} }
type Backtick struct { type Backtick struct {
Subshell BaseToken
} }
type BacktickParameters struct { type BacktickParameters struct{}
SubshellParameters
func (p *BacktickParameters) MakeToken() Token {
return &Backtick{}
}
func (p *BacktickParameters) Enter(i *CharIterator) error {
return i.Next()
} }
func (p *BacktickParameters) Supports(charsBefore []rune, r rune) bool { func (p *BacktickParameters) Supports(charsBefore []rune, r rune) bool {
@ -88,3 +95,53 @@ func (p *BacktickParameters) ShouldLeave(charsBefore []rune, r rune) bool {
return false return false
} }
func (b *Backtick) Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8) {
var ret uint8
stdout := bytes.NewBuffer([]byte{})
for _, token := range b.Tokens() {
if eval, ok := token.(Evalable); ok {
ret = state.Eval(eval, stdin, stdout, stderr)
} else {
logstr := "shell: Unexpected token: " + token.String()
log.Println(logstr)
stderr.Write([]byte(logstr))
}
}
return []string{stdout.String()}, ret
}
func (p *BacktickParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &StatementInsideBacktickparameters{},
},
}
}
type StatementInsideBacktickparameters struct {
StatementParameters
}
// Note: this is a bit of a hack. There is no way to know whether the new
// backtick is supposed to open a new backtick statement within the backtick, or
// close the current one. At least not in the current implementation that
// doesn't allow us to look ahead. Which means that backticks inside backticks
// are not supported.
func (p *StatementInsideBacktickparameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '\n' || r == ';' || r == ')' || r == '`' {
return countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *StatementInsideBacktickparameters) Leave(i *CharIterator) error {
if i.Current() == '`' {
return i.Previous()
}
return nil
}

Loading…
Cancel
Save