1package flags 2 3import ( 4 "fmt" 5 "io" 6 "os" 7 "runtime" 8 "strconv" 9 "strings" 10 "time" 11) 12 13func manQuoteLines(s string) string { 14 lines := strings.Split(s, "\n") 15 parts := []string{} 16 17 for _, line := range lines { 18 parts = append(parts, manQuote(line)) 19 } 20 21 return strings.Join(parts, "\n") 22} 23 24func manQuote(s string) string { 25 return strings.Replace(s, "\\", "\\\\", -1) 26} 27 28func formatForMan(wr io.Writer, s string, quoter func(s string) string) { 29 for { 30 idx := strings.IndexRune(s, '`') 31 32 if idx < 0 { 33 fmt.Fprintf(wr, "%s", quoter(s)) 34 break 35 } 36 37 fmt.Fprintf(wr, "%s", quoter(s[:idx])) 38 39 s = s[idx+1:] 40 idx = strings.IndexRune(s, '\'') 41 42 if idx < 0 { 43 fmt.Fprintf(wr, "%s", quoter(s)) 44 break 45 } 46 47 fmt.Fprintf(wr, "\\fB%s\\fP", quoter(s[:idx])) 48 s = s[idx+1:] 49 } 50} 51 52func writeManPageOptions(wr io.Writer, grp *Group) { 53 grp.eachGroup(func(group *Group) { 54 if !group.showInHelp() { 55 return 56 } 57 58 // If the parent (grp) has any subgroups, display their descriptions as 59 // subsection headers similar to the output of --help. 60 if group.ShortDescription != "" && len(grp.groups) > 0 { 61 fmt.Fprintf(wr, ".SS %s\n", group.ShortDescription) 62 63 if group.LongDescription != "" { 64 formatForMan(wr, group.LongDescription, manQuoteLines) 65 fmt.Fprintln(wr, "") 66 } 67 } 68 69 for _, opt := range group.options { 70 if !opt.showInHelp() { 71 continue 72 } 73 74 fmt.Fprintln(wr, ".TP") 75 fmt.Fprintf(wr, "\\fB") 76 77 if opt.ShortName != 0 { 78 fmt.Fprintf(wr, "\\fB\\-%c\\fR", opt.ShortName) 79 } 80 81 if len(opt.LongName) != 0 { 82 if opt.ShortName != 0 { 83 fmt.Fprintf(wr, ", ") 84 } 85 86 fmt.Fprintf(wr, "\\fB\\-\\-%s\\fR", manQuote(opt.LongNameWithNamespace())) 87 } 88 89 if len(opt.ValueName) != 0 || opt.OptionalArgument { 90 if opt.OptionalArgument { 91 fmt.Fprintf(wr, " [\\fI%s=%s\\fR]", manQuote(opt.ValueName), manQuote(strings.Join(quoteV(opt.OptionalValue), ", "))) 92 } else { 93 fmt.Fprintf(wr, " \\fI%s\\fR", manQuote(opt.ValueName)) 94 } 95 } 96 97 if len(opt.Default) != 0 { 98 fmt.Fprintf(wr, " <default: \\fI%s\\fR>", manQuote(strings.Join(quoteV(opt.Default), ", "))) 99 } else if len(opt.EnvKeyWithNamespace()) != 0 { 100 if runtime.GOOS == "windows" { 101 fmt.Fprintf(wr, " <default: \\fI%%%s%%\\fR>", manQuote(opt.EnvKeyWithNamespace())) 102 } else { 103 fmt.Fprintf(wr, " <default: \\fI$%s\\fR>", manQuote(opt.EnvKeyWithNamespace())) 104 } 105 } 106 107 if opt.Required { 108 fmt.Fprintf(wr, " (\\fIrequired\\fR)") 109 } 110 111 fmt.Fprintln(wr, "\\fP") 112 113 if len(opt.Description) != 0 { 114 formatForMan(wr, opt.Description, manQuoteLines) 115 fmt.Fprintln(wr, "") 116 } 117 } 118 }) 119} 120 121func writeManPageSubcommands(wr io.Writer, name string, usagePrefix string, root *Command) { 122 commands := root.sortedVisibleCommands() 123 124 for _, c := range commands { 125 var nn string 126 127 if c.Hidden { 128 continue 129 } 130 131 if len(name) != 0 { 132 nn = name + " " + c.Name 133 } else { 134 nn = c.Name 135 } 136 137 writeManPageCommand(wr, nn, usagePrefix, c) 138 } 139} 140 141func writeManPageCommand(wr io.Writer, name string, usagePrefix string, command *Command) { 142 fmt.Fprintf(wr, ".SS %s\n", name) 143 fmt.Fprintln(wr, command.ShortDescription) 144 145 if len(command.LongDescription) > 0 { 146 fmt.Fprintln(wr, "") 147 148 cmdstart := fmt.Sprintf("The %s command", manQuote(command.Name)) 149 150 if strings.HasPrefix(command.LongDescription, cmdstart) { 151 fmt.Fprintf(wr, "The \\fI%s\\fP command", manQuote(command.Name)) 152 153 formatForMan(wr, command.LongDescription[len(cmdstart):], manQuoteLines) 154 fmt.Fprintln(wr, "") 155 } else { 156 formatForMan(wr, command.LongDescription, manQuoteLines) 157 fmt.Fprintln(wr, "") 158 } 159 } 160 161 var pre = usagePrefix + " " + command.Name 162 163 var usage string 164 if us, ok := command.data.(Usage); ok { 165 usage = us.Usage() 166 } else if command.hasHelpOptions() { 167 usage = fmt.Sprintf("[%s-OPTIONS]", command.Name) 168 } 169 170 var nextPrefix = pre 171 if len(usage) > 0 { 172 fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n.TP\n", manQuote(pre), manQuote(usage)) 173 nextPrefix = pre + " " + usage 174 } 175 176 if len(command.Aliases) > 0 { 177 fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", manQuote(strings.Join(command.Aliases, ", "))) 178 } 179 180 writeManPageOptions(wr, command.Group) 181 writeManPageSubcommands(wr, name, nextPrefix, command) 182} 183 184// WriteManPage writes a basic man page in groff format to the specified 185// writer. 186func (p *Parser) WriteManPage(wr io.Writer) { 187 t := time.Now() 188 source_date_epoch := os.Getenv("SOURCE_DATE_EPOCH") 189 if source_date_epoch != "" { 190 sde, err := strconv.ParseInt(source_date_epoch, 10, 64) 191 if err != nil { 192 panic(fmt.Sprintf("Invalid SOURCE_DATE_EPOCH: %s", err)) 193 } 194 t = time.Unix(sde, 0) 195 } 196 197 fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", manQuote(p.Name), t.Format("2 January 2006")) 198 fmt.Fprintln(wr, ".SH NAME") 199 fmt.Fprintf(wr, "%s \\- %s\n", manQuote(p.Name), manQuoteLines(p.ShortDescription)) 200 fmt.Fprintln(wr, ".SH SYNOPSIS") 201 202 usage := p.Usage 203 204 if len(usage) == 0 { 205 usage = "[OPTIONS]" 206 } 207 208 fmt.Fprintf(wr, "\\fB%s\\fP %s\n", manQuote(p.Name), manQuote(usage)) 209 fmt.Fprintln(wr, ".SH DESCRIPTION") 210 211 formatForMan(wr, p.LongDescription, manQuoteLines) 212 fmt.Fprintln(wr, "") 213 214 fmt.Fprintln(wr, ".SH OPTIONS") 215 216 writeManPageOptions(wr, p.Command.Group) 217 218 if len(p.visibleCommands()) > 0 { 219 fmt.Fprintln(wr, ".SH COMMANDS") 220 221 writeManPageSubcommands(wr, "", p.Name+" "+usage, p.Command) 222 } 223} 224