Repository: segmentio/terraform-docs
Pull request #195 (2020-02-12 22:02:09)

Introduce Format interface and expose to public pkg
### Prerequisites

Put an `x` into the box(es) that apply:

- [ ] This pull request fixes a bug.
- [ ] 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

A complete overhaul of the `internal/pkg` packages and move the to either

1) `internal/` for simplicity and not duplicate _pkg_ name
2) `pkg/` to be accessible publicly (as this is a hard requirement for enabling Plugin system)

Publicly available packages are:

- `print`: defines an interface to be implemented by a _Formatter_
- `tfconf`: internal representation of a loaded Terraform Module
- `tmpl`: templating system which can be used internally or by external plugin developer

Change in the `internal` package:

- The only notable change is that because `print.Format` interface was introduced everything from `internal/pkg/print/...` was moved under a flat `internal/format` package. This new `format` package contains all the different available formats and their corresponding test files and `testdata` golden files.

### Issues Resolved

#149 is blocked by this.

### 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

- [ ] 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.



Patch:
diff --git a/Makefile b/Makefile
index 8e28f7d..ab9c572 100644
--- a/Makefile
+++ b/Makefile
@@ -24,9 +24,9 @@ GOPKGS      ?= $(shell $(GOCMD) list $(MODVENDOR) ./... | grep -v /vendor)
 MODVENDOR   := -mod=vendor
 
 GOLDFLAGS   :="
-GOLDFLAGS   += -X $(PACKAGE)/internal/pkg/version.version=$(VERSION)
-GOLDFLAGS   += -X $(PACKAGE)/internal/pkg/version.commitHash=$(COMMIT_HASH)
-GOLDFLAGS   += -X $(PACKAGE)/internal/pkg/version.buildDate=$(BUILD_DATE)
+GOLDFLAGS   += -X $(PACKAGE)/internal/version.version=$(VERSION)
+GOLDFLAGS   += -X $(PACKAGE)/internal/version.commitHash=$(COMMIT_HASH)
+GOLDFLAGS   += -X $(PACKAGE)/internal/version.buildDate=$(BUILD_DATE)
 GOLDFLAGS   +="
 
 GOBUILD     ?= CGO_ENABLED=0 $(GOCMD) build $(MODVENDOR) -ldflags $(GOLDFLAGS)
@@ -147,19 +147,27 @@ changelog: ## Generate Changelog
 
 .PHONY: git-chglog
 git-chglog:
+ifeq (, $(shell which git-chglog))
 	curl -sfL https://github.com/git-chglog/git-chglog/releases/download/$(GITCHGLOG_VERSION)/git-chglog_$(shell go env GOOS)_$(shell go env GOARCH) -o $(shell go env GOPATH)/bin/git-chglog && chmod +x $(shell go env GOPATH)/bin/git-chglog
+endif
 
 .PHONY: goimports
 goimports:
+ifeq (, $(shell which goimports))
 	GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports
+endif
 
 .PHONY: golangci
 golangci:
+ifeq (, $(shell which golangci-lint))
 	curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s  -- -b $(shell go env GOPATH)/bin $(GOLANGCI_VERSION)
+endif
 
 .PHONY: gox
 gox:
+ifeq (, $(shell which gox))
 	GO111MODULE=off go get -u github.com/mitchellh/gox
+endif
 
 .PHONY: tools
 tools: ## Install required tools
diff --git a/cmd/json.go b/cmd/json.go
index a389a6a..87f7613 100644
--- a/cmd/json.go
+++ b/cmd/json.go
@@ -1,8 +1,7 @@
 package cmd
 
 import (
-	"github.com/segmentio/terraform-docs/internal/pkg/print/json"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/format"
 	"github.com/spf13/cobra"
 )
 
@@ -10,13 +9,13 @@ var jsonCmd = &cobra.Command{
 	Args:  cobra.ExactArgs(1),
 	Use:   "json [PATH]",
 	Short: "Generate JSON of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return json.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewJSON(settings))
 	},
 }
 
 func init() {
+	jsonCmd.PersistentFlags().BoolVar(new(bool), "no-escape", false, "do not escape special characters")
+
 	rootCmd.AddCommand(jsonCmd)
 }
diff --git a/cmd/markdown.go b/cmd/markdown.go
index 9073100..3f68f27 100644
--- a/cmd/markdown.go
+++ b/cmd/markdown.go
@@ -1,9 +1,7 @@
 package cmd
 
 import (
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown/document"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown/table"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/format"
 	"github.com/spf13/cobra"
 )
 
@@ -12,10 +10,8 @@ var markdownCmd = &cobra.Command{
 	Use:     "markdown [PATH]",
 	Aliases: []string{"md"},
 	Short:   "Generate Markdown of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return table.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewTable(settings))
 	},
 }
 
@@ -24,10 +20,8 @@ var mdTableCmd = &cobra.Command{
 	Use:     "table [PATH]",
 	Aliases: []string{"tbl"},
 	Short:   "Generate Markdown tables of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return table.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewTable(settings))
 	},
 }
 
@@ -36,14 +30,16 @@ var mdDocumentCmd = &cobra.Command{
 	Use:     "document [PATH]",
 	Aliases: []string{"doc"},
 	Short:   "Generate Markdown document of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return document.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewDocument(settings))
 	},
 }
 
 func init() {
+	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-required", false, "do not show \"Required\" column or section")
+	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-escape", false, "do not escape special characters")
+	markdownCmd.PersistentFlags().IntVar(&settings.MarkdownIndent, "indent", 2, "indention level of Markdown sections [1, 2, 3, 4, 5]")
+
 	markdownCmd.AddCommand(mdTableCmd)
 	markdownCmd.AddCommand(mdDocumentCmd)
 
diff --git a/cmd/pretty.go b/cmd/pretty.go
index 4a93797..3167850 100644
--- a/cmd/pretty.go
+++ b/cmd/pretty.go
@@ -1,8 +1,7 @@
 package cmd
 
 import (
-	"github.com/segmentio/terraform-docs/internal/pkg/print/pretty"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/format"
 	"github.com/spf13/cobra"
 )
 
@@ -10,13 +9,13 @@ var prettyCmd = &cobra.Command{
 	Args:  cobra.ExactArgs(1),
 	Use:   "pretty [PATH]",
 	Short: "Generate colorized pretty of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return pretty.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewPretty(settings))
 	},
 }
 
 func init() {
+	prettyCmd.PersistentFlags().BoolVar(new(bool), "no-color", false, "do not colorize printed result")
+
 	rootCmd.AddCommand(prettyCmd)
 }
diff --git a/cmd/root.go b/cmd/root.go
index 2ab9450..6ae132a 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -2,16 +2,15 @@ package cmd
 
 import (
 	"fmt"
-	"log"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
-	"github.com/segmentio/terraform-docs/internal/pkg/version"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/version"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/spf13/cobra"
 )
 
 var settings = print.NewSettings()
-var options = tfconf.Options{}
+var options = module.NewOptions()
 
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
@@ -21,27 +20,19 @@ var rootCmd = &cobra.Command{
 	Long:    "A utility to generate documentation from Terraform modules in various output formats",
 	Version: version.Version(),
 	PersistentPreRun: func(cmd *cobra.Command, args []string) {
-		noheader, _ := cmd.Flags().GetBool("no-header")
-		noproviders, _ := cmd.Flags().GetBool("no-providers")
-		noinputs, _ := cmd.Flags().GetBool("no-inputs")
-		nooutputs, _ := cmd.Flags().GetBool("no-outputs")
-
-		nocolor, _ := cmd.Flags().GetBool("no-color")
-		nosort, _ := cmd.Flags().GetBool("no-sort")
-		norequired, _ := cmd.Flags().GetBool("no-required")
-		noescape, _ := cmd.Flags().GetBool("no-escape")
-
-		settings.ShowHeader = !noheader
-		settings.ShowProviders = !noproviders
-		settings.ShowInputs = !noinputs
-		settings.ShowOutputs = !nooutputs
-
-		settings.OutputValues = options.OutputValues
-
-		settings.ShowColor = !nocolor
-		settings.SortByName = !nosort
-		settings.ShowRequired = !norequired
-		settings.EscapeCharacters = !noescape
+		oppositeBool := func(name string) bool {
+			val, _ := cmd.Flags().GetBool(name)
+			return !val
+		}
+		settings.ShowHeader = oppositeBool("no-header")
+		settings.ShowProviders = oppositeBool("no-providers")
+		settings.ShowInputs = oppositeBool("no-inputs")
+		settings.ShowOutputs = oppositeBool("no-outputs")
+
+		settings.ShowColor = oppositeBool("no-color")
+		settings.SortByName = oppositeBool("no-sort")
+		settings.ShowRequired = oppositeBool("no-required")
+		settings.EscapeCharacters = oppositeBool("no-escape")
 	},
 }
 
@@ -63,14 +54,6 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&settings.SortByRequired, "sort-inputs-by-required", false, "[deprecated] use '--sort-by-required' instead")
 	rootCmd.PersistentFlags().BoolVar(new(bool), "with-aggregate-type-defaults", false, "[deprecated] print default values of aggregate types")
 	//-----------------------------
-
-	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-required", false, "do not show \"Required\" column or section")
-	markdownCmd.PersistentFlags().BoolVar(new(bool), "no-escape", false, "do not escape special characters")
-	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.
@@ -92,18 +75,22 @@ func FormatterCmds() []*cobra.Command {
 	}
 }
 
