1
0
mirror of https://github.com/go-task/task.git synced 2025-01-18 04:59:01 +02:00
task/vendor/mvdan.cc/sh/v3/syntax/braces.go
2019-09-26 19:04:13 -03:00

179 lines
3.8 KiB
Go

// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "strconv"
var (
litLeftBrace = &Lit{Value: "{"}
litComma = &Lit{Value: ","}
litDots = &Lit{Value: ".."}
litRightBrace = &Lit{Value: "}"}
)
// SplitBraces parses brace expansions within a word's literal parts. If any
// valid brace expansions are found, they are replaced with BraceExp nodes, and
// the function returns true. Otherwise, the word is left untouched and the
// function returns false.
//
// For example, a literal word "foo{bar,baz}" will result in a word containing
// the literal "foo", and a brace expansion with the elements "bar" and "baz".
//
// It does not return an error; malformed brace expansions are simply skipped.
// For example, the literal word "a{b" is left unchanged.
func SplitBraces(word *Word) bool {
any := false
top := &Word{}
acc := top
var cur *BraceExp
open := []*BraceExp{}
pop := func() *BraceExp {
old := cur
open = open[:len(open)-1]
if len(open) == 0 {
cur = nil
acc = top
} else {
cur = open[len(open)-1]
acc = cur.Elems[len(cur.Elems)-1]
}
return old
}
addLit := func(lit *Lit) {
acc.Parts = append(acc.Parts, lit)
}
for _, wp := range word.Parts {
lit, ok := wp.(*Lit)
if !ok {
acc.Parts = append(acc.Parts, wp)
continue
}
last := 0
for j := 0; j < len(lit.Value); j++ {
addlitidx := func() {
if last == j {
return // empty lit
}
l2 := *lit
l2.Value = l2.Value[last:j]
addLit(&l2)
}
switch lit.Value[j] {
case '{':
addlitidx()
acc = &Word{}
cur = &BraceExp{Elems: []*Word{acc}}
open = append(open, cur)
case ',':
if cur == nil {
continue
}
addlitidx()
acc = &Word{}
cur.Elems = append(cur.Elems, acc)
case '.':
if cur == nil {
continue
}
if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
continue
}
addlitidx()
cur.Sequence = true
acc = &Word{}
cur.Elems = append(cur.Elems, acc)
j++
case '}':
if cur == nil {
continue
}
any = true
addlitidx()
br := pop()
if len(br.Elems) == 1 {
// return {x} to a non-brace
addLit(litLeftBrace)
acc.Parts = append(acc.Parts, br.Elems[0].Parts...)
addLit(litRightBrace)
break
}
if !br.Sequence {
acc.Parts = append(acc.Parts, br)
break
}
var chars [2]bool
broken := false
for i, elem := range br.Elems[:2] {
val := elem.Lit()
if _, err := strconv.Atoi(val); err == nil {
} else if len(val) == 1 &&
'a' <= val[0] && val[0] <= 'z' {
chars[i] = true
} else {
broken = true
}
}
if len(br.Elems) == 3 {
// increment must be a number
val := br.Elems[2].Lit()
if _, err := strconv.Atoi(val); err != nil {
broken = true
}
}
// are start and end both chars or
// non-chars?
if chars[0] != chars[1] {
broken = true
}
if !broken {
br.Chars = chars[0]
acc.Parts = append(acc.Parts, br)
break
}
// return broken {x..y[..incr]} to a non-brace
addLit(litLeftBrace)
for i, elem := range br.Elems {
if i > 0 {
addLit(litDots)
}
acc.Parts = append(acc.Parts, elem.Parts...)
}
addLit(litRightBrace)
default:
continue
}
last = j + 1
}
if last == 0 {
addLit(lit)
} else {
left := *lit
left.Value = left.Value[last:]
addLit(&left)
}
}
if !any {
return false
}
// open braces that were never closed fall back to non-braces
for acc != top {
br := pop()
addLit(litLeftBrace)
for i, elem := range br.Elems {
if i > 0 {
if br.Sequence {
addLit(litDots)
} else {
addLit(litComma)
}
}
acc.Parts = append(acc.Parts, elem.Parts...)
}
}
*word = *top
return true
}