Skip to content

Commit d9e37da

Browse files
authored
Implement text type option (#77)
* implement text type option * fix example
1 parent 434deac commit d9e37da

File tree

11 files changed

+694
-26
lines changed

11 files changed

+694
-26
lines changed

example/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ $ aws ssm put-parameter --name '/yashiro/example/secure' --value 'password' --ty
2828

2929
```sh
3030
$ ysr template -c ./yashiro.yaml example.yaml.tmpl
31+
# This is a example of yashiro template.
3132
---
3233
apiVersion: v1
3334
kind: Secret

example/example.yaml.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# This is a example of yashiro template.
12
---
23
apiVersion: v1
34
kind: Secret

internal/cmd/template.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"os"
2222
"path/filepath"
23+
"strings"
2324

2425
"github.com/dwango/yashiro/pkg/config"
2526
"github.com/dwango/yashiro/pkg/engine"
@@ -36,9 +37,19 @@ const example = ` # specify single file.
3637
ysr template ./example/*.tmpl
3738
`
3839

40+
var textTypeValues = []string{
41+
string(engine.TextTypePlane),
42+
string(engine.TextTypeJSON),
43+
string(engine.TextTypeJSONArray),
44+
string(engine.TextTypeYAML),
45+
string(engine.TextTypeYAMLArray),
46+
string(engine.TextTypeYAMLDocs),
47+
}
48+
3949
func newTemplateCommand() *cobra.Command {
4050
var configFile string
4151
var ignoreNotFound bool
52+
var textType string
4253

4354
cmd := cobra.Command{
4455
Use: "template <file>",
@@ -55,7 +66,9 @@ func newTemplateCommand() *cobra.Command {
5566
return err
5667
}
5768

58-
eng, err := engine.New(cfg, engine.IgnoreNotFound(ignoreNotFound))
69+
eng, err := engine.New(cfg,
70+
engine.IgnoreNotFound(ignoreNotFound), engine.TextType(engine.TextTypeOpt(textType)),
71+
)
5972
if err != nil {
6073
return err
6174
}
@@ -71,6 +84,9 @@ func newTemplateCommand() *cobra.Command {
7184

7285
f := cmd.Flags()
7386
f.StringVarP(&configFile, "config", "c", config.DefaultConfigFilename, "specify config file.")
87+
f.StringVar(&textType, "text-type", string(engine.TextTypePlane),
88+
fmt.Sprintf("specify text type after rendering. available values: %s", strings.Join(textTypeValues, ", ")),
89+
)
7490
f.BoolVar(&ignoreNotFound, "ignore-not-found", false, "ignore values are not found in the external store.")
7591

7692
return &cmd

pkg/engine/encoding/encoding.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright 2024 DWANGO Co., Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package encoding
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
)
22+
23+
type TextType string
24+
25+
// Define text types
26+
const (
27+
TextTypeJSON TextType = "json"
28+
TextTypeJSONArray TextType = "json-array"
29+
TextTypeYAML TextType = "yaml"
30+
TextTypeYAMLArray TextType = "yaml-array"
31+
TextTypeYAMLDocs TextType = "yaml-docs"
32+
)
33+
34+
// Define errors
35+
var (
36+
ErrUnsupportedTextType = errors.New("unsupported text type")
37+
ErrFailedToEncodeAndDecode = errors.New("failed to encode and decode")
38+
)
39+
40+
// EncodeAndDecoder is an interface that provides encoding and decoding functionality.
41+
type EncodeAndDecoder interface {
42+
EncodeAndDecode(b []byte) ([]byte, error)
43+
}
44+
45+
func NewEncodeAndDecoder(t TextType) (EncodeAndDecoder, error) {
46+
switch t {
47+
case TextTypeJSON:
48+
return &jsonEncodeAndDecoder{}, nil
49+
case TextTypeJSONArray:
50+
return &jsonEncodeAndDecoder{isArray: true}, nil
51+
case TextTypeYAML:
52+
return &yamlEncodeAndDecoder{docType: yamlDocTypeSingle}, nil
53+
case TextTypeYAMLArray:
54+
return &yamlEncodeAndDecoder{docType: yamlDocTypeArray}, nil
55+
case TextTypeYAMLDocs:
56+
return &yamlEncodeAndDecoder{docType: yamlDocTypeMulti}, nil
57+
default:
58+
return nil, fmt.Errorf("%w: %s", ErrUnsupportedTextType, t)
59+
}
60+
}

pkg/engine/encoding/json.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright 2024 DWANGO Co., Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package encoding
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
)
22+
23+
type jsonEncodeAndDecoder struct {
24+
isArray bool
25+
}
26+
27+
func (ed jsonEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) {
28+
if ed.isArray {
29+
v := []any{}
30+
if err := json.Unmarshal(b, &v); err != nil {
31+
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
32+
}
33+
return json.Marshal(v)
34+
}
35+
36+
v := map[string]any{}
37+
if err := json.Unmarshal(b, &v); err != nil {
38+
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
39+
}
40+
41+
return json.Marshal(v)
42+
}

pkg/engine/encoding/json_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright 2024 DWANGO Co., Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package encoding
17+
18+
import (
19+
"reflect"
20+
"testing"
21+
)
22+
23+
func Test_jsonEncodeAndDecoder_EncodeAndDecode(t *testing.T) {
24+
type fields struct {
25+
isArray bool
26+
}
27+
type args struct {
28+
str string
29+
}
30+
tests := []struct {
31+
name string
32+
fields fields
33+
args args
34+
wantStr string
35+
wantErr bool
36+
}{
37+
{
38+
name: "ok",
39+
fields: fields{
40+
isArray: false,
41+
},
42+
args: args{
43+
str: `{"key":"value"}`,
44+
},
45+
wantStr: `{"key":"value"}`,
46+
},
47+
{
48+
name: "ok: array",
49+
fields: fields{
50+
isArray: true,
51+
},
52+
args: args{
53+
str: `[{"key":"value"},{"key2":"value2"}]`,
54+
},
55+
wantStr: `[{"key":"value"},{"key2":"value2"}]`,
56+
},
57+
{
58+
name: "error: invalid json",
59+
fields: fields{
60+
isArray: false,
61+
},
62+
args: args{
63+
str: "invalid json",
64+
},
65+
wantErr: true,
66+
},
67+
{
68+
name: "error: invalid json array",
69+
fields: fields{
70+
isArray: true,
71+
},
72+
args: args{
73+
str: "invalid json",
74+
},
75+
wantErr: true,
76+
},
77+
}
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
ed := jsonEncodeAndDecoder{
81+
isArray: tt.fields.isArray,
82+
}
83+
got, err := ed.EncodeAndDecode([]byte(tt.args.str))
84+
if (err != nil) != tt.wantErr {
85+
t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() error = %v, wantErr %v", err, tt.wantErr)
86+
return
87+
}
88+
if !reflect.DeepEqual(string(got), tt.wantStr) {
89+
t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() = %v, want %v", string(got), tt.wantStr)
90+
}
91+
})
92+
}
93+
}

pkg/engine/encoding/yaml.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright 2024 DWANGO Co., Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package encoding
17+
18+
import (
19+
"bufio"
20+
"bytes"
21+
"fmt"
22+
23+
"sigs.k8s.io/yaml"
24+
)
25+
26+
type yamlDocType int
27+
28+
const (
29+
yamlDocTypeSingle yamlDocType = iota
30+
yamlDocTypeArray
31+
yamlDocTypeMulti
32+
)
33+
34+
type yamlEncodeAndDecoder struct {
35+
docType yamlDocType
36+
}
37+
38+
const (
39+
yamlSeparator = "\n---"
40+
separator = "---\n"
41+
)
42+
43+
func (ed yamlEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) {
44+
switch ed.docType {
45+
case yamlDocTypeMulti:
46+
buf := bytes.NewBuffer(make([]byte, 0, len(b)))
47+
48+
scn := bufio.NewScanner(bytes.NewReader(b))
49+
scn.Split(splitYAMLDocument)
50+
for scn.Scan() {
51+
v := map[string]any{}
52+
if err := yaml.Unmarshal(scn.Bytes(), &v); err != nil {
53+
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
54+
}
55+
if len(v) == 0 {
56+
continue
57+
}
58+
b, _ := yaml.Marshal(v)
59+
buf.WriteString(separator)
60+
buf.Write(b)
61+
}
62+
return buf.Bytes(), nil
63+
case yamlDocTypeArray:
64+
v := []any{}
65+
if err := yaml.Unmarshal(b, &v); err != nil {
66+
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
67+
}
68+
return yaml.Marshal(v)
69+
default:
70+
v := map[string]any{}
71+
if err := yaml.Unmarshal(b, &v); err != nil {
72+
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
73+
}
74+
return yaml.Marshal(v)
75+
}
76+
}
77+
78+
// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents.
79+
func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {
80+
if atEOF && len(data) == 0 {
81+
return 0, nil, nil
82+
}
83+
sep := len([]byte(yamlSeparator))
84+
if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {
85+
// We have a potential document terminator
86+
i += sep
87+
after := data[i:]
88+
if len(after) == 0 {
89+
// we can't read any more characters
90+
if atEOF {
91+
return len(data), data[:len(data)-sep], nil
92+
}
93+
return 0, nil, nil
94+
}
95+
if j := bytes.IndexByte(after, '\n'); j >= 0 {
96+
return i + j + 1, data[0 : i-sep], nil
97+
}
98+
return 0, nil, nil
99+
}
100+
// If we're at EOF, we have a final, non-terminated line. Return it.
101+
if atEOF {
102+
return len(data), data, nil
103+
}
104+
// Request more data.
105+
return 0, nil, nil
106+
}

0 commit comments

Comments
 (0)