golang.org/x/text@v0.14.0/cmd/gotext/main.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:generate go build -o gotext.latest 6 //go:generate ./gotext.latest help gendocumentation 7 //go:generate rm gotext.latest 8 9 package main 10 11 import ( 12 "bufio" 13 "bytes" 14 "flag" 15 "fmt" 16 "go/build" 17 "go/format" 18 "io" 19 "log" 20 "os" 21 "strings" 22 "sync" 23 "text/template" 24 "unicode" 25 "unicode/utf8" 26 27 "golang.org/x/text/message/pipeline" 28 29 "golang.org/x/text/language" 30 "golang.org/x/tools/go/buildutil" 31 ) 32 33 func init() { 34 flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) 35 } 36 37 var ( 38 lang *string 39 out *string 40 overwrite *bool 41 42 srcLang = flag.String("srclang", "en-US", "the source-code language") 43 dir = flag.String("dir", "locales", "default subdirectory to store translation files") 44 ) 45 46 func config() (*pipeline.Config, error) { 47 tag, err := language.Parse(*srcLang) 48 if err != nil { 49 return nil, wrap(err, "invalid srclang") 50 } 51 return &pipeline.Config{ 52 SourceLanguage: tag, 53 Supported: getLangs(), 54 TranslationsPattern: `messages\.(.*)\.json$`, 55 GenFile: *out, 56 Dir: *dir, 57 }, nil 58 } 59 60 // NOTE: the Command struct is copied from the go tool in core. 61 62 // A Command is an implementation of a go command 63 // like go build or go fix. 64 type Command struct { 65 // Init initializes the flag set of the command. 66 Init func(cmd *Command) 67 68 // Run runs the command. 69 // The args are the arguments after the command name. 70 Run func(cmd *Command, c *pipeline.Config, args []string) error 71 72 // UsageLine is the one-line usage message. 73 // The first word in the line is taken to be the command name. 74 UsageLine string 75 76 // Short is the short description shown in the 'go help' output. 77 Short string 78 79 // Long is the long message shown in the 'go help <this-command>' output. 80 Long string 81 82 // Flag is a set of flags specific to this command. 83 Flag flag.FlagSet 84 } 85 86 // Name returns the command's name: the first word in the usage line. 87 func (c *Command) Name() string { 88 name := c.UsageLine 89 i := strings.Index(name, " ") 90 if i >= 0 { 91 name = name[:i] 92 } 93 return name 94 } 95 96 func (c *Command) Usage() { 97 fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 98 fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) 99 os.Exit(2) 100 } 101 102 // Runnable reports whether the command can be run; otherwise 103 // it is a documentation pseudo-command such as importpath. 104 func (c *Command) Runnable() bool { 105 return c.Run != nil 106 } 107 108 // Commands lists the available commands and help topics. 109 // The order here is the order in which they are printed by 'go help'. 110 var commands = []*Command{ 111 cmdUpdate, 112 cmdExtract, 113 cmdRewrite, 114 cmdGenerate, 115 // TODO: 116 // - update: full-cycle update of extraction, sending, and integration 117 // - report: report of freshness of translations 118 } 119 120 var exitStatus = 0 121 var exitMu sync.Mutex 122 123 func setExitStatus(n int) { 124 exitMu.Lock() 125 if exitStatus < n { 126 exitStatus = n 127 } 128 exitMu.Unlock() 129 } 130 131 var origEnv []string 132 133 func main() { 134 flag.Usage = usage 135 flag.Parse() 136 log.SetFlags(0) 137 138 args := flag.Args() 139 if len(args) < 1 { 140 usage() 141 } 142 143 if args[0] == "help" { 144 help(args[1:]) 145 return 146 } 147 148 for _, cmd := range commands { 149 if cmd.Name() == args[0] && cmd.Runnable() { 150 cmd.Init(cmd) 151 cmd.Flag.Usage = func() { cmd.Usage() } 152 cmd.Flag.Parse(args[1:]) 153 args = cmd.Flag.Args() 154 config, err := config() 155 if err != nil { 156 fatalf("gotext: %+v", err) 157 } 158 if err := cmd.Run(cmd, config, args); err != nil { 159 fatalf("gotext: %+v", err) 160 } 161 exit() 162 return 163 } 164 } 165 166 fmt.Fprintf(os.Stderr, "gotext: unknown subcommand %q\nRun 'go help' for usage.\n", args[0]) 167 setExitStatus(2) 168 exit() 169 } 170 171 var usageTemplate = `gotext is a tool for managing text in Go source code. 172 173 Usage: 174 175 gotext command [arguments] 176 177 The commands are: 178 {{range .}}{{if .Runnable}} 179 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 180 181 Use "gotext help [command]" for more information about a command. 182 183 Additional help topics: 184 {{range .}}{{if not .Runnable}} 185 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 186 187 Use "gotext help [topic]" for more information about that topic. 188 189 ` 190 191 var helpTemplate = `{{if .Runnable}}usage: gotext {{.UsageLine}} 192 193 {{end}}{{.Long | trim}} 194 ` 195 196 var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}} 197 198 {{end}}{{if .Runnable}}Usage: 199 200 gotext {{.UsageLine}} 201 202 {{end}}{{.Long | trim}} 203 204 205 {{end}}` 206 207 // commentWriter writes a Go comment to the underlying io.Writer, 208 // using line comment form (//). 209 type commentWriter struct { 210 W io.Writer 211 wroteSlashes bool // Wrote "//" at the beginning of the current line. 212 } 213 214 func (c *commentWriter) Write(p []byte) (int, error) { 215 var n int 216 for i, b := range p { 217 if !c.wroteSlashes { 218 s := "//" 219 if b != '\n' { 220 s = "// " 221 } 222 if _, err := io.WriteString(c.W, s); err != nil { 223 return n, err 224 } 225 c.wroteSlashes = true 226 } 227 n0, err := c.W.Write(p[i : i+1]) 228 n += n0 229 if err != nil { 230 return n, err 231 } 232 if b == '\n' { 233 c.wroteSlashes = false 234 } 235 } 236 return len(p), nil 237 } 238 239 // An errWriter wraps a writer, recording whether a write error occurred. 240 type errWriter struct { 241 w io.Writer 242 err error 243 } 244 245 func (w *errWriter) Write(b []byte) (int, error) { 246 n, err := w.w.Write(b) 247 if err != nil { 248 w.err = err 249 } 250 return n, err 251 } 252 253 // tmpl executes the given template text on data, writing the result to w. 254 func tmpl(w io.Writer, text string, data interface{}) { 255 t := template.New("top") 256 t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 257 template.Must(t.Parse(text)) 258 ew := &errWriter{w: w} 259 err := t.Execute(ew, data) 260 if ew.err != nil { 261 // I/O error writing. Ignore write on closed pipe. 262 if strings.Contains(ew.err.Error(), "pipe") { 263 os.Exit(1) 264 } 265 fatalf("writing output: %v", ew.err) 266 } 267 if err != nil { 268 panic(err) 269 } 270 } 271 272 func capitalize(s string) string { 273 if s == "" { 274 return s 275 } 276 r, n := utf8.DecodeRuneInString(s) 277 return string(unicode.ToTitle(r)) + s[n:] 278 } 279 280 func printUsage(w io.Writer) { 281 bw := bufio.NewWriter(w) 282 tmpl(bw, usageTemplate, commands) 283 bw.Flush() 284 } 285 286 func usage() { 287 printUsage(os.Stderr) 288 os.Exit(2) 289 } 290 291 // help implements the 'help' command. 292 func help(args []string) { 293 if len(args) == 0 { 294 printUsage(os.Stdout) 295 // not exit 2: succeeded at 'go help'. 296 return 297 } 298 if len(args) != 1 { 299 fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n") 300 os.Exit(2) // failed at 'go help' 301 } 302 303 arg := args[0] 304 305 // 'go help documentation' generates doc.go. 306 if strings.HasSuffix(arg, "documentation") { 307 w := &bytes.Buffer{} 308 309 fmt.Fprintln(w, "// Code generated by go generate. DO NOT EDIT.") 310 fmt.Fprintln(w) 311 buf := new(bytes.Buffer) 312 printUsage(buf) 313 usage := &Command{Long: buf.String()} 314 tmpl(&commentWriter{W: w}, documentationTemplate, append([]*Command{usage}, commands...)) 315 fmt.Fprintln(w, "package main") 316 if arg == "gendocumentation" { 317 b, err := format.Source(w.Bytes()) 318 if err != nil { 319 logf("Could not format generated docs: %v\n", err) 320 } 321 if err := os.WriteFile("doc.go", b, 0666); err != nil { 322 logf("Could not create file alldocs.go: %v\n", err) 323 } 324 } else { 325 fmt.Println(w.String()) 326 } 327 return 328 } 329 330 for _, cmd := range commands { 331 if cmd.Name() == arg { 332 tmpl(os.Stdout, helpTemplate, cmd) 333 // not exit 2: succeeded at 'go help cmd'. 334 return 335 } 336 } 337 338 fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'gotext help'.\n", arg) 339 os.Exit(2) // failed at 'go help cmd' 340 } 341 342 func getLangs() (tags []language.Tag) { 343 if lang == nil { 344 return []language.Tag{language.AmericanEnglish} 345 } 346 for _, t := range strings.Split(*lang, ",") { 347 if t == "" { 348 continue 349 } 350 tag, err := language.Parse(t) 351 if err != nil { 352 fatalf("gotext: could not parse language %q: %v", t, err) 353 } 354 tags = append(tags, tag) 355 } 356 return tags 357 } 358 359 var atexitFuncs []func() 360 361 func atexit(f func()) { 362 atexitFuncs = append(atexitFuncs, f) 363 } 364 365 func exit() { 366 for _, f := range atexitFuncs { 367 f() 368 } 369 os.Exit(exitStatus) 370 } 371 372 func fatalf(format string, args ...interface{}) { 373 logf(format, args...) 374 exit() 375 } 376 377 func logf(format string, args ...interface{}) { 378 log.Printf(format, args...) 379 setExitStatus(1) 380 } 381 382 func exitIfErrors() { 383 if exitStatus != 0 { 384 exit() 385 } 386 }