Repository: terraform-docs/terraform-docs
Pull request #559 (2021-09-13 22:42:32)

Make terraform.Module available in content
<!--
Thank you for helping to improve terraform-docs!

Please read through https://git.io/JtEzg if this is your first time opening a
terraform-docs pull request. Find us in https://slack.terraform-docs.io/messages/dev if
you need any help contributing.
-->

### Description of your changes

<!--
Briefly describe what this pull request does. Be sure to direct your reviewers'
attention to anything that needs special consideration.

We love pull requests that resolve an open terraform-docs issue. If yours does, you
can uncomment the below line to indicate which issue your PR fixes, for example
"Fixes #500":
-->

Add one extra special variable the `content`:

- `{{ .Module }}`

As opposed to the other variables, which are generated sections based on
a selected formatter, the `{{ .Module }}` variable is just a `struct`
representing a Terraform module.

It can be used to build highly complex and highly customized content:

```yaml
content: |-
  ## Resources

  {{ range .Module.Resources }}
  - {{ .GetMode }}.{{ .Spec }} ({{ .Position.Filename }}#{{ .Position.Line }})
  {{- end }}
```

<!-- Fixes # -->
xref: #549 
xref: #550

I have:

- [x] Read and followed terraform-docs' [contribution process].
- [x] All tests pass when I run `make test`.

### How has this code been tested

<!--
Before reviewers can be confident in the correctness of this pull request, it
needs to tested and shown to be correct. Briefly describe the testing that has
already been done or which is planned for this change.
-->

- added new `TestGeneratorFuncModule` test
- manually built and ran against updated example of `examples/.terraform-docs.yml`

[contribution process]: https://git.io/JtEzg



Patch:
diff --git a/README.md b/README.md
index 82026e1..9cb0989 100644
--- a/README.md
+++ b/README.md
@@ -312,11 +312,11 @@ func buildTerraformDocs(path string, tmpl string) (string, error) {
 
     // // Note: if you don't intend to provide additional template for the generated
     // // content, or the target format doesn't provide templating (e.g. json, yaml,
-    // // xml, or toml) you can use `Content()` function instead of `ExecuteTemplate()`.
+    // // xml, or toml) you can use `Content()` function instead of `Render()`.
     // // `Content()` returns all the sections combined with predefined order.
     // return formatter.Content(), nil
 
-    return formatter.ExecuteTemplate(tmpl)
+    return formatter.Render(tmpl)
 }
 ```
 
diff --git a/docs/user-guide/configuration/content.md b/docs/user-guide/configuration/content.md
index b14cd1b..fff5f0d 100644
--- a/docs/user-guide/configuration/content.md
+++ b/docs/user-guide/configuration/content.md
@@ -29,19 +29,27 @@ will be ignored for other formatters.
 - `{{ .Requirements }}`
 - `{{ .Resources }}`
 
-and following functions:
-
-- `{{ include "relative/path/to/file" }}`
-
 These variables are the generated output of individual sections in the selected
 formatter. For example `{{ .Inputs }}` is Markdown Table representation of _inputs_
 when formatter is set to `markdown table`.
 
 {{< alert type="info" >}}
-Sections visibility (i.e. `sections.show` and `sections.hide`) takes
-precedence over the `content`.
+Sections visibility (i.e. `sections.show` and `sections.hide`) takes precedence
+over the `content`.
 {{< /alert >}}
 
+`content` also has the following function:
+
+- `{{ include "relative/path/to/file" }}`
+
+Additionally there's also one extra special variable avaialble to the `content`:
+
+- `{{ .Module }}`
+
+As opposed to the other variables mentioned above, which are generated sections
+based on a selected formatter, the `{{ .Module }}` variable is just a `struct`
+representing a [Terraform module].
+
 ## Options
 
 Available options with their default values.
@@ -93,7 +101,7 @@ content: |-
 ````
 
 In the following example, although `{{ .Providers }}` is defined it won't be
-rendered because `providers` is not set to be shown in `sections.show`.
+rendered because `providers` is not set to be shown in `sections.show`:
 
 ```yaml
 sections:
@@ -113,3 +121,16 @@ content: |-
 
   {{ .Outputs }}
 ```
+
+Building highly complex and highly customized content using `{{ .Module }}` struct:
+
+```yaml
+content: |-
+  ## Resources
+
+  {{ range .Module.Resources }}
+  - {{ .GetMode }}.{{ .Spec }} ({{ .Position.Filename }}#{{ .Position.Line }})
+  {{- end }}
+```
+
+[Terraform module]: https://pkg.go.dev/github.com/terraform-docs/terraform-docs/terraform#Module
\ No newline at end of file
diff --git a/examples/.terraform-docs.yml b/examples/.terraform-docs.yml
index 3c27dae..4da1441 100644
--- a/examples/.terraform-docs.yml
+++ b/examples/.terraform-docs.yml
@@ -32,6 +32,11 @@ sections:
 #
 #   and even in between sections
 #
+#   ## Resources
+#   {{ range .Module.Resources }}
+#   - {{ .GetMode }}.{{ .Spec }} ({{ .Position.Filename }}#{{ .Position.Line }})
+#   {{- end }}
+#
 #   ## Examples
 #
 #   ```hcl
diff --git a/format/asciidoc_document.go b/format/asciidoc_document.go
index 8b789a6..260d833 100644
--- a/format/asciidoc_document.go
+++ b/format/asciidoc_document.go
@@ -24,7 +24,7 @@ var asciidocsDocumentFS embed.FS
 
 // asciidocDocument represents AsciiDoc Document format.
 type asciidocDocument struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -61,7 +61,7 @@ func NewAsciidocDocument(config *print.Config) Type {
 	})
 
 	return &asciidocDocument{
-		Generator: print.NewGenerator("json", config.ModuleRoot),
+		generator: newGenerator(config, true),
 		config:    config,
 		template:  tt,
 	}
