yaml_docs.go

  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
 14package doc
 15
 16import (
 17	"fmt"
 18	"io"
 19	"os"
 20	"path/filepath"
 21	"sort"
 22	"strings"
 23
 24	"github.com/spf13/cobra"
 25	"github.com/spf13/pflag"
 26	"gopkg.in/yaml.v2"
 27)
 28
 29type cmdOption struct {
 30	Name         string
 31	Shorthand    string `yaml:",omitempty"`
 32	DefaultValue string `yaml:"default_value,omitempty"`
 33	Usage        string `yaml:",omitempty"`
 34}
 35
 36type cmdDoc struct {
 37	Name             string
 38	Synopsis         string      `yaml:",omitempty"`
 39	Description      string      `yaml:",omitempty"`
 40	Options          []cmdOption `yaml:",omitempty"`
 41	InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
 42	Example          string      `yaml:",omitempty"`
 43	SeeAlso          []string    `yaml:"see_also,omitempty"`
 44}
 45
 46// GenYamlTree creates yaml structured ref files for this command and all descendants
 47// in the directory given. This function may not work
 48// correctly if your command names have `-` in them. If you have `cmd` with two
 49// subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
 50// it is undefined which help output will be in the file `cmd-sub-third.1`.
 51func GenYamlTree(cmd *cobra.Command, dir string) error {
 52	identity := func(s string) string { return s }
 53	emptyStr := func(s string) string { return "" }
 54	return GenYamlTreeCustom(cmd, dir, emptyStr, identity)
 55}
 56
 57// GenYamlTreeCustom creates yaml structured ref files.
 58func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
 59	for _, c := range cmd.Commands() {
 60		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
 61			continue
 62		}
 63		if err := GenYamlTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
 64			return err
 65		}
 66	}
 67
 68	basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml"
 69	filename := filepath.Join(dir, basename)
 70	f, err := os.Create(filename)
 71	if err != nil {
 72		return err
 73	}
 74	defer f.Close()
 75
 76	if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
 77		return err
 78	}
 79	if err := GenYamlCustom(cmd, f, linkHandler); err != nil {
 80		return err
 81	}
 82	return nil
 83}
 84
 85// GenYaml creates yaml output.
 86func GenYaml(cmd *cobra.Command, w io.Writer) error {
 87	return GenYamlCustom(cmd, w, func(s string) string { return s })
 88}
 89
 90// GenYamlCustom creates custom yaml output.
 91func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
 92	cmd.InitDefaultHelpCmd()
 93	cmd.InitDefaultHelpFlag()
 94
 95	yamlDoc := cmdDoc{}
 96	yamlDoc.Name = cmd.CommandPath()
 97
 98	yamlDoc.Synopsis = forceMultiLine(cmd.Short)
 99	yamlDoc.Description = forceMultiLine(cmd.Long)
100
101	if len(cmd.Example) > 0 {
102		yamlDoc.Example = cmd.Example
103	}
104
105	flags := cmd.NonInheritedFlags()
106	if flags.HasFlags() {
107		yamlDoc.Options = genFlagResult(flags)
108	}
109	flags = cmd.InheritedFlags()
110	if flags.HasFlags() {
111		yamlDoc.InheritedOptions = genFlagResult(flags)
112	}
113
114	if hasSeeAlso(cmd) {
115		result := []string{}
116		if cmd.HasParent() {
117			parent := cmd.Parent()
118			result = append(result, parent.CommandPath()+" - "+parent.Short)
119		}
120		children := cmd.Commands()
121		sort.Sort(byName(children))
122		for _, child := range children {
123			if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() {
124				continue
125			}
126			result = append(result, child.Name()+" - "+child.Short)
127		}
128		yamlDoc.SeeAlso = result
129	}
130
131	final, err := yaml.Marshal(&yamlDoc)
132	if err != nil {
133		fmt.Println(err)
134		os.Exit(1)
135	}
136
137	if _, err := w.Write(final); err != nil {
138		return err
139	}
140	return nil
141}
142
143func genFlagResult(flags *pflag.FlagSet) []cmdOption {
144	var result []cmdOption
145
146	flags.VisitAll(func(flag *pflag.Flag) {
147		// Todo, when we mark a shorthand is deprecated, but specify an empty message.
148		// The flag.ShorthandDeprecated is empty as the shorthand is deprecated.
149		// Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok.
150		if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 {
151			opt := cmdOption{
152				flag.Name,
153				flag.Shorthand,
154				flag.DefValue,
155				forceMultiLine(flag.Usage),
156			}
157			result = append(result, opt)
158		} else {
159			opt := cmdOption{
160				Name:         flag.Name,
161				DefaultValue: forceMultiLine(flag.DefValue),
162				Usage:        forceMultiLine(flag.Usage),
163			}
164			result = append(result, opt)
165		}
166	})
167
168	return result
169}