Skip to content

Commit

Permalink
Add subcategory support to the generate command
Browse files Browse the repository at this point in the history
This patch adds a --subcategory flag that let users give a mapping that
will use to set the `subcategory` field in the documentation:

	tfplugindocs generate --subcategory consul_acl=ACL --subcategory consul_admin="Admin Partition"

The format for the flag is `prefix="Sub Category"` so all resources and
datasources starting with `consul_acl` like `consul_acl_policy`,
`consul_acl_token`, etc. will have the `subcategory: "ACL"` in the
generated documentation. This is not very elegant but should work in most
cases as needed.

Closes hashicorp#156
  • Loading branch information
remilapeyre committed Nov 26, 2022
1 parent edb4eab commit 0f32f5d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 18 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Available commands are:
the generate command is run by default
generate generates a plugin website from code, templates, and examples for the current directory
validate validates a plugin website for the current directory

```

`generate` command:
Expand All @@ -39,14 +39,15 @@ $ tfplugindocs generate --help

Usage: tfplugindocs generate [<args>]

--examples-dir <ARG> examples directory (default: "examples")
--ignore-deprecated <ARG> don't generate documentation for deprecated resources and data-sources (default: "false")
--legacy-sidebar <ARG> generate the legacy .erb sidebar file (default: "false")
--examples-dir <ARG> examples directory (default: "examples")
--ignore-deprecated <ARG> don't generate documentation for deprecated resources and data-sources (default: "false")
--legacy-sidebar <ARG> generate the legacy .erb sidebar file (default: "false")
--provider-name <ARG> provider name, as used in Terraform configurations
--rendered-provider-name <ARG> provider name, as generated in documentation (ex. page titles, ...)
--rendered-website-dir <ARG> output directory (default: "docs")
--rendered-website-dir <ARG> output directory (default: "docs")
--subcategory <ARG> an optional subcategory mapping to group resources, can be specified multiple time e.g. --subcategory consul_acl=ACL --subcategory consul_admin="Admin Partition"
--tf-version <ARG> terraform binary version to download
--website-source-dir <ARG> templates directory (default: "templates")
--website-source-dir <ARG> templates directory (default: "templates")
--website-temp-dir <ARG> temporary directory (used during generation)
```
Expand Down Expand Up @@ -150,6 +151,7 @@ using the following data fields and functions:
|------------------------:|:------:|-------------------------------------------------------------------------------------------|
| `.Name` | string | Name of the resource/data-source (ex. `tls_certificate`) |
| `.Type` | string | Either `Resource` or `Data Source` |
| `.SubCategory` | string | The subcategory for this resource or an empty string if unset |
| `.Description` | string | Resource / Data Source description |
| `.HasExample` | bool | Is there an example file? |
| `.ExampleFile` | string | Path to the file with the terraform configuration example |
Expand Down
7 changes: 6 additions & 1 deletion internal/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type generateCmd struct {
flagWebsiteTmpDir string
flagWebsiteSourceDir string
tfVersion string
flagSubcategory provider.SubCategories
}

func (cmd *generateCmd) Synopsis() string {
Expand All @@ -42,7 +43,7 @@ func (cmd *generateCmd) Help() string {
}
})

strBuilder.WriteString(fmt.Sprintf("\nUsage: tfplugindocs generate [<args>]\n\n"))
strBuilder.WriteString("\nUsage: tfplugindocs generate [<args>]\n\n")
cmd.Flags().VisitAll(func(f *flag.Flag) {
if f.DefValue != "" {
strBuilder.WriteString(fmt.Sprintf(" --%s <ARG> %s%s%s (default: %q)\n",
Expand All @@ -67,6 +68,8 @@ func (cmd *generateCmd) Help() string {
}

func (cmd *generateCmd) Flags() *flag.FlagSet {
cmd.flagSubcategory = provider.SubCategories{}

fs := flag.NewFlagSet("generate", flag.ExitOnError)
fs.BoolVar(&cmd.flagLegacySidebar, "legacy-sidebar", false, "generate the legacy .erb sidebar file")
fs.StringVar(&cmd.flagProviderName, "provider-name", "", "provider name, as used in Terraform configurations")
Expand All @@ -77,6 +80,7 @@ func (cmd *generateCmd) Flags() *flag.FlagSet {
fs.StringVar(&cmd.flagWebsiteSourceDir, "website-source-dir", "templates", "templates directory")
fs.StringVar(&cmd.tfVersion, "tf-version", "", "terraform binary version to download")
fs.BoolVar(&cmd.flagIgnoreDeprecated, "ignore-deprecated", false, "don't generate documentation for deprecated resources and data-sources")
fs.Var(&cmd.flagSubcategory, "subcategory", "an optional subcategory mapping to group resources, can be specified multiple time e.g. --subcategory consul_acl=ACL --subcategory consul_admin=\"Admin Partition\"")
return fs
}

Expand All @@ -103,6 +107,7 @@ func (cmd *generateCmd) runInternal() error {
cmd.flagWebsiteSourceDir,
cmd.tfVersion,
cmd.flagIgnoreDeprecated,
cmd.flagSubcategory,
)
if err != nil {
return fmt.Errorf("unable to generate website: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (cmd *validateCmd) Help() string {
}
})

strBuilder.WriteString(fmt.Sprintf("\nUsage: tfplugindocs validate [<args>]\n\n"))
strBuilder.WriteString("\nUsage: tfplugindocs validate [<args>]\n\n")
cmd.Flags().VisitAll(func(f *flag.Flag) {
if f.DefValue != "" {
strBuilder.WriteString(fmt.Sprintf(" --%s <ARG> %s%s%s (default: %q)\n",
Expand Down
66 changes: 59 additions & 7 deletions internal/provider/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,51 @@ type generator struct {
websiteTmpDir string
websiteSourceDir string

subcategories SubCategories

ui cli.Ui
}

type SubCategories map[string]string

func (s *SubCategories) String() string {
if s == nil {
return ""
}

var subcategories []string
for k, v := range *s {
subcategories = append(subcategories, fmt.Sprintf("%s=%q", k, v))
}

return strings.Join(subcategories, ", ")
}

func (s *SubCategories) Set(v string) error {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("wrong format for %q, expected prefix=\"SubCategory Name\"", v)
}

prefix := parts[0]
if sc, found := (*s)[prefix]; found {
return fmt.Errorf("%s is already registered with subcategory %q", prefix, sc)
}

(*s)[prefix] = parts[1]
return nil
}

func (s *SubCategories) Get(name string) string {
for k, v := range *s {
if strings.HasPrefix(name, k) {
return v
}
}

return ""
}

func (g *generator) infof(format string, a ...interface{}) {
g.ui.Info(fmt.Sprintf(format, a...))
}
Expand All @@ -86,7 +128,7 @@ func (g *generator) warnf(format string, a ...interface{}) {
g.ui.Warn(fmt.Sprintf(format, a...))
}

func Generate(ui cli.Ui, legacySidebar bool, providerName, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, websiteSourceDir, tfVersion string, ignoreDeprecated bool) error {
func Generate(ui cli.Ui, legacySidebar bool, providerName, renderedProviderName, renderedWebsiteDir, examplesDir, websiteTmpDir, websiteSourceDir, tfVersion string, ignoreDeprecated bool, subcategories SubCategories) error {
g := &generator{
ignoreDeprecated: ignoreDeprecated,
legacySidebar: legacySidebar,
Expand All @@ -99,6 +141,8 @@ func Generate(ui cli.Ui, legacySidebar bool, providerName, renderedProviderName,
websiteTmpDir: websiteTmpDir,
websiteSourceDir: websiteSourceDir,

subcategories: subcategories,

ui: ui,
}

Expand Down Expand Up @@ -192,7 +236,7 @@ func (g *generator) Generate(ctx context.Context) error {
return nil
}

func (g *generator) renderMissingResourceDoc(providerName, name, typeName string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error {
func (g *generator) renderMissingResourceDoc(providerName, name, typeName, subCategory string, schema *tfjson.Schema, websiteFileTemplate resourceFileTemplate, fallbackWebsiteFileTemplate resourceFileTemplate, websiteStaticCandidateTemplates []resourceFileTemplate, examplesFileTemplate resourceFileTemplate, examplesImportTemplate *resourceFileTemplate) error {
tmplPath, err := websiteFileTemplate.Render(name, providerName)
if err != nil {
return fmt.Errorf("unable to render path for resource %q: %w", name, err)
Expand Down Expand Up @@ -257,7 +301,7 @@ func (g *generator) renderMissingResourceDoc(providerName, name, typeName string
}

g.infof("generating template for %q", name)
md, err := targetResourceTemplate.Render(name, providerName, g.renderedProviderName, typeName, examplePath, importPath, schema)
md, err := targetResourceTemplate.Render(name, providerName, g.renderedProviderName, typeName, subCategory, examplePath, importPath, schema)
if err != nil {
return fmt.Errorf("unable to render template for %q: %w", name, err)
}
Expand Down Expand Up @@ -325,7 +369,10 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso
continue
}

err := g.renderMissingResourceDoc(providerName, name, "Resource", schema,
subCategory := g.subcategories.Get(name)

err := g.renderMissingResourceDoc(providerName, name, "Resource", subCategory,
schema,
websiteResourceFileTemplate,
websiteResourceFallbackFileTemplate,
websiteResourceFileStatic,
Expand All @@ -342,7 +389,10 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso
continue
}

err := g.renderMissingResourceDoc(providerName, name, "Data Source", schema,
subCategory := g.subcategories.Get(name)

err := g.renderMissingResourceDoc(providerName, name, "Data Source", subCategory,
schema,
websiteDataSourceFileTemplate,
websiteDataSourceFallbackFileTemplate,
websiteDataSourceFileStatic,
Expand Down Expand Up @@ -425,10 +475,11 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
switch relDir {
case "data-sources/":
resSchema, resName := resourceSchema(providerSchema.DataSourceSchemas, shortName, relFile)
subCategory := g.subcategories.Get(resName)
exampleFilePath := filepath.Join(g.examplesDir, "data-sources", resName, "data-source.tf")
if resSchema != nil {
tmpl := resourceTemplate(tmplData)
render, err := tmpl.Render(resName, providerName, g.renderedProviderName, "Data Source", exampleFilePath, "", resSchema)
render, err := tmpl.Render(resName, providerName, g.renderedProviderName, "Data Source", subCategory, exampleFilePath, "", resSchema)
if err != nil {
return fmt.Errorf("unable to render data source template %q: %w", rel, err)
}
Expand All @@ -441,12 +492,13 @@ func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfj
g.warnf("data source entitled %q, or %q does not exist", shortName, resName)
case "resources/":
resSchema, resName := resourceSchema(providerSchema.ResourceSchemas, shortName, relFile)
subCategory := g.subcategories.Get(resName)
exampleFilePath := filepath.Join(g.examplesDir, "resources", resName, "resource.tf")
importFilePath := filepath.Join(g.examplesDir, "resources", resName, "import.sh")

if resSchema != nil {
tmpl := resourceTemplate(tmplData)
render, err := tmpl.Render(resName, providerName, g.renderedProviderName, "Resource", exampleFilePath, importFilePath, resSchema)
render, err := tmpl.Render(resName, providerName, g.renderedProviderName, "Resource", subCategory, exampleFilePath, importFilePath, resSchema)
if err != nil {
return fmt.Errorf("unable to render resource template %q: %w", rel, err)
}
Expand Down
8 changes: 5 additions & 3 deletions internal/provider/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (t providerTemplate) Render(providerName, renderedProviderName, exampleFile
})
}

func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeName, exampleFile, importFile string, schema *tfjson.Schema) (string, error) {
func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeName, subCategory, exampleFile, importFile string, schema *tfjson.Schema) (string, error) {
schemaBuffer := bytes.NewBuffer(nil)
err := schemamd.Render(schema, schemaBuffer)
if err != nil {
Expand All @@ -181,6 +181,7 @@ func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeN
Type string
Name string
Description string
SubCategory string

HasExample bool
ExampleFile string
Expand All @@ -198,6 +199,7 @@ func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeN
Type: typeName,
Name: name,
Description: schema.Block.Description,
SubCategory: subCategory,

HasExample: exampleFile != "" && fileExists(exampleFile),
ExampleFile: exampleFile,
Expand All @@ -217,7 +219,7 @@ func (t resourceTemplate) Render(name, providerName, renderedProviderName, typeN
const defaultResourceTemplate resourceTemplate = `---
` + frontmatterComment + `
page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
subcategory: ""
subcategory: "{{.SubCategory}}"
description: |-
{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---
Expand Down Expand Up @@ -246,7 +248,7 @@ Import is supported using the following syntax:
const defaultProviderTemplate providerTemplate = `---
` + frontmatterComment + `
page_title: "{{.ProviderShortName}} Provider"
subcategory: ""
subcategory: "{{.SubCategory}}"
description: |-
{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
---
Expand Down

0 comments on commit 0f32f5d

Please sign in to comment.