diff --git a/example/README.md b/example/README.md index 977d231..3fc6beb 100644 --- a/example/README.md +++ b/example/README.md @@ -28,6 +28,7 @@ $ aws ssm put-parameter --name '/yashiro/example/secure' --value 'password' --ty ```sh $ ysr template -c ./yashiro.yaml example.yaml.tmpl +# This is a example of yashiro template. --- apiVersion: v1 kind: Secret diff --git a/example/example.yaml.tmpl b/example/example.yaml.tmpl index 48afb30..0678b08 100644 --- a/example/example.yaml.tmpl +++ b/example/example.yaml.tmpl @@ -1,3 +1,4 @@ +# This is a example of yashiro template. --- apiVersion: v1 kind: Secret diff --git a/internal/cmd/template.go b/internal/cmd/template.go index 45ded00..c66fbaa 100644 --- a/internal/cmd/template.go +++ b/internal/cmd/template.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/dwango/yashiro/pkg/config" "github.com/dwango/yashiro/pkg/engine" @@ -36,9 +37,19 @@ const example = ` # specify single file. ysr template ./example/*.tmpl ` +var textTypeValues = []string{ + string(engine.TextTypePlane), + string(engine.TextTypeJSON), + string(engine.TextTypeJSONArray), + string(engine.TextTypeYAML), + string(engine.TextTypeYAMLArray), + string(engine.TextTypeYAMLDocs), +} + func newTemplateCommand() *cobra.Command { var configFile string var ignoreNotFound bool + var textType string cmd := cobra.Command{ Use: "template ", @@ -55,7 +66,9 @@ func newTemplateCommand() *cobra.Command { return err } - eng, err := engine.New(cfg, engine.IgnoreNotFound(ignoreNotFound)) + eng, err := engine.New(cfg, + engine.IgnoreNotFound(ignoreNotFound), engine.TextType(engine.TextTypeOpt(textType)), + ) if err != nil { return err } @@ -71,6 +84,9 @@ func newTemplateCommand() *cobra.Command { f := cmd.Flags() f.StringVarP(&configFile, "config", "c", config.DefaultConfigFilename, "specify config file.") + f.StringVar(&textType, "text-type", string(engine.TextTypePlane), + fmt.Sprintf("specify text type after rendering. available values: %s", strings.Join(textTypeValues, ", ")), + ) f.BoolVar(&ignoreNotFound, "ignore-not-found", false, "ignore values are not found in the external store.") return &cmd diff --git a/pkg/engine/encoding/encoding.go b/pkg/engine/encoding/encoding.go new file mode 100644 index 0000000..861097a --- /dev/null +++ b/pkg/engine/encoding/encoding.go @@ -0,0 +1,60 @@ +/** + * Copyright 2024 DWANGO Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package encoding + +import ( + "errors" + "fmt" +) + +type TextType string + +// Define text types +const ( + TextTypeJSON TextType = "json" + TextTypeJSONArray TextType = "json-array" + TextTypeYAML TextType = "yaml" + TextTypeYAMLArray TextType = "yaml-array" + TextTypeYAMLDocs TextType = "yaml-docs" +) + +// Define errors +var ( + ErrUnsupportedTextType = errors.New("unsupported text type") + ErrFailedToEncodeAndDecode = errors.New("failed to encode and decode") +) + +// EncodeAndDecoder is an interface that provides encoding and decoding functionality. +type EncodeAndDecoder interface { + EncodeAndDecode(b []byte) ([]byte, error) +} + +func NewEncodeAndDecoder(t TextType) (EncodeAndDecoder, error) { + switch t { + case TextTypeJSON: + return &jsonEncodeAndDecoder{}, nil + case TextTypeJSONArray: + return &jsonEncodeAndDecoder{isArray: true}, nil + case TextTypeYAML: + return &yamlEncodeAndDecoder{docType: yamlDocTypeSingle}, nil + case TextTypeYAMLArray: + return &yamlEncodeAndDecoder{docType: yamlDocTypeArray}, nil + case TextTypeYAMLDocs: + return &yamlEncodeAndDecoder{docType: yamlDocTypeMulti}, nil + default: + return nil, fmt.Errorf("%w: %s", ErrUnsupportedTextType, t) + } +} diff --git a/pkg/engine/encoding/json.go b/pkg/engine/encoding/json.go new file mode 100644 index 0000000..873277b --- /dev/null +++ b/pkg/engine/encoding/json.go @@ -0,0 +1,42 @@ +/** + * Copyright 2024 DWANGO Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package encoding + +import ( + "encoding/json" + "fmt" +) + +type jsonEncodeAndDecoder struct { + isArray bool +} + +func (ed jsonEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) { + if ed.isArray { + v := []any{} + if err := json.Unmarshal(b, &v); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err) + } + return json.Marshal(v) + } + + v := map[string]any{} + if err := json.Unmarshal(b, &v); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err) + } + + return json.Marshal(v) +} diff --git a/pkg/engine/encoding/json_test.go b/pkg/engine/encoding/json_test.go new file mode 100644 index 0000000..97d35de --- /dev/null +++ b/pkg/engine/encoding/json_test.go @@ -0,0 +1,93 @@ +/** + * Copyright 2024 DWANGO Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package encoding + +import ( + "reflect" + "testing" +) + +func Test_jsonEncodeAndDecoder_EncodeAndDecode(t *testing.T) { + type fields struct { + isArray bool + } + type args struct { + str string + } + tests := []struct { + name string + fields fields + args args + wantStr string + wantErr bool + }{ + { + name: "ok", + fields: fields{ + isArray: false, + }, + args: args{ + str: `{"key":"value"}`, + }, + wantStr: `{"key":"value"}`, + }, + { + name: "ok: array", + fields: fields{ + isArray: true, + }, + args: args{ + str: `[{"key":"value"},{"key2":"value2"}]`, + }, + wantStr: `[{"key":"value"},{"key2":"value2"}]`, + }, + { + name: "error: invalid json", + fields: fields{ + isArray: false, + }, + args: args{ + str: "invalid json", + }, + wantErr: true, + }, + { + name: "error: invalid json array", + fields: fields{ + isArray: true, + }, + args: args{ + str: "invalid json", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ed := jsonEncodeAndDecoder{ + isArray: tt.fields.isArray, + } + got, err := ed.EncodeAndDecode([]byte(tt.args.str)) + if (err != nil) != tt.wantErr { + t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(string(got), tt.wantStr) { + t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() = %v, want %v", string(got), tt.wantStr) + } + }) + } +} diff --git a/pkg/engine/encoding/yaml.go b/pkg/engine/encoding/yaml.go new file mode 100644 index 0000000..45cb222 --- /dev/null +++ b/pkg/engine/encoding/yaml.go @@ -0,0 +1,106 @@ +/** + * Copyright 2024 DWANGO Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package encoding + +import ( + "bufio" + "bytes" + "fmt" + + "sigs.k8s.io/yaml" +) + +type yamlDocType int + +const ( + yamlDocTypeSingle yamlDocType = iota + yamlDocTypeArray + yamlDocTypeMulti +) + +type yamlEncodeAndDecoder struct { + docType yamlDocType +} + +const ( + yamlSeparator = "\n---" + separator = "---\n" +) + +func (ed yamlEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) { + switch ed.docType { + case yamlDocTypeMulti: + buf := bytes.NewBuffer(make([]byte, 0, len(b))) + + scn := bufio.NewScanner(bytes.NewReader(b)) + scn.Split(splitYAMLDocument) + for scn.Scan() { + v := map[string]any{} + if err := yaml.Unmarshal(scn.Bytes(), &v); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err) + } + if len(v) == 0 { + continue + } + b, _ := yaml.Marshal(v) + buf.WriteString(separator) + buf.Write(b) + } + return buf.Bytes(), nil + case yamlDocTypeArray: + v := []any{} + if err := yaml.Unmarshal(b, &v); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err) + } + return yaml.Marshal(v) + default: + v := map[string]any{} + if err := yaml.Unmarshal(b, &v); err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err) + } + return yaml.Marshal(v) + } +} + +// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents. +func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + sep := len([]byte(yamlSeparator)) + if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 { + // We have a potential document terminator + i += sep + after := data[i:] + if len(after) == 0 { + // we can't read any more characters + if atEOF { + return len(data), data[:len(data)-sep], nil + } + return 0, nil, nil + } + if j := bytes.IndexByte(after, '\n'); j >= 0 { + return i + j + 1, data[0 : i-sep], nil + } + return 0, nil, nil + } + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} diff --git a/pkg/engine/encoding/yaml_test.go b/pkg/engine/encoding/yaml_test.go new file mode 100644 index 0000000..ed1f114 --- /dev/null +++ b/pkg/engine/encoding/yaml_test.go @@ -0,0 +1,244 @@ +/** + * Copyright 2024 DWANGO Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package encoding + +import ( + "reflect" + "testing" +) + +func Test_yamlEncodeAndDecoder_EncodeAndDecode(t *testing.T) { + type fields struct { + docType yamlDocType + } + type args struct { + str string + } + tests := []struct { + name string + fields fields + args args + wantStr string + wantErr bool + }{ + { + name: "ok: single type", + fields: fields{ + docType: yamlDocTypeSingle, + }, + args: args{ + str: "---\nkey: value\n", + }, + wantStr: "key: value\n", + }, + { + name: "ok: array type", + fields: fields{ + docType: yamlDocTypeArray, + }, + args: args{ + str: "- key: value\n- key2: value2", + }, + wantStr: "- key: value\n- key2: value2\n", + }, + { + name: "ok: multi type", + fields: fields{ + docType: yamlDocTypeMulti, + }, + args: args{ + str: "---\nkey: value\n---\nkey2: value2", + }, + wantStr: "---\nkey: value\n---\nkey2: value2\n", + }, + { + name: "ok: single type with comment", + fields: fields{ + docType: yamlDocTypeSingle, + }, + args: args{ + str: "# comment\n---\nkey: value\n", + }, + wantStr: "key: value\n", + }, + { + name: "ok: multi type with comment", + fields: fields{ + docType: yamlDocTypeMulti, + }, + args: args{ + str: "# comment\n---\nkey: value\n---\nkey2: value2", + }, + wantStr: "---\nkey: value\n---\nkey2: value2\n", + }, + { + name: "error: invalid yaml with single type", + fields: fields{ + docType: yamlDocTypeSingle, + }, + args: args{ + str: "invalid yaml", + }, + wantErr: true, + }, + { + name: "error: invalid yaml with array type", + fields: fields{ + docType: yamlDocTypeArray, + }, + args: args{ + str: "invalid yaml", + }, + wantErr: true, + }, + { + name: "error: invalid yaml with multi type", + fields: fields{ + docType: yamlDocTypeMulti, + }, + args: args{ + str: "invalid yaml", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ed := yamlEncodeAndDecoder{ + docType: tt.fields.docType, + } + got, err := ed.EncodeAndDecode([]byte(tt.args.str)) + if (err != nil) != tt.wantErr { + t.Errorf("yamlEncodeAndDecoder.EncodeAndDecode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(string(got), tt.wantStr) { + t.Errorf("yamlEncodeAndDecoder.EncodeAndDecode() = %v, want %v", string(got), tt.wantStr) + } + }) + } +} + +func Test_splitYAMLDocument(t *testing.T) { + type args struct { + dataStr string + atEOF bool + } + tests := []struct { + name string + args args + wantAdvance int + wantTokenStr string + wantErr bool + }{ + { + name: "ok: at EOF separated", + args: args{ + dataStr: "abc\n---\ndef", + atEOF: true, + }, + wantAdvance: 8, + wantTokenStr: "abc", + }, + { + name: "ok: empty", + args: args{ + dataStr: "", + atEOF: true, + }, + wantAdvance: 0, + wantTokenStr: "", + }, + { + name: "ok: at EOF", + args: args{ + dataStr: "test", + atEOF: true, + }, + wantAdvance: 4, + wantTokenStr: "test", + }, + { + name: "ok: not at EOF", + args: args{ + dataStr: "test", + atEOF: false, + }, + wantAdvance: 0, + wantTokenStr: "", + }, + { + name: "ok: at EOF separator without newline", + args: args{ + dataStr: "---", + atEOF: true, + }, + wantAdvance: 3, + wantTokenStr: "---", + }, + { + name: "ok: at EOF separator", + args: args{ + dataStr: "---\n", + atEOF: true, + }, + wantAdvance: 4, + wantTokenStr: "---\n", + }, + { + name: "ok: not at EOF separator", + args: args{ + dataStr: "---\n", + atEOF: false, + }, + wantAdvance: 0, + wantTokenStr: "", + }, + { + name: "ok: not at EOF separator after newline", + args: args{ + dataStr: "\n---\n", + atEOF: false, + }, + wantAdvance: 5, + wantTokenStr: "", + }, + { + name: "ok: at EOF separator after newline", + args: args{ + dataStr: "\n---\n", + atEOF: true, + }, + wantAdvance: 5, + wantTokenStr: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotAdvance, gotToken, err := splitYAMLDocument([]byte(tt.args.dataStr), tt.args.atEOF) + if (err != nil) != tt.wantErr { + t.Errorf("splitYAMLDocument() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotAdvance != tt.wantAdvance { + t.Errorf("splitYAMLDocument() gotAdvance = %v, want %v", gotAdvance, tt.wantAdvance) + } + if !reflect.DeepEqual(string(gotToken), tt.wantTokenStr) { + t.Errorf("splitYAMLDocument() gotToken = %v, want %v", string(gotToken), tt.wantTokenStr) + } + }) + } +} diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 90341b0..6d9fe1a 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -17,22 +17,26 @@ package engine import ( + "bytes" "context" "io" "text/template" "github.com/dwango/yashiro/internal/client" "github.com/dwango/yashiro/pkg/config" + "github.com/dwango/yashiro/pkg/engine/encoding" ) +// Engine is a template engine. type Engine interface { Render(ctx context.Context, text string, dest io.Writer) error } type engine struct { - client client.Client - template *template.Template - option *opts + client client.Client + encodeAndDecoder encoding.EncodeAndDecoder + template *template.Template + option *opts } func New(cfg *config.Config, option ...Option) (Engine, error) { @@ -46,12 +50,23 @@ func New(cfg *config.Config, option ...Option) (Engine, error) { return nil, err } + var encAndDec encoding.EncodeAndDecoder + if opts.TextType == TextTypePlane { + encAndDec = &noOpEncodeAndDecoder{} + } else { + encAndDec, err = encoding.NewEncodeAndDecoder(opts.TextType) + if err != nil { + return nil, err + } + } + tmpl := template.New("yashiro").Option("missingkey=error").Funcs(funcMap()) return &engine{ - client: cli, - template: tmpl, - option: opts, + client: cli, + encodeAndDecoder: encAndDec, + template: tmpl, + option: opts, }, nil } @@ -69,5 +84,26 @@ func (e engine) render(text string, dest io.Writer, data any) error { return err } - return e.template.Execute(dest, data) + tmp := &bytes.Buffer{} + if err := e.template.Execute(tmp, data); err != nil { + return err + } + + b, err := e.encodeAndDecoder.EncodeAndDecode(tmp.Bytes()) + if err != nil { + return err + } + + _, err = io.Copy(dest, bytes.NewReader(b)) + if err != nil { + return err + } + + return nil +} + +type noOpEncodeAndDecoder struct{} + +func (noOpEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) { + return b, nil } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 4d439c4..b38c0d9 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -25,6 +25,7 @@ import ( "github.com/dwango/yashiro/internal/client" "github.com/dwango/yashiro/pkg/config" + "github.com/dwango/yashiro/pkg/engine/encoding" ) func TestNew(t *testing.T) { @@ -62,14 +63,21 @@ func (m mockClient) GetValues(ctx context.Context, ignoreNotFound bool) (client. func Test_engine_Render(t *testing.T) { type fields struct { - client client.Client - template *template.Template - option *opts + client client.Client + encodeAndDecoder encoding.EncodeAndDecoder + template *template.Template + option *opts } type args struct { ctx context.Context text string } + + createEncodeAndDecoder := func(docType encoding.TextType) encoding.EncodeAndDecoder { + encAndDec, _ := encoding.NewEncodeAndDecoder(docType) + return encAndDec + } + tests := []struct { name string fields fields @@ -83,8 +91,9 @@ func Test_engine_Render(t *testing.T) { client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return map[string]any{"key": "value"}, nil }), - template: template.New("test"), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test"), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -98,8 +107,9 @@ func Test_engine_Render(t *testing.T) { client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return map[string]any{"Values": map[string]any{"key": "value"}}, nil }), - template: template.New("test"), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test"), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -113,8 +123,9 @@ func Test_engine_Render(t *testing.T) { client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return map[string]any{"key": "value"}, nil }), - template: template.New("test").Funcs(funcMap()), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test").Funcs(funcMap()), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -122,14 +133,31 @@ func Test_engine_Render(t *testing.T) { }, wantDest: "VALUE", }, + { + name: "ok: encode and decode as yaml-docs after rendering", + fields: fields{ + client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { + return map[string]any{"key": "value"}, nil + }), + encodeAndDecoder: createEncodeAndDecoder(encoding.TextTypeYAMLDocs), + template: template.New("test"), + option: &opts{}, + }, + args: args{ + ctx: context.Background(), + text: "---\nkey: {{ .key }}\n---\n# comment\nkey2: value2\n", + }, + wantDest: "---\nkey: value\n---\nkey2: value2\n", + }, { name: "error: failed to get values", fields: fields{ client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return nil, client.ErrValueIsEmpty }), - template: template.New("test"), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test"), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -143,8 +171,9 @@ func Test_engine_Render(t *testing.T) { client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return map[string]any{"key": "value"}, nil }), - template: template.New("test"), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test"), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -158,8 +187,9 @@ func Test_engine_Render(t *testing.T) { client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { return map[string]any{"key": "value"}, nil }), - template: template.New("test").Option("missingkey=error"), - option: &opts{}, + encodeAndDecoder: &noOpEncodeAndDecoder{}, + template: template.New("test").Option("missingkey=error"), + option: &opts{}, }, args: args{ ctx: context.Background(), @@ -167,13 +197,30 @@ func Test_engine_Render(t *testing.T) { }, wantErr: true, }, + { + name: "error: failed to encode and decode", + fields: fields{ + client: mockClient(func(ctx context.Context, ignoreNotFound bool) (client.Values, error) { + return map[string]any{"key": "value"}, nil + }), + encodeAndDecoder: createEncodeAndDecoder(encoding.TextTypeJSON), + template: template.New("test"), + option: &opts{}, + }, + args: args{ + ctx: context.Background(), + text: "{{ .key }}", + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := engine{ - client: tt.fields.client, - template: tt.fields.template, - option: tt.fields.option, + client: tt.fields.client, + encodeAndDecoder: tt.fields.encodeAndDecoder, + template: tt.fields.template, + option: tt.fields.option, } dest := &bytes.Buffer{} if err := e.Render(tt.args.ctx, tt.args.text, dest); (err != nil) != tt.wantErr { diff --git a/pkg/engine/options.go b/pkg/engine/options.go index b0bad40..90c207c 100644 --- a/pkg/engine/options.go +++ b/pkg/engine/options.go @@ -16,9 +16,29 @@ package engine +import "github.com/dwango/yashiro/pkg/engine/encoding" + // Option is configurable Engine behavior. type Option func(*opts) +type TextTypeOpt = encoding.TextType + +const ( + TextTypePlane TextTypeOpt = "plane" + TextTypeJSON TextTypeOpt = encoding.TextTypeJSON + TextTypeJSONArray TextTypeOpt = encoding.TextTypeJSONArray + TextTypeYAML TextTypeOpt = encoding.TextTypeYAML + TextTypeYAMLArray TextTypeOpt = encoding.TextTypeYAMLArray + TextTypeYAMLDocs TextTypeOpt = encoding.TextTypeYAMLDocs +) + +// TextType sets the text type of rendered text. +func TextType(tto TextTypeOpt) Option { + return func(o *opts) { + o.TextType = tto + } +} + // IgnoreNotFound ignores values are not found in the external store. func IgnoreNotFound(b bool) Option { return func(o *opts) { @@ -28,8 +48,10 @@ func IgnoreNotFound(b bool) Option { type opts struct { IgnoreNotFound bool + TextType TextTypeOpt } var defaultOpts = &opts{ IgnoreNotFound: false, + TextType: TextTypePlane, }