refactor: Introduce Format interface and expose to public pkg (#195)

* Introduce format interface and expose to public pkg

* fix issues after merge

* don't panic

* Rename TFString back to String
This commit is contained in:
Khosrow Moossavi
2020-02-19 14:07:10 -05:00
committed by GitHub
parent 3da3769a58
commit 38e18970ed
122 changed files with 1238 additions and 1001 deletions

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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")
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")
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
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
}

View File

@@ -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"
)

View File

@@ -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))
},
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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),

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

43
internal/format/util.go Normal file
View File

@@ -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
}

View File

@@ -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),

View File

@@ -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)

39
internal/module/input.go Normal file
View File

@@ -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
}

264
internal/module/module.go Normal file
View File

@@ -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))
}
}

View File

@@ -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
}

28
internal/module/output.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1 +0,0 @@
package print

View File

@@ -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, " ")
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
}

Some files were not shown because too many files have changed in this diff Show More