Added existing files
This commit is contained in:
parent
af3d6a39f8
commit
70ba95098e
137
dict-set-context.go
Normal file
137
dict-set-context.go
Normal file
@ -0,0 +1,137 @@
|
||||
// dict-set-context.go
|
||||
package text
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sq "github.com/kballard/go-shellquote"
|
||||
"portale-stac.it/packages/utils"
|
||||
)
|
||||
|
||||
type DictSetFlag uint16
|
||||
|
||||
const (
|
||||
KeepVar = DictSetFlag(1 << iota)
|
||||
)
|
||||
|
||||
const (
|
||||
pipeValueKey = "."
|
||||
pipeNameKey = "_"
|
||||
)
|
||||
|
||||
type DictSetContext struct {
|
||||
BaseExpander
|
||||
flags DictSetFlag
|
||||
varStores []map[string]string
|
||||
localStore map[string]string
|
||||
}
|
||||
|
||||
func NewDictSetContext(flags DictSetFlag, varStores ...map[string]string) *DictSetContext {
|
||||
return NewDictSetContextV(flags, varStores)
|
||||
}
|
||||
|
||||
func NewDictSetContextV(flags DictSetFlag, varStores []map[string]string) *DictSetContext {
|
||||
ctx := &DictSetContext{
|
||||
flags: flags,
|
||||
varStores: varStores,
|
||||
localStore: make(map[string]string, 10),
|
||||
}
|
||||
(&ctx.BaseExpander).Init()
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *DictSetContext) GetVar(name string) (value string, exists bool) {
|
||||
if value, exists = self.localStore[name]; !exists {
|
||||
value, exists = findInStores(name, self.varStores)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *DictSetContext) SetVar(name string, value string) (repeatedValue string) {
|
||||
self.localStore[name] = value
|
||||
return value
|
||||
}
|
||||
|
||||
func findInStores(key string, stores []map[string]string) (value string, found bool) {
|
||||
for _, vars := range stores {
|
||||
if vars != nil {
|
||||
if value, found = vars[key]; found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *DictSetContext) Handle(spec string, scanFlags ScannerFlag) (value string, err error) {
|
||||
var found bool
|
||||
var rawValue string
|
||||
|
||||
if len(spec) > 0 {
|
||||
//parts := utils.SplitByRune(spec, '|', -1)
|
||||
parts := utils.ScopedSplitByRune(spec, '|', `'"{`, `'"}`, -1)
|
||||
name := parts[0]
|
||||
parts = parts[1:]
|
||||
if len(name) > 0 {
|
||||
if scanFlags&DirectValue != 0 {
|
||||
rawValue = name
|
||||
found = true
|
||||
} else {
|
||||
rawValue, found = findInStores(name, self.varStores)
|
||||
}
|
||||
}
|
||||
self.SetVar(pipeNameKey, name) // NOTE Maybe this line can be deleted
|
||||
|
||||
origVarFound := found
|
||||
for _, part := range parts {
|
||||
var expandedPart string
|
||||
self.varStores[0][pipeValueKey] = rawValue
|
||||
self.SetVar(pipeValueKey, rawValue)
|
||||
if expandedPart, err = Scan(self, part); err != nil {
|
||||
break
|
||||
}
|
||||
self.SetVar(pipeNameKey, name)
|
||||
if rawValue, err = self.evalContent(origVarFound, rawValue, expandedPart); err != nil {
|
||||
break
|
||||
}
|
||||
origVarFound = true
|
||||
}
|
||||
if err == nil {
|
||||
if len(rawValue) == 0 && (self.flags&KeepVar) != 0 {
|
||||
value = Var(name)
|
||||
} else {
|
||||
value = rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && len(value) == 0 && (scanFlags&ValueRequired) != 0 {
|
||||
err = fmt.Errorf("the variable specification %q requires a value", spec)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *DictSetContext) evalContent(varFound bool, rawValue string, alternateValue string) (value string, err error) {
|
||||
var args []string
|
||||
if args, err = sq.Split(alternateValue); err != nil {
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
value = rawValue
|
||||
} else {
|
||||
op := args[0]
|
||||
if strings.HasPrefix(op, "__") {
|
||||
var f ExpanderFunc
|
||||
if f, err = self.GetFunc(op); err == nil {
|
||||
value, err = f(self, rawValue, args[1:])
|
||||
}
|
||||
} else if varFound {
|
||||
value = rawValue
|
||||
} else {
|
||||
value = alternateValue
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
93
expander-base-funcs.go
Normal file
93
expander-base-funcs.go
Normal file
@ -0,0 +1,93 @@
|
||||
// expander-base-funcs.go
|
||||
package text
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ExFuncDefault(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
|
||||
if len(varValue) > 0 {
|
||||
result = varValue
|
||||
} else if len(args) > 0 {
|
||||
if len(args) == 1 {
|
||||
result = args[0]
|
||||
} else if len(args) > 1 {
|
||||
result = strings.Join(args[1:], args[0])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncUpper(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
result = strings.ToUpper(varValue)
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncLower(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
result = strings.ToLower(varValue)
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncJoin(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
if len(args) > 0 {
|
||||
result = strings.Join(args[1:], args[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncSet(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
if len(args) == 1 {
|
||||
result = ctx.SetVar(args[0], varValue)
|
||||
} else if len(args) > 1 {
|
||||
result = ctx.SetVar(args[0], args[1])
|
||||
} else {
|
||||
result = varValue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncGet(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
var found bool
|
||||
if len(args) == 0 {
|
||||
result, found = ctx.GetVar(varValue)
|
||||
} else {
|
||||
result, found = ctx.GetVar(args[0])
|
||||
if !found && len(args) > 1 {
|
||||
result = args[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ExFuncTrimSuffix(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
if len(args) == 0 {
|
||||
if pos := strings.LastIndexByte(varValue, '.'); pos >= 0 {
|
||||
result = varValue[:pos]
|
||||
}
|
||||
} else {
|
||||
found := false
|
||||
for _, arg := range args {
|
||||
if remainder := strings.TrimSuffix(varValue, arg); remainder != varValue {
|
||||
result = remainder
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result = varValue
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func AddBaseFuncs(ctx ExpanderContext) ExpanderContext {
|
||||
ctx.AddFunc("__default", ExFuncDefault, 0, -1, "Returns the flow value if not empty, else all args joined; first arg is used as separator.")
|
||||
ctx.AddFunc("__get", ExFuncGet, 0, 2, "Returns the value of the specified variable (arg1). With no arguments it returns the value of the variable having the flow value as name (same as __get ${.}). Arg2 is the default value. ")
|
||||
ctx.AddFunc("__set", ExFuncSet, 1, 2, "Sets the value (arg2 or flow) of the specified variables (arg1).")
|
||||
ctx.AddFunc("__join", ExFuncJoin, 1, -1, "Joins its args starting from the second. The first one (arg1) is the separator")
|
||||
ctx.AddFunc("__lower", ExFuncLower, 0, 0, "Changes the flow value to lower case.")
|
||||
ctx.AddFunc("__upper", ExFuncUpper, 0, 0, "Changes the flow value to upper case.")
|
||||
ctx.AddFunc("__trim-suffix", ExFuncTrimSuffix, 1, 1, "Removes the specified suffix (arg1) from the flow.")
|
||||
return ctx
|
||||
}
|
142
expander-context.go
Normal file
142
expander-context.go
Normal file
@ -0,0 +1,142 @@
|
||||
// expander-context.go
|
||||
package text
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"portale-stac.it/packages/golang"
|
||||
)
|
||||
|
||||
const varIntro = '$'
|
||||
|
||||
type ExpanderFunc func(ctx ExpanderContext, varValue string, args []string) (result string, err error)
|
||||
|
||||
type ExpanderFuncBlock struct {
|
||||
name string
|
||||
expFunc ExpanderFunc
|
||||
minArgs int
|
||||
maxArgs int
|
||||
description string
|
||||
}
|
||||
|
||||
func (self *ExpanderFuncBlock) CheckNumArgs(n int) (ok bool) {
|
||||
ok = (self.minArgs < 0 || n >= self.minArgs) && (self.maxArgs < 0 || n <= self.maxArgs)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *ExpanderFuncBlock) synopsys(sb *strings.Builder) {
|
||||
if self.minArgs < 0 && self.maxArgs < 0 {
|
||||
sb.WriteString("[...]")
|
||||
} else {
|
||||
var i int
|
||||
for i = 0; i < self.minArgs; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString("arg")
|
||||
sb.WriteString(strconv.Itoa(i + 1))
|
||||
}
|
||||
if self.maxArgs < 0 {
|
||||
sb.WriteString("...")
|
||||
} else if i < self.maxArgs {
|
||||
for j := i; j < self.maxArgs; j++ {
|
||||
if j > 0 {
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString("[arg")
|
||||
sb.WriteString(strconv.Itoa(j + 1))
|
||||
}
|
||||
for j := i; j < self.maxArgs; j++ {
|
||||
sb.WriteByte(']')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ExpanderFuncBlock) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(self.name)
|
||||
sb.WriteByte(' ')
|
||||
self.synopsys(&sb)
|
||||
if len(self.description) > 0 {
|
||||
sb.WriteString(" -- ")
|
||||
if len(self.description) > 80 {
|
||||
sb.WriteString(self.description[0:80])
|
||||
sb.WriteString("...")
|
||||
} else {
|
||||
sb.WriteString(self.description)
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type ExpanderContext interface {
|
||||
VariableHandler
|
||||
GetFunc(name string) (f ExpanderFunc, err error)
|
||||
AddFunc(name string, f ExpanderFunc, minArgs, maxArgs int, description string)
|
||||
GetVar(name string) (value string, exists bool)
|
||||
SetVar(name string, value string) (repeatedValue string)
|
||||
}
|
||||
|
||||
type BaseExpander struct {
|
||||
functions map[string]*ExpanderFuncBlock
|
||||
}
|
||||
|
||||
func (self *BaseExpander) GetVar(name string) (value string, exists bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *BaseExpander) SetVar(name string, value string) (repeatedValue string) {
|
||||
return value
|
||||
}
|
||||
|
||||
func NewExpander() *BaseExpander {
|
||||
return &BaseExpander{
|
||||
functions: make(map[string]*ExpanderFuncBlock),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseExpander) Init() {
|
||||
if self.functions == nil {
|
||||
self.functions = make(map[string]*ExpanderFuncBlock)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseExpander) GetFunc(name string) (f ExpanderFunc, err error) {
|
||||
var ok bool
|
||||
var info *ExpanderFuncBlock
|
||||
if info, ok = self.functions[name]; !ok {
|
||||
err = fmt.Errorf("unknown function %q", name)
|
||||
} else {
|
||||
f = info.expFunc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *BaseExpander) AddFunc(name string, f ExpanderFunc, minArgs, maxArgs int, description string) {
|
||||
self.functions[name] = &ExpanderFuncBlock{
|
||||
name: name,
|
||||
expFunc: f,
|
||||
minArgs: minArgs,
|
||||
maxArgs: maxArgs,
|
||||
description: description,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseExpander) Handle(varSpec string, flags ScannerFlag) (value string, err error) {
|
||||
err = errors.New("maybe you did somwthing wrong")
|
||||
return
|
||||
}
|
||||
|
||||
func (self *BaseExpander) IterateFunc(op func(funcInfo *ExpanderFuncBlock) (err error)) (err error) {
|
||||
if op != nil {
|
||||
err = golang.IterateSortedMap(self.functions, func(key string, info *ExpanderFuncBlock) error {
|
||||
return op(info)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
188
expander-time-funcs.go
Normal file
188
expander-time-funcs.go
Normal file
@ -0,0 +1,188 @@
|
||||
// expander-time-funcs.go
|
||||
package text
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const func_time_fmt = "__time-fmt"
|
||||
|
||||
/*
|
||||
If format is empty, the default template will be used.
|
||||
*/
|
||||
func TimeFmt(when time.Time, format string) (result string, err error) {
|
||||
var sb strings.Builder
|
||||
var sep byte = 0
|
||||
var filler byte
|
||||
var field int
|
||||
|
||||
if len(format) == 0 {
|
||||
format = "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
perc := false
|
||||
i := 0
|
||||
for i < len(format) && err == nil {
|
||||
ch := format[i]
|
||||
if ch == '%' {
|
||||
if perc {
|
||||
sb.WriteByte(ch)
|
||||
perc = false
|
||||
} else {
|
||||
perc = true
|
||||
}
|
||||
} else if perc {
|
||||
perc = false
|
||||
switch ch {
|
||||
case '.', '-', '/', '_', ':':
|
||||
if field > 0 {
|
||||
err = fmt.Errorf("invalid time specifier format at %d", i)
|
||||
} else {
|
||||
sep = ch
|
||||
j := i + 1
|
||||
if j < len(format) && (format[j] == '0' || format[j] == ' ') {
|
||||
filler = format[j]
|
||||
j++
|
||||
start := j
|
||||
for j < len(format) && format[j] >= '0' && format[j] <= '9' {
|
||||
j++
|
||||
}
|
||||
field, err = strconv.Atoi(format[start:j])
|
||||
i = j - 1
|
||||
}
|
||||
perc = true
|
||||
}
|
||||
case '0', ' ':
|
||||
filler = ch
|
||||
j := i + 1
|
||||
for j < len(format) && format[j] >= '0' && format[j] <= '9' {
|
||||
j++
|
||||
}
|
||||
field, err = strconv.Atoi(format[i+1 : j])
|
||||
i = j - 1
|
||||
perc = true
|
||||
default:
|
||||
switch ch {
|
||||
case 'D':
|
||||
if filler != 0 {
|
||||
templ := fmt.Sprintf("%%d%c%%%c%dd%c%%%c%dd", sep, filler, field, sep, filler, field)
|
||||
_, err = sb.WriteString(fmt.Sprintf(templ, when.Year(), when.Month(), when.Day()))
|
||||
} else {
|
||||
_, err = sb.WriteString(fmt.Sprintf("%d%c%*d%c%*d", when.Year(), sep, field, when.Month(), sep, field, when.Day()))
|
||||
}
|
||||
case 'T':
|
||||
if filler != 0 {
|
||||
templ := fmt.Sprintf("%%%c%dd%c%%%c%dd%c%%%c%dd", filler, field, sep, filler, field, sep, filler, field)
|
||||
_, err = sb.WriteString(fmt.Sprintf(templ, when.Hour(), when.Minute(), when.Second()))
|
||||
} else {
|
||||
_, err = sb.WriteString(fmt.Sprintf("%d%c%*d%c%*d", when.Hour(), sep, field, when.Minute(), sep, field, when.Second()))
|
||||
}
|
||||
case 'Y':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%d", when.Year()))
|
||||
case 'm':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Month()))
|
||||
case 'd':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Day()))
|
||||
case 'H':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Hour()))
|
||||
case 'M':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Minute()))
|
||||
case 'S':
|
||||
_, err = sb.WriteString(fmt.Sprintf("%02d", when.Second()))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported '%c' time specifier at %q/%d", ch, format, i+1)
|
||||
}
|
||||
filler = 0
|
||||
field = 0
|
||||
sep = 0
|
||||
}
|
||||
} else {
|
||||
err = sb.WriteByte(ch)
|
||||
}
|
||||
i++
|
||||
}
|
||||
if err == nil {
|
||||
result = sb.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseDelta(source string) (delta time.Duration) {
|
||||
// TODO implementare
|
||||
var err error
|
||||
if delta, err = time.ParseDuration(strings.TrimSpace(source)); err != nil {
|
||||
delta = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Format a time value using a template (see TimeFmt()).
|
||||
// __time-fmt <time> <template>
|
||||
// <time> can be:
|
||||
//
|
||||
// now
|
||||
// yesterday
|
||||
// @<timestamp>, e.g. @1707889716
|
||||
// @., where '.' will be replaced by hte pipeline value
|
||||
func ExFuncTimeFmt(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||
var format string
|
||||
var whenAsString string
|
||||
var when time.Time
|
||||
|
||||
if len(args) == 0 {
|
||||
whenAsString = "now"
|
||||
} else {
|
||||
whenAsString = args[0]
|
||||
if len(args) > 1 {
|
||||
format = args[1]
|
||||
}
|
||||
}
|
||||
|
||||
if len(whenAsString) == 0 {
|
||||
err = errors.New("time value is empty")
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(whenAsString, "now"):
|
||||
delta := parseDelta(whenAsString[3:])
|
||||
when = time.Now().Add(delta)
|
||||
case strings.HasPrefix(whenAsString, "yesterday"):
|
||||
delta := parseDelta(whenAsString[9:])
|
||||
when = time.Now().Add(-24 * time.Hour).Add(delta)
|
||||
case strings.HasPrefix(whenAsString, "@."):
|
||||
var ts int64
|
||||
if ts, err = strconv.ParseInt(varValue, 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
delta := parseDelta(whenAsString[2:])
|
||||
when = time.UnixMilli(ts * 1000).Add(delta)
|
||||
case strings.HasPrefix(whenAsString, "@"):
|
||||
var ts int64
|
||||
var i int
|
||||
for i = 1; i < len(whenAsString) && whenAsString[i] >= '0' && whenAsString[i] <= '9'; i++ {
|
||||
}
|
||||
|
||||
if ts, err = strconv.ParseInt(whenAsString[1:i], 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
delta := parseDelta(whenAsString[i:])
|
||||
when = time.UnixMilli(ts * 1000).Add(delta)
|
||||
default:
|
||||
err = fmt.Errorf("time specifier '%s' not supported", whenAsString)
|
||||
return
|
||||
}
|
||||
|
||||
result, err = TimeFmt(when, format)
|
||||
return
|
||||
}
|
||||
|
||||
func AddTimeFuncs(ctx ExpanderContext) ExpanderContext {
|
||||
ctx.AddFunc(func_time_fmt, ExFuncTimeFmt, 1, 2, "Formats the specified time value (arg1) according with the format specifier (arg2 or default).")
|
||||
return ctx
|
||||
}
|
||||
|
8
expander.go
Normal file
8
expander.go
Normal file
@ -0,0 +1,8 @@
|
||||
// expander.go
|
||||
package text
|
||||
|
||||
func ExpandStringTemplate(ctx ExpanderContext, template string) (result string, err error) {
|
||||
AddBaseFuncs(ctx)
|
||||
result, err = Scan(ctx, template)
|
||||
return
|
||||
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module portale-stac.it/packages/text
|
||||
|
||||
go 1.21.6
|
||||
|
||||
require github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
|
||||
require (
|
||||
portale-stac.it/packages/golang v0.0.0
|
||||
portale-stac.it/packages/utils v0.0.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.14.0 // indirect
|
||||
|
||||
replace (
|
||||
portale-stac.it/packages/golang => ../golang
|
||||
portale-stac.it/packages/utils => ../utils
|
||||
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
116
scanner.go
Normal file
116
scanner.go
Normal file
@ -0,0 +1,116 @@
|
||||
// scanner.go
|
||||
package text
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ScannerFlag uint16
|
||||
|
||||
const (
|
||||
ValueRequired = ScannerFlag(1 << iota)
|
||||
DirectValue
|
||||
EncryptValue
|
||||
)
|
||||
|
||||
// Flags symbols in the abova iota order
|
||||
const flagSymbols = "!=*"
|
||||
|
||||
type VariableHandler interface {
|
||||
Handle(varSpec string, flags ScannerFlag) (value string, err error)
|
||||
}
|
||||
|
||||
func Scan(handler VariableHandler, source string) (result string, err error) {
|
||||
var spec, value string
|
||||
var flags ScannerFlag
|
||||
var backSlash bool
|
||||
var sb strings.Builder
|
||||
offset := 0
|
||||
for offset < len(source) {
|
||||
ch := source[offset]
|
||||
offset++
|
||||
if backSlash {
|
||||
switch ch {
|
||||
case '\\':
|
||||
sb.WriteByte('\\')
|
||||
case varIntro:
|
||||
sb.WriteByte(varIntro)
|
||||
default:
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
backSlash = false
|
||||
} else if ch == '\\' {
|
||||
backSlash = true
|
||||
} else if ch == varIntro {
|
||||
if spec, flags, offset, err = getVarSpec(source, offset); err != nil {
|
||||
//err = fmt.Errorf("Source:\n%s", source)
|
||||
return
|
||||
}
|
||||
if handler != nil {
|
||||
if value, err = handler.Handle(spec, flags); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
sb.WriteString(value)
|
||||
} else {
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
result = sb.String()
|
||||
return
|
||||
}
|
||||
|
||||
func getVarSpec(source string, startOffset int) (spec string, flags ScannerFlag, offset int, err error) {
|
||||
var ch byte
|
||||
var lastOpenBrace int = -1
|
||||
|
||||
braceCount := 0
|
||||
if startOffset < len(source) && source[startOffset] == '{' {
|
||||
lastOpenBrace = startOffset
|
||||
braceCount++
|
||||
startOffset++
|
||||
}
|
||||
count := 0
|
||||
beginning := true
|
||||
for offset = startOffset; offset < len(source); offset++ {
|
||||
ch = source[offset]
|
||||
if ch == '{' && braceCount > 0 {
|
||||
lastOpenBrace = offset
|
||||
count++
|
||||
braceCount++
|
||||
} else if ch == '}' {
|
||||
braceCount--
|
||||
if braceCount == 0 {
|
||||
break
|
||||
}
|
||||
count++
|
||||
} else if braceCount == 0 && strings.IndexByte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", ch) < 0 {
|
||||
break
|
||||
} else if braceCount == 1 && beginning {
|
||||
if flagIndex := strings.IndexByte(flagSymbols, ch); flagIndex >= 0 {
|
||||
startOffset++
|
||||
flags |= ScannerFlag(1 << flagIndex)
|
||||
} else {
|
||||
count++
|
||||
beginning = false
|
||||
}
|
||||
} else {
|
||||
count++
|
||||
beginning = false
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if braceCount > 0 {
|
||||
err = fmt.Errorf("unbalanced open braces at offset %d", lastOpenBrace)
|
||||
} else {
|
||||
if offset < len(source) && ch == '}' {
|
||||
offset++
|
||||
}
|
||||
//fmt.Println("count:", count)
|
||||
spec = source[startOffset : startOffset+count]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user