1
0
mirror of https://github.com/go-task/task.git synced 2025-03-05 15:05:42 +02:00

353 lines
7.2 KiB
Go
Raw Normal View History

2017-04-24 09:47:10 -03:00
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
2018-12-15 15:44:17 -02:00
package expand
2017-04-24 09:47:10 -03:00
import (
2018-01-03 15:12:40 -02:00
"fmt"
"regexp"
2018-02-11 22:02:22 -02:00
"sort"
2017-04-24 09:47:10 -03:00
"strconv"
"strings"
"unicode"
"unicode/utf8"
2019-11-24 21:00:02 -03:00
"mvdan.cc/sh/v3/pattern"
2019-09-26 19:04:09 -03:00
"mvdan.cc/sh/v3/syntax"
2017-04-24 09:47:10 -03:00
)
2018-12-15 15:44:17 -02:00
func nodeLit(node syntax.Node) string {
if word, ok := node.(*syntax.Word); ok {
return word.Lit()
2018-01-03 15:12:40 -02:00
}
return ""
}
2018-12-15 15:44:17 -02:00
type UnsetParameterError struct {
Node *syntax.ParamExp
Message string
}
func (u UnsetParameterError) Error() string {
return u.Message
2017-06-04 16:06:04 -03:00
}
2018-12-15 15:44:17 -02:00
func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
oldParam := cfg.curParam
cfg.curParam = pe
defer func() { cfg.curParam = oldParam }()
2017-04-24 09:47:10 -03:00
name := pe.Param.Value
2018-01-03 15:12:40 -02:00
index := pe.Index
2017-04-24 09:47:10 -03:00
switch name {
2018-01-03 15:12:40 -02:00
case "@", "*":
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: name},
}}
2018-12-15 15:44:17 -02:00
}
var vr Variable
switch name {
2018-01-03 15:12:40 -02:00
case "LINENO":
2018-12-15 15:44:17 -02:00
// This is the only parameter expansion that the environment
// interface cannot satisfy.
line := uint64(cfg.curParam.Pos().Line())
2019-09-26 19:04:09 -03:00
vr = Variable{Kind: String, Str: strconv.FormatUint(line, 10)}
2017-04-24 09:47:10 -03:00
default:
2018-12-15 15:44:17 -02:00
vr = cfg.Env.Get(name)
2017-04-24 09:47:10 -03:00
}
2018-12-15 15:44:17 -02:00
orig := vr
_, vr = vr.Resolve(cfg.Env)
str, err := cfg.varInd(vr, index)
if err != nil {
return "", err
2018-01-03 15:12:40 -02:00
}
2018-12-15 15:44:17 -02:00
slicePos := func(n int) int {
if n < 0 {
n = len(str) + n
if n < 0 {
n = len(str)
2018-01-21 09:39:15 -02:00
}
2018-12-15 15:44:17 -02:00
} else if n > len(str) {
n = len(str)
2018-01-21 09:39:15 -02:00
}
2018-12-15 15:44:17 -02:00
return n
2018-01-21 09:39:15 -02:00
}
2018-02-11 22:02:22 -02:00
elems := []string{str}
2018-12-15 15:44:17 -02:00
switch nodeLit(index) {
case "@", "*":
2019-09-26 19:04:09 -03:00
switch vr.Kind {
case Unset:
2018-02-11 22:02:22 -02:00
elems = nil
2019-09-26 19:04:09 -03:00
case Indexed:
elems = vr.List
2018-02-11 22:02:22 -02:00
}
}
2018-01-21 09:39:15 -02:00
switch {
case pe.Length:
2018-02-11 22:02:22 -02:00
n := len(elems)
2018-12-15 15:44:17 -02:00
switch nodeLit(index) {
case "@", "*":
default:
2018-01-03 15:12:40 -02:00
n = utf8.RuneCountInString(str)
}
str = strconv.Itoa(n)
case pe.Excl:
2018-02-11 22:02:22 -02:00
var strs []string
2019-09-26 19:04:09 -03:00
switch {
case pe.Names != 0:
2018-12-15 15:44:17 -02:00
strs = cfg.namesByPrefix(pe.Param.Value)
2019-09-26 19:04:09 -03:00
case orig.Kind == NameRef:
strs = append(strs, orig.Str)
case vr.Kind == Indexed:
for i, e := range vr.List {
2018-02-11 22:02:22 -02:00
if e != "" {
strs = append(strs, strconv.Itoa(i))
}
}
2019-09-26 19:04:09 -03:00
case vr.Kind == Associative:
for k := range vr.Map {
2018-02-11 22:02:22 -02:00
strs = append(strs, k)
}
2019-09-26 19:04:09 -03:00
case !syntax.ValidName(str):
return "", fmt.Errorf("invalid indirect expansion")
default:
2018-12-15 15:44:17 -02:00
vr = cfg.Env.Get(str)
strs = append(strs, vr.String())
2018-01-03 15:12:40 -02:00
}
2018-02-11 22:02:22 -02:00
sort.Strings(strs)
str = strings.Join(strs, " ")
2018-01-21 09:39:15 -02:00
case pe.Slice != nil:
2017-04-24 09:47:10 -03:00
if pe.Slice.Offset != nil {
2018-12-15 15:44:17 -02:00
n, err := Arithm(cfg, pe.Slice.Offset)
if err != nil {
return "", err
}
str = str[slicePos(n):]
2017-04-24 09:47:10 -03:00
}
if pe.Slice.Length != nil {
2018-12-15 15:44:17 -02:00
n, err := Arithm(cfg, pe.Slice.Length)
if err != nil {
return "", err
}
str = str[:slicePos(n)]
2017-04-24 09:47:10 -03:00
}
2018-01-21 09:39:15 -02:00
case pe.Repl != nil:
2018-12-15 15:44:17 -02:00
orig, err := Pattern(cfg, pe.Repl.Orig)
if err != nil {
return "", err
}
with, err := Literal(cfg, pe.Repl.With)
if err != nil {
return "", err
}
2017-04-24 09:47:10 -03:00
n := 1
if pe.Repl.All {
n = -1
}
2018-01-03 15:12:40 -02:00
locs := findAllIndex(orig, str, n)
2018-12-15 15:44:17 -02:00
buf := cfg.strBuilder()
2018-01-03 15:12:40 -02:00
last := 0
for _, loc := range locs {
buf.WriteString(str[last:loc[0]])
buf.WriteString(with)
last = loc[1]
}
buf.WriteString(str[last:])
str = buf.String()
2018-01-21 09:39:15 -02:00
case pe.Exp != nil:
2018-12-15 15:44:17 -02:00
arg, err := Literal(cfg, pe.Exp.Word)
if err != nil {
return "", err
}
2018-02-11 22:02:22 -02:00
switch op := pe.Exp.Op; op {
2019-09-26 19:04:09 -03:00
case syntax.AlternateUnsetOrNull:
2017-04-24 09:47:10 -03:00
if str == "" {
break
}
fallthrough
2019-09-26 19:04:09 -03:00
case syntax.AlternateUnset:
2018-12-15 15:44:17 -02:00
if vr.IsSet() {
2017-04-24 09:47:10 -03:00
str = arg
}
2019-09-26 19:04:09 -03:00
case syntax.DefaultUnset:
2018-12-15 15:44:17 -02:00
if vr.IsSet() {
2017-04-24 09:47:10 -03:00
break
}
fallthrough
2019-09-26 19:04:09 -03:00
case syntax.DefaultUnsetOrNull:
2017-04-24 09:47:10 -03:00
if str == "" {
str = arg
}
2019-09-26 19:04:09 -03:00
case syntax.ErrorUnset:
2018-12-15 15:44:17 -02:00
if vr.IsSet() {
2017-04-24 09:47:10 -03:00
break
}
fallthrough
2019-09-26 19:04:09 -03:00
case syntax.ErrorUnsetOrNull:
2017-04-24 09:47:10 -03:00
if str == "" {
2018-12-15 15:44:17 -02:00
return "", UnsetParameterError{
Node: pe,
Message: arg,
}
2017-04-24 09:47:10 -03:00
}
2019-09-26 19:04:09 -03:00
case syntax.AssignUnset:
2018-12-15 15:44:17 -02:00
if vr.IsSet() {
2017-04-24 09:47:10 -03:00
break
}
fallthrough
2019-09-26 19:04:09 -03:00
case syntax.AssignUnsetOrNull:
2017-04-24 09:47:10 -03:00
if str == "" {
2019-09-26 19:04:09 -03:00
if err := cfg.envSet(name, arg); err != nil {
return "", err
}
2017-04-24 09:47:10 -03:00
str = arg
}
2018-02-11 22:02:22 -02:00
case syntax.RemSmallPrefix, syntax.RemLargePrefix,
syntax.RemSmallSuffix, syntax.RemLargeSuffix:
2019-11-24 21:00:02 -03:00
suffix := op == syntax.RemSmallSuffix || op == syntax.RemLargeSuffix
small := op == syntax.RemSmallPrefix || op == syntax.RemSmallSuffix
2018-02-11 22:02:22 -02:00
for i, elem := range elems {
2019-11-24 21:00:02 -03:00
elems[i] = removePattern(elem, arg, suffix, small)
2018-02-11 22:02:22 -02:00
}
str = strings.Join(elems, " ")
case syntax.UpperFirst, syntax.UpperAll,
syntax.LowerFirst, syntax.LowerAll:
caseFunc := unicode.ToLower
if op == syntax.UpperFirst || op == syntax.UpperAll {
caseFunc = unicode.ToUpper
}
all := op == syntax.UpperAll || op == syntax.LowerAll
// empty string means '?'; nothing to do there
2019-11-24 21:00:02 -03:00
expr, err := pattern.Regexp(arg, 0)
2018-02-11 22:02:22 -02:00
if err != nil {
2018-12-15 15:44:17 -02:00
return str, nil
2018-02-11 22:02:22 -02:00
}
rx := regexp.MustCompile(expr)
for i, elem := range elems {
rs := []rune(elem)
for ri, r := range rs {
if rx.MatchString(string(r)) {
rs[ri] = caseFunc(r)
if !all {
break
}
}
}
elems[i] = string(rs)
}
str = strings.Join(elems, " ")
2017-04-30 19:50:22 -03:00
case syntax.OtherParamOps:
2017-04-24 09:47:10 -03:00
switch arg {
case "Q":
str = strconv.Quote(str)
case "E":
tail := str
var rns []rune
for tail != "" {
var rn rune
rn, _, tail, _ = strconv.UnquoteChar(tail, 0)
rns = append(rns, rn)
}
str = string(rns)
case "P", "A", "a":
2018-01-03 15:12:40 -02:00
panic(fmt.Sprintf("unhandled @%s param expansion", arg))
2017-04-24 09:47:10 -03:00
default:
2018-01-03 15:12:40 -02:00
panic(fmt.Sprintf("unexpected @%s param expansion", arg))
2017-04-24 09:47:10 -03:00
}
}
}
2018-12-15 15:44:17 -02:00
return str, nil
2017-04-24 09:47:10 -03:00
}
2019-11-24 21:00:02 -03:00
func removePattern(str, pat string, fromEnd, shortest bool) string {
var mode pattern.Mode
if shortest {
mode |= pattern.Shortest
}
expr, err := pattern.Regexp(pat, mode)
2018-01-03 15:12:40 -02:00
if err != nil {
return str
2017-04-24 09:47:10 -03:00
}
2018-01-03 15:12:40 -02:00
switch {
2019-11-24 21:00:02 -03:00
case fromEnd && shortest:
// use .* to get the right-most shortest match
2018-01-03 15:12:40 -02:00
expr = ".*(" + expr + ")$"
case fromEnd:
// simple suffix
expr = "(" + expr + ")$"
default:
// simple prefix
expr = "^(" + expr + ")"
}
2019-11-24 21:00:02 -03:00
// no need to check error as Translate returns one
2018-01-03 15:12:40 -02:00
rx := regexp.MustCompile(expr)
if loc := rx.FindStringSubmatchIndex(str); loc != nil {
// remove the original pattern (the submatch)
str = str[:loc[2]] + str[loc[3]:]
}
return str
2017-04-24 09:47:10 -03:00
}
2018-12-15 15:44:17 -02:00
func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) {
if idx == nil {
return vr.String(), nil
}
2019-09-26 19:04:09 -03:00
switch vr.Kind {
case String:
2018-12-15 15:44:17 -02:00
n, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
if n == 0 {
2019-09-26 19:04:09 -03:00
return vr.Str, nil
2018-12-15 15:44:17 -02:00
}
2019-09-26 19:04:09 -03:00
case Indexed:
2018-12-15 15:44:17 -02:00
switch nodeLit(idx) {
case "@":
2019-09-26 19:04:09 -03:00
return strings.Join(vr.List, " "), nil
2018-12-15 15:44:17 -02:00
case "*":
2019-09-26 19:04:09 -03:00
return cfg.ifsJoin(vr.List), nil
2018-12-15 15:44:17 -02:00
}
i, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
2019-09-26 19:04:09 -03:00
if len(vr.List) > 0 {
return vr.List[i], nil
2018-12-15 15:44:17 -02:00
}
2019-09-26 19:04:09 -03:00
case Associative:
2018-12-15 15:44:17 -02:00
switch lit := nodeLit(idx); lit {
case "@", "*":
2019-09-26 19:04:09 -03:00
strs := make([]string, 0, len(vr.Map))
for _, val := range vr.Map {
strs = append(strs, val)
2018-12-15 15:44:17 -02:00
}
2019-09-26 19:04:09 -03:00
sort.Strings(strs)
2018-12-15 15:44:17 -02:00
if lit == "*" {
return cfg.ifsJoin(strs), nil
}
return strings.Join(strs, " "), nil
}
val, err := Literal(cfg, idx.(*syntax.Word))
if err != nil {
return "", err
}
2019-09-26 19:04:09 -03:00
return vr.Map[val], nil
2018-12-15 15:44:17 -02:00
}
return "", nil
}
func (cfg *Config) namesByPrefix(prefix string) []string {
var names []string
cfg.Env.Each(func(name string, vr Variable) bool {
if strings.HasPrefix(name, prefix) {
names = append(names, name)
}
return true
})
return names
}