Repository: segmentio/terraform-docs
Pull request #147 (2020-01-06 21:33:52)

Add '--no-escape' flag to 'json' command
### Prerequisites

Put an `x` into the box(es) that apply:

- [ ] This pull request fixes a bug.
- [x] This pull request adds a feature.
- [x] This pull request enhances existing functionality.
- [ ] This pull request introduces breaking change.

For more information, see the [Contributing Guide](https://github.com/segmentio/terraform-docs/tree/master/CONTRIBUTING.md).

### Description

This PR adds a new flag `--no-escape` to `json` command. Following addition of Providers information (#140) the version will most likely have `>`, `>=`, `~>` and such in it which will be escaped by the json marshaler.

### Issues Resolved

List any existing issues this pull request resolves.

### Checklist

Put an `x` into all boxes that apply:

- [ ] I have read the [Contributing Guidelines](https://github.com/segmentio/terraform-docs/tree/master/CONTRIBUTING.md).

#### Tests

- [x] I have added tests to cover my changes.
- [x] All tests pass when I run `make test`.

#### Documentation

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.

#### Code Style

- [ ] My code follows the code style of this project.



Patch:
diff --git a/cmd/root.go b/cmd/root.go
index 3fa346c..6866648 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -36,7 +36,7 @@ var rootCmd = &cobra.Command{
 		settings.ShowColor = !nocolor
 		settings.SortByName = !nosort
 		settings.ShowRequired = !norequired
-		settings.EscapeMarkdown = !noescape
+		settings.EscapeCharacters = !noescape
 	},
 }
 
@@ -54,6 +54,8 @@ func init() {
 	markdownCmd.PersistentFlags().IntVar(&settings.MarkdownIndent, "indent", 2, "indention level of Markdown sections [1, 2, 3, 4, 5]")
 
 	prettyCmd.PersistentFlags().BoolVar(new(bool), "no-color", false, "do not colorize printed result")
+
+	jsonCmd.PersistentFlags().BoolVar(new(bool), "no-escape", false, "do not escape special characters")
 }
 
 // Execute adds all child commands to the root command and sets flags appropriately.
diff --git a/internal/pkg/print/json/json.go b/internal/pkg/print/json/json.go
index 2a4a1f6..dd586e4 100644
--- a/internal/pkg/print/json/json.go
+++ b/internal/pkg/print/json/json.go
@@ -1,7 +1,9 @@
 package json
 
 import (
+	"bytes"
 	"encoding/json"
+	"strings"
 
 	"github.com/segmentio/terraform-docs/internal/pkg/print"
 	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
@@ -32,10 +34,16 @@ func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 		copy.Outputs = module.Outputs
 	}
 
-	buffer, err := json.MarshalIndent(copy, prefix, indent)
+	buffer := new(bytes.Buffer)
+
+	encoder := json.NewEncoder(buffer)
+	encoder.SetIndent(prefix, indent)
+	encoder.SetEscapeHTML(settings.EscapeCharacters)
+
+	err := encoder.Encode(copy)
 	if err != nil {
 		return "", err
 	}
 
-	return string(buffer), nil
+	return strings.TrimSuffix(buffer.String(), "\n"), nil
 }
diff --git a/internal/pkg/print/markdown/document/testdata/document-EscapeMarkdown.golden b/internal/pkg/print/markdown/document/testdata/document-EscapeCharacters.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-EscapeMarkdown.golden
rename to internal/pkg/print/markdown/document/testdata/document-EscapeCharacters.golden
diff --git a/internal/pkg/print/markdown/markdown.go b/internal/pkg/print/markdown/markdown.go
index 5eefa9c..dc7128b 100644
--- a/internal/pkg/print/markdown/markdown.go
+++ b/internal/pkg/print/markdown/markdown.go
@@ -32,7 +32,7 @@ func Sanitize(markdown string) string {
 
 // SanitizeName escapes underscore character which have special meaning in Markdown.
 func SanitizeName(name string, settings *print.Settings) string {
-	if settings.EscapeMarkdown {
+	if settings.EscapeCharacters {
 		// Escape underscore
 		name = strings.Replace(name, "_", "\\_", -1)
 	}
@@ -123,7 +123,7 @@ func EscapeIllegalCharacters(s string, settings *print.Settings) string {
 	// Escape pipe
 	s = strings.Replace(s, "|", "\\|", -1)
 
-	if settings.EscapeMarkdown {
+	if settings.EscapeCharacters {
 		// Escape underscore
 		s = strings.Replace(s, "_", "\\_", -1)
 
diff --git a/internal/pkg/print/markdown/table/testdata/table-EscapeMarkdown.golden b/internal/pkg/print/markdown/table/testdata/table-EscapeCharacters.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-EscapeMarkdown.golden
rename to internal/pkg/print/markdown/table/testdata/table-EscapeCharacters.golden
diff --git a/internal/pkg/print/settings.go b/internal/pkg/print/settings.go
index ea0a9ec..89fc2b9 100644
--- a/internal/pkg/print/settings.go
+++ b/internal/pkg/print/settings.go
@@ -6,9 +6,9 @@ type Settings struct {
 	// scope: Global
 	AggregateTypeDefaults bool
 
-	// EscapeMarkdown escapes special Markdown characters (such as | _ * and etc) (default: true)
+	// EscapeCharacters escapes special characters (such as | _ * in Markdown and > < in JSON) (default: true)
 	// scope: Markdown
-	EscapeMarkdown bool
+	EscapeCharacters bool
 
 	// MarkdownIndent control the indentation of Markdown headers [available: 1, 2, 3, 4, 5] (default: 2)
 	// scope: Markdown
@@ -47,7 +47,7 @@ type Settings struct {
 func NewSettings() *Settings {
 	return &Settings{
 		AggregateTypeDefaults: false,
-		EscapeMarkdown:        true,
+		EscapeCharacters:      true,
 		MarkdownIndent:        2,
 		ShowColor:             true,
 		ShowInputs:            true,


Test patch:
diff --git a/internal/pkg/print/json/json_test.go b/internal/pkg/print/json/json_test.go
index cfa7d70..4acf278 100644
--- a/internal/pkg/print/json/json_test.go
+++ b/internal/pkg/print/json/json_test.go
@@ -163,3 +163,21 @@ func TestJsonOnlyOutputs(t *testing.T) {
 	assert.Nil(err)
 	assert.Equal(expected, actual)
 }
+
+func TestJsonEscapeCharacters(t *testing.T) {
+	assert := assert.New(t)
+	settings := &print.Settings{
+		EscapeCharacters: true,
+		ShowProviders:    true,
+		ShowInputs:       true,
+		ShowOutputs:      true,
+	}
+
+	module, expected, err := testutil.GetExpected("json-EscapeCharacters")
+	assert.Nil(err)
+
+	actual, err := Print(module, settings)
+
+	assert.Nil(err)
+	assert.Equal(expected, actual)
+}
diff --git a/internal/pkg/print/json/testdata/json-EscapeCharacters.golden b/internal/pkg/print/json/testdata/json-EscapeCharacters.golden
new file mode 100644
index 0000000..8022c2c
--- /dev/null
+++ b/internal/pkg/print/json/testdata/json-EscapeCharacters.golden
@@ -0,0 +1,109 @@
+{
+  "inputs": [
+    {
+      "name": "unquoted",
+      "type": "any"
+    },
+    {
+      "name": "string-3",
+      "type": "string",
+      "default": "\"\""
+    },
+    {
+      "name": "string-2",
+      "type": "string",
+      "description": "It's string number two."
+    },
+    {
+      "name": "string-1",
+      "type": "string",
+      "default": "\"bar\""
+    },
+    {
+      "name": "map-3",
+      "type": "map",
+      "default": "{}"
+    },
+    {
+      "name": "map-2",
+      "type": "map",
+      "description": "It's map number two."
+    },
+    {
+      "name": "map-1",
+      "type": "map",
+      "default": "{\n  \"a\": 1,\n  \"b\": 2,\n  \"c\": 3\n}"
+    },
+    {
+      "name": "list-3",
+      "type": "list",
+      "default": "[]"
+    },
+    {
+      "name": "list-2",
+      "type": "list",
+      "description": "It's list number two."
+    },
+    {
+      "name": "list-1",
+      "type": "list",
+      "default": "[\n  \"a\",\n  \"b\",\n  \"c\"\n]"
+    },
+    {
+      "name": "input_with_underscores",
+      "type": "any"
+    },
+    {
+      "name": "input-with-pipe",
+      "type": "string",
+      "description": "It includes v1 | v2 | v3",
+      "default": "\"v1\""
+    },
+    {
+      "name": "input-with-code-block",
+      "type": "list",
+      "description": "This is a complicated one. We need a newline.  \nAnd an example in a code block\n```\ndefault     = [\n  \"machine rack01:neptune\"\n]\n```\n",
+      "default": "[\n  \"name rack:location\"\n]"
+    },
+    {
+      "name": "long_type",
+      "type": "object({\n    name = string,\n    foo  = object({ foo = string, bar = string }),\n    bar  = object({ foo = string, bar = string }),\n    fizz = list(string),\n    buzz = list(string)\n  })",
+      "description": "This description is itself markdown.\n\nIt spans over multiple lines.\n",
+      "default": "{\n  \"bar\": {\n    \"bar\": \"bar\",\n    \"foo\": \"bar\"\n  },\n  \"buzz\": [\n    \"fizz\",\n    \"buzz\"\n  ],\n  \"fizz\": [],\n  \"foo\": {\n    \"bar\": \"foo\",\n    \"foo\": \"foo\"\n  },\n  \"name\": \"hello\"\n}"
+    }
+  ],
+  "outputs": [
+    {
+      "name": "unquoted",
+      "description": "It's unquoted output."
+    },
+    {
+      "name": "output-2",
+      "description": "It's output number two."
+    },
+    {
+      "name": "output-1"
+    },
+    {
+      "name": "output-0.12",
+      "description": "terraform 0.12 only"
+    }
+  ],
+  "providers": [
+    {
+      "name": "tls"
+    },
+    {
+      "name": "aws",
+      "version": "\u003e= 2.15.0"
+    },
+    {
+      "name": "aws",
+      "alias": "ident",
+      "version": "\u003e= 2.15.0"
+    },
+    {
+      "name": "null"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/internal/pkg/print/json/testdata/json-NoInputs.golden b/internal/pkg/print/json/testdata/json-NoInputs.golden
index 8cdd224..72bef77 100644
--- a/internal/pkg/print/json/testdata/json-NoInputs.golden
+++ b/internal/pkg/print/json/testdata/json-NoInputs.golden
@@ -23,12 +23,12 @@
     },
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/json/testdata/json-NoOutputs.golden b/internal/pkg/print/json/testdata/json-NoOutputs.golden
index d40c9d7..3fd2069 100644
--- a/internal/pkg/print/json/testdata/json-NoOutputs.golden
+++ b/internal/pkg/print/json/testdata/json-NoOutputs.golden
@@ -79,12 +79,12 @@
     },
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/json/testdata/json-OnlyProviders.golden b/internal/pkg/print/json/testdata/json-OnlyProviders.golden
index 9762e68..b359a69 100644
--- a/internal/pkg/print/json/testdata/json-OnlyProviders.golden
+++ b/internal/pkg/print/json/testdata/json-OnlyProviders.golden
@@ -7,12 +7,12 @@
     },
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/json/testdata/json-SortByName.golden b/internal/pkg/print/json/testdata/json-SortByName.golden
index f00edac..806e76d 100644
--- a/internal/pkg/print/json/testdata/json-SortByName.golden
+++ b/internal/pkg/print/json/testdata/json-SortByName.golden
@@ -92,12 +92,12 @@
   "providers": [
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/json/testdata/json-SortByRequired.golden b/internal/pkg/print/json/testdata/json-SortByRequired.golden
index 0ed2798..0b3476d 100644
--- a/internal/pkg/print/json/testdata/json-SortByRequired.golden
+++ b/internal/pkg/print/json/testdata/json-SortByRequired.golden
@@ -92,12 +92,12 @@
   "providers": [
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/json/testdata/json.golden b/internal/pkg/print/json/testdata/json.golden
index 8022c2c..3dfe56b 100644
--- a/internal/pkg/print/json/testdata/json.golden
+++ b/internal/pkg/print/json/testdata/json.golden
@@ -95,12 +95,12 @@
     },
     {
       "name": "aws",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "aws",
       "alias": "ident",
-      "version": "\u003e= 2.15.0"
+      "version": ">= 2.15.0"
     },
     {
       "name": "null"
diff --git a/internal/pkg/print/markdown/document/document_test.go b/internal/pkg/print/markdown/document/document_test.go
index 76f5e85..d9bf06e 100644
--- a/internal/pkg/print/markdown/document/document_test.go
+++ b/internal/pkg/print/markdown/document/document_test.go
@@ -182,16 +182,16 @@ func TestDocumentOnlyOutputs(t *testing.T) {
 	assert.Equal(expected, actual)
 }
 
-func TestDocumentEscapeMarkdown(t *testing.T) {
+func TestDocumentEscapeCharacters(t *testing.T) {
 	assert := assert.New(t)
 	settings := &print.Settings{
-		EscapeMarkdown: true,
-		ShowProviders:  true,
-		ShowInputs:     true,
-		ShowOutputs:    true,
+		EscapeCharacters: true,
+		ShowProviders:    true,
+		ShowInputs:       true,
+		ShowOutputs:      true,
 	}
 
-	module, expected, err := testutil.GetExpected("document-EscapeMarkdown")
+	module, expected, err := testutil.GetExpected("document-EscapeCharacters")
 	assert.Nil(err)
 
 	actual, err := Print(module, settings)
diff --git a/internal/pkg/print/markdown/table/table_test.go b/internal/pkg/print/markdown/table/table_test.go
index 1db8f1d..bf6016e 100644
--- a/internal/pkg/print/markdown/table/table_test.go
+++ b/internal/pkg/print/markdown/table/table_test.go
@@ -182,16 +182,16 @@ func TestTableOnlyOutputs(t *testing.T) {
 	assert.Equal(expected, actual)
 }
 
-func TestTableEscapeMarkdown(t *testing.T) {
+func TestTableEscapeCharacters(t *testing.T) {
 	assert := assert.New(t)
 	settings := &print.Settings{
-		EscapeMarkdown: true,
-		ShowProviders:  true,
-		ShowInputs:     true,
-		ShowOutputs:    true,
+		EscapeCharacters: true,
+		ShowProviders:    true,
+		ShowInputs:       true,
+		ShowOutputs:      true,
 	}
 
-	module, expected, err := testutil.GetExpected("table-EscapeMarkdown")
+	module, expected, err := testutil.GetExpected("table-EscapeCharacters")
 	assert.Nil(err)
 
 	actual, err := Print(module, settings)