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