Added existing files

This commit is contained in:
Celestino Amoroso 2024-02-16 15:56:50 +01:00
parent af3d6a39f8
commit 70ba95098e
9 changed files with 715 additions and 0 deletions

137
dict-set-context.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

10
utils.go Normal file
View File

@ -0,0 +1,10 @@
// utils.go
package text
import (
"fmt"
)
func Var(name string) string {
return fmt.Sprintf("%c{%s}", varIntro, name)
}