yq/cmd/utils.go

273 lines
8.4 KiB
Go
Raw Normal View History

2021-10-30 02:04:05 +00:00
package cmd
import (
"fmt"
"io"
"os"
"strings"
2021-10-30 02:04:05 +00:00
"github.com/mikefarah/yq/v4/pkg/yqlib"
"github.com/spf13/cobra"
"gopkg.in/op/go-logging.v1"
2021-10-30 02:04:05 +00:00
)
func isAutomaticOutputFormat() bool {
return outputFormat == "" || outputFormat == "auto" || outputFormat == "a"
}
func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
2021-10-30 02:04:05 +00:00
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
2021-10-30 02:04:05 +00:00
}
if splitFileExpFile != "" {
splitExpressionBytes, err := os.ReadFile(splitFileExpFile)
if err != nil {
return "", nil, err
}
splitFileExp = string(splitExpressionBytes)
}
2021-11-25 09:24:51 +00:00
// backwards compatibility
2021-10-30 02:04:05 +00:00
if outputToJSON {
outputFormat = "json"
}
if writeInplace && (len(args) == 0 || args[0] == "-") {
2023-09-18 23:52:36 +00:00
return "", nil, fmt.Errorf("write in place 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")
2021-10-30 02:04:05 +00:00
}
if writeInplace && splitFileExp != "" {
2023-09-18 23:52:36 +00:00
return "", nil, fmt.Errorf("write in place cannot be used with split file")
2021-10-30 02:04:05 +00:00
}
if nullInput && len(args) > 0 {
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
2021-10-30 02:04:05 +00:00
}
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
}
2023-09-18 23:52:36 +00:00
if unwrapScalarFlag.IsExplicitlySet() {
unwrapScalar = unwrapScalarFlag.IsSet()
}
//copy preference form global setting
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
return expression, args, nil
2021-10-30 02:04:05 +00:00
}
func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
2021-12-21 04:02:07 +00:00
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 {
2023-10-03 05:00:51 +00:00
case yqlib.LuaInputFormat:
return yqlib.NewLuaDecoder(yqlib.ConfiguredLuaPreferences), nil
case yqlib.XMLInputFormat:
2022-10-25 03:27:16 +00:00
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
case yqlib.PropertiesInputFormat:
return yqlib.NewPropertiesDecoder(), nil
2022-07-27 02:26:22 +00:00
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
2021-12-21 04:02:07 +00:00
}
return nil, fmt.Errorf("invalid decoder: %v", format)
2021-12-21 04:02:07 +00:00
}
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
2021-10-30 02:04:05 +00:00
var printerWriter yqlib.PrinterWriter
if splitFileExp != "" {
colorsEnabled = forceColor
2022-02-01 03:47:51 +00:00
splitExp, err := yqlib.ExpressionParser.ParseExpression(splitFileExp)
2021-10-30 02:04:05 +00:00
if err != nil {
return nil, fmt.Errorf("bad split document expression: %w", err)
2021-10-30 02:04:05 +00:00
}
printerWriter = yqlib.NewMultiPrinterWriter(splitExp, format)
} else {
printerWriter = yqlib.NewSinglePrinterWriter(out)
}
return printerWriter, nil
2021-10-30 02:04:05 +00:00
}
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
2023-04-03 05:40:06 +00:00
case yqlib.TomlOutputFormat:
return yqlib.NewTomlEncoder(), nil
case yqlib.ShellVariablesOutputFormat:
return yqlib.NewShellVariablesEncoder(), 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 yqlib.LuaOutputFormat:
return yqlib.NewLuaEncoder(yqlib.ConfiguredLuaPreferences), nil
}
return nil, fmt.Errorf("invalid encoder: %v", format)
}
2022-01-27 04:54:26 +00:00
// 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, "-")
2022-01-27 04:54:26 +00:00
}
func processArgs(originalArgs []string) (string, []string, error) {
2022-03-01 00:50:09 +00:00
expression := forceExpression
if expressionFile != "" {
expressionBytes, err := os.ReadFile(expressionFile)
if err != nil {
return "", nil, err
}
//replace \r\n (windows) with good ol' unix file endings.
expression = strings.ReplaceAll(string(expressionBytes), "\r\n", "\n")
2022-03-01 00:50:09 +00:00
}
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:]
}
2022-03-01 00:50:09 +00:00
return expression, args, nil
}