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() }