Skip to content

Commit

Permalink
interp: support aliases with many words
Browse files Browse the repository at this point in the history
Also add support for the trailing blank feature.

This is still not complete, but it covers pretty much all sane use cases
of aliases.

Fixes #456.
  • Loading branch information
mvdan committed Jan 16, 2020
1 parent 8953e71 commit 0f82a9d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 16 deletions.
44 changes: 37 additions & 7 deletions interp/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package interp

import (
"bytes"
"context"
"fmt"
"io"
Expand Down Expand Up @@ -561,28 +562,57 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.updateExpandOpts()

case "alias":
show := func(name string, als alias) {
var buf bytes.Buffer
if len(als.args) > 0 {
printer := syntax.NewPrinter()
printer.Print(&buf, &syntax.CallExpr{
Args: als.args,
})
}
if als.blank {
buf.WriteByte(' ')
}
r.outf("alias %s='%s'\n", name, &buf)
}

if len(args) == 0 {
for name, value := range r.alias {
r.outf("alias %s='%s'\n", name, value)
for name, als := range r.alias {
show(name, als)
}
}
for _, name := range args {
i := strings.IndexByte(name, '=')
if i < 1 { // don't save an empty name
value, ok := r.alias[name]
als, ok := r.alias[name]
if !ok {
r.errf("alias: %q not found\n", name)
continue
}
r.outf("alias %s='%s'\n", name, value)
show(name, als)
continue
}
value := name[i+1:]

// TODO: parse any CallExpr perhaps, or even any Stmt
parser := syntax.NewParser()
var words []*syntax.Word
src := name[i+1:]
if err := parser.Words(strings.NewReader(src), func(w *syntax.Word) bool {
words = append(words, w)
return true
}); err != nil {
r.errf("alias: could not parse %q: %v", src, err)
continue
}

name = name[:i]
if r.alias == nil {
r.alias = make(map[string]string)
r.alias = make(map[string]alias)
}
r.alias[name] = alias{
args: words,
blank: strings.TrimRight(src, " \t") != src,
}
r.alias[name] = value
}
case "unalias":
for _, name := range args {
Expand Down
29 changes: 20 additions & 9 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ type Runner struct {
Vars map[string]expand.Variable
Funcs map[string]*syntax.Stmt

alias map[string]string
alias map[string]alias

// execHandler is a function responsible for executing programs. It must be non-nil.
execHandler ExecHandlerFunc
Expand Down Expand Up @@ -466,6 +466,11 @@ type Runner struct {
bufCopier bufCopier
}

type alias struct {
args []*syntax.Word
blank bool
}

type bufCopier struct {
io.Reader
buf []byte
Expand Down Expand Up @@ -843,16 +848,22 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
r.exit = r2.exit
r.setErr(r2.err)
case *syntax.CallExpr:
if len(x.Args) > 0 && r.opts[optExpandAliases] {
if value, ok := r.alias[x.Args[0].Lit()]; ok {
// TODO: we probably want to parse the alias
// value as shell code (words?) instead.
x.Args[0].Parts = []syntax.WordPart{
&syntax.Lit{Value: value},
}
// Use a new slice, to not modify the slice in the alias map.
var args []*syntax.Word
left := x.Args
for len(left) > 0 && r.opts[optExpandAliases] {
als, ok := r.alias[left[0].Lit()]
if !ok {
break
}
args = append(args, als.args...)
left = left[1:]
if !als.blank {
break
}
}
fields := r.fields(x.Args...)
args = append(args, left...)
fields := r.fields(args...)
if len(fields) == 0 {
for _, as := range x.Assigns {
vr := r.assignVal(as, "")
Expand Down
8 changes: 8 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,14 @@ var runTests = []runTest{
"shopt -s expand_aliases; alias true=echo\ntrue foo; unalias true\ntrue bar",
"foo\n",
},
{
"shopt -s expand_aliases; alias echo='echo a'\necho b c",
"a b c\n",
},
{
"shopt -s expand_aliases; alias foo='echo '\nfoo foo; foo bar",
"echo\nbar\n",
},

// case
{
Expand Down

0 comments on commit 0f82a9d

Please sign in to comment.