mirror of
https://github.com/mikefarah/yq.git
synced 2026-09-01 14:14:52 +08:00
Merge branch 'master' into go-yaml-v4
This commit is contained in:
@@ -152,8 +152,6 @@ func TestCandidateNodeAddKeyValueChild(t *testing.T) {
|
||||
key := CandidateNode{Value: "cool", IsMapKey: true}
|
||||
node := CandidateNode{}
|
||||
|
||||
// if we use a key in a new node as a value, it should no longer be marked as a key
|
||||
|
||||
_, keyIsValueNow := node.AddKeyValueChild(&CandidateNode{Value: "newKey"}, &key)
|
||||
|
||||
test.AssertResult(t, keyIsValueNow.IsMapKey, false)
|
||||
@@ -204,3 +202,193 @@ func TestConvertToNodeInfo(t *testing.T) {
|
||||
test.AssertResult(t, 2, childInfo.Line)
|
||||
test.AssertResult(t, 3, childInfo.Column)
|
||||
}
|
||||
|
||||
func TestCandidateNodeGetPath(t *testing.T) {
|
||||
// Test root node with no parent
|
||||
root := CandidateNode{Value: "root"}
|
||||
path := root.GetPath()
|
||||
test.AssertResult(t, 0, len(path))
|
||||
|
||||
// Test node with key
|
||||
key := createStringScalarNode("myKey")
|
||||
node := CandidateNode{Key: key, Value: "myValue"}
|
||||
path = node.GetPath()
|
||||
test.AssertResult(t, 1, len(path))
|
||||
test.AssertResult(t, "myKey", path[0])
|
||||
|
||||
// Test nested path
|
||||
parent := CandidateNode{}
|
||||
parentKey := createStringScalarNode("parent")
|
||||
parent.Key = parentKey
|
||||
node.Parent = &parent
|
||||
path = node.GetPath()
|
||||
test.AssertResult(t, 2, len(path))
|
||||
test.AssertResult(t, "parent", path[0])
|
||||
test.AssertResult(t, "myKey", path[1])
|
||||
}
|
||||
|
||||
func TestCandidateNodeGetNicePath(t *testing.T) {
|
||||
// Test simple key
|
||||
key := createStringScalarNode("simple")
|
||||
node := CandidateNode{Key: key}
|
||||
nicePath := node.GetNicePath()
|
||||
test.AssertResult(t, "simple", nicePath)
|
||||
|
||||
// Test array index
|
||||
arrayKey := createScalarNode(0, "0")
|
||||
arrayNode := CandidateNode{Key: arrayKey}
|
||||
nicePath = arrayNode.GetNicePath()
|
||||
test.AssertResult(t, "[0]", nicePath)
|
||||
|
||||
dotKey := createStringScalarNode("key.with.dots")
|
||||
dotNode := CandidateNode{Key: dotKey}
|
||||
nicePath = dotNode.GetNicePath()
|
||||
test.AssertResult(t, "key.with.dots", nicePath)
|
||||
|
||||
// Test nested path
|
||||
parentKey := createStringScalarNode("parent")
|
||||
parent := CandidateNode{Key: parentKey}
|
||||
childKey := createStringScalarNode("child")
|
||||
child := CandidateNode{Key: childKey, Parent: &parent}
|
||||
nicePath = child.GetNicePath()
|
||||
test.AssertResult(t, "parent.child", nicePath)
|
||||
}
|
||||
|
||||
func TestCandidateNodeFilterMapContentByKey(t *testing.T) {
|
||||
// Create a map with multiple key-value pairs
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
key3 := createStringScalarNode("key3")
|
||||
value3 := createStringScalarNode("value3")
|
||||
|
||||
mapNode := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key2, value2, key3, value3},
|
||||
}
|
||||
|
||||
// Filter by key predicate that matches key1 and key3
|
||||
filtered := mapNode.FilterMapContentByKey(func(key *CandidateNode) bool {
|
||||
return key.Value == "key1" || key.Value == "key3"
|
||||
})
|
||||
|
||||
// Should return key1, value1, key3, value3
|
||||
test.AssertResult(t, 4, len(filtered))
|
||||
test.AssertResult(t, "key1", filtered[0].Value)
|
||||
test.AssertResult(t, "value1", filtered[1].Value)
|
||||
test.AssertResult(t, "key3", filtered[2].Value)
|
||||
test.AssertResult(t, "value3", filtered[3].Value)
|
||||
}
|
||||
|
||||
func TestCandidateNodeVisitValues(t *testing.T) {
|
||||
// Test mapping node
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
|
||||
mapNode := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key2, value2},
|
||||
}
|
||||
|
||||
var visited []string
|
||||
err := mapNode.VisitValues(func(node *CandidateNode) error {
|
||||
visited = append(visited, node.Value)
|
||||
return nil
|
||||
})
|
||||
|
||||
test.AssertResult(t, nil, err)
|
||||
test.AssertResult(t, 2, len(visited))
|
||||
test.AssertResult(t, "value1", visited[0])
|
||||
test.AssertResult(t, "value2", visited[1])
|
||||
|
||||
// Test sequence node
|
||||
item1 := createStringScalarNode("item1")
|
||||
item2 := createStringScalarNode("item2")
|
||||
|
||||
seqNode := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{item1, item2},
|
||||
}
|
||||
|
||||
visited = []string{}
|
||||
err = seqNode.VisitValues(func(node *CandidateNode) error {
|
||||
visited = append(visited, node.Value)
|
||||
return nil
|
||||
})
|
||||
|
||||
test.AssertResult(t, nil, err)
|
||||
test.AssertResult(t, 2, len(visited))
|
||||
test.AssertResult(t, "item1", visited[0])
|
||||
test.AssertResult(t, "item2", visited[1])
|
||||
|
||||
// Test scalar node (should not visit anything)
|
||||
scalarNode := &CandidateNode{
|
||||
Kind: ScalarNode,
|
||||
Value: "scalar",
|
||||
}
|
||||
|
||||
visited = []string{}
|
||||
err = scalarNode.VisitValues(func(node *CandidateNode) error {
|
||||
visited = append(visited, node.Value)
|
||||
return nil
|
||||
})
|
||||
|
||||
test.AssertResult(t, nil, err)
|
||||
test.AssertResult(t, 0, len(visited))
|
||||
}
|
||||
|
||||
func TestCandidateNodeCanVisitValues(t *testing.T) {
|
||||
mapNode := &CandidateNode{Kind: MappingNode}
|
||||
seqNode := &CandidateNode{Kind: SequenceNode}
|
||||
scalarNode := &CandidateNode{Kind: ScalarNode}
|
||||
|
||||
test.AssertResult(t, true, mapNode.CanVisitValues())
|
||||
test.AssertResult(t, true, seqNode.CanVisitValues())
|
||||
test.AssertResult(t, false, scalarNode.CanVisitValues())
|
||||
}
|
||||
|
||||
func TestCandidateNodeAddChild(t *testing.T) {
|
||||
parent := &CandidateNode{Kind: SequenceNode}
|
||||
child := createStringScalarNode("child")
|
||||
|
||||
parent.AddChild(child)
|
||||
|
||||
test.AssertResult(t, 1, len(parent.Content))
|
||||
test.AssertResult(t, false, parent.Content[0].IsMapKey)
|
||||
test.AssertResult(t, "0", parent.Content[0].Key.Value)
|
||||
// Check that parent is set correctly
|
||||
if parent.Content[0].Parent != parent {
|
||||
t.Errorf("Expected parent to be set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateNodeAddChildren(t *testing.T) {
|
||||
// Test sequence node
|
||||
parent := &CandidateNode{Kind: SequenceNode}
|
||||
child1 := createStringScalarNode("child1")
|
||||
child2 := createStringScalarNode("child2")
|
||||
|
||||
parent.AddChildren([]*CandidateNode{child1, child2})
|
||||
|
||||
test.AssertResult(t, 2, len(parent.Content))
|
||||
test.AssertResult(t, "child1", parent.Content[0].Value)
|
||||
test.AssertResult(t, "child2", parent.Content[1].Value)
|
||||
|
||||
// Test mapping node
|
||||
mapParent := &CandidateNode{Kind: MappingNode}
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
|
||||
mapParent.AddChildren([]*CandidateNode{key1, value1, key2, value2})
|
||||
|
||||
test.AssertResult(t, 4, len(mapParent.Content))
|
||||
test.AssertResult(t, true, mapParent.Content[0].IsMapKey) // key1
|
||||
test.AssertResult(t, false, mapParent.Content[1].IsMapKey) // value1
|
||||
test.AssertResult(t, true, mapParent.Content[2].IsMapKey) // key2
|
||||
test.AssertResult(t, false, mapParent.Content[3].IsMapKey) // value2
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
//go:build linux
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestChangeOwner(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tempDir := t.TempDir()
|
||||
testFile := filepath.Join(tempDir, "testfile.txt")
|
||||
|
||||
// Create a test file
|
||||
err := os.WriteFile(testFile, []byte("test content"), 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
// Get file info
|
||||
info, err := os.Stat(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat test file: %v", err)
|
||||
}
|
||||
|
||||
// Create another temporary file to change ownership of
|
||||
tempFile, err := os.CreateTemp(tempDir, "chown_test_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
// Test changeOwner function
|
||||
err = changeOwner(info, tempFile)
|
||||
if err != nil {
|
||||
t.Errorf("changeOwner failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify that the function doesn't panic with valid input
|
||||
tempFile2, err := os.CreateTemp(tempDir, "chown_test2_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create second temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile2.Name())
|
||||
tempFile2.Close()
|
||||
|
||||
// Test with the second file
|
||||
err = changeOwner(info, tempFile2)
|
||||
if err != nil {
|
||||
t.Errorf("changeOwner failed on second file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeOwnerWithInvalidFileInfo(t *testing.T) {
|
||||
// Create a mock file info that doesn't have syscall.Stat_t
|
||||
mockInfo := &mockFileInfo{
|
||||
name: "mock",
|
||||
size: 0,
|
||||
mode: 0600,
|
||||
}
|
||||
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp(t.TempDir(), "chown_test_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
// Test changeOwner with mock file info (should not panic)
|
||||
err = changeOwner(mockInfo, tempFile)
|
||||
if err != nil {
|
||||
t.Errorf("changeOwner failed with mock file info: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeOwnerWithNonExistentFile(t *testing.T) {
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp(t.TempDir(), "chown_test_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
// Get file info
|
||||
info, err := os.Stat(tempFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat temp file: %v", err)
|
||||
}
|
||||
|
||||
// Remove the file
|
||||
os.Remove(tempFile.Name())
|
||||
|
||||
err = changeOwner(info, tempFile)
|
||||
// The function should not panic even if the file doesn't exist
|
||||
if err != nil {
|
||||
t.Logf("Expected error when changing owner of non-existent file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// mockFileInfo implements fs.FileInfo but doesn't have syscall.Stat_t
|
||||
type mockFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
}
|
||||
|
||||
func (m *mockFileInfo) Name() string { return m.name }
|
||||
func (m *mockFileInfo) Size() int64 { return m.size }
|
||||
func (m *mockFileInfo) Mode() os.FileMode { return m.mode }
|
||||
func (m *mockFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (m *mockFileInfo) IsDir() bool { return false }
|
||||
func (m *mockFileInfo) Sys() interface{} { return nil } // This will cause the type assertion to fail
|
||||
|
||||
func TestChangeOwnerWithSyscallStatT(t *testing.T) {
|
||||
// Create a temporary file
|
||||
tempFile, err := os.CreateTemp(t.TempDir(), "chown_test_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
// Get file info
|
||||
info, err := os.Stat(tempFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat temp file: %v", err)
|
||||
}
|
||||
|
||||
err = changeOwner(info, tempFile)
|
||||
if err != nil {
|
||||
t.Logf("changeOwner returned error (this might be expected in some environments): %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
attr color.Attribute
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "reset color",
|
||||
attr: color.Reset,
|
||||
expected: "\x1b[0m",
|
||||
},
|
||||
{
|
||||
name: "red color",
|
||||
attr: color.FgRed,
|
||||
expected: "\x1b[31m",
|
||||
},
|
||||
{
|
||||
name: "green color",
|
||||
attr: color.FgGreen,
|
||||
expected: "\x1b[32m",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := format(tt.attr)
|
||||
if result != tt.expected {
|
||||
t.Errorf("format(%d) = %q, want %q", tt.attr, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorizeAndPrint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
yamlBytes []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple yaml",
|
||||
yamlBytes: []byte("name: test\nage: 25\n"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "yaml with strings",
|
||||
yamlBytes: []byte("name: \"hello world\"\nactive: true\ncount: 42\n"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "yaml with anchors and aliases",
|
||||
yamlBytes: []byte("default: &default\n name: test\nuser: *default\n"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "yaml with comments",
|
||||
yamlBytes: []byte("# This is a comment\nname: test\n"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty yaml",
|
||||
yamlBytes: []byte(""),
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
err := colorizeAndPrint(tt.yamlBytes, &buf)
|
||||
|
||||
if tt.expectErr && err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if !tt.expectErr && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Check that output contains escape sequences (color codes)
|
||||
if !tt.expectErr && len(tt.yamlBytes) > 0 {
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "\x1b[") {
|
||||
t.Error("Expected output to contain color escape sequences")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorizeAndPrintWithDifferentYamlTypes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
yaml string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "boolean values",
|
||||
yaml: "active: true\ninactive: false\n",
|
||||
},
|
||||
{
|
||||
name: "numeric values",
|
||||
yaml: "integer: 42\nfloat: 3.14\nnegative: -10\n",
|
||||
},
|
||||
{
|
||||
name: "map keys",
|
||||
yaml: "user:\n name: john\n age: 30\n",
|
||||
},
|
||||
{
|
||||
name: "string values",
|
||||
yaml: "message: \"hello world\"\ndescription: 'single quotes'\n",
|
||||
},
|
||||
{
|
||||
name: "mixed types",
|
||||
yaml: "config:\n debug: true\n port: 8080\n host: \"localhost\"\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
err := colorizeAndPrint([]byte(tc.yaml), &buf)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify output contains color codes
|
||||
if !tc.expectErr {
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "\x1b[") {
|
||||
t.Error("Expected output to contain color escape sequences")
|
||||
}
|
||||
// Should end with newline
|
||||
if !strings.HasSuffix(output, "\n") {
|
||||
t.Error("Expected output to end with newline")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,6 @@ func TestDeeplyAssign_ErrorHandling(t *testing.T) {
|
||||
Value: "value",
|
||||
}
|
||||
|
||||
// Try to assign to a path on a scalar (should fail)
|
||||
path := []interface{}{"key"}
|
||||
err := navigator.DeeplyAssign(context, path, assignNode)
|
||||
|
||||
@@ -321,7 +320,6 @@ func TestDeeplyAssign_ErrorHandling(t *testing.T) {
|
||||
t.Logf("Actual error: %v", err)
|
||||
}
|
||||
|
||||
// This should fail because we can't assign to a scalar
|
||||
test.AssertResult(t, nil, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -270,10 +270,16 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
log.Debug("start element %v", se.Name.Local)
|
||||
elem.state = "started"
|
||||
// Build new a new current element and link it to its parent
|
||||
var label = se.Name.Local
|
||||
if dec.prefs.KeepNamespace {
|
||||
if se.Name.Space != "" {
|
||||
label = se.Name.Space + ":" + se.Name.Local
|
||||
}
|
||||
}
|
||||
elem = &element{
|
||||
parent: elem,
|
||||
n: &xmlNode{},
|
||||
label: se.Name.Local,
|
||||
label: label,
|
||||
}
|
||||
|
||||
// Extract attributes as children
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# First
|
||||
|
||||
Returns the first matching element in an array, or first matching value in a map.
|
||||
|
||||
Can be given an expression to match with, otherwise will just return the first.
|
||||
@@ -84,3 +84,23 @@ will output
|
||||
name='Miles O'"'"'Brien'
|
||||
```
|
||||
|
||||
## Encode shell variables: custom separator
|
||||
Use --shell-key-separator to specify a custom separator between keys. This is useful when the original keys contain underscores.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
my_app:
|
||||
db_config:
|
||||
host: localhost
|
||||
port: 5432
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=shell --shell-key-separator="__" sample.yml
|
||||
```
|
||||
will output
|
||||
```sh
|
||||
my_app__db_config__host=localhost
|
||||
my_app__db_config__port=5432
|
||||
```
|
||||
|
||||
|
||||
@@ -319,7 +319,10 @@ Defaults to true
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<xsi:item>foobar</xsi:item>
|
||||
</map>
|
||||
|
||||
```
|
||||
then
|
||||
@@ -329,13 +332,19 @@ yq --xml-keep-namespace=false '.' sample.xml
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<item>foobar</item>
|
||||
</map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<xsi:item>foobar</xsi:item>
|
||||
</map>
|
||||
```
|
||||
|
||||
## Parse xml: keep raw attribute namespace
|
||||
@@ -344,7 +353,10 @@ Defaults to true
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<xsi:item>foobar</xsi:item>
|
||||
</map>
|
||||
|
||||
```
|
||||
then
|
||||
@@ -354,13 +366,19 @@ yq --xml-raw-token=false '.' sample.xml
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
<some-namespace:map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url">
|
||||
<some-namespace:item foo="bar">baz</some-namespace:item>
|
||||
<some-instance:item>foobar</some-instance:item>
|
||||
</some-namespace:map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<xsi:item>foobar</xsi:item>
|
||||
</map>
|
||||
```
|
||||
|
||||
## Encode xml: simple
|
||||
|
||||
@@ -12,10 +12,13 @@ import (
|
||||
)
|
||||
|
||||
type shellVariablesEncoder struct {
|
||||
prefs ShellVariablesPreferences
|
||||
}
|
||||
|
||||
func NewShellVariablesEncoder() Encoder {
|
||||
return &shellVariablesEncoder{}
|
||||
return &shellVariablesEncoder{
|
||||
prefs: ConfiguredShellVariablesPreferences,
|
||||
}
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) CanHandleAliases() bool {
|
||||
@@ -58,7 +61,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
|
||||
return err
|
||||
case SequenceNode:
|
||||
for index, child := range node.Content {
|
||||
err := pe.doEncode(w, child, appendPath(path, index))
|
||||
err := pe.doEncode(w, child, pe.appendPath(path, index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -68,7 +71,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
key := node.Content[index]
|
||||
value := node.Content[index+1]
|
||||
err := pe.doEncode(w, value, appendPath(path, key.Value))
|
||||
err := pe.doEncode(w, value, pe.appendPath(path, key.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +84,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
|
||||
}
|
||||
}
|
||||
|
||||
func appendPath(cookedPath string, rawKey interface{}) string {
|
||||
func (pe *shellVariablesEncoder) appendPath(cookedPath string, rawKey interface{}) string {
|
||||
|
||||
// Shell variable names must match
|
||||
// [a-zA-Z_]+[a-zA-Z0-9_]*
|
||||
@@ -126,7 +129,7 @@ func appendPath(cookedPath string, rawKey interface{}) string {
|
||||
}
|
||||
return key
|
||||
}
|
||||
return cookedPath + "_" + key
|
||||
return cookedPath + pe.prefs.KeySeparator + key
|
||||
}
|
||||
|
||||
func quoteValue(value string) string {
|
||||
|
||||
@@ -91,3 +91,47 @@ func TestShellVariablesEncoderEmptyMap(t *testing.T) {
|
||||
func TestShellVariablesEncoderScalarNode(t *testing.T) {
|
||||
assertEncodesTo(t, "some string", "value='some string'")
|
||||
}
|
||||
|
||||
func assertEncodesToWithSeparator(t *testing.T, yaml string, shellvars string, separator string) {
|
||||
var output bytes.Buffer
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
// Save the original separator
|
||||
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
|
||||
defer func() {
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
|
||||
}()
|
||||
|
||||
// Set the custom separator
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = separator
|
||||
|
||||
var encoder = NewShellVariablesEncoder()
|
||||
inputs, err := readDocuments(strings.NewReader(yaml), "test.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
node := inputs.Front().Value.(*CandidateNode)
|
||||
err = encoder.Encode(writer, node)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
test.AssertResult(t, shellvars, strings.TrimSuffix(output.String(), "\n"))
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderCustomSeparator(t *testing.T) {
|
||||
assertEncodesToWithSeparator(t, "a:\n b: Lewis\n c: Carroll", "a__b=Lewis\na__c=Carroll", "__")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderCustomSeparatorNested(t *testing.T) {
|
||||
assertEncodesToWithSeparator(t, "my_app:\n db_config:\n host: localhost", "my_app__db_config__host=localhost", "__")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderCustomSeparatorArray(t *testing.T) {
|
||||
assertEncodesToWithSeparator(t, "a: [{n: Alice}, {n: Bob}]", "a__0__n=Alice\na__1__n=Bob", "__")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderCustomSeparatorSingleChar(t *testing.T) {
|
||||
assertEncodesToWithSeparator(t, "a:\n b: value", "aXb=value", "X")
|
||||
}
|
||||
|
||||
@@ -84,3 +84,42 @@ func TestParserExtraArgs(t *testing.T) {
|
||||
_, err := getExpressionParser().ParseExpression("sortKeys(.) explode(.)")
|
||||
test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error())
|
||||
}
|
||||
|
||||
func TestParserEmptyExpression(t *testing.T) {
|
||||
_, err := getExpressionParser().ParseExpression("")
|
||||
test.AssertResultComplex(t, nil, err)
|
||||
}
|
||||
|
||||
func TestParserSingleOperation(t *testing.T) {
|
||||
result, err := getExpressionParser().ParseExpression(".")
|
||||
test.AssertResultComplex(t, nil, err)
|
||||
if result == nil {
|
||||
t.Fatal("Expected non-nil result for single operation")
|
||||
}
|
||||
if result.Operation == nil {
|
||||
t.Fatal("Expected operation to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserFirstOpWithZeroArgs(t *testing.T) {
|
||||
// Test the special case where firstOpType can accept zero args
|
||||
result, err := getExpressionParser().ParseExpression("first")
|
||||
test.AssertResultComplex(t, nil, err)
|
||||
if result == nil {
|
||||
t.Fatal("Expected non-nil result for first operation with zero args")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserInvalidExpressionTree(t *testing.T) {
|
||||
// This tests the createExpressionTree function with malformed postfix
|
||||
parser := getExpressionParser().(*expressionParserImpl)
|
||||
|
||||
// Create invalid postfix operations that would leave more than one item on stack
|
||||
invalidOps := []*Operation{
|
||||
{OperationType: &operationType{NumArgs: 0}},
|
||||
{OperationType: &operationType{NumArgs: 0}},
|
||||
}
|
||||
|
||||
_, err := parser.createExpressionTree(invalidOps)
|
||||
test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
@@ -160,3 +161,250 @@ func TestParseInt64(t *testing.T) {
|
||||
test.AssertResultComplexWithContext(t, tt.expectedFormatString, fmt.Sprintf(format, actualNumber), fmt.Sprintf("Formatting of: %v", tt.numberString))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContentValueByKey(t *testing.T) {
|
||||
// Create content with key-value pairs
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
|
||||
content := []*CandidateNode{key1, value1, key2, value2}
|
||||
|
||||
// Test finding existing key
|
||||
result := getContentValueByKey(content, "key1")
|
||||
test.AssertResult(t, value1, result)
|
||||
|
||||
// Test finding another existing key
|
||||
result = getContentValueByKey(content, "key2")
|
||||
test.AssertResult(t, value2, result)
|
||||
|
||||
// Test finding non-existing key
|
||||
result = getContentValueByKey(content, "nonexistent")
|
||||
test.AssertResult(t, (*CandidateNode)(nil), result)
|
||||
|
||||
// Test with empty content
|
||||
result = getContentValueByKey([]*CandidateNode{}, "key1")
|
||||
test.AssertResult(t, (*CandidateNode)(nil), result)
|
||||
}
|
||||
|
||||
func TestRecurseNodeArrayEqual(t *testing.T) {
|
||||
// Create two arrays with same content
|
||||
array1 := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{
|
||||
createStringScalarNode("item1"),
|
||||
createStringScalarNode("item2"),
|
||||
},
|
||||
}
|
||||
|
||||
array2 := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{
|
||||
createStringScalarNode("item1"),
|
||||
createStringScalarNode("item2"),
|
||||
},
|
||||
}
|
||||
|
||||
array3 := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{
|
||||
createStringScalarNode("item1"),
|
||||
createStringScalarNode("different"),
|
||||
},
|
||||
}
|
||||
|
||||
array4 := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{
|
||||
createStringScalarNode("item1"),
|
||||
},
|
||||
}
|
||||
|
||||
test.AssertResult(t, true, recurseNodeArrayEqual(array1, array2))
|
||||
test.AssertResult(t, false, recurseNodeArrayEqual(array1, array3))
|
||||
test.AssertResult(t, false, recurseNodeArrayEqual(array1, array4))
|
||||
}
|
||||
|
||||
func TestFindInArray(t *testing.T) {
|
||||
item1 := createStringScalarNode("item1")
|
||||
item2 := createStringScalarNode("item2")
|
||||
item3 := createStringScalarNode("item3")
|
||||
|
||||
array := &CandidateNode{
|
||||
Kind: SequenceNode,
|
||||
Content: []*CandidateNode{item1, item2, item3},
|
||||
}
|
||||
|
||||
// Test finding existing items
|
||||
test.AssertResult(t, 0, findInArray(array, item1))
|
||||
test.AssertResult(t, 1, findInArray(array, item2))
|
||||
test.AssertResult(t, 2, findInArray(array, item3))
|
||||
|
||||
// Test finding non-existing item
|
||||
nonExistent := createStringScalarNode("nonexistent")
|
||||
test.AssertResult(t, -1, findInArray(array, nonExistent))
|
||||
}
|
||||
|
||||
func TestFindKeyInMap(t *testing.T) {
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
|
||||
mapNode := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key2, value2},
|
||||
}
|
||||
|
||||
// Test finding existing keys
|
||||
test.AssertResult(t, 0, findKeyInMap(mapNode, key1))
|
||||
test.AssertResult(t, 2, findKeyInMap(mapNode, key2))
|
||||
|
||||
// Test finding non-existing key
|
||||
nonExistent := createStringScalarNode("nonexistent")
|
||||
test.AssertResult(t, -1, findKeyInMap(mapNode, nonExistent))
|
||||
}
|
||||
|
||||
func TestRecurseNodeObjectEqual(t *testing.T) {
|
||||
// Create two objects with same content
|
||||
key1 := createStringScalarNode("key1")
|
||||
value1 := createStringScalarNode("value1")
|
||||
key2 := createStringScalarNode("key2")
|
||||
value2 := createStringScalarNode("value2")
|
||||
|
||||
obj1 := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key2, value2},
|
||||
}
|
||||
|
||||
obj2 := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key2, value2},
|
||||
}
|
||||
|
||||
// Create object with different values
|
||||
value3 := createStringScalarNode("value3")
|
||||
obj3 := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value3, key2, value2},
|
||||
}
|
||||
|
||||
// Create object with different keys
|
||||
key3 := createStringScalarNode("key3")
|
||||
obj4 := &CandidateNode{
|
||||
Kind: MappingNode,
|
||||
Content: []*CandidateNode{key1, value1, key3, value2},
|
||||
}
|
||||
|
||||
test.AssertResult(t, true, recurseNodeObjectEqual(obj1, obj2))
|
||||
test.AssertResult(t, false, recurseNodeObjectEqual(obj1, obj3))
|
||||
test.AssertResult(t, false, recurseNodeObjectEqual(obj1, obj4))
|
||||
}
|
||||
|
||||
func TestParseInt(t *testing.T) {
|
||||
type parseIntScenario struct {
|
||||
numberString string
|
||||
expectedParsedNumber int
|
||||
expectedError string
|
||||
}
|
||||
|
||||
scenarios := []parseIntScenario{
|
||||
{
|
||||
numberString: "34",
|
||||
expectedParsedNumber: 34,
|
||||
},
|
||||
{
|
||||
numberString: "10_000",
|
||||
expectedParsedNumber: 10000,
|
||||
},
|
||||
{
|
||||
numberString: "0x10",
|
||||
expectedParsedNumber: 16,
|
||||
},
|
||||
{
|
||||
numberString: "0o10",
|
||||
expectedParsedNumber: 8,
|
||||
},
|
||||
{
|
||||
numberString: "invalid",
|
||||
expectedError: "strconv.ParseInt",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range scenarios {
|
||||
actualNumber, err := parseInt(tt.numberString)
|
||||
if tt.expectedError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for '%s' but got none", tt.numberString)
|
||||
} else if !strings.Contains(err.Error(), tt.expectedError) {
|
||||
t.Errorf("Expected error containing '%s' for '%s', got '%s'", tt.expectedError, tt.numberString, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for '%s': %v", tt.numberString, err)
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, tt.expectedParsedNumber, actualNumber, tt.numberString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeadAndLineComment(t *testing.T) {
|
||||
node := &CandidateNode{
|
||||
HeadComment: "# head comment",
|
||||
LineComment: "# line comment",
|
||||
}
|
||||
|
||||
result := headAndLineComment(node)
|
||||
test.AssertResult(t, " head comment line comment", result)
|
||||
}
|
||||
|
||||
func TestHeadComment(t *testing.T) {
|
||||
node := &CandidateNode{
|
||||
HeadComment: "# head comment",
|
||||
}
|
||||
|
||||
result := headComment(node)
|
||||
test.AssertResult(t, " head comment", result)
|
||||
|
||||
// Test without #
|
||||
node.HeadComment = "no hash comment"
|
||||
result = headComment(node)
|
||||
test.AssertResult(t, "no hash comment", result)
|
||||
}
|
||||
|
||||
func TestLineComment(t *testing.T) {
|
||||
node := &CandidateNode{
|
||||
LineComment: "# line comment",
|
||||
}
|
||||
|
||||
result := lineComment(node)
|
||||
test.AssertResult(t, " line comment", result)
|
||||
|
||||
// Test without #
|
||||
node.LineComment = "no hash comment"
|
||||
result = lineComment(node)
|
||||
test.AssertResult(t, "no hash comment", result)
|
||||
}
|
||||
|
||||
func TestFootComment(t *testing.T) {
|
||||
node := &CandidateNode{
|
||||
FootComment: "# foot comment",
|
||||
}
|
||||
|
||||
result := footComment(node)
|
||||
test.AssertResult(t, " foot comment", result)
|
||||
|
||||
// Test without #
|
||||
node.FootComment = "no hash comment"
|
||||
result = footComment(node)
|
||||
test.AssertResult(t, "no hash comment", result)
|
||||
}
|
||||
|
||||
func TestKindString(t *testing.T) {
|
||||
test.AssertResult(t, "ScalarNode", KindString(ScalarNode))
|
||||
test.AssertResult(t, "SequenceNode", KindString(SequenceNode))
|
||||
test.AssertResult(t, "MappingNode", KindString(MappingNode))
|
||||
test.AssertResult(t, "AliasNode", KindString(AliasNode))
|
||||
test.AssertResult(t, "unknown!", KindString(Kind(999))) // Invalid kind
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ var firstOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "First matching element from array with multiple matches",
|
||||
document: "[{a: banana},{a: cat},{a: apple},{a: cat}]",
|
||||
document: "[{a: banana},{a: cat, b: firstCat},{a: apple},{a: cat, b: secondCat}]",
|
||||
expression: `first(.a == "cat")`,
|
||||
expected: []string{
|
||||
"D0, P[1], (!!map)::{a: cat}\n",
|
||||
"D0, P[1], (!!map)::{a: cat, b: firstCat}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "First matching element from array with numeric condition",
|
||||
document: "[{a: 10},{a: 100},{a: 1}]",
|
||||
document: "[{a: 10},{a: 100},{a: 1},{a: 101}]",
|
||||
expression: `first(.a > 50)`,
|
||||
expected: []string{
|
||||
"D0, P[1], (!!map)::{a: 100}\n",
|
||||
@@ -29,10 +29,10 @@ var firstOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "First matching element from array with boolean condition",
|
||||
document: "[{a: false},{a: true},{a: false}]",
|
||||
document: "[{a: false},{a: true, b: firstTrue},{a: false}, {a: true, b: secondTrue}]",
|
||||
expression: `first(.a == true)`,
|
||||
expected: []string{
|
||||
"D0, P[1], (!!map)::{a: true}\n",
|
||||
"D0, P[1], (!!map)::{a: true, b: firstTrue}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -45,10 +45,10 @@ var firstOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "First matching element from array with complex condition",
|
||||
document: "[{a: dog, b: 5},{a: cat, b: 3},{a: apple, b: 7}]",
|
||||
expression: `first(.b > 4)`,
|
||||
document: "[{a: dog, b: 7},{a: cat, b: 3},{a: apple, b: 5}]",
|
||||
expression: `first(.b > 4 and .b < 6)`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!map)::{a: dog, b: 5}\n",
|
||||
"D0, P[2], (!!map)::{a: apple, b: 5}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,7 +61,7 @@ var firstOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "First matching element from map with numeric condition",
|
||||
document: "x: {a: 10}\ny: {a: 100}\nz: {a: 1}",
|
||||
document: "x: {a: 10}\ny: {a: 100}\nz: {a: 101}",
|
||||
expression: `first(.a > 50)`,
|
||||
expected: []string{
|
||||
"D0, P[y], (!!map)::{a: 100}\n",
|
||||
|
||||
@@ -414,3 +414,100 @@ func TestPrinterRootUnwrap(t *testing.T) {
|
||||
`
|
||||
test.AssertResult(t, expected, output.String())
|
||||
}
|
||||
|
||||
func TestRemoveLastEOL(t *testing.T) {
|
||||
// Test with \r\n
|
||||
buffer := bytes.NewBufferString("test\r\n")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "test", buffer.String())
|
||||
|
||||
// Test with \n only
|
||||
buffer = bytes.NewBufferString("test\n")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "test", buffer.String())
|
||||
|
||||
// Test with \r only
|
||||
buffer = bytes.NewBufferString("test\r")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "test", buffer.String())
|
||||
|
||||
// Test with no EOL
|
||||
buffer = bytes.NewBufferString("test")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "test", buffer.String())
|
||||
|
||||
// Test with empty buffer
|
||||
buffer = bytes.NewBufferString("")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "", buffer.String())
|
||||
|
||||
// Test with multiple \r\n
|
||||
buffer = bytes.NewBufferString("line1\r\nline2\r\n")
|
||||
removeLastEOL(buffer)
|
||||
test.AssertResult(t, "line1\r\nline2", buffer.String())
|
||||
}
|
||||
|
||||
func TestPrinterPrintedAnything(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
printer := NewSimpleYamlPrinter(writer, true, 2, true)
|
||||
|
||||
test.AssertResult(t, false, printer.PrintedAnything())
|
||||
|
||||
// Print a scalar value
|
||||
node := createStringScalarNode("test")
|
||||
nodeList := nodeToList(node)
|
||||
err := printer.PrintResults(nodeList)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should now be true
|
||||
test.AssertResult(t, true, printer.PrintedAnything())
|
||||
}
|
||||
|
||||
func TestPrinterNulSeparatorWithNullChar(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
printer := NewSimpleYamlPrinter(writer, true, 2, false)
|
||||
printer.SetNulSepOutput(true)
|
||||
|
||||
// Create a node with null character
|
||||
node := createStringScalarNode("test\x00value")
|
||||
nodeList := nodeToList(node)
|
||||
|
||||
err := printer.PrintResults(nodeList)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for null character in NUL separated output")
|
||||
}
|
||||
|
||||
expectedError := "can't serialize value because it contains NUL char and you are using NUL separated output"
|
||||
if err.Error() != expectedError {
|
||||
t.Fatalf("Expected error '%s', got '%s'", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrinterSetNulSepOutput(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
printer := NewSimpleYamlPrinter(writer, true, 2, false)
|
||||
|
||||
// Test setting NUL separator output
|
||||
printer.SetNulSepOutput(true)
|
||||
test.AssertResult(t, true, true) // Placeholder assertion
|
||||
|
||||
printer.SetNulSepOutput(false)
|
||||
// Should also not cause errors
|
||||
test.AssertResult(t, false, false) // Placeholder assertion
|
||||
}
|
||||
|
||||
func TestPrinterSetAppendix(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
printer := NewSimpleYamlPrinter(writer, true, 2, true)
|
||||
|
||||
// Test setting appendix
|
||||
appendix := strings.NewReader("appendix content")
|
||||
printer.SetAppendix(appendix)
|
||||
test.AssertResult(t, true, true) // Placeholder assertion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package yqlib
|
||||
|
||||
type ShellVariablesPreferences struct {
|
||||
KeySeparator string
|
||||
}
|
||||
|
||||
func NewDefaultShellVariablesPreferences() ShellVariablesPreferences {
|
||||
return ShellVariablesPreferences{
|
||||
KeySeparator: "_",
|
||||
}
|
||||
}
|
||||
|
||||
var ConfiguredShellVariablesPreferences = NewDefaultShellVariablesPreferences()
|
||||
|
||||
@@ -54,12 +54,33 @@ var shellVariablesScenarios = []formatScenario{
|
||||
input: "name: Miles O'Brien",
|
||||
expected: `name='Miles O'"'"'Brien'` + "\n",
|
||||
},
|
||||
{
|
||||
description: "Encode shell variables: custom separator",
|
||||
subdescription: "Use --shell-key-separator to specify a custom separator between keys. This is useful when the original keys contain underscores.",
|
||||
input: "" +
|
||||
"my_app:" + "\n" +
|
||||
" db_config:" + "\n" +
|
||||
" host: localhost" + "\n" +
|
||||
" port: 5432",
|
||||
expected: "" +
|
||||
"my_app__db_config__host=localhost" + "\n" +
|
||||
"my_app__db_config__port=5432" + "\n",
|
||||
scenarioType: "shell-separator",
|
||||
},
|
||||
}
|
||||
|
||||
func TestShellVariableScenarios(t *testing.T) {
|
||||
for _, s := range shellVariablesScenarios {
|
||||
//fmt.Printf("\t<%s> <%s>\n", s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()))
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
|
||||
if s.scenarioType == "shell-separator" {
|
||||
// Save and restore the original separator
|
||||
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = "__"
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
|
||||
} else {
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
|
||||
}
|
||||
}
|
||||
genericScenarios := make([]interface{}, len(shellVariablesScenarios))
|
||||
for i, s := range shellVariablesScenarios {
|
||||
@@ -87,12 +108,22 @@ func documentShellVariableScenario(_ *testing.T, w *bufio.Writer, i interface{})
|
||||
|
||||
expression := s.expression
|
||||
|
||||
if expression != "" {
|
||||
if s.scenarioType == "shell-separator" {
|
||||
writeOrPanic(w, "```bash\nyq -o=shell --shell-key-separator=\"__\" sample.yml\n```\n")
|
||||
} else if expression != "" {
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=shell '%v' sample.yml\n```\n", expression))
|
||||
} else {
|
||||
writeOrPanic(w, "```bash\nyq -o=shell sample.yml\n```\n")
|
||||
}
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
|
||||
if s.scenarioType == "shell-separator" {
|
||||
// Save and restore the original separator
|
||||
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = "__"
|
||||
writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
|
||||
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
|
||||
} else {
|
||||
writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_CreateTempFile(t *testing.T) {
|
||||
// Create a temporary directory and file for testing
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "input.yaml")
|
||||
|
||||
// Create input file with some content
|
||||
content := []byte("test: value\n")
|
||||
err := os.WriteFile(inputFile, content, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create input file: %v", err)
|
||||
}
|
||||
|
||||
handler := NewWriteInPlaceHandler(inputFile)
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTempFile failed: %v", err)
|
||||
}
|
||||
|
||||
if tempFile == nil {
|
||||
t.Fatal("CreateTempFile returned nil file")
|
||||
}
|
||||
|
||||
// Clean up
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
}
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_CreateTempFile_NonExistentInput(t *testing.T) {
|
||||
// Test with non-existent input file
|
||||
handler := NewWriteInPlaceHandler("/non/existent/file.yaml")
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent input file, got nil")
|
||||
}
|
||||
|
||||
if tempFile != nil {
|
||||
t.Error("Expected nil temp file for non-existent input file")
|
||||
tempFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_FinishWriteInPlace_Success(t *testing.T) {
|
||||
// Create a temporary directory and file for testing
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "input.yaml")
|
||||
|
||||
// Create input file with some content
|
||||
content := []byte("test: value\n")
|
||||
err := os.WriteFile(inputFile, content, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create input file: %v", err)
|
||||
}
|
||||
|
||||
handler := NewWriteInPlaceHandler(inputFile)
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTempFile failed: %v", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Write some content to temp file
|
||||
tempContent := []byte("updated: content\n")
|
||||
_, err = tempFile.Write(tempContent)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Test successful finish
|
||||
err = handler.FinishWriteInPlace(true)
|
||||
if err != nil {
|
||||
t.Fatalf("FinishWriteInPlace failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the original file was updated
|
||||
updatedContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read updated file: %v", err)
|
||||
}
|
||||
|
||||
if string(updatedContent) != string(tempContent) {
|
||||
t.Errorf("File content not updated correctly. Expected %q, got %q",
|
||||
string(tempContent), string(updatedContent))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_FinishWriteInPlace_Failure(t *testing.T) {
|
||||
// Create a temporary directory and file for testing
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "input.yaml")
|
||||
|
||||
// Create input file with some content
|
||||
content := []byte("test: value\n")
|
||||
err := os.WriteFile(inputFile, content, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create input file: %v", err)
|
||||
}
|
||||
|
||||
handler := NewWriteInPlaceHandler(inputFile)
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTempFile failed: %v", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Write some content to temp file
|
||||
tempContent := []byte("updated: content\n")
|
||||
_, err = tempFile.Write(tempContent)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Test failure finish (should not update the original file)
|
||||
err = handler.FinishWriteInPlace(false)
|
||||
if err != nil {
|
||||
t.Fatalf("FinishWriteInPlace failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the original file was NOT updated
|
||||
originalContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read original file: %v", err)
|
||||
}
|
||||
|
||||
if string(originalContent) != string(content) {
|
||||
t.Errorf("File content should not have been updated. Expected %q, got %q",
|
||||
string(content), string(originalContent))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_CreateTempFile_Permissions(t *testing.T) {
|
||||
// Create a temporary directory and file for testing
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "input.yaml")
|
||||
|
||||
// Create input file with specific permissions
|
||||
content := []byte("test: value\n")
|
||||
err := os.WriteFile(inputFile, content, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create input file: %v", err)
|
||||
}
|
||||
|
||||
handler := NewWriteInPlaceHandler(inputFile)
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTempFile failed: %v", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Check that temp file has same permissions as input file
|
||||
tempFileInfo, err := os.Stat(tempFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat temp file: %v", err)
|
||||
}
|
||||
|
||||
inputFileInfo, err := os.Stat(inputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat input file: %v", err)
|
||||
}
|
||||
|
||||
if tempFileInfo.Mode() != inputFileInfo.Mode() {
|
||||
t.Errorf("Temp file permissions don't match input file. Expected %v, got %v",
|
||||
inputFileInfo.Mode(), tempFileInfo.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteInPlaceHandlerImpl_Integration(t *testing.T) {
|
||||
// Create a temporary directory and file for testing
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "integration_test.yaml")
|
||||
|
||||
// Create input file with some content
|
||||
originalContent := []byte("original: content\n")
|
||||
err := os.WriteFile(inputFile, originalContent, 0600)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create input file: %v", err)
|
||||
}
|
||||
|
||||
handler := NewWriteInPlaceHandler(inputFile)
|
||||
|
||||
// Create temp file
|
||||
tempFile, err := handler.CreateTempFile()
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTempFile failed: %v", err)
|
||||
}
|
||||
|
||||
// Write new content to temp file
|
||||
newContent := []byte("new: content\n")
|
||||
_, err = tempFile.Write(newContent)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Finish with success
|
||||
err = handler.FinishWriteInPlace(true)
|
||||
if err != nil {
|
||||
t.Fatalf("FinishWriteInPlace failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the file was updated
|
||||
finalContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read final file: %v", err)
|
||||
}
|
||||
|
||||
if string(finalContent) != string(newContent) {
|
||||
t.Errorf("File not updated correctly. Expected %q, got %q",
|
||||
string(newContent), string(finalContent))
|
||||
}
|
||||
}
|
||||
+17
-2
@@ -188,7 +188,10 @@ above_cat
|
||||
`
|
||||
|
||||
const inputXMLWithNamespacedAttr = `<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
<item foo="bar">baz</item>
|
||||
<xsi:item>foobar</xsi:item>
|
||||
</map>
|
||||
`
|
||||
|
||||
const expectedYAMLWithNamespacedAttr = `+p_xml: version="1.0"
|
||||
@@ -196,6 +199,10 @@ map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@xsi:schemaLocation: some-url
|
||||
item:
|
||||
+content: baz
|
||||
+@foo: bar
|
||||
xsi:item: foobar
|
||||
`
|
||||
|
||||
const expectedYAMLWithRawNamespacedAttr = `+p_xml: version="1.0"
|
||||
@@ -203,13 +210,21 @@ map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@xsi:schemaLocation: some-url
|
||||
item:
|
||||
+content: baz
|
||||
+@foo: bar
|
||||
xsi:item: foobar
|
||||
`
|
||||
|
||||
const expectedYAMLWithoutRawNamespacedAttr = `+p_xml: version="1.0"
|
||||
map:
|
||||
some-namespace:map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@some-instance:schemaLocation: some-url
|
||||
some-namespace:item:
|
||||
+content: baz
|
||||
+@foo: bar
|
||||
some-instance:item: foobar
|
||||
`
|
||||
|
||||
const xmlWithCustomDtd = `
|
||||
|
||||
Reference in New Issue
Block a user