blob: e4533eec0a538759528c8a627bec5bab3ba73764 [file] [log] [blame]
// Copyright Istio Authors
//
// 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
//
// http://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 kubeyaml
import (
"bufio"
"bytes"
"io"
"strings"
"unicode"
)
const (
yamlSeparator = "---\n"
separator = "---"
)
// Join the given yaml parts into a single multipart document.
func Join(parts ...[]byte) []byte {
var b bytes.Buffer
var lastIsNewLine bool
for _, p := range parts {
if len(p) == 0 {
continue
}
if b.Len() != 0 {
if !lastIsNewLine {
_, _ = b.WriteString("\n")
}
b.WriteString(yamlSeparator)
}
_, _ = b.Write(p)
s := string(p)
lastIsNewLine = s[len(s)-1] == '\n'
}
return b.Bytes()
}
// JoinString joins the given yaml parts into a single multipart document.
func JoinString(parts ...string) string {
var st strings.Builder
var lastIsNewLine bool
for _, p := range parts {
if len(p) == 0 {
continue
}
if st.Len() != 0 {
if !lastIsNewLine {
_, _ = st.WriteString("\n")
}
st.WriteString(yamlSeparator)
}
_, _ = st.WriteString(p)
lastIsNewLine = p[len(p)-1] == '\n'
}
return st.String()
}
type Reader interface {
Read() ([]byte, error)
}
// YAMLReader adapts from Kubernetes YAMLReader(apimachinery.k8s.io/pkg/util/yaml/decoder.go).
// It records the start line number of the chunk it reads each time.
type YAMLReader struct {
reader Reader
currLine int
}
func NewYAMLReader(r *bufio.Reader) *YAMLReader {
return &YAMLReader{
reader: &LineReader{reader: r},
currLine: 0,
}
}
// Read returns a full YAML document and its first line number.
func (r *YAMLReader) Read() ([]byte, int, error) {
var buffer bytes.Buffer
startLine := r.currLine + 1
foundStart := false
for {
r.currLine++
line, err := r.reader.Read()
if err != nil && err != io.EOF {
return nil, startLine, err
}
// detect beginning of the chunk
if !bytes.Equal(line, []byte("\n")) && !bytes.Equal(line, []byte(yamlSeparator)) && !foundStart {
startLine = r.currLine
foundStart = true
}
sep := len([]byte(separator))
if i := bytes.Index(line, []byte(separator)); i == 0 {
// We have a potential document terminator
i += sep
after := line[i:]
if len(strings.TrimRightFunc(string(after), unicode.IsSpace)) == 0 {
if buffer.Len() != 0 {
return buffer.Bytes(), startLine, nil
}
if err == io.EOF {
return nil, startLine, err
}
}
}
if err == io.EOF {
if buffer.Len() != 0 {
// If we're at EOF, we have a final, non-terminated line. Return it.
return buffer.Bytes(), startLine, nil
}
return nil, startLine, err
}
buffer.Write(line)
}
}
type LineReader struct {
reader *bufio.Reader
}
// Read returns a single line (with '\n' ended) from the underlying reader.
// An error is returned iff there is an error with the underlying reader.
func (r *LineReader) Read() ([]byte, error) {
var (
isPrefix = true
err error
line []byte
buffer bytes.Buffer
)
for isPrefix && err == nil {
line, isPrefix, err = r.reader.ReadLine()
buffer.Write(line)
}
buffer.WriteByte('\n')
return buffer.Bytes(), err
}