2021-10-30 02:04:05 +00:00
package cmd
import (
"fmt"
"io"
"os"
2023-11-09 22:31:29 +00:00
"strings"
2021-10-30 02:04:05 +00:00
"github.com/mikefarah/yq/v4/pkg/yqlib"
"github.com/spf13/cobra"
2022-02-06 21:46:43 +00:00
"gopkg.in/op/go-logging.v1"
2021-10-30 02:04:05 +00:00
)
2023-03-19 22:16:20 +00:00
func isAutomaticOutputFormat ( ) bool {
return outputFormat == "" || outputFormat == "auto" || outputFormat == "a"
}
2022-04-26 23:08:50 +00:00
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
}
2022-04-26 23:08:50 +00:00
expression , args , err := processArgs ( args )
if err != nil {
return "" , nil , err
2021-10-30 02:04:05 +00:00
}
2022-04-29 01:08:41 +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"
}
2022-04-26 23:08:50 +00:00
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" )
2022-04-26 23:08:50 +00:00
}
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
}
2022-04-26 23:08:50 +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
}
2023-03-15 02:22:58 +00:00
inputFilename := ""
if len ( args ) > 0 {
inputFilename = args [ 0 ]
}
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
inputFormat = yqlib . FormatFromFilename ( inputFilename )
2023-03-19 22:16:20 +00:00
_ , 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
2023-03-26 00:00:05 +00:00
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" )
}
2023-03-19 22:16:20 +00:00
outputFormat = inputFormat
2023-03-15 02:22:58 +00:00
}
2023-03-19 22:16:20 +00:00
} else if isAutomaticOutputFormat ( ) {
2023-03-15 02:22:58 +00:00
// 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
}
2023-03-19 22:16:20 +00:00
yqlib . GetLogger ( ) . Debug ( "Using input format %v" , inputFormat )
yqlib . GetLogger ( ) . Debug ( "Using output format %v" , outputFormat )
2023-03-15 02:22:58 +00:00
if outputFormatType == yqlib . YamlOutputFormat ||
outputFormatType == yqlib . PropsOutputFormat {
unwrapScalar = true
}
2023-09-18 23:52:36 +00:00
if unwrapScalarFlag . IsExplicitlySet ( ) {
2023-03-15 02:22:58 +00:00
unwrapScalar = unwrapScalarFlag . IsSet ( )
}
//copy preference form global setting
yqlib . ConfiguredYamlPreferences . UnwrapScalar = unwrapScalar
yqlib . ConfiguredYamlPreferences . PrintDocSeparators = ! noDocSeparators
2022-04-26 23:08:50 +00:00
return expression , args , nil
2021-10-30 02:04:05 +00:00
}
2023-03-15 02:22:58 +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
}
2023-03-01 02:19:06 +00:00
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
2022-02-07 00:55:55 +00:00
case yqlib . XMLInputFormat :
2022-10-25 03:27:16 +00:00
return yqlib . NewXMLDecoder ( yqlib . ConfiguredXMLPreferences ) , nil
2022-02-10 01:02:53 +00:00
case yqlib . PropertiesInputFormat :
return yqlib . NewPropertiesDecoder ( ) , nil
2022-07-27 02:26:22 +00:00
case yqlib . JsonInputFormat :
return yqlib . NewJSONDecoder ( ) , nil
2022-08-01 00:28:34 +00:00
case yqlib . CSVObjectInputFormat :
2024-02-15 02:11:53 +00:00
return yqlib . NewCSVObjectDecoder ( yqlib . ConfiguredCsvPreferences ) , nil
2022-08-01 00:28:34 +00:00
case yqlib . TSVObjectInputFormat :
2024-02-15 02:11:53 +00:00
return yqlib . NewCSVObjectDecoder ( yqlib . ConfiguredTsvPreferences ) , nil
2023-03-25 23:59:15 +00:00
case yqlib . TomlInputFormat :
return yqlib . NewTomlDecoder ( ) , nil
2023-03-01 02:19:06 +00:00
case yqlib . YamlInputFormat :
prefs := yqlib . ConfiguredYamlPreferences
prefs . EvaluateTogether = evaluateTogether
return yqlib . NewYamlDecoder ( prefs ) , nil
2021-12-21 04:02:07 +00:00
}
2023-03-01 02:19:06 +00:00
return nil , fmt . Errorf ( "invalid decoder: %v" , format )
2021-12-21 04:02:07 +00:00
}
2024-02-24 03:58:11 +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 {
2022-01-26 22:29:41 +00:00
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 )
}
2022-01-26 22:29:41 +00:00
return printerWriter , nil
2021-10-30 02:04:05 +00:00
}
2022-01-15 00:57:59 +00:00
2023-03-01 02:19:06 +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
}
2024-02-24 03:58:11 +00:00
func createEncoder ( format * yqlib . PrinterOutputFormat ) ( yqlib . Encoder , error ) {
2024-02-24 04:03:30 +00:00
yqlib . ConfiguredXMLPreferences . Indent = indent
2022-01-15 00:57:59 +00:00
switch format {
2022-02-07 00:55:55 +00:00
case yqlib . JSONOutputFormat :
2023-03-01 02:19:06 +00:00
return yqlib . NewJSONEncoder ( indent , colorsEnabled , unwrapScalar ) , nil
2022-01-15 00:57:59 +00:00
case yqlib . PropsOutputFormat :
2024-02-19 23:57:44 +00:00
return yqlib . NewPropertiesEncoder ( unwrapScalar , yqlib . ConfiguredPropertiesPreferences ) , nil
2022-02-07 00:55:55 +00:00
case yqlib . CSVOutputFormat :
2024-02-17 08:10:13 +00:00
return yqlib . NewCsvEncoder ( yqlib . ConfiguredCsvPreferences ) , nil
2022-02-07 00:55:55 +00:00
case yqlib . TSVOutputFormat :
2024-02-17 08:10:13 +00:00
return yqlib . NewCsvEncoder ( yqlib . ConfiguredTsvPreferences ) , nil
2022-01-15 00:57:59 +00:00
case yqlib . YamlOutputFormat :
2023-03-01 02:19:06 +00:00
return yqlib . NewYamlEncoder ( indent , colorsEnabled , yqlib . ConfiguredYamlPreferences ) , nil
2022-02-07 00:55:55 +00:00
case yqlib . XMLOutputFormat :
2024-02-24 04:03:30 +00:00
return yqlib . NewXMLEncoder ( yqlib . ConfiguredXMLPreferences ) , nil
2023-04-03 05:40:06 +00:00
case yqlib . TomlOutputFormat :
return yqlib . NewTomlEncoder ( ) , nil
2023-05-04 01:06:56 +00:00
case yqlib . ShellVariablesOutputFormat :
return yqlib . NewShellVariablesEncoder ( ) , nil
2023-08-11 02:56:49 +00:00
case yqlib . LuaOutputFormat :
return yqlib . NewLuaEncoder ( yqlib . ConfiguredLuaPreferences ) , nil
2022-01-15 00:57:59 +00:00
}
2023-03-01 02:19:06 +00:00
return nil , fmt . Errorf ( "invalid encoder: %v" , format )
2022-01-15 00:57:59 +00:00
}
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 {
2022-02-06 21:46:43 +00:00
yqlib . GetLogger ( ) . Debugf ( "checking '%v' is a file" , str )
2022-02-06 21:04:26 +00:00
stat , err := os . Stat ( str ) // #nosec
2022-02-06 21:46:43 +00:00
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
}
2022-04-26 23:08:50 +00:00
func processStdInArgs ( args [ ] string ) [ ] string {
2023-11-18 01:20:12 +00:00
stat , err := os . Stdin . Stat ( )
if err != nil {
yqlib . GetLogger ( ) . Debugf ( "error getting stdin: %v" , err )
}
pipingStdin := stat != nil && ( stat . Mode ( ) & os . ModeCharDevice ) == 0
2022-04-26 23:08:50 +00:00
2022-02-20 02:15:21 +00:00
// 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
2022-04-26 23:24:25 +00:00
if nullInput || ! pipingStdin || len ( args ) > 1 || ( len ( args ) > 0 && maybeFile ( args [ 0 ] ) ) {
2022-02-06 21:46:43 +00:00
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
}
2022-02-06 22:27:52 +00:00
2022-04-26 23:08:50 +00:00
func processArgs ( originalArgs [ ] string ) ( string , [ ] string , error ) {
2022-03-01 00:50:09 +00:00
expression := forceExpression
2024-02-15 23:47:50 +00:00
args := processStdInArgs ( originalArgs )
maybeFirstArgIsAFile := len ( args ) > 0 && maybeFile ( args [ 0 ] )
if expressionFile == "" && maybeFirstArgIsAFile && strings . HasSuffix ( args [ 0 ] , ".yq" ) {
// lets check if an expression file was given
yqlib . GetLogger ( ) . Debug ( "Assuming arg %v is an expression file" , args [ 0 ] )
expressionFile = args [ 0 ]
args = args [ 1 : ]
}
2022-03-01 00:50:09 +00:00
if expressionFile != "" {
expressionBytes , err := os . ReadFile ( expressionFile )
if err != nil {
return "" , nil , err
}
2023-11-09 22:31:29 +00:00
//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
}
2022-02-06 22:27:52 +00:00
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
2022-02-06 22:27:52 +00:00
}