Repository: TomWright/dasel
Pull request #129 (2021-04-21 09:25:34)

Merge documents


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28dfcaf..0866d2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- `--merge-input-documents` flag.
+
 ### Changed
 
 - Optional `noupdater` build tag to disable the self-update command.
diff --git a/internal/command/put.go b/internal/command/put.go
index 6dbbdc9..c32cdc1 100644
--- a/internal/command/put.go
+++ b/internal/command/put.go
@@ -98,9 +98,10 @@ func getWriteParser(readParser storage.ReadParser, writeParserFlag string, parse
 }
 
 type getRootNodeOpts struct {
-	File   string
-	Reader io.Reader
-	Parser storage.ReadParser
+	File                string
+	Reader              io.Reader
+	Parser              storage.ReadParser
+	MergeInputDocuments bool
 }
 
 func getRootNode(opts getRootNodeOpts, cmd *cobra.Command) (*dasel.Node, error) {
@@ -122,6 +123,15 @@ func getRootNode(opts getRootNodeOpts, cmd *cobra.Command) (*dasel.Node, error)
 		return nil, fmt.Errorf("could not load input: %w", err)
 	}
 
+	if opts.MergeInputDocuments {
+		switch val := value.(type) {
+		case storage.SingleDocument:
+			value = &storage.BasicSingleDocument{Value: []interface{}{val.Document()}}
+		case storage.MultiDocument:
+			value = &storage.BasicSingleDocument{Value: val.Documents()}
+		}
+	}
+
 	return dasel.New(value), nil
 }
 
@@ -260,7 +270,7 @@ func getOutputWriter(cmd *cobra.Command, in io.Writer, file string, out string)
 
 func putCommand() *cobra.Command {
 	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag, outFlag string
-	var multiFlag, compactFlag bool
+	var multiFlag, compactFlag, mergeInputDocumentsFlag bool
 
 	cmd := &cobra.Command{
 		Use:   "put -f <file> -s <selector>",
@@ -283,6 +293,7 @@ func putCommand() *cobra.Command {
 	cmd.PersistentFlags().StringVarP(&outFlag, "out", "o", "", "Output destination.")
 	cmd.PersistentFlags().BoolVarP(&multiFlag, "multiple", "m", false, "Select multiple results.")
 	cmd.PersistentFlags().BoolVarP(&compactFlag, "compact", "c", false, "Compact the output by removing all pretty-printing where possible.")
+	cmd.PersistentFlags().BoolVar(&mergeInputDocumentsFlag, "merge-input-documents", false, "Merge multiple input documents into an array.")
 
 	_ = cmd.MarkPersistentFlagFilename("file")
 
@@ -290,19 +301,20 @@ func putCommand() *cobra.Command {
 }
 
 type genericPutOptions struct {
-	File        string
-	Out         string
-	Parser      string
-	ReadParser  string
-	WriteParser string
-	Selector    string
-	Value       string
-	ValueType   string
-	Init        func(genericPutOptions) genericPutOptions
-	Reader      io.Reader
-	Writer      io.Writer
-	Multi       bool
-	Compact     bool
+	File                string
+	Out                 string
+	Parser              string
+	ReadParser          string
+	WriteParser         string
+	Selector            string
+	Value               string
+	ValueType           string
+	Init                func(genericPutOptions) genericPutOptions
+	Reader              io.Reader
+	Writer              io.Writer
+	Multi               bool
+	Compact             bool
+	MergeInputDocuments bool
 }
 
 func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOptions) genericPutOptions {
@@ -315,6 +327,7 @@ func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOp
 		opts.Selector = cmd.Flag("selector").Value.String()
 		opts.Multi, _ = cmd.Flags().GetBool("multiple")
 		opts.Compact, _ = cmd.Flags().GetBool("compact")
+		opts.MergeInputDocuments, _ = cmd.Flags().GetBool("merge-input-documents")
 
 		if opts.Selector == "" && len(args) > 0 {
 			opts.Selector = args[0]
@@ -338,9 +351,10 @@ func runGenericPutCommand(opts genericPutOptions, cmd *cobra.Command) error {
 		return err
 	}
 	rootNode, err := getRootNode(getRootNodeOpts{
-		File:   opts.File,
-		Parser: readParser,
-		Reader: opts.Reader,
+		File:                opts.File,
+		Parser:              readParser,
+		Reader:              opts.Reader,
+		MergeInputDocuments: opts.MergeInputDocuments,
 	}, cmd)
 	if err != nil {
 		return err
diff --git a/internal/command/put_document_internal.go b/internal/command/put_document_internal.go
index 99ee468..75dfcb0 100644
--- a/internal/command/put_document_internal.go
+++ b/internal/command/put_document_internal.go
@@ -8,18 +8,19 @@ import (
 )
 
 type putDocumentOpts struct {
-	File           string
-	Out            string
-	ReadParser     string
-	WriteParser    string
-	Parser         string
-	Selector       string
-	DocumentString string
-	DocumentParser string
-	Reader         io.Reader
-	Writer         io.Writer
-	Multi          bool
-	Compact        bool
+	File                string
+	Out                 string
+	ReadParser          string
+	WriteParser         string
+	Parser              string
+	Selector            string
+	DocumentString      string
+	DocumentParser      string
+	Reader              io.Reader
+	Writer              io.Writer
+	Multi               bool
+	Compact             bool
+	MergeInputDocuments bool
 }
 
 func runPutDocumentCommand(opts putDocumentOpts, cmd *cobra.Command) error {
@@ -28,9 +29,10 @@ func runPutDocumentCommand(opts putDocumentOpts, cmd *cobra.Command) error {
 		return err
 	}
 	rootNode, err := getRootNode(getRootNodeOpts{
-		File:   opts.File,
-		Parser: readParser,
-		Reader: opts.Reader,
+		File:                opts.File,
+		Parser:              readParser,
+		Reader:              opts.Reader,
+		MergeInputDocuments: opts.MergeInputDocuments,
 	}, cmd)
 	if err != nil {
 		return err
diff --git a/internal/command/put_object_internal.go b/internal/command/put_object_internal.go
index 3017cde..42c4ae3 100644
--- a/internal/command/put_object_internal.go
+++ b/internal/command/put_object_internal.go
@@ -10,18 +10,19 @@ import (
 )
 
 type putObjectOpts struct {
-	File        string
-	Out         string
-	ReadParser  string
-	WriteParser string
-	Parser      string
-	Selector    string
-	InputTypes  []string
-	InputValues []string
-	Reader      io.Reader
-	Writer      io.Writer
-	Multi       bool
-	Compact     bool
+	File                string
+	Out                 string
+	ReadParser          string
+	WriteParser         string
+	Parser              string
+	Selector            string
+	InputTypes          []string
+	InputValues         []string
+	Reader              io.Reader
+	Writer              io.Writer
+	Multi               bool
+	Compact             bool
+	MergeInputDocuments bool
 }
 
 func getMapFromTypesValues(inputTypes []string, inputValues []string) (map[string]interface{}, error) {
@@ -51,9 +52,10 @@ func runPutObjectCommand(opts putObjectOpts, cmd *cobra.Command) error {
 		return err
 	}
 	rootNode, err := getRootNode(getRootNodeOpts{
-		File:   opts.File,
-		Parser: readParser,
-		Reader: opts.Reader,
+		File:                opts.File,
+		Parser:              readParser,
+		Reader:              opts.Reader,
+		MergeInputDocuments: opts.MergeInputDocuments,
 	}, cmd)
 	if err != nil {
 		return err
diff --git a/internal/command/select.go b/internal/command/select.go
index 08a5009..f815513 100644
--- a/internal/command/select.go
+++ b/internal/command/select.go
@@ -10,17 +10,18 @@ import (
 )
 
 type selectOptions struct {
-	File              string
-	Parser            string
-	ReadParser        string
-	WriteParser       string
-	Selector          string
-	Reader            io.Reader
-	Writer            io.Writer
-	Multi             bool
-	NullValueNotFound bool
-	Compact           bool
-	DisplayLength     bool
+	File                string
+	Parser              string
+	ReadParser          string
+	WriteParser         string
+	Selector            string
+	Reader              io.Reader
+	Writer              io.Writer
+	Multi               bool
+	NullValueNotFound   bool
+	Compact             bool
+	DisplayLength       bool
+	MergeInputDocuments bool
 }
 
 func outputNodeLength(writer io.Writer, nodes ...*dasel.Node) error {
@@ -86,9 +87,10 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
 		return err
 	}
 	rootNode, err := getRootNode(getRootNodeOpts{
-		File:   opts.File,
-		Parser: readParser,
-		Reader: opts.Reader,
+		File:                opts.File,
+		Parser:              readParser,
+		Reader:              opts.Reader,
+		MergeInputDocuments: opts.MergeInputDocuments,
 	}, cmd)
 	if err != nil {
 		return err
@@ -161,7 +163,7 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
 
 func selectCommand() *cobra.Command {
 	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag string
-	var plainFlag, multiFlag, nullValueNotFoundFlag, compactFlag, lengthFlag bool
+	var plainFlag, multiFlag, nullValueNotFoundFlag, compactFlag, lengthFlag, mergeInputDocumentsFlag bool
 
 	cmd := &cobra.Command{
 		Use:   "select -f <file> -p <json,yaml> -s <selector>",
@@ -175,15 +177,16 @@ func selectCommand() *cobra.Command {
 				writeParserFlag = "-"
 			}
 			return runSelectCommand(selectOptions{
-				File:              fileFlag,
-				Parser:            parserFlag,
-				ReadParser:        readParserFlag,
-				WriteParser:       writeParserFlag,
-				Selector:          selectorFlag,
-				Multi:             multiFlag,
-				NullValueNotFound: nullValueNotFoundFlag,
-				Compact:           compactFlag,
-				DisplayLength:     lengthFlag,
+				File:                fileFlag,
+				Parser:              parserFlag,
+				ReadParser:          readParserFlag,
+				WriteParser:         writeParserFlag,
+				Selector:            selectorFlag,
+				Multi:               multiFlag,
+				NullValueNotFound:   nullValueNotFoundFlag,
+				Compact:             compactFlag,
+				DisplayLength:       lengthFlag,
+				MergeInputDocuments: mergeInputDocumentsFlag,
 			}, cmd)
 		},
 	}
@@ -197,7 +200,8 @@ func selectCommand() *cobra.Command {
 	cmd.Flags().BoolVarP(&multiFlag, "multiple", "m", false, "Select multiple results.")
 	cmd.Flags().BoolVarP(&nullValueNotFoundFlag, "null", "n", false, "Output null instead of value not found errors.")
 	cmd.Flags().BoolVar(&lengthFlag, "length", false, "Output the length of the selected value.")
-	cmd.PersistentFlags().BoolVarP(&compactFlag, "compact", "c", false, "Compact the output by removing all pretty-printing where possible.")
+	cmd.Flags().BoolVar(&mergeInputDocumentsFlag, "merge-input-documents", false, "Merge multiple input documents into an array.")
+	cmd.Flags().BoolVarP(&compactFlag, "compact", "c", false, "Compact the output by removing all pretty-printing where possible.")
 
 	_ = cmd.MarkFlagFilename("file")
 


Test patch:
diff --git a/internal/command/root_put_test.go b/internal/command/root_put_test.go
index 2eddc6d..9a092f2 100644
--- a/internal/command/root_put_test.go
+++ b/internal/command/root_put_test.go
@@ -630,6 +630,29 @@ baz:
 foo: true
 `, nil, "-d", "json"))
 
+	t.Run("MergeInputDocumentsPut", putIntTest(`
+{
+"number": 1
+}
+{
+"number": 2
+}
+{
+"number": 3
+}
+`, "json", ".[0].number", `4`, `[
+  {
+    "number": 4
+  },
+  {
+    "number": 2
+  },
+  {
+    "number": 3
+  }
+]
+`, nil, "--merge-input-documents"))
+
 }
 
 func TestRootCMD_Put_YAML(t *testing.T) {
diff --git a/internal/command/root_select_test.go b/internal/command/root_select_test.go
index 8ca2e5c..dd669ae 100644
--- a/internal/command/root_select_test.go
+++ b/internal/command/root_select_test.go
@@ -455,6 +455,28 @@ func TestRootCmd_Select_JSON(t *testing.T) {
   }
 ]`, "json", `.(.uses.[#]>2).id`, newline("3"), nil, "-m"))
 
+	t.Run("MergeInputDocuments", selectTest(`{
+  "number": 1
+}
+{
+  "number": 2
+}
+{
+  "number": 3
+}
+`, "json", `.`, `[
+  {
+    "number": 1
+  },
+  {
+    "number": 2
+  },
+  {
+    "number": 3
+  }
+]
+`, nil, "--merge-input-documents"))
+
 }
 
 func TestRootCmd_Select_YAML(t *testing.T) {
diff --git a/internal/storage/yaml_test.go b/internal/storage/yaml_test.go
index 5890c2d..9df82a1 100644
--- a/internal/storage/yaml_test.go
+++ b/internal/storage/yaml_test.go
@@ -8,9 +8,16 @@ import (
 )
 
 var yamlBytes = []byte(`name: Tom
+numbers:
+- 1
+- 2
 `)
 var yamlMap = map[string]interface{}{
 	"name": "Tom",
+	"numbers": []interface{}{
+		1,
+		2,
+	},
 }
 
 var yamlBytesMulti = []byte(`name: Tom