yaml_docs.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2016 French Ben. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. package doc
  14. import (
  15. "fmt"
  16. "io"
  17. "os"
  18. "path/filepath"
  19. "sort"
  20. "strings"
  21. "github.com/spf13/cobra"
  22. "github.com/spf13/pflag"
  23. "gopkg.in/yaml.v2"
  24. )
  25. type cmdOption struct {
  26. Name string
  27. Shorthand string `yaml:",omitempty"`
  28. DefaultValue string `yaml:"default_value,omitempty"`
  29. Usage string `yaml:",omitempty"`
  30. }
  31. type cmdDoc struct {
  32. Name string
  33. Synopsis string `yaml:",omitempty"`
  34. Description string `yaml:",omitempty"`
  35. Options []cmdOption `yaml:",omitempty"`
  36. InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
  37. Example string `yaml:",omitempty"`
  38. SeeAlso []string `yaml:"see_also,omitempty"`
  39. }
  40. // GenYamlTree creates yaml structured ref files for this command and all descendants
  41. // in the directory given. This function may not work
  42. // correctly if your command names have `-` in them. If you have `cmd` with two
  43. // subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
  44. // it is undefined which help output will be in the file `cmd-sub-third.1`.
  45. func GenYamlTree(cmd *cobra.Command, dir string) error {
  46. identity := func(s string) string { return s }
  47. emptyStr := func(s string) string { return "" }
  48. return GenYamlTreeCustom(cmd, dir, emptyStr, identity)
  49. }
  50. // GenYamlTreeCustom creates yaml structured ref files.
  51. func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
  52. for _, c := range cmd.Commands() {
  53. if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
  54. continue
  55. }
  56. if err := GenYamlTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
  57. return err
  58. }
  59. }
  60. basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml"
  61. filename := filepath.Join(dir, basename)
  62. f, err := os.Create(filename)
  63. if err != nil {
  64. return err
  65. }
  66. defer f.Close()
  67. if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
  68. return err
  69. }
  70. if err := GenYamlCustom(cmd, f, linkHandler); err != nil {
  71. return err
  72. }
  73. return nil
  74. }
  75. // GenYaml creates yaml output.
  76. func GenYaml(cmd *cobra.Command, w io.Writer) error {
  77. return GenYamlCustom(cmd, w, func(s string) string { return s })
  78. }
  79. // GenYamlCustom creates custom yaml output.
  80. func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
  81. cmd.InitDefaultHelpCmd()
  82. cmd.InitDefaultHelpFlag()
  83. yamlDoc := cmdDoc{}
  84. yamlDoc.Name = cmd.CommandPath()
  85. yamlDoc.Synopsis = forceMultiLine(cmd.Short)
  86. yamlDoc.Description = forceMultiLine(cmd.Long)
  87. if len(cmd.Example) > 0 {
  88. yamlDoc.Example = cmd.Example
  89. }
  90. flags := cmd.NonInheritedFlags()
  91. if flags.HasFlags() {
  92. yamlDoc.Options = genFlagResult(flags)
  93. }
  94. flags = cmd.InheritedFlags()
  95. if flags.HasFlags() {
  96. yamlDoc.InheritedOptions = genFlagResult(flags)
  97. }
  98. if hasSeeAlso(cmd) {
  99. result := []string{}
  100. if cmd.HasParent() {
  101. parent := cmd.Parent()
  102. result = append(result, parent.CommandPath()+" - "+parent.Short)
  103. }
  104. children := cmd.Commands()
  105. sort.Sort(byName(children))
  106. for _, child := range children {
  107. if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
  108. continue
  109. }
  110. result = append(result, child.Name()+" - "+child.Short)
  111. }
  112. yamlDoc.SeeAlso = result
  113. }
  114. final, err := yaml.Marshal(&yamlDoc)
  115. if err != nil {
  116. fmt.Println(err)
  117. os.Exit(1)
  118. }
  119. if _, err := w.Write(final); err != nil {
  120. return err
  121. }
  122. return nil
  123. }
  124. func genFlagResult(flags *pflag.FlagSet) []cmdOption {
  125. var result []cmdOption
  126. flags.VisitAll(func(flag *pflag.Flag) {
  127. // Todo, when we mark a shorthand is deprecated, but specify an empty message.
  128. // The flag.ShorthandDeprecated is empty as the shorthand is deprecated.
  129. // Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok.
  130. if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 {
  131. opt := cmdOption{
  132. flag.Name,
  133. flag.Shorthand,
  134. flag.DefValue,
  135. forceMultiLine(flag.Usage),
  136. }
  137. result = append(result, opt)
  138. } else {
  139. opt := cmdOption{
  140. Name: flag.Name,
  141. DefaultValue: forceMultiLine(flag.DefValue),
  142. Usage: forceMultiLine(flag.Usage),
  143. }
  144. result = append(result, opt)
  145. }
  146. })
  147. return result
  148. }