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}