braces.go

  1// Copyright (c) 2018, Daniel MartΓ­ <mvdan@mvdan.cc>
  2// See LICENSE for licensing information
  3
  4package expand
  5
  6import (
  7	"strconv"
  8	"strings"
  9
 10	"mvdan.cc/sh/v3/syntax"
 11)
 12
 13// Braces performs brace expansion on a word, given that it contains any
 14// [syntax.BraceExp] parts. For example, the word with a brace expansion
 15// "foo{bar,baz}" will return two literal words, "foobar" and "foobaz".
 16//
 17// Note that the resulting words may share word parts.
 18func Braces(word *syntax.Word) []*syntax.Word {
 19	var all []*syntax.Word
 20	var left []syntax.WordPart
 21	for i, wp := range word.Parts {
 22		br, ok := wp.(*syntax.BraceExp)
 23		if !ok {
 24			left = append(left, wp)
 25			continue
 26		}
 27		if br.Sequence {
 28			chars := false
 29
 30			fromLit := br.Elems[0].Lit()
 31			toLit := br.Elems[1].Lit()
 32			zeros := max(extraLeadingZeros(fromLit), extraLeadingZeros(toLit))
 33
 34			from, err1 := strconv.Atoi(fromLit)
 35			to, err2 := strconv.Atoi(toLit)
 36			if err1 != nil || err2 != nil {
 37				chars = true
 38				from = int(br.Elems[0].Lit()[0])
 39				to = int(br.Elems[1].Lit()[0])
 40			}
 41			upward := from <= to
 42			incr := 1
 43			if !upward {
 44				incr = -1
 45			}
 46			if len(br.Elems) > 2 {
 47				n, _ := strconv.Atoi(br.Elems[2].Lit())
 48				if n != 0 && n > 0 == upward {
 49					incr = n
 50				}
 51			}
 52			n := from
 53			for {
 54				if upward && n > to {
 55					break
 56				}
 57				if !upward && n < to {
 58					break
 59				}
 60				next := *word
 61				next.Parts = next.Parts[i+1:]
 62				lit := &syntax.Lit{}
 63				if chars {
 64					lit.Value = string(rune(n))
 65				} else {
 66					lit.Value = strings.Repeat("0", zeros) + strconv.Itoa(n)
 67				}
 68				next.Parts = append([]syntax.WordPart{lit}, next.Parts...)
 69				exp := Braces(&next)
 70				for _, w := range exp {
 71					w.Parts = append(left, w.Parts...)
 72				}
 73				all = append(all, exp...)
 74				n += incr
 75			}
 76			return all
 77		}
 78		for _, elem := range br.Elems {
 79			next := *word
 80			next.Parts = next.Parts[i+1:]
 81			next.Parts = append(elem.Parts, next.Parts...)
 82			exp := Braces(&next)
 83			for _, w := range exp {
 84				w.Parts = append(left, w.Parts...)
 85			}
 86			all = append(all, exp...)
 87		}
 88		return all
 89	}
 90	return []*syntax.Word{{Parts: left}}
 91}
 92
 93func extraLeadingZeros(s string) int {
 94	for i, r := range s {
 95		if r != '0' {
 96			return i
 97		}
 98	}
 99	return 0 // "0" has no extra leading zeros
100}