From e9b8c7ccfa0b35777df945512a53a3f46231548d Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Mon, 13 Mar 2023 23:28:38 +0000 Subject: [PATCH] Update REPL to use ext.Strings formatter when possible. ext.Strings provides a string formatter that formats builtin values as CEL literals in most cases. --- ext/strings.go | 11 +++++++---- repl/BUILD.bazel | 1 + repl/evaluator.go | 9 ++++++++- repl/evaluator_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/ext/strings.go b/ext/strings.go index e1d15589..060aa06a 100644 --- a/ext/strings.go +++ b/ext/strings.go @@ -681,7 +681,7 @@ func clauseForType(argType ref.Type) (clauseImpl, error) { case types.IntType, types.UintType: return formatDecimal, nil case types.StringType, types.BytesType, types.BoolType, types.NullType, types.TypeType: - return formatString, nil + return FormatString, nil case types.TimestampType, types.DurationType: // special case to ensure timestamps/durations get printed as CEL literals return func(arg ref.Val, locale string) (string, error) { @@ -770,7 +770,7 @@ func formatMap(arg ref.Val, locale string) (string, error) { var keyFormat clauseImpl switch key.Type() { case types.StringType, types.BoolType: - keyFormat = formatString + keyFormat = FormatString case types.IntType, types.UintType: keyFormat = formatDecimal default: @@ -844,7 +844,10 @@ func quoteForCEL(refVal ref.Val, unquotedValue string) string { } } -func formatString(arg ref.Val, locale string) (string, error) { +// FormatString returns the string representation of a CEL value. +// It is used to implement the %s specifier in the (string).format() extension +// function. +func FormatString(arg ref.Val, locale string) (string, error) { switch arg.Type() { case types.ListType: return formatList(arg, locale) @@ -1018,7 +1021,7 @@ func parseFormattingClause(lastStrIndex int, formatStr string) (int, clauseImpl, i++ switch r { case 's': - return i, formatString, nil + return i, FormatString, nil case 'd': return i, formatDecimal, nil case 'f': diff --git a/repl/BUILD.bazel b/repl/BUILD.bazel index ebd5bbee..ddba2f99 100644 --- a/repl/BUILD.bazel +++ b/repl/BUILD.bazel @@ -32,6 +32,7 @@ go_library( "//checker/decls:go_default_library", "//common/types:go_default_library", "//common/types/ref:go_default_library", + "//ext:go_default_library", "//interpreter:go_default_library", "//interpreter/functions:go_default_library", "//repl/parser:go_default_library", diff --git a/repl/evaluator.go b/repl/evaluator.go index d4b19ff5..614c7638 100644 --- a/repl/evaluator.go +++ b/repl/evaluator.go @@ -25,6 +25,7 @@ import ( "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/ext" "github.com/google/cel-go/interpreter" "github.com/google/cel-go/interpreter/functions" @@ -758,7 +759,13 @@ func (e *Evaluator) Process(cmd Cmder) (string, bool, error) { return "", false, fmt.Errorf("expr failed:\n%v", err) } if val != nil { - return fmt.Sprintf("%v : %s", val.Value(), UnparseType(resultT)), false, nil + t := UnparseType(resultT) + v, err := ext.FormatString(val, "") + if err != nil { + // Default format if type is unsupported by ext.Strings formatter. + return fmt.Sprintf("%v : %s", val.Value(), t), false, nil + } + return fmt.Sprintf("%s : %s", v, t), false, nil } case *letVarCmd: var err error diff --git a/repl/evaluator_test.go b/repl/evaluator_test.go index a1fa290e..20ee847a 100644 --- a/repl/evaluator_test.go +++ b/repl/evaluator_test.go @@ -494,6 +494,50 @@ func TestProcess(t *testing.T) { wantExit bool wantError bool }{ + { + name: "FormatNumberResult", + commands: []Cmder{ + &evalCmd{ + expr: "1u + 2u", + }, + }, + wantText: "3 : uint", + wantExit: false, + wantError: false, + }, + { + name: "FormatStringResult", + commands: []Cmder{ + &evalCmd{ + expr: `'a' + r'b\1'`, + }, + }, + wantText: `ab\1 : string`, + wantExit: false, + wantError: false, + }, + { + name: "FormatListResult", + commands: []Cmder{ + &evalCmd{ + expr: `['abc', 123, 3.14, duration('2m')]`, + }, + }, + wantText: `["abc", 123, 3.140000, duration("120s")] : list(dyn)`, + wantExit: false, + wantError: false, + }, + { + name: "FormatMapResult", + commands: []Cmder{ + &evalCmd{ + expr: `{1: 123, 2: 3.14, 3: duration('2m'), 4: b'123'}`, + }, + }, + wantText: `{1:123, 2:3.140000, 3:duration("120s"), 4:b"123"} : map(int, dyn)`, + wantExit: false, + wantError: false, + }, { name: "OptionBasic", commands: []Cmder{