@@ -69,7 +69,7 @@ func NewAsciidocDocument(config *print.Config) Type {
 
 // Generate a Terraform module as AsciiDoc document.
 func (d *asciidocDocument) Generate(module *terraform.Module) error {
-	err := d.Generator.ForEach(func(name string) (string, error) {
+	err := d.generator.forEach(func(name string) (string, error) {
 		rendered, err := d.template.Render(name, module)
 		if err != nil {
 			return "", err
@@ -77,6 +77,8 @@ func (d *asciidocDocument) Generate(module *terraform.Module) error {
 		return sanitize(rendered), nil
 	})
 
+	d.generator.funcs(withModule(module))
+
 	return err
 }
 
diff --git a/format/asciidoc_table.go b/format/asciidoc_table.go
index 8ab1759..c8cd675 100644
--- a/format/asciidoc_table.go
+++ b/format/asciidoc_table.go
@@ -24,7 +24,7 @@ var asciidocTableFS embed.FS
 
 // asciidocTable represents AsciiDoc Table format.
 type asciidocTable struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -52,7 +52,7 @@ func NewAsciidocTable(config *print.Config) Type {
 	})
 
 	return &asciidocTable{
-		Generator: print.NewGenerator("json", config.ModuleRoot),
+		generator: newGenerator(config, true),
 		config:    config,
 		template:  tt,
 	}
@@ -60,7 +60,7 @@ func NewAsciidocTable(config *print.Config) Type {
 
 // Generate a Terraform module as AsciiDoc tables.
 func (t *asciidocTable) Generate(module *terraform.Module) error {
-	err := t.Generator.ForEach(func(name string) (string, error) {
+	err := t.generator.forEach(func(name string) (string, error) {
 		rendered, err := t.template.Render(name, module)
 		if err != nil {
 			return "", err
@@ -68,6 +68,8 @@ func (t *asciidocTable) Generate(module *terraform.Module) error {
 		return sanitize(rendered), nil
 	})
 
+	t.generator.funcs(withModule(module))
+
 	return err
 }
 
diff --git a/format/doc.go b/format/doc.go
index a85442b..b1a1ec8 100644
--- a/format/doc.go
+++ b/format/doc.go
@@ -28,15 +28,15 @@ the root directory of this source tree.
 //         return err
 //     }
 //
-//     output, err := formatter.ExecuteTemplate("")
+//     output, err := formatter.Render"")
 //     if err != nil {
 //         return err
 //     }
 //
 // Note: if you don't intend to provide additional template for the generated
 // content, or the target format doesn't provide templating (e.g. json, yaml,
-// xml, or toml) you can use `Content()` function instead of `ExecuteTemplate()`.
-// `Content()` returns all the sections combined with predefined order.
+// xml, or toml) you can use `Content()` function instead of `Render)`. Note
+// that `Content()` returns all the sections combined with predefined order.
 //
 //     output := formatter.Content()
 //
diff --git a/format/generator.go b/format/generator.go
new file mode 100644
index 0000000..baf4c7b
--- /dev/null
+++ b/format/generator.go
@@ -0,0 +1,267 @@
+/*
+Copyright 2021 The terraform-docs Authors.
+
+Licensed under the MIT license (the "License"); you may not
+use this file except in compliance with the License.
+
+You may obtain a copy of the License at the LICENSE file in
+the root directory of this source tree.
+*/
+
+package format
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	gotemplate "text/template"
+
+	"github.com/terraform-docs/terraform-docs/print"
+	"github.com/terraform-docs/terraform-docs/template"
+	"github.com/terraform-docs/terraform-docs/terraform"
+)
+
+// generateFunc configures generator.
+type generateFunc func(*generator)
+
+// withContent specifies how the generator should add content.
+func withContent(content string) generateFunc {
+	return func(g *generator) {
+		g.content = content
+	}
+}
+
+// withHeader specifies how the generator should add Header.
+func withHeader(header string) generateFunc {
+	return func(g *generator) {
+		g.header = header
+	}
+}
+
+// withFooter specifies how the generator should add Footer.
+func withFooter(footer string) generateFunc {
+	return func(g *generator) {
+		g.footer = footer
+	}
+}
+
+// withInputs specifies how the generator should add Inputs.
+func withInputs(inputs string) generateFunc {
+	return func(g *generator) {
+		g.inputs = inputs
+	}
+}
+
+// withModules specifies how the generator should add Modules.
+func withModules(modules string) generateFunc {
+	return func(g *generator) {
+		g.modules = modules
+	}
+}
+
+// withOutputs specifies how the generator should add Outputs.
+func withOutputs(outputs string) generateFunc {
+	return func(g *generator) {
+		g.outputs = outputs
+	}
+}
+
+// withProviders specifies how the generator should add Providers.
+func withProviders(providers string) generateFunc {
+	return func(g *generator) {
+		g.providers = providers
+	}
+}
+
+// withRequirements specifies how the generator should add Requirements.
+func withRequirements(requirements string) generateFunc {
+	return func(g *generator) {
+		g.requirements = requirements
+	}
+}
+
+// withResources specifies how the generator should add Resources.
+func withResources(resources string) generateFunc {
+	return func(g *generator) {
+		g.resources = resources
+	}
+}
+
+// withModule specifies how the generator should add Resources.
+func withModule(module *terraform.Module) generateFunc {
+	return func(g *generator) {
+		g.module = module
+	}
+}
+
+// generator represents all the sections that can be generated for a Terraform
+// modules (e.g. header, footer, inputs, etc). All these sections are being
+// generated individually and if no content template was passed they will be
+// combined together with a predefined order.
+//
+// On the other hand these sections can individually be used in content template
+// to form a custom format (and order).
+//
+// Note that the notion of custom content template will be ignored for incompatible
+// formatters and custom plugins. Compatible formatters are:
+//
+// - asciidoc document
+// - asciidoc table
+// - markdown document
+// - markdown table
+type generator struct {
+	// all the content combined
+	content string
+
+	// individual sections
+	header       string
+	footer       string
+	inputs       string
+	modules      string
+	outputs      string
+	providers    string
+	requirements string
+	resources    string
+
+	config *print.Config
+	module *terraform.Module
+
+	path string         // module's path
+	fns  []generateFunc // generator helper functions
+
+	canRender bool // indicates if the generator can render with custom template
+}
+
+// newGenerator returns a generator for specific formatter name and with
+// provided sets of GeneratorFunc functions to build and add individual
+// sections.
+func newGenerator(config *print.Config, canRender bool, fns ...generateFunc) *generator {
+	g := &generator{
+		config: config,
+
+		path: config.ModuleRoot,
+		fns:  []generateFunc{},
+
+		canRender: canRender,
+	}
+
+	g.funcs(fns...)
+
+	return g
+}
+
+// Content returns generted all the sections combined based on the underlying format.
+func (g *generator) Content() string { return g.content }
+
+// Header returns generted header section based on the underlying format.
+func (g *generator) Header() string { return g.header }
+
+// Footer returns generted footer section based on the underlying format.
+func (g *generator) Footer() string { return g.footer }
+
+// Inputs returns generted inputs section based on the underlying format.
+func (g *generator) Inputs() string { return g.inputs }
+
+// Modules returns generted modules section based on the underlying format.
+func (g *generator) Modules() string { return g.modules }
+
+// Outputs returns generted outputs section based on the underlying format.
+func (g *generator) Outputs() string { return g.outputs }
+
+// Providers returns generted providers section based on the underlying format.
+func (g *generator) Providers() string { return g.providers }
+
+// Requirements returns generted resources section based on the underlying format.
+func (g *generator) Requirements() string { return g.requirements }
+
+// Resources returns generted requirements section based on the underlying format.
+func (g *generator) Resources() string { return g.resources }
+
+// Module returns generted requirements section based on the underlying format.
+func (g *generator) Module() *terraform.Module { return g.module }
+
+// funcs adds GenerateFunc to the list of available functions, for further use
+// if need be, and then runs them.
+func (g *generator) funcs(fns ...generateFunc) {
+	for _, fn := range fns {
+		g.fns = append(g.fns, fn)
+		fn(g)
+	}
+}
+
+// Path set path of module's root directory.
+func (g *generator) Path(root string) {
+	g.path = root
+}
+
+func (g *generator) Render(tpl string) (string, error) {
+	if !g.canRender {
+		return g.content, nil
+	}
+
+	if tpl == "" {
+		return g.content, nil
+	}
+
+	tt := template.New(g.config, &template.Item{
+		Name: "content",
+		Text: tpl,
+	})
+	tt.CustomFunc(gotemplate.FuncMap{
+		"include": func(s string) string {
+			content, err := os.ReadFile(filepath.Join(g.path, s))
+			if err != nil {
+				panic(err)
+			}
+			return strings.TrimSuffix(string(content), "\n")
+		},
+	})
+
+	data := struct {
+		*generator
+		Config *print.Config
+		Module *terraform.Module
+	}{
+		generator: g,
+		Config:    g.config,
+		Module:    g.module,
+	}
+
+	rendered, err := tt.RenderContent("content", data)
+	if err != nil {
+		return "", err
+	}
+
+	return strings.TrimSuffix(rendered, "\n"), nil
+}
+
+// generatorCallback renders a Terraform module and creates a GenerateFunc.
+type generatorCallback func(string) generateFunc
+
+// forEach section executes generatorCallback to render the content for that
+// section and create corresponding GeneratorFunc. If there is any error in
+// executing the template for the section forEach function immediately returns
+// it and exits.
+func (g *generator) forEach(render func(string) (string, error)) error {
+	mappings := map[string]generatorCallback{
+		"all":          withContent,
+		"header":       withHeader,
+		"footer":       withFooter,
+		"inputs":       withInputs,
+		"modules":      withModules,
+		"outputs":      withOutputs,
+		"providers":    withProviders,
+		"requirements": withRequirements,
+		"resources":    withResources,
+	}
+	for name, callback := range mappings {
+		result, err := render(name)
+		if err != nil {
+			return err
+		}
+		fn := callback(result)
+		g.fns = append(g.fns, fn)
+		fn(g)
+	}
+	return nil
+}
diff --git a/format/json.go b/format/json.go
index cc5f1a7..357ca9f 100644
--- a/format/json.go
+++ b/format/json.go
@@ -21,7 +21,7 @@ import (
 
 // json represents JSON format.
 type json struct {
-	*print.Generator
+	*generator
 
 	config *print.Config
 }
@@ -29,7 +29,7 @@ type json struct {
 // NewJSON returns new instance of JSON.
 func NewJSON(config *print.Config) Type {
 	return &json{
-		Generator: print.NewGenerator("json", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 	}
 }
@@ -47,7 +47,7 @@ func (j *json) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	j.Generator.Funcs(print.WithContent(strings.TrimSuffix(buffer.String(), "\n")))
+	j.generator.funcs(withContent(strings.TrimSuffix(buffer.String(), "\n")))
 
 	return nil
 }
diff --git a/format/markdown_document.go b/format/markdown_document.go
index 977eaf8..23f8982 100644
--- a/format/markdown_document.go
+++ b/format/markdown_document.go
@@ -24,7 +24,7 @@ var markdownDocumentFS embed.FS
 
 // markdownDocument represents Markdown Document format.
 type markdownDocument struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -59,7 +59,7 @@ func NewMarkdownDocument(config *print.Config) Type {
 	})
 
 	return &markdownDocument{
-		Generator: print.NewGenerator("json", config.ModuleRoot),
+		generator: newGenerator(config, true),
 		config:    config,
 		template:  tt,
 	}
@@ -67,7 +67,7 @@ func NewMarkdownDocument(config *print.Config) Type {
 
 // Generate a Terraform module as Markdown document.
 func (d *markdownDocument) Generate(module *terraform.Module) error {
-	err := d.Generator.ForEach(func(name string) (string, error) {
+	err := d.generator.forEach(func(name string) (string, error) {
 		rendered, err := d.template.Render(name, module)
 		if err != nil {
 			return "", err
@@ -75,6 +75,8 @@ func (d *markdownDocument) Generate(module *terraform.Module) error {
 		return sanitize(rendered), nil
 	})
 
+	d.generator.funcs(withModule(module))
+
 	return err
 }
 
diff --git a/format/markdown_table.go b/format/markdown_table.go
index 4ab1783..19214d6 100644
--- a/format/markdown_table.go
+++ b/format/markdown_table.go
@@ -24,7 +24,7 @@ var markdownTableFS embed.FS
 
 // markdownTable represents Markdown Table format.
 type markdownTable struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -50,7 +50,7 @@ func NewMarkdownTable(config *print.Config) Type {
 	})
 
 	return &markdownTable{
-		Generator: print.NewGenerator("markdown table", config.ModuleRoot),
+		generator: newGenerator(config, true),
 		config:    config,
 		template:  tt,
 	}
@@ -58,7 +58,7 @@ func NewMarkdownTable(config *print.Config) Type {
 
 // Generate a Terraform module as Markdown tables.
 func (t *markdownTable) Generate(module *terraform.Module) error {
-	err := t.Generator.ForEach(func(name string) (string, error) {
+	err := t.generator.forEach(func(name string) (string, error) {
 		rendered, err := t.template.Render(name, module)
 		if err != nil {
 			return "", err
@@ -66,6 +66,8 @@ func (t *markdownTable) Generate(module *terraform.Module) error {
 		return sanitize(rendered), nil
 	})
 
+	t.generator.funcs(withModule(module))
+
 	return err
 }
 
diff --git a/format/pretty.go b/format/pretty.go
index 462f9c2..7776773 100644
--- a/format/pretty.go
+++ b/format/pretty.go
@@ -26,7 +26,7 @@ var prettyTpl []byte
 
 // pretty represents colorized pretty format.
 type pretty struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -50,7 +50,7 @@ func NewPretty(config *print.Config) Type {
 	})
 
 	return &pretty{
-		Generator: print.NewGenerator("pretty", config.ModuleRoot),
+		generator: newGenerator(config, true),
 		config:    config,
 		template:  tt,
 	}
@@ -63,7 +63,8 @@ func (p *pretty) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	p.Generator.Funcs(print.WithContent(regexp.MustCompile(`(\r?\n)*$`).ReplaceAllString(rendered, "")))
+	p.generator.funcs(withContent(regexp.MustCompile(`(\r?\n)*$`).ReplaceAllString(rendered, "")))
+	p.generator.funcs(withModule(module))
 
 	return nil
 }
diff --git a/print/testdata/sample-file.txt b/format/testdata/generator/sample-file.txt
similarity index 100%
rename from print/testdata/sample-file.txt
rename to format/testdata/generator/sample-file.txt
diff --git a/format/tfvars_hcl.go b/format/tfvars_hcl.go
index c487e32..87f1f5b 100644
--- a/format/tfvars_hcl.go
+++ b/format/tfvars_hcl.go
@@ -27,7 +27,7 @@ var tfvarsHCLTpl []byte
 
 // tfvarsHCL represents Terraform tfvars HCL format.
 type tfvarsHCL struct {
-	*print.Generator
+	*generator
 
 	config   *print.Config
 	template *template.Template
@@ -60,7 +60,7 @@ func NewTfvarsHCL(config *print.Config) Type {
 	})
 
 	return &tfvarsHCL{
-		Generator: print.NewGenerator("tfvars hcl", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 		template:  tt,
 	}
@@ -75,7 +75,7 @@ func (h *tfvarsHCL) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	h.Generator.Funcs(print.WithContent(strings.TrimSuffix(sanitize(rendered), "\n")))
+	h.generator.funcs(withContent(strings.TrimSuffix(sanitize(rendered), "\n")))
 
 	return nil
 }
diff --git a/format/tfvars_json.go b/format/tfvars_json.go
index adaf252..c6203f8 100644
--- a/format/tfvars_json.go
+++ b/format/tfvars_json.go
@@ -23,7 +23,7 @@ import (
 
 // tfvarsJSON represents Terraform tfvars JSON format.
 type tfvarsJSON struct {
-	*print.Generator
+	*generator
 
 	config *print.Config
 }
@@ -31,7 +31,7 @@ type tfvarsJSON struct {
 // NewTfvarsJSON returns new instance of TfvarsJSON.
 func NewTfvarsJSON(config *print.Config) Type {
 	return &tfvarsJSON{
-		Generator: print.NewGenerator("tfvars json", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 	}
 }
@@ -53,10 +53,9 @@ func (j *tfvarsJSON) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	j.Generator.Funcs(print.WithContent(strings.TrimSuffix(buffer.String(), "\n")))
+	j.generator.funcs(withContent(strings.TrimSuffix(buffer.String(), "\n")))
 
 	return nil
-
 }
 
 func init() {
diff --git a/format/toml.go b/format/toml.go
index d9b44c3..ce46298 100644
--- a/format/toml.go
+++ b/format/toml.go
@@ -22,7 +22,7 @@ import (
 
 // toml represents TOML format.
 type toml struct {
-	*print.Generator
+	*generator
 
 	config *print.Config
 }
@@ -30,7 +30,7 @@ type toml struct {
 // NewTOML returns new instance of TOML.
 func NewTOML(config *print.Config) Type {
 	return &toml{
-		Generator: print.NewGenerator("toml", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 	}
 }
@@ -46,7 +46,7 @@ func (t *toml) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	t.Generator.Funcs(print.WithContent(strings.TrimSuffix(buffer.String(), "\n")))
+	t.generator.funcs(withContent(strings.TrimSuffix(buffer.String(), "\n")))
 
 	return nil
 
diff --git a/format/type.go b/format/type.go
index 19a1579..994b4ae 100644
--- a/format/type.go
+++ b/format/type.go
@@ -32,7 +32,7 @@ type Type interface {
 	Requirements() string // requirements section based on the underlying format
 	Resources() string    // resources section based on the underlying format
 
-	ExecuteTemplate(contentTmpl string) (string, error)
+	Render(tmpl string) (string, error)
 }
 
 // initializerFn returns a concrete implementation of an Engine.
diff --git a/format/xml.go b/format/xml.go
index 7091a82..1093e94 100644
--- a/format/xml.go
+++ b/format/xml.go
@@ -20,7 +20,7 @@ import (
 
 // xml represents XML format.
 type xml struct {
-	*print.Generator
+	*generator
 
 	config *print.Config
 }
@@ -28,7 +28,7 @@ type xml struct {
 // NewXML returns new instance of XML.
 func NewXML(config *print.Config) Type {
 	return &xml{
-		Generator: print.NewGenerator("xml", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 	}
 }
@@ -42,7 +42,7 @@ func (x *xml) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	x.Generator.Funcs(print.WithContent(strings.TrimSuffix(string(out), "\n")))
+	x.generator.funcs(withContent(strings.TrimSuffix(string(out), "\n")))
 
 	return nil
 }
diff --git a/format/yaml.go b/format/yaml.go
index 16245fb..a12a125 100644
--- a/format/yaml.go
+++ b/format/yaml.go
@@ -22,7 +22,7 @@ import (
 
 // yaml represents YAML format.
 type yaml struct {
-	*print.Generator
+	*generator
 
 	config *print.Config
 }
@@ -30,7 +30,7 @@ type yaml struct {
 // NewYAML returns new instance of YAML.
 func NewYAML(config *print.Config) Type {
 	return &yaml{
-		Generator: print.NewGenerator("yaml", config.ModuleRoot),
+		generator: newGenerator(config, false),
 		config:    config,
 	}
 }
@@ -47,7 +47,7 @@ func (y *yaml) Generate(module *terraform.Module) error {
 		return err
 	}
 
-	y.Generator.Funcs(print.WithContent(strings.TrimSuffix(buffer.String(), "\n")))
+	y.generator.funcs(withContent(strings.TrimSuffix(buffer.String(), "\n")))
 
 	return nil
 }
diff --git a/internal/cli/run.go b/internal/cli/run.go
index f0cea1f..b48f6ea 100644
--- a/internal/cli/run.go
+++ b/internal/cli/run.go
@@ -360,7 +360,7 @@ func generateContent(config *print.Config) error {
 		return err
 	}
 
-	content, err := formatter.ExecuteTemplate(config.Content)
+	content, err := formatter.Render(config.Content)
 	if err != nil {
 		return err
 	}
diff --git a/print/config.go b/print/config.go
index d20ed30..1ed910e 100644
--- a/print/config.go
+++ b/print/config.go
@@ -11,8 +11,13 @@ the root directory of this source tree.
 package print
 
 import (
+	"errors"
 	"fmt"
+	"os"
+	"path"
 	"strings"
+
+	"github.com/spf13/viper"
 )
 
 // Config represents all the available config options that can be accessed and
@@ -135,15 +140,15 @@ func defaultSections() sections {
 		Show: []string{},
 		Hide: []string{},
 
-		DataSources:  false,
-		Header:       false,
+		DataSources:  true,
+		Header:       true,
 		Footer:       false,
-		Inputs:       false,
-		ModuleCalls:  false,
-		Outputs:      false,
-		Providers:    false,
-		Requirements: false,
-		Resources:    false,
+		Inputs:       true,
+		ModuleCalls:  true,
+		Outputs:      true,
+		Providers:    true,
+		Requirements: true,
+		Resources:    true,
 	}
 }
 
@@ -466,3 +471,40 @@ func (c *Config) Validate() error {
 
 	return nil
 }
+
+// ReadConfig reads config file in `rootDir` with given `filename` and returns
+// instance of Config. It returns error if config file not found or there is a
+// problem with unmarshalling.
+func ReadConfig(rootDir string, filename string) (*Config, error) {
+	cfg := NewConfig()
+
+	v := viper.New()
+	v.SetConfigFile(path.Join(rootDir, filename))
+
+	if err := v.ReadInConfig(); err != nil {
+		var perr *os.PathError
+		if errors.As(err, &perr) {
+			return nil, fmt.Errorf("config file %s not found", filename)
+		}
+
+		var cerr viper.ConfigFileNotFoundError
+		if !errors.As(err, &cerr) {
+			return nil, err
+		}
+	}
+
+	if err := v.Unmarshal(cfg); err != nil {
+		return nil, fmt.Errorf("unable to decode config, %w", err)
+	}
+
+	cfg.ModuleRoot = rootDir
+
+	// process and validate configuration
+	if err := cfg.Validate(); err != nil {
+		return nil, err
+	}
+
+	cfg.Parse()
+
+	return cfg, nil
+}
diff --git a/print/doc.go b/print/doc.go
index e5e3038..888fff9 100644
--- a/print/doc.go
+++ b/print/doc.go
@@ -34,9 +34,8 @@ the root directory of this source tree.
 // Generator holds a reference to all the sections (e.g. header, footer, inputs, etc)
 // and also it renders all of them, in a predefined order, in `Content()`.
 //
-// It also provides `ExecuteTemplate(string)` function to process and render the
-// template to generate the final output content. Following variables and functions are
-// available:
+// It also provides `Render(string)` function to process and render the template to generate
+// the final output content. Following variables and functions are available:
 //
 // • `{{ .Header }}`
 // • `{{ .Footer }}`
diff --git a/print/generator.go b/print/generator.go
deleted file mode 100644
index 70ab34f..0000000
--- a/print/generator.go
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
-Copyright 2021 The terraform-docs Authors.
-
-Licensed under the MIT license (the "License"); you may not
-use this file except in compliance with the License.
-
-You may obtain a copy of the License at the LICENSE file in
-the root directory of this source tree.
-*/
-
-package print
-
-import (
-	"bytes"
-	"os"
-	"path/filepath"
-	"text/template"
-)
-
-// GenerateFunc configures Generator.
-type GenerateFunc func(*Generator)
-
-// WithContent specifies how the Generator should add content.
-func WithContent(content string) GenerateFunc {
-	return func(g *Generator) {
-		g.content = content
-	}
-}
-
-// WithHeader specifies how the Generator should add Header.
-func WithHeader(header string) GenerateFunc {
-	return func(g *Generator) {
-		g.header = header
-	}
-}
-
-// WithFooter specifies how the Generator should add Footer.
-func WithFooter(footer string) GenerateFunc {
-	return func(g *Generator) {
-		g.footer = footer
-	}
-}
-
-// WithInputs specifies how the Generator should add Inputs.
-func WithInputs(inputs string) GenerateFunc {
-	return func(g *Generator) {
-		g.inputs = inputs
-	}
-}
-
-// WithModules specifies how the Generator should add Modules.
-func WithModules(modules string) GenerateFunc {
-	return func(g *Generator) {
-		g.modules = modules
-	}
-}
-
-// WithOutputs specifies how the Generator should add Outputs.
-func WithOutputs(outputs string) GenerateFunc {
-	return func(g *Generator) {
-		g.outputs = outputs
-	}
-}
-
-// WithProviders specifies how the Generator should add Providers.
-func WithProviders(providers string) GenerateFunc {
-	return func(g *Generator) {
-		g.providers = providers
-	}
-}
-
-// WithRequirements specifies how the Generator should add Requirements.
-func WithRequirements(requirements string) GenerateFunc {
-	return func(g *Generator) {
-		g.requirements = requirements
-	}
-}
-
-// WithResources specifies how the Generator should add Resources.
-func WithResources(resources string) GenerateFunc {
-	return func(g *Generator) {
-		g.resources = resources
-	}
-}
-
-// Generator represents all the sections that can be generated for a Terraform
-// modules (e.g. header, footer, inputs, etc). All these sections are being
-// generated individually and if no content template was passed they will be
-// combined together with a predefined order.
-//
-// On the other hand these sections can individually be used in content template
-// to form a custom format (and order).
-//
-// Note that the notion of custom content template will be ignored for incompatible
-// formatters and custom plugins. Compatible formatters are:
-//
-// - asciidoc document
-// - asciidoc table
-// - markdown document
-// - markdown table
-type Generator struct {
-	// all the content combined
-	content string
-
-	// individual sections
-	header       string
-	footer       string
-	inputs       string
-	modules      string
-	outputs      string
-	providers    string
-	requirements string
-	resources    string
-
-	path      string // module's path
-	formatter string // formatter name
-
-	funcs []GenerateFunc
-}
-
-// NewGenerator returns a Generator for specific formatter name and with
-// provided sets of GeneratorFunc functions to build and add individual
-// sections.
-func NewGenerator(name string, root string, fns ...GenerateFunc) *Generator {
-	g := &Generator{
-		path:      root,
-		formatter: name,
-		funcs:     []GenerateFunc{},
-	}
-
-	g.Funcs(fns...)
-
-	return g
-}
-
-// Content returns generted all the sections combined based on the underlying format.
-func (g *Generator) Content() string { return g.content }
-
-// Header returns generted header section based on the underlying format.
-func (g *Generator) Header() string { return g.header }
-
-// Footer returns generted footer section based on the underlying format.
-func (g *Generator) Footer() string { return g.footer }
-
-// Inputs returns generted inputs section based on the underlying format.
-func (g *Generator) Inputs() string { return g.inputs }
-
-// Modules returns generted modules section based on the underlying format.
-func (g *Generator) Modules() string { return g.modules }
-
-// Outputs returns generted outputs section based on the underlying format.
-func (g *Generator) Outputs() string { return g.outputs }
-
-// Providers returns generted providers section based on the underlying format.
-func (g *Generator) Providers() string { return g.providers }
-
-// Requirements returns generted resources section based on the underlying format.
-func (g *Generator) Requirements() string { return g.requirements }
-
-// Resources returns generted requirements section based on the underlying format.
-func (g *Generator) Resources() string { return g.resources }
-
-// Funcs adds GenerateFunc to the list of available functions, for further use
-// if need be, and then runs them.
-func (g *Generator) Funcs(fns ...GenerateFunc) {
-	for _, fn := range fns {
-		g.funcs = append(g.funcs, fn)
-		fn(g)
-	}
-}
-
-// Path set path of module's root directory.
-func (g *Generator) Path(root string) {
-	g.path = root
-}
-
-// ExecuteTemplate applies the template with Renderer known items. If template
-// is empty Renderer.content is returned as is. If template is not empty this
-// still returns Renderer.content for incompatible formatters.
-// func (g *Renderer) Render(contentTmpl string) (string, error) {
-func (g *Generator) ExecuteTemplate(contentTmpl string) (string, error) {
-	if !g.isCompatible() {
-		return g.content, nil
-	}
-
-	if contentTmpl == "" {
-		return g.content, nil
-	}
-
-	var buf bytes.Buffer
-
-	tmpl := template.New("content")
-	tmpl.Funcs(template.FuncMap{
-		"include": func(s string) string {
-			content, err := os.ReadFile(filepath.Join(g.path, s))
-			if err != nil {
-				panic(err)
-			}
-			return string(content)
-		},
-	})
-	template.Must(tmpl.Parse(contentTmpl))
-
-	if err := tmpl.ExecuteTemplate(&buf, "content", g); err != nil {
-		return "", err
-	}
-
-	return buf.String(), nil
-}
-
-func (g *Generator) isCompatible() bool {
-	switch g.formatter {
-	case "asciidoc document", "asciidoc table", "markdown document", "markdown table":
-		return true
-	}
-	return false
-}
-
-// generatorCallback renders a Terraform module and creates a GenerateFunc.
-type generatorCallback func(string) GenerateFunc
-
-// ForEach section executes generatorCallback to render the content for that
-// section and create corresponding GeneratorFunc. If there is any error in
-// the executing the template for the section ForEach function immediately
-// returns it and exit.
-func (g *Generator) ForEach(render func(string) (string, error)) error {
-	mappings := map[string]generatorCallback{
-		"all":          WithContent,
-		"header":       WithHeader,
-		"footer":       WithFooter,
-		"inputs":       WithInputs,
-		"modules":      WithModules,
-		"outputs":      WithOutputs,
-		"providers":    WithProviders,
-		"requirements": WithRequirements,
-		"resources":    WithResources,
-	}
-	for name, callback := range mappings {
-		result, err := render(name)
-		if err != nil {
-			return err
-		}
-		fn := callback(result)
-		g.funcs = append(g.funcs, fn)
-		fn(g)
-	}
-	return nil
-}
diff --git a/template/template.go b/template/template.go
index ee6d71c..04681e8 100644
--- a/template/template.go
+++ b/template/template.go
@@ -74,6 +74,19 @@ func (t *Template) applyCustomFunc() {
 
 // Render template with given Module struct.
 func (t *Template) Render(name string, module *terraform.Module) (string, error) {
+	data := struct {
+		Config *print.Config
+		Module *terraform.Module
+	}{
+		Config: t.config,
+		Module: module,
+	}
+	return t.RenderContent(name, data)
+}
+
+// RenderContent template with given data. It can contain anything but most
+// probably it will only contain terraform.Module and print.generator.
+func (t *Template) RenderContent(name string, data interface{}) (string, error) {
 	if len(t.items) < 1 {
 		return "", fmt.Errorf("base template not found")
 	}
@@ -95,13 +108,7 @@ func (t *Template) Render(name string, module *terraform.Module) (string, error)
 		gotemplate.Must(tt.Parse(normalize(ii.Text)))
 	}
 
-	if err := tmpl.ExecuteTemplate(&buffer, item.Name, struct {
-		Module *terraform.Module
-		Config *print.Config
-	}{
-		Module: module,
-		Config: t.config,
-	}); err != nil {
+	if err := tmpl.ExecuteTemplate(&buffer, item.Name, data); err != nil {
 		return "", err
 	}
 


Test patch:
diff --git a/format/asciidoc_document_test.go b/format/asciidoc_document_test.go
index ac25c02..5aebdbe 100644
--- a/format/asciidoc_document_test.go
+++ b/format/asciidoc_document_test.go
@@ -153,10 +153,7 @@ func TestAsciidocDocument(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/asciidoc_table_test.go b/format/asciidoc_table_test.go
index 5bb884e..dbaa9b6 100644
--- a/format/asciidoc_table_test.go
+++ b/format/asciidoc_table_test.go
@@ -153,10 +153,7 @@ func TestAsciidocTable(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/print/generator_test.go b/format/generator_test.go
similarity index 56%
rename from print/generator_test.go
rename to format/generator_test.go
index 10dbc4a..baa7af2 100644
--- a/print/generator_test.go
+++ b/format/generator_test.go
@@ -8,128 +8,80 @@ You may obtain a copy of the License at the LICENSE file in
 the root directory of this source tree.
 */
 
-package print
+package format
 
 import (
+	"io/ioutil"
+	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-)
-
-func TestIsCompatible(t *testing.T) {
-	tests := map[string]struct {
-		expected bool
-	}{
-		"asciidoc document": {
-			expected: true,
-		},
-		"asciidoc table": {
-			expected: true,
-		},
-		"markdown document": {
-			expected: true,
-		},
-		"markdown table": {
-			expected: true,
-		},
-		"markdown": {
-			expected: false,
-		},
-		"markdown-table": {
-			expected: false,
-		},
-		"md": {
-			expected: false,
-		},
-		"md tbl": {
-			expected: false,
-		},
-		"md-tbl": {
-			expected: false,
-		},
-		"json": {
-			expected: false,
-		},
-		"yaml": {
-			expected: false,
-		},
-		"xml": {
-			expected: false,
-		},
-	}
-	for name, tt := range tests {
-		t.Run(name, func(t *testing.T) {
-			assert := assert.New(t)
 
-			generator := NewGenerator(name, "")
-			actual := generator.isCompatible()
-
-			assert.Equal(tt.expected, actual)
-		})
-	}
-}
+	"github.com/terraform-docs/terraform-docs/print"
+	"github.com/terraform-docs/terraform-docs/terraform"
+)
 
 func TestExecuteTemplate(t *testing.T) {
 	header := "this is the header"
 	footer := "this is the footer"
 	tests := map[string]struct {
-		name     string
+		complex  bool
 		content  string
 		template string
 		expected string
 		wantErr  bool
 	}{
 		"Compatible without template": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
 			template: "",
 			expected: "this is the header\nthis is the footer",
 			wantErr:  false,
 		},
 		"Compatible with template not empty section": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
 			template: "{{ .Header }}",
 			expected: "this is the header",
 			wantErr:  false,
 		},
 		"Compatible with template empty section": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
 			template: "{{ .Inputs }}",
 			expected: "",
 			wantErr:  false,
 		},
 		"Compatible with template and unknown section": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
 			template: "{{ .Unknown }}",
 			expected: "",
 			wantErr:  true,
 		},
 		"Compatible with template include file": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
-			template: "{{ include \"testdata/sample-file.txt\" }}",
-			expected: "Sample file to be included.\n",
+			template: "{{ include \"testdata/generator/sample-file.txt\" }}",
+			expected: "Sample file to be included.",
 			wantErr:  false,
 		},
 		"Compatible with template include unknown file": {
-			name:     "markdown table",
+			complex:  true,
 			content:  "this is the header\nthis is the footer",
 			template: "{{ include \"file-not-found\" }}",
 			expected: "",
 			wantErr:  true,
 		},
 		"Incompatible without template": {
-			name:     "yaml",
+			complex:  false,
 			content:  "header: \"this is the header\"\nfooter: \"this is the footer\"",
 			template: "",
 			expected: "header: \"this is the header\"\nfooter: \"this is the footer\"",
 			wantErr:  false,
 		},
 		"Incompatible with template": {
-			name:     "yaml",
+			complex:  false,
 			content:  "header: \"this is the header\"\nfooter: \"this is the footer\"",
 			template: "{{ .Header }}",
 			expected: "header: \"this is the header\"\nfooter: \"this is the footer\"",
@@ -140,12 +92,14 @@ func TestExecuteTemplate(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			assert := assert.New(t)
 
-			generator := NewGenerator(tt.name, "")
+			config := print.DefaultConfig()
+
+			generator := newGenerator(config, tt.complex)
 			generator.content = tt.content
 			generator.header = header
 			generator.footer = footer
 
-			actual, err := generator.ExecuteTemplate(tt.template)
+			actual, err := generator.Render(tt.template)
 
 			if tt.wantErr {
 				assert.NotNil(err)
@@ -160,60 +114,92 @@ func TestExecuteTemplate(t *testing.T) {
 func TestGeneratorFunc(t *testing.T) {
 	text := "foo"
 	tests := map[string]struct {
-		fn     func(string) GenerateFunc
-		actual func(*Generator) string
+		fn     func(string) generateFunc
+		actual func(*generator) string
 	}{
-		"WithContent": {
-			fn:     WithContent,
-			actual: func(r *Generator) string { return r.content },
+		"withContent": {
+			fn:     withContent,
+			actual: func(r *generator) string { return r.content },
 		},
-		"WithHeader": {
-			fn:     WithHeader,
-			actual: func(r *Generator) string { return r.header },
+		"withHeader": {
+			fn:     withHeader,
+			actual: func(r *generator) string { return r.header },
 		},
-		"WithFooter": {
-			fn:     WithFooter,
-			actual: func(r *Generator) string { return r.footer },
+		"withFooter": {
+			fn:     withFooter,
+			actual: func(r *generator) string { return r.footer },
 		},
-		"WithInputs": {
-			fn:     WithInputs,
-			actual: func(r *Generator) string { return r.inputs },
+		"withInputs": {
+			fn:     withInputs,
+			actual: func(r *generator) string { return r.inputs },
 		},
-		"WithModules": {
-			fn:     WithModules,
-			actual: func(r *Generator) string { return r.modules },
+		"withModules": {
+			fn:     withModules,
+			actual: func(r *generator) string { return r.modules },
 		},
-		"WithOutputs": {
-			fn:     WithOutputs,
-			actual: func(r *Generator) string { return r.outputs },
+		"withOutputs": {
+			fn:     withOutputs,
+			actual: func(r *generator) string { return r.outputs },
 		},
-		"WithProviders": {
-			fn:     WithProviders,
-			actual: func(r *Generator) string { return r.providers },
+		"withProviders": {
+			fn:     withProviders,
+			actual: func(r *generator) string { return r.providers },
 		},
-		"WithRequirements": {
-			fn:     WithRequirements,
-			actual: func(r *Generator) string { return r.requirements },
+		"withRequirements": {
+			fn:     withRequirements,
+			actual: func(r *generator) string { return r.requirements },
 		},
-		"WithResources": {
-			fn:     WithResources,
-			actual: func(r *Generator) string { return r.resources },
+		"withResources": {
+			fn:     withResources,
+			actual: func(r *generator) string { return r.resources },
 		},
 	}
 	for name, tt := range tests {
 		t.Run(name, func(t *testing.T) {
 			assert := assert.New(t)
 
-			generator := NewGenerator(name, "", tt.fn(text))
+			config := print.DefaultConfig()
+			config.Sections.Footer = true
+
+			generator := newGenerator(config, false, tt.fn(text))
 
 			assert.Equal(text, tt.actual(generator))
 		})
 	}
 }
 
+func TestGeneratorFuncModule(t *testing.T) {
+	t.Run("withModule", func(t *testing.T) {
+		assert := assert.New(t)
+
+		config := print.DefaultConfig()
+		config.ModuleRoot = filepath.Join("..", "terraform", "testdata", "full-example")
+
+		module, err := terraform.LoadWithOptions(config)
+
+		assert.Nil(err)
+
+		generator := newGenerator(config, true, withModule(module))
+
+		path := filepath.Join("..", "terraform", "testdata", "expected", "full-example-mainTf-Header.golden")
+		data, err := ioutil.ReadFile(path)
+
+		assert.Nil(err)
+
+		expected := string(data)
+
+		assert.Equal(expected, generator.module.Header)
+		assert.Equal("", generator.module.Footer)
+		assert.Equal(7, len(generator.module.Inputs))
+		assert.Equal(3, len(generator.module.Outputs))
+	})
+}
+
 func TestForEach(t *testing.T) {
-	generator := NewGenerator("foo", "")
-	generator.ForEach(func(name string) (string, error) {
+	config := print.DefaultConfig()
+
+	generator := newGenerator(config, false)
+	generator.forEach(func(name string) (string, error) {
 		return name, nil
 	})
 
diff --git a/format/json_test.go b/format/json_test.go
index 3aa627e..2c401cf 100644
--- a/format/json_test.go
+++ b/format/json_test.go
@@ -105,10 +105,7 @@ func TestJson(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/markdown_document_test.go b/format/markdown_document_test.go
index 232c130..fc119a3 100644
--- a/format/markdown_document_test.go
+++ b/format/markdown_document_test.go
@@ -190,10 +190,7 @@ func TestMarkdownDocument(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/markdown_table_test.go b/format/markdown_table_test.go
index 0527370..2de24cf 100644
--- a/format/markdown_table_test.go
+++ b/format/markdown_table_test.go
@@ -190,10 +190,7 @@ func TestMarkdownTable(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/pretty_test.go b/format/pretty_test.go
index 7ce60ff..181e445 100644
--- a/format/pretty_test.go
+++ b/format/pretty_test.go
@@ -105,10 +105,7 @@ func TestPretty(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/tfvars_hcl_test.go b/format/tfvars_hcl_test.go
index 8c84ee0..92c2691 100644
--- a/format/tfvars_hcl_test.go
+++ b/format/tfvars_hcl_test.go
@@ -97,10 +97,7 @@ func TestTfvarsHcl(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/tfvars_json_test.go b/format/tfvars_json_test.go
index 2b255d1..b90f402 100644
--- a/format/tfvars_json_test.go
+++ b/format/tfvars_json_test.go
@@ -88,10 +88,7 @@ func TestTfvarsJson(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/toml_test.go b/format/toml_test.go
index 0a92515..3ee2464 100644
--- a/format/toml_test.go
+++ b/format/toml_test.go
@@ -98,10 +98,7 @@ func TestToml(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/xml_test.go b/format/xml_test.go
index 081291c..a8390ba 100644
--- a/format/xml_test.go
+++ b/format/xml_test.go
@@ -98,10 +98,7 @@ func TestXml(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }
diff --git a/format/yaml_test.go b/format/yaml_test.go
index be6f642..2d9c7c3 100644
--- a/format/yaml_test.go
+++ b/format/yaml_test.go
@@ -98,10 +98,7 @@ func TestYaml(t *testing.T) {
 			err = formatter.Generate(module)
 			assert.Nil(err)
 
-			actual, err := formatter.ExecuteTemplate("")
-
-			assert.Nil(err)
-			assert.Equal(expected, actual)
+			assert.Equal(expected, formatter.Content())
 		})
 	}
 }