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