Compare commits

...
Author SHA1 Message Date
Mike Farah 38dd4175fb Added more tests 2021-07-19 20:18:42 +10:00
Mike Farah dafa114e65 Added printer tests 2021-07-19 20:12:04 +10:00
Mike Farah 519cf1dcd7 wip 2021-07-19 19:58:47 +10:00
Mike Farah 3a6f73e836 wip 2021-07-19 19:52:51 +10:00
Mike Farah 85bbbbeed4 shellcheck 2021-07-18 17:05:12 +10:00
Mike Farah 7474ac62ef Now using shunit2 for acceptance tests 2021-07-18 16:55:08 +10:00
Mike Farah 4c288e8d90 Removed blank file disclaimer 2021-07-18 15:39:28 +10:00
Mike Farah 102e7e7ab0 Version bump 2021-07-18 13:45:21 +10:00
Mike Farah 9c8253b582 Front matter processor seems to be working! 2021-07-18 13:17:35 +10:00
Mike Farah 555ad0762c Added front-matter handler 2021-07-18 12:28:46 +10:00
Mike Farah f6e2ab5cef Remember comments in empty files 2021-07-16 22:08:22 +10:00
28 changed files with 2370 additions and 276 deletions
+2
View File
@@ -38,3 +38,5 @@ parts/
prime/
.snapcraft/
yq*.snap
test.yml
test2.yml
+16 -14
View File
@@ -176,6 +176,7 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
- Written in portable go, so you can download a lovely dependency free binary
- Uses similar syntax as `jq` but works with YAML and JSON files
- Fully supports multi document yaml files
- Supports yaml [front matter](https://mikefarah.gitbook.io/yq/usage/front-matter) blocks (e.g. jekyll/assemble)
- Colorized yaml output
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/operators/traverse-read)
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/operators/sort-keys)
@@ -199,24 +200,26 @@ Usage:
yq [command]
Available Commands:
eval Apply expression to each document in each yaml file given in sequence
eval Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
shell-completion Generate completion script
Flags:
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the yaml file inplace of first yaml file given.
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-j, --tojson output as json. Set indent to 0 to print json in one line.
-v, --verbose verbose mode
-V, --version Print version information and quit
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
-f, --front-matter string (extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the yaml file inplace of first yaml file given.
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-j, --tojson output as json. Set indent to 0 to print json in one line.
--unwrapScalar unwrap scalar, print the value with no quotes, colors or comments (default true)
-v, --verbose verbose mode
-V, --version Print version information and quit
Use "yq [command] --help" for more information about a command.
```
@@ -230,6 +233,5 @@ yq e '.a.b | length' f1.yml f2.yml
## Known Issues / Missing Features
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
- Powershell has its own...opinions: https://mikefarah.gitbook.io/yq/usage/tips-and-tricks#quotes-in-windows-powershell
- Running expressions against blank files does not work, because the file is empty, there are no matches for yq to run through the expression pipeline and so nothing happens. Instead, you can do something like `yq e -n '.someNew="content"' > newfile.yml` to create a new file.
See [tips and tricks](https://mikefarah.gitbook.io/yq/usage/tips-and-tricks) for more common problems and solutions.
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
setUp() {
rm -f test.yml
}
testBasicEvalRoundTrip() {
./yq e -n ".a = 123" > test.yml
X=$(./yq e '.a' test.yml)
assertEquals 123 "$X"
}
testBasicUpdateInPlaceSequence() {
cat >test.yml <<EOL
a: 0
EOL
./yq e -i ".a = 10" test.yml
X=$(./yq e '.a' test.yml)
assertEquals "10" "$X"
}
testBasicUpdateInPlaceSequenceEvalAll() {
cat >test.yml <<EOL
a: 0
EOL
./yq ea -i ".a = 10" test.yml
X=$(./yq e '.a' test.yml)
assertEquals "10" "$X"
}
testBasicNoExitStatus() {
echo "a: cat" > test.yml
X=$(./yq e '.z' test.yml)
assertEquals "null" "$X"
}
testBasicExitStatus() {
echo "a: cat" > test.yml
X=$(./yq e -e '.z' test.yml 2&>/dev/null)
assertEquals 1 "$?"
}
source ./scripts/shunit2
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
setUp() {
cat >test.yml <<EOL
# comment
EOL
}
testEmptyEval() {
X=$(./yq e test.yml)
assertEquals 0 $?
}
testEmptyEvalPipe() {
X=$(./yq e - < test.yml)
assertEquals 0 $?
}
testEmptyCommentsWithExpressionEval() {
read -r -d '' expected << EOM
# comment
apple: tree
EOM
X=$(./yq e '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyCommentsWithExpressionEvalAll() {
read -r -d '' expected << EOM
# comment
apple: tree
EOM
X=$(./yq ea '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyWithExpressionEval() {
rm test.yml
touch test.yml
expected="apple: tree"
X=$(./yq e '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyWithExpressionEvalAll() {
rm test.yml
touch test.yml
expected="apple: tree"
X=$(./yq ea '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
setUp() {
cat >test.yml <<EOL
---
a: apple
b: cat
---
not yaml
c: at
EOL
}
testFrontMatterProcessEval() {
read -r -d '' expected << EOM
---
a: apple
b: dog
---
not yaml
c: at
EOM
./yq e --front-matter="process" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterProcessEvalAll() {
read -r -d '' expected << EOM
---
a: apple
b: dog
---
not yaml
c: at
EOM
./yq ea --front-matter="process" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterExtractEval() {
cat >test.yml <<EOL
a: apple
b: cat
---
not yaml
c: at
EOL
read -r -d '' expected << EOM
a: apple
b: dog
EOM
./yq e --front-matter="extract" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterExtractEvalAll() {
cat >test.yml <<EOL
a: apple
b: cat
---
not yaml
c: at
EOL
read -r -d '' expected << EOM
a: apple
b: dog
EOM
./yq ea --front-matter="extract" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
source ./scripts/shunit2
+246
View File
@@ -0,0 +1,246 @@
#!/bin/bash
setUp() {
cat >test.yml <<EOL
---
a: test
EOL
}
testLeadingSeperatorWithDoc() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
---
b: cool
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: thing
---
b: cool
EOM
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorPipeIntoEvalSeq() {
X=$(./yq e - < test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorEvalSeq() {
X=$(./yq e test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorPipeIntoEvalAll() {
X=$(./yq ea - < test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorEvalAll() {
X=$(./yq ea test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalSimple() {
read -r -d '' expected << EOM
---
a: test
---
version: 3
application: MyApp
EOM
X=$(./yq e '.' test.yml examples/order.yaml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocInOneFile() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
b: things
EOL
expected=$(cat test.yml)
X=$(./yq e '.' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocInOneFileEvalAll() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
b: things
EOL
expected=$(cat test.yml)
X=$(./yq ea '.' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalComments() {
cat >test.yml <<EOL
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsTrailingSep() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
EOL
cat >test2.yml <<EOL
# this is another doc
# great
---
b: sane
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: test
---
# this is another doc
# great
---
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiMultiDocEvalCommentsTrailingSep() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
---
a1: test2
EOL
cat >test2.yml <<EOL
# this is another doc
# great
---
b: sane
---
b2: cool
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: test
---
a1: test2
---
# this is another doc
# great
---
b: sane
---
b2: cool
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsLeadingSep() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
---
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
---
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalAll() {
read -r -d '' expected << EOM
---
a: test
---
version: 3
application: MyApp
EOM
X=$(./yq ea '.' test.yml examples/order.yaml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
+3
View File
@@ -16,4 +16,7 @@ var verbose = false
var version = false
var prettyPrint = false
// can be either "" (off), "extract" or "process"
var frontMatter = ""
var completedSuccessfully = false
+20 -1
View File
@@ -89,6 +89,25 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
if frontMatter != "" {
frontMatterHandler := yqlib.NewFrontMatterHandler(args[firstFileIndex])
err = frontMatterHandler.Split()
if err != nil {
return err
}
args[firstFileIndex] = frontMatterHandler.GetYamlFrontMatterFilename()
if frontMatter == "process" {
reader, err := os.Open(frontMatterHandler.GetContentFilename()) // #nosec
if err != nil {
return err
}
printer.SetAppendix(reader)
defer yqlib.SafelyCloseReader(reader)
}
defer frontMatterHandler.CleanUp()
}
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
@@ -100,7 +119,7 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
}
case 1:
if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer, "")
} else {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
+20 -1
View File
@@ -103,6 +103,25 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
return errors.New("Cannot pass files in when using null-input flag")
}
if frontMatter != "" {
frontMatterHandler := yqlib.NewFrontMatterHandler(args[firstFileIndex])
err = frontMatterHandler.Split()
if err != nil {
return err
}
args[firstFileIndex] = frontMatterHandler.GetYamlFrontMatterFilename()
if frontMatter == "process" {
reader, err := os.Open(frontMatterHandler.GetContentFilename()) // #nosec
if err != nil {
return err
}
printer.SetAppendix(reader)
defer yqlib.SafelyCloseReader(reader)
}
defer frontMatterHandler.CleanUp()
}
switch len(args) {
case 0:
if pipingStdIn {
@@ -113,7 +132,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
}
case 1:
if nullInput {
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer, "")
} else {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
+1
View File
@@ -54,6 +54,7 @@ See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
rootCmd.PersistentFlags().StringVarP(&frontMatter, "front-matter", "f", "", "(extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact")
rootCmd.AddCommand(
createEvaluateSequenceCommand(),
createEvaluateAllCommand(),
+1 -1
View File
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "4.10.0"
Version = "4.11.0"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
+1
View File
@@ -0,0 +1 @@
# comment
+5
View File
@@ -0,0 +1,5 @@
---
a: apple
b: bannana
---
<h1>I like {{a}} and {{b}} </h1>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM mikefarah/yq:4.10.0
FROM mikefarah/yq:4.11.0
COPY entrypoint.sh /entrypoint.sh
+5 -6
View File
@@ -48,17 +48,17 @@ func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCand
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
fileIndex := 0
firstFileLeadingSeperator := false
firstFileLeadingContent := ""
var allDocuments *list.List = list.New()
for _, filename := range filenames {
reader, leadingSeperator, err := readStream(filename)
reader, leadingContent, err := readStream(filename)
if err != nil {
return err
}
if fileIndex == 0 && leadingSeperator {
firstFileLeadingSeperator = leadingSeperator
if fileIndex == 0 {
firstFileLeadingContent = leadingContent
}
fileDocuments, err := readDocuments(reader, filename, fileIndex)
@@ -83,6 +83,5 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
if err != nil {
return err
}
printer.SetPrintLeadingSeperator(firstFileLeadingSeperator)
return printer.PrintResults(matches)
return printer.PrintResults(matches, firstFileLeadingContent)
}
+35
View File
@@ -2,6 +2,7 @@ package yqlib
import (
"io"
"io/ioutil"
"os"
)
@@ -23,6 +24,14 @@ func safelyRenameFile(from string, to string) {
}
}
func tryRemoveFile(filename string) {
log.Debug("Removing temp file: %v", filename)
removeErr := os.Remove(filename)
if removeErr != nil {
log.Errorf("Failed to remove temp file: %v", filename)
}
}
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
func copyFileContents(src, dst string) (err error) {
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
@@ -44,6 +53,13 @@ func copyFileContents(src, dst string) (err error) {
return out.Sync()
}
func SafelyCloseReader(reader io.Reader) {
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
}
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
@@ -51,3 +67,22 @@ func safelyCloseFile(file *os.File) {
log.Error(err.Error())
}
}
func createTempFile() (*os.File, error) {
_, err := os.Stat(os.TempDir())
if os.IsNotExist(err) {
err = os.Mkdir(os.TempDir(), 0700)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
file, err := ioutil.TempFile("", "temp")
if err != nil {
return nil, err
}
return file, err
}
+96
View File
@@ -0,0 +1,96 @@
package yqlib
import (
"bufio"
"io"
"os"
)
type frontMatterHandler interface {
Split() error
GetYamlFrontMatterFilename() string
GetContentFilename() string
CleanUp()
}
type frontMatterHandlerImpl struct {
originalFilename string
yamlFrontMatterFilename string
contentFilename string
}
func NewFrontMatterHandler(originalFilename string) frontMatterHandler {
return &frontMatterHandlerImpl{originalFilename, "", ""}
}
func (f *frontMatterHandlerImpl) GetYamlFrontMatterFilename() string {
return f.yamlFrontMatterFilename
}
func (f *frontMatterHandlerImpl) GetContentFilename() string {
return f.contentFilename
}
func (f *frontMatterHandlerImpl) CleanUp() {
tryRemoveFile(f.yamlFrontMatterFilename)
tryRemoveFile(f.contentFilename)
}
// Splits the given file by yaml front matter
// yaml content will be saved to first temporary file
// remaining content will be saved to second temporary file
func (f *frontMatterHandlerImpl) Split() error {
var reader io.Reader
var err error
if f.originalFilename == "-" {
reader = bufio.NewReader(os.Stdin)
} else {
reader, err = os.Open(f.originalFilename) // #nosec
if err != nil {
return err
}
}
yamlTempFile, err := createTempFile()
if err != nil {
return err
}
f.yamlFrontMatterFilename = yamlTempFile.Name()
log.Debug("yamlTempFile: %v", yamlTempFile.Name())
contentTempFile, err := createTempFile()
if err != nil {
return err
}
f.contentFilename = contentTempFile.Name()
log.Debug("contentTempFile: %v", contentTempFile.Name())
scanner := bufio.NewScanner(reader)
lineCount := 0
yamlContentBlock := true
for scanner.Scan() {
line := scanner.Text()
if lineCount > 0 && line == "---" {
//we've finished reading the yaml content
yamlContentBlock = false
}
if yamlContentBlock {
_, err = yamlTempFile.Write([]byte(line + "\n"))
} else {
_, err = contentTempFile.Write([]byte(line + "\n"))
}
if err != nil {
return err
}
lineCount = lineCount + 1
}
safelyCloseFile(yamlTempFile)
safelyCloseFile(contentTempFile)
return scanner.Err()
}
+133
View File
@@ -0,0 +1,133 @@
package yqlib
import (
"io/ioutil"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func createTestFile(content string) string {
tempFile, err := createTempFile()
if err != nil {
panic(err)
}
_, err = tempFile.Write([]byte(content))
if err != nil {
panic(err)
}
safelyCloseFile(tempFile)
return tempFile.Name()
}
func readFile(filename string) string {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
return string(bytes)
}
func TestFrontMatterSplitWithLeadingSep(t *testing.T) {
file := createTestFile(`---
a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `---
a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
content := readFile(fmHandler.GetContentFilename())
test.AssertResult(t, expectedContent, content)
tryRemoveFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithNoLeadingSep(t *testing.T) {
file := createTestFile(`a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
content := readFile(fmHandler.GetContentFilename())
test.AssertResult(t, expectedContent, content)
tryRemoveFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithArray(t *testing.T) {
file := createTestFile(`[1,2,3]
---
not a
yaml: doc
`)
expectedYamlFm := "[1,2,3]\n"
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
content := readFile(fmHandler.GetContentFilename())
test.AssertResult(t, expectedContent, content)
tryRemoveFile(file)
fmHandler.CleanUp()
}
+2 -2
View File
@@ -114,7 +114,7 @@ func formatYaml(yaml string, filename string) string {
panic(err)
}
streamEvaluator := NewStreamEvaluator()
_, err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
_, err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer, "")
if err != nil {
panic(err)
}
@@ -256,7 +256,7 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
t.Error(err, s.expression)
}
err = printer.PrintResults(context.MatchingNodes)
err = printer.PrintResults(context.MatchingNodes, "")
if err != nil {
t.Error(err, s.expression)
}
+37 -9
View File
@@ -4,14 +4,16 @@ import (
"bufio"
"container/list"
"io"
"strings"
yaml "gopkg.in/yaml.v3"
)
type Printer interface {
PrintResults(matchingNodes *list.List) error
PrintResults(matchingNodes *list.List, leadingContent string) error
PrintedAnything() bool
SetPrintLeadingSeperator(bool)
//e.g. when given a front-matter doc, like jekyll
SetAppendix(reader io.Reader)
}
type resultsPrinter struct {
@@ -26,6 +28,8 @@ type resultsPrinter struct {
previousFileIndex int
printedMatches bool
treeNavigator DataTreeNavigator
preambleReader io.Reader
appendixReader io.Reader
}
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
@@ -41,11 +45,12 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn
}
}
func (p *resultsPrinter) SetPrintLeadingSeperator(printLeadingSeperator bool) {
if printLeadingSeperator {
p.firstTimePrinting = false
p.previousFileIndex = -1
}
func (p *resultsPrinter) SetPreamble(reader io.Reader) {
p.preambleReader = reader
}
func (p *resultsPrinter) SetAppendix(reader io.Reader) {
p.appendixReader = reader
}
func (p *resultsPrinter) PrintedAnything() bool {
@@ -80,7 +85,7 @@ func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) {
}
}
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
func (p *resultsPrinter) PrintResults(matchingNodes *list.List, leadingContent string) error {
log.Debug("PrintResults for %v matches", matchingNodes.Len())
if p.outputToJSON {
explodeOp := Operation{OperationType: explodeOpType}
@@ -96,6 +101,9 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
defer p.safelyFlush(bufferedWriter)
if matchingNodes.Len() == 0 {
if err := p.writeString(bufferedWriter, leadingContent); err != nil {
return err
}
log.Debug("no matching results, nothing to print")
return nil
}
@@ -106,16 +114,27 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
p.firstTimePrinting = false
}
printedLead := false
for el := matchingNodes.Front(); el != nil; el = el.Next() {
mappedDoc := el.Value.(*CandidateNode)
log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators)
if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && p.printDocSeparators {
if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && p.printDocSeparators &&
(printedLead || !strings.HasPrefix(leadingContent, "---")) {
log.Debug("-- writing doc sep")
if err := p.writeString(bufferedWriter, "---\n"); err != nil {
return err
}
}
if !printedLead {
// we want to print this after the seperator logic
if err := p.writeString(bufferedWriter, leadingContent); err != nil {
return err
}
printedLead = true
}
if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil {
return err
}
@@ -123,5 +142,14 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
p.previousDocIndex = mappedDoc.Document
}
if p.appendixReader != nil && !p.outputToJSON {
log.Debug("Piping appendix reader...")
betterReader := bufio.NewReader(p.appendixReader)
_, err := io.Copy(bufferedWriter, betterReader)
if err != nil {
return err
}
}
return nil
}
+155 -30
View File
@@ -17,8 +17,14 @@ a: apple
a: coconut
`
var leadingSeperatorSample = `---
a: good doc
var multiDocSampleLeadingExpected = `# go cats
---
a: banana
---
a: apple
---
# cool
a: coconut
`
func nodeToList(candidate *CandidateNode) *list.List {
@@ -27,26 +33,6 @@ func nodeToList(candidate *CandidateNode) *list.List {
return elMap
}
func TestPrinterWithLeadingSeperator(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(leadingSeperatorSample), "sample.yml", 0)
if err != nil {
panic(err)
}
printer.SetPrintLeadingSeperator(true)
err = printer.PrintResults(inputs)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, leadingSeperatorSample, output.String())
}
func TestPrinterMultipleDocsInSequence(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
@@ -66,17 +52,17 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
el = el.Next()
sample3 := nodeToList(el.Value.(*CandidateNode))
err = printer.PrintResults(sample1)
err = printer.PrintResults(sample1, "")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2)
err = printer.PrintResults(sample2, "")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3)
err = printer.PrintResults(sample3, "")
if err != nil {
panic(err)
}
@@ -85,6 +71,45 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsInSequenceWithLeadingContent(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
el := inputs.Front()
sample1 := nodeToList(el.Value.(*CandidateNode))
el = el.Next()
sample2 := nodeToList(el.Value.(*CandidateNode))
el = el.Next()
sample3 := nodeToList(el.Value.(*CandidateNode))
err = printer.PrintResults(sample1, "# go cats\n---\n")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2, "---\n")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3, "---\n# cool\n")
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSampleLeadingExpected, output.String())
}
func TestPrinterMultipleFilesInSequence(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
@@ -113,17 +138,17 @@ func TestPrinterMultipleFilesInSequence(t *testing.T) {
elNode.FileIndex = 2
sample3 := nodeToList(elNode)
err = printer.PrintResults(sample1)
err = printer.PrintResults(sample1, "")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2)
err = printer.PrintResults(sample2, "")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3)
err = printer.PrintResults(sample3, "")
if err != nil {
panic(err)
}
@@ -132,6 +157,53 @@ func TestPrinterMultipleFilesInSequence(t *testing.T) {
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleFilesInSequenceWithLeadingContent(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
el := inputs.Front()
elNode := el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 0
sample1 := nodeToList(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 1
sample2 := nodeToList(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 2
sample3 := nodeToList(elNode)
err = printer.PrintResults(sample1, "# go cats\n---\n")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2, "---\n")
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3, "---\n# cool\n")
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSampleLeadingExpected, output.String())
}
func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
@@ -142,7 +214,7 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
panic(err)
}
err = printer.PrintResults(inputs)
err = printer.PrintResults(inputs, "")
if err != nil {
panic(err)
}
@@ -151,6 +223,59 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsInSinglePrintWithLeadingDoc(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
err = printer.PrintResults(inputs, "# go cats\n---\n")
if err != nil {
panic(err)
}
writer.Flush()
expected := `# go cats
---
a: banana
---
a: apple
---
a: coconut
`
test.AssertResult(t, expected, output.String())
}
func TestPrinterMultipleDocsInSinglePrintWithLeadingDocTrailing(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
err = printer.PrintResults(inputs, "---\n")
if err != nil {
panic(err)
}
writer.Flush()
expected := `---
a: banana
---
a: apple
---
a: coconut
`
test.AssertResult(t, expected, output.String())
}
func TestPrinterMultipleDocsJson(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
@@ -163,7 +288,7 @@ func TestPrinterMultipleDocsJson(t *testing.T) {
panic(err)
}
err = printer.PrintResults(inputs)
err = printer.PrintResults(inputs, "")
if err != nil {
panic(err)
}
+21 -11
View File
@@ -12,9 +12,9 @@ import (
// Uses less memory than loading all documents and running the expression once, but this cannot process
// cross document expressions.
type StreamEvaluator interface {
Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) (uint, error)
Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer, leadingContent string) (uint, error)
EvaluateFiles(expression string, filenames []string, printer Printer) error
EvaluateNew(expression string, printer Printer) error
EvaluateNew(expression string, printer Printer, leadingContent string) error
}
type streamEvaluator struct {
@@ -27,7 +27,7 @@ func NewStreamEvaluator() StreamEvaluator {
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
}
func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error {
func (s *streamEvaluator) EvaluateNew(expression string, printer Printer, leadingContent string) error {
node, err := s.treeCreator.ParseExpression(expression)
if err != nil {
return err
@@ -45,7 +45,7 @@ func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error
if errorParsing != nil {
return errorParsing
}
return printer.PrintResults(result.MatchingNodes)
return printer.PrintResults(result.MatchingNodes, leadingContent)
}
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
@@ -55,15 +55,19 @@ func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, p
return err
}
var firstFileLeadingContent string
for index, filename := range filenames {
reader, leadingSeperator, err := readStream(filename)
if index == 0 && leadingSeperator {
printer.SetPrintLeadingSeperator(leadingSeperator)
reader, leadingContent, err := readStream(filename)
if index == 0 {
firstFileLeadingContent = leadingContent
}
if err != nil {
return err
}
processedDocs, err := s.Evaluate(filename, reader, node, printer)
processedDocs, err := s.Evaluate(filename, reader, node, printer, leadingContent)
if err != nil {
return err
}
@@ -76,13 +80,13 @@ func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, p
}
if totalProcessDocs == 0 {
return s.EvaluateNew(expression, printer)
return s.EvaluateNew(expression, printer, firstFileLeadingContent)
}
return nil
}
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) (uint, error) {
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer, leadingContent string) (uint, error) {
var currentIndex uint
decoder := yaml.NewDecoder(reader)
@@ -109,7 +113,13 @@ func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *Expr
if errorParsing != nil {
return currentIndex, errorParsing
}
err := printer.PrintResults(result.MatchingNodes)
var err error
if currentIndex == 0 {
err = printer.PrintResults(result.MatchingNodes, leadingContent)
} else {
err = printer.PrintResults(result.MatchingNodes, "")
}
if err != nil {
return currentIndex, err
}
+28 -21
View File
@@ -5,42 +5,49 @@ import (
"container/list"
"io"
"os"
"regexp"
"strings"
yaml "gopkg.in/yaml.v3"
)
func readStream(filename string) (io.Reader, bool, error) {
func readStream(filename string) (io.Reader, string, error) {
var commentLineRegEx = regexp.MustCompile(`^\s*#`)
var reader *bufio.Reader
if filename == "-" {
reader := bufio.NewReader(os.Stdin)
seperatorBytes, err := reader.Peek(3)
if err == io.EOF {
// EOF are handled else where..
return reader, false, nil
}
return reader, string(seperatorBytes) == "---", err
reader = bufio.NewReader(os.Stdin)
} else {
// 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 thar directory.
reader, err := os.Open(filename) // #nosec
file, err := os.Open(filename) // #nosec
if err != nil {
return nil, false, err
return nil, "", err
}
seperatorBytes := make([]byte, 3)
_, err = reader.Read(seperatorBytes)
reader = bufio.NewReader(file)
}
var sb strings.Builder
for {
peekBytes, err := reader.Peek(3)
if err == io.EOF {
// EOF are handled else where..
return reader, false, nil
return reader, sb.String(), nil
} else if err != nil {
return nil, false, err
return reader, sb.String(), err
} else if string(peekBytes) == "---" || commentLineRegEx.MatchString(string(peekBytes)) {
line, err := reader.ReadString('\n')
sb.WriteString(line)
if err == io.EOF {
return reader, sb.String(), nil
} else if err != nil {
return reader, sb.String(), err
}
} else {
return reader, sb.String(), nil
}
_, err = reader.Seek(0, 0)
return reader, string(seperatorBytes) == "---", err
}
}
func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List, error) {
+9 -20
View File
@@ -1,7 +1,6 @@
package yqlib
import (
"io/ioutil"
"os"
)
@@ -21,27 +20,21 @@ func NewWriteInPlaceHandler(inputFile string) writeInPlaceHandler {
}
func (w *writeInPlaceHandlerImpl) CreateTempFile() (*os.File, error) {
file, err := createTempFile()
if err != nil {
return nil, err
}
info, err := os.Stat(w.inputFilename)
if err != nil {
return nil, err
}
_, err = os.Stat(os.TempDir())
if os.IsNotExist(err) {
err = os.Mkdir(os.TempDir(), 0700)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
err = os.Chmod(file.Name(), info.Mode())
file, err := ioutil.TempFile("", "temp")
if err != nil {
return nil, err
}
err = os.Chmod(file.Name(), info.Mode())
log.Debug("writing to tempfile: %v", file.Name())
log.Debug("WriteInPlaceHandler: writing to tempfile: %v", file.Name())
w.tempFile = file
return file, err
}
@@ -50,13 +43,9 @@ func (w *writeInPlaceHandlerImpl) FinishWriteInPlace(evaluatedSuccessfully bool)
log.Debug("Going to write-inplace, evaluatedSuccessfully=%v, target=%v", evaluatedSuccessfully, w.inputFilename)
safelyCloseFile(w.tempFile)
if evaluatedSuccessfully {
log.Debug("moved temp file to target")
log.Debug("Moving temp file to target")
safelyRenameFile(w.tempFile.Name(), w.inputFilename)
} else {
log.Debug("removed temp file")
removeErr := os.Remove(w.tempFile.Name())
if removeErr != nil {
log.Errorf("failed removing temp file: %s", w.tempFile.Name())
}
tryRemoveFile(w.tempFile.Name())
}
}
+7 -156
View File
@@ -1,159 +1,10 @@
#!/bin/bash
#! /bin/bash
set -e
# acceptance test
for test in acceptance_tests/*.sh; do
echo "--------------------------------------------------------------"
echo "$test"
echo "--------------------------------------------------------------"
(exec "$test");
done
echo "test eval-sequence"
random=$((1 + $RANDOM % 10))
./yq e -n ".a = $random" > test.yml
X=$(./yq e '.a' test.yml)
if [[ $X != $random ]]; then
echo "Failed create: expected $random but was $X"
exit 1
fi
echo "--success"
echo "test update-in-place"
update=$(($random + 1))
./yq e -i ".a = $update" test.yml
X=$(./yq e '.a' test.yml)
if [[ $X != $update ]]; then
echo "Failed to update inplace test: expected $update but was $X"
exit 1
fi
echo "--success"
echo "test eval-all"
./yq ea -n ".a = $random" > test-eval-all.yml
Y=$(./yq ea '.a' test-eval-all.yml)
if [[ $Y != $random ]]; then
echo "Failed create with eval all: expected $random but was $X"
exit 1
fi
echo "--success"
echo "test no exit status"
./yq e '.z' test.yml
echo "--success"
echo "test exit status"
set +e
./yq e -e '.z' test.yml
if [[ $? != 1 ]]; then
echo "Expected error code 1 but was $?"
exit 1
fi
echo "Test: leading seperator logic"
expected=$(cat examples/leading-seperator.yaml)
X=$(cat examples/leading-seperator.yaml | ./yq e '.' -)
if [[ $X != $expected ]]; then
echo "Pipe into e"
echo "Expected $expected but was $X"
exit 1
fi
X=$(./yq e '.' examples/leading-seperator.yaml)
expected=$(cat examples/leading-seperator.yaml)
if [[ $X != $expected ]]; then
echo "read given file e"
echo "Expected $expected but was $X"
exit 1
fi
X=$(cat examples/leading-seperator.yaml | ./yq ea '.' -)
if [[ $X != $expected ]]; then
echo "Pipe into e"
echo "Expected $expected but was $X"
exit 1
fi
X=$(./yq ea '.' examples/leading-seperator.yaml)
expected=$(cat examples/leading-seperator.yaml)
if [[ $X != $expected ]]; then
echo "read given file e"
echo "Expected $expected but was $X"
exit 1
fi
# multidoc
read -r -d '' expected << EOM
---
a: test
---
version: 3
application: MyApp
EOM
X=$(./yq e '.' examples/leading-seperator.yaml examples/order.yaml)
if [[ $X != $expected ]]; then
echo "Multidoc with leading seperator"
echo "Expected $expected but was $X"
exit 1
fi
X=$(./yq ea '.' examples/leading-seperator.yaml examples/order.yaml)
if [[ $X != $expected ]]; then
echo "Multidoc with leading seperator"
echo "Expected $expected but was $X"
exit 1
fi
echo "Test: handle empty files"
./yq e '.' examples/empty.yaml
if [[ $? != 0 ]]; then
echo "Expected no error when processing empty file but got one"
exit 1
fi
cat examples/empty.yaml | ./yq e '.' -
if [[ $? != 0 ]]; then
echo "Expected no error when processing empty stdin but got one"
exit 1
fi
# run expression against empty file
touch temp.yaml
expected="apple: tree"
./yq e '.apple = "tree"' temp.yaml -i
X=$(cat temp.yaml)
rm temp.yaml
if [[ $X != $expected ]]; then
echo "Write empty doc"
echo "Expected $expected but was $X"
exit 1
fi
touch temp.yaml
./yq ea '.apple = "tree"' temp.yaml -i
X=$(cat temp.yaml)
rm temp.yaml
if [[ $X != $expected ]]; then
echo "Write all empty doc"
echo "Expected $expected but was $X"
exit 1
fi
echo "--success"
set -e
rm test.yml
rm test-eval-all.yml
echo "acceptance tests passed"
+2 -2
View File
@@ -5,7 +5,7 @@ set -o pipefail
if command -v gosec &> /dev/null
then
gosec ${PWD} ./...
gosec "${PWD}" ./...
else
./bin/gosec ${PWD} ./...
./bin/gosec "${PWD}" ./...
fi
+1343
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
name: yq
version: '4.10.0'
version: '4.11.0'
summary: A lightweight and portable command-line YAML processor
description: |
The aim of the project is to be the jq or sed of yaml files.