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