-func doPrint(path string, fn func(*tfconf.Module) (string, error)) {
-	options.Path = path
-	module, err := tfconf.CreateModule(&options)
+func doPrint(path string, printer print.Format) error {
+	options.With(&module.Options{
+		Path: path,
+		SortBy: &module.SortBy{
+			Name:     settings.SortByName,
+			Required: settings.SortByRequired,
+		},
+	})
+	tfmodule, err := module.LoadWithOptions(options)
 	if err != nil {
-		log.Fatal(err)
+		return err
 	}
-
-	output, err := fn(module)
-
+	output, err := printer.Print(tfmodule, settings)
 	if err != nil {
-		log.Fatal(err)
+		return err
 	}
-
 	fmt.Println(output)
+	return nil
 }
diff --git a/cmd/version.go b/cmd/version.go
index 97864d6..457d63c 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -3,7 +3,7 @@ package cmd
 import (
 	"fmt"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/version"
+	"github.com/segmentio/terraform-docs/internal/version"
 	"github.com/spf13/cobra"
 )
 
diff --git a/cmd/yaml.go b/cmd/yaml.go
index f86fb44..854cb85 100644
--- a/cmd/yaml.go
+++ b/cmd/yaml.go
@@ -1,8 +1,7 @@
 package cmd
 
 import (
-	"github.com/segmentio/terraform-docs/internal/pkg/print/yaml"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/format"
 	"github.com/spf13/cobra"
 )
 
@@ -10,10 +9,8 @@ var yamlCmd = &cobra.Command{
 	Args:  cobra.ExactArgs(1),
 	Use:   "yaml [PATH]",
 	Short: "Generate YAML of inputs and outputs",
-	Run: func(cmd *cobra.Command, args []string) {
-		doPrint(args[0], func(module *tfconf.Module) (string, error) {
-			return yaml.Print(module, settings)
-		})
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return doPrint(args[0], format.NewYAML(settings))
 	},
 }
 
diff --git a/internal/pkg/print/markdown/document/document.go b/internal/format/document.go
similarity index 72%
rename from internal/pkg/print/markdown/document/document.go
rename to internal/format/document.go
index 38cc7e0..a057862 100644
--- a/internal/pkg/print/markdown/document/document.go
+++ b/internal/format/document.go
@@ -1,16 +1,15 @@
-package document
+package format
 
 import (
 	"text/template"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
-	"github.com/segmentio/terraform-docs/internal/pkg/tmpl"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/pkg/tmpl"
 )
 
 const (
-	headerTpl = `
+	documentHeaderTpl = `
 	{{- if .Settings.ShowHeader -}}
 		{{- with .Module.Header -}}
 			{{ sanitizeHeader . }}
@@ -19,7 +18,7 @@ const (
 	{{ end -}}
 	`
 
-	providersTpl = `
+	documentProvidersTpl = `
 	{{- if .Settings.ShowProviders -}}
 		{{ indent 0 }} Providers
 		{{ if not .Module.Providers }}
@@ -34,7 +33,7 @@ const (
 	{{ end -}}
 	`
 
-	inputsTpl = `
+	documentInputsTpl = `
 	{{- if .Settings.ShowInputs -}}
 		{{- if .Settings.ShowRequired -}}
 			{{ indent 0 }} Required Inputs
@@ -69,7 +68,7 @@ const (
 	{{ end -}}
 	`
 
-	inputTpl = `
+	documentInputTpl = `
 	{{ printf "\n" }}
 	{{ indent 1 }} {{ name .Name }}
 
@@ -82,7 +81,7 @@ const (
 	{{- end }}
 	`
 
-	outputsTpl = `
+	documentOutputsTpl = `
 	{{- if .Settings.ShowOutputs -}}
 		{{ indent 0 }} Outputs
 		{{ if not .Module.Outputs }}
@@ -110,33 +109,36 @@ const (
 	`
 )
 
-// Print prints a document as Markdown document.
-func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
-	module.Sort(settings)
+// Document represents Markdown Document format.
+type Document struct {
+	template *tmpl.Template
+}
 
-	t := tmpl.NewTemplate(&tmpl.Item{
+// NewDocument returns new instance of Document.
+func NewDocument(settings *print.Settings) *Document {
+	tt := tmpl.NewTemplate(&tmpl.Item{
 		Name: "document",
 		Text: documentTpl,
 	}, &tmpl.Item{
 		Name: "header",
-		Text: headerTpl,
+		Text: documentHeaderTpl,
 	}, &tmpl.Item{
 		Name: "providers",
-		Text: providersTpl,
+		Text: documentProvidersTpl,
 	}, &tmpl.Item{
 		Name: "inputs",
-		Text: inputsTpl,
+		Text: documentInputsTpl,
 	}, &tmpl.Item{
 		Name: "input",
-		Text: inputTpl,
+		Text: documentInputTpl,
 	}, &tmpl.Item{
 		Name: "outputs",
-		Text: outputsTpl,
+		Text: documentOutputsTpl,
 	})
-	t.Settings(settings)
-	t.CustomFunc(template.FuncMap{
+	tt.Settings(settings)
+	tt.CustomFunc(template.FuncMap{
 		"type": func(t string) string {
-			result, extraline := markdown.PrintFencedCodeBlock(t, "hcl")
+			result, extraline := printFencedCodeBlock(t, "hcl")
 			if !extraline {
 				result += "\n"
 			}
@@ -146,7 +148,7 @@ func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 			if v == "n/a" {
 				return v
 			}
-			result, extraline := markdown.PrintFencedCodeBlock(v, "json")
+			result, extraline := printFencedCodeBlock(v, "json")
 			if !extraline {
 				result += "\n"
 			}
@@ -156,10 +158,16 @@ func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 			return settings.ShowRequired
 		},
 	})
-	rendered, err := t.Render(module)
+	return &Document{
+		template: tt,
+	}
+}
+
+// Print prints a Terraform module as Markdown document.
+func (d *Document) Print(module *tfconf.Module, settings *print.Settings) (string, error) {
+	rendered, err := d.template.Render(module)
 	if err != nil {
 		return "", err
 	}
-
-	return markdown.Sanitize(rendered), nil
+	return sanitize(rendered), nil
 }
diff --git a/internal/pkg/print/json/json.go b/internal/format/json.go
similarity index 64%
rename from internal/pkg/print/json/json.go
rename to internal/format/json.go
index 72c6f9d..d0c3107 100644
--- a/internal/pkg/print/json/json.go
+++ b/internal/format/json.go
@@ -1,18 +1,24 @@
-package json
+package format
 
 import (
 	"bytes"
 	"encoding/json"
 	"strings"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
 )
 
-// Print prints a document as json.
-func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
-	module.Sort(settings)
+// JSON represents JSON format.
+type JSON struct{}
 
+// NewJSON returns new instance of JSON.
+func NewJSON(settings *print.Settings) *JSON {
+	return &JSON{}
+}
+
+// Print prints a Terraform module as json.
+func (j *JSON) Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 	copy := &tfconf.Module{
 		Header:    "",
 		Providers: make([]*tfconf.Provider, 0),
diff --git a/internal/pkg/print/pretty/pretty.go b/internal/format/pretty.go
similarity index 68%
rename from internal/pkg/print/pretty/pretty.go
rename to internal/format/pretty.go
index ff9f296..1e93d8b 100644
--- a/internal/pkg/print/pretty/pretty.go
+++ b/internal/format/pretty.go
@@ -1,16 +1,16 @@
-package pretty
+package format
 
 import (
 	"fmt"
 	"text/template"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
-	"github.com/segmentio/terraform-docs/internal/pkg/tmpl"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/pkg/tmpl"
 )
 
 const (
-	headerTpl = `
+	prettyHeaderTpl = `
 	{{- if .Settings.ShowHeader -}}
 		{{- with .Module.Header }}
 			{{- printf "\n" }}
@@ -20,7 +20,7 @@ const (
 	{{ end -}}
 	`
 
-	providersTpl = `
+	prettyProvidersTpl = `
 	{{- if .Settings.ShowProviders -}}
 		{{- with .Module.Providers }}
 			{{- printf "\n" -}}
@@ -33,7 +33,7 @@ const (
 	{{ end -}}
 	`
 
-	inputsTpl = `
+	prettyInputsTpl = `
 	{{- if .Settings.ShowInputs -}}
 		{{- with .Module.Inputs }}
 			{{- printf "\n" -}}
@@ -46,7 +46,7 @@ const (
 	{{ end -}}
 	`
 
-	outputsTpl = `
+	prettyOutputsTpl = `
 	{{- if .Settings.ShowOutputs -}}
 		{{- with .Module.Outputs }}
 			{{- printf "\n" -}}
@@ -70,28 +70,31 @@ const (
 	`
 )
 
-// Print prints a pretty document.
-func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
-	module.Sort(settings)
+// Pretty represents colorized pretty format.
+type Pretty struct {
+	template *tmpl.Template
+}
 
-	t := tmpl.NewTemplate(&tmpl.Item{
+// NewPretty returns new instance of Pretty.
+func NewPretty(settings *print.Settings) *Pretty {
+	tt := tmpl.NewTemplate(&tmpl.Item{
 		Name: "pretty",
 		Text: prettyTpl,
 	}, &tmpl.Item{
 		Name: "header",
-		Text: headerTpl,
+		Text: prettyHeaderTpl,
 	}, &tmpl.Item{
 		Name: "providers",
-		Text: providersTpl,
+		Text: prettyProvidersTpl,
 	}, &tmpl.Item{
 		Name: "inputs",
-		Text: inputsTpl,
+		Text: prettyInputsTpl,
 	}, &tmpl.Item{
 		Name: "outputs",
-		Text: outputsTpl,
+		Text: prettyOutputsTpl,
 	})
-	t.Settings(settings)
-	t.CustomFunc(template.FuncMap{
+	tt.Settings(settings)
+	tt.CustomFunc(template.FuncMap{
 		"colorize": func(c string, s string) string {
 			r := "\033[0m"
 			if !settings.ShowColor {
@@ -101,10 +104,16 @@ func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 			return fmt.Sprintf("%s%s%s", c, s, r)
 		},
 	})
-	rendered, err := t.Render(module)
+	return &Pretty{
+		template: tt,
+	}
+}
+
+// Print prints a Terraform module document.
+func (p *Pretty) Print(module *tfconf.Module, settings *print.Settings) (string, error) {
+	rendered, err := p.template.Render(module)
 	if err != nil {
 		return "", err
 	}
-
 	return rendered, nil
 }
diff --git a/internal/pkg/print/markdown/table/table.go b/internal/format/table.go
similarity index 71%
rename from internal/pkg/print/markdown/table/table.go
rename to internal/format/table.go
index e9f0977..8c2de19 100644
--- a/internal/pkg/print/markdown/table/table.go
+++ b/internal/format/table.go
@@ -1,16 +1,15 @@
-package table
+package format
 
 import (
 	"text/template"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
-	"github.com/segmentio/terraform-docs/internal/pkg/tmpl"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/pkg/tmpl"
 )
 
 const (
-	headerTpl = `
+	tableHeaderTpl = `
 	{{- if .Settings.ShowHeader -}}
 		{{- with .Module.Header -}}
 			{{ sanitizeHeader . }}
@@ -19,7 +18,7 @@ const (
 	{{ end -}}
 	`
 
-	providersTpl = `
+	tableProvidersTpl = `
 	{{- if .Settings.ShowProviders -}}
 		{{ indent 0 }} Providers
 		{{ if not .Module.Providers }}
@@ -34,7 +33,7 @@ const (
 	{{ end -}}
 	`
 
-	inputsTpl = `
+	tableInputsTpl = `
 	{{- if .Settings.ShowInputs -}}
 		{{ indent 0 }} Inputs
 		{{ if not .Module.Inputs }}
@@ -58,7 +57,7 @@ const (
 	{{ end -}}
 	`
 
-	outputsTpl = `
+	tableOutputsTpl = `
 	{{- if .Settings.ShowOutputs -}}
 		{{ indent 0 }} Outputs
 		{{ if not .Module.Outputs }}
@@ -81,44 +80,53 @@ const (
 	`
 )
 
-// Print prints a document as Markdown tables.
-func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
-	module.Sort(settings)
+// Table represents Markdown Table format.
+type Table struct {
+	template *tmpl.Template
+}
 
-	t := tmpl.NewTemplate(&tmpl.Item{
+// NewTable returns new instance of Table.
+func NewTable(settings *print.Settings) *Table {
+	tt := tmpl.NewTemplate(&tmpl.Item{
 		Name: "table",
 		Text: tableTpl,
 	}, &tmpl.Item{
 		Name: "header",
-		Text: headerTpl,
+		Text: tableHeaderTpl,
 	}, &tmpl.Item{
 		Name: "providers",
-		Text: providersTpl,
+		Text: tableProvidersTpl,
 	}, &tmpl.Item{
 		Name: "inputs",
-		Text: inputsTpl,
+		Text: tableInputsTpl,
 	}, &tmpl.Item{
 		Name: "outputs",
-		Text: outputsTpl,
+		Text: tableOutputsTpl,
 	})
-	t.Settings(settings)
-	t.CustomFunc(template.FuncMap{
+	tt.Settings(settings)
+	tt.CustomFunc(template.FuncMap{
 		"type": func(t string) string {
-			inputType, _ := markdown.PrintFencedCodeBlock(t, "")
+			inputType, _ := printFencedCodeBlock(t, "")
 			return inputType
 		},
 		"value": func(v string) string {
 			var result = "n/a"
 			if v != "" {
-				result, _ = markdown.PrintFencedCodeBlock(v, "")
+				result, _ = printFencedCodeBlock(v, "")
 			}
 			return result
 		},
 	})
-	rendered, err := t.Render(module)
+	return &Table{
+		template: tt,
+	}
+}
+
+// Print prints a Terraform module as Markdown tables.
+func (t *Table) Print(module *tfconf.Module, settings *print.Settings) (string, error) {
+	rendered, err := t.template.Render(module)
 	if err != nil {
 		return "", err
 	}
-
-	return markdown.Sanitize(rendered), nil
+	return sanitize(rendered), nil
 }
diff --git a/internal/pkg/print/markdown/document/testdata/document-EscapeCharacters.golden b/internal/format/testdata/document/document-EscapeCharacters.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-EscapeCharacters.golden
rename to internal/format/testdata/document/document-EscapeCharacters.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-IndentationAboveAllowed.golden b/internal/format/testdata/document/document-IndentationAboveAllowed.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-IndentationAboveAllowed.golden
rename to internal/format/testdata/document/document-IndentationAboveAllowed.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-IndentationBelowAllowed.golden b/internal/format/testdata/document/document-IndentationBelowAllowed.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-IndentationBelowAllowed.golden
rename to internal/format/testdata/document/document-IndentationBelowAllowed.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-IndentationOfFour.golden b/internal/format/testdata/document/document-IndentationOfFour.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-IndentationOfFour.golden
rename to internal/format/testdata/document/document-IndentationOfFour.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-NoHeader.golden b/internal/format/testdata/document/document-NoHeader.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-NoHeader.golden
rename to internal/format/testdata/document/document-NoHeader.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-NoInputs.golden b/internal/format/testdata/document/document-NoInputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-NoInputs.golden
rename to internal/format/testdata/document/document-NoInputs.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-NoOutputs.golden b/internal/format/testdata/document/document-NoOutputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-NoOutputs.golden
rename to internal/format/testdata/document/document-NoOutputs.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-NoProviders.golden b/internal/format/testdata/document/document-NoProviders.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-NoProviders.golden
rename to internal/format/testdata/document/document-NoProviders.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-OnlyHeader.golden b/internal/format/testdata/document/document-OnlyHeader.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-OnlyHeader.golden
rename to internal/format/testdata/document/document-OnlyHeader.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-OnlyInputs.golden b/internal/format/testdata/document/document-OnlyInputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-OnlyInputs.golden
rename to internal/format/testdata/document/document-OnlyInputs.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-OnlyOutputs.golden b/internal/format/testdata/document/document-OnlyOutputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-OnlyOutputs.golden
rename to internal/format/testdata/document/document-OnlyOutputs.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-OnlyProviders.golden b/internal/format/testdata/document/document-OnlyProviders.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-OnlyProviders.golden
rename to internal/format/testdata/document/document-OnlyProviders.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-OutputValues.golden b/internal/format/testdata/document/document-OutputValues.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-OutputValues.golden
rename to internal/format/testdata/document/document-OutputValues.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-SortByName.golden b/internal/format/testdata/document/document-SortByName.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-SortByName.golden
rename to internal/format/testdata/document/document-SortByName.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-SortByRequired.golden b/internal/format/testdata/document/document-SortByRequired.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-SortByRequired.golden
rename to internal/format/testdata/document/document-SortByRequired.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document-WithRequired.golden b/internal/format/testdata/document/document-WithRequired.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document-WithRequired.golden
rename to internal/format/testdata/document/document-WithRequired.golden
diff --git a/internal/pkg/print/markdown/document/testdata/document.golden b/internal/format/testdata/document/document.golden
similarity index 100%
rename from internal/pkg/print/markdown/document/testdata/document.golden
rename to internal/format/testdata/document/document.golden
diff --git a/internal/pkg/print/json/testdata/json-EscapeCharacters.golden b/internal/format/testdata/json/json-EscapeCharacters.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-EscapeCharacters.golden
rename to internal/format/testdata/json/json-EscapeCharacters.golden
diff --git a/internal/pkg/print/json/testdata/json-NoHeader.golden b/internal/format/testdata/json/json-NoHeader.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-NoHeader.golden
rename to internal/format/testdata/json/json-NoHeader.golden
diff --git a/internal/pkg/print/json/testdata/json-NoInputs.golden b/internal/format/testdata/json/json-NoInputs.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-NoInputs.golden
rename to internal/format/testdata/json/json-NoInputs.golden
diff --git a/internal/pkg/print/json/testdata/json-NoOutputs.golden b/internal/format/testdata/json/json-NoOutputs.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-NoOutputs.golden
rename to internal/format/testdata/json/json-NoOutputs.golden
diff --git a/internal/pkg/print/json/testdata/json-NoProviders.golden b/internal/format/testdata/json/json-NoProviders.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-NoProviders.golden
rename to internal/format/testdata/json/json-NoProviders.golden
diff --git a/internal/pkg/print/json/testdata/json-OnlyHeader.golden b/internal/format/testdata/json/json-OnlyHeader.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-OnlyHeader.golden
rename to internal/format/testdata/json/json-OnlyHeader.golden
diff --git a/internal/pkg/print/json/testdata/json-OnlyInputs.golden b/internal/format/testdata/json/json-OnlyInputs.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-OnlyInputs.golden
rename to internal/format/testdata/json/json-OnlyInputs.golden
diff --git a/internal/pkg/print/json/testdata/json-OnlyOutputs.golden b/internal/format/testdata/json/json-OnlyOutputs.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-OnlyOutputs.golden
rename to internal/format/testdata/json/json-OnlyOutputs.golden
diff --git a/internal/pkg/print/json/testdata/json-OnlyProviders.golden b/internal/format/testdata/json/json-OnlyProviders.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-OnlyProviders.golden
rename to internal/format/testdata/json/json-OnlyProviders.golden
diff --git a/internal/pkg/print/json/testdata/json-OutputValues.golden b/internal/format/testdata/json/json-OutputValues.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-OutputValues.golden
rename to internal/format/testdata/json/json-OutputValues.golden
diff --git a/internal/pkg/print/json/testdata/json-SortByName.golden b/internal/format/testdata/json/json-SortByName.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-SortByName.golden
rename to internal/format/testdata/json/json-SortByName.golden
diff --git a/internal/pkg/print/json/testdata/json-SortByRequired.golden b/internal/format/testdata/json/json-SortByRequired.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json-SortByRequired.golden
rename to internal/format/testdata/json/json-SortByRequired.golden
diff --git a/internal/pkg/print/json/testdata/json.golden b/internal/format/testdata/json/json.golden
similarity index 100%
rename from internal/pkg/print/json/testdata/json.golden
rename to internal/format/testdata/json/json.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-NoColor.golden b/internal/format/testdata/pretty/pretty-NoColor.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-NoColor.golden
rename to internal/format/testdata/pretty/pretty-NoColor.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-NoHeader.golden b/internal/format/testdata/pretty/pretty-NoHeader.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-NoHeader.golden
rename to internal/format/testdata/pretty/pretty-NoHeader.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-NoInputs.golden b/internal/format/testdata/pretty/pretty-NoInputs.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-NoInputs.golden
rename to internal/format/testdata/pretty/pretty-NoInputs.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-NoOutputs.golden b/internal/format/testdata/pretty/pretty-NoOutputs.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-NoOutputs.golden
rename to internal/format/testdata/pretty/pretty-NoOutputs.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-NoProviders.golden b/internal/format/testdata/pretty/pretty-NoProviders.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-NoProviders.golden
rename to internal/format/testdata/pretty/pretty-NoProviders.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-OnlyHeader.golden b/internal/format/testdata/pretty/pretty-OnlyHeader.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-OnlyHeader.golden
rename to internal/format/testdata/pretty/pretty-OnlyHeader.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-OnlyInputs.golden b/internal/format/testdata/pretty/pretty-OnlyInputs.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-OnlyInputs.golden
rename to internal/format/testdata/pretty/pretty-OnlyInputs.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-OnlyOutputs.golden b/internal/format/testdata/pretty/pretty-OnlyOutputs.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-OnlyOutputs.golden
rename to internal/format/testdata/pretty/pretty-OnlyOutputs.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-OnlyProviders.golden b/internal/format/testdata/pretty/pretty-OnlyProviders.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-OnlyProviders.golden
rename to internal/format/testdata/pretty/pretty-OnlyProviders.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-OutputValues.golden b/internal/format/testdata/pretty/pretty-OutputValues.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-OutputValues.golden
rename to internal/format/testdata/pretty/pretty-OutputValues.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-SortByName.golden b/internal/format/testdata/pretty/pretty-SortByName.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-SortByName.golden
rename to internal/format/testdata/pretty/pretty-SortByName.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty-SortByRequired.golden b/internal/format/testdata/pretty/pretty-SortByRequired.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty-SortByRequired.golden
rename to internal/format/testdata/pretty/pretty-SortByRequired.golden
diff --git a/internal/pkg/print/pretty/testdata/pretty.golden b/internal/format/testdata/pretty/pretty.golden
similarity index 100%
rename from internal/pkg/print/pretty/testdata/pretty.golden
rename to internal/format/testdata/pretty/pretty.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-EscapeCharacters.golden b/internal/format/testdata/table/table-EscapeCharacters.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-EscapeCharacters.golden
rename to internal/format/testdata/table/table-EscapeCharacters.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-IndentationAboveAllowed.golden b/internal/format/testdata/table/table-IndentationAboveAllowed.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-IndentationAboveAllowed.golden
rename to internal/format/testdata/table/table-IndentationAboveAllowed.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-IndentationBellowAllowed.golden b/internal/format/testdata/table/table-IndentationBelowAllowed.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-IndentationBellowAllowed.golden
rename to internal/format/testdata/table/table-IndentationBelowAllowed.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-IndentationOfFour.golden b/internal/format/testdata/table/table-IndentationOfFour.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-IndentationOfFour.golden
rename to internal/format/testdata/table/table-IndentationOfFour.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-NoHeader.golden b/internal/format/testdata/table/table-NoHeader.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-NoHeader.golden
rename to internal/format/testdata/table/table-NoHeader.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-NoInputs.golden b/internal/format/testdata/table/table-NoInputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-NoInputs.golden
rename to internal/format/testdata/table/table-NoInputs.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-NoOutputs.golden b/internal/format/testdata/table/table-NoOutputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-NoOutputs.golden
rename to internal/format/testdata/table/table-NoOutputs.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-NoProviders.golden b/internal/format/testdata/table/table-NoProviders.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-NoProviders.golden
rename to internal/format/testdata/table/table-NoProviders.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-OnlyHeader.golden b/internal/format/testdata/table/table-OnlyHeader.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-OnlyHeader.golden
rename to internal/format/testdata/table/table-OnlyHeader.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-OnlyInputs.golden b/internal/format/testdata/table/table-OnlyInputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-OnlyInputs.golden
rename to internal/format/testdata/table/table-OnlyInputs.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-OnlyOutputs.golden b/internal/format/testdata/table/table-OnlyOutputs.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-OnlyOutputs.golden
rename to internal/format/testdata/table/table-OnlyOutputs.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-OnlyProviders.golden b/internal/format/testdata/table/table-OnlyProviders.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-OnlyProviders.golden
rename to internal/format/testdata/table/table-OnlyProviders.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-OutputValues.golden b/internal/format/testdata/table/table-OutputValues.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-OutputValues.golden
rename to internal/format/testdata/table/table-OutputValues.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-SortByName.golden b/internal/format/testdata/table/table-SortByName.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-SortByName.golden
rename to internal/format/testdata/table/table-SortByName.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-SortByRequired.golden b/internal/format/testdata/table/table-SortByRequired.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-SortByRequired.golden
rename to internal/format/testdata/table/table-SortByRequired.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table-WithRequired.golden b/internal/format/testdata/table/table-WithRequired.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table-WithRequired.golden
rename to internal/format/testdata/table/table-WithRequired.golden
diff --git a/internal/pkg/print/markdown/table/testdata/table.golden b/internal/format/testdata/table/table.golden
similarity index 100%
rename from internal/pkg/print/markdown/table/testdata/table.golden
rename to internal/format/testdata/table/table.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-NoHeader.golden b/internal/format/testdata/yaml/yaml-NoHeader.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-NoHeader.golden
rename to internal/format/testdata/yaml/yaml-NoHeader.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-NoInputs.golden b/internal/format/testdata/yaml/yaml-NoInputs.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-NoInputs.golden
rename to internal/format/testdata/yaml/yaml-NoInputs.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-NoOutputs.golden b/internal/format/testdata/yaml/yaml-NoOutputs.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-NoOutputs.golden
rename to internal/format/testdata/yaml/yaml-NoOutputs.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-NoProviders.golden b/internal/format/testdata/yaml/yaml-NoProviders.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-NoProviders.golden
rename to internal/format/testdata/yaml/yaml-NoProviders.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-OnlyHeader.golden b/internal/format/testdata/yaml/yaml-OnlyHeader.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-OnlyHeader.golden
rename to internal/format/testdata/yaml/yaml-OnlyHeader.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-OnlyInputs.golden b/internal/format/testdata/yaml/yaml-OnlyInputs.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-OnlyInputs.golden
rename to internal/format/testdata/yaml/yaml-OnlyInputs.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-OnlyOutputs.golden b/internal/format/testdata/yaml/yaml-OnlyOutputs.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-OnlyOutputs.golden
rename to internal/format/testdata/yaml/yaml-OnlyOutputs.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-OnlyProviders.golden b/internal/format/testdata/yaml/yaml-OnlyProviders.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-OnlyProviders.golden
rename to internal/format/testdata/yaml/yaml-OnlyProviders.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-OutputValues.golden b/internal/format/testdata/yaml/yaml-OutputValues.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-OutputValues.golden
rename to internal/format/testdata/yaml/yaml-OutputValues.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-SortByName.golden b/internal/format/testdata/yaml/yaml-SortByName.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-SortByName.golden
rename to internal/format/testdata/yaml/yaml-SortByName.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml-SortByRequired.golden b/internal/format/testdata/yaml/yaml-SortByRequired.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml-SortByRequired.golden
rename to internal/format/testdata/yaml/yaml-SortByRequired.golden
diff --git a/internal/pkg/print/yaml/testdata/yaml.golden b/internal/format/testdata/yaml/yaml.golden
similarity index 100%
rename from internal/pkg/print/yaml/testdata/yaml.golden
rename to internal/format/testdata/yaml/yaml.golden
diff --git a/internal/format/util.go b/internal/format/util.go
new file mode 100644
index 0000000..455edf1
--- /dev/null
+++ b/internal/format/util.go
@@ -0,0 +1,43 @@
+package format
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+// sanitize cleans a Markdown document to soothe linters.
+func sanitize(markdown string) string {
+	result := markdown
+
+	// Preserve double spaces at the end of the line
+	result = regexp.MustCompile(` {2}(\r?\n)`).ReplaceAllString(result, "‡‡$1")
+
+	// Remove trailing spaces from the end of lines
+	result = regexp.MustCompile(` +(\r?\n)`).ReplaceAllString(result, "$1")
+	result = regexp.MustCompile(` +$`).ReplaceAllLiteralString(result, "")
+
+	// Preserve double spaces at the end of the line
+	result = regexp.MustCompile(`‡‡(\r?\n)`).ReplaceAllString(result, "  $1")
+
+	// Remove blank line with only double spaces in it
+	result = regexp.MustCompile(`(\r?\n)  (\r?\n)`).ReplaceAllString(result, "$1")
+
+	// Remove multiple consecutive blank lines
+	result = regexp.MustCompile(`(\r?\n){3,}`).ReplaceAllString(result, "$1$1")
+	result = regexp.MustCompile(`(\r?\n){2,}$`).ReplaceAllString(result, "$1")
+
+	return result
+}
+
+// printFencedCodeBlock prints codes in fences, it automatically detects if
+// the input 'code' contains '\n' it will use multi line fence, otherwise it
+// wraps the 'code' inside single-tick block.
+// If the fenced is multi-line it also appens an extra '\n` at the end and
+// returns true accordingly, otherwise returns false for non-carriage return.
+func printFencedCodeBlock(code string, language string) (string, bool) {
+	if strings.Contains(code, "\n") {
+		return fmt.Sprintf("\n\n```%s\n%s\n```\n", language, code), true
+	}
+	return fmt.Sprintf("`%s`", code), false
+}
diff --git a/internal/pkg/print/yaml/yaml.go b/internal/format/yaml.go
similarity index 59%
rename from internal/pkg/print/yaml/yaml.go
rename to internal/format/yaml.go
index 890607f..dba3ac0 100644
--- a/internal/pkg/print/yaml/yaml.go
+++ b/internal/format/yaml.go
@@ -1,17 +1,23 @@
-package yaml
+package format
 
 import (
 	"strings"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
 	"gopkg.in/yaml.v2"
 )
 
-// Print prints a document as yaml.
-func Print(module *tfconf.Module, settings *print.Settings) (string, error) {
-	module.Sort(settings)
+// YAML represents YAML format.
+type YAML struct{}
 
+// NewYAML returns new instance of YAML.
+func NewYAML(settings *print.Settings) *YAML {
+	return &YAML{}
+}
+
+// Print prints a Terraform module as yaml.
+func (y *YAML) Print(module *tfconf.Module, settings *print.Settings) (string, error) {
 	copy := &tfconf.Module{
 		Header:    "",
 		Providers: make([]*tfconf.Provider, 0),
diff --git a/internal/module/input.go b/internal/module/input.go
new file mode 100644
index 0000000..f0b176c
--- /dev/null
+++ b/internal/module/input.go
@@ -0,0 +1,39 @@
+package module
+
+import (
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+)
+
+type inputsSortedByName []*tfconf.Input
+
+func (a inputsSortedByName) Len() int      { return len(a) }
+func (a inputsSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a inputsSortedByName) Less(i, j int) bool {
+	return a[i].Name < a[j].Name
+}
+
+type inputsSortedByRequired []*tfconf.Input
+
+func (a inputsSortedByRequired) Len() int      { return len(a) }
+func (a inputsSortedByRequired) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a inputsSortedByRequired) Less(i, j int) bool {
+	switch {
+	// i required, j not: i gets priority
+	case !a[i].HasDefault() && a[j].HasDefault():
+		return true
+	// j required, i not: i does not get priority
+	case a[i].HasDefault() && !a[j].HasDefault():
+		return false
+	// Otherwise, sort by name
+	default:
+		return a[i].Name < a[j].Name
+	}
+}
+
+type inputsSortedByPosition []*tfconf.Input
+
+func (a inputsSortedByPosition) Len() int      { return len(a) }
+func (a inputsSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a inputsSortedByPosition) Less(i, j int) bool {
+	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
+}
diff --git a/internal/module/module.go b/internal/module/module.go
new file mode 100644
index 0000000..6ebe9f1
--- /dev/null
+++ b/internal/module/module.go
@@ -0,0 +1,264 @@
+package module
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"github.com/hashicorp/terraform-config-inspect/tfconfig"
+	"github.com/segmentio/terraform-docs/internal/reader"
+	"github.com/segmentio/terraform-docs/internal/types"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+)
+
+// LoadWithOptions returns new instance of Module with all the inputs and
+// outputs discovered from provided 'path' containing Terraform config
+func LoadWithOptions(options *Options) (*tfconf.Module, error) {
+	tfmodule := loadModule(options.Path)
+
+	header := loadHeader(options.Path)
+	inputs, required, optional := loadInputs(tfmodule)
+	outputs := loadOutputs(tfmodule, options)
+	providers := loadProviders(tfmodule)
+
+	module := &tfconf.Module{
+		Header:    header,
+		Inputs:    inputs,
+		Outputs:   outputs,
+		Providers: providers,
+
+		RequiredInputs: required,
+		OptionalInputs: optional,
+	}
+	sortItems(module, options.SortBy)
+	return module, nil
+}
+
+func loadModule(path string) *tfconfig.Module {
+	module, diag := tfconfig.LoadModule(path)
+	if diag != nil && diag.HasErrors() {
+		log.Fatal(diag)
+	}
+	return module
+}
+
+func loadHeader(path string) string {
+	filename := filepath.Join(path, "main.tf")
+	_, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return "" // absorb the error, we don't need to bubble it up or break the execution
+	}
+	lines := reader.Lines{
+		FileName: filename,
+		LineNum:  -1,
+		Condition: func(line string) bool {
+			line = strings.TrimSpace(line)
+			return strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*") || strings.HasPrefix(line, "*/")
+		},
+		Parser: func(line string) (string, bool) {
+			line = strings.TrimSpace(line)
+			if strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*/") {
+				return "", false
+			}
+			if line == "*" {
+				return "", true
+			}
+			line = strings.TrimPrefix(line, "* ")
+			return line, true
+		},
+	}
+	header, err := lines.Extract()
+	if err != nil {
+		return "" // absorb the error, we don't need to bubble it up or break the execution
+	}
+	return strings.Join(header, "\n")
+}
+
+func loadInputs(tfmodule *tfconfig.Module) ([]*tfconf.Input, []*tfconf.Input, []*tfconf.Input) {
+	var inputs = make([]*tfconf.Input, 0, len(tfmodule.Variables))
+	var required = make([]*tfconf.Input, 0, len(tfmodule.Variables))
+	var optional = make([]*tfconf.Input, 0, len(tfmodule.Variables))
+
+	for _, input := range tfmodule.Variables {
+		inputType := input.Type
+		if input.Type == "" {
+			inputType = "any"
+			if input.Default != nil {
+				switch xType := fmt.Sprintf("%T", input.Default); xType {
+				case "string":
+					inputType = "string"
+				case "int", "int8", "int16", "int32", "int64", "float32", "float64":
+					inputType = "number"
+				case "bool":
+					inputType = "bool"
+				case "[]interface {}":
+					inputType = "list"
+				case "map[string]interface {}":
+					inputType = "map"
+				}
+			}
+		}
+
+		inputDescription := input.Description
+		if inputDescription == "" {
+			inputDescription = loadComments(input.Pos.Filename, input.Pos.Line-1)
+		}
+
+		i := &tfconf.Input{
+			Name:        input.Name,
+			Type:        types.String(inputType),
+			Description: types.String(inputDescription),
+			Default:     input.Default,
+			Position: tfconf.Position{
+				Filename: input.Pos.Filename,
+				Line:     input.Pos.Line,
+			},
+		}
+
+		inputs = append(inputs, i)
+		if i.HasDefault() {
+			optional = append(optional, i)
+		} else {
+			required = append(required, i)
+		}
+	}
+	return inputs, required, optional
+}
+
+func loadOutputs(tfmodule *tfconfig.Module, options *Options) []*tfconf.Output {
+	outputs := make([]*tfconf.Output, 0, len(tfmodule.Outputs))
+	for _, o := range tfmodule.Outputs {
+		description := o.Description
+		if description == "" {
+			description = loadComments(o.Pos.Filename, o.Pos.Line-1)
+		}
+		output := &tfconf.Output{
+			Name:        o.Name,
+			Description: types.String(description),
+			Position: tfconf.Position{
+				Filename: o.Pos.Filename,
+				Line:     o.Pos.Line,
+			},
+		}
+		if options.OutputValues {
+			terraformOutputs, err := loadOutputValues(options)
+			if err != nil {
+				log.Fatal(err)
+			}
+			if terraformOutputs[output.Name].Sensitive {
+				output.Value = "<sensitive>"
+			} else {
+				output.Value = terraformOutputs[output.Name].Value
+			}
+		}
+		outputs = append(outputs, output)
+	}
+	return outputs
+}
+
+func loadOutputValues(options *Options) (map[string]*TerraformOutput, error) {
+	var out []byte
+	var err error
+	if options.OutputValuesPath == "" {
+		cmd := exec.Command("terraform", "output", "-json")
+		cmd.Dir = options.Path
+		if out, err = cmd.Output(); err != nil {
+			return nil, fmt.Errorf("caught error while reading the terraform outputs: %v", err)
+		}
+	} else {
+		if out, err = ioutil.ReadFile(options.OutputValuesPath); err != nil {
+			return nil, fmt.Errorf("caught error while reading the terraform outputs file at %s: %v", options.OutputValuesPath, err)
+		}
+	}
+	var terraformOutputs map[string]*TerraformOutput
+	err = json.Unmarshal(out, &terraformOutputs)
+	if err != nil {
+		return nil, err
+	}
+	return terraformOutputs, err
+}
+
+func loadProviders(tfmodule *tfconfig.Module) []*tfconf.Provider {
+	resources := []map[string]*tfconfig.Resource{tfmodule.ManagedResources, tfmodule.DataResources}
+	discovered := make(map[string]*tfconf.Provider)
+	for _, resource := range resources {
+		for _, r := range resource {
+			var version = ""
+			if rv, ok := tfmodule.RequiredProviders[r.Provider.Name]; ok && len(rv.VersionConstraints) > 0 {
+				version = strings.Join(rv.VersionConstraints, " ")
+			}
+			key := fmt.Sprintf("%s.%s", r.Provider.Name, r.Provider.Alias)
+			discovered[key] = &tfconf.Provider{
+				Name:    r.Provider.Name,
+				Alias:   types.String(r.Provider.Alias),
+				Version: types.String(version),
+				Position: tfconf.Position{
+					Filename: r.Pos.Filename,
+					Line:     r.Pos.Line,
+				},
+			}
+		}
+	}
+	providers := make([]*tfconf.Provider, 0, len(discovered))
+	for _, provider := range discovered {
+		providers = append(providers, provider)
+	}
+	return providers
+}
+
+func loadComments(filename string, lineNum int) string {
+	lines := reader.Lines{
+		FileName: filename,
+		LineNum:  lineNum,
+		Condition: func(line string) bool {
+			return strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//")
+		},
+		Parser: func(line string) (string, bool) {
+			line = strings.TrimSpace(line)
+			line = strings.TrimPrefix(line, "#")
+			line = strings.TrimPrefix(line, "//")
+			line = strings.TrimSpace(line)
+			return line, true
+		},
+	}
+	comment, err := lines.Extract()
+	if err != nil {
+		return "" // absorb the error, we don't need to bubble it up or break the execution
+	}
+	return strings.Join(comment, " ")
+}
+
+func sortItems(tfmodule *tfconf.Module, sortby *SortBy) {
+	if sortby.Name {
+		sort.Sort(providersSortedByName(tfmodule.Providers))
+	} else {
+		sort.Sort(providersSortedByPosition(tfmodule.Providers))
+	}
+
+	if sortby.Name {
+		if sortby.Required {
+			sort.Sort(inputsSortedByRequired(tfmodule.Inputs))
+			sort.Sort(inputsSortedByRequired(tfmodule.RequiredInputs))
+			sort.Sort(inputsSortedByRequired(tfmodule.OptionalInputs))
+		} else {
+			sort.Sort(inputsSortedByName(tfmodule.Inputs))
+			sort.Sort(inputsSortedByName(tfmodule.RequiredInputs))
+			sort.Sort(inputsSortedByName(tfmodule.OptionalInputs))
+		}
+	} else {
+		sort.Sort(inputsSortedByPosition(tfmodule.Inputs))
+		sort.Sort(inputsSortedByPosition(tfmodule.RequiredInputs))
+		sort.Sort(inputsSortedByPosition(tfmodule.OptionalInputs))
+	}
+
+	if sortby.Name {
+		sort.Sort(outputsSortedByName(tfmodule.Outputs))
+	} else {
+		sort.Sort(outputsSortedByPosition(tfmodule.Outputs))
+	}
+}
diff --git a/internal/module/options.go b/internal/module/options.go
new file mode 100644
index 0000000..2412175
--- /dev/null
+++ b/internal/module/options.go
@@ -0,0 +1,40 @@
+package module
+
+import (
+	"log"
+
+	"github.com/imdario/mergo"
+)
+
+// SortBy contains different sort criteria corresponding
+// to available flags (e.g. name, required, etc)
+type SortBy struct {
+	Name     bool
+	Required bool
+}
+
+// Options contains required options to load a Module from path
+type Options struct {
+	Path             string
+	SortBy           *SortBy
+	OutputValues     bool
+	OutputValuesPath string
+}
+
+// NewOptions returns new instance of Options
+func NewOptions() *Options {
+	return &Options{
+		Path:             "",
+		SortBy:           &SortBy{Name: false, Required: false},
+		OutputValues:     false,
+		OutputValuesPath: "",
+	}
+}
+
+// With override options with existing Options
+func (o *Options) With(override *Options) *Options {
+	if err := mergo.Merge(o, override); err != nil {
+		log.Fatal(err)
+	}
+	return o
+}
diff --git a/internal/module/output.go b/internal/module/output.go
new file mode 100644
index 0000000..845297e
--- /dev/null
+++ b/internal/module/output.go
@@ -0,0 +1,28 @@
+package module
+
+import (
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+)
+
+// TerraformOutput is used for unmarshalling `terraform outputs --json` into
+type TerraformOutput struct {
+	Sensitive bool        `json:"sensitive"`
+	Type      interface{} `json:"type"`
+	Value     interface{} `json:"value"`
+}
+
+type outputsSortedByName []*tfconf.Output
+
+func (a outputsSortedByName) Len() int      { return len(a) }
+func (a outputsSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a outputsSortedByName) Less(i, j int) bool {
+	return a[i].Name < a[j].Name
+}
+
+type outputsSortedByPosition []*tfconf.Output
+
+func (a outputsSortedByPosition) Len() int      { return len(a) }
+func (a outputsSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a outputsSortedByPosition) Less(i, j int) bool {
+	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
+}
diff --git a/internal/module/provider.go b/internal/module/provider.go
new file mode 100644
index 0000000..440806d
--- /dev/null
+++ b/internal/module/provider.go
@@ -0,0 +1,21 @@
+package module
+
+import (
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+)
+
+type providersSortedByName []*tfconf.Provider
+
+func (a providersSortedByName) Len() int      { return len(a) }
+func (a providersSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a providersSortedByName) Less(i, j int) bool {
+	return a[i].Name < a[j].Name || (a[i].Name == a[j].Name && a[i].Alias < a[j].Alias)
+}
+
+type providersSortedByPosition []*tfconf.Provider
+
+func (a providersSortedByPosition) Len() int      { return len(a) }
+func (a providersSortedByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a providersSortedByPosition) Less(i, j int) bool {
+	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
+}
diff --git a/internal/pkg/print/print.go b/internal/pkg/print/print.go
deleted file mode 100644
index 91d3830..0000000
--- a/internal/pkg/print/print.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package print
diff --git a/internal/pkg/tfconf/comment.go b/internal/pkg/tfconf/comment.go
deleted file mode 100644
index f607ce7..0000000
--- a/internal/pkg/tfconf/comment.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package tfconf
-
-import (
-	"strings"
-
-	"github.com/segmentio/terraform-docs/internal/pkg/reader"
-)
-
-func readComment(filename string, lineNum int) string {
-	lines := reader.Lines{
-		FileName: filename,
-		LineNum:  lineNum,
-		Condition: func(line string) bool {
-			return strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//")
-		},
-		Parser: func(line string) (string, bool) {
-			line = strings.TrimSpace(line)
-			line = strings.TrimPrefix(line, "#")
-			line = strings.TrimPrefix(line, "//")
-			line = strings.TrimSpace(line)
-			return line, true
-		},
-	}
-	comment, err := lines.Extract()
-	if err != nil {
-		return "" // absorb the error, we don't need to bubble it up or break the execution
-	}
-	return strings.Join(comment, " ")
-}
diff --git a/internal/pkg/tfconf/header.go b/internal/pkg/tfconf/header.go
deleted file mode 100644
index 3b1b0c3..0000000
--- a/internal/pkg/tfconf/header.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package tfconf
-
-import (
-	"io/ioutil"
-	"path/filepath"
-	"strings"
-
-	"github.com/segmentio/terraform-docs/internal/pkg/reader"
-)
-
-func readHeader(path string) string {
-	filename := filepath.Join(path, "main.tf")
-	_, err := ioutil.ReadFile(filename)
-	if err != nil {
-		return "" // absorb the error, we don't need to bubble it up or break the execution
-	}
-	lines := reader.Lines{
-		FileName: filename,
-		LineNum:  -1,
-		Condition: func(line string) bool {
-			line = strings.TrimSpace(line)
-			return strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*") || strings.HasPrefix(line, "*/")
-		},
-		Parser: func(line string) (string, bool) {
-			line = strings.TrimSpace(line)
-			if strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*/") {
-				return "", false
-			}
-			if line == "*" {
-				return "", true
-			}
-			line = strings.TrimPrefix(line, "* ")
-			return line, true
-		},
-	}
-	header, err := lines.Extract()
-	if err != nil {
-		return "" // absorb the error, we don't need to bubble it up or break the execution
-	}
-	return strings.Join(header, "\n")
-}
diff --git a/internal/pkg/tfconf/input.go b/internal/pkg/tfconf/input.go
deleted file mode 100644
index b901c6a..0000000
--- a/internal/pkg/tfconf/input.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package tfconf
-
-import (
-	"encoding/json"
-)
-
-// Input represents a Terraform input.
-type Input struct {
-	Name        string      `json:"name" yaml:"name"`
-	Type        String      `json:"type" yaml:"type"`
-	Description String      `json:"description" yaml:"description"`
-	Default     interface{} `json:"default" yaml:"default"`
-	Position    Position    `json:"-" yaml:"-"`
-}
-
-// Value returns JSON representation of the 'Default' value, which is an 'interface'.
-// If 'Default' is a primitive type, the primitive value of 'Default' will be returned
-// and not the JSON formatted of it.
-func (i *Input) Value() string {
-	marshaled, err := json.MarshalIndent(i.Default, "", "  ")
-	if err != nil {
-		panic(err)
-	}
-	if value := string(marshaled); value != "null" {
-		return value
-	}
-	return ""
-}
-
-// HasDefault indicates if a Terraform variable has a default value set.
-func (i *Input) HasDefault() bool {
-	return i.Default != nil
-}
-
-type inputsSortedByName []*Input
-
-func (a inputsSortedByName) Len() int {
-	return len(a)
-}
-
-func (a inputsSortedByName) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a inputsSortedByName) Less(i, j int) bool {
-	return a[i].Name < a[j].Name
-}
-
-type inputsSortedByRequired []*Input
-
-func (a inputsSortedByRequired) Len() int {
-	return len(a)
-}
-
-func (a inputsSortedByRequired) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a inputsSortedByRequired) Less(i, j int) bool {
-	switch {
-	// i required, j not: i gets priority
-	case !a[i].HasDefault() && a[j].HasDefault():
-		return true
-	// j required, i not: i does not get priority
-	case a[i].HasDefault() && !a[j].HasDefault():
-		return false
-	// Otherwise, sort by name
-	default:
-		return a[i].Name < a[j].Name
-	}
-}
-
-type inputsSortedByPosition []*Input
-
-func (a inputsSortedByPosition) Len() int {
-	return len(a)
-}
-
-func (a inputsSortedByPosition) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a inputsSortedByPosition) Less(i, j int) bool {
-	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
-}
diff --git a/internal/pkg/tfconf/module.go b/internal/pkg/tfconf/module.go
deleted file mode 100644
index 7d9665a..0000000
--- a/internal/pkg/tfconf/module.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package tfconf
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"log"
-	"os/exec"
-	"sort"
-	"strings"
-
-	"github.com/hashicorp/terraform-config-inspect/tfconfig"
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-)
-
-// Module represents a Terraform mod. It consists of
-// - Header    ('header' json key):    Module header found in shape of multi line comments at the beginning of 'main.tf'
-// - Inputs    ('inputs' json key):    List of input 'variables' extracted from the Terraform module .tf files
-// - Outputs   ('outputs' json key):   List of 'outputs' extracted from Terraform module .tf files
-// - Providers ('providers' json key): List of 'providers' extracted from resources used in Terraform module
-type Module struct {
-	Header         string      `json:"header" yaml:"header"`
-	Inputs         []*Input    `json:"inputs" yaml:"inputs"`
-	Outputs        []*Output   `json:"outputs" yaml:"outputs"`
-	Providers      []*Provider `json:"providers" yaml:"providers"`
-	RequiredInputs []*Input    `json:"-" yaml:"-"`
-	OptionalInputs []*Input    `json:"-" yaml:"-"`
-}
-
-// HasInputs indicates if the document has inputs.
-func (m *Module) HasInputs() bool {
-	return len(m.Inputs) > 0
-}
-
-// HasOutputs indicates if the document has outputs.
-func (m *Module) HasOutputs() bool {
-	return len(m.Outputs) > 0
-}
-
-// Sort sorts list of inputs and outputs based on provided flags (name, required, etc)
-func (m *Module) Sort(settings *print.Settings) {
-	if settings.SortByName {
-		sort.Sort(providersSortedByName(m.Providers))
-	} else {
-		sort.Sort(providersSortedByPosition(m.Providers))
-	}
-
-	if settings.SortByName {
-		if settings.SortByRequired {
-			sort.Sort(inputsSortedByRequired(m.Inputs))
-			sort.Sort(inputsSortedByRequired(m.RequiredInputs))
-			sort.Sort(inputsSortedByRequired(m.OptionalInputs))
-		} else {
-			sort.Sort(inputsSortedByName(m.Inputs))
-			sort.Sort(inputsSortedByName(m.RequiredInputs))
-			sort.Sort(inputsSortedByName(m.OptionalInputs))
-		}
-	} else {
-		sort.Sort(inputsSortedByPosition(m.Inputs))
-		sort.Sort(inputsSortedByPosition(m.RequiredInputs))
-		sort.Sort(inputsSortedByPosition(m.OptionalInputs))
-	}
-
-	if settings.SortByName {
-		sort.Sort(outputsSortedByName(m.Outputs))
-	} else {
-		sort.Sort(outputsSortedByPosition(m.Outputs))
-	}
-}
-
-// CreateModule returns new instance of Module with all the inputs and
-// outputs discovered from provided 'path' containing Terraform config
-func CreateModule(options *Options) (*Module, error) {
-	mod := loadModule(options.Path)
-
-	header := readHeader(options.Path)
-
-	var inputs = make([]*Input, 0, len(mod.Variables))
-	var requiredInputs = make([]*Input, 0, len(mod.Variables))
-	var optionalInputs = make([]*Input, 0, len(mod.Variables))
-
-	for _, input := range mod.Variables {
-		inputType := input.Type
-		if input.Type == "" {
-			inputType = "any"
-			if input.Default != nil {
-				switch xType := fmt.Sprintf("%T", input.Default); xType {
-				case "string":
-					inputType = "string"
-				case "int", "int8", "int16", "int32", "int64", "float32", "float64":
-					inputType = "number"
-				case "bool":
-					inputType = "bool"
-				case "[]interface {}":
-					inputType = "list"
-				case "map[string]interface {}":
-					inputType = "map"
-				}
-			}
-		}
-
-		inputDescription := input.Description
-		if inputDescription == "" {
-			inputDescription = readComment(input.Pos.Filename, input.Pos.Line-1)
-		}
-
-		i := &Input{
-			Name:        input.Name,
-			Type:        String(inputType),
-			Description: String(inputDescription),
-			Default:     input.Default,
-			Position: Position{
-				Filename: input.Pos.Filename,
-				Line:     input.Pos.Line,
-			},
-		}
-
-		inputs = append(inputs, i)
-		if i.HasDefault() {
-			optionalInputs = append(optionalInputs, i)
-		} else {
-			requiredInputs = append(requiredInputs, i)
-		}
-	}
-
-	// output module
-	var outputs = make([]*Output, 0, len(mod.Outputs))
-	for _, o := range mod.Outputs {
-		outputDescription := o.Description
-		if outputDescription == "" {
-			outputDescription = readComment(o.Pos.Filename, o.Pos.Line-1)
-		}
-		output := &Output{
-			Name:        o.Name,
-			Description: String(outputDescription),
-			Position: Position{
-				Filename: o.Pos.Filename,
-				Line:     o.Pos.Line,
-			},
-		}
-		if options.OutputValues {
-			terraformOutputs, err := loadOutputValues(options)
-			if err != nil {
-				log.Fatal(err)
-			}
-
-			if terraformOutputs[output.Name].Sensitive {
-				output.Value = "<sensitive>"
-			} else {
-				output.Value = terraformOutputs[output.Name].Value
-			}
-		}
-		outputs = append(outputs, output)
-	}
-
-	var providerSet = loadProviders(mod.RequiredProviders, mod.ManagedResources, mod.DataResources)
-	var providers = make([]*Provider, 0, len(providerSet))
-	for _, provider := range providerSet {
-		providers = append(providers, provider)
-	}
-
-	module := &Module{
-		Header:         header,
-		Inputs:         inputs,
-		Outputs:        outputs,
-		Providers:      providers,
-		RequiredInputs: requiredInputs,
-		OptionalInputs: optionalInputs,
-	}
-	return module, nil
-}
-
-func loadModule(path string) *tfconfig.Module {
-	module, diag := tfconfig.LoadModule(path)
-	if diag != nil && diag.HasErrors() {
-		log.Fatal(diag)
-	}
-	return module
-}
-
-func loadProviders(requiredProviders map[string]*tfconfig.ProviderRequirement, resources ...map[string]*tfconfig.Resource) map[string]*Provider {
-	var providers = make(map[string]*Provider)
-	for _, resource := range resources {
-		for _, r := range resource {
-			var version = ""
-			if requiredVersion, ok := requiredProviders[r.Provider.Name]; ok && len(requiredVersion.VersionConstraints) > 0 {
-				version = strings.Join(requiredVersion.VersionConstraints, " ")
-			}
-			key := fmt.Sprintf("%s.%s", r.Provider.Name, r.Provider.Alias)
-			providers[key] = &Provider{
-				Name:    r.Provider.Name,
-				Alias:   String(r.Provider.Alias),
-				Version: String(version),
-				Position: Position{
-					Filename: r.Pos.Filename,
-					Line:     r.Pos.Line,
-				},
-			}
-		}
-	}
-	return providers
-}
-
-func loadOutputValues(options *Options) (map[string]*TerraformOutput, error) {
-	var out []byte
-	var err error
-
-	if options.OutputValuesPath == "" {
-		cmd := exec.Command("terraform", "output", "-json")
-		cmd.Dir = options.Path
-		if out, err = cmd.Output(); err != nil {
-			return nil, fmt.Errorf("caught error while reading the terraform outputs: %v", err)
-		}
-	} else {
-		if out, err = ioutil.ReadFile(options.OutputValuesPath); err != nil {
-			return nil, fmt.Errorf("caught error while reading the terraform outputs file at %s: %v", options.OutputValuesPath, err)
-		}
-	}
-
-	var terraformOutputs map[string]*TerraformOutput
-	err = json.Unmarshal(out, &terraformOutputs)
-	if err != nil {
-		return nil, err
-	}
-	return terraformOutputs, err
-}
diff --git a/internal/pkg/tfconf/options.go b/internal/pkg/tfconf/options.go
deleted file mode 100644
index 61ce408..0000000
--- a/internal/pkg/tfconf/options.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package tfconf
-
-// Options contains required options to load a Module from path
-type Options struct {
-	Path             string
-	OutputValues     bool
-	OutputValuesPath string
-}
diff --git a/internal/pkg/tfconf/output.go b/internal/pkg/tfconf/output.go
deleted file mode 100644
index 0f8abca..0000000
--- a/internal/pkg/tfconf/output.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package tfconf
-
-// Output represents a Terraform output.
-type Output struct {
-	Name        string      `json:"name" yaml:"name"`
-	Description String      `json:"description" yaml:"description"`
-	Value       interface{} `json:"value,omitempty" yaml:"value,omitempty"`
-	Position    Position    `json:"-" yaml:"-"`
-}
-
-type outputsSortedByName []*Output
-
-func (a outputsSortedByName) Len() int {
-	return len(a)
-}
-
-func (a outputsSortedByName) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a outputsSortedByName) Less(i, j int) bool {
-	return a[i].Name < a[j].Name
-}
-
-type outputsSortedByPosition []*Output
-
-func (a outputsSortedByPosition) Len() int {
-	return len(a)
-}
-
-func (a outputsSortedByPosition) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a outputsSortedByPosition) Less(i, j int) bool {
-	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
-}
-
-// TerraformOutput is used for unmarshalling `terraform outputs --json` into
-type TerraformOutput struct {
-	Sensitive bool        `json:"sensitive"`
-	Type      interface{} `json:"type"`
-	Value     interface{} `json:"value"`
-}
diff --git a/internal/pkg/tfconf/provider.go b/internal/pkg/tfconf/provider.go
deleted file mode 100644
index 3b8b43b..0000000
--- a/internal/pkg/tfconf/provider.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package tfconf
-
-import (
-	"fmt"
-)
-
-// Provider represents a Terraform output.
-type Provider struct {
-	Name     string   `json:"name" yaml:"name"`
-	Alias    String   `json:"alias" yaml:"alias"`
-	Version  String   `json:"version" yaml:"version"`
-	Position Position `json:"-" yaml:"-"`
-}
-
-// FullName returns full name of the provider, with alias if available
-func (p *Provider) FullName() string {
-	if p.Alias != "" {
-		return fmt.Sprintf("%s.%s", p.Name, p.Alias)
-	}
-	return p.Name
-}
-
-type providersSortedByName []*Provider
-
-func (a providersSortedByName) Len() int {
-	return len(a)
-}
-
-func (a providersSortedByName) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a providersSortedByName) Less(i, j int) bool {
-	return a[i].Name < a[j].Name || (a[i].Name == a[j].Name && a[i].Alias < a[j].Alias)
-}
-
-type providersSortedByPosition []*Provider
-
-func (a providersSortedByPosition) Len() int {
-	return len(a)
-}
-
-func (a providersSortedByPosition) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a providersSortedByPosition) Less(i, j int) bool {
-	return a[i].Position.Filename < a[j].Position.Filename || a[i].Position.Line < a[j].Position.Line
-}
diff --git a/internal/pkg/tfconf/string.go b/internal/types/string.go
similarity index 98%
rename from internal/pkg/tfconf/string.go
rename to internal/types/string.go
index 052c32b..2ce3045 100644
--- a/internal/pkg/tfconf/string.go
+++ b/internal/types/string.go
@@ -1,4 +1,4 @@
-package tfconf
+package types
 
 import (
 	"bytes"
diff --git a/internal/pkg/version/version.go b/internal/version/version.go
similarity index 99%
rename from internal/pkg/version/version.go
rename to internal/version/version.go
index edde678..6148fb5 100644
--- a/internal/pkg/version/version.go
+++ b/internal/version/version.go
@@ -22,11 +22,9 @@ func init() {
 	if version == "" {
 		version = dev
 	}
-
 	if commitHash == "" {
 		commitHash = dev
 	}
-
 	if buildDate == "" {
 		buildDate = time.Now().Format(time.RFC3339)
 	}
@@ -37,8 +35,6 @@ func Version() string {
 	if !strings.HasSuffix(version, commitHash) {
 		version += " " + commitHash
 	}
-
 	osArch := runtime.GOOS + "/" + runtime.GOARCH
-
 	return fmt.Sprintf("%s %s BuildDate: %s", version, osArch, buildDate)
 }
diff --git a/pkg/print/doc.go b/pkg/print/doc.go
new file mode 100644
index 0000000..47e53ba
--- /dev/null
+++ b/pkg/print/doc.go
@@ -0,0 +1,2 @@
+// Package print provides a specific definition of a printer Format
+package print
diff --git a/pkg/print/print.go b/pkg/print/print.go
new file mode 100644
index 0000000..2fc8c4f
--- /dev/null
+++ b/pkg/print/print.go
@@ -0,0 +1,10 @@
+package print
+
+import (
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
+)
+
+// Format represents a printer format (e.g. json, table, yaml, ...)
+type Format interface {
+	Print(*tfconf.Module, *Settings) (string, error)
+}
diff --git a/internal/pkg/print/settings.go b/pkg/print/settings.go
similarity index 100%
rename from internal/pkg/print/settings.go
rename to pkg/print/settings.go
diff --git a/pkg/tfconf/doc.go b/pkg/tfconf/doc.go
new file mode 100644
index 0000000..089e108
--- /dev/null
+++ b/pkg/tfconf/doc.go
@@ -0,0 +1,2 @@
+// Package tfconf is the representation of a Terraform Module
+package tfconf
diff --git a/pkg/tfconf/input.go b/pkg/tfconf/input.go
new file mode 100644
index 0000000..c190c32
--- /dev/null
+++ b/pkg/tfconf/input.go
@@ -0,0 +1,35 @@
+package tfconf
+
+import (
+	"encoding/json"
+
+	"github.com/segmentio/terraform-docs/internal/types"
+)
+
+// Input represents a Terraform input.
+type Input struct {
+	Name        string       `json:"name" yaml:"name"`
+	Type        types.String `json:"type" yaml:"type"`
+	Description types.String `json:"description" yaml:"description"`
+	Default     interface{}  `json:"default" yaml:"default"`
+	Position    Position     `json:"-" yaml:"-"`
+}
+
+// Value returns JSON representation of the 'Default' value, which is an 'interface'.
+// If 'Default' is a primitive type, the primitive value of 'Default' will be returned
+// and not the JSON formatted of it.
+func (i *Input) Value() string {
+	marshaled, err := json.MarshalIndent(i.Default, "", "  ")
+	if err != nil {
+		panic(err)
+	}
+	if value := string(marshaled); value != "null" {
+		return value
+	}
+	return ""
+}
+
+// HasDefault indicates if a Terraform variable has a default value set.
+func (i *Input) HasDefault() bool {
+	return i.Default != nil
+}
diff --git a/pkg/tfconf/module.go b/pkg/tfconf/module.go
new file mode 100644
index 0000000..afc3e53
--- /dev/null
+++ b/pkg/tfconf/module.go
@@ -0,0 +1,36 @@
+package tfconf
+
+// Module represents a Terraform module. It consists of
+// - Header    ('header' json key):    Module header found in shape of multi line comments at the beginning of 'main.tf'
+// - Inputs    ('inputs' json key):    List of input 'variables' extracted from the Terraform module .tf files
+// - Outputs   ('outputs' json key):   List of 'outputs' extracted from Terraform module .tf files
+// - Providers ('providers' json key): List of 'providers' extracted from resources used in Terraform module
+type Module struct {
+	Header    string      `json:"header" yaml:"header"`
+	Inputs    []*Input    `json:"inputs" yaml:"inputs"`
+	Outputs   []*Output   `json:"outputs" yaml:"outputs"`
+	Providers []*Provider `json:"providers" yaml:"providers"`
+
+	RequiredInputs []*Input `json:"-" yaml:"-"`
+	OptionalInputs []*Input `json:"-" yaml:"-"`
+}
+
+// HasHeader indicates if the module has header.
+func (m *Module) HasHeader() bool {
+	return len(m.Header) > 0
+}
+
+// HasInputs indicates if the module has inputs.
+func (m *Module) HasInputs() bool {
+	return len(m.Inputs) > 0
+}
+
+// HasOutputs indicates if the module has outputs.
+func (m *Module) HasOutputs() bool {
+	return len(m.Outputs) > 0
+}
+
+// HasProviders indicates if the module has providers.
+func (m *Module) HasProviders() bool {
+	return len(m.Providers) > 0
+}
diff --git a/pkg/tfconf/output.go b/pkg/tfconf/output.go
new file mode 100644
index 0000000..1bb53de
--- /dev/null
+++ b/pkg/tfconf/output.go
@@ -0,0 +1,13 @@
+package tfconf
+
+import (
+	"github.com/segmentio/terraform-docs/internal/types"
+)
+
+// Output represents a Terraform output.
+type Output struct {
+	Name        string       `json:"name" yaml:"name"`
+	Description types.String `json:"description" yaml:"description"`
+	Value       interface{}  `json:"value,omitempty" yaml:"value,omitempty"`
+	Position    Position     `json:"-" yaml:"-"`
+}
diff --git a/internal/pkg/tfconf/position.go b/pkg/tfconf/position.go
similarity index 100%
rename from internal/pkg/tfconf/position.go
rename to pkg/tfconf/position.go
diff --git a/pkg/tfconf/provider.go b/pkg/tfconf/provider.go
new file mode 100644
index 0000000..e14e521
--- /dev/null
+++ b/pkg/tfconf/provider.go
@@ -0,0 +1,23 @@
+package tfconf
+
+import (
+	"fmt"
+
+	"github.com/segmentio/terraform-docs/internal/types"
+)
+
+// Provider represents a Terraform output.
+type Provider struct {
+	Name     string       `json:"name" yaml:"name"`
+	Alias    types.String `json:"alias" yaml:"alias"`
+	Version  types.String `json:"version" yaml:"version"`
+	Position Position     `json:"-" yaml:"-"`
+}
+
+// FullName returns full name of the provider, with alias if available
+func (p *Provider) FullName() string {
+	if p.Alias != "" {
+		return fmt.Sprintf("%s.%s", p.Name, p.Alias)
+	}
+	return p.Name
+}
diff --git a/pkg/tmpl/doc.go b/pkg/tmpl/doc.go
new file mode 100644
index 0000000..cbe4e15
--- /dev/null
+++ b/pkg/tmpl/doc.go
@@ -0,0 +1,2 @@
+// Package tmpl provides templating functionality
+package tmpl
diff --git a/internal/pkg/print/markdown/markdown.go b/pkg/tmpl/sanitizer.go
similarity index 60%
rename from internal/pkg/print/markdown/markdown.go
rename to pkg/tmpl/sanitizer.go
index e02a2f7..6652543 100644
--- a/internal/pkg/print/markdown/markdown.go
+++ b/pkg/tmpl/sanitizer.go
@@ -1,4 +1,4 @@
-package markdown
+package tmpl
 
 import (
 	"bytes"
@@ -6,37 +6,12 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"mvdan.cc/xurls/v2"
 )
 
-// Sanitize cleans a Markdown document to soothe linters.
-func Sanitize(markdown string) string {
-	result := markdown
-
-	// Preserve double spaces at the end of the line
-	result = regexp.MustCompile(` {2}(\r?\n)`).ReplaceAllString(result, "‡‡$1")
-
-	// Remove trailing spaces from the end of lines
-	result = regexp.MustCompile(` +(\r?\n)`).ReplaceAllString(result, "$1")
-	result = regexp.MustCompile(` +$`).ReplaceAllLiteralString(result, "")
-
-	// Preserve double spaces at the end of the line
-	result = regexp.MustCompile(`‡‡(\r?\n)`).ReplaceAllString(result, "  $1")
-
-	// Remove blank line with only double spaces in it
-	result = regexp.MustCompile(`(\r?\n)  (\r?\n)`).ReplaceAllString(result, "$1")
-
-	// Remove multiple consecutive blank lines
-	result = regexp.MustCompile(`(\r?\n){3,}`).ReplaceAllString(result, "$1$1")
-	result = regexp.MustCompile(`(\r?\n){2,}$`).ReplaceAllString(result, "$1")
-
-	return result
-}
-
-// SanitizeName escapes underscore character which have special meaning in Markdown.
-func SanitizeName(name string, settings *print.Settings) string {
+// sanitizeName escapes underscore character which have special meaning in Markdown.
+func sanitizeName(name string, settings *print.Settings) string {
 	if settings.EscapeCharacters {
 		// Escape underscore
 		name = strings.Replace(name, "_", "\\_", -1)
@@ -44,9 +19,9 @@ func SanitizeName(name string, settings *print.Settings) string {
 	return name
 }
 
-// SanitizeItemForDocument converts passed 'string' to suitable Markdown representation
+// sanitizeItemForDocument converts passed 'string' to suitable Markdown representation
 // for a document. (including line-break, illegal characters, code blocks etc)
-func SanitizeItemForDocument(s string, settings *print.Settings) string {
+func sanitizeItemForDocument(s string, settings *print.Settings) string {
 	if s == "" {
 		return "n/a"
 	}
@@ -54,9 +29,9 @@ func SanitizeItemForDocument(s string, settings *print.Settings) string {
 		s,
 		"```",
 		func(segment string) string {
-			segment = ConvertMultiLineText(segment, false)
-			segment = EscapeIllegalCharacters(segment, settings)
-			segment = NormalizeURLs(segment, settings)
+			segment = convertMultiLineText(segment, false)
+			segment = escapeIllegalCharacters(segment, settings)
+			segment = normalizeURLs(segment, settings)
 			return segment
 		},
 		func(segment string) string {
@@ -71,9 +46,9 @@ func SanitizeItemForDocument(s string, settings *print.Settings) string {
 	return strings.Replace(result, "<br>", "\n", -1)
 }
 
-// SanitizeItemForTable converts passed 'string' to suitable Markdown representation
+// sanitizeItemForTable converts passed 'string' to suitable Markdown representation
 // for a table. (including line-break, illegal characters, code blocks etc)
-func SanitizeItemForTable(s string, settings *print.Settings) string {
+func sanitizeItemForTable(s string, settings *print.Settings) string {
 	if s == "" {
 		return "n/a"
 	}
@@ -81,9 +56,9 @@ func SanitizeItemForTable(s string, settings *print.Settings) string {
 		s,
 		"```",
 		func(segment string) string {
-			segment = ConvertMultiLineText(segment, true)
-			segment = EscapeIllegalCharacters(segment, settings)
-			segment = NormalizeURLs(segment, settings)
+			segment = convertMultiLineText(segment, true)
+			segment = escapeIllegalCharacters(segment, settings)
+			segment = normalizeURLs(segment, settings)
 			return segment
 		},
 		func(segment string) string {
@@ -97,8 +72,8 @@ func SanitizeItemForTable(s string, settings *print.Settings) string {
 	return result
 }
 
-// ConvertMultiLineText converts a multi-line text into a suitable Markdown representation.
-func ConvertMultiLineText(s string, isTable bool) string {
+// convertMultiLineText converts a multi-line text into a suitable Markdown representation.
+func convertMultiLineText(s string, isTable bool) string {
 	if isTable {
 		s = strings.TrimSpace(s)
 	}
@@ -125,8 +100,8 @@ func ConvertMultiLineText(s string, isTable bool) string {
 	return s
 }
 
-// EscapeIllegalCharacters escapes characters which have special meaning in Markdown into their corresponding literal.
-func EscapeIllegalCharacters(s string, settings *print.Settings) string {
+// escapeIllegalCharacters escapes characters which have special meaning in Markdown into their corresponding literal.
+func escapeIllegalCharacters(s string, settings *print.Settings) string {
 	// Escape pipe
 	if settings.EscapePipe {
 		s = strings.Replace(s, "|", "\\|", -1)
@@ -160,10 +135,10 @@ func EscapeIllegalCharacters(s string, settings *print.Settings) string {
 	return s
 }
 
-// NormalizeURLs runs after escape function and normalizes URL back
+// normalizeURLs runs after escape function and normalizes URL back
 // to the original state. For example any underscore in the URL which
 // got escaped by 'EscapeIllegalCharacters' will be reverted back.
-func NormalizeURLs(s string, settings *print.Settings) string {
+func normalizeURLs(s string, settings *print.Settings) string {
 	if settings.EscapeCharacters {
 		if urls := xurls.Strict().FindAllString(s, -1); len(urls) > 0 {
 			for _, url := range urls {
@@ -175,11 +150,11 @@ func NormalizeURLs(s string, settings *print.Settings) string {
 	return s
 }
 
-// GenerateIndentation generates indentation of Markdown headers
+// generateIndentation generates indentation of Markdown headers
 // with base level of provided 'settings.MarkdownIndent' plus any
 // extra level needed for subsection (e.g. 'Required Inputs' which
 // is a subsection of 'Inputs' section)
-func GenerateIndentation(extra int, settings *print.Settings) string {
+func generateIndentation(extra int, settings *print.Settings) string {
 	var base = settings.MarkdownIndent
 	if base < 1 || base > 5 {
 		base = 2
@@ -191,18 +166,6 @@ func GenerateIndentation(extra int, settings *print.Settings) string {
 	return indent
 }
 
-// PrintFencedCodeBlock prints codes in fences, it automatically detects if
-// the input 'code' contains '\n' it will use multi line fence, otherwise it
-// wraps the 'code' inside single-tick block.
-// If the fenced is multi-line it also appens an extra '\n` at the end and
-// returns true accordingly, otherwise returns false for non-carriage return.
-func PrintFencedCodeBlock(code string, language string) (string, bool) {
-	if strings.Contains(code, "\n") {
-		return fmt.Sprintf("\n\n```%s\n%s\n```\n", language, code), true
-	}
-	return fmt.Sprintf("`%s`", code), false
-}
-
 func processSegments(s string, prefix string, normalFn func(segment string) string, codeFn func(segment string) string) string {
 	// Isolate blocks of code. Dont escape anything inside them
 	nextIsInCodeBlock := strings.HasPrefix(s, prefix)
diff --git a/internal/pkg/tmpl/template.go b/pkg/tmpl/template.go
similarity index 85%
rename from internal/pkg/tmpl/template.go
rename to pkg/tmpl/template.go
index 0b21e70..751a12e 100644
--- a/internal/pkg/tmpl/template.go
+++ b/pkg/tmpl/template.go
@@ -6,9 +6,9 @@ import (
 	"strings"
 	"text/template"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/types"
+	"github.com/segmentio/terraform-docs/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
 )
 
 // Item represents a named templated which can reference
@@ -22,9 +22,10 @@ type Item struct {
 // to be rendered with provided settings with use of built-in and
 // custom functions
 type Template struct {
-	Items    []*Item
+	Items []*Item
+
 	settings *print.Settings
-	funcMap  map[string]interface{}
+	funcMap  template.FuncMap
 }
 
 // NewTemplate returns new instance of Template
@@ -39,7 +40,7 @@ func NewTemplate(items ...*Item) *Template {
 
 // CustomFunc adds new custom functions to the template
 // if functions with the same names didn't exist
-func (t *Template) CustomFunc(funcs map[string]interface{}) {
+func (t *Template) CustomFunc(funcs template.FuncMap) {
 	for name, fn := range funcs {
 		if t.funcMap[name] == nil {
 			t.funcMap[name] = fn
@@ -85,7 +86,7 @@ func (t *Template) Render(module *tfconf.Module) (string, error) {
 	return buffer.String(), nil
 }
 
-func builtinFuncs(settings *print.Settings) map[string]interface{} {
+func builtinFuncs(settings *print.Settings) template.FuncMap {
 	return template.FuncMap{
 		"default": func(d string, s string) string {
 			if s != "" {
@@ -108,7 +109,7 @@ func builtinFuncs(settings *print.Settings) map[string]interface{} {
 			}
 			return f
 		},
-		"tostring": func(s tfconf.String) string {
+		"tostring": func(s types.String) string {
 			return string(s)
 		},
 		"trim": func(t string, s string) string {
@@ -143,22 +144,22 @@ func builtinFuncs(settings *print.Settings) map[string]interface{} {
 		},
 
 		"indent": func(l int) string {
-			return markdown.GenerateIndentation(l, settings)
+			return generateIndentation(l, settings)
 		},
 		"name": func(n string) string {
-			return markdown.SanitizeName(n, settings)
+			return sanitizeName(n, settings)
 		},
 		"sanitizeHeader": func(s string) string {
 			settings.EscapePipe = false
-			s = markdown.SanitizeItemForDocument(s, settings)
+			s = sanitizeItemForDocument(s, settings)
 			settings.EscapePipe = true
 			return s
 		},
 		"sanitizeDoc": func(s string) string {
-			return markdown.SanitizeItemForDocument(s, settings)
+			return sanitizeItemForDocument(s, settings)
 		},
 		"sanitizeTbl": func(s string) string {
-			return markdown.SanitizeItemForTable(s, settings)
+			return sanitizeItemForTable(s, settings)
 		},
 		"sanitizeInterface": func(i interface{}) string {
 			var v string
diff --git a/scripts/docs/generate.go b/scripts/docs/generate.go
index cacdfc9..32667c1 100644
--- a/scripts/docs/generate.go
+++ b/scripts/docs/generate.go
@@ -11,13 +11,9 @@ import (
 	"time"
 
 	"github.com/segmentio/terraform-docs/cmd"
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/json"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown/document"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/markdown/table"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/pretty"
-	"github.com/segmentio/terraform-docs/internal/pkg/print/yaml"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/format"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/spf13/cobra"
 )
 
@@ -125,20 +121,18 @@ func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
 	return nil
 }
 
-type printer func(*tfconf.Module, *print.Settings) (string, error)
-
-func getPrinter(name string) printer {
+func getPrinter(name string, settings *print.Settings) print.Format {
 	switch strings.Replace(name, "terraform-docs ", "", -1) {
 	case "json":
-		return json.Print
+		return format.NewJSON(settings)
 	case "markdown document":
-		return document.Print
+		return format.NewDocument(settings)
 	case "markdown table":
-		return table.Print
+		return format.NewTable(settings)
 	case "pretty":
-		return pretty.Print
+		return format.NewPretty(settings)
 	case "yaml":
-		return yaml.Print
+		return format.NewYAML(settings)
 	}
 	return nil
 }
@@ -159,20 +153,22 @@ func printExample(buf *bytes.Buffer, name string) error {
 	buf.WriteString("```\n\n")
 	buf.WriteString("generates the following output:\n\n")
 
-	options := &tfconf.Options{
+	settings := print.NewSettings()
+	settings.ShowColor = false
+	options := &module.Options{
 		Path: "./examples",
+		SortBy: &module.SortBy{
+			Name:     settings.SortByName,
+			Required: settings.SortByRequired,
+		},
 	}
-	module, err := tfconf.CreateModule(options)
+	tfmodule, err := module.LoadWithOptions(options)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	// fmt.Println(name)
-	if fn := getPrinter(name); fn != nil {
-		settings := print.NewSettings()
-		settings.ShowColor = false
-
-		output, err := fn(module, settings)
+	if printer := getPrinter(name, settings); printer != nil {
+		output, err := printer.Print(tfmodule, settings)
 		if err != nil {
 			return err
 		}
diff --git a/scripts/release/release.sh b/scripts/release/release.sh
index 116aacd..401d29b 100755
--- a/scripts/release/release.sh
+++ b/scripts/release/release.sh
@@ -58,11 +58,11 @@ CLOSEST_VERSION=$(getClosestVersion)
 
 # Bump the released version in README and version.go
 sed -i -E 's|'${CLOSEST_VERSION}'|v'${RELEASE_VERSION}'|g' README.md
-sed -i -E 's|v'${RELEASE_VERSION}'-alpha|v'${RELEASE_VERSION}'|g' internal/pkg/version/version.go
+sed -i -E 's|v'${RELEASE_VERSION}'-alpha|v'${RELEASE_VERSION}'|g' internal/version/version.go
 
 # Commit changes
 printf "\033[36m==> %s\033[0m\n" "Commit changes for release version v${RELEASE_VERSION}"
-git add README.md internal/pkg/version/version.go
+git add README.md internal/version/version.go
 git commit -m "Release version v${RELEASE_VERSION}"
 
 printf "\033[36m==> %s\033[0m\n" "Push commits for v${RELEASE_VERSION}"
@@ -80,11 +80,11 @@ git push origin v${RELEASE_VERSION}
 
 # Bump the next version in version.go
 NEXT_VERSION=$(echo "${RELEASE_VERSION}" | sed 's/^v//' | awk -F'[ .]' '{print $1"."$2+1".0"}')
-sed -i -E 's|v'${RELEASE_VERSION}'|v'${NEXT_VERSION}'-alpha|g' internal/pkg/version/version.go
+sed -i -E 's|v'${RELEASE_VERSION}'|v'${NEXT_VERSION}'-alpha|g' internal/version/version.go
 
 # Commit changes
 printf "\033[36m==> %s\033[0m\n" "Bump version to v${NEXT_VERSION}-alpha"
-git add internal/pkg/version/version.go
+git add internal/version/version.go
 git commit -m "Bump version to v${NEXT_VERSION}-alpha"
 
 printf "\033[36m==> %s\033[0m\n" "Push commits for v${NEXT_VERSION}-alpha"


Test patch:
diff --git a/internal/pkg/print/markdown/document/document_test.go b/internal/format/document_test.go
similarity index 54%
rename from internal/pkg/print/markdown/document/document_test.go
rename to internal/format/document_test.go
index 95e0725..808df38 100644
--- a/internal/pkg/print/markdown/document/document_test.go
+++ b/internal/format/document_test.go
@@ -1,11 +1,11 @@
-package document
+package format
 
 import (
 	"testing"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/testutil"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/testutil"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -13,13 +13,15 @@ func TestDocument(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().Build()
 
-	expected, err := testutil.GetExpected("document")
+	expected, err := testutil.GetExpected("document", "document")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -31,13 +33,15 @@ func TestDocumentWithRequired(t *testing.T) {
 		ShowRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-WithRequired")
+	expected, err := testutil.GetExpected("document", "document-WithRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -49,13 +53,19 @@ func TestDocumentSortByName(t *testing.T) {
 		SortByName: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-SortByName")
+	expected, err := testutil.GetExpected("document", "document-SortByName")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -68,13 +78,20 @@ func TestDocumentSortByRequired(t *testing.T) {
 		SortByRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-SortByRequired")
+	expected, err := testutil.GetExpected("document", "document-SortByRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name:     true,
+			Required: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -89,13 +106,15 @@ func TestDocumentNoHeader(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-NoHeader")
+	expected, err := testutil.GetExpected("document", "document-NoHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -110,13 +129,15 @@ func TestDocumentNoProviders(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-NoProviders")
+	expected, err := testutil.GetExpected("document", "document-NoProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -131,13 +152,15 @@ func TestDocumentNoInputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-NoInputs")
+	expected, err := testutil.GetExpected("document", "document-NoInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -152,13 +175,15 @@ func TestDocumentNoOutputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-NoOutputs")
+	expected, err := testutil.GetExpected("document", "document-NoOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -173,13 +198,15 @@ func TestDocumentOnlyHeader(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-OnlyHeader")
+	expected, err := testutil.GetExpected("document", "document-OnlyHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -194,13 +221,15 @@ func TestDocumentOnlyProviders(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-OnlyProviders")
+	expected, err := testutil.GetExpected("document", "document-OnlyProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -215,13 +244,15 @@ func TestDocumentOnlyInputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-OnlyInputs")
+	expected, err := testutil.GetExpected("document", "document-OnlyInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -236,13 +267,15 @@ func TestDocumentOnlyOutputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-OnlyOutputs")
+	expected, err := testutil.GetExpected("document", "document-OnlyOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -254,13 +287,15 @@ func TestDocumentEscapeCharacters(t *testing.T) {
 		EscapeCharacters: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-EscapeCharacters")
+	expected, err := testutil.GetExpected("document", "document-EscapeCharacters")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -272,13 +307,15 @@ func TestDocumentIndentationBelowAllowed(t *testing.T) {
 		MarkdownIndent: 0,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-IndentationBelowAllowed")
+	expected, err := testutil.GetExpected("document", "document-IndentationBelowAllowed")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -290,13 +327,15 @@ func TestDocumentIndentationAboveAllowed(t *testing.T) {
 		MarkdownIndent: 10,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-IndentationAboveAllowed")
+	expected, err := testutil.GetExpected("document", "document-IndentationAboveAllowed")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -308,13 +347,15 @@ func TestDocumentIndentationOfFour(t *testing.T) {
 		MarkdownIndent: 4,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-IndentationOfFour")
+	expected, err := testutil.GetExpected("document", "document-IndentationOfFour")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -326,17 +367,18 @@ func TestDocumentOutputValues(t *testing.T) {
 		OutputValues: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("document-OutputValues")
+	expected, err := testutil.GetExpected("document", "document-OutputValues")
 	assert.Nil(err)
 
-	options := &tfconf.Options{
+	options := module.NewOptions().With(&module.Options{
 		OutputValues:     true,
 		OutputValuesPath: "output_values.json",
-	}
+	})
 	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewDocument(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
diff --git a/internal/pkg/print/json/json_test.go b/internal/format/json_test.go
similarity index 55%
rename from internal/pkg/print/json/json_test.go
rename to internal/format/json_test.go
index 82b5c4a..b324961 100644
--- a/internal/pkg/print/json/json_test.go
+++ b/internal/format/json_test.go
@@ -1,11 +1,11 @@
-package json
+package format
 
 import (
 	"testing"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/testutil"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/testutil"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -13,13 +13,15 @@ func TestJson(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().Build()
 
-	expected, err := testutil.GetExpected("json")
+	expected, err := testutil.GetExpected("json", "json")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -31,13 +33,19 @@ func TestJsonSortByName(t *testing.T) {
 		SortByName: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-SortByName")
+	expected, err := testutil.GetExpected("json", "json-SortByName")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -50,13 +58,20 @@ func TestJsonSortByRequired(t *testing.T) {
 		SortByRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-SortByRequired")
+	expected, err := testutil.GetExpected("json", "json-SortByRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name:     true,
+			Required: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -71,13 +86,15 @@ func TestJsonNoHeader(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-NoHeader")
+	expected, err := testutil.GetExpected("json", "json-NoHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -92,13 +109,15 @@ func TestJsonNoProviders(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-NoProviders")
+	expected, err := testutil.GetExpected("json", "json-NoProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -113,13 +132,15 @@ func TestJsonNoInputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-NoInputs")
+	expected, err := testutil.GetExpected("json", "json-NoInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -134,13 +155,15 @@ func TestJsonNoOutputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-NoOutputs")
+	expected, err := testutil.GetExpected("json", "json-NoOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -155,13 +178,15 @@ func TestJsonOnlyHeader(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-OnlyHeader")
+	expected, err := testutil.GetExpected("json", "json-OnlyHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -176,13 +201,15 @@ func TestJsonOnlyProviders(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-OnlyProviders")
+	expected, err := testutil.GetExpected("json", "json-OnlyProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -197,13 +224,15 @@ func TestJsonOnlyInputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-OnlyInputs")
+	expected, err := testutil.GetExpected("json", "json-OnlyInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -218,13 +247,15 @@ func TestJsonOnlyOutputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-OnlyOutputs")
+	expected, err := testutil.GetExpected("json", "json-OnlyOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -236,13 +267,15 @@ func TestJsonEscapeCharacters(t *testing.T) {
 		EscapeCharacters: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-EscapeCharacters")
+	expected, err := testutil.GetExpected("json", "json-EscapeCharacters")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -253,17 +286,18 @@ func TestJsonOutputValues(t *testing.T) {
 		OutputValues: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("json-OutputValues")
+	expected, err := testutil.GetExpected("json", "json-OutputValues")
 	assert.Nil(err)
 
-	options := &tfconf.Options{
+	options := module.NewOptions().With(&module.Options{
 		OutputValues:     true,
 		OutputValuesPath: "output_values.json",
-	}
+	})
 	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewJSON(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
diff --git a/internal/pkg/print/pretty/pretty_test.go b/internal/format/pretty_test.go
similarity index 55%
rename from internal/pkg/print/pretty/pretty_test.go
rename to internal/format/pretty_test.go
index b76ae9d..21b2b26 100644
--- a/internal/pkg/print/pretty/pretty_test.go
+++ b/internal/format/pretty_test.go
@@ -1,11 +1,11 @@
-package pretty
+package format
 
 import (
 	"testing"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/testutil"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/testutil"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -13,13 +13,15 @@ func TestPretty(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().WithColor().Build()
 
-	expected, err := testutil.GetExpected("pretty")
+	expected, err := testutil.GetExpected("pretty", "pretty")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -31,13 +33,19 @@ func TestPrettySortByName(t *testing.T) {
 		SortByName: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-SortByName")
+	expected, err := testutil.GetExpected("pretty", "pretty-SortByName")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -50,13 +58,20 @@ func TestPrettySortByRequired(t *testing.T) {
 		SortByRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-SortByRequired")
+	expected, err := testutil.GetExpected("pretty", "pretty-SortByRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name:     true,
+			Required: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -71,13 +86,15 @@ func TestPrettyNoHeader(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-NoHeader")
+	expected, err := testutil.GetExpected("pretty", "pretty-NoHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -92,13 +109,15 @@ func TestPrettyNoProviders(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-NoProviders")
+	expected, err := testutil.GetExpected("pretty", "pretty-NoProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -113,13 +132,15 @@ func TestPrettyNoInputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-NoInputs")
+	expected, err := testutil.GetExpected("pretty", "pretty-NoInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -134,13 +155,15 @@ func TestPrettyNoOutputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-NoOutputs")
+	expected, err := testutil.GetExpected("pretty", "pretty-NoOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -155,13 +178,15 @@ func TestPrettyOnlyHeader(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-OnlyHeader")
+	expected, err := testutil.GetExpected("pretty", "pretty-OnlyHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -176,13 +201,15 @@ func TestPrettyOnlyProviders(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-OnlyProviders")
+	expected, err := testutil.GetExpected("pretty", "pretty-OnlyProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -197,13 +224,15 @@ func TestPrettyOnlyInputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-OnlyInputs")
+	expected, err := testutil.GetExpected("pretty", "pretty-OnlyInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -218,13 +247,15 @@ func TestPrettyOnlyOutputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-OnlyOutputs")
+	expected, err := testutil.GetExpected("pretty", "pretty-OnlyOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -236,13 +267,15 @@ func TestPrettyNoColor(t *testing.T) {
 		ShowColor: false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-NoColor")
+	expected, err := testutil.GetExpected("pretty", "pretty-NoColor")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -254,17 +287,18 @@ func TestPrettyOutputValues(t *testing.T) {
 		OutputValues: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("pretty-OutputValues")
+	expected, err := testutil.GetExpected("pretty", "pretty-OutputValues")
 	assert.Nil(err)
 
-	options := &tfconf.Options{
+	options := module.NewOptions().With(&module.Options{
 		OutputValues:     true,
 		OutputValuesPath: "output_values.json",
-	}
+	})
 	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewPretty(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
diff --git a/internal/pkg/print/markdown/table/table_test.go b/internal/format/table_test.go
similarity index 54%
rename from internal/pkg/print/markdown/table/table_test.go
rename to internal/format/table_test.go
index d105dbe..91aca4b 100644
--- a/internal/pkg/print/markdown/table/table_test.go
+++ b/internal/format/table_test.go
@@ -1,11 +1,11 @@
-package table
+package format
 
 import (
 	"testing"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/testutil"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/testutil"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -13,13 +13,15 @@ func TestTable(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().Build()
 
-	expected, err := testutil.GetExpected("table")
+	expected, err := testutil.GetExpected("table", "table")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -31,13 +33,15 @@ func TestTableWithRequired(t *testing.T) {
 		ShowRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-WithRequired")
+	expected, err := testutil.GetExpected("table", "table-WithRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -49,13 +53,19 @@ func TestTableSortByName(t *testing.T) {
 		SortByName: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-SortByName")
+	expected, err := testutil.GetExpected("table", "table-SortByName")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -68,13 +78,20 @@ func TestTableSortByRequired(t *testing.T) {
 		SortByRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-SortByRequired")
+	expected, err := testutil.GetExpected("table", "table-SortByRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name:     true,
+			Required: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -89,13 +106,15 @@ func TestTableNoHeader(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-NoHeader")
+	expected, err := testutil.GetExpected("table", "table-NoHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -110,13 +129,15 @@ func TestTableNoProviders(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-NoProviders")
+	expected, err := testutil.GetExpected("table", "table-NoProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -131,13 +152,15 @@ func TestTableNoInputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-NoInputs")
+	expected, err := testutil.GetExpected("table", "table-NoInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -152,13 +175,15 @@ func TestTableNoOutputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-NoOutputs")
+	expected, err := testutil.GetExpected("table", "table-NoOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -173,13 +198,15 @@ func TestTableOnlyHeader(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-OnlyHeader")
+	expected, err := testutil.GetExpected("table", "table-OnlyHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -194,13 +221,15 @@ func TestTableOnlyProviders(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-OnlyProviders")
+	expected, err := testutil.GetExpected("table", "table-OnlyProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -215,13 +244,15 @@ func TestTableOnlyInputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-OnlyInputs")
+	expected, err := testutil.GetExpected("table", "table-OnlyInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -236,13 +267,15 @@ func TestTableOnlyOutputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-OnlyOutputs")
+	expected, err := testutil.GetExpected("table", "table-OnlyOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -254,31 +287,35 @@ func TestTableEscapeCharacters(t *testing.T) {
 		EscapeCharacters: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-EscapeCharacters")
+	expected, err := testutil.GetExpected("table", "table-EscapeCharacters")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
 }
 
-func TestTableIndentationBellowAllowed(t *testing.T) {
+func TestTableIndentationBelowAllowed(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().With(&print.Settings{
 		MarkdownIndent: 0,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-IndentationBellowAllowed")
+	expected, err := testutil.GetExpected("table", "table-IndentationBelowAllowed")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -290,13 +327,15 @@ func TestTableIndentationAboveAllowed(t *testing.T) {
 		MarkdownIndent: 10,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-IndentationAboveAllowed")
+	expected, err := testutil.GetExpected("table", "table-IndentationAboveAllowed")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -308,13 +347,15 @@ func TestTableIndentationOfFour(t *testing.T) {
 		MarkdownIndent: 4,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-IndentationOfFour")
+	expected, err := testutil.GetExpected("table", "table-IndentationOfFour")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -326,17 +367,18 @@ func TestTableOutputValues(t *testing.T) {
 		OutputValues: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("table-OutputValues")
+	expected, err := testutil.GetExpected("table", "table-OutputValues")
 	assert.Nil(err)
 
-	options := &tfconf.Options{
+	options := module.NewOptions().With(&module.Options{
 		OutputValues:     true,
 		OutputValuesPath: "output_values.json",
-	}
+	})
 	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewTable(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
diff --git a/internal/pkg/print/yaml/yaml_test.go b/internal/format/yaml_test.go
similarity index 55%
rename from internal/pkg/print/yaml/yaml_test.go
rename to internal/format/yaml_test.go
index 2d0705d..229636c 100644
--- a/internal/pkg/print/yaml/yaml_test.go
+++ b/internal/format/yaml_test.go
@@ -1,11 +1,11 @@
-package yaml
+package format
 
 import (
 	"testing"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
-	"github.com/segmentio/terraform-docs/internal/pkg/testutil"
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/internal/testutil"
+	"github.com/segmentio/terraform-docs/pkg/print"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -13,13 +13,15 @@ func TestYaml(t *testing.T) {
 	assert := assert.New(t)
 	settings := testutil.Settings().WithSections().Build()
 
-	expected, err := testutil.GetExpected("yaml")
+	expected, err := testutil.GetExpected("yaml", "yaml")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -31,13 +33,19 @@ func TestYamlSortByName(t *testing.T) {
 		SortByName: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-SortByName")
+	expected, err := testutil.GetExpected("yaml", "yaml-SortByName")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -50,13 +58,20 @@ func TestYamlSortByRequired(t *testing.T) {
 		SortByRequired: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-SortByRequired")
+	expected, err := testutil.GetExpected("yaml", "yaml-SortByRequired")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions().With(&module.Options{
+		SortBy: &module.SortBy{
+			Name:     true,
+			Required: true,
+		},
+	})
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -71,13 +86,15 @@ func TestYamlNoHeader(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-NoHeader")
+	expected, err := testutil.GetExpected("yaml", "yaml-NoHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -92,13 +109,15 @@ func TestYamlNoProviders(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-NoProviders")
+	expected, err := testutil.GetExpected("yaml", "yaml-NoProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -113,13 +132,15 @@ func TestYamlNoInputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-NoInputs")
+	expected, err := testutil.GetExpected("yaml", "yaml-NoInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -134,13 +155,15 @@ func TestYamlNoOutputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-NoOutputs")
+	expected, err := testutil.GetExpected("yaml", "yaml-NoOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -155,13 +178,15 @@ func TestYamlOnlyHeader(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-OnlyHeader")
+	expected, err := testutil.GetExpected("yaml", "yaml-OnlyHeader")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -176,13 +201,15 @@ func TestYamlOnlyProviders(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-OnlyProviders")
+	expected, err := testutil.GetExpected("yaml", "yaml-OnlyProviders")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -197,13 +224,15 @@ func TestYamlOnlyInputs(t *testing.T) {
 		ShowOutputs:   false,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-OnlyInputs")
+	expected, err := testutil.GetExpected("yaml", "yaml-OnlyInputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -218,13 +247,15 @@ func TestYamlOnlyOutputs(t *testing.T) {
 		ShowOutputs:   true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-OnlyOutputs")
+	expected, err := testutil.GetExpected("yaml", "yaml-OnlyOutputs")
 	assert.Nil(err)
 
-	module, err := testutil.GetModule(new(tfconf.Options))
+	options := module.NewOptions()
+	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
@@ -236,17 +267,18 @@ func TestYamlOutputValues(t *testing.T) {
 		OutputValues: true,
 	}).Build()
 
-	expected, err := testutil.GetExpected("yaml-OutputValues")
+	expected, err := testutil.GetExpected("yaml", "yaml-OutputValues")
 	assert.Nil(err)
 
-	options := &tfconf.Options{
+	options := module.NewOptions().With(&module.Options{
 		OutputValues:     true,
 		OutputValuesPath: "output_values.json",
-	}
+	})
 	module, err := testutil.GetModule(options)
 	assert.Nil(err)
 
-	actual, err := Print(module, settings)
+	printer := NewYAML(settings)
+	actual, err := printer.Print(module, settings)
 
 	assert.Nil(err)
 	assert.Equal(expected, actual)
diff --git a/internal/pkg/reader/lines.go b/internal/reader/lines.go
similarity index 100%
rename from internal/pkg/reader/lines.go
rename to internal/reader/lines.go
diff --git a/internal/pkg/testutil/settings.go b/internal/testutil/settings.go
similarity index 96%
rename from internal/pkg/testutil/settings.go
rename to internal/testutil/settings.go
index 1838cf1..b710d01 100644
--- a/internal/pkg/testutil/settings.go
+++ b/internal/testutil/settings.go
@@ -2,7 +2,7 @@ package testutil
 
 import (
 	"github.com/imdario/mergo"
-	"github.com/segmentio/terraform-docs/internal/pkg/print"
+	"github.com/segmentio/terraform-docs/pkg/print"
 )
 
 // TestSettings respresents the Settings instance for tests
diff --git a/internal/pkg/testutil/testing.go b/internal/testutil/testing.go
similarity index 61%
rename from internal/pkg/testutil/testing.go
rename to internal/testutil/testing.go
index 56b7b42..e90dc5d 100644
--- a/internal/pkg/testutil/testing.go
+++ b/internal/testutil/testing.go
@@ -6,31 +6,30 @@ import (
 	"path/filepath"
 	"runtime"
 
-	"github.com/segmentio/terraform-docs/internal/pkg/tfconf"
+	"github.com/segmentio/terraform-docs/internal/module"
+	"github.com/segmentio/terraform-docs/pkg/tfconf"
 )
 
-// GetModule returns what is generated by the test params
-func GetModule(options *tfconf.Options) (*tfconf.Module, error) {
+// GetModule returns 'example' Module
+func GetModule(options *module.Options) (*tfconf.Module, error) {
 	path, err := getExampleFolder()
 	if err != nil {
 		return nil, err
 	}
 	options.Path = path
-
 	if options.OutputValues {
 		options.OutputValuesPath = filepath.Join(path, options.OutputValuesPath)
 	}
-
-	module, err := tfconf.CreateModule(options)
+	tfmodule, err := module.LoadWithOptions(options)
 	if err != nil {
 		return nil, err
 	}
-	return module, err
+	return tfmodule, nil
 }
 
 // GetExpected returns 'example' Module and expected Golden file content
-func GetExpected(name string) (string, error) {
-	path := filepath.Join(testDataPath(), name+".golden")
+func GetExpected(format, name string) (string, error) {
+	path := filepath.Join(testDataPath(), format, name+".golden")
 	bytes, err := ioutil.ReadFile(path)
 	if err != nil {
 		return "", err
@@ -40,7 +39,7 @@ func GetExpected(name string) (string, error) {
 
 func getExampleFolder() (string, error) {
 	_, b, _, _ := runtime.Caller(0)
-	path := filepath.Join(filepath.Dir(b), "..", "..", "..", "examples")
+	path := filepath.Join(filepath.Dir(b), "..", "..", "examples")
 	if _, err := os.Stat(path); os.IsNotExist(err) {
 		return "", err
 	}