Compare commits
11 Commits
db726467c6
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 549578cb48 | |||
| ab722abe2d | |||
| b1fdc7d511 | |||
| 857155274e | |||
| f0e600dd8b | |||
| c480e81c0a | |||
| 55821af498 | |||
| 01ec36c7b6 | |||
| ccdd5c5cdb | |||
| d297530309 | |||
| a78c42a161 |
@@ -0,0 +1,29 @@
|
|||||||
|
Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Celestino Amoroso, nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
+39
-23
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// dict-set-context.go
|
// dict-set-context.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
@@ -41,30 +44,43 @@ func NewDictSetContextV(flags DictSetFlag, varStores []map[string]string) *DictS
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DictSetContext) GetVar(name string) (value string, exists bool) {
|
func (ctx *DictSetContext) GetVar(name string) (value string, exists bool) {
|
||||||
if value, exists = self.localStore[name]; !exists {
|
if value, exists = ctx.localStore[name]; !exists {
|
||||||
value, exists = findInStores(name, self.varStores)
|
value, exists = ctx.findInStores(name)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DictSetContext) SetVar(name string, value string) (repeatedValue string) {
|
func (ctx *DictSetContext) SetVar(name string, value string) (repeatedValue string) {
|
||||||
self.localStore[name] = value
|
ctx.localStore[name] = value
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func findInStores(key string, stores []map[string]string) (value string, found bool) {
|
func (ctx *DictSetContext) findInStores(key string) (value string, found bool) {
|
||||||
for _, vars := range stores {
|
if value, found = ctx.localStore[key]; !found {
|
||||||
if vars != nil {
|
for _, vars := range ctx.varStores {
|
||||||
if value, found = vars[key]; found {
|
if vars != nil {
|
||||||
break
|
if value, found = vars[key]; found {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DictSetContext) Handle(spec string, scanFlags ScannerFlag) (value string, err error) {
|
// 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 (ctx *DictSetContext) Handle(spec string, scanFlags ScannerFlag) (value string, err error) {
|
||||||
var found bool
|
var found bool
|
||||||
var rawValue string
|
var rawValue string
|
||||||
|
|
||||||
@@ -78,30 +94,30 @@ func (self *DictSetContext) Handle(spec string, scanFlags ScannerFlag) (value st
|
|||||||
rawValue = name
|
rawValue = name
|
||||||
found = true
|
found = true
|
||||||
} else {
|
} else {
|
||||||
rawValue, found = findInStores(name, self.varStores)
|
rawValue, found = ctx.findInStores(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.SetVar(pipeNameKey, name) // NOTE Maybe this line can be deleted
|
ctx.SetVar(pipeNameKey, name) // NOTE Maybe this line can be deleted
|
||||||
|
|
||||||
origVarFound := found
|
origVarFound := found
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
var expandedPart string
|
var expandedPart string
|
||||||
self.varStores[0][pipeValueKey] = rawValue
|
// ctx.varStores[0][pipeValueKey] = rawValue
|
||||||
self.SetVar(pipeValueKey, rawValue)
|
ctx.SetVar(pipeValueKey, rawValue)
|
||||||
if expandedPart, err = Scan(self, part); err != nil {
|
if expandedPart, err = Scan(ctx, part); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
self.SetVar(pipeNameKey, name)
|
ctx.SetVar(pipeNameKey, name)
|
||||||
if rawValue, err = self.evalContent(origVarFound, rawValue, expandedPart); err != nil {
|
if rawValue, err = ctx.evalContent(origVarFound, rawValue, expandedPart); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
origVarFound = true
|
origVarFound = true
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(rawValue) == 0 && (self.flags&KeepVar) != 0 {
|
if len(rawValue) == 0 && (ctx.flags&KeepVar) != 0 {
|
||||||
value = Var(name)
|
value = Var(name)
|
||||||
} else {
|
} else {
|
||||||
value, err = Scan(self, rawValue)
|
value, err = Scan(ctx, rawValue)
|
||||||
//value = rawValue
|
//value = rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +129,7 @@ func (self *DictSetContext) Handle(spec string, scanFlags ScannerFlag) (value st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *DictSetContext) evalContent(varFound bool, rawValue string, alternateValue string) (value string, err error) {
|
func (ctx *DictSetContext) evalContent(varFound bool, rawValue string, alternateValue string) (value string, err error) {
|
||||||
var args []string
|
var args []string
|
||||||
if args, err = sq.Split(alternateValue); err != nil {
|
if args, err = sq.Split(alternateValue); err != nil {
|
||||||
return
|
return
|
||||||
@@ -124,8 +140,8 @@ func (self *DictSetContext) evalContent(varFound bool, rawValue string, alternat
|
|||||||
op := args[0]
|
op := args[0]
|
||||||
if strings.HasPrefix(op, "__") {
|
if strings.HasPrefix(op, "__") {
|
||||||
var f ExpanderFunc
|
var f ExpanderFunc
|
||||||
if f, err = self.GetFunc(op); err == nil {
|
if f, err = ctx.GetFunc(op); err == nil {
|
||||||
value, err = f(self, rawValue, args[1:])
|
value, err = f(ctx, rawValue, args[1:])
|
||||||
}
|
}
|
||||||
} else if varFound {
|
} else if varFound {
|
||||||
value = rawValue
|
value = rawValue
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// dict-set-context_test.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDictSetContext(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
ctx ExpanderContext
|
||||||
|
source string
|
||||||
|
wantResult string
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := "scan"
|
||||||
|
|
||||||
|
flags := DictSetFlag(0)
|
||||||
|
vars := map[string]string{"one": "1", "two": "2", "three": "3"}
|
||||||
|
ctx1 := NewDictSetContext(flags, vars)
|
||||||
|
ctx2 := NewDictSetContext(KeepVar, vars)
|
||||||
|
AddTimeFuncs(ctx2)
|
||||||
|
/*
|
||||||
|
err = fmt.Errorf("unsupported '%%%c' time specifier at %d", ch, i)
|
||||||
|
*/
|
||||||
|
now := time.Now()
|
||||||
|
yday := now.Add(-24 * time.Hour)
|
||||||
|
inputs := []inputType{
|
||||||
|
{ctx1, "${|__default ' abc cba '|__trim}", "abc cba", nil},
|
||||||
|
{ctx1, "${|__default two|__map ':' 'one:1' 'two:2' 'six:6'}", "2", nil},
|
||||||
|
{ctx1, "${|__default two|__map ':' 'one:1' 'TWO:2' 'six:6'}", "", nil},
|
||||||
|
{ctx1, "${|__default two|__map-nc ':' 'one:1' 'TWO:2' 'six:6'}", "2", nil},
|
||||||
|
{ctx2, `form: ${|__time-fmt now ""}`, fmt.Sprintf("form: %d-%02d-%02d %02d:%02d:%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()), nil},
|
||||||
|
{ctx2, `form: ${|__time-fmt now "%-D"}`, fmt.Sprintf("form: %d-%d-%d", now.Year(), now.Month(), now.Day()), nil},
|
||||||
|
{ctx2, `form: ${|__time-fmt now "%:T"}`, fmt.Sprintf("form: %d:%d:%d", now.Hour(), now.Minute(), now.Second()), nil},
|
||||||
|
{ctx2, `form: ${|__time-fmt now "%02:T"}`, "", errors.New("invalid time specifier format at 3")},
|
||||||
|
{ctx2, `form: ${|__time-fmt ""}`, "", errors.New("time value is empty")},
|
||||||
|
{ctx2, `form: ${|__time-fmt yesterday "%-D"}`, fmt.Sprintf("form: %d-%d-%d", yday.Year(), yday.Month(), yday.Day()), nil},
|
||||||
|
{ctx1, "${one|pippo}", "1", nil},
|
||||||
|
{ctx1, "${one|__default pluto}", "1", nil},
|
||||||
|
{ctx1, "${six|__default pluto}", "pluto", nil},
|
||||||
|
{ctx1, "${six|__default - pluto paperino|__lower|__upper}", "PLUTO-PAPERINO", nil},
|
||||||
|
{ctx1, "${two|__join + 1 ${.} ${|__get three}}", "1+2+3", nil},
|
||||||
|
{ctx1, "${=one|__get}", "1", nil},
|
||||||
|
{ctx1, "${|__get unknown default}", "default", nil},
|
||||||
|
{ctx1, "${one|__set A ${.}}", "1", nil},
|
||||||
|
{ctx1, "${one|__set B }", "1", nil},
|
||||||
|
{ctx1, "${one|__set}", "1", nil},
|
||||||
|
{ctx1, "${=archive.tar.gz|__trim-suffix}", "archive.tar", nil},
|
||||||
|
{ctx1, "${=prog.exe|__trim-suffix .zip .png .exe .com}", "prog", nil},
|
||||||
|
{ctx1, "${=prog.exe|__trim-suffix .zip .png .jpeg .com}", "prog.exe", nil},
|
||||||
|
{ctx1, "${=ciccio}", "ciccio", nil},
|
||||||
|
{ctx1, "${=ciccio{}", "", errors.New("unbalanced open braces at offset 9")},
|
||||||
|
{ctx1, "${|__get two}", "2", nil},
|
||||||
|
{ctx1, "${one} + ${two} = ${three}", "1 + 2 = 3", nil},
|
||||||
|
{ctx1, "${one} + ${two} = ${four}", "1 + 2 = ", nil},
|
||||||
|
{ctx2, "${one} + ${two} = ${four}", "1 + 2 = ${four}", nil},
|
||||||
|
{ctx2, `now: ${|__time-fmt now "%:02T"}`, fmt.Sprintf("now: %02d:%02d:%02d", now.Hour(), now.Minute(), now.Second()), nil},
|
||||||
|
{ctx2, `hour: ${|__time-fmt now "%02H"}`, fmt.Sprintf("hour: %02d", now.Hour()), nil},
|
||||||
|
{ctx2, `minute: ${|__time-fmt now "%02M"}`, fmt.Sprintf("minute: %02d", now.Minute()), nil},
|
||||||
|
{ctx2, `second: ${|__time-fmt now "%02S" }`, fmt.Sprintf("second: %02d", now.Second()), nil},
|
||||||
|
{ctx2, `today: ${|__time-fmt now "%-02D"}`, fmt.Sprintf("today: %d-%02d-%02d", now.Year(), now.Month(), now.Day()), nil},
|
||||||
|
{ctx2, `year: ${|__time-fmt now "%Y"}`, fmt.Sprintf("year: %d", now.Year()), nil},
|
||||||
|
{ctx2, `month: ${|__time-fmt now "%m"}`, fmt.Sprintf("month: %02d", now.Month()), nil},
|
||||||
|
{ctx2, `weird: ${|__time-fmt now "%c"}`, "", errors.New(`unsupported 'c' time specifier at "%c"/2`)},
|
||||||
|
{ctx2, `perc: ${|__time-fmt now "%%"}`, "perc: %", nil},
|
||||||
|
{ctx1, `unknown var ${!unknown}`, ``, errors.New(`the variable specification "unknown" requires a value`)},
|
||||||
|
{ctx1, `${hello-word|}!`, `!`, nil},
|
||||||
|
{ctx1, `${hello-word|ciao}!`, `ciao!`, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotResult, gotErr := ExpandStringTemplate(input.ctx, input.source)
|
||||||
|
|
||||||
|
if gotResult != input.wantResult {
|
||||||
|
t.Errorf("%d: %s(%q)/result = %q, want %q", i, funcName, input.source, gotResult, input.wantResult)
|
||||||
|
}
|
||||||
|
if gotErr == nil && input.wantErr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (gotErr != nil && input.wantErr == nil) || (gotErr == nil && input.wantErr != nil) || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %s(%q)/err = <%v>, want <%v>", i, funcName, input.source, gotErr, input.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckNumArgs(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
info ExpanderFuncBlock
|
||||||
|
n int // number of args
|
||||||
|
want bool
|
||||||
|
}
|
||||||
|
funcName := "CheckNumArgs"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
{ExpanderFuncBlock{"", nil, -1, -1, ""}, 0, true},
|
||||||
|
{ExpanderFuncBlock{"", nil, 0, -1, ""}, 10, true},
|
||||||
|
{ExpanderFuncBlock{"", nil, 2, 4, ""}, 1, false},
|
||||||
|
}
|
||||||
|
for i, input := range inputs {
|
||||||
|
got := input.info.CheckNumArgs(input.n)
|
||||||
|
|
||||||
|
if got != input.want {
|
||||||
|
t.Errorf("%d: %s(%d)/result = %v, want %v", i, funcName, input.n, got, input.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIterateFunc(t *testing.T) {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
funcName := "IterateFunc"
|
||||||
|
|
||||||
|
want := "f141c5fa802ed509c08ec057eea5ba0086a84b21"
|
||||||
|
vars := map[string]string{"one": "1", "two": "2", "three": "3"}
|
||||||
|
ctx := NewDictSetContext(KeepVar, vars)
|
||||||
|
ctx.AddFunc("dumm1", nil, 1, 2, "bla-bla")
|
||||||
|
ctx.AddFunc("dumm2", nil, 0, 5, strings.Repeat(".", 81))
|
||||||
|
ctx.AddFunc("dumm3", nil, -1, -1, "bla-bla")
|
||||||
|
ctx.AddFunc("dumm4", nil, 2, -1, "bla-bla")
|
||||||
|
|
||||||
|
ctx.IterateFunc(func(info *ExpanderFuncBlock) error {
|
||||||
|
sb.WriteString(info.String())
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
hasher := sha1.New()
|
||||||
|
hasher.Write([]byte(sb.String()))
|
||||||
|
got := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
//fmt.Println("sha:", got)
|
||||||
|
//fmt.Println(sb.String())
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("%s()/result = %q, want %q", funcName, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// expander-base-funcs.go
|
// expander-base-funcs.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
@@ -81,6 +84,40 @@ func ExFuncTrimSuffix(ctx ExpanderContext, varValue string, args []string) (resu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExFuncTrim(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||||
|
result = strings.TrimSpace(varValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExFuncMap(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||||
|
sep := args[0]
|
||||||
|
for _, pairSpec := range args[1:] {
|
||||||
|
pair := strings.SplitN(pairSpec, sep, 2)
|
||||||
|
if pair[0] == varValue {
|
||||||
|
if len(pair) == 2 {
|
||||||
|
result = pair[1]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExFuncMapNC(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||||
|
sep := args[0]
|
||||||
|
varValue = strings.ToLower(varValue)
|
||||||
|
for _, pairSpec := range args[1:] {
|
||||||
|
pair := strings.SplitN(pairSpec, sep, 2)
|
||||||
|
if strings.ToLower(pair[0]) == varValue {
|
||||||
|
if len(pair) == 2 {
|
||||||
|
result = pair[1]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func AddBaseFuncs(ctx ExpanderContext) ExpanderContext {
|
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("__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("__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. ")
|
||||||
@@ -88,6 +125,9 @@ func AddBaseFuncs(ctx ExpanderContext) ExpanderContext {
|
|||||||
ctx.AddFunc("__join", ExFuncJoin, 1, -1, "Joins its args starting from the second. The first one (arg1) is the separator")
|
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("__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("__upper", ExFuncUpper, 0, 0, "Changes the flow value to upper case.")
|
||||||
|
ctx.AddFunc("__trim", ExFuncTrim, 0, 0, "Removes blanks on the left and on the right of the flow.")
|
||||||
ctx.AddFunc("__trim-suffix", ExFuncTrimSuffix, 1, 1, "Removes the specified suffix (arg1) from the flow.")
|
ctx.AddFunc("__trim-suffix", ExFuncTrimSuffix, 1, 1, "Removes the specified suffix (arg1) from the flow.")
|
||||||
|
ctx.AddFunc("__map", ExFuncMap, 1, 1, "Returns the value mapped by the flow value as a key; e.g. two __map : 'one:1' 'two:2' 'six:6' returns 2")
|
||||||
|
ctx.AddFunc("__map-nc", ExFuncMapNC, 1, 1, "Same as __map but this comapares keys ignoring case differencies; e.g. two __map-nc : 'ONE:1' 'Two:2' 'sIX:6' returns 2")
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// expander-context.go
|
// expander-context.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
@@ -10,8 +13,6 @@ import (
|
|||||||
"git.portale-stac.it/go-pkg/golang"
|
"git.portale-stac.it/go-pkg/golang"
|
||||||
)
|
)
|
||||||
|
|
||||||
const varIntro = '$'
|
|
||||||
|
|
||||||
type ExpanderFunc func(ctx ExpanderContext, varValue string, args []string) (result string, err error)
|
type ExpanderFunc func(ctx ExpanderContext, varValue string, args []string) (result string, err error)
|
||||||
|
|
||||||
type ExpanderFuncBlock struct {
|
type ExpanderFuncBlock struct {
|
||||||
@@ -127,7 +128,7 @@ func (self *BaseExpander) AddFunc(name string, f ExpanderFunc, minArgs, maxArgs
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BaseExpander) Handle(varSpec string, flags ScannerFlag) (value string, err error) {
|
func (self *BaseExpander) Handle(varSpec string, flags ScannerFlag) (value string, err error) {
|
||||||
err = errors.New("maybe you did somwthing wrong")
|
err = errors.New("maybe you did something wrong")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,4 +140,3 @@ func (self *BaseExpander) IterateFunc(op func(funcInfo *ExpanderFuncBlock) (err
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expander-context_test.go
|
||||||
|
package text
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// expander-time-funcs.go
|
// expander-time-funcs.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// expander.go
|
// expander.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// scanner_test.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxExpType struct {
|
||||||
|
BaseExpander
|
||||||
|
funcs map[string]ExpanderFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCtxExp() *ctxExpType {
|
||||||
|
return &ctxExpType{funcs: make(map[string]ExpanderFunc)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxExpType) Handle(spec string, flags ScannerFlag) (value string, err error) {
|
||||||
|
var f ExpanderFunc
|
||||||
|
|
||||||
|
//fmt.Println("Spec:", spec)
|
||||||
|
switch spec {
|
||||||
|
case "value":
|
||||||
|
value = "right-value"
|
||||||
|
case "|ciao":
|
||||||
|
value = "ciao"
|
||||||
|
case `|__sum(1,2,3)`:
|
||||||
|
if f, err = ctx.GetFunc("__sum"); err == nil {
|
||||||
|
value, err = f(ctx, "", []string{"1", "2", "3"})
|
||||||
|
}
|
||||||
|
case `|__cat(1,"2",3)`:
|
||||||
|
if f, err = ctx.GetFunc("__cat"); err == nil {
|
||||||
|
value, err = f(ctx, "", []string{"1", "2", "3"})
|
||||||
|
}
|
||||||
|
case `uno|due`:
|
||||||
|
value = "uno"
|
||||||
|
case "|__xyz()":
|
||||||
|
err = errors.New("unknown function __xyz")
|
||||||
|
default:
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (ctx *ctxExpType) GetFunc(name string) (f ExpanderFunc, err error) {
|
||||||
|
var ok bool
|
||||||
|
if f, ok = ctx.funcs[name]; !ok {
|
||||||
|
err = fmt.Errorf("function %s not found", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxExpType) AddFunc(name string, f ExpanderFunc, minArgs, maxArgs int, description string) {
|
||||||
|
ctx.funcs[name] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func sum(ctx ExpanderContext, varValue string, args []string) (result string, err error) {
|
||||||
|
if len(varValue) > 0 {
|
||||||
|
result = varValue
|
||||||
|
} else {
|
||||||
|
var v int
|
||||||
|
s := 0
|
||||||
|
for _, arg := range args {
|
||||||
|
if v, err = strconv.Atoi(arg); err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
s += v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = strconv.Itoa(s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpandStringTemplate(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
ctx ExpanderContext
|
||||||
|
source string
|
||||||
|
wantResult string
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := "scan"
|
||||||
|
ctx := newCtxExp()
|
||||||
|
ctx.AddFunc("__sum", sum, 1, 1, "fake function")
|
||||||
|
inputs := []inputType{
|
||||||
|
{ctx, "this is the ${value}!", "this is the right-value!", nil},
|
||||||
|
{ctx, `pay \$31`, `pay $31`, nil},
|
||||||
|
{ctx, `\\regex\\`, `\regex\`, nil},
|
||||||
|
{ctx, `fake \x.`, `fake x.`, nil},
|
||||||
|
{ctx, ">${|ciao}<", ">ciao<", nil},
|
||||||
|
{ctx, `>${|__sum(1,2,3)}<`, ">6<", nil},
|
||||||
|
{ctx, `>${|__cat(1,"2",3)}<`, "", errors.New("function __cat not found")},
|
||||||
|
{ctx, `>${|__cat(1,"2",3)<`, "", errors.New("unbalanced open braces at offset 2")},
|
||||||
|
{ctx, `>${|__xyz()}<`, "", errors.New("unknown function __xyz")},
|
||||||
|
{ctx, `>${uno|due}<`, ">uno<", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotResult, gotErr := ExpandStringTemplate(input.ctx, input.source)
|
||||||
|
|
||||||
|
if gotResult != input.wantResult {
|
||||||
|
t.Errorf("%d: %s(%q)/result = %q, want %q", i, funcName, input.source, gotResult, input.wantResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr == nil && input.wantErr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (gotErr != nil && input.wantErr == nil) || (gotErr == nil && input.wantErr != nil) || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %s(%q)/err = %v, want %v", i, funcName, input.source, gotErr, input.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// scanner.go
|
// scanner.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
@@ -14,14 +17,20 @@ const (
|
|||||||
EncryptValue
|
EncryptValue
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flags symbols in the abova iota order
|
// Flags symbols in the above iota order
|
||||||
const flagSymbols = "!=*"
|
const flagSymbols = "!=*"
|
||||||
|
|
||||||
|
const defaultVarIntro = '$'
|
||||||
|
|
||||||
type VariableHandler interface {
|
type VariableHandler interface {
|
||||||
Handle(varSpec string, flags ScannerFlag) (value string, err error)
|
Handle(varSpec string, flags ScannerFlag) (value string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Scan(handler VariableHandler, source string) (result string, err error) {
|
func Scan(handler VariableHandler, source string) (result string, err error) {
|
||||||
|
return ScanExt(defaultVarIntro, handler, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScanExt(varIntro byte, handler VariableHandler, source string) (result string, err error) {
|
||||||
var spec, value string
|
var spec, value string
|
||||||
var flags ScannerFlag
|
var flags ScannerFlag
|
||||||
var backSlash bool
|
var backSlash bool
|
||||||
@@ -113,4 +122,3 @@ func getVarSpec(source string, startOffset int) (spec string, flags ScannerFlag,
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// scanner_test.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetVarSpec(t *testing.T) {
|
||||||
|
type inputGetVarSpec struct {
|
||||||
|
source string
|
||||||
|
offset int
|
||||||
|
wantSpec string
|
||||||
|
wantFlags ScannerFlag
|
||||||
|
wantOffset int
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := "getVarSpec"
|
||||||
|
|
||||||
|
inputs := []inputGetVarSpec{
|
||||||
|
{"Hello ${name}.", 7, "name", ScannerFlag(0), 13, nil},
|
||||||
|
{"one ${!varspec}", 5, "varspec", ValueRequired, 15, nil},
|
||||||
|
{"one ${*varspec}", 5, "varspec", EncryptValue, 15, nil},
|
||||||
|
{"one ${*!varspec}", 5, "varspec", ValueRequired | EncryptValue, 16, nil},
|
||||||
|
{"one ${*!var{spec}", 5, "", ValueRequired | EncryptValue, 17, errors.New("unbalanced open braces at offset 11")},
|
||||||
|
{"one ${two}${three}", 11, "three", ScannerFlag(0), 18, nil},
|
||||||
|
{"one $2${three}", 5, "2", ScannerFlag(0), 6, nil},
|
||||||
|
{"one $!2${three}", 5, "", ScannerFlag(0), 5, nil},
|
||||||
|
{"one $2{3}${three}", 5, "2", ScannerFlag(0), 6, nil},
|
||||||
|
{"one ${{2}}{3}${three}", 5, "{2}", ScannerFlag(0), 10, nil},
|
||||||
|
{">${|ciao}<", 2, "|ciao", ScannerFlag(0), 9, nil},
|
||||||
|
{">${|{ciao}<", 2, "", ScannerFlag(0), 11, errors.New("unbalanced open braces at offset 4")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotSpec, gotFlags, gotOffset, gotErr := getVarSpec(input.source, input.offset)
|
||||||
|
|
||||||
|
if gotSpec != input.wantSpec {
|
||||||
|
t.Errorf("%d: %s(%q, %d)/spec = %q, want %q", i, funcName, input.source, input.offset, gotSpec, input.wantSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotFlags != input.wantFlags {
|
||||||
|
t.Errorf("%d: %s(%q, %d)/flags = %d, want %d", i, funcName, input.source, input.offset, gotFlags, input.wantFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotOffset != input.wantOffset {
|
||||||
|
t.Errorf("%d: %s(%q, %d)/offset = %d, want %d", i, funcName, input.source, input.offset, gotOffset, input.wantOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr == nil && input.wantErr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (gotErr != nil && input.wantErr == nil) || (gotErr == nil && input.wantErr != nil) || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %s(%q, %d)/err = %v, want %v", i, funcName, input.source, input.offset, gotErr, input.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctxType struct{}
|
||||||
|
|
||||||
|
func (ctx *ctxType) Handle(spec string, flags ScannerFlag) (value string, err error) {
|
||||||
|
//fmt.Println("Spec:", spec)
|
||||||
|
switch spec {
|
||||||
|
case "value":
|
||||||
|
value = "right-value"
|
||||||
|
case "|ciao":
|
||||||
|
value = "ciao"
|
||||||
|
case `|__sum(1,2,3)`:
|
||||||
|
value = "6"
|
||||||
|
case `|__cat(1,"2",3)`:
|
||||||
|
value = "123"
|
||||||
|
case `uno|due`:
|
||||||
|
value = "uno"
|
||||||
|
case "|__xyz()":
|
||||||
|
err = errors.New("unknown function __xyz")
|
||||||
|
default:
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScan(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
ctx VariableHandler
|
||||||
|
source string
|
||||||
|
wantResult string
|
||||||
|
wantErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName := "scan"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
{&ctxType{}, "this is the ${value}!", "this is the right-value!", nil},
|
||||||
|
{&ctxType{}, `pay \$31`, `pay $31`, nil},
|
||||||
|
{&ctxType{}, `\\regex\\`, `\regex\`, nil},
|
||||||
|
{&ctxType{}, `fake \x.`, `fake x.`, nil},
|
||||||
|
{&ctxType{}, ">${|ciao}<", ">ciao<", nil},
|
||||||
|
{&ctxType{}, `>${|__sum(1,2,3)}<`, ">6<", nil},
|
||||||
|
{&ctxType{}, `>${|__cat(1,"2",3)}<`, ">123<", nil},
|
||||||
|
{&ctxType{}, `>${|__cat(1,"2",3)<`, "", errors.New("unbalanced open braces at offset 2")},
|
||||||
|
{&ctxType{}, `>${|__xyz()}<`, "", errors.New("unknown function __xyz")},
|
||||||
|
{&ctxType{}, `>${uno|due}<`, ">uno<", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotResult, gotErr := Scan(input.ctx, input.source)
|
||||||
|
|
||||||
|
if gotResult != input.wantResult {
|
||||||
|
t.Errorf("%d: %s(%q)/result = %q, want %q", i, funcName, input.source, gotResult, input.wantResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr == nil && input.wantErr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (gotErr != nil && input.wantErr == nil) || (gotErr == nil && input.wantErr != nil) || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %s(%q)/err = %v, want %v", i, funcName, input.source, gotErr, input.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// tty.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type caseMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
normal = caseMode(iota)
|
||||||
|
upper
|
||||||
|
lower
|
||||||
|
title
|
||||||
|
firstOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
type alignMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
noAlign = alignMode(iota)
|
||||||
|
alignLeft
|
||||||
|
alignRight
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxFmt struct {
|
||||||
|
isTTY bool
|
||||||
|
ansiLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateColor(name string) (c int) {
|
||||||
|
//fmt.Println("TranslateColor:", name)
|
||||||
|
if len(name) == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
norm := strings.ToLower(name)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix("black", norm):
|
||||||
|
c = 30
|
||||||
|
case strings.HasPrefix("blue", norm):
|
||||||
|
c = 34
|
||||||
|
case strings.HasPrefix("cyan", norm):
|
||||||
|
c = 36
|
||||||
|
case strings.HasPrefix("green", norm):
|
||||||
|
c = 32
|
||||||
|
case strings.HasPrefix("magenta", norm):
|
||||||
|
c = 35
|
||||||
|
case strings.HasPrefix("red", norm):
|
||||||
|
c = 31
|
||||||
|
case strings.HasPrefix("white", norm):
|
||||||
|
c = 37
|
||||||
|
case strings.HasPrefix("yellow", norm):
|
||||||
|
c = 33
|
||||||
|
default:
|
||||||
|
c = -1
|
||||||
|
}
|
||||||
|
if c > 0 && name[0] >= 'A' && name[0] <= 'Z' {
|
||||||
|
c += 10
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractArgs(spec string) (result string) {
|
||||||
|
if len(spec) < 2 || spec[0] != '(' || spec[len(spec)-1] != ')' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = spec[1 : len(spec)-1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseColor(fg bool, spec string) (result string) {
|
||||||
|
var intro string
|
||||||
|
if spec = extractArgs(spec); len(spec) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec[0] >= '0' && spec[0] <= '9' {
|
||||||
|
if fg {
|
||||||
|
intro = "38;"
|
||||||
|
} else {
|
||||||
|
intro = "48;"
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(spec, ",", 3)
|
||||||
|
if len(parts) == 3 {
|
||||||
|
result = intro + "2;" + strings.Join(parts, ";")
|
||||||
|
} else if len(parts) == 1 {
|
||||||
|
result = intro + "5;" + parts[0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := translateColor(spec); c >= 0 {
|
||||||
|
result = strconv.Itoa(c)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func advanceToTerms(s string, start int, terms string) (offset int) {
|
||||||
|
for offset = start; offset < len(s) && strings.IndexByte(terms, s[offset]) < 0; offset++ {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiString(sb *strings.Builder, code string) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[")
|
||||||
|
b, _ := sb.WriteString(code)
|
||||||
|
sb.WriteByte('m')
|
||||||
|
ctx.ansiLen += a + b + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiCode(sb *strings.Builder, code int) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[")
|
||||||
|
b, _ := sb.WriteString(strconv.Itoa(code))
|
||||||
|
sb.WriteByte('m')
|
||||||
|
ctx.ansiLen += a + b + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putAnsiReset(sb *strings.Builder) {
|
||||||
|
if ctx.isTTY {
|
||||||
|
a, _ := sb.WriteString("\x1b[0m")
|
||||||
|
ctx.ansiLen += a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) putText(sb *strings.Builder, text string, start int, mode caseMode, align alignMode, fieldSize int, filler byte) (offset int) {
|
||||||
|
if t := text[start+1:]; len(t) > 0 {
|
||||||
|
if align == alignRight {
|
||||||
|
for k := 0; k < fieldSize-len(t); k++ {
|
||||||
|
sb.WriteByte(filler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mode == upper {
|
||||||
|
sb.WriteString(strings.ToUpper(t))
|
||||||
|
} else if mode == lower {
|
||||||
|
sb.WriteString(strings.ToLower(t))
|
||||||
|
} else if mode == title {
|
||||||
|
sb.WriteString(strings.Title(t))
|
||||||
|
} else if mode == firstOnly {
|
||||||
|
if t[0] >= 'a' && t[0] <= 'z' {
|
||||||
|
sb.WriteByte(t[0] - 'a' + 'A')
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(t[0])
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.ToLower(t[1:]))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(t)
|
||||||
|
}
|
||||||
|
if align == alignLeft {
|
||||||
|
for k := 0; k < fieldSize-len(t); k++ {
|
||||||
|
sb.WriteByte(filler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = start + len(t)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ctxFmt) Handle(varSpec string, flags ScannerFlag) (value string, err error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var mode caseMode = normal
|
||||||
|
var align alignMode = noAlign
|
||||||
|
var fieldSize int
|
||||||
|
var filler byte = ' '
|
||||||
|
|
||||||
|
//fmt.Println("TTY:", varSpec)
|
||||||
|
if len(varSpec) == 0 {
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(varSpec); i++ {
|
||||||
|
if j := advanceToTerms(varSpec, i, ";-"); j > i+1 {
|
||||||
|
if c := translateColor(varSpec[i:j]); c >= 0 {
|
||||||
|
ctx.putAnsiCode(&sb, c)
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := varSpec[i]
|
||||||
|
switch ch {
|
||||||
|
case '.':
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
case 'b':
|
||||||
|
ctx.putAnsiString(&sb, "1")
|
||||||
|
case 'B':
|
||||||
|
ctx.putAnsiString(&sb, "22")
|
||||||
|
case 'i':
|
||||||
|
ctx.putAnsiString(&sb, "3")
|
||||||
|
case 'I':
|
||||||
|
ctx.putAnsiString(&sb, "23")
|
||||||
|
case 'u':
|
||||||
|
ctx.putAnsiString(&sb, "4")
|
||||||
|
case 'U':
|
||||||
|
ctx.putAnsiString(&sb, "24")
|
||||||
|
case 'c', 'C':
|
||||||
|
j := advanceToTerms(varSpec, i, ")")
|
||||||
|
if c := parseColor(ch == 'c', varSpec[i+1:j+1]); len(c) > 0 {
|
||||||
|
ctx.putAnsiString(&sb, c)
|
||||||
|
//sb.WriteString("\x1b[" + c + "m")
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
case '\'':
|
||||||
|
mode = upper
|
||||||
|
case ',':
|
||||||
|
mode = lower
|
||||||
|
case '"':
|
||||||
|
mode = title
|
||||||
|
case '^':
|
||||||
|
mode = firstOnly
|
||||||
|
case '<', '>':
|
||||||
|
j := advanceToTerms(varSpec, i, ")")
|
||||||
|
if arg := extractArgs(varSpec[i+1 : j+1]); len(arg) > 0 {
|
||||||
|
var err1 error
|
||||||
|
if arg[0] <= '0' || arg[0] > '9' {
|
||||||
|
filler = arg[0]
|
||||||
|
arg = arg[1:]
|
||||||
|
}
|
||||||
|
if fieldSize, err1 = strconv.Atoi(arg); err1 == nil {
|
||||||
|
if ch == '<' {
|
||||||
|
align = alignLeft
|
||||||
|
} else {
|
||||||
|
align = alignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
case '-':
|
||||||
|
i = ctx.putText(&sb, varSpec, i, mode, align, fieldSize, filler)
|
||||||
|
ctx.putAnsiReset(&sb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTty(w io.Writer) (isTty bool) {
|
||||||
|
if fh, ok := w.(*os.File); ok {
|
||||||
|
if fi, err := fh.Stat(); err == nil {
|
||||||
|
isTty = (fi.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTty(source string) (result string, cleanLen int, err error) {
|
||||||
|
ctx := ctxFmt{true, 0}
|
||||||
|
if result, err = ScanExt('#', &ctx, source); err == nil {
|
||||||
|
cleanLen = len(result) - ctx.ansiLen
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTtyStream(w io.Writer, source string) (result string, cleanLen int, err error) {
|
||||||
|
if IsTty(w) {
|
||||||
|
return ToTty(source)
|
||||||
|
} else {
|
||||||
|
return source, len(source), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sprintf(templ string, args ...any) string {
|
||||||
|
templ, _, _ = ToTty(templ)
|
||||||
|
return fmt.Sprintf(templ, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Printf(templ string, args ...any) (int, error) {
|
||||||
|
return Fprintf(os.Stdout, templ, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fprintf(w io.Writer, templ string, args ...any) (int, error) {
|
||||||
|
templ, _, _ = ToTtyStream(w, templ)
|
||||||
|
return fmt.Fprintf(w, templ, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func DebugSprintf() {
|
||||||
|
// list := []string{
|
||||||
|
// "#{<(.20)-Param}: #{>(06)-123}",
|
||||||
|
// "ciao #{c(r)i<(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i>(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(red);i}Mario#. #{u;b}Come# #{GREEN;b}Stai#{.}?",
|
||||||
|
// "ciao #{c(red)i-Mario}. #{ub-Come} #{GREEN;b-Stai}?",
|
||||||
|
// "ciao #{c(r)i-Mario}. #{ub-Come} #{Gr;b-Stai}?",
|
||||||
|
// "ciao #{c(r)i-Mario}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i'-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i\"-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i,-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// "ciao #{c(r)i^-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fmt.Println("--- Sprintf() ---")
|
||||||
|
// for i, s := range list {
|
||||||
|
// x := Sprintf(s)
|
||||||
|
// fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
// fmt.Println(x)
|
||||||
|
// }
|
||||||
|
// fmt.Println("\n--- Sprintf() ---")
|
||||||
|
// for i, s := range list {
|
||||||
|
// fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
// Printf(s)
|
||||||
|
// fmt.Println()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expander-context_test.go
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testCases() []string {
|
||||||
|
return []string{
|
||||||
|
"#{<(.20)-Param}: #{>(06)-123}",
|
||||||
|
"ciao #{c(r)i<(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(r)i>(12)-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(red);i}Mario#. #{u;b}Come# #{GREEN;b}Stai#{.}?",
|
||||||
|
"ciao #{c(red)i-Mario}. #{ub-Come} #{GREEN;b-Stai}?",
|
||||||
|
"ciao #{c(r)i-Mario}. #{ub-Come} #{Gr;b-Stai}?",
|
||||||
|
"ciao #{c(r)i-Mario}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(r)i'-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(r)i\"-gian carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(r)i,-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
"ciao #{c(r)i^-GIAN Carlo}. #{ub-Come} #{c(G)b-Stai}?",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSprintf(t *testing.T) {
|
||||||
|
list := testCases()
|
||||||
|
fmt.Println("--- Sprintf() ---")
|
||||||
|
for i, s := range list {
|
||||||
|
x := Sprintf(s)
|
||||||
|
fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
fmt.Println(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintf(t *testing.T) {
|
||||||
|
list := testCases()
|
||||||
|
fmt.Println("\n--- Printf() ---")
|
||||||
|
for i, s := range list {
|
||||||
|
fmt.Printf("--- Test nr %d: %q\n", i+1, s)
|
||||||
|
Printf(s)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
// utils.go
|
// utils.go
|
||||||
package text
|
package text
|
||||||
|
|
||||||
@@ -6,5 +9,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Var(name string) string {
|
func Var(name string) string {
|
||||||
return fmt.Sprintf("%c{%s}", varIntro, name)
|
return fmt.Sprintf("%c{%s}", defaultVarIntro, name)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user