yq/pkg/yqlib/printer.go

221 lines
5.5 KiB
Go
Raw Normal View History

2020-11-03 23:48:43 +00:00
package yqlib
import (
"bufio"
"bytes"
2020-11-03 23:48:43 +00:00
"container/list"
2021-07-25 08:08:33 +00:00
"fmt"
2020-11-03 23:48:43 +00:00
"io"
2021-11-13 23:31:37 +00:00
"regexp"
2020-11-03 23:48:43 +00:00
2020-11-29 09:25:47 +00:00
yaml "gopkg.in/yaml.v3"
2020-11-03 23:48:43 +00:00
)
type Printer interface {
PrintResults(matchingNodes *list.List) error
2020-11-30 05:35:21 +00:00
PrintedAnything() bool
2021-07-18 02:28:46 +00:00
//e.g. when given a front-matter doc, like jekyll
SetAppendix(reader io.Reader)
SetNulSepOutput(nulSepOutput bool)
2020-11-03 23:48:43 +00:00
}
2021-07-25 08:08:33 +00:00
type PrinterOutputFormat uint32
const (
YamlOutputFormat = 1 << iota
JSONOutputFormat
2021-07-25 08:08:33 +00:00
PropsOutputFormat
CSVOutputFormat
TSVOutputFormat
XMLOutputFormat
2022-02-22 22:26:35 +00:00
Base64OutputFormat
2023-01-23 00:37:18 +00:00
UriOutputFormat
2023-02-02 01:22:52 +00:00
ShOutputFormat
2023-04-03 05:40:06 +00:00
TomlOutputFormat
ShellVariablesOutputFormat
Implement basic Lua output support (#1745) * Implement basic Lua output support Ref #1700 Basic but working serialization to Lua tables. * Escape larger set of characters in Lua output Started with a minimum of replacements, this should be more complete, tho not all substitutions are strictly required in Lua. * Print simple keys unquoted in Lua output String keys that satisfy the requirements for variable names can be used as keys without quotes in tables. * Quote Lua keywords in table keys Keywords are not valid as unquoted keys, thus must be quoted * Make output of unquoted Lua table keys optional Generally safer and simpler to not do it. * Hook up settings for Lua output * Allow special characters in Lua prefix and suffix --lua-suffix='});^M' didn't work, so taking this approach instead * Panic on unhandled YAML Kind in Lua encoder * Handle YAML case varied booleans in Lua encoder * Handle special-case numbers in Lua encoder * Reject unhandled scalar Tags in Lua encoder * Add note about how Lua nil is unsuitable as table key Could add some context tracking in the future to allow rejecting nil in a table key context. * Return error instead of panic in Lua encoder * Add initial test for Lua encoder Boilerplate mostly copied from toml_test.go * Additional Lua output tests * Generate Lua encoder documentation Mostly just for the boilerplate * Convert octal for Lua output Lua doesn't have the 0oNNN syntax for octal integers, only decimal and hexadecimal, hence those can be passed trough as is while octal needs special treatment. * Implement indentation in in Lua output * Respect string Style in Lua encoder Lua has 'single', "double" and [[ long ]] strings. * Expand Lua examples * Output line comments in Lua output * Implement Lua globals output mode
2023-08-11 02:56:49 +00:00
LuaOutputFormat
2021-07-25 08:08:33 +00:00
)
func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
switch format {
case "yaml", "y", "yml":
2021-07-25 08:08:33 +00:00
return YamlOutputFormat, nil
2021-08-20 05:46:33 +00:00
case "json", "j":
return JSONOutputFormat, nil
case "props", "p", "properties":
2021-07-25 08:08:33 +00:00
return PropsOutputFormat, nil
2021-12-01 01:08:47 +00:00
case "csv", "c":
return CSVOutputFormat, nil
2021-12-01 01:08:47 +00:00
case "tsv", "t":
return TSVOutputFormat, nil
2021-12-21 05:19:27 +00:00
case "xml", "x":
return XMLOutputFormat, nil
2023-04-03 05:40:06 +00:00
case "toml":
return TomlOutputFormat, nil
case "shell", "s", "sh":
return ShellVariablesOutputFormat, nil
Implement basic Lua output support (#1745) * Implement basic Lua output support Ref #1700 Basic but working serialization to Lua tables. * Escape larger set of characters in Lua output Started with a minimum of replacements, this should be more complete, tho not all substitutions are strictly required in Lua. * Print simple keys unquoted in Lua output String keys that satisfy the requirements for variable names can be used as keys without quotes in tables. * Quote Lua keywords in table keys Keywords are not valid as unquoted keys, thus must be quoted * Make output of unquoted Lua table keys optional Generally safer and simpler to not do it. * Hook up settings for Lua output * Allow special characters in Lua prefix and suffix --lua-suffix='});^M' didn't work, so taking this approach instead * Panic on unhandled YAML Kind in Lua encoder * Handle YAML case varied booleans in Lua encoder * Handle special-case numbers in Lua encoder * Reject unhandled scalar Tags in Lua encoder * Add note about how Lua nil is unsuitable as table key Could add some context tracking in the future to allow rejecting nil in a table key context. * Return error instead of panic in Lua encoder * Add initial test for Lua encoder Boilerplate mostly copied from toml_test.go * Additional Lua output tests * Generate Lua encoder documentation Mostly just for the boilerplate * Convert octal for Lua output Lua doesn't have the 0oNNN syntax for octal integers, only decimal and hexadecimal, hence those can be passed trough as is while octal needs special treatment. * Implement indentation in in Lua output * Respect string Style in Lua encoder Lua has 'single', "double" and [[ long ]] strings. * Expand Lua examples * Output line comments in Lua output * Implement Lua globals output mode
2023-08-11 02:56:49 +00:00
case "lua", "l":
return LuaOutputFormat, nil
2021-07-25 08:08:33 +00:00
default:
Implement basic Lua output support (#1745) * Implement basic Lua output support Ref #1700 Basic but working serialization to Lua tables. * Escape larger set of characters in Lua output Started with a minimum of replacements, this should be more complete, tho not all substitutions are strictly required in Lua. * Print simple keys unquoted in Lua output String keys that satisfy the requirements for variable names can be used as keys without quotes in tables. * Quote Lua keywords in table keys Keywords are not valid as unquoted keys, thus must be quoted * Make output of unquoted Lua table keys optional Generally safer and simpler to not do it. * Hook up settings for Lua output * Allow special characters in Lua prefix and suffix --lua-suffix='});^M' didn't work, so taking this approach instead * Panic on unhandled YAML Kind in Lua encoder * Handle YAML case varied booleans in Lua encoder * Handle special-case numbers in Lua encoder * Reject unhandled scalar Tags in Lua encoder * Add note about how Lua nil is unsuitable as table key Could add some context tracking in the future to allow rejecting nil in a table key context. * Return error instead of panic in Lua encoder * Add initial test for Lua encoder Boilerplate mostly copied from toml_test.go * Additional Lua output tests * Generate Lua encoder documentation Mostly just for the boilerplate * Convert octal for Lua output Lua doesn't have the 0oNNN syntax for octal integers, only decimal and hexadecimal, hence those can be passed trough as is while octal needs special treatment. * Implement indentation in in Lua output * Respect string Style in Lua encoder Lua has 'single', "double" and [[ long ]] strings. * Expand Lua examples * Output line comments in Lua output * Implement Lua globals output mode
2023-08-11 02:56:49 +00:00
return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv|xml|toml|shell|lua]", format)
2021-07-25 08:08:33 +00:00
}
}
2020-11-03 23:48:43 +00:00
type resultsPrinter struct {
encoder Encoder
printerWriter PrinterWriter
firstTimePrinting bool
previousDocIndex uint
previousFileIndex int
printedMatches bool
treeNavigator DataTreeNavigator
appendixReader io.Reader
nulSepOutput bool
2020-11-03 23:48:43 +00:00
}
func NewPrinter(encoder Encoder, printerWriter PrinterWriter) Printer {
2020-11-13 02:19:54 +00:00
return &resultsPrinter{
encoder: encoder,
printerWriter: printerWriter,
firstTimePrinting: true,
treeNavigator: NewDataTreeNavigator(),
nulSepOutput: false,
2020-11-13 02:19:54 +00:00
}
2020-11-03 23:48:43 +00:00
}
func (p *resultsPrinter) SetNulSepOutput(nulSepOutput bool) {
log.Debug("Setting NUL separator output")
p.nulSepOutput = nulSepOutput
}
2021-07-18 02:28:46 +00:00
func (p *resultsPrinter) SetAppendix(reader io.Reader) {
p.appendixReader = reader
}
2020-11-30 05:35:21 +00:00
func (p *resultsPrinter) PrintedAnything() bool {
return p.printedMatches
}
2020-11-03 23:48:43 +00:00
func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
2020-11-30 05:35:21 +00:00
p.printedMatches = p.printedMatches || (node.Tag != "!!null" &&
(node.Tag != "!!bool" || node.Value != "false"))
return p.encoder.Encode(writer, node)
2020-11-03 23:48:43 +00:00
}
func removeLastEOL(b *bytes.Buffer) {
data := b.Bytes()
n := len(data)
if n >= 2 && data[n-2] == '\r' && data[n-1] == '\n' {
b.Truncate(n - 2)
} else if n >= 1 && (data[n-1] == '\r' || data[n-1] == '\n') {
b.Truncate(n - 1)
}
}
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
2020-11-16 01:09:57 +00:00
log.Debug("PrintResults for %v matches", matchingNodes.Len())
2021-10-29 03:14:39 +00:00
if matchingNodes.Len() == 0 {
log.Debug("no matching results, nothing to print")
return nil
}
if !p.encoder.CanHandleAliases() {
explodeOp := Operation{OperationType: explodeOpType}
2021-01-12 23:18:53 +00:00
explodeNode := ExpressionNode{Operation: &explodeOp}
context, err := p.treeNavigator.GetMatchingNodes(Context{MatchingNodes: matchingNodes}, &explodeNode)
2020-11-06 03:37:01 +00:00
if err != nil {
return err
}
matchingNodes = context.MatchingNodes
2020-11-06 03:37:01 +00:00
}
2020-11-03 23:48:43 +00:00
2020-11-16 01:09:57 +00:00
if p.firstTimePrinting {
2020-12-15 03:33:50 +00:00
node := matchingNodes.Front().Value.(*CandidateNode)
p.previousDocIndex = node.Document
p.previousFileIndex = node.FileIndex
2020-11-16 01:09:57 +00:00
p.firstTimePrinting = false
}
2020-11-03 23:48:43 +00:00
for el := matchingNodes.Front(); el != nil; el = el.Next() {
2021-10-29 03:14:39 +00:00
2020-11-03 23:48:43 +00:00
mappedDoc := el.Value.(*CandidateNode)
log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document)
log.Debug("%v", NodeToString(mappedDoc))
2021-10-29 03:14:39 +00:00
writer, errorWriting := p.printerWriter.GetWriter(mappedDoc)
if errorWriting != nil {
return errorWriting
}
2023-09-18 23:52:36 +00:00
commentsStartWithSepExp := regexp.MustCompile(`^\$yqDocSeparator\$`)
2021-11-13 23:31:37 +00:00
commentStartsWithSeparator := commentsStartWithSepExp.MatchString(mappedDoc.LeadingContent)
if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && !commentStartsWithSeparator {
if err := p.encoder.PrintDocumentSeparator(writer); err != nil {
2020-11-13 03:07:11 +00:00
return err
}
2020-11-03 23:48:43 +00:00
}
var destination io.Writer = writer
tempBuffer := bytes.NewBuffer(nil)
if p.nulSepOutput {
destination = tempBuffer
}
if err := p.encoder.PrintLeadingContent(destination, mappedDoc.LeadingContent); err != nil {
2021-10-29 03:14:39 +00:00
return err
}
2021-07-25 08:08:33 +00:00
if err := p.printNode(mappedDoc.Node, destination); err != nil {
2020-11-03 23:48:43 +00:00
return err
}
if err := p.encoder.PrintLeadingContent(destination, mappedDoc.TrailingContent); err != nil {
return err
}
if p.nulSepOutput {
removeLastEOL(tempBuffer)
tempBufferBytes := tempBuffer.Bytes()
if bytes.IndexByte(tempBufferBytes, 0) != -1 {
return fmt.Errorf(
"Can't serialize value because it contains NUL char and you are using NUL separated output",
)
}
if _, err := writer.Write(tempBufferBytes); err != nil {
return err
}
if _, err := writer.Write([]byte{0}); err != nil {
return err
}
}
2020-11-16 01:09:57 +00:00
p.previousDocIndex = mappedDoc.Document
2021-10-29 03:14:39 +00:00
if err := writer.Flush(); err != nil {
return err
}
log.Debugf("done printing results")
2020-11-03 23:48:43 +00:00
}
// what happens if I remove output format check?
if p.appendixReader != nil {
2021-10-29 03:14:39 +00:00
writer, err := p.printerWriter.GetWriter(nil)
if err != nil {
return err
}
2021-07-18 02:28:46 +00:00
log.Debug("Piping appendix reader...")
betterReader := bufio.NewReader(p.appendixReader)
2021-10-29 03:14:39 +00:00
_, err = io.Copy(writer, betterReader)
2021-07-18 02:28:46 +00:00
if err != nil {
return err
}
2021-10-29 03:14:39 +00:00
if err := writer.Flush(); err != nil {
return err
}
2021-07-18 02:28:46 +00:00
}
2020-11-03 23:48:43 +00:00
return nil
}