-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstream.go
More file actions
176 lines (143 loc) · 4.36 KB
/
stream.go
File metadata and controls
176 lines (143 loc) · 4.36 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package yamlstream
import (
"bytes"
"fmt"
"io"
"os"
"reflect"
"gopkg.in/yaml.v3"
)
var (
yamlDelimiter = []byte("---\n")
)
// Document is a single YAML document.
type Document map[string]interface{}
// Bytes will return the byte array representation of the YAML document.
// Note that this will include the YAML delimiter of '---' even if it was not
// originally included in order to allow the separation of multiple documents.
func (d *Document) Bytes() []byte {
b := &bytes.Buffer{}
data, err := yaml.Marshal(d)
if err != nil {
panic(err)
}
// The YAML library does not include a delimiter to separate a document
// so it is added in by yaml-stream to ensure separation between other
// documents that may be present.
b.Write(yamlDelimiter)
b.Write(data)
return b.Bytes()
}
// String returns the string representation of the YAML document.
// This is usually expected to be how the file looks to the human eye, unless
// there are no delimiters in place, in which case they will be added by the
// library.
func (d *Document) String() string {
return string(d.Bytes())
}
// Unmarshal will unmarshal the document into the type provided in the out value.
func (d *Document) Unmarshal(out interface{}) error {
err := yaml.Unmarshal(d.Bytes(), out)
if err != nil {
return err
}
return nil
}
// Stream represents a stream of YAML, delimited by `---`, although there
// is no requirement that this be the case, as a single Stream, i.e. a single
// file, is still valid.
type Stream struct {
// Stream represents a stream of YAML. It is composed of zero or more YAML
// documents. Conceptually, this is similar to an array of YAML documents,
// able to be accessed at a particular index.
Stream []Document
// Count is the number of YAML documents that are present in the provided
// stream.
// Note that this is not the number of delimiters present, but how many
// YAML files there would be if the file were to be split separately.
//
// For example:
//
// hello: 'world'
// ---
// library: 'yaml-stream'
//
// This is a Count of 2.
Count int
}
// Get will retrieve a YAML document at a provided index.
func (s *Stream) Get(index int) Document {
return s.Stream[index]
}
// GetUnmarshal will unmarshal the Document at the provided index into the type
// that is provided by the 'out' value.
func (s *Stream) GetUnmarshal(index int, out interface{}) error {
if reflect.ValueOf(out).Kind() != reflect.Pointer {
return fmt.Errorf("expected pointer (&) for 'to' value that was passed")
}
doc := s.Stream[index]
err := doc.Unmarshal(out)
if err != nil {
return err
}
return nil
}
// Bytes returns the given stream as a single byte array, this is effectively
// similar to a call to `io.ReadFull` or `os.ReadFile`.
// Note that because a Document is separated by a delimiter, if they are not
// included in the file provided to `Read`, they will be included when being
// called into a byte array.
func (s *Stream) Bytes() []byte {
b := &bytes.Buffer{}
for _, data := range s.Stream {
b.Write(data.Bytes())
}
return b.Bytes()
}
// String returns the given stream in its string representated format.
// It is expected to be the file as it looks to the user.
func (s *Stream) String() string {
return string(s.Bytes())
}
// Read takes a reader, such as a YAML file, that contains zero or more documents
// and collects it into a singlular Stream.
func (s *Stream) Read(r io.Reader) error {
stream := make([]Document, 0)
d := yaml.NewDecoder(r)
for {
var document Document
err := d.Decode(&document)
if err != nil {
if err == io.EOF {
break
}
}
stream = append(stream, document)
}
s.Stream = stream
s.Count = len(stream)
return nil
}
// ReadWithOpen is a convenience function which wraps the utility of Read alongside
// the opening of a file, allowing the caller to simply pass a path to a file.
func (s *Stream) ReadWithOpen(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
// The file can be closed since it is no longer required after being
// successfully read.
defer f.Close()
err = s.Read(f)
if err != nil {
return err
}
return nil
}
// New will return a default Stream with default values.
// There is no requirement to use this, as `&Stream{}` is
// effectively the same. It serves as the standard entrypoint
// to the library and creating the `Stream` type.
func New() *Stream {
return &Stream{}
}