mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
d302d75c77
* 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
269 lines
8.2 KiB
Go
269 lines
8.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
|
"github.com/spf13/cobra"
|
|
"gopkg.in/op/go-logging.v1"
|
|
)
|
|
|
|
func isAutomaticOutputFormat() bool {
|
|
return outputFormat == "" || outputFormat == "auto" || outputFormat == "a"
|
|
}
|
|
|
|
func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
|
|
cmd.SilenceUsage = true
|
|
|
|
fileInfo, _ := os.Stdout.Stat()
|
|
|
|
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
|
colorsEnabled = true
|
|
}
|
|
|
|
expression, args, err := processArgs(args)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
if splitFileExpFile != "" {
|
|
splitExpressionBytes, err := os.ReadFile(splitFileExpFile)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
splitFileExp = string(splitExpressionBytes)
|
|
}
|
|
|
|
// backwards compatibility
|
|
if outputToJSON {
|
|
outputFormat = "json"
|
|
}
|
|
|
|
if writeInplace && (len(args) == 0 || args[0] == "-") {
|
|
return "", nil, fmt.Errorf("write inplace flag only applicable when giving an expression and at least one file")
|
|
}
|
|
|
|
if frontMatter != "" && len(args) == 0 {
|
|
return "", nil, fmt.Errorf("front matter flag only applicable when giving an expression and at least one file")
|
|
}
|
|
|
|
if writeInplace && splitFileExp != "" {
|
|
return "", nil, fmt.Errorf("write inplace cannot be used with split file")
|
|
}
|
|
|
|
if nullInput && len(args) > 0 {
|
|
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
|
|
}
|
|
|
|
inputFilename := ""
|
|
if len(args) > 0 {
|
|
inputFilename = args[0]
|
|
}
|
|
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
|
|
|
|
inputFormat = yqlib.FormatFromFilename(inputFilename)
|
|
|
|
_, err := yqlib.InputFormatFromString(inputFormat)
|
|
if err != nil {
|
|
// unknown file type, default to yaml
|
|
yqlib.GetLogger().Debug("Unknown file format extension '%v', defaulting to yaml", inputFormat)
|
|
inputFormat = "yaml"
|
|
if isAutomaticOutputFormat() {
|
|
outputFormat = "yaml"
|
|
}
|
|
} else if isAutomaticOutputFormat() {
|
|
// automatic input worked, we can do it for output too unless specified
|
|
if inputFormat == "json" {
|
|
yqlib.GetLogger().Warning("JSON file output is now JSON by default (instead of yaml). Use '-oy' or '--output-format=yaml' for yaml output")
|
|
}
|
|
outputFormat = inputFormat
|
|
}
|
|
} else if isAutomaticOutputFormat() {
|
|
// backwards compatibility -
|
|
// before this was introduced, `yq -pcsv things.csv`
|
|
// would produce *yaml* output.
|
|
//
|
|
outputFormat = yqlib.FormatFromFilename(inputFilename)
|
|
if inputFilename != "-" {
|
|
yqlib.GetLogger().Warning("yq default output is now 'auto' (based on the filename extension). Normally yq would output '%v', but for backwards compatibility 'yaml' has been set. Please use -oy to specify yaml, or drop the -p flag.", outputFormat)
|
|
}
|
|
outputFormat = "yaml"
|
|
}
|
|
|
|
outputFormatType, err := yqlib.OutputFormatFromString(outputFormat)
|
|
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
|
|
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
|
|
|
|
if outputFormatType == yqlib.YamlOutputFormat ||
|
|
outputFormatType == yqlib.PropsOutputFormat {
|
|
unwrapScalar = true
|
|
}
|
|
if unwrapScalarFlag.IsExplicitySet() {
|
|
unwrapScalar = unwrapScalarFlag.IsSet()
|
|
}
|
|
|
|
//copy preference form global setting
|
|
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
|
|
|
|
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
|
|
|
|
return expression, args, nil
|
|
}
|
|
|
|
func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
|
|
yqlibInputFormat, err := yqlib.InputFormatFromString(inputFormat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
yqlibDecoder, err := createDecoder(yqlibInputFormat, evaluateTogether)
|
|
if yqlibDecoder == nil {
|
|
return nil, fmt.Errorf("no support for %s input format", inputFormat)
|
|
}
|
|
return yqlibDecoder, err
|
|
}
|
|
|
|
func createDecoder(format yqlib.InputFormat, evaluateTogether bool) (yqlib.Decoder, error) {
|
|
switch format {
|
|
case yqlib.XMLInputFormat:
|
|
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
|
|
case yqlib.PropertiesInputFormat:
|
|
return yqlib.NewPropertiesDecoder(), nil
|
|
case yqlib.JsonInputFormat:
|
|
return yqlib.NewJSONDecoder(), nil
|
|
case yqlib.CSVObjectInputFormat:
|
|
return yqlib.NewCSVObjectDecoder(','), nil
|
|
case yqlib.TSVObjectInputFormat:
|
|
return yqlib.NewCSVObjectDecoder('\t'), nil
|
|
case yqlib.TomlInputFormat:
|
|
return yqlib.NewTomlDecoder(), nil
|
|
case yqlib.YamlInputFormat:
|
|
prefs := yqlib.ConfiguredYamlPreferences
|
|
prefs.EvaluateTogether = evaluateTogether
|
|
return yqlib.NewYamlDecoder(prefs), nil
|
|
}
|
|
return nil, fmt.Errorf("invalid decoder: %v", format)
|
|
}
|
|
|
|
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
|
|
|
|
var printerWriter yqlib.PrinterWriter
|
|
|
|
if splitFileExp != "" {
|
|
colorsEnabled = forceColor
|
|
splitExp, err := yqlib.ExpressionParser.ParseExpression(splitFileExp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bad split document expression: %w", err)
|
|
}
|
|
printerWriter = yqlib.NewMultiPrinterWriter(splitExp, format)
|
|
} else {
|
|
printerWriter = yqlib.NewSinglePrinterWriter(out)
|
|
}
|
|
return printerWriter, nil
|
|
}
|
|
|
|
func configureEncoder() (yqlib.Encoder, error) {
|
|
yqlibOutputFormat, err := yqlib.OutputFormatFromString(outputFormat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
yqlibEncoder, err := createEncoder(yqlibOutputFormat)
|
|
if yqlibEncoder == nil {
|
|
return nil, fmt.Errorf("no support for %s output format", outputFormat)
|
|
}
|
|
return yqlibEncoder, err
|
|
}
|
|
|
|
func createEncoder(format yqlib.PrinterOutputFormat) (yqlib.Encoder, error) {
|
|
switch format {
|
|
case yqlib.JSONOutputFormat:
|
|
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar), nil
|
|
case yqlib.PropsOutputFormat:
|
|
return yqlib.NewPropertiesEncoder(unwrapScalar), nil
|
|
case yqlib.CSVOutputFormat:
|
|
return yqlib.NewCsvEncoder(','), nil
|
|
case yqlib.TSVOutputFormat:
|
|
return yqlib.NewCsvEncoder('\t'), nil
|
|
case yqlib.YamlOutputFormat:
|
|
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences), nil
|
|
case yqlib.XMLOutputFormat:
|
|
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences), nil
|
|
case yqlib.TomlOutputFormat:
|
|
return yqlib.NewTomlEncoder(), nil
|
|
case yqlib.ShellVariablesOutputFormat:
|
|
return yqlib.NewShellVariablesEncoder(), nil
|
|
case yqlib.LuaOutputFormat:
|
|
return yqlib.NewLuaEncoder(yqlib.ConfiguredLuaPreferences), nil
|
|
}
|
|
return nil, fmt.Errorf("invalid encoder: %v", format)
|
|
}
|
|
|
|
// this is a hack to enable backwards compatibility with githubactions (which pipe /dev/null into everything)
|
|
// and being able to call yq with the filename as a single parameter
|
|
//
|
|
// without this - yq detects there is stdin (thanks githubactions),
|
|
// then tries to parse the filename as an expression
|
|
func maybeFile(str string) bool {
|
|
yqlib.GetLogger().Debugf("checking '%v' is a file", str)
|
|
stat, err := os.Stat(str) // #nosec
|
|
result := err == nil && !stat.IsDir()
|
|
if yqlib.GetLogger().IsEnabledFor(logging.DEBUG) {
|
|
if err != nil {
|
|
yqlib.GetLogger().Debugf("error: %v", err)
|
|
} else {
|
|
yqlib.GetLogger().Debugf("error: %v, dir: %v", err, stat.IsDir())
|
|
}
|
|
yqlib.GetLogger().Debugf("result: %v", result)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func processStdInArgs(args []string) []string {
|
|
stat, _ := os.Stdin.Stat()
|
|
pipingStdin := (stat.Mode() & os.ModeCharDevice) == 0
|
|
|
|
// if we've been given a file, don't automatically
|
|
// read from stdin.
|
|
// this happens if there is more than one argument
|
|
// or only one argument and its a file
|
|
if nullInput || !pipingStdin || len(args) > 1 || (len(args) > 0 && maybeFile(args[0])) {
|
|
return args
|
|
}
|
|
|
|
for _, arg := range args {
|
|
if arg == "-" {
|
|
return args
|
|
}
|
|
}
|
|
yqlib.GetLogger().Debugf("missing '-', adding it to the end")
|
|
|
|
// we're piping from stdin, but there's no '-' arg
|
|
// lets add one to the end
|
|
return append(args, "-")
|
|
}
|
|
|
|
func processArgs(originalArgs []string) (string, []string, error) {
|
|
expression := forceExpression
|
|
if expressionFile != "" {
|
|
expressionBytes, err := os.ReadFile(expressionFile)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
expression = string(expressionBytes)
|
|
}
|
|
|
|
args := processStdInArgs(originalArgs)
|
|
yqlib.GetLogger().Debugf("processed args: %v", args)
|
|
if expression == "" && len(args) > 0 && args[0] != "-" && !maybeFile(args[0]) {
|
|
yqlib.GetLogger().Debug("assuming expression is '%v'", args[0])
|
|
expression = args[0]
|
|
args = args[1:]
|
|
}
|
|
return expression, args, nil
|
|
}
|