interpolate.go

  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}