Repository: TomWright/dasel
Pull request #168 (2021-09-29 09:44:57)

Add --escape-html flag


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ce24b3..77ce47d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
-- Nothing yet.
+### Added
+- `--escape-html` flag.
+
+### Fixed
+- `put document` and `put object` are now aware of the `--merge-input-documents` flag.
 
 ## [v1.20.1] - 2021-08-28
 
diff --git a/internal/command/delete.go b/internal/command/delete.go
index 06662b7..3cee5e6 100644
--- a/internal/command/delete.go
+++ b/internal/command/delete.go
@@ -20,6 +20,7 @@ type deleteOptions struct {
 	Compact             bool
 	MergeInputDocuments bool
 	Out                 string
+	EscapeHTML          bool
 }
 
 func runDeleteMultiCommand(cmd *cobra.Command, rootNode *dasel.Node, opts deleteOptions, writeParser storage.WriteParser, writeOptions []storage.ReadWriteOption) error {
@@ -78,7 +79,9 @@ func runDeleteCommand(opts deleteOptions, cmd *cobra.Command) error {
 		return err
 	}
 
-	writeOptions := make([]storage.ReadWriteOption, 0)
+	writeOptions := []storage.ReadWriteOption{
+		storage.EscapeHTMLOption(opts.EscapeHTML),
+	}
 
 	if opts.Compact {
 		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
@@ -121,7 +124,7 @@ func runDeleteCommand(opts deleteOptions, cmd *cobra.Command) error {
 
 func deleteCommand() *cobra.Command {
 	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag, outFlag string
-	var plainFlag, multiFlag, compactFlag, mergeInputDocumentsFlag bool
+	var plainFlag, multiFlag, compactFlag, mergeInputDocumentsFlag, escapeHTMLFlag bool
 
 	cmd := &cobra.Command{
 		Use:   "delete -f <file> -p <json,yaml> -s <selector>",
@@ -144,6 +147,7 @@ func deleteCommand() *cobra.Command {
 				Compact:             compactFlag,
 				MergeInputDocuments: mergeInputDocumentsFlag,
 				Out:                 outFlag,
+				EscapeHTML:          escapeHTMLFlag,
 			}, cmd)
 		},
 	}
@@ -157,6 +161,7 @@ func deleteCommand() *cobra.Command {
 	cmd.Flags().BoolVarP(&multiFlag, "multiple", "m", false, "Delete multiple results.")
 	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.Flags().BoolVar(&escapeHTMLFlag, "escape-html", true, "Escape HTML tags when writing output.")
 	cmd.Flags().StringVarP(&outFlag, "out", "o", "", "Output destination.")
 
 	_ = cmd.MarkFlagFilename("file")
diff --git a/internal/command/put.go b/internal/command/put.go
index d1f4a5f..3057946 100644
--- a/internal/command/put.go
+++ b/internal/command/put.go
@@ -291,7 +291,7 @@ func getOutputWriter(cmd *cobra.Command, in io.Writer, file string, out string)
 
 func putCommand() *cobra.Command {
 	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag, outFlag, valueFlag string
-	var multiFlag, compactFlag, mergeInputDocumentsFlag bool
+	var multiFlag, compactFlag, mergeInputDocumentsFlag, escapeHTMLFlag bool
 
 	cmd := &cobra.Command{
 		Use:   "put -f <file> -s <selector>",
@@ -316,6 +316,7 @@ func putCommand() *cobra.Command {
 	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.PersistentFlags().StringVarP(&valueFlag, "value", "v", "", "Value to put.")
+	cmd.PersistentFlags().BoolVar(&escapeHTMLFlag, "escape-html", true, "Escape HTML tags when writing output.")
 
 	_ = cmd.MarkPersistentFlagFilename("file")
 
@@ -337,6 +338,7 @@ type genericPutOptions struct {
 	Multi               bool
 	Compact             bool
 	MergeInputDocuments bool
+	EscapeHTML          bool
 }
 
 func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOptions) genericPutOptions {
@@ -351,6 +353,7 @@ func getGenericInit(cmd *cobra.Command, args []string) func(options genericPutOp
 		opts.Compact, _ = cmd.Flags().GetBool("compact")
 		opts.MergeInputDocuments, _ = cmd.Flags().GetBool("merge-input-documents")
 		opts.Value, _ = cmd.Flags().GetString("value")
+		opts.EscapeHTML, _ = cmd.Flags().GetBool("escape-html")
 
 		if opts.Selector == "" && len(args) > 0 {
 			opts.Selector = args[0]
@@ -404,7 +407,9 @@ func runGenericPutCommand(opts genericPutOptions, cmd *cobra.Command) error {
 		return err
 	}
 
-	writeOptions := make([]storage.ReadWriteOption, 0)
+	writeOptions := []storage.ReadWriteOption{
+		storage.EscapeHTMLOption(opts.EscapeHTML),
+	}
 
 	if opts.Compact {
 		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
diff --git a/internal/command/put_document_internal.go b/internal/command/put_document_internal.go
index 0028b94..0998ee0 100644
--- a/internal/command/put_document_internal.go
+++ b/internal/command/put_document_internal.go
@@ -21,6 +21,7 @@ type putDocumentOpts struct {
 	Multi               bool
 	Compact             bool
 	MergeInputDocuments bool
+	EscapeHTML          bool
 }
 
 func runPutDocumentCommand(opts putDocumentOpts, cmd *cobra.Command) error {
@@ -63,7 +64,9 @@ func runPutDocumentCommand(opts putDocumentOpts, cmd *cobra.Command) error {
 		return err
 	}
 
-	writeOptions := make([]storage.ReadWriteOption, 0)
+	writeOptions := []storage.ReadWriteOption{
+		storage.EscapeHTMLOption(opts.EscapeHTML),
+	}
 
 	if opts.Compact {
 		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
@@ -102,6 +105,8 @@ func putDocumentCommand() *cobra.Command {
 			opts.Multi, _ = cmd.Flags().GetBool("multiple")
 			opts.Compact, _ = cmd.Flags().GetBool("compact")
 			opts.DocumentString, _ = cmd.Flags().GetString("value")
+			opts.MergeInputDocuments, _ = cmd.Flags().GetBool("merge-input-documents")
+			opts.EscapeHTML, _ = cmd.Flags().GetBool("escape-html")
 
 			if opts.Selector == "" && len(args) > 0 {
 				opts.Selector = args[0]
diff --git a/internal/command/put_object_internal.go b/internal/command/put_object_internal.go
index 385ef4a..89c8045 100644
--- a/internal/command/put_object_internal.go
+++ b/internal/command/put_object_internal.go
@@ -23,6 +23,7 @@ type putObjectOpts struct {
 	Multi               bool
 	Compact             bool
 	MergeInputDocuments bool
+	EscapeHTML          bool
 }
 
 func getMapFromTypesValues(inputTypes []string, inputValues []string) (map[string]interface{}, error) {
@@ -81,7 +82,9 @@ func runPutObjectCommand(opts putObjectOpts, cmd *cobra.Command) error {
 		return err
 	}
 
-	writeOptions := make([]storage.ReadWriteOption, 0)
+	writeOptions := []storage.ReadWriteOption{
+		storage.EscapeHTMLOption(opts.EscapeHTML),
+	}
 
 	if opts.Compact {
 		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
@@ -120,6 +123,8 @@ func putObjectCommand() *cobra.Command {
 			}
 			opts.Multi, _ = cmd.Flags().GetBool("multiple")
 			opts.Compact, _ = cmd.Flags().GetBool("compact")
+			opts.MergeInputDocuments, _ = cmd.Flags().GetBool("merge-input-documents")
+			opts.EscapeHTML, _ = cmd.Flags().GetBool("escape-html")
 
 			if opts.Selector == "" && len(opts.InputValues) > 0 {
 				opts.Selector = opts.InputValues[0]
diff --git a/internal/command/select.go b/internal/command/select.go
index 09ee8c0..19f7503 100644
--- a/internal/command/select.go
+++ b/internal/command/select.go
@@ -24,6 +24,7 @@ type selectOptions struct {
 	MergeInputDocuments bool
 	FormatTemplate      string
 	Colourise           bool
+	EscapeHTML          bool
 }
 
 func outputNodeLength(writer io.Writer, nodes ...*dasel.Node) error {
@@ -114,7 +115,9 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
 		return err
 	}
 
-	writeOptions := make([]storage.ReadWriteOption, 0)
+	writeOptions := []storage.ReadWriteOption{
+		storage.EscapeHTMLOption(opts.EscapeHTML),
+	}
 
 	if opts.Compact {
 		writeOptions = append(writeOptions, storage.PrettyPrintOption(false))
@@ -170,7 +173,8 @@ func runSelectCommand(opts selectOptions, cmd *cobra.Command) error {
 
 func selectCommand() *cobra.Command {
 	var fileFlag, selectorFlag, parserFlag, readParserFlag, writeParserFlag, formatTemplateFlag string
-	var plainFlag, multiFlag, nullValueNotFoundFlag, compactFlag, lengthFlag, mergeInputDocumentsFlag, colourFlag, colorFlag bool
+	var plainFlag, multiFlag, nullValueNotFoundFlag, compactFlag, lengthFlag, mergeInputDocumentsFlag,
+		colourFlag, colorFlag, escapeHTMLFlag bool
 
 	cmd := &cobra.Command{
 		Use:   "select -f <file> -p <json,yaml> -s <selector>",
@@ -196,6 +200,7 @@ func selectCommand() *cobra.Command {
 				MergeInputDocuments: mergeInputDocumentsFlag,
 				FormatTemplate:      formatTemplateFlag,
 				Colourise:           colourFlag || colorFlag,
+				EscapeHTML:          escapeHTMLFlag,
 			}, cmd)
 		},
 	}
@@ -214,6 +219,7 @@ func selectCommand() *cobra.Command {
 	cmd.Flags().StringVar(&formatTemplateFlag, "format", "", "Formatting template to use when writing results.")
 	cmd.Flags().BoolVar(&colourFlag, "colour", false, "Print colourised output.")
 	cmd.Flags().BoolVar(&colorFlag, "color", false, "Alias of --colour.")
+	cmd.Flags().BoolVar(&escapeHTMLFlag, "escape-html", true, "Escape HTML tags when writing output.")
 
 	_ = cmd.MarkFlagFilename("file")
 
diff --git a/internal/storage/json.go b/internal/storage/json.go
index 903e1e8..6955f94 100644
--- a/internal/storage/json.go
+++ b/internal/storage/json.go
@@ -66,6 +66,10 @@ func (p *JSONParser) ToBytes(value interface{}, options ...ReadWriteOption) ([]b
 			if value, ok := o.Value.(bool); ok {
 				colourise = value
 			}
+		case OptionEscapeHTML:
+			if value, ok := o.Value.(bool); ok {
+				encoder.SetEscapeHTML(value)
+			}
 		}
 	}
 
diff --git a/internal/storage/option.go b/internal/storage/option.go
index 65b6285..9fa71d7 100644
--- a/internal/storage/option.go
+++ b/internal/storage/option.go
@@ -24,6 +24,14 @@ func ColouriseOption(enabled bool) ReadWriteOption {
 	}
 }
 
+// EscapeHTMLOption returns an option that enables or disables HTML escaping.
+func EscapeHTMLOption(enabled bool) ReadWriteOption {
+	return ReadWriteOption{
+		Key:   OptionEscapeHTML,
+		Value: enabled,
+	}
+}
+
 // OptionKey is a defined type for keys within a ReadWriteOption.
 type OptionKey string
 
@@ -34,6 +42,8 @@ const (
 	OptionPrettyPrint OptionKey = "prettyPrint"
 	// OptionColourise is the key used with ColouriseOption.
 	OptionColourise OptionKey = "colourise"
+	// OptionEscapeHTML is the key used with EscapeHTMLOption.
+	OptionEscapeHTML OptionKey = "escapeHtml"
 )
 
 // ReadWriteOption is an option to be used when writing.


Test patch:
diff --git a/internal/command/root_delete_test.go b/internal/command/root_delete_test.go
index 85cfc81..64c4d5a 100644
--- a/internal/command/root_delete_test.go
+++ b/internal/command/root_delete_test.go
@@ -163,4 +163,21 @@ func TestRootCmd_Delete_JSON(t *testing.T) {
 	t.Run("RootArray", deleteTest(`[1, 2, 3]`, "json", ".", newline(`[]`), nil))
 	t.Run("RootArrayMulti", deleteTest(`[1, 2, 3]`, "json", ".", newline(`[]`), nil, "-m"))
 
+	t.Run("DeleteStringEscapeHTMLOn", deleteTest(`{
+  "name": "Tom",
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.name`, `{
+  "user": "Tom \u003ccontact@tomwright.me\u003e"
+}
+`, nil, "--escape-html=true"))
+
+	t.Run("DeleteStringEscapeHTMLOff", deleteTest(`{
+  "name": "Tom",
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.name`, `{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, nil, "--escape-html=false"))
 }
diff --git a/internal/command/root_put_test.go b/internal/command/root_put_test.go
index 9bf44e1..75967f8 100644
--- a/internal/command/root_put_test.go
+++ b/internal/command/root_put_test.go
@@ -688,6 +688,54 @@ foo: true
   "id": -1
 }
 `, nil, "put", "int", "-p", "json", "-v", "-1", "-s", ".id"))
+
+	t.Run("PutStringEscapeHTMLOn", putStringTest(`{
+  "user": "Old"
+}
+`, "json", `.user`, `Tom <contact@tomwright.me>`, `{
+  "user": "Tom \u003ccontact@tomwright.me\u003e"
+}
+`, nil, "--escape-html=true"))
+
+	t.Run("PutStringEscapeHTMLOff", putStringTest(`{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.user`, `Tom <contact@tomwright.me>`, `{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, nil, "--escape-html=false"))
+
+	t.Run("PutObjectEscapeHTMLOn", putObjectTest(`{
+  "user": "Old"
+}
+`, "json", `.`, []string{`user=Tom <contact@tomwright.me>`}, []string{"string"}, `{
+  "user": "Tom \u003ccontact@tomwright.me\u003e"
+}
+`, nil, "--escape-html=true"))
+
+	t.Run("PutObjectEscapeHTMLOff", putObjectTest(`{
+  "user": "Old"
+}
+`, "json", `.`, []string{`user=Tom <contact@tomwright.me>`}, []string{"string"}, `{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, nil, "--escape-html=false"))
+
+	t.Run("PutDocumentEscapeHTMLOn", putDocumentTest(`{
+  "user": "Old"
+}
+`, "json", `.`, `{"user": "Tom <contact@tomwright.me>"}`, `{
+  "user": "Tom \u003ccontact@tomwright.me\u003e"
+}
+`, nil, "--escape-html=true"))
+
+	t.Run("PutDocumentEscapeHTMLOff", putDocumentTest(`{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.`, `{"user": "Tom <contact@tomwright.me>"}`, `{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, nil, "--escape-html=false"))
 }
 
 func TestRootCMD_Put_YAML(t *testing.T) {
diff --git a/internal/command/root_select_test.go b/internal/command/root_select_test.go
index ad29726..2b7f769 100644
--- a/internal/command/root_select_test.go
+++ b/internal/command/root_select_test.go
@@ -477,6 +477,22 @@ func TestRootCmd_Select_JSON(t *testing.T) {
 ]
 `, nil, "--merge-input-documents"))
 
+	t.Run("EscapeHTMLOn", selectTest(`{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.`, `{
+  "user": "Tom \u003ccontact@tomwright.me\u003e"
+}
+`, nil, "--escape-html=true"))
+
+	t.Run("EscapeHTMLOff", selectTest(`{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, "json", `.`, `{
+  "user": "Tom <contact@tomwright.me>"
+}
+`, nil, "--escape-html=false"))
+
 }
 
 func TestRootCmd_Select_YAML(t *testing.T) {