github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/docs/yaml/yaml.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/spf13/cobra" 12 "github.com/spf13/pflag" 13 yaml "gopkg.in/yaml.v2" 14 ) 15 16 type cmdOption struct { 17 Option string 18 Shorthand string `yaml:",omitempty"` 19 ValueType string `yaml:"value_type,omitempty"` 20 DefaultValue string `yaml:"default_value,omitempty"` 21 Description string `yaml:",omitempty"` 22 Deprecated bool 23 MinAPIVersion string `yaml:"min_api_version,omitempty"` 24 Experimental bool 25 ExperimentalCLI bool 26 Kubernetes bool 27 Swarm bool 28 OSType string `yaml:"os_type,omitempty"` 29 } 30 31 type cmdDoc struct { 32 Name string `yaml:"command"` 33 SeeAlso []string `yaml:"parent,omitempty"` 34 Version string `yaml:"engine_version,omitempty"` 35 Aliases string `yaml:",omitempty"` 36 Short string `yaml:",omitempty"` 37 Long string `yaml:",omitempty"` 38 Usage string `yaml:",omitempty"` 39 Pname string `yaml:",omitempty"` 40 Plink string `yaml:",omitempty"` 41 Cname []string `yaml:",omitempty"` 42 Clink []string `yaml:",omitempty"` 43 Options []cmdOption `yaml:",omitempty"` 44 InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"` 45 Example string `yaml:"examples,omitempty"` 46 Deprecated bool 47 MinAPIVersion string `yaml:"min_api_version,omitempty"` 48 Experimental bool 49 ExperimentalCLI bool 50 Kubernetes bool 51 Swarm bool 52 OSType string `yaml:"os_type,omitempty"` 53 } 54 55 // GenYamlTree creates yaml structured ref files 56 func GenYamlTree(cmd *cobra.Command, dir string) error { 57 emptyStr := func(s string) string { return "" } 58 return GenYamlTreeCustom(cmd, dir, emptyStr) 59 } 60 61 // GenYamlTreeCustom creates yaml structured ref files 62 func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string) error { 63 for _, c := range cmd.Commands() { 64 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { 65 continue 66 } 67 if err := GenYamlTreeCustom(c, dir, filePrepender); err != nil { 68 return err 69 } 70 } 71 if !cmd.HasParent() { 72 return nil 73 } 74 75 basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml" 76 filename := filepath.Join(dir, basename) 77 f, err := os.Create(filename) 78 if err != nil { 79 return err 80 } 81 defer f.Close() 82 83 if _, err := io.WriteString(f, filePrepender(filename)); err != nil { 84 return err 85 } 86 return GenYamlCustom(cmd, f) 87 } 88 89 // GenYamlCustom creates custom yaml output 90 // nolint: gocyclo 91 func GenYamlCustom(cmd *cobra.Command, w io.Writer) error { 92 cliDoc := cmdDoc{} 93 cliDoc.Name = cmd.CommandPath() 94 95 cliDoc.Aliases = strings.Join(cmd.Aliases, ", ") 96 cliDoc.Short = cmd.Short 97 cliDoc.Long = cmd.Long 98 if len(cliDoc.Long) == 0 { 99 cliDoc.Long = cliDoc.Short 100 } 101 102 if cmd.Runnable() { 103 cliDoc.Usage = cmd.UseLine() 104 } 105 106 if len(cmd.Example) > 0 { 107 cliDoc.Example = cmd.Example 108 } 109 if len(cmd.Deprecated) > 0 { 110 cliDoc.Deprecated = true 111 } 112 // Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack` 113 for curr := cmd; curr != nil; curr = curr.Parent() { 114 if v, ok := curr.Annotations["version"]; ok && cliDoc.MinAPIVersion == "" { 115 cliDoc.MinAPIVersion = v 116 } 117 if _, ok := curr.Annotations["experimental"]; ok && !cliDoc.Experimental { 118 cliDoc.Experimental = true 119 } 120 if _, ok := curr.Annotations["experimentalCLI"]; ok && !cliDoc.ExperimentalCLI { 121 cliDoc.ExperimentalCLI = true 122 } 123 if _, ok := curr.Annotations["kubernetes"]; ok && !cliDoc.Kubernetes { 124 cliDoc.Kubernetes = true 125 } 126 if _, ok := curr.Annotations["swarm"]; ok && !cliDoc.Swarm { 127 cliDoc.Swarm = true 128 } 129 if os, ok := curr.Annotations["ostype"]; ok && cliDoc.OSType == "" { 130 cliDoc.OSType = os 131 } 132 } 133 134 flags := cmd.NonInheritedFlags() 135 if flags.HasFlags() { 136 cliDoc.Options = genFlagResult(flags) 137 } 138 flags = cmd.InheritedFlags() 139 if flags.HasFlags() { 140 cliDoc.InheritedOptions = genFlagResult(flags) 141 } 142 143 if hasSeeAlso(cmd) { 144 if cmd.HasParent() { 145 parent := cmd.Parent() 146 cliDoc.Pname = parent.CommandPath() 147 link := cliDoc.Pname + ".yaml" 148 cliDoc.Plink = strings.Replace(link, " ", "_", -1) 149 cmd.VisitParents(func(c *cobra.Command) { 150 if c.DisableAutoGenTag { 151 cmd.DisableAutoGenTag = c.DisableAutoGenTag 152 } 153 }) 154 } 155 156 children := cmd.Commands() 157 sort.Sort(byName(children)) 158 159 for _, child := range children { 160 if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() { 161 continue 162 } 163 currentChild := cliDoc.Name + " " + child.Name() 164 cliDoc.Cname = append(cliDoc.Cname, cliDoc.Name+" "+child.Name()) 165 link := currentChild + ".yaml" 166 cliDoc.Clink = append(cliDoc.Clink, strings.Replace(link, " ", "_", -1)) 167 } 168 } 169 170 final, err := yaml.Marshal(&cliDoc) 171 if err != nil { 172 fmt.Println(err) 173 os.Exit(1) 174 } 175 if _, err := fmt.Fprintln(w, string(final)); err != nil { 176 return err 177 } 178 return nil 179 } 180 181 func genFlagResult(flags *pflag.FlagSet) []cmdOption { 182 var ( 183 result []cmdOption 184 opt cmdOption 185 ) 186 187 flags.VisitAll(func(flag *pflag.Flag) { 188 opt = cmdOption{ 189 Option: flag.Name, 190 ValueType: flag.Value.Type(), 191 DefaultValue: forceMultiLine(flag.DefValue), 192 Description: forceMultiLine(flag.Usage), 193 Deprecated: len(flag.Deprecated) > 0, 194 } 195 196 // Todo, when we mark a shorthand is deprecated, but specify an empty message. 197 // The flag.ShorthandDeprecated is empty as the shorthand is deprecated. 198 // Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok. 199 if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 { 200 opt.Shorthand = flag.Shorthand 201 } 202 if _, ok := flag.Annotations["experimental"]; ok { 203 opt.Experimental = true 204 } 205 if v, ok := flag.Annotations["version"]; ok { 206 opt.MinAPIVersion = v[0] 207 } 208 if _, ok := flag.Annotations["experimentalCLI"]; ok { 209 opt.ExperimentalCLI = true 210 } 211 if _, ok := flag.Annotations["kubernetes"]; ok { 212 opt.Kubernetes = true 213 } 214 if _, ok := flag.Annotations["swarm"]; ok { 215 opt.Swarm = true 216 } 217 218 // Note that the annotation can have multiple ostypes set, however, multiple 219 // values are currently not used (and unlikely will). 220 // 221 // To simplify usage of the os_type property in the YAML, and for consistency 222 // with the same property for commands, we're only using the first ostype that's set. 223 if ostypes, ok := flag.Annotations["ostype"]; ok && len(opt.OSType) == 0 && len(ostypes) > 0 { 224 opt.OSType = ostypes[0] 225 } 226 227 result = append(result, opt) 228 }) 229 230 return result 231 } 232 233 // Temporary workaround for yaml lib generating incorrect yaml with long strings 234 // that do not contain \n. 235 func forceMultiLine(s string) string { 236 if len(s) > 60 && !strings.Contains(s, "\n") { 237 s = s + "\n" 238 } 239 return s 240 } 241 242 // Small duplication for cobra utils 243 func hasSeeAlso(cmd *cobra.Command) bool { 244 if cmd.HasParent() { 245 return true 246 } 247 for _, c := range cmd.Commands() { 248 if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { 249 continue 250 } 251 return true 252 } 253 return false 254 } 255 256 func parseMDContent(mdString string) (description string, examples string) { 257 parsedContent := strings.Split(mdString, "\n## ") 258 for _, s := range parsedContent { 259 if strings.Index(s, "Description") == 0 { 260 description = strings.TrimSpace(strings.TrimPrefix(s, "Description")) 261 } 262 if strings.Index(s, "Examples") == 0 { 263 examples = strings.TrimSpace(strings.TrimPrefix(s, "Examples")) 264 } 265 } 266 return description, examples 267 } 268 269 type byName []*cobra.Command 270 271 func (s byName) Len() int { return len(s) } 272 func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 273 func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }