mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 05:38:04 +00:00
wip
This commit is contained in:
parent
76841fab3b
commit
356eff3b0b
11
acceptance_tests/load-file.sh
Executable file
11
acceptance_tests/load-file.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
setUp() {
|
||||
rm test*.yml || true
|
||||
}
|
||||
|
||||
testLoadFileNotExist() {
|
||||
}
|
||||
|
||||
testStrLoadFileNotExist() {
|
||||
}
|
@ -1,8 +1,2 @@
|
||||
# welcome! # cat
|
||||
# bob
|
||||
---
|
||||
# bob
|
||||
|
||||
a: cat # meow
|
||||
|
||||
# have a great day
|
||||
a:
|
||||
include: 'data2.yaml'
|
||||
|
2
examples/thing.yml
Normal file
2
examples/thing.yml
Normal file
@ -0,0 +1,2 @@
|
||||
a: apple is included
|
||||
b: cool.
|
13
pkg/yqlib/doc/headers/load.md
Normal file
13
pkg/yqlib/doc/headers/load.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Load
|
||||
|
||||
The `load`/`strload` operator allows you to load in content from another file referenced in your yaml document.
|
||||
|
||||
Note that you can use string operators like `+` and `sub` to modify the value in the yaml file to a path that exists in your system.
|
||||
|
||||
|
||||
Lets say there is a file `../../examples/thing.yml`:
|
||||
|
||||
```yaml
|
||||
a: apple is included
|
||||
b: cool
|
||||
```
|
93
pkg/yqlib/doc/load.md
Normal file
93
pkg/yqlib/doc/load.md
Normal file
@ -0,0 +1,93 @@
|
||||
# Load
|
||||
|
||||
The `load`/`strload` operator allows you to load in content from another file referenced in your yaml document.
|
||||
|
||||
Note that you can use string operators like `+` and `sub` to modify the value in the yaml file to a path that exists in your system.
|
||||
|
||||
|
||||
Lets say there is a file `../../examples/thing.yml`:
|
||||
|
||||
```yaml
|
||||
a: apple is included
|
||||
b: cool
|
||||
```
|
||||
|
||||
## Simple example
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
myFile: ../../examples/thing.yml
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'load(.myFile)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: apple is included
|
||||
b: cool.
|
||||
```
|
||||
|
||||
## Replace node with referenced file
|
||||
Note that you can modify the filename in the load operator if needed.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
something:
|
||||
file: thing.yml
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.something |= load("../../examples/" + .file)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
something:
|
||||
a: apple is included
|
||||
b: cool.
|
||||
```
|
||||
|
||||
## Replace _all_ nodes with referenced file
|
||||
Recursively match all the nodes (`..`) and then filter the ones that have a 'file' attribute.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
something:
|
||||
file: thing.yml
|
||||
over:
|
||||
here:
|
||||
- file: thing.yml
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '(.. | select(has("file"))) |= load("../../examples/" + .file)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
something:
|
||||
a: apple is included
|
||||
b: cool.
|
||||
over:
|
||||
here:
|
||||
- a: apple is included
|
||||
b: cool.
|
||||
```
|
||||
|
||||
## Replace node with referenced file as string
|
||||
This will work for any text based file
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
something:
|
||||
file: thing.yml
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.something |= strload("../../examples/" + .file)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
something: |-
|
||||
a: apple is included
|
||||
b: cool.
|
||||
```
|
||||
|
@ -336,6 +336,8 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`from_json`), opToken(decodeOpType))
|
||||
|
||||
lexer.Add([]byte(`sortKeys`), opToken(sortKeysOpType))
|
||||
lexer.Add([]byte(`load`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: false}))
|
||||
lexer.Add([]byte(`strload`), opTokenWithPrefs(loadOpType, nil, loadPrefs{loadAsString: true}))
|
||||
lexer.Add([]byte(`select`), opToken(selectOpType))
|
||||
lexer.Add([]byte(`has`), opToken(hasOpType))
|
||||
lexer.Add([]byte(`unique`), opToken(uniqueOpType))
|
||||
|
@ -95,6 +95,8 @@ var captureOpType = &operationType{Type: "CAPTURE", NumArgs: 1, Precedence: 50,
|
||||
var testOpType = &operationType{Type: "TEST", NumArgs: 1, Precedence: 50, Handler: testOperator}
|
||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||
|
||||
var loadOpType = &operationType{Type: "LOAD", NumArgs: 1, Precedence: 50, Handler: loadYamlOperator}
|
||||
|
||||
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||
|
||||
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
||||
|
97
pkg/yqlib/operator_load.go
Normal file
97
pkg/yqlib/operator_load.go
Normal file
@ -0,0 +1,97 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type loadPrefs struct {
|
||||
loadAsString bool
|
||||
}
|
||||
|
||||
func loadString(filename string) (*CandidateNode, error) {
|
||||
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
|
||||
// and ensuring that it's not possible to give a path to a file outside that directory.
|
||||
|
||||
filebytes, err := ioutil.ReadFile(filename) // #nosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CandidateNode{Node: &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: string(filebytes)}}, nil
|
||||
}
|
||||
|
||||
func loadYaml(filename string) (*CandidateNode, error) {
|
||||
|
||||
file, err := os.Open(filename) // #nosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
documents, err := readDocuments(reader, filename, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if documents.Len() == 0 {
|
||||
// return null candidate
|
||||
return &CandidateNode{Node: &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null"}}, nil
|
||||
} else if documents.Len() == 1 {
|
||||
return documents.Front().Value.(*CandidateNode), nil
|
||||
|
||||
} else {
|
||||
sequenceNode := &CandidateNode{Node: &yaml.Node{Kind: yaml.SequenceNode}}
|
||||
for doc := documents.Front(); doc != nil; doc = doc.Next() {
|
||||
sequenceNode.Node.Content = append(sequenceNode.Node.Content, doc.Value.(*CandidateNode).Node)
|
||||
}
|
||||
return sequenceNode, nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadYamlOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("loadYamlOperator")
|
||||
|
||||
loadPrefs := expressionNode.Operation.Preferences.(loadPrefs)
|
||||
|
||||
// need to evaluate the 1st parameter against the context
|
||||
// and return the data accordingly.
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
if rhs.MatchingNodes.Front() == nil {
|
||||
return Context{}, fmt.Errorf("Filename expression returned nil")
|
||||
}
|
||||
nameCandidateNode := rhs.MatchingNodes.Front().Value.(*CandidateNode)
|
||||
|
||||
filename := nameCandidateNode.Node.Value
|
||||
|
||||
var contentsCandidate *CandidateNode
|
||||
|
||||
if loadPrefs.loadAsString {
|
||||
contentsCandidate, err = loadString(filename)
|
||||
} else {
|
||||
contentsCandidate, err = loadYaml(filename)
|
||||
}
|
||||
if err != nil {
|
||||
return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
|
||||
}
|
||||
|
||||
results.PushBack(contentsCandidate)
|
||||
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
50
pkg/yqlib/operator_load_test.go
Normal file
50
pkg/yqlib/operator_load_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var loadScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Simple example",
|
||||
document: `{myFile: "../../examples/thing.yml"}`,
|
||||
expression: `load(.myFile)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: apple is included\nb: cool.\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Replace node with referenced file",
|
||||
subdescription: "Note that you can modify the filename in the load operator if needed.",
|
||||
document: `{something: {file: "thing.yml"}}`,
|
||||
expression: `.something |= load("../../examples/" + .file)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{something: {a: apple is included, b: cool.}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Replace _all_ nodes with referenced file",
|
||||
subdescription: "Recursively match all the nodes (`..`) and then filter the ones that have a 'file' attribute. ",
|
||||
document: `{something: {file: "thing.yml"}, over: {here: [{file: "thing.yml"}]}}`,
|
||||
expression: `(.. | select(has("file"))) |= load("../../examples/" + .file)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{something: {a: apple is included, b: cool.}, over: {here: [{a: apple is included, b: cool.}]}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Replace node with referenced file as string",
|
||||
subdescription: "This will work for any text based file",
|
||||
document: `{something: {file: "thing.yml"}}`,
|
||||
expression: `.something |= strload("../../examples/" + .file)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{something: \"a: apple is included\\nb: cool.\"}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLoadScenarios(t *testing.T) {
|
||||
for _, tt := range loadScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "load", loadScenarios)
|
||||
}
|
Loading…
Reference in New Issue
Block a user