diff --git a/pkg/yqlib/candidate_node.go b/pkg/yqlib/candidate_node.go index 41bac01d..f1e092bb 100644 --- a/pkg/yqlib/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -1,6 +1,7 @@ package yqlib import ( + "container/list" "fmt" "github.com/jinzhu/copier" @@ -28,6 +29,12 @@ func (n *CandidateNode) GetKey() string { return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path) } +func (n *CandidateNode) AsList() *list.List { + elMap := list.New() + elMap.PushBack(n) + return elMap +} + func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode { return &CandidateNode{ Node: node, diff --git a/pkg/yqlib/doc/Encoder and Decoder.md b/pkg/yqlib/doc/Encoder and Decoder.md index dbc695f1..3cc179cc 100644 --- a/pkg/yqlib/doc/Encoder and Decoder.md +++ b/pkg/yqlib/doc/Encoder and Decoder.md @@ -92,11 +92,12 @@ b: foo: bar ``` -## Update an encoded yaml string +## Update a multiline encoded yaml string Given a sample.yml file of: ```yaml a: | foo: bar + baz: dog ``` then ```bash @@ -106,5 +107,20 @@ will output ```yaml a: | foo: cat + baz: dog +``` + +## Update a single line encoded yaml string +Given a sample.yml file of: +```yaml +a: 'foo: bar' +``` +then +```bash +yq eval '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml +``` +will output +```yaml +a: 'foo: cat' ``` diff --git a/pkg/yqlib/operator_encoder_decoder.go b/pkg/yqlib/operator_encoder_decoder.go index 2b6c53e4..629d0e45 100644 --- a/pkg/yqlib/operator_encoder_decoder.go +++ b/pkg/yqlib/operator_encoder_decoder.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "container/list" + "regexp" "strings" "gopkg.in/yaml.v3" @@ -12,9 +13,7 @@ import ( func yamlToString(candidate *CandidateNode, prefs encoderPreferences) (string, error) { var output bytes.Buffer printer := NewPrinter(bufio.NewWriter(&output), prefs.format, true, false, 2, true) - elMap := list.New() - elMap.PushBack(candidate) - err := printer.PrintResults(elMap) + err := printer.PrintResults(candidate.AsList()) return output.String(), err } @@ -27,13 +26,30 @@ type encoderPreferences struct { func encodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { preferences := expressionNode.Operation.Preferences.(encoderPreferences) var results = list.New() + + hasOnlyOneNewLine := regexp.MustCompile("[^\n].*\n$") + chomper := regexp.MustCompile("\n+$") + for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) stringValue, err := yamlToString(candidate, preferences) + if err != nil { return Context{}, err } + // remove trailing newlines if needed. + // check if we originally decoded this path, and the original thing had a single line. + originalList := context.GetVariable("decoded: " + candidate.GetKey()) + if originalList != nil && originalList.Len() > 0 && hasOnlyOneNewLine.MatchString(stringValue) { + + original := originalList.Front().Value.(*CandidateNode) + if unwrapDoc(original.Node).Style == yaml.SingleQuotedStyle || + unwrapDoc(original.Node).Style == yaml.DoubleQuotedStyle { + stringValue = chomper.ReplaceAllString(stringValue, "") + } + } + stringContentNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: stringValue} results.PushBack(candidate.CreateChild(nil, stringContentNode)) } @@ -46,6 +62,9 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre var results = list.New() for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { candidate := el.Value.(*CandidateNode) + + context.SetVariable("decoded: "+candidate.GetKey(), candidate.AsList()) + var dataBucket yaml.Node log.Debugf("got: [%v]", candidate.Node.Value) decoder := yaml.NewDecoder(strings.NewReader(unwrapDoc(candidate.Node).Value)) diff --git a/pkg/yqlib/operator_encoder_decoder_test.go b/pkg/yqlib/operator_encoder_decoder_test.go index 1252d12e..55bcbaad 100644 --- a/pkg/yqlib/operator_encoder_decoder_test.go +++ b/pkg/yqlib/operator_encoder_decoder_test.go @@ -51,12 +51,30 @@ var encoderDecoderOperatorScenarios = []expressionScenario{ }, }, { - description: "Update an encoded yaml string", + description: "Update a multiline encoded yaml string", dontFormatInputForDoc: true, - document: "a: |\n foo: bar", + document: "a: |\n foo: bar\n baz: dog", expression: `.a |= (from_yaml | .foo = "cat" | to_yaml)`, expected: []string{ - "D0, P[], (doc)::a: |\n foo: cat\n", + "D0, P[], (doc)::a: |\n foo: cat\n baz: dog\n", + }, + }, + { + description: "Update a single line encoded yaml string", + dontFormatInputForDoc: true, + document: "a: 'foo: bar'", + expression: `.a |= (from_yaml | .foo = "cat" | to_yaml)`, + expected: []string{ + "D0, P[], (doc)::a: 'foo: cat'\n", + }, + }, + { + skipDoc: true, + dontFormatInputForDoc: true, + document: "a: \"foo: bar\"", + expression: `.a |= (from_yaml | .foo = {"a": "frog"} | to_yaml)`, + expected: []string{ + "D0, P[], (doc)::a: \"foo:\\n a: frog\"\n", }, }, }