mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 13:48:06 +00:00
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(yqlib.ConfiguredCsvPreferences), nil
|
|
case yqlib.TSVObjectInputFormat:
|
|
return yqlib.NewCSVObjectDecoder(yqlib.ConfiguredTsvPreferences), 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
|
|
}
|