github.com/tomwright/dasel@v1.27.3/internal/command/put.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/spf13/cobra"
     8  	"github.com/tomwright/dasel"
     9  	"github.com/tomwright/dasel/storage"
    10  	"io"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  func parseValue(value string, valueType string) (interface{}, error) {
    17  	switch strings.ToLower(valueType) {
    18  	case "string", "str":
    19  		return value, nil
    20  	case "int", "integer":
    21  		val, err := strconv.ParseInt(value, 10, 64)
    22  		if err != nil {
    23  			return nil, fmt.Errorf("could not parse int [%s]: %w", value, err)
    24  		}
    25  		return val, nil
    26  	case "bool", "boolean":
    27  		switch strings.ToLower(value) {
    28  		case "true", "t", "yes", "y", "1":
    29  			return true, nil
    30  		case "false", "f", "no", "n", "0":
    31  			return false, nil
    32  		default:
    33  			return nil, fmt.Errorf("could not parse bool [%s]: unhandled value", value)
    34  		}
    35  	default:
    36  		return nil, fmt.Errorf("unhandled type: %s", valueType)
    37  	}
    38  }
    39  
    40  func shouldReadFromStdin(fileFlag string) bool {
    41  	return fileFlag == "" || fileFlag == "stdin" || fileFlag == "-"
    42  }
    43  
    44  func shouldWriteToStdout(fileFlag string, outFlag string) bool {
    45  	return (outFlag == "stdout" || outFlag == "-") || outFlag == "" && shouldReadFromStdin(fileFlag)
    46  }
    47  
    48  func getReadParser(fileFlag string, readParserFlag string, parserFlag string) (storage.ReadParser, error) {
    49  	useStdin := shouldReadFromStdin(fileFlag)
    50  
    51  	if readParserFlag == "" {
    52  		readParserFlag = parserFlag
    53  	}
    54  
    55  	if useStdin && readParserFlag == "" {
    56  		return nil, fmt.Errorf("read parser flag required when reading from stdin")
    57  	}
    58  
    59  	if readParserFlag == "" {
    60  		parser, err := storage.NewReadParserFromFilename(fileFlag)
    61  		if err != nil {
    62  			return nil, fmt.Errorf("could not get read parser from filename: %w", err)
    63  		}
    64  		return parser, nil
    65  	}
    66  	parser, err := storage.NewReadParserFromString(readParserFlag)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("could not get read parser: %w", err)
    69  	}
    70  	return parser, nil
    71  }
    72  
    73  func getWriteParser(readParser storage.ReadParser, writeParserFlag string, parserFlag string,
    74  	outFlag string, fileFlag string, formatTemplateFlag string) (storage.WriteParser, error) {
    75  	if formatTemplateFlag != "" {
    76  		writeParserFlag = "plain"
    77  	}
    78  
    79  	if writeParserFlag == "" {
    80  		writeParserFlag = parserFlag
    81  	}
    82  
    83  	if writeParserFlag != "" {
    84  		parser, err := storage.NewWriteParserFromString(writeParserFlag)
    85  		if err != nil {
    86  			return nil, fmt.Errorf("could not get write parser: %w", err)
    87  		}
    88  		return parser, nil
    89  	}
    90  
    91  	if !shouldWriteToStdout(fileFlag, outFlag) {
    92  		p, err := storage.NewWriteParserFromFilename(fileFlag)
    93  		if err != nil {
    94  			return nil, fmt.Errorf("could not get write parser from filename: %w", err)
    95  		}
    96  		return p, nil
    97  	}
    98  
    99  	if p, ok := readParser.(storage.WriteParser); ok {
   100  		return p, nil
   101  	}
   102  	return nil, fmt.Errorf("read parser cannot be used to write. please specify a write parser")
   103  }
   104  
   105  type getRootNodeOpts struct {
   106  	File                string
   107  	Reader              io.Reader
   108  	Parser              storage.ReadParser
   109  	MergeInputDocuments bool
   110  }
   111  
   112  func getRootNode(opts getRootNodeOpts, cmd *cobra.Command) (*dasel.Node, error) {
   113  	if opts.Reader == nil {
   114  		if shouldReadFromStdin(opts.File) {
   115  			opts.Reader = cmd.InOrStdin()
   116  		} else {
   117  			f, err := os.Open(opts.File)
   118  			if err != nil {
   119  				return nil, fmt.Errorf("could not open input file: %w", err)
   120  			}
   121  			defer f.Close()
   122  			opts.Reader = f
   123  		}
   124  	}
   125  
   126  	value, err := storage.Load(opts.Parser, opts.Reader)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("could not load input: %w", err)
   129  	}
   130  
   131  	if opts.MergeInputDocuments {
   132  		switch val := value.(type) {
   133  		case storage.SingleDocument:
   134  			value = &storage.BasicSingleDocument{Value: []interface{}{val.Document()}}
   135  		case storage.MultiDocument:
   136  			value = &storage.BasicSingleDocument{Value: val.Documents()}
   137  		}
   138  	}
   139  
   140  	return dasel.New(value), nil
   141  }
   142  
   143  type writeNodeToOutputOpts struct {
   144  	Node           *dasel.Node
   145  	Parser         storage.WriteParser
   146  	File           string
   147  	Out            string
   148  	Writer         io.Writer
   149  	FormatTemplate string
   150  }
   151  
   152  type customErrorHandlingOpts struct {
   153  	File     string
   154  	Out      string
   155  	Writer   io.Writer
   156  	Err      error
   157  	Cmd      *cobra.Command
   158  	NullFlag bool
   159  }
   160  
   161  func customErrorHandling(opts customErrorHandlingOpts) (bool, error) {
   162  	if opts.Err == nil {
   163  		return false, nil
   164  	}
   165  
   166  	if !opts.NullFlag {
   167  		return false, opts.Err
   168  	}
   169  
   170  	var valNotFound *dasel.ValueNotFound
   171  	if !errors.As(opts.Err, &valNotFound) {
   172  		return false, opts.Err
   173  	}
   174  
   175  	if err := writeStringToOutput("null\n", opts.File, opts.Out, opts.Writer, opts.Cmd); err != nil {
   176  		return false, fmt.Errorf("could not write string to output: %w", err)
   177  	}
   178  
   179  	return true, nil
   180  }
   181  
   182  func writeStringToOutput(value string, file string, out string, writer io.Writer, cmd *cobra.Command) error {
   183  	writer, writerCleanUp, err := getOutputWriter(cmd, writer, file, out)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	defer writerCleanUp()
   188  
   189  	if _, err := writer.Write([]byte(value)); err != nil {
   190  		return fmt.Errorf("could not write to output file: %w", err)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func writeNodeToOutput(opts writeNodeToOutputOpts, cmd *cobra.Command, options ...storage.ReadWriteOption) error {
   197  	writer, writerCleanUp, err := getOutputWriter(cmd, opts.Writer, opts.File, opts.Out)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	opts.Writer = writer
   202  	defer writerCleanUp()
   203  
   204  	var value, originalValue interface{}
   205  	if opts.FormatTemplate == "" {
   206  		value = opts.Node.InterfaceValue()
   207  		originalValue = opts.Node.OriginalValue
   208  	} else {
   209  		result, err := dasel.FormatNode(opts.Node, opts.FormatTemplate)
   210  		if err != nil {
   211  			return fmt.Errorf("could not format node: %w", err)
   212  		}
   213  		value = result.String()
   214  		originalValue = value
   215  	}
   216  
   217  	if err := storage.Write(opts.Parser, value, originalValue, opts.Writer, options...); err != nil {
   218  		return fmt.Errorf("could not write to output file: %w", err)
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  type writeNodesToOutputOpts struct {
   225  	Nodes          []*dasel.Node
   226  	Parser         storage.WriteParser
   227  	File           string
   228  	Out            string
   229  	Writer         io.Writer
   230  	FormatTemplate string
   231  }
   232  
   233  func writeNodesToOutput(opts writeNodesToOutputOpts, cmd *cobra.Command, options ...storage.ReadWriteOption) error {
   234  	writer, writerCleanUp, err := getOutputWriter(cmd, opts.Writer, opts.File, opts.Out)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	opts.Writer = writer
   239  	defer writerCleanUp()
   240  
   241  	buf := new(bytes.Buffer)
   242  
   243  	for i, n := range opts.Nodes {
   244  		subOpts := writeNodeToOutputOpts{
   245  			Node:           n,
   246  			Parser:         opts.Parser,
   247  			Writer:         buf,
   248  			FormatTemplate: opts.FormatTemplate,
   249  		}
   250  		if err := writeNodeToOutput(subOpts, cmd, options...); err != nil {
   251  			return fmt.Errorf("could not write node %d to output: %w", i, err)
   252  		}
   253  	}
   254  
   255  	if _, err := io.Copy(opts.Writer, buf); err != nil {
   256  		return fmt.Errorf("could not copy buffer to real output: %w", err)
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func getOutputWriter(cmd *cobra.Command, in io.Writer, file string, out string) (io.Writer, func(), error) {
   263  	if in == nil {
   264  		switch {
   265  		case shouldWriteToStdout(file, out):
   266  			return cmd.OutOrStdout(), func() {}, nil
   267  
   268  		case out == "":
   269  			// No out flag... write to the file we read from.
   270  			f, err := os.Create(file)
   271  			if err != nil {
   272  				return nil, nil, fmt.Errorf("could not open output file: %w", err)
   273  			}
   274  			return f, func() {
   275  				_ = f.Close()
   276  			}, nil
   277  
   278  		case out != "":
   279  			// Out flag was set.
   280  			f, err := os.Create(out)
   281  			if err != nil {
   282  				return nil, nil, fmt.Errorf("could not open output file: %w", err)
   283  			}
   284  			return f, func() {
   285  				_ = f.Close()
   286  			}, nil
   287  		}
   288  	}
   289  	return in, func() {}, nil
   290  }
   291  
   292  func putCommand() *cobra.Command {
   293  	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag, outFlag, valueFlag, valueFileFlag string
   294  	var multiFlag, compactFlag, mergeInputDocumentsFlag, escapeHTMLFlag bool
   295  
   296  	cmd := &cobra.Command{
   297  		Use:   "put -f <file> -s <selector>",
   298  		Short: "Update properties in the given file.",
   299  	}
   300  
   301  	cmd.AddCommand(
   302  		putStringCommand(),
   303  		putBoolCommand(),
   304  		putIntCommand(),
   305  		putObjectCommand(),
   306  		putDocumentCommand(),
   307  	)
   308  
   309  	cmd.PersistentFlags().StringVarP(&fileFlag, "file", "f", "", "The file to query.")
   310  	cmd.PersistentFlags().StringVarP(&selectorFlag, "selector", "s", "", "The selector to use when querying the data structure.")
   311  	cmd.PersistentFlags().StringVarP(&parserFlag, "parser", "p", "", "Shorthand for -r FORMAT -w FORMAT.")
   312  	cmd.PersistentFlags().StringVarP(&readParserFlag, "read", "r", "", "The parser to use when reading.")
   313  	cmd.PersistentFlags().StringVarP(&writeParserFlag, "write", "w", "", "The parser to use when writing.")
   314  	cmd.PersistentFlags().StringVarP(&outFlag, "out", "o", "", "Output destination.")
   315  	cmd.PersistentFlags().BoolVarP(&multiFlag, "multiple", "m", false, "Select multiple results.")
   316  	cmd.PersistentFlags().BoolVarP(&compactFlag, "compact", "c", false, "Compact the output by removing all pretty-printing where possible.")
   317  	cmd.PersistentFlags().BoolVar(&mergeInputDocumentsFlag, "merge-input-documents", false, "Merge multiple input documents into an array.")
   318  	cmd.PersistentFlags().StringVarP(&valueFlag, "value", "v", "", "Value to put.")
   319  	cmd.PersistentFlags().StringVar(&valueFileFlag, "value-file", "", "File containing value to put.")
   320  	cmd.PersistentFlags().BoolVar(&escapeHTMLFlag, "escape-html", false, "Escape HTML tags when writing output.")
   321  
   322  	_ = cmd.MarkPersistentFlagFilename("file")
   323  
   324  	return cmd
   325  }
   326  
   327  type genericPutOptions struct {
   328  	File                string
   329  	Out                 string
   330  	Parser              string
   331  	ReadParser          string
   332  	WriteParser         string
   333  	Selector            string
   334  	Value               string
   335  	ValueFile           string
   336  	ValueType           string
   337  	Init                func(genericPutOptions) genericPutOptions
   338  	Reader              io.Reader
   339  	Writer              io.Writer
   340  	Multi               bool
   341  	Compact             bool
   342  	MergeInputDocuments bool
   343  	EscapeHTML          bool
   344  }
   345  
   346  func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOptions) genericPutOptions {
   347  	return func(opts genericPutOptions) genericPutOptions {
   348  		opts.File = cmd.Flag("file").Value.String()
   349  		opts.Out = cmd.Flag("out").Value.String()
   350  		opts.Parser = cmd.Flag("parser").Value.String()
   351  		opts.ReadParser = cmd.Flag("read").Value.String()
   352  		opts.WriteParser = cmd.Flag("write").Value.String()
   353  		opts.Selector = cmd.Flag("selector").Value.String()
   354  		opts.Multi, _ = cmd.Flags().GetBool("multiple")
   355  		opts.Compact, _ = cmd.Flags().GetBool("compact")
   356  		opts.MergeInputDocuments, _ = cmd.Flags().GetBool("merge-input-documents")
   357  		opts.Value, _ = cmd.Flags().GetString("value")
   358  		opts.ValueFile, _ = cmd.Flags().GetString("value-file")
   359  		opts.EscapeHTML, _ = cmd.Flags().GetBool("escape-html")
   360  
   361  		if opts.Selector == "" && len(args) > 0 {
   362  			opts.Selector = args[0]
   363  			args = args[1:]
   364  		}
   365  
   366  		if opts.Value == "" && opts.ValueFile == "" && len(args) > 0 {
   367  			opts.Value = args[0]
   368  			args = args[1:]
   369  		}
   370  
   371  		return opts
   372  	}
   373  }
   374  
   375  func runGenericPutCommand(opts genericPutOptions, cmd *cobra.Command) error {
   376  	if opts.Init != nil {
   377  		opts = opts.Init(opts)
   378  	}
   379  	readParser, err := getReadParser(opts.File, opts.ReadParser, opts.Parser)
   380  	if err != nil {
   381  		return err
   382  	}
   383  	rootNode, err := getRootNode(getRootNodeOpts{
   384  		File:                opts.File,
   385  		Parser:              readParser,
   386  		Reader:              opts.Reader,
   387  		MergeInputDocuments: opts.MergeInputDocuments,
   388  	}, cmd)
   389  	if err != nil {
   390  		return err
   391  	}
   392  
   393  	if opts.ValueFile != "" {
   394  		valueFile, err := readFileContents(opts.ValueFile)
   395  		if err != nil {
   396  			return err
   397  		}
   398  		opts.Value = string(valueFile)
   399  	}
   400  
   401  	updateValue, err := parseValue(opts.Value, opts.ValueType)
   402  	if err != nil {
   403  		return err
   404  	}
   405  
   406  	if opts.Multi {
   407  		if err := rootNode.PutMultiple(opts.Selector, updateValue); err != nil {
   408  			return fmt.Errorf("could not put multi value: %w", err)
   409  		}
   410  	} else {
   411  		if err := rootNode.Put(opts.Selector, updateValue); err != nil {
   412  			return fmt.Errorf("could not put value: %w", err)
   413  		}
   414  	}
   415  
   416  	writeParser, err := getWriteParser(readParser, opts.WriteParser, opts.Parser, opts.Out, opts.File, "")
   417  	if err != nil {
   418  		return err
   419  	}
   420  
   421  	writeOptions := []storage.ReadWriteOption{
   422  		storage.EscapeHTMLOption(opts.EscapeHTML),
   423  	}
   424  
   425  	if opts.Compact {
   426  		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
   427  	}
   428  
   429  	if err := writeNodeToOutput(writeNodeToOutputOpts{
   430  		Node:   rootNode,
   431  		Parser: writeParser,
   432  		File:   opts.File,
   433  		Out:    opts.Out,
   434  		Writer: opts.Writer,
   435  	}, cmd, writeOptions...); err != nil {
   436  		return fmt.Errorf("could not write output: %w", err)
   437  	}
   438  
   439  	return nil
   440  }