yq/pkg/yqlib/encoder_csv.go
2022-08-01 10:05:26 +10:00

127 lines
3.1 KiB
Go

package yqlib
import (
"encoding/csv"
"fmt"
"io"
yaml "gopkg.in/yaml.v3"
)
type csvEncoder struct {
separator rune
}
func NewCsvEncoder(separator rune) Encoder {
return &csvEncoder{separator: separator}
}
func (e *csvEncoder) CanHandleAliases() bool {
return false
}
func (e *csvEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (e *csvEncoder) PrintLeadingContent(writer io.Writer, content string) error {
return nil
}
func (e *csvEncoder) encodeRow(csvWriter *csv.Writer, contents []*yaml.Node) error {
stringValues := make([]string, len(contents))
for i, child := range contents {
if child.Kind != yaml.ScalarNode {
return fmt.Errorf("csv encoding only works for arrays of scalars (string/numbers/booleans), child[%v] is a %v", i, child.Tag)
}
stringValues[i] = child.Value
}
return csvWriter.Write(stringValues)
}
func (e *csvEncoder) encodeArrays(csvWriter *csv.Writer, content []*yaml.Node) error {
for i, child := range content {
if child.Kind != yaml.SequenceNode {
return fmt.Errorf("csv encoding only works for arrays of scalars (string/numbers/booleans), child[%v] is a %v", i, child.Tag)
}
err := e.encodeRow(csvWriter, child.Content)
if err != nil {
return err
}
}
return nil
}
func (e *csvEncoder) extractHeader(child *yaml.Node) ([]*yaml.Node, error) {
if child.Kind != yaml.MappingNode {
return nil, fmt.Errorf("csv object encoding only works for arrays of flat objects (string key => string/numbers/boolean value), child[0] is a %v", child.Tag)
}
mapKeys := getMapKeys(child)
return mapKeys.Content, nil
}
func (e *csvEncoder) createChildRow(child *yaml.Node, headers []*yaml.Node) []*yaml.Node {
childRow := make([]*yaml.Node, 0)
for _, header := range headers {
keyIndex := findKeyInMap(child, header)
value := createScalarNode(nil, "")
if keyIndex != -1 {
value = child.Content[keyIndex+1]
}
childRow = append(childRow, value)
}
return childRow
}
func (e *csvEncoder) encodeObjects(csvWriter *csv.Writer, content []*yaml.Node) error {
headers, err := e.extractHeader(content[0])
if err != nil {
return nil
}
err = e.encodeRow(csvWriter, headers)
if err != nil {
return nil
}
for i, child := range content {
if child.Kind != yaml.MappingNode {
return fmt.Errorf("csv object encoding only works for arrays of flat objects (string key => string/numbers/boolean value), child[%v] is a %v", i, child.Tag)
}
row := e.createChildRow(child, headers)
err = e.encodeRow(csvWriter, row)
if err != nil {
return err
}
}
return nil
}
func (e *csvEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
csvWriter := csv.NewWriter(writer)
csvWriter.Comma = e.separator
// node must be a sequence
node := unwrapDoc(originalNode)
if node.Kind != yaml.SequenceNode {
return fmt.Errorf("csv encoding only works for arrays, got: %v", node.Tag)
} else if len(node.Content) == 0 {
return nil
}
if node.Content[0].Kind == yaml.ScalarNode {
return e.encodeRow(csvWriter, node.Content)
}
if node.Content[0].Kind == yaml.MappingNode {
return e.encodeObjects(csvWriter, node.Content)
}
return e.encodeArrays(csvWriter, node.Content)
}