Repository: segmentio/terraform-docs
Pull request #114 (2019-06-23 17:10:50)

Escape pipe character when generating Markdown
### Prerequisites

Put an `x` into the box(es) that apply:

- [x] This pull request fixes a bug.
- [ ] This pull request adds a feature.
- [ ] 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 esacpes pipe (`|`) while generating Markdown document or table.

### Issues Resolved

List any existing issues this pull request resolves. #112 

### Checklist

Put an `x` into all boxes that apply:

- [x] 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

- [x] My code follows the code style of this project.


metmajer: Great stuff, @khos2ow and thanks for taking the initiative!

Patch:
diff --git a/examples/variables.tf b/examples/variables.tf
index 37bf358..dd84249 100644
--- a/examples/variables.tf
+++ b/examples/variables.tf
@@ -51,3 +51,9 @@ variable "list-1" {
 
 // A variable with underscores.
 variable "input_with_underscores" {}
+
+// A variable with pipe in the description
+variable "input-with-pipe" {
+  description = "It includes v1 | v2 | v3"
+  default     = "v1"
+}
diff --git a/internal/pkg/print/markdown/document/document.go b/internal/pkg/print/markdown/document/document.go
index cc1b528..e7a1c21 100644
--- a/internal/pkg/print/markdown/document/document.go
+++ b/internal/pkg/print/markdown/document/document.go
@@ -77,7 +77,7 @@ func printFencedCodeBlock(code string) string {
 func printInput(buffer *bytes.Buffer, input doc.Input, settings settings.Settings) {
 	buffer.WriteString("\n")
 	buffer.WriteString(fmt.Sprintf("### %s\n\n", strings.Replace(input.Name, "_", "\\_", -1)))
-	buffer.WriteString(fmt.Sprintf("Description: %s\n\n", markdown.ConvertMultiLineText(input.Description)))
+	buffer.WriteString(fmt.Sprintf("Description: %s\n\n", markdown.SanitizeDescription(input.Description)))
 	buffer.WriteString(fmt.Sprintf("Type: `%s`\n", input.Type))
 
 	// Don't print defaults for required inputs when we're already explicit about it being required
@@ -123,6 +123,6 @@ func printOutputs(buffer *bytes.Buffer, outputs []doc.Output, settings settings.
 	for _, output := range outputs {
 		buffer.WriteString("\n")
 		buffer.WriteString(fmt.Sprintf("### %s\n\n", strings.Replace(output.Name, "_", "\\_", -1)))
-		buffer.WriteString(fmt.Sprintf("Description: %s\n", markdown.ConvertMultiLineText(output.Description)))
+		buffer.WriteString(fmt.Sprintf("Description: %s\n", markdown.SanitizeDescription(output.Description)))
 	}
 }
diff --git a/internal/pkg/print/markdown/markdown.go b/internal/pkg/print/markdown/markdown.go
index 856e8c1..b4a4d3b 100644
--- a/internal/pkg/print/markdown/markdown.go
+++ b/internal/pkg/print/markdown/markdown.go
@@ -5,6 +5,14 @@ import (
 	"strings"
 )
 
+// SanitizeDescription converts description to suitable Markdown representation. (including mline-break, illegal characters, etc)
+func SanitizeDescription(s string) string {
+	s = ConvertMultiLineText(s)
+	s = EscapeIllegalCharacters(s)
+
+	return s
+}
+
 // ConvertMultiLineText converts a multi-line text into a suitable Markdown representation.
 func ConvertMultiLineText(s string) string {
 	// Convert double newlines to <br><br>.
@@ -18,6 +26,17 @@ func ConvertMultiLineText(s string) string {
 	return strings.Replace(s, "\n", " ", -1)
 }
 
+// EscapeIllegalCharacters escapes characters which have special meaning in Markdown into their corresponding literal.
+func EscapeIllegalCharacters(s string) string {
+	// Escape pipe
+	s = strings.Replace(s, "|", "\\|", -1)
+
+	// Escape underscore
+	s = strings.Replace(s, "_", "\\_", -1)
+
+	return s
+}
+
 // Sanitize cleans a Markdown document to soothe linters.
 func Sanitize(markdown string) string {
 	result := markdown
diff --git a/internal/pkg/print/markdown/table/table.go b/internal/pkg/print/markdown/table/table.go
index a73cfa6..c16f5a3 100644
--- a/internal/pkg/print/markdown/table/table.go
+++ b/internal/pkg/print/markdown/table/table.go
@@ -82,7 +82,7 @@ func printInputs(buffer *bytes.Buffer, inputs []doc.Input, settings settings.Set
 		buffer.WriteString(
 			fmt.Sprintf("| %s | %s | %s | %s |",
 				strings.Replace(input.Name, "_", "\\_", -1),
-				markdown.ConvertMultiLineText(input.Description),
+				markdown.SanitizeDescription(input.Description),
 				input.Type,
 				getInputDefaultValue(&input, settings)))
 
@@ -111,6 +111,6 @@ func printOutputs(buffer *bytes.Buffer, outputs []doc.Output, settings settings.
 		buffer.WriteString(
 			fmt.Sprintf("| %s | %s |\n",
 				strings.Replace(output.Name, "_", "\\_", -1),
-				markdown.ConvertMultiLineText(output.Description)))
+				markdown.SanitizeDescription(output.Description)))
 	}
 }


Test patch:
diff --git a/internal/pkg/doc/doc_test.go b/internal/pkg/doc/doc_test.go
index 0ca7255..e7c21d7 100644
--- a/internal/pkg/doc/doc_test.go
+++ b/internal/pkg/doc/doc_test.go
@@ -110,6 +110,16 @@ var inputWithUnderscores = doc.Input{
 	Default:     nil,
 }
 
+var inputWithPipe = doc.Input{
+	Name:        "input-with-pipe",
+	Description: "It includes v1 | v2 | v3",
+	Default: &doc.Value{
+		Type:  "string",
+		Value: "v1",
+	},
+	Type: "string",
+}
+
 var output1 = doc.Output{
 	Name:        "output-1",
 	Description: "It's output number one.",
@@ -204,6 +214,7 @@ func TestInputs(t *testing.T) {
 		inputList2,
 		inputList1,
 		inputWithUnderscores,
+		inputWithPipe,
 	}
 
 	assert.Equal(t, expected, actual)
@@ -234,6 +245,7 @@ func TestInputsFromVariablesTf(t *testing.T) {
 		inputList2,
 		inputList1,
 		inputWithUnderscores,
+		inputWithPipe,
 	}
 
 	assert.Equal(t, expected, actual)
@@ -245,6 +257,7 @@ func TestInputsSortedByName(t *testing.T) {
 	doc.SortInputsByName(actual)
 
 	expected := []doc.Input{
+		inputWithPipe,
 		inputWithUnderscores,
 		inputList1,
 		inputList2,
@@ -272,6 +285,7 @@ func TestInputsSortedByRequired(t *testing.T) {
 		inputMap2,
 		inputString2,
 		inputUnquoted,
+		inputWithPipe,
 		inputList1,
 		inputList3,
 		inputMap1,
diff --git a/internal/pkg/print/json/testdata/json-WithSortByName.golden b/internal/pkg/print/json/testdata/json-WithSortByName.golden
index 5dc7898..9015d1f 100644
--- a/internal/pkg/print/json/testdata/json-WithSortByName.golden
+++ b/internal/pkg/print/json/testdata/json-WithSortByName.golden
@@ -1,6 +1,15 @@
 {
   "Comment": "Usage:\n\nmodule \"foo\" {\n  source = \"github.com/foo/bar\"\n\n  id   = \"1234567890\"\n  name = \"baz\"\n\n  zones = [\"us-east-1\", \"us-west-1\"]\n\n  tags = {\n    Name         = \"baz\"\n    Created-By   = \"first.last@email.com\"\n    Date-Created = \"20180101\"\n  }\n}\n",
   "Inputs": [
+    {
+      "Name": "input-with-pipe",
+      "Description": "It includes v1 | v2 | v3",
+      "Default": {
+        "Type": "string",
+        "Value": "v1"
+      },
+      "Type": "string"
+    },
     {
       "Name": "input_with_underscores",
       "Description": "A variable with underscores.",
diff --git a/internal/pkg/print/json/testdata/json-WithSortInputsByRequired.golden b/internal/pkg/print/json/testdata/json-WithSortInputsByRequired.golden
index 79ad2e5..018e8bc 100644
--- a/internal/pkg/print/json/testdata/json-WithSortInputsByRequired.golden
+++ b/internal/pkg/print/json/testdata/json-WithSortInputsByRequired.golden
@@ -31,6 +31,15 @@
       "Default": null,
       "Type": "string"
     },
+    {
+      "Name": "input-with-pipe",
+      "Description": "It includes v1 | v2 | v3",
+      "Default": {
+        "Type": "string",
+        "Value": "v1"
+      },
+      "Type": "string"
+    },
     {
       "Name": "list-1",
       "Description": "It's list number one.",
diff --git a/internal/pkg/print/json/testdata/json.golden b/internal/pkg/print/json/testdata/json.golden
index fb320b5..c477e2e 100644
--- a/internal/pkg/print/json/testdata/json.golden
+++ b/internal/pkg/print/json/testdata/json.golden
@@ -92,6 +92,15 @@
       "Description": "A variable with underscores.",
       "Default": null,
       "Type": "string"
+    },
+    {
+      "Name": "input-with-pipe",
+      "Description": "It includes v1 | v2 | v3",
+      "Default": {
+        "Type": "string",
+        "Value": "v1"
+      },
+      "Type": "string"
     }
   ],
   "Outputs": [
diff --git a/internal/pkg/print/markdown/document/testdata/document-WithAggregateTypeDefaults.golden b/internal/pkg/print/markdown/document/testdata/document-WithAggregateTypeDefaults.golden
index 820faf5..f28ba35 100644
--- a/internal/pkg/print/markdown/document/testdata/document-WithAggregateTypeDefaults.golden
+++ b/internal/pkg/print/markdown/document/testdata/document-WithAggregateTypeDefaults.golden
@@ -131,6 +131,14 @@ Type: `string`
 
 Default: n/a
 
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
 ## Outputs
 
 The following outputs are exported:
diff --git a/internal/pkg/print/markdown/document/testdata/document-WithRequired.golden b/internal/pkg/print/markdown/document/testdata/document-WithRequired.golden
index 9c3b2a2..8fd8b83 100644
--- a/internal/pkg/print/markdown/document/testdata/document-WithRequired.golden
+++ b/internal/pkg/print/markdown/document/testdata/document-WithRequired.golden
@@ -101,6 +101,14 @@ Type: `list`
 
 Default: `<list>`
 
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
 ## Outputs
 
 The following outputs are exported:
diff --git a/internal/pkg/print/markdown/document/testdata/document-WithSortByName.golden b/internal/pkg/print/markdown/document/testdata/document-WithSortByName.golden
index a684fd6..c9f2009 100644
--- a/internal/pkg/print/markdown/document/testdata/document-WithSortByName.golden
+++ b/internal/pkg/print/markdown/document/testdata/document-WithSortByName.golden
@@ -19,6 +19,14 @@ module "foo" {
 
 The following input variables are supported:
 
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
 ### input\_with\_underscores
 
 Description: A variable with underscores.
diff --git a/internal/pkg/print/markdown/document/testdata/document-WithSortInputsByRequired.golden b/internal/pkg/print/markdown/document/testdata/document-WithSortInputsByRequired.golden
index e672ef7..3d6eae7 100644
--- a/internal/pkg/print/markdown/document/testdata/document-WithSortInputsByRequired.golden
+++ b/internal/pkg/print/markdown/document/testdata/document-WithSortInputsByRequired.golden
@@ -59,6 +59,14 @@ Type: `string`
 
 Default: n/a
 
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
 ### list-1
 
 Description: It's list number one.
diff --git a/internal/pkg/print/markdown/document/testdata/document.golden b/internal/pkg/print/markdown/document/testdata/document.golden
index a21f386..d1d4ddd 100644
--- a/internal/pkg/print/markdown/document/testdata/document.golden
+++ b/internal/pkg/print/markdown/document/testdata/document.golden
@@ -107,6 +107,14 @@ Type: `string`
 
 Default: n/a
 
+### input-with-pipe
+
+Description: It includes v1 \| v2 \| v3
+
+Type: `string`
+
+Default: `"v1"`
+
 ## Outputs
 
 The following outputs are exported:
diff --git a/internal/pkg/print/markdown/table/testdata/table-WithAggregateTypeDefaults.golden b/internal/pkg/print/markdown/table/testdata/table-WithAggregateTypeDefaults.golden
index 7b297bb..d00b754 100644
--- a/internal/pkg/print/markdown/table/testdata/table-WithAggregateTypeDefaults.golden
+++ b/internal/pkg/print/markdown/table/testdata/table-WithAggregateTypeDefaults.golden
@@ -30,6 +30,7 @@ module "foo" {
 | list-2 | It's list number two. | list | n/a |
 | list-1 | It's list number one. | list | `[ "a", "b", "c" ]` |
 | input\_with\_underscores | A variable with underscores. | string | n/a |
+| input-with-pipe | It includes v1 \| v2 \| v3 | string | `"v1"` |
 
 ## Outputs
 
diff --git a/internal/pkg/print/markdown/table/testdata/table-WithRequired.golden b/internal/pkg/print/markdown/table/testdata/table-WithRequired.golden
index fbee235..3832eb2 100644
--- a/internal/pkg/print/markdown/table/testdata/table-WithRequired.golden
+++ b/internal/pkg/print/markdown/table/testdata/table-WithRequired.golden
@@ -30,6 +30,7 @@ module "foo" {
 | list-2 | It's list number two. | list | n/a | yes |
 | list-1 | It's list number one. | list | `<list>` | no |
 | input\_with\_underscores | A variable with underscores. | string | n/a | yes |
+| input-with-pipe | It includes v1 \| v2 \| v3 | string | `"v1"` | no |
 
 ## Outputs
 
diff --git a/internal/pkg/print/markdown/table/testdata/table-WithSortByName.golden b/internal/pkg/print/markdown/table/testdata/table-WithSortByName.golden
index ecb72b7..4a38dc3 100644
--- a/internal/pkg/print/markdown/table/testdata/table-WithSortByName.golden
+++ b/internal/pkg/print/markdown/table/testdata/table-WithSortByName.golden
@@ -19,6 +19,7 @@ module "foo" {
 
 | Name | Description | Type | Default |
 |------|-------------|:----:|:-----:|
+| input-with-pipe | It includes v1 \| v2 \| v3 | string | `"v1"` |
 | input\_with\_underscores | A variable with underscores. | string | n/a |
 | list-1 | It's list number one. | list | `<list>` |
 | list-2 | It's list number two. | list | n/a |
diff --git a/internal/pkg/print/markdown/table/testdata/table-WithSortInputsByRequired.golden b/internal/pkg/print/markdown/table/testdata/table-WithSortInputsByRequired.golden
index fbc4e9b..bb5f7f3 100644
--- a/internal/pkg/print/markdown/table/testdata/table-WithSortInputsByRequired.golden
+++ b/internal/pkg/print/markdown/table/testdata/table-WithSortInputsByRequired.golden
@@ -24,6 +24,7 @@ module "foo" {
 | map-2 | It's map number two. | map | n/a |
 | string-2 | It's string number two. | string | n/a |
 | unquoted |  | string | n/a |
+| input-with-pipe | It includes v1 \| v2 \| v3 | string | `"v1"` |
 | list-1 | It's list number one. | list | `<list>` |
 | list-3 |  | list | `<list>` |
 | map-1 | It's map number one. | map | `<map>` |
diff --git a/internal/pkg/print/markdown/table/testdata/table.golden b/internal/pkg/print/markdown/table/testdata/table.golden
index 32a79b3..9d73f09 100644
--- a/internal/pkg/print/markdown/table/testdata/table.golden
+++ b/internal/pkg/print/markdown/table/testdata/table.golden
@@ -30,6 +30,7 @@ module "foo" {
 | list-2 | It's list number two. | list | n/a |
 | list-1 | It's list number one. | list | `<list>` |
 | input\_with\_underscores | A variable with underscores. | string | n/a |
+| input-with-pipe | It includes v1 \| v2 \| v3 | string | `"v1"` |
 
 ## Outputs
 
diff --git a/internal/pkg/print/pretty/pretty_test.go b/internal/pkg/print/pretty/pretty_test.go
index 4134936..e163d6c 100644
--- a/internal/pkg/print/pretty/pretty_test.go
+++ b/internal/pkg/print/pretty/pretty_test.go
@@ -75,6 +75,9 @@ func TestPretty(t *testing.T) {
 			"  " + sgr_color_1 + "var.input_with_underscores" + sgr_reset + " (required)\n" +
 			"  " + sgr_color_2 + "A variable with underscores." + sgr_reset + "\n" +
 			"\n" +
+			"  " + sgr_color_1 + "var.input-with-pipe" + sgr_reset + " (\"v1\")\n" +
+			"  " + sgr_color_2 + "It includes v1 | v2 | v3" + sgr_reset + "\n" +
+			"\n" +
 			"\n" +
 			"\n" +
 			"  " + sgr_color_1 + "output.unquoted" + sgr_reset + "\n" +
@@ -158,6 +161,9 @@ func TestPrettyWithWithAggregateTypeDefaults(t *testing.T) {
 			"  " + sgr_color_1 + "var.input_with_underscores" + sgr_reset + " (required)\n" +
 			"  " + sgr_color_2 + "A variable with underscores." + sgr_reset + "\n" +
 			"\n" +
+			"  " + sgr_color_1 + "var.input-with-pipe" + sgr_reset + " (\"v1\")\n" +
+			"  " + sgr_color_2 + "It includes v1 | v2 | v3" + sgr_reset + "\n" +
+			"\n" +
 			"\n" +
 			"\n" +
 			"  " + sgr_color_1 + "output.unquoted" + sgr_reset + "\n" +
@@ -208,6 +214,9 @@ func TestPrettyWithSortByName(t *testing.T) {
 			"}\n" +
 			"\n" +
 			"\n" +
+			"  " + sgr_color_1 + "var.input-with-pipe" + sgr_reset + " (\"v1\")\n" +
+			"  " + sgr_color_2 + "It includes v1 | v2 | v3" + sgr_reset + "\n" +
+			"\n" +
 			"  " + sgr_color_1 + "var.input_with_underscores" + sgr_reset + " (required)\n" +
 			"  " + sgr_color_2 + "A variable with underscores." + sgr_reset + "\n" +
 			"\n" +
@@ -307,6 +316,9 @@ func TestPrettyWithSortInputsByRequired(t *testing.T) {
 			"  " + sgr_color_1 + "var.unquoted" + sgr_reset + " (required)\n" +
 			"  " + sgr_color_2 + "" + sgr_reset + "\n" +
 			"\n" +
+			"  " + sgr_color_1 + "var.input-with-pipe" + sgr_reset + " (\"v1\")\n" +
+			"  " + sgr_color_2 + "It includes v1 | v2 | v3" + sgr_reset + "\n" +
+			"\n" +
 			"  " + sgr_color_1 + "var.list-1" + sgr_reset + " (<list>)\n" +
 			"  " + sgr_color_2 + "It's list number one." + sgr_reset + "\n" +
 			"\n" +