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