From d0419ceedf87a0f794ac5a98decb716ea15af63b Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 1 Dec 2021 12:08:47 +1100 Subject: [PATCH] Added csv, tsv output formats --- examples/data1.yaml | 3 +-- pkg/yqlib/encoder_csv.go | 38 +++++++++++++++++++++++++++++++ pkg/yqlib/expression_tokeniser.go | 10 ++++++++ pkg/yqlib/printer.go | 20 ++++++++++++---- test.sh | 20 ++++++++++++++++ 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 pkg/yqlib/encoder_csv.go create mode 100755 test.sh diff --git a/examples/data1.yaml b/examples/data1.yaml index f504f397..59d92ac3 100644 --- a/examples/data1.yaml +++ b/examples/data1.yaml @@ -1,2 +1 @@ -a: - include: 'data2.yaml' +["a a", "b thing", 1, true] \ No newline at end of file diff --git a/pkg/yqlib/encoder_csv.go b/pkg/yqlib/encoder_csv.go new file mode 100644 index 00000000..bc5a4260 --- /dev/null +++ b/pkg/yqlib/encoder_csv.go @@ -0,0 +1,38 @@ +package yqlib + +import ( + "encoding/csv" + "fmt" + "io" + + yaml "gopkg.in/yaml.v3" +) + +type csvEncoder struct { + destination csv.Writer +} + +func NewCsvEncoder(destination io.Writer, separator rune) Encoder { + csvWriter := *csv.NewWriter(destination) + csvWriter.Comma = separator + return &csvEncoder{csvWriter} +} + +func (e *csvEncoder) Encode(originalNode *yaml.Node) error { + // node must be a sequence + node := unwrapDoc(originalNode) + if node.Kind != yaml.SequenceNode { + return fmt.Errorf("csv encoding only works for arrays of scalars (string/numbers/booleans), got: %v", node.Tag) + } + + stringValues := make([]string, len(node.Content)) + + for i, child := range node.Content { + + 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 e.destination.Write(stringValues) +} diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 7ff5dca8..6383efeb 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -325,11 +325,21 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`to_json\([0-9]+\)`), encodeWithIndent(JsonOutputFormat)) lexer.Add([]byte(`toyaml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: YamlOutputFormat, indent: 2})) + lexer.Add([]byte(`@yaml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: YamlOutputFormat, indent: 0})) + lexer.Add([]byte(`tojson`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: JsonOutputFormat, indent: 2})) lexer.Add([]byte(`toprops`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: PropsOutputFormat, indent: 2})) lexer.Add([]byte(`to_yaml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: YamlOutputFormat, indent: 2})) lexer.Add([]byte(`to_json`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: JsonOutputFormat, indent: 2})) + lexer.Add([]byte(`@json`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: JsonOutputFormat, indent: 0})) + + lexer.Add([]byte(`to_csv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: CsvOutputFormat})) + lexer.Add([]byte(`@csv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: CsvOutputFormat})) + + lexer.Add([]byte(`to_tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat})) + lexer.Add([]byte(`@tsv`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: TsvOutputFormat})) + lexer.Add([]byte(`to_props`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: PropsOutputFormat, indent: 2})) lexer.Add([]byte(`fromyaml`), opToken(decodeOpType)) diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index a264bd49..20446bbd 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -23,6 +23,8 @@ const ( YamlOutputFormat = 1 << iota JsonOutputFormat PropsOutputFormat + CsvOutputFormat + TsvOutputFormat ) func OutputFormatFromString(format string) (PrinterOutputFormat, error) { @@ -33,8 +35,12 @@ func OutputFormatFromString(format string) (PrinterOutputFormat, error) { return JsonOutputFormat, nil case "props", "p": return PropsOutputFormat, nil + case "csv", "c": + return CsvOutputFormat, nil + case "tsv", "t": + return TsvOutputFormat, nil default: - return 0, fmt.Errorf("Unknown fromat '%v' please use [yaml|json|props]", format) + return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv]", format) } } @@ -87,13 +93,19 @@ func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error { return writeString(writer, node.Value+"\n") } - if p.outputFormat == JsonOutputFormat { + switch p.outputFormat { + case JsonOutputFormat: encoder = NewJsonEncoder(writer, p.indent) - } else if p.outputFormat == PropsOutputFormat { + case PropsOutputFormat: encoder = NewPropertiesEncoder(writer) - } else { + case CsvOutputFormat: + encoder = NewCsvEncoder(writer, ',') + case TsvOutputFormat: + encoder = NewCsvEncoder(writer, '\t') + case YamlOutputFormat: encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled) } + return encoder.Encode(node) } diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..c4c1e3f5 --- /dev/null +++ b/test.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# load array into a bash array +# need to output each entry as a single line +# readarray identityMappings < <(./yq e -o=j -I=0 '.identitymappings[]' test.yml ) + +# for identityMapping in "${identityMappings[@]}"; do +# # identity mapping is a yaml snippet representing a single entry +# roleArn=$(echo "$identityMapping" | yq e '.arn' -) +# echo "roleArn: $roleArn" +# done + + + + +while IFS=$'\t' read -r roleArn group user _; do + echo "Role: $roleArn" + echo "Group: $group" + echo "User: $user" +done < <(yq -j read test.yaml \ + | jq -r '.identitymappings[] | [.arn, .group, .user] | @tsv') \ No newline at end of file