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