From 0bad57f55344c1ab9738ace3a695987e99f9d35b Mon Sep 17 00:00:00 2001 From: David McClure Date: Thu, 3 Jun 2021 18:00:39 +0000 Subject: [PATCH] Add indent with multiline to json.encode - allow json.encode to receive an indent named argument: ``` json.encode('{"a":"b"}', indent=2) ``` - indent greater than 0 indicates the json shall be rendered as a multiline string indented with the specified number of spaces - indent=0 and omitting indent both result in a single line json string - errors are returned if indent > 4 or if arguments other than indent are included Issue #410 Co-authored-by: Tyler Schultz --- pkg/template/core/args.go | 28 +++++++++ .../filetests/ytt-library/json.yml | 63 +++++++++++++------ pkg/yttlibrary/json.go | 22 ++++++- 3 files changed, 94 insertions(+), 19 deletions(-) diff --git a/pkg/template/core/args.go b/pkg/template/core/args.go index 95b32f1c4..dc022b1fc 100644 --- a/pkg/template/core/args.go +++ b/pkg/template/core/args.go @@ -4,6 +4,8 @@ package core import ( + "fmt" + "github.com/k14s/starlark-go/starlark" ) @@ -19,3 +21,29 @@ func BoolArg(kwargs []starlark.Tuple, keyToFind string) (bool, error) { } return false, nil } + +func Int64Arg(kwargs []starlark.Tuple, keyToFind string) (int64, error) { + for _, arg := range kwargs { + key, err := NewStarlarkValue(arg.Index(0)).AsString() + if err != nil { + return 0, err + } + if key == keyToFind { + return NewStarlarkValue(arg.Index(1)).AsInt64() + } + } + return 0, nil +} + +func CheckArgNames(kwargs []starlark.Tuple, validKeys map[string]struct{}) error { + for _, arg := range kwargs { + key, err := NewStarlarkValue(arg.Index(0)).AsString() + if err != nil { + return err + } + if _, ok := validKeys[key]; !ok { + return fmt.Errorf("invalid argument name: %s", key) + } + } + return nil +} diff --git a/pkg/yamltemplate/filetests/ytt-library/json.yml b/pkg/yamltemplate/filetests/ytt-library/json.yml index cf042e39d..7d9999174 100644 --- a/pkg/yamltemplate/filetests/ytt-library/json.yml +++ b/pkg/yamltemplate/filetests/ytt-library/json.yml @@ -7,24 +7,51 @@ fragment: piece1: false #@ end -test1: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}) -test1a: #@ json.encode(yaml_fragment()) -test1b: #@ json.encode({"inside_map": yaml_fragment(), "inside_array": [yaml_fragment()]}) -test2: #@ json.encode({}) -test3: #@ json.decode("{}") -test4: #@ json.decode('{"a":[1,2,3,{"c":456}],"b":"str"}') +encode: + test1: #@ json.encode({}) + test2: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}) + test3: #@ json.encode(yaml_fragment()) + test4: #@ json.encode({"inside_map": yaml_fragment(), "inside_array": [yaml_fragment()]}) + indent: + test1: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}, indent=4) + test2: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}, indent=0) + #! TODO: how should we test error cases like these? + #! test1e: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}, indent=5) + #! test1f: #@ json.encode({"a": [1,2,3,{"c":456}], "b": "str"}, inschment=4) + #! + #! TODO: update docs to include indent: + #! https://github.com/vmware-tanzu/carvel.dev/blob/develop/content/ytt/docs/latest/lang-ref-ytt.md#serialization-modules +decode: + test1: #@ json.decode("{}") + test2: #@ json.decode('{"a":[1,2,3,{"c":456}],"b":"str"}') +++ -test1: '{"a":[1,2,3,{"c":456}],"b":"str"}' -test1a: '{"fragment":["piece1",{"piece1":false,"piece2":true}]}' -test1b: '{"inside_array":[{"fragment":["piece1",{"piece1":false,"piece2":true}]}],"inside_map":{"fragment":["piece1",{"piece1":false,"piece2":true}]}}' -test2: '{}' -test3: {} -test4: - a: - - 1 - - 2 - - 3 - - c: 456 - b: str +encode: + test1: '{}' + test2: '{"a":[1,2,3,{"c":456}],"b":"str"}' + test3: '{"fragment":["piece1",{"piece1":false,"piece2":true}]}' + test4: '{"inside_array":[{"fragment":["piece1",{"piece1":false,"piece2":true}]}],"inside_map":{"fragment":["piece1",{"piece1":false,"piece2":true}]}}' + indent: + test1: |- + { + "a": [ + 1, + 2, + 3, + { + "c": 456 + } + ], + "b": "str" + } + test2: '{"a":[1,2,3,{"c":456}],"b":"str"}' +decode: + test1: {} + test2: + a: + - 1 + - 2 + - 3 + - c: 456 + b: str diff --git a/pkg/yttlibrary/json.go b/pkg/yttlibrary/json.go index e85168a7f..9171d2588 100644 --- a/pkg/yttlibrary/json.go +++ b/pkg/yttlibrary/json.go @@ -6,6 +6,7 @@ package yttlibrary import ( "encoding/json" "fmt" + "strings" "github.com/k14s/starlark-go/starlark" "github.com/k14s/starlark-go/starlarkstruct" @@ -24,6 +25,9 @@ var ( }, }, } + JSONKWARGS = map[string]struct{}{ + "indent": struct{}{}, + } ) type jsonModule struct{} @@ -32,6 +36,9 @@ func (b jsonModule) Encode(thread *starlark.Thread, f *starlark.Builtin, args st if args.Len() != 1 { return starlark.None, fmt.Errorf("expected exactly one argument") } + if err := core.CheckArgNames(kwargs, JSONKWARGS); err != nil { + return starlark.None, err + } val, err := core.NewStarlarkValue(args.Index(0)).AsGoValue() if err != nil { @@ -39,7 +46,20 @@ func (b jsonModule) Encode(thread *starlark.Thread, f *starlark.Builtin, args st } val = orderedmap.Conversion{yamlmeta.NewGoFromAST(val)}.AsUnorderedStringMaps() - valBs, err := json.Marshal(val) + var valBs []byte + indent, err := core.Int64Arg(kwargs, "indent") + if err != nil { + return starlark.None, err + } + if indent > 4 || indent < 0 { + return starlark.None, fmt.Errorf("indent value must be between 0 and 4") + } + if indent > 0 { + valBs, err = json.MarshalIndent(val, "", strings.Repeat(" ", int(indent))) + } else { + valBs, err = json.Marshal(val) + } + if err != nil { return starlark.None, err }