Files
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

167 lines
3.9 KiB
Go

// Package varexpr provides some commonly ENV var parse functions.
//
// parse env value, allow expressions:
//
// ${VAR_NAME} Only var name
// ${VAR_NAME | default} With default value, if value is empty.
// ${VAR_NAME | ?error} With error on value is empty.
//
// Examples:
//
// only key - "${SHELL}"
// with default - "${NotExist | defValue}"
// multi key - "${GOPATH}/${APP_ENV | prod}/dir"
package varexpr
import (
"errors"
"os"
"regexp"
"strings"
)
const (
// SepChar separator char split var name and default value
SepChar = "|"
VarLeft = "${" // default var left format chars
VarRight = "}" // default var right format chars
mustPrefix = '?' // must prefix char
)
// ParseOptFn option func
type ParseOptFn func(o *ParseOpts)
// ParseOpts parse options for ParseValue
type ParseOpts struct {
// Getter Env value provider func.
Getter func(string) string
// ParseFn custom parse expr func. expr like "${SHELL}" "${NotExist|defValue}"
ParseFn func(string) (string, error)
// Regexp custom expression regex.
Regexp *regexp.Regexp
// var format chars for expression.
// default left="${", right="}"
VarLeft, VarRight string
}
func (opt *ParseOpts) useDefaultRegex() {
opt.Regexp = envRegex
opt.VarLeft = VarLeft
opt.VarRight = VarRight
}
// must add "?" - To ensure that there is no greedy match
var envRegex = regexp.MustCompile(`\${.+?}`)
var std = New()
// Parse parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
//
// see Parser.Parse
func Parse(val string) (string, error) {
return std.Parse(val)
}
// SafeParse parse ENV var value from input string, support default value.
//
// see Parser.Parse
func SafeParse(val string) string {
s, _ := std.Parse(val)
return s
}
// ParseWith parse ENV var value from input string, support default value.
func ParseWith(val string, optFns ...ParseOptFn) (string, error) {
return New(optFns...).Parse(val)
}
// Parser parse ENV var value from input string, support default value.
type Parser struct {
ParseOpts
}
// New create a new Parser
func New(optFns ...ParseOptFn) *Parser {
opts := &ParseOpts{Getter: os.Getenv}
opts.useDefaultRegex()
for _, fn := range optFns {
fn(opts)
}
return &Parser{ParseOpts: *opts}
}
// Parse parse ENV var value from input string, support default value.
//
// Format:
//
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
// ${VAR_NAME1}/path/${VAR_NAME2} Allow multi var name.
func (p *Parser) Parse(val string) (newVal string, err error) {
if p.Regexp == nil {
p.useDefaultRegex()
}
times := strings.Count(val, p.VarLeft)
if times == 0 {
return val, nil
}
// enhance: see https://github.com/gookit/goutil/issues/135
if times == 1 && strings.HasPrefix(val, p.VarLeft) && strings.HasSuffix(val, p.VarRight) {
return p.parseOne(val)
}
// parse expression
newVal = p.Regexp.ReplaceAllStringFunc(val, func(s string) string {
if err != nil {
return s
}
s, err = p.parseOne(s)
return s
})
return
}
// parse one node expression.
func (p *Parser) parseOne(eVar string) (val string, err error) {
if p.ParseFn != nil {
return p.ParseFn(eVar)
}
// like "${NotExist | defValue}". first remove "${" and "}", then split it
ss := strings.SplitN(eVar[2:len(eVar)-1], SepChar, 2)
var name, def string
// with default value.
if len(ss) == 2 {
name, def = strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
} else {
name = strings.TrimSpace(ss[0])
}
// get ENV value by name
val = p.Getter(name)
if val == "" && def != "" {
// check def is "?error"
if def[0] == mustPrefix {
msg := "value is required for var: " + name
if len(def) > 1 {
msg = def[1:]
}
err = errors.New(msg)
} else {
val = def
}
}
return
}