1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package jsonutil
import (
"encoding/json"
"errors"
"fmt"
"io"
"github.com/BurntSushi/toml"
"github.com/ethereum-optimism/optimism/op-service/ioutil"
)
type Decoder interface {
Decode(v interface{}) error
}
type DecoderFactory func(r io.Reader) Decoder
type Encoder interface {
Encode(v interface{}) error
}
type EncoderFactory func(w io.Writer) Encoder
type jsonDecoder struct {
d *json.Decoder
}
func newJSONDecoder(r io.Reader) Decoder {
return &jsonDecoder{
d: json.NewDecoder(r),
}
}
func (d *jsonDecoder) Decode(v interface{}) error {
if err := d.d.Decode(v); err != nil {
return fmt.Errorf("failed to decode JSON: %w", err)
}
if _, err := d.d.Token(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}
type tomlDecoder struct {
r io.Reader
}
func newTOMLDecoder(r io.Reader) Decoder {
return &tomlDecoder{
r: r,
}
}
func (d *tomlDecoder) Decode(v interface{}) error {
if _, err := toml.NewDecoder(d.r).Decode(v); err != nil {
return fmt.Errorf("failed to decode TOML: %w", err)
}
return nil
}
type jsonEncoder struct {
e *json.Encoder
}
func newJSONEncoder(w io.Writer) Encoder {
e := json.NewEncoder(w)
e.SetIndent("", " ")
e.SetEscapeHTML(false)
return &jsonEncoder{
e: e,
}
}
func (e *jsonEncoder) Encode(v interface{}) error {
if err := e.e.Encode(v); err != nil {
return fmt.Errorf("failed to encode JSON: %w", err)
}
return nil
}
type tomlEncoder struct {
w io.Writer
}
func newTOMLEncoder(w io.Writer) Encoder {
return &tomlEncoder{
w: w,
}
}
func (e *tomlEncoder) Encode(v interface{}) error {
if err := toml.NewEncoder(e.w).Encode(v); err != nil {
return fmt.Errorf("failed to encode TOML: %w", err)
}
return nil
}
func LoadJSON[X any](inputPath string) (*X, error) {
return load[X](inputPath, newJSONDecoder)
}
func LoadTOML[X any](inputPath string) (*X, error) {
return load[X](inputPath, newTOMLDecoder)
}
func load[X any](inputPath string, dec DecoderFactory) (*X, error) {
if inputPath == "" {
return nil, errors.New("no path specified")
}
var f io.ReadCloser
f, err := ioutil.OpenDecompressed(inputPath)
if err != nil {
return nil, fmt.Errorf("failed to open file %q: %w", inputPath, err)
}
defer f.Close()
var state X
if err := dec(f).Decode(&state); err != nil {
return nil, fmt.Errorf("failed to decode file %q: %w", inputPath, err)
}
return &state, nil
}
func WriteJSON[X any](value X, target ioutil.OutputTarget) error {
return write(value, target, newJSONEncoder)
}
func WriteTOML[X any](value X, target ioutil.OutputTarget) error {
return write(value, target, newTOMLEncoder)
}
func write[X any](value X, target ioutil.OutputTarget, enc EncoderFactory) error {
out, closer, abort, err := target()
if err != nil {
return err
}
if out == nil {
return nil // No output stream selected so skip generating the content entirely
}
defer abort()
if err := enc(out).Encode(value); err != nil {
return fmt.Errorf("failed to encode: %w", err)
}
_, err = out.Write([]byte{'\n'})
if err != nil {
return fmt.Errorf("failed to append new-line: %w", err)
}
if err := closer.Close(); err != nil {
return fmt.Errorf("failed to finish write: %w", err)
}
return nil
}