157 lines
3.1 KiB
Go
157 lines
3.1 KiB
Go
package envsubst
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"github.com/drone/envsubst/parse"
|
|
)
|
|
|
|
// state represents the state of template execution. It is not part of the
|
|
// template so that multiple executions can run in parallel.
|
|
type state struct {
|
|
template *Template
|
|
writer io.Writer
|
|
node parse.Node // current node
|
|
|
|
// maps variable names to values
|
|
mapper func(string) string
|
|
}
|
|
|
|
// Template is the representation of a parsed shell format string.
|
|
type Template struct {
|
|
tree *parse.Tree
|
|
}
|
|
|
|
// Parse creates a new shell format template and parses the template
|
|
// definition from string s.
|
|
func Parse(s string) (t *Template, err error) {
|
|
t = new(Template)
|
|
t.tree, err = parse.Parse(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// ParseFile creates a new shell format template and parses the template
|
|
// definition from the named file.
|
|
func ParseFile(path string) (*Template, error) {
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Parse(string(b))
|
|
}
|
|
|
|
// Execute applies a parsed template to the specified data mapping.
|
|
func (t *Template) Execute(mapping func(string) string) (str string, err error) {
|
|
b := new(bytes.Buffer)
|
|
s := new(state)
|
|
s.node = t.tree.Root
|
|
s.mapper = mapping
|
|
s.writer = b
|
|
err = t.eval(s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
func (t *Template) eval(s *state) (err error) {
|
|
switch node := s.node.(type) {
|
|
case *parse.TextNode:
|
|
err = t.evalText(s, node)
|
|
case *parse.FuncNode:
|
|
err = t.evalFunc(s, node)
|
|
case *parse.ListNode:
|
|
err = t.evalList(s, node)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (t *Template) evalText(s *state, node *parse.TextNode) error {
|
|
_, err := io.WriteString(s.writer, node.Value)
|
|
return err
|
|
}
|
|
|
|
func (t *Template) evalList(s *state, node *parse.ListNode) (err error) {
|
|
for _, n := range node.Nodes {
|
|
s.node = n
|
|
err = t.eval(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *Template) evalFunc(s *state, node *parse.FuncNode) error {
|
|
var w = s.writer
|
|
var buf bytes.Buffer
|
|
var args []string
|
|
for _, n := range node.Args {
|
|
buf.Reset()
|
|
s.writer = &buf
|
|
s.node = n
|
|
err := t.eval(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
args = append(args, buf.String())
|
|
}
|
|
|
|
// restore the origin writer
|
|
s.writer = w
|
|
s.node = node
|
|
|
|
v := s.mapper(node.Param)
|
|
|
|
fn := lookupFunc(node.Name, len(args))
|
|
|
|
_, err := io.WriteString(s.writer, fn(v, args...))
|
|
return err
|
|
}
|
|
|
|
// lookupFunc returns the parameters substitution function by name. If the
|
|
// named function does not exists, a default function is returned.
|
|
func lookupFunc(name string, args int) substituteFunc {
|
|
switch name {
|
|
case ",":
|
|
return toLowerFirst
|
|
case ",,":
|
|
return toLower
|
|
case "^":
|
|
return toUpperFirst
|
|
case "^^":
|
|
return toUpper
|
|
case "#":
|
|
if args == 0 {
|
|
return toLen
|
|
}
|
|
return trimShortestPrefix
|
|
case "##":
|
|
return trimLongestPrefix
|
|
case "%":
|
|
return trimShortestSuffix
|
|
case "%%":
|
|
return trimLongestSuffix
|
|
case ":":
|
|
return toSubstr
|
|
case "/#":
|
|
return replacePrefix
|
|
case "/%":
|
|
return replaceSuffix
|
|
case "/":
|
|
return replaceFirst
|
|
case "//":
|
|
return replaceAll
|
|
case "=", ":=", ":-":
|
|
return toDefault
|
|
case ":?", ":+", "-", "+":
|
|
return toDefault
|
|
default:
|
|
return toDefault
|
|
}
|
|
}
|