mirror of
https://github.com/terraform-docs/terraform-docs.git
synced 2026-03-27 12:58:35 +07:00
feat: Add support for .terraform-docs.yml config file (#272)
* Add support for .terraform-docs.yml config file * add config reader * add usage documentation and reference guide * typo * update docs
This commit is contained in:
@@ -32,16 +32,21 @@ func Execute() error {
|
||||
func NewCommand() *cobra.Command {
|
||||
config := cli.DefaultConfig()
|
||||
cmd := &cobra.Command{
|
||||
Args: cobra.NoArgs,
|
||||
Use: "terraform-docs",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Use: "terraform-docs [PATH]",
|
||||
Short: "A utility to generate documentation from Terraform modules in various output formats",
|
||||
Long: "A utility to generate documentation from Terraform modules in various output formats",
|
||||
Version: version.Full(),
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
Annotations: cli.Annotations("root"),
|
||||
PreRunE: cli.PreRunEFunc(config),
|
||||
RunE: cli.RunEFunc(config),
|
||||
}
|
||||
|
||||
// flags
|
||||
cmd.PersistentFlags().StringVarP(&config.File, "config", "c", ".terraform-docs.yml", "config file name")
|
||||
|
||||
cmd.PersistentFlags().StringSliceVar(&config.Sections.Show, "show", []string{}, "show section [header, inputs, outputs, providers, requirements]")
|
||||
cmd.PersistentFlags().StringSliceVar(&config.Sections.Hide, "hide", []string{}, "hide section [header, inputs, outputs, providers, requirements]")
|
||||
cmd.PersistentFlags().BoolVar(&config.Sections.ShowAll, "show-all", true, "show all sections")
|
||||
|
||||
57
docs/CONFIG_FILE.md
Normal file
57
docs/CONFIG_FILE.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Config File Reference
|
||||
|
||||
All available options for `.terraform-docs.yml`. Note that not all of them can be used at the same time (e.g. `sections.hide` and `sections.show`)
|
||||
|
||||
```yaml
|
||||
formatter: <FORMATTER_NAME>
|
||||
header-from: main.tf
|
||||
|
||||
sections:
|
||||
hide-all: false
|
||||
hide:
|
||||
- header
|
||||
- inputs
|
||||
- outputs
|
||||
- providers
|
||||
- requirements
|
||||
show-all: true
|
||||
show:
|
||||
- header
|
||||
- inputs
|
||||
- outputs
|
||||
- providers
|
||||
- requirements
|
||||
|
||||
output-values:
|
||||
enabled: false
|
||||
from: ""
|
||||
|
||||
sort:
|
||||
enabled: true
|
||||
by:
|
||||
- required
|
||||
- type
|
||||
|
||||
settings:
|
||||
color: true
|
||||
escape: true
|
||||
indent: 2
|
||||
required: true
|
||||
sensitive: true
|
||||
```
|
||||
|
||||
Available options for `FORMATTER_NAME` are:
|
||||
|
||||
- `asciidoc`
|
||||
- `asciidoc document`
|
||||
- `asciidoc table`
|
||||
- `json`
|
||||
- `markdonw`
|
||||
- `markdonw document`
|
||||
- `markdonw table`
|
||||
- `pretty`
|
||||
- `tfvars hcl`
|
||||
- `tfvars json`
|
||||
- `toml`
|
||||
- `xml`
|
||||
- `yaml`
|
||||
@@ -6,9 +6,14 @@ A utility to generate documentation from Terraform modules in various output for
|
||||
|
||||
A utility to generate documentation from Terraform modules in various output formats
|
||||
|
||||
```
|
||||
terraform-docs [PATH] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
-h, --help help for terraform-docs
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
@@ -39,4 +44,4 @@ A utility to generate documentation from Terraform modules in various output for
|
||||
* [terraform-docs xml](/docs/formats/xml.md) - Generate XML of inputs and outputs
|
||||
* [terraform-docs yaml](/docs/formats/yaml.md) - Generate YAML of inputs and outputs
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -8,6 +8,36 @@ Support for Terraform `v0.12.x` has been added in `terraform-docs` version `v0.8
|
||||
|
||||
Please refer to [Formats Guide](/docs/FORMATS_GUIDE.md) for guidance on output formats, execution syntax, CLI options, etc.
|
||||
|
||||
## Configuration File
|
||||
|
||||
`terraform-docs` can read the desired formatter and options from a file, instead of being passed to in CLI. This is a convenient way to share the configuation amongst teammates and also CI pipelines. To do so you can use `-c` or `--config` flag which accepts name of the config file (default to `.terraform-docs.yml`). Example `.terraform-docs.yml`:
|
||||
|
||||
```yaml
|
||||
formatter: markdown table
|
||||
header-from: doc.txt
|
||||
sections:
|
||||
hide:
|
||||
- inputs
|
||||
- outputs
|
||||
settings:
|
||||
indent: 4
|
||||
required: false
|
||||
```
|
||||
|
||||
when executed:
|
||||
|
||||
```bash
|
||||
terraform-docs ./example/ # this will read the config above and:
|
||||
#
|
||||
# 1) generate output of 'Markdown Table'
|
||||
# 2) read module 'Header' from 'doc.txt'
|
||||
# 3) hide 'Inputs' and 'Outputs'
|
||||
# 4) set 'Indention' to 4
|
||||
# 5) hide 'Required' column
|
||||
```
|
||||
|
||||
Please refer to [Config File Reference](/docs/CONFIG_FILE.md) for all the available confiuartion options.
|
||||
|
||||
## Control Visibility of Sections
|
||||
|
||||
Output generated by `terraform-docs` consists of different sections (header, requirements, providers, inputs, outputs) which are visible by default. The visibility of these can be controlled by one or combination of : `--show-all`, `--hide-all`, `--show <name>` and `--hide <name>`. For example:
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs asciidoc document [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -419,4 +420,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs asciidoc table [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -375,4 +376,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -22,6 +22,7 @@ terraform-docs asciidoc [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -39,4 +40,4 @@ terraform-docs asciidoc [PATH] [flags]
|
||||
* [terraform-docs asciidoc document](/docs/formats/asciidoc-document.md) - Generate AsciiDoc document of inputs and outputs
|
||||
* [terraform-docs asciidoc table](/docs/formats/asciidoc-table.md) - Generate AsciiDoc tables of inputs and outputs
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -20,6 +20,7 @@ terraform-docs json [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -338,4 +339,4 @@ generates the following output:
|
||||
}
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs markdown document [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--escape escape special characters (default true)
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
@@ -420,4 +421,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs markdown table [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--escape escape special characters (default true)
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
@@ -146,4 +147,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -23,6 +23,7 @@ terraform-docs markdown [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -40,4 +41,4 @@ terraform-docs markdown [PATH] [flags]
|
||||
* [terraform-docs markdown document](/docs/formats/markdown-document.md) - Generate Markdown document of inputs and outputs
|
||||
* [terraform-docs markdown table](/docs/formats/markdown-table.md) - Generate Markdown tables of inputs and outputs
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -20,6 +20,7 @@ terraform-docs pretty [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -242,4 +243,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs tfvars hcl [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -98,4 +99,4 @@ generates the following output:
|
||||
with-url = ""
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs tfvars json [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -100,4 +101,4 @@ generates the following output:
|
||||
}
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -15,6 +15,7 @@ Generate terraform.tfvars of inputs
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -32,4 +33,4 @@ Generate terraform.tfvars of inputs
|
||||
* [terraform-docs tfvars hcl](/docs/formats/tfvars-hcl.md) - Generate HCL format of terraform.tfvars of inputs
|
||||
* [terraform-docs tfvars json](/docs/formats/tfvars-json.md) - Generate JSON format of terraform.tfvars of inputs
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs toml [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -315,4 +316,4 @@ generates the following output:
|
||||
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs xml [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -337,4 +338,4 @@ generates the following output:
|
||||
</module>
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
@@ -19,6 +19,7 @@ terraform-docs yaml [PATH] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-c, --config string config file name (default ".terraform-docs.yml")
|
||||
--header-from string relative path of a file to read header from (default "main.tf")
|
||||
--hide strings hide section [header, inputs, outputs, providers, requirements]
|
||||
--hide-all hide all sections (default false)
|
||||
@@ -289,4 +290,4 @@ generates the following output:
|
||||
version: '>= 2.2.0'
|
||||
|
||||
|
||||
###### Auto generated by spf13/cobra on 24-May-2020
|
||||
###### Auto generated by spf13/cobra on 13-Jul-2020
|
||||
|
||||
15
examples/.terraform-docs.yml
Normal file
15
examples/.terraform-docs.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
formatter: markdown table
|
||||
header-from: doc.txt
|
||||
sections:
|
||||
hide-all: true
|
||||
show:
|
||||
- header
|
||||
- inputs
|
||||
- providers
|
||||
sort:
|
||||
enabled: true
|
||||
by:
|
||||
- required
|
||||
settings:
|
||||
indent: 4
|
||||
escape: false
|
||||
@@ -1,16 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Annotations returns set of annotations for cobra.Commands,
|
||||
// specifically the 'command' namd and command 'kind'
|
||||
// specifically the command 'name' and command 'kind'
|
||||
func Annotations(cmd string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for _, s := range strings.Split(cmd, " ") {
|
||||
annotations["command"] = s
|
||||
return map[string]string{
|
||||
"command": cmd,
|
||||
"kind": "formatter",
|
||||
}
|
||||
annotations["kind"] = "formatter"
|
||||
return annotations
|
||||
}
|
||||
|
||||
35
internal/cli/annotations_test.go
Normal file
35
internal/cli/annotations_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommandAnnotations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command string
|
||||
}{
|
||||
{
|
||||
name: "command annotations",
|
||||
command: "foo",
|
||||
},
|
||||
{
|
||||
name: "command annotations",
|
||||
command: "foo bar",
|
||||
},
|
||||
{
|
||||
name: "command annotations",
|
||||
command: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual := Annotations(tt.command)
|
||||
assert.Equal(tt.command, actual["command"])
|
||||
assert.Equal("formatter", actual["kind"])
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,11 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/terraform-docs/terraform-docs/internal/module"
|
||||
"github.com/terraform-docs/terraform-docs/pkg/print"
|
||||
)
|
||||
|
||||
// list of flagset items which explicitly changed from CLI
|
||||
var changedfs = make(map[string]bool)
|
||||
|
||||
type _sections struct {
|
||||
NoHeader bool
|
||||
NoInputs bool
|
||||
@@ -19,26 +15,26 @@ type _sections struct {
|
||||
NoRequirements bool
|
||||
}
|
||||
type sections struct {
|
||||
Show []string
|
||||
Hide []string
|
||||
ShowAll bool
|
||||
HideAll bool
|
||||
Deprecated *_sections
|
||||
Show []string `yaml:"show"`
|
||||
Hide []string `yaml:"hide"`
|
||||
ShowAll bool `yaml:"show-all"`
|
||||
HideAll bool `yaml:"hide-all"`
|
||||
Deprecated _sections `yaml:"-"`
|
||||
|
||||
header bool
|
||||
inputs bool
|
||||
outputs bool
|
||||
providers bool
|
||||
requirements bool
|
||||
header bool `yaml:"-"`
|
||||
inputs bool `yaml:"-"`
|
||||
outputs bool `yaml:"-"`
|
||||
providers bool `yaml:"-"`
|
||||
requirements bool `yaml:"-"`
|
||||
}
|
||||
|
||||
func defaultSections() *sections {
|
||||
return §ions{
|
||||
func defaultSections() sections {
|
||||
return sections{
|
||||
Show: []string{},
|
||||
Hide: []string{},
|
||||
ShowAll: true,
|
||||
HideAll: false,
|
||||
Deprecated: &_sections{
|
||||
Deprecated: _sections{
|
||||
NoHeader: false,
|
||||
NoInputs: false,
|
||||
NoOutputs: false,
|
||||
@@ -110,12 +106,12 @@ func (s *sections) visibility(section string) bool {
|
||||
}
|
||||
|
||||
type outputvalues struct {
|
||||
Enabled bool
|
||||
From string
|
||||
Enabled bool `yaml:"enabled"`
|
||||
From string `yaml:"from"`
|
||||
}
|
||||
|
||||
func defaultOutputValues() *outputvalues {
|
||||
return &outputvalues{
|
||||
func defaultOutputValues() outputvalues {
|
||||
return outputvalues{
|
||||
Enabled: false,
|
||||
From: "",
|
||||
}
|
||||
@@ -132,26 +128,28 @@ func (o *outputvalues) validate() error {
|
||||
}
|
||||
|
||||
type sortby struct {
|
||||
Required bool
|
||||
Type bool
|
||||
Required bool `name:"required"`
|
||||
Type bool `name:"type"`
|
||||
}
|
||||
type _sort struct {
|
||||
NoSort bool
|
||||
}
|
||||
type sort struct {
|
||||
Enabled bool
|
||||
By *sortby
|
||||
Deprecated *_sort
|
||||
Enabled bool `yaml:"enabled"`
|
||||
ByList []string `yaml:"by"`
|
||||
By sortby `yaml:"-"`
|
||||
Deprecated _sort `yaml:"-"`
|
||||
}
|
||||
|
||||
func defaultSort() *sort {
|
||||
return &sort{
|
||||
func defaultSort() sort {
|
||||
return sort{
|
||||
Enabled: true,
|
||||
By: &sortby{
|
||||
ByList: []string{},
|
||||
By: sortby{
|
||||
Required: false,
|
||||
Type: false,
|
||||
},
|
||||
Deprecated: &_sort{
|
||||
Deprecated: _sort{
|
||||
NoSort: false,
|
||||
},
|
||||
}
|
||||
@@ -164,6 +162,14 @@ func (s *sort) validate() error {
|
||||
return fmt.Errorf("'--%s' and '--no-%s' can't be used together", item, item)
|
||||
}
|
||||
}
|
||||
types := []string{"required", "type"}
|
||||
for _, item := range s.ByList {
|
||||
switch item {
|
||||
case types[0], types[1]:
|
||||
default:
|
||||
return fmt.Errorf("'%s' is not a valid sort type", item)
|
||||
}
|
||||
}
|
||||
if s.By.Required && s.By.Type {
|
||||
return fmt.Errorf("'--sort-by-required' and '--sort-by-type' can't be used together")
|
||||
}
|
||||
@@ -177,22 +183,22 @@ type _settings struct {
|
||||
NoSensitive bool
|
||||
}
|
||||
type settings struct {
|
||||
Color bool
|
||||
Escape bool
|
||||
Indent int
|
||||
Required bool
|
||||
Sensitive bool
|
||||
Deprecated *_settings
|
||||
Color bool `yaml:"color"`
|
||||
Escape bool `yaml:"escape"`
|
||||
Indent int `yaml:"indent"`
|
||||
Required bool `yaml:"required"`
|
||||
Sensitive bool `yaml:"sensitive"`
|
||||
Deprecated _settings `yaml:"-"`
|
||||
}
|
||||
|
||||
func defaultSettings() *settings {
|
||||
return &settings{
|
||||
func defaultSettings() settings {
|
||||
return settings{
|
||||
Color: true,
|
||||
Escape: true,
|
||||
Indent: 2,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
Deprecated: &_settings{
|
||||
Deprecated: _settings{
|
||||
NoColor: false,
|
||||
NoEscape: false,
|
||||
NoRequired: false,
|
||||
@@ -213,17 +219,19 @@ func (s *settings) validate() error {
|
||||
|
||||
// Config represents all the available config options that can be accessed and passed through CLI
|
||||
type Config struct {
|
||||
Formatter string
|
||||
HeaderFrom string
|
||||
Sections *sections
|
||||
OutputValues *outputvalues
|
||||
Sort *sort
|
||||
Settings *settings
|
||||
File string `yaml:"-"`
|
||||
Formatter string `yaml:"formatter"`
|
||||
HeaderFrom string `yaml:"header-from"`
|
||||
Sections sections `yaml:"sections"`
|
||||
OutputValues outputvalues `yaml:"output-values"`
|
||||
Sort sort `yaml:"sort"`
|
||||
Settings settings `yaml:"settings"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns new instance of Config with default values set
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
File: "",
|
||||
Formatter: "",
|
||||
HeaderFrom: "main.tf",
|
||||
Sections: defaultSections(),
|
||||
@@ -233,10 +241,8 @@ func DefaultConfig() *Config {
|
||||
}
|
||||
}
|
||||
|
||||
// normalize provided Config
|
||||
func (c *Config) normalize(command string) {
|
||||
c.Formatter = strings.Replace(command, "terraform-docs ", "", -1)
|
||||
|
||||
// process provided Config
|
||||
func (c *Config) process() {
|
||||
// sections
|
||||
if c.Sections.HideAll && !changedfs["show-all"] {
|
||||
c.Sections.ShowAll = false
|
||||
@@ -251,27 +257,32 @@ func (c *Config) normalize(command string) {
|
||||
c.Sections.requirements = c.Sections.visibility("requirements")
|
||||
|
||||
// sort
|
||||
if !changedfs["sort"] {
|
||||
if !changedfs["sort"] && changedfs["no-sort"] {
|
||||
c.Sort.Enabled = !c.Sort.Deprecated.NoSort
|
||||
}
|
||||
|
||||
// settings
|
||||
if !changedfs["escape"] {
|
||||
if !changedfs["escape"] && changedfs["no-escape"] {
|
||||
c.Settings.Escape = !c.Settings.Deprecated.NoEscape
|
||||
}
|
||||
if !changedfs["color"] {
|
||||
if !changedfs["color"] && changedfs["no-color"] {
|
||||
c.Settings.Color = !c.Settings.Deprecated.NoColor
|
||||
}
|
||||
if !changedfs["required"] {
|
||||
if !changedfs["required"] && changedfs["no-required"] {
|
||||
c.Settings.Required = !c.Settings.Deprecated.NoRequired
|
||||
}
|
||||
if !changedfs["sensitive"] {
|
||||
if !changedfs["sensitive"] && changedfs["no-sensitive"] {
|
||||
c.Settings.Sensitive = !c.Settings.Deprecated.NoSensitive
|
||||
}
|
||||
}
|
||||
|
||||
// validate config and check for any misuse or misconfiguration
|
||||
func (c *Config) validate() error {
|
||||
// formatter
|
||||
if c.Formatter == "" {
|
||||
return fmt.Errorf("value of 'formatter' can't be empty")
|
||||
}
|
||||
|
||||
// header-from
|
||||
if c.HeaderFrom == "" {
|
||||
return fmt.Errorf("value of '--header-from' can't be empty")
|
||||
@@ -338,12 +349,3 @@ func (c *Config) extract() (*print.Settings, *module.Options) {
|
||||
|
||||
return settings, options
|
||||
}
|
||||
|
||||
func contains(list []string, name string) bool {
|
||||
for _, i := range list {
|
||||
if i == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
166
internal/cli/reader.go
Normal file
166
internal/cli/reader.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type cfgreader struct {
|
||||
file string
|
||||
config *Config
|
||||
overrides Config
|
||||
}
|
||||
|
||||
func (c *cfgreader) exist() (bool, error) {
|
||||
if c.file == "" {
|
||||
return false, fmt.Errorf("config file name is missing")
|
||||
}
|
||||
if info, err := os.Stat(c.file); os.IsNotExist(err) || info.IsDir() {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *cfgreader) parse() error {
|
||||
if ok, err := c.exist(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(c.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.overrides = *c.config
|
||||
if err := yaml.Unmarshal(content, c.config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.config.Sections.HideAll && !changedfs["show-all"] {
|
||||
c.config.Sections.ShowAll = false
|
||||
}
|
||||
if !c.config.Sections.ShowAll && !changedfs["hide-all"] {
|
||||
c.config.Sections.HideAll = true
|
||||
}
|
||||
|
||||
for flag, enabled := range changedfs {
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
switch flag {
|
||||
case "header-from":
|
||||
if err := c.overrideValue(flag, c.config, &c.overrides); err != nil {
|
||||
return err
|
||||
}
|
||||
case "show":
|
||||
c.overrideShow()
|
||||
case "hide":
|
||||
c.overrideHide()
|
||||
case "sort":
|
||||
if err := c.overrideValue("enabled", &c.config.Sort, &c.overrides.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
case "sort-by-required", "sort-by-type":
|
||||
mapping := map[string]string{"sort-by-required": "required", "sort-by-type": "type"}
|
||||
if !contains(c.config.Sort.ByList, mapping[flag]) {
|
||||
c.config.Sort.ByList = append(c.config.Sort.ByList, mapping[flag])
|
||||
}
|
||||
el := reflect.ValueOf(&c.overrides.Sort.By).Elem()
|
||||
field, err := c.findField(el, "name", mapping[flag])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !el.FieldByName(field).Bool() {
|
||||
c.config.Sort.ByList = remove(c.config.Sort.ByList, mapping[flag])
|
||||
}
|
||||
case "output-values", "output-values-from":
|
||||
mapping := map[string]string{"output-values": "enabled", "output-values-from": "from"}
|
||||
if err := c.overrideValue(mapping[flag], &c.config.OutputValues, &c.overrides.OutputValues); err != nil {
|
||||
return err
|
||||
}
|
||||
case "color", "escape", "indent", "required", "sensitive":
|
||||
if err := c.overrideValue(flag, &c.config.Settings, &c.overrides.Settings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.updateSortTypes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cfgreader) overrideValue(name string, to interface{}, from interface{}) error {
|
||||
if name == "" || name == "-" {
|
||||
return fmt.Errorf("tag name cannot be blank or empty")
|
||||
}
|
||||
toEl := reflect.ValueOf(to).Elem()
|
||||
field, err := c.findField(toEl, "yaml", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fromEl := reflect.ValueOf(from).Elem()
|
||||
toEl.FieldByName(field).Set(fromEl.FieldByName(field))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cfgreader) overrideShow() {
|
||||
for _, item := range c.overrides.Sections.Show {
|
||||
if c.config.Sections.ShowAll {
|
||||
if contains(c.config.Sections.Hide, item) {
|
||||
c.config.Sections.Hide = remove(c.config.Sections.Hide, item)
|
||||
c.config.Sections.Show = remove(c.config.Sections.Show, item)
|
||||
}
|
||||
} else {
|
||||
if !contains(c.config.Sections.Show, item) {
|
||||
c.config.Sections.Show = append(c.config.Sections.Show, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cfgreader) overrideHide() {
|
||||
for _, item := range c.overrides.Sections.Hide {
|
||||
if c.config.Sections.HideAll {
|
||||
if contains(c.config.Sections.Show, item) {
|
||||
c.config.Sections.Show = remove(c.config.Sections.Show, item)
|
||||
c.config.Sections.Hide = remove(c.config.Sections.Hide, item)
|
||||
}
|
||||
} else {
|
||||
if !contains(c.config.Sections.Hide, item) {
|
||||
c.config.Sections.Hide = append(c.config.Sections.Hide, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cfgreader) updateSortTypes() error {
|
||||
for _, item := range c.config.Sort.ByList {
|
||||
el := reflect.ValueOf(&c.config.Sort.By).Elem()
|
||||
field, err := c.findField(el, "name", item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el.FieldByName(field).Set(reflect.ValueOf(true))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cfgreader) findField(el reflect.Value, tag string, value string) (string, error) {
|
||||
for i := 0; i < el.NumField(); i++ {
|
||||
f := el.Type().Field(i)
|
||||
t := f.Tag.Get(tag)
|
||||
if t == "" || t == "-" || t != value {
|
||||
continue
|
||||
}
|
||||
return f.Name, nil
|
||||
}
|
||||
return "", fmt.Errorf("field with tag: '%s', value; '%s' not found or not readable", tag, value)
|
||||
}
|
||||
425
internal/cli/reader_test.go
Normal file
425
internal/cli/reader_test.go
Normal file
@@ -0,0 +1,425 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config cfgreader
|
||||
expected bool
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "config file exists",
|
||||
config: cfgreader{file: "testdata/sample-config.yaml"},
|
||||
expected: true,
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "config file not found",
|
||||
config: cfgreader{file: "testdata/noop.yaml"},
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "config file empty",
|
||||
config: cfgreader{file: ""},
|
||||
expected: false,
|
||||
wantErr: true,
|
||||
errMsg: "config file name is missing",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual, err := tt.config.exist()
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
if tt.errMsg != "" {
|
||||
assert.Equal(tt.errMsg, err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
}
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideValue(t *testing.T) {
|
||||
config := DefaultConfig()
|
||||
override := DefaultConfig()
|
||||
tests := []struct {
|
||||
name string
|
||||
tag string
|
||||
to func() interface{}
|
||||
from func() interface{}
|
||||
overrideFn func()
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "override values of given field",
|
||||
tag: "header-from",
|
||||
to: func() interface{} { return config },
|
||||
from: func() interface{} { return override },
|
||||
overrideFn: func() { override.HeaderFrom = "foo.txt" },
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "override values of given field",
|
||||
tag: "enabled",
|
||||
to: func() interface{} { return &config.Sort },
|
||||
from: func() interface{} { return &override.Sort },
|
||||
overrideFn: func() { override.Sort.Enabled = false },
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "override values of given field",
|
||||
tag: "color",
|
||||
to: func() interface{} { return &config.Settings },
|
||||
from: func() interface{} { return &override.Settings },
|
||||
overrideFn: func() { override.Settings.Color = false },
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "override values of unkwon field tag",
|
||||
tag: "not-available",
|
||||
to: func() interface{} { return config },
|
||||
from: func() interface{} { return override },
|
||||
overrideFn: func() {},
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'yaml', value; 'not-available' not found or not readable",
|
||||
},
|
||||
{
|
||||
name: "override values of blank field tag",
|
||||
tag: "-",
|
||||
to: func() interface{} { return config },
|
||||
from: func() interface{} { return override },
|
||||
overrideFn: func() {},
|
||||
wantErr: true,
|
||||
errMsg: "tag name cannot be blank or empty",
|
||||
},
|
||||
{
|
||||
name: "override values of empty field tag",
|
||||
tag: "",
|
||||
to: func() interface{} { return config },
|
||||
from: func() interface{} { return override },
|
||||
overrideFn: func() {},
|
||||
wantErr: true,
|
||||
errMsg: "tag name cannot be blank or empty",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
c := cfgreader{config: config}
|
||||
|
||||
tt.overrideFn()
|
||||
|
||||
if !tt.wantErr {
|
||||
// make sure before values are different
|
||||
assert.NotEqual(override, config)
|
||||
}
|
||||
|
||||
// then override property 'from' to 'to'
|
||||
err := c.overrideValue(tt.tag, tt.to(), tt.from())
|
||||
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
assert.Equal(tt.errMsg, err.Error())
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
// then make sure values are the same now
|
||||
assert.Equal(override, config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideShow(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
show []string
|
||||
hide []string
|
||||
showall bool
|
||||
overrideShow []string
|
||||
expectedShow []string
|
||||
expectedHide []string
|
||||
}{
|
||||
{
|
||||
name: "override section show",
|
||||
show: []string{""},
|
||||
hide: []string{"inputs", "outputs"},
|
||||
showall: true,
|
||||
overrideShow: []string{"inputs"},
|
||||
expectedShow: []string{""},
|
||||
expectedHide: []string{"outputs"},
|
||||
},
|
||||
{
|
||||
name: "override section show",
|
||||
show: []string{"providers"},
|
||||
hide: []string{"inputs"},
|
||||
showall: true,
|
||||
overrideShow: []string{"outputs"},
|
||||
expectedShow: []string{"providers"},
|
||||
expectedHide: []string{"inputs"},
|
||||
},
|
||||
{
|
||||
name: "override section show",
|
||||
show: []string{"inputs"},
|
||||
hide: []string{"providers"},
|
||||
showall: false,
|
||||
overrideShow: []string{"outputs"},
|
||||
expectedShow: []string{"inputs", "outputs"},
|
||||
expectedHide: []string{"providers"},
|
||||
},
|
||||
{
|
||||
name: "override section show",
|
||||
show: []string{"inputs"},
|
||||
hide: []string{"inputs"},
|
||||
showall: false,
|
||||
overrideShow: []string{"inputs"},
|
||||
expectedShow: []string{"inputs"},
|
||||
expectedHide: []string{"inputs"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
config := DefaultConfig()
|
||||
override := DefaultConfig()
|
||||
c := cfgreader{config: config, overrides: *override}
|
||||
|
||||
c.config.Sections.Show = tt.show
|
||||
c.config.Sections.Hide = tt.hide
|
||||
c.config.Sections.ShowAll = tt.showall
|
||||
c.overrides.Sections.Show = tt.overrideShow
|
||||
|
||||
c.overrideShow()
|
||||
|
||||
assert.Equal(tt.expectedShow, c.config.Sections.Show)
|
||||
assert.Equal(tt.expectedHide, c.config.Sections.Hide)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideHide(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
show []string
|
||||
hide []string
|
||||
hideall bool
|
||||
overrideHide []string
|
||||
expectedShow []string
|
||||
expectedHide []string
|
||||
}{
|
||||
{
|
||||
name: "override section hide",
|
||||
show: []string{"inputs", "outputs"},
|
||||
hide: []string{""},
|
||||
hideall: true,
|
||||
overrideHide: []string{"inputs"},
|
||||
expectedShow: []string{"outputs"},
|
||||
expectedHide: []string{""},
|
||||
},
|
||||
{
|
||||
name: "override section hide",
|
||||
show: []string{"inputs"},
|
||||
hide: []string{"providers"},
|
||||
hideall: true,
|
||||
overrideHide: []string{"outputs"},
|
||||
expectedShow: []string{"inputs"},
|
||||
expectedHide: []string{"providers"},
|
||||
},
|
||||
{
|
||||
name: "override section hide",
|
||||
show: []string{"providers"},
|
||||
hide: []string{"inputs"},
|
||||
hideall: false,
|
||||
overrideHide: []string{"outputs"},
|
||||
expectedShow: []string{"providers"},
|
||||
expectedHide: []string{"inputs", "outputs"},
|
||||
},
|
||||
{
|
||||
name: "override section hide",
|
||||
show: []string{"inputs"},
|
||||
hide: []string{"inputs"},
|
||||
hideall: false,
|
||||
overrideHide: []string{"inputs"},
|
||||
expectedShow: []string{"inputs"},
|
||||
expectedHide: []string{"inputs"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
config := DefaultConfig()
|
||||
override := DefaultConfig()
|
||||
c := cfgreader{config: config, overrides: *override}
|
||||
|
||||
c.config.Sections.Show = tt.show
|
||||
c.config.Sections.Hide = tt.hide
|
||||
c.config.Sections.HideAll = tt.hideall
|
||||
c.overrides.Sections.Hide = tt.overrideHide
|
||||
|
||||
c.overrideHide()
|
||||
|
||||
assert.Equal(tt.expectedShow, c.config.Sections.Show)
|
||||
assert.Equal(tt.expectedHide, c.config.Sections.Hide)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSortTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
appendFn func(config *Config)
|
||||
expectedFn func(config *Config) bool
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "override values of given field",
|
||||
appendFn: func(config *Config) { config.Sort.ByList = append(config.Sort.ByList, "required") },
|
||||
expectedFn: func(config *Config) bool { return config.Sort.By.Required },
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "override values of given field",
|
||||
appendFn: func(config *Config) { config.Sort.ByList = append(config.Sort.ByList, "type") },
|
||||
expectedFn: func(config *Config) bool { return config.Sort.By.Type },
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "override values of given field",
|
||||
appendFn: func(config *Config) { config.Sort.ByList = append(config.Sort.ByList, "unknown") },
|
||||
expectedFn: func(config *Config) bool { return false },
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'name', value; 'unknown' not found or not readable",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
config := DefaultConfig()
|
||||
c := cfgreader{config: config}
|
||||
|
||||
tt.appendFn(config)
|
||||
|
||||
// make sure before values is false
|
||||
assert.Equal(false, tt.expectedFn(config))
|
||||
|
||||
// then update sort types
|
||||
err := c.updateSortTypes()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
assert.Equal(tt.errMsg, err.Error())
|
||||
} else {
|
||||
// then make sure values is true
|
||||
assert.Nil(err)
|
||||
assert.Equal(true, tt.expectedFn(config))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindField(t *testing.T) {
|
||||
type sample struct {
|
||||
A string `foo:"a"`
|
||||
B string `bar:"b"`
|
||||
C string `baz:"-"`
|
||||
D string `fizz:"-"`
|
||||
E string `buzz:""`
|
||||
F string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
tag string
|
||||
value string
|
||||
expected string
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "find field with given tag",
|
||||
tag: "foo",
|
||||
value: "a",
|
||||
expected: "A",
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "find field with given tag",
|
||||
tag: "bar",
|
||||
value: "b",
|
||||
expected: "B",
|
||||
wantErr: false,
|
||||
errMsg: "",
|
||||
},
|
||||
{
|
||||
name: "find field with tag none",
|
||||
tag: "baz",
|
||||
value: "-",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'baz', value; '-' not found or not readable",
|
||||
},
|
||||
{
|
||||
name: "find field with tag none",
|
||||
tag: "fizz",
|
||||
value: "-",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'fizz', value; '-' not found or not readable",
|
||||
},
|
||||
{
|
||||
name: "find field with tag empty",
|
||||
tag: "buzz",
|
||||
value: "",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'buzz', value; '' not found or not readable",
|
||||
},
|
||||
{
|
||||
name: "find field with tag unknown",
|
||||
tag: "unknown",
|
||||
value: "unknown",
|
||||
expected: "",
|
||||
wantErr: true,
|
||||
errMsg: "field with tag: 'unknown', value; 'unknown' not found or not readable",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
c := cfgreader{}
|
||||
el := reflect.ValueOf(&sample{}).Elem()
|
||||
|
||||
actual, err := c.findField(el, tt.tag, tt.value)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.NotNil(err)
|
||||
assert.Equal(tt.errMsg, err.Error())
|
||||
} else {
|
||||
assert.Nil(err)
|
||||
assert.Equal(tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -10,16 +12,63 @@ import (
|
||||
"github.com/terraform-docs/terraform-docs/internal/module"
|
||||
)
|
||||
|
||||
// list of flagset items which are explicitly changed from CLI
|
||||
var changedfs = make(map[string]bool)
|
||||
|
||||
// PreRunEFunc returns actual 'cobra.Command#PreRunE' function
|
||||
// for 'formatter' commands. This functions reads and normalizes
|
||||
// flags and arguments passed through CLI execution.
|
||||
func PreRunEFunc(config *Config) func(*cobra.Command, []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
formatter := cmd.Annotations["command"]
|
||||
|
||||
// root command must have an argument, otherwise we're going to show help
|
||||
if formatter == "root" && len(args) == 0 {
|
||||
cmd.Help() //nolint:errcheck
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
changedfs[f.Name] = f.Changed
|
||||
})
|
||||
|
||||
config.normalize(cmd.CommandPath())
|
||||
// read config file if provided and/or available
|
||||
if config.File == "" {
|
||||
return fmt.Errorf("value of '--config' can't be empty")
|
||||
}
|
||||
|
||||
file := filepath.Join(args[0], config.File)
|
||||
cfgreader := &cfgreader{
|
||||
file: file,
|
||||
config: config,
|
||||
}
|
||||
|
||||
if found, err := cfgreader.exist(); !found {
|
||||
// config is explicitly provided and file not found, this is an error
|
||||
if changedfs["config"] {
|
||||
return err
|
||||
}
|
||||
// config is not provided and file not found, only show an error for the root command
|
||||
if formatter == "root" {
|
||||
cmd.Help() //nolint:errcheck
|
||||
os.Exit(0)
|
||||
}
|
||||
} else {
|
||||
// config file is found, we're now going to parse it
|
||||
if err := cfgreader.parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// explicitly setting formatter to Config for non-root commands this
|
||||
// will effectively override formattter properties from config file
|
||||
// if 1) config file exists and 2) formatter is set and 3) explicitly
|
||||
// a subcommand was executed in the terminal
|
||||
if formatter != "root" {
|
||||
config.Formatter = formatter
|
||||
}
|
||||
|
||||
config.process()
|
||||
|
||||
if err := config.validate(); err != nil {
|
||||
return err
|
||||
|
||||
1
internal/cli/testdata/sample-config.yaml
vendored
Normal file
1
internal/cli/testdata/sample-config.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
formatter: foo
|
||||
29
internal/cli/util.go
Normal file
29
internal/cli/util.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package cli
|
||||
|
||||
func contains(list []string, name string) bool {
|
||||
for _, v := range list {
|
||||
if v == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func index(list []string, name string) int {
|
||||
for i, v := range list {
|
||||
if v == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func remove(list []string, name string) []string {
|
||||
index := index(list, name)
|
||||
if index < 0 {
|
||||
return list
|
||||
}
|
||||
list[index] = list[len(list)-1]
|
||||
list[len(list)-1] = ""
|
||||
return list[:len(list)-1]
|
||||
}
|
||||
121
internal/cli/util_test.go
Normal file
121
internal/cli/util_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSliceContains(t *testing.T) {
|
||||
list := []string{"foo", "bar", "buzz"}
|
||||
tests := []struct {
|
||||
name string
|
||||
item string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "item exists in slice",
|
||||
item: "foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "item exists in slice",
|
||||
item: "bar",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "item not exist in slice",
|
||||
item: "fizz",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty item",
|
||||
item: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual := contains(list, tt.item)
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceIndex(t *testing.T) {
|
||||
list := []string{"foo", "bar", "buzz"}
|
||||
tests := []struct {
|
||||
name string
|
||||
item string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "index of item exists in slice",
|
||||
item: "foo",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "index of item exists in slice",
|
||||
item: "bar",
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "index of item not exist in slice",
|
||||
item: "fizz",
|
||||
expected: -1,
|
||||
},
|
||||
{
|
||||
name: "index of empty item",
|
||||
item: "",
|
||||
expected: -1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
actual := index(list, tt.item)
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceRemove(t *testing.T) {
|
||||
list := []string{"foo", "bar", "buzz"}
|
||||
tests := []struct {
|
||||
name string
|
||||
item string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "remove item exists in slice",
|
||||
item: "foo",
|
||||
expected: []string{"buzz", "bar"},
|
||||
},
|
||||
{
|
||||
name: "remove item exists in slice",
|
||||
item: "bar",
|
||||
expected: []string{"foo", "buzz"},
|
||||
},
|
||||
{
|
||||
name: "remove item not exist in slice",
|
||||
item: "fizz",
|
||||
expected: []string{"foo", "bar", "buzz"},
|
||||
},
|
||||
{
|
||||
name: "remove empty item",
|
||||
item: "",
|
||||
expected: []string{"foo", "bar", "buzz"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
cpy := make([]string, len(list))
|
||||
copy(cpy, list)
|
||||
actual := remove(cpy, tt.item)
|
||||
assert.Equal(len(tt.expected), len(actual))
|
||||
assert.Equal(tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user