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 }