mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
e5cc57bedf
When the shell executing yq has no open stdin, os.Stdin.Stat() return nil and yq fails to continue. This commit fixes a missing verification on the result of os.Stdin.Stat() in the utils.processStdInArgs function and adds an acceptance test to cover this scenario in the future. This bug affects yq since version 4.26.1.
276 lines
8.5 KiB
Go
276 lines
8.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"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 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")
|
|
}
|
|
|
|
if writeInplace && splitFileExp != "" {
|
|
return "", nil, fmt.Errorf("write in place 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.IsExplicitlySet() {
|
|
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.LuaInputFormat:
|
|
return yqlib.NewLuaDecoder(yqlib.ConfiguredLuaPreferences), nil
|
|
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, err := os.Stdin.Stat()
|
|
if err != nil {
|
|
yqlib.GetLogger().Debugf("error getting stdin: %v", err)
|
|
}
|
|
pipingStdin := stat != nil && (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
|
|
}
|
|
//replace \r\n (windows) with good ol' unix file endings.
|
|
expression = strings.ReplaceAll(string(expressionBytes), "\r\n", "\n")
|
|
}
|
|
|
|
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
|
|
}
|