1/*
2Copyright 2021 The terraform-docs Authors.
3
4Licensed under the MIT license (the "License"); you may not
5use this file except in compliance with the License.
6
7You may obtain a copy of the License at the LICENSE file in
8the root directory of this source tree.
9*/
10
11package main
12
13import (
14	"bytes"
15	"fmt"
16	"io"
17	"log"
18	"os"
19	"path/filepath"
20	"strings"
21	"text/template"
22
23	"github.com/spf13/cobra"
24
25	"github.com/terraform-docs/terraform-docs/cmd"
26	"github.com/terraform-docs/terraform-docs/format"
27	"github.com/terraform-docs/terraform-docs/print"
28	"github.com/terraform-docs/terraform-docs/terraform"
29)
30
31// These are practiaclly a copy/paste of https://github.com/spf13/cobra/blob/master/doc/md_docs.go
32// The reason we've decided to bring them over and not use them directly
33// from cobra module was that we wanted to inject custom "Example" section
34// with generated output based on the "examples" folder.
35
36var (
37	baseWeight = 950
38)
39
40func main() {
41	if err := generate(cmd.NewCommand(), baseWeight, "terraform-docs"); err != nil {
42		log.Fatal(err)
43	}
44}
45
46func ignore(cmd *cobra.Command) bool {
47	switch {
48	case !cmd.IsAvailableCommand():
49		return true
50	case cmd.IsAdditionalHelpTopicCommand():
51		return true
52	case cmd.Annotations["kind"] == "":
53		return true
54	case cmd.Annotations["kind"] != "formatter":
55		return true
56	}
57	return false
58}
59
60func generate(cmd *cobra.Command, weight int, basename string) error {
61	for _, c := range cmd.Commands() {
62		if ignore(c) {
63			continue
64		}
65		b := extractFilename(c.CommandPath())
66		baseWeight++
67		if err := generate(c, baseWeight, b); err != nil {
68			return err
69		}
70	}
71
72	filename := filepath.Join("docs", "reference", basename+".md")
73	f, err := os.Create(filename)
74	if err != nil {
75		return err
76	}
77	defer f.Close() //nolint:errcheck,gosec
78
79	if _, err := io.WriteString(f, ""); err != nil {
80		return err
81	}
82	if err := generateMarkdown(cmd, weight, f); err != nil {
83		return err
84	}
85	return nil
86}
87
88type reference struct {
89	Name             string
90	Command          string
91	Description      string
92	Parent           string
93	Synopsis         string
94	Runnable         bool
95	HasChildren      bool
96	UseLine          string
97	Options          string
98	InheritedOptions string
99	Usage            string
100	Example          string
101	Subcommands      []command
102	Weight           int
103}
104
105type command struct {
106	Name     string
107	Link     string
108	Children []command
109}
110
111func generateMarkdown(cmd *cobra.Command, weight int, w io.Writer) error {
112	cmd.InitDefaultHelpCmd()
113	cmd.InitDefaultHelpFlag()
114
115	command := cmd.CommandPath()
116	name := strings.ReplaceAll(command, "terraform-docs ", "")
117
118	short := cmd.Short
119	long := cmd.Long
120
121	if len(long) == 0 {
122		long = short
123	}
124
125	parent := "reference"
126	if cmd.Parent() != nil {
127		parent = cmd.Parent().Name()
128	}
129
130	ref := &reference{
131		Name:        name,
132		Command:     command,
133		Description: short,
134		Parent:      parent,
135		Synopsis:    long,
136		Runnable:    cmd.Runnable(),
137		HasChildren: len(cmd.Commands()) > 0,
138		UseLine:     cmd.UseLine(),
139		Weight:      weight,
140	}
141
142	// Options
143	if f := cmd.NonInheritedFlags(); f.HasAvailableFlags() {
144		ref.Options = f.FlagUsages()
145	}
146
147	// Inherited Options
148	if f := cmd.InheritedFlags(); f.HasAvailableFlags() {
149		ref.InheritedOptions = f.FlagUsages()
150	}
151
152	if ref.HasChildren {
153		subcommands(ref, cmd.Commands())
154	} else {
155		example(ref) //nolint:errcheck,gosec
156	}
157
158	file := "format.tmpl"
159	paths := []string{filepath.Join("scripts", "docs", file)}
160
161	t := template.Must(template.New(file).ParseFiles(paths...))
162
163	return t.Execute(w, ref)
164}
165
166func example(ref *reference) error {
167	flag := " --footer-from footer.md"
168	if ref.Name == "pretty" {
169		flag += " --no-color"
170	}
171
172	ref.Usage = fmt.Sprintf("%s%s ./examples/", ref.Command, flag)
173
174	config := print.DefaultConfig()
175	config.ModuleRoot = "./examples"
176	config.Formatter = ref.Name
177	config.Settings.Color = false
178	config.Sections.Show = append(config.Sections.Show, "all")
179	config.Sections.Footer = true
180	config.FooterFrom = "footer.md"
181	config.Parse()
182
183	tfmodule, err := terraform.LoadWithOptions(config)
184	if err != nil {
185		log.Fatal(err)
186	}
187
188	formatter, err := format.New(config)
189	if err != nil {
190		return err
191	}
192
193	if err := formatter.Generate(tfmodule); err != nil {
194		return err
195	}
196
197	segments := strings.Split(formatter.Content(), "\n")
198	buf := new(bytes.Buffer)
199	for _, s := range segments {
200		if s == "" {
201			buf.WriteString("\n")
202		} else {
203			buf.WriteString(fmt.Sprintf("    %s\n", s))
204		}
205	}
206	ref.Example = buf.String()
207
208	return nil
209}
210
211func subcommands(ref *reference, children []*cobra.Command) {
212	subs := []command{}
213	for _, child := range children {
214		if ignore(child) {
215			continue
216		}
217		subchild := []command{}
218		for _, c := range child.Commands() {
219			if ignore(c) {
220				continue
221			}
222			cname := c.CommandPath()
223			link := extractFilename(cname)
224			subchild = append(subchild, command{Name: cname, Link: link})
225		}
226		cname := child.CommandPath()
227		link := extractFilename(cname)
228		subs = append(subs, command{Name: cname, Link: link, Children: subchild})
229	}
230	ref.Subcommands = subs
231}
232
233func extractFilename(s string) string {
234	s = strings.ReplaceAll(s, " ", "-")
235	s = strings.ReplaceAll(s, "terraform-docs-", "")
236	return s
237}
238