1package interpolate
2
3import (
4 "bytes"
5 "fmt"
6)
7
8// Interpolate takes a set of environment and interpolates it into the provided string using shell
9// script expansions
10func Interpolate(env Env, str string) (string, error) {
11 if env == nil {
12 env = NewSliceEnv(nil)
13 }
14 expr, err := NewParser(str).Parse()
15 if err != nil {
16 return "", err
17 }
18 return expr.Expand(env)
19}
20
21// Indentifiers parses the identifiers from any expansions in the provided string
22func Identifiers(str string) ([]string, error) {
23 expr, err := NewParser(str).Parse()
24 if err != nil {
25 return nil, err
26 }
27 return expr.Identifiers(), nil
28}
29
30// An expansion is something that takes in ENV and returns a string or an error
31type Expansion interface {
32 Expand(env Env) (string, error)
33 Identifiers() []string
34}
35
36// VariableExpansion represents either $VAR or ${VAR}, our simplest expansion
37type VariableExpansion struct {
38 Identifier string
39}
40
41func (e VariableExpansion) Identifiers() []string {
42 return []string{e.Identifier}
43}
44
45func (e VariableExpansion) Expand(env Env) (string, error) {
46 val, _ := env.Get(e.Identifier)
47 return val, nil
48}
49
50// EmptyValueExpansion returns either the value of an env, or a default value if it's unset or null
51type EmptyValueExpansion struct {
52 Identifier string
53 Content Expression
54}
55
56func (e EmptyValueExpansion) Identifiers() []string {
57 return append([]string{e.Identifier}, e.Content.Identifiers()...)
58}
59
60func (e EmptyValueExpansion) Expand(env Env) (string, error) {
61 val, _ := env.Get(e.Identifier)
62 if val == "" {
63 return e.Content.Expand(env)
64 }
65 return val, nil
66}
67
68// UnsetValueExpansion returns either the value of an env, or a default value if it's unset
69type UnsetValueExpansion struct {
70 Identifier string
71 Content Expression
72}
73
74func (e UnsetValueExpansion) Identifiers() []string {
75 return []string{e.Identifier}
76}
77
78func (e UnsetValueExpansion) Expand(env Env) (string, error) {
79 val, ok := env.Get(e.Identifier)
80 if !ok {
81 return e.Content.Expand(env)
82 }
83 return val, nil
84}
85
86// SubstringExpansion returns a substring (or slice) of the env
87type SubstringExpansion struct {
88 Identifier string
89 Offset int
90 Length int
91 HasLength bool
92}
93
94func (e SubstringExpansion) Identifiers() []string {
95 return []string{e.Identifier}
96}
97
98func (e SubstringExpansion) Expand(env Env) (string, error) {
99 val, _ := env.Get(e.Identifier)
100
101 from := e.Offset
102
103 // Negative offsets = from end
104 if from < 0 {
105 from += len(val)
106 }
107
108 // Still negative = too far from end? Truncate to start.
109 if from < 0 {
110 from = 0
111 }
112
113 // Beyond end? Truncate to end.
114 if from > len(val) {
115 from = len(val)
116 }
117
118 if !e.HasLength {
119 return val[from:], nil
120 }
121
122 to := e.Length
123
124 if to >= 0 {
125 // Positive length = from offset
126 to += from
127 } else {
128 // Negative length = from end
129 to += len(val)
130
131 // Too far? Truncate to offset.
132 if to < from {
133 to = from
134 }
135 }
136
137 // Beyond end? Truncate to end.
138 if to > len(val) {
139 to = len(val)
140 }
141
142 return val[from:to], nil
143}
144
145// RequiredExpansion returns an env value, or an error if it is unset
146type RequiredExpansion struct {
147 Identifier string
148 Message Expression
149}
150
151func (e RequiredExpansion) Identifiers() []string {
152 return []string{e.Identifier}
153}
154
155func (e RequiredExpansion) Expand(env Env) (string, error) {
156 val, ok := env.Get(e.Identifier)
157 if !ok {
158 msg, err := e.Message.Expand(env)
159 if err != nil {
160 return "", err
161 }
162 if msg == "" {
163 msg = "not set"
164 }
165 return "", fmt.Errorf("$%s: %s", e.Identifier, msg)
166 }
167 return val, nil
168}
169
170// Expression is a collection of either Text or Expansions
171type Expression []ExpressionItem
172
173func (e Expression) Identifiers() []string {
174 identifiers := []string{}
175 for _, item := range e {
176 if item.Expansion != nil {
177 identifiers = append(identifiers, item.Expansion.Identifiers()...)
178 }
179 }
180 return identifiers
181}
182
183func (e Expression) Expand(env Env) (string, error) {
184 buf := &bytes.Buffer{}
185
186 for _, item := range e {
187 if item.Expansion != nil {
188 result, err := item.Expansion.Expand(env)
189 if err != nil {
190 return "", err
191 }
192 _, _ = buf.WriteString(result)
193 } else {
194 _, _ = buf.WriteString(item.Text)
195 }
196 }
197
198 return buf.String(), nil
199}
200
201// ExpressionItem models either an Expansion or Text. Either/Or, never both.
202type ExpressionItem struct {
203 Text string
204 // -- or --
205 Expansion Expansion
206}
207
208func (i ExpressionItem) String() string {
209 if i.Expansion != nil {
210 return fmt.Sprintf("%#v", i.Expansion)
211 }
212 return fmt.Sprintf("%q", i.Text)
213}