Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow build without json and xml support #1556

Merged
merged 3 commits into from Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmd/evaluate_all_command.go
Expand Up @@ -84,7 +84,10 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
if err != nil {
return err
}
encoder := configureEncoder(format)
encoder, err := configureEncoder()
if err != nil {
return err
}

printer := yqlib.NewPrinter(encoder, printerWriter)

Expand Down
5 changes: 4 additions & 1 deletion cmd/evalute_sequence_command.go
Expand Up @@ -93,7 +93,10 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
if err != nil {
return err
}
encoder := configureEncoder(format)
encoder, err := configureEncoder()
if err != nil {
return err
}

printer := yqlib.NewPrinter(encoder, printerWriter)

Expand Down
46 changes: 34 additions & 12 deletions cmd/utils.go
Expand Up @@ -61,7 +61,15 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
if err != nil {
return nil, err
}
switch yqlibInputFormat {
yqlibDecoder, err := createDecoder(yqlibInputFormat, evaluateTogether)
if yqlibDecoder == nil {
return nil, fmt.Errorf("no support for %s input format", inputFormat)
}
return yqlibDecoder, err
}

func createDecoder(format yqlib.InputFormat, evaluateTogether bool) (yqlib.Decoder, error) {
switch format {
case yqlib.XMLInputFormat:
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
case yqlib.PropertiesInputFormat:
Expand All @@ -72,10 +80,12 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
return yqlib.NewCSVObjectDecoder(','), nil
case yqlib.TSVObjectInputFormat:
return yqlib.NewCSVObjectDecoder('\t'), nil
case yqlib.YamlInputFormat:
prefs := yqlib.ConfiguredYamlPreferences
prefs.EvaluateTogether = evaluateTogether
return yqlib.NewYamlDecoder(prefs), nil
}
prefs := yqlib.ConfiguredYamlPreferences
prefs.EvaluateTogether = evaluateTogether
return yqlib.NewYamlDecoder(prefs), nil
return nil, fmt.Errorf("invalid decoder: %v", format)
}

func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
Expand All @@ -95,22 +105,34 @@ func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yq
return printerWriter, nil
}

func configureEncoder(format yqlib.PrinterOutputFormat) yqlib.Encoder {
func configureEncoder() (yqlib.Encoder, error) {
yqlibOutputFormat, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return nil, err
}
yqlibEncoder, err := createEncoder(yqlibOutputFormat)
if yqlibEncoder == nil {
return nil, fmt.Errorf("no support for %s output format", outputFormat)
}
return yqlibEncoder, err
}

func createEncoder(format yqlib.PrinterOutputFormat) (yqlib.Encoder, error) {
switch format {
case yqlib.JSONOutputFormat:
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar)
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar), nil
case yqlib.PropsOutputFormat:
return yqlib.NewPropertiesEncoder(unwrapScalar)
return yqlib.NewPropertiesEncoder(unwrapScalar), nil
case yqlib.CSVOutputFormat:
return yqlib.NewCsvEncoder(',')
return yqlib.NewCsvEncoder(','), nil
case yqlib.TSVOutputFormat:
return yqlib.NewCsvEncoder('\t')
return yqlib.NewCsvEncoder('\t'), nil
case yqlib.YamlOutputFormat:
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences)
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences), nil
case yqlib.XMLOutputFormat:
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences)
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences), nil
}
panic("invalid encoder")
return nil, fmt.Errorf("invalid encoder: %v", format)
}

// this is a hack to enable backwards compatibility with githubactions (which pipe /dev/null into everything)
Expand Down
2 changes: 2 additions & 0 deletions pkg/yqlib/decoder_json.go
@@ -1,3 +1,5 @@
//go:build !yq_nojson

package yqlib

import (
Expand Down
2 changes: 2 additions & 0 deletions pkg/yqlib/decoder_xml.go
@@ -1,3 +1,5 @@
//go:build !yq_noxml

package yqlib

import (
Expand Down
163 changes: 7 additions & 156 deletions pkg/yqlib/encoder.go
@@ -1,10 +1,6 @@
package yqlib

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"

yaml "gopkg.in/yaml.v3"
Expand All @@ -17,162 +13,17 @@ type Encoder interface {
CanHandleAliases() bool
}

// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
// order of keys and values in a map or an object.
type orderedMap struct {
// if this is an object, kv != nil. If this is not an object, kv == nil.
kv []orderedMapKV
altVal interface{}
}

type orderedMapKV struct {
K string
V orderedMap
}

func (o *orderedMap) UnmarshalJSON(data []byte) error {
switch data[0] {
case '{':
// initialise so that even if the object is empty it is not nil
o.kv = []orderedMapKV{}

// create decoder
dec := json.NewDecoder(bytes.NewReader(data))
_, err := dec.Token() // open object
if err != nil {
return err
}

// cycle through k/v
var tok json.Token
for tok, err = dec.Token(); err == nil; tok, err = dec.Token() {
// we can expect two types: string or Delim. Delim automatically means
// that it is the closing bracket of the object, whereas string means
// that there is another key.
if _, ok := tok.(json.Delim); ok {
break
}
kv := orderedMapKV{
K: tok.(string),
}
if err := dec.Decode(&kv.V); err != nil {
return err
}
o.kv = append(o.kv, kv)
}
// unexpected error
if err != nil && !errors.Is(err, io.EOF) {
return err
}
return nil
case '[':
var res []*orderedMap
if err := json.Unmarshal(data, &res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
}

return json.Unmarshal(data, &o.altVal)
}
func mapKeysToStrings(node *yaml.Node) {

func (o orderedMap) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
if o.kv == nil {
if err := enc.Encode(o.altVal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
buf.WriteByte('{')
for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil {
return nil, err
}
buf.WriteByte(':')
if err := enc.Encode(el.V); err != nil {
return nil, err
}
if idx != len(o.kv)-1 {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}

func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.DocumentNode:
if len(node.Content) == 0 {
return nil
}
return o.UnmarshalYAML(node.Content[0])
case yaml.AliasNode:
return o.UnmarshalYAML(node.Alias)
case yaml.ScalarNode:
return node.Decode(&o.altVal)
case yaml.MappingNode:
// set kv to non-nil
o.kv = []orderedMapKV{}
for i := 0; i < len(node.Content); i += 2 {
var key string
var val orderedMap
if err := node.Content[i].Decode(&key); err != nil {
return err
if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
if err := node.Content[i+1].Decode(&val); err != nil {
return err
}
o.kv = append(o.kv, orderedMapKV{
K: key,
V: val,
})
}
return nil
case yaml.SequenceNode:
// note that this has to be a pointer, so that nulls can be represented.
var res []*orderedMap
if err := node.Decode(&res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
case 0:
// null
o.kv = nil
o.altVal = nil
return nil
default:
return fmt.Errorf("orderedMap: invalid yaml node")
}
}

func (o *orderedMap) MarshalYAML() (interface{}, error) {
// fast path: kv is nil, use altVal
if o.kv == nil {
return o.altVal, nil
}
content := make([]*yaml.Node, 0, len(o.kv)*2)
for _, val := range o.kv {
n := new(yaml.Node)
if err := n.Encode(val.V); err != nil {
return nil, err
}
content = append(content, &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: val.K,
}, n)
for _, child := range node.Content {
mapKeysToStrings(child)
}
return &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Content: content,
}, nil
}
17 changes: 2 additions & 15 deletions pkg/yqlib/encoder_json.go
@@ -1,3 +1,5 @@
//go:build !yq_nojson

package yqlib

import (
Expand All @@ -14,21 +16,6 @@ type jsonEncoder struct {
UnwrapScalar bool
}

func mapKeysToStrings(node *yaml.Node) {

if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
}
}

for _, child := range node.Content {
mapKeysToStrings(child)
}
}

func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
var indentString = ""

Expand Down
2 changes: 2 additions & 0 deletions pkg/yqlib/encoder_test.go
@@ -1,3 +1,5 @@
//go:build !yq_nojson

package yqlib

import (
Expand Down
2 changes: 2 additions & 0 deletions pkg/yqlib/encoder_xml.go
@@ -1,3 +1,5 @@
//go:build !yq_noxml

package yqlib

import (
Expand Down
2 changes: 2 additions & 0 deletions pkg/yqlib/json_test.go
@@ -1,3 +1,5 @@
//go:build !yq_nojson

package yqlib

import (
Expand Down
11 changes: 11 additions & 0 deletions pkg/yqlib/no_json.go
@@ -0,0 +1,11 @@
//go:build yq_nojson

package yqlib

func NewJSONDecoder() Decoder {
return nil
}

func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
return nil
}
11 changes: 11 additions & 0 deletions pkg/yqlib/no_xml.go
@@ -0,0 +1,11 @@
//go:build yq_noxml

package yqlib

func NewXMLDecoder(prefs XmlPreferences) Decoder {
return nil
}

func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
return nil
}