1
2
3
4
5
6
7
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
52
53 genFile := ""
54 if out != nil {
55 genFile = *out
56 }
57
58 return &pipeline.Config{
59 SourceLanguage: tag,
60 Supported: getLangs(),
61 TranslationsPattern: `messages\.(.*)\.json$`,
62 GenFile: genFile,
63 Dir: *dir,
64 }, nil
65 }
66
67
68
69
70
71 type Command struct {
72
73 Init func(cmd *Command)
74
75
76
77 Run func(cmd *Command, c *pipeline.Config, args []string) error
78
79
80
81 UsageLine string
82
83
84 Short string
85
86
87 Long string
88
89
90 Flag flag.FlagSet
91 }
92
93
94 func (c *Command) Name() string {
95 name := c.UsageLine
96 i := strings.Index(name, " ")
97 if i >= 0 {
98 name = name[:i]
99 }
100 return name
101 }
102
103 func (c *Command) Usage() {
104 fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
105 fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
106 os.Exit(2)
107 }
108
109
110
111 func (c *Command) Runnable() bool {
112 return c.Run != nil
113 }
114
115
116
117 var commands = []*Command{
118 cmdUpdate,
119 cmdExtract,
120 cmdRewrite,
121 cmdGenerate,
122
123
124
125 }
126
127 var exitStatus = 0
128 var exitMu sync.Mutex
129
130 func setExitStatus(n int) {
131 exitMu.Lock()
132 if exitStatus < n {
133 exitStatus = n
134 }
135 exitMu.Unlock()
136 }
137
138 var origEnv []string
139
140 func main() {
141 flag.Usage = usage
142 flag.Parse()
143 log.SetFlags(0)
144
145 args := flag.Args()
146 if len(args) < 1 {
147 usage()
148 }
149
150 if args[0] == "help" {
151 help(args[1:])
152 return
153 }
154
155 for _, cmd := range commands {
156 if cmd.Name() == args[0] && cmd.Runnable() {
157 cmd.Init(cmd)
158 cmd.Flag.Usage = func() { cmd.Usage() }
159 cmd.Flag.Parse(args[1:])
160 args = cmd.Flag.Args()
161 config, err := config()
162 if err != nil {
163 fatalf("gotext: %+v", err)
164 }
165 if err := cmd.Run(cmd, config, args); err != nil {
166 fatalf("gotext: %+v", err)
167 }
168 exit()
169 return
170 }
171 }
172
173 fmt.Fprintf(os.Stderr, "gotext: unknown subcommand %q\nRun 'go help' for usage.\n", args[0])
174 setExitStatus(2)
175 exit()
176 }
177
178 var usageTemplate = `gotext is a tool for managing text in Go source code.
179
180 Usage:
181
182 gotext command [arguments]
183
184 The commands are:
185 {{range .}}{{if .Runnable}}
186 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
187
188 Use "gotext help [command]" for more information about a command.
189
190 Additional help topics:
191 {{range .}}{{if not .Runnable}}
192 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
193
194 Use "gotext help [topic]" for more information about that topic.
195
196 `
197
198 var helpTemplate = `{{if .Runnable}}usage: gotext {{.UsageLine}}
199
200 {{end}}{{.Long | trim}}
201 `
202
203 var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}}
204
205 {{end}}{{if .Runnable}}Usage:
206
207 gotext {{.UsageLine}}
208
209 {{end}}{{.Long | trim}}
210
211
212 {{end}}`
213
214
215
216 type commentWriter struct {
217 W io.Writer
218 wroteSlashes bool
219 }
220
221 func (c *commentWriter) Write(p []byte) (int, error) {
222 var n int
223 for i, b := range p {
224 if !c.wroteSlashes {
225 s := "//"
226 if b != '\n' {
227 s = "// "
228 }
229 if _, err := io.WriteString(c.W, s); err != nil {
230 return n, err
231 }
232 c.wroteSlashes = true
233 }
234 n0, err := c.W.Write(p[i : i+1])
235 n += n0
236 if err != nil {
237 return n, err
238 }
239 if b == '\n' {
240 c.wroteSlashes = false
241 }
242 }
243 return len(p), nil
244 }
245
246
247 type errWriter struct {
248 w io.Writer
249 err error
250 }
251
252 func (w *errWriter) Write(b []byte) (int, error) {
253 n, err := w.w.Write(b)
254 if err != nil {
255 w.err = err
256 }
257 return n, err
258 }
259
260
261 func tmpl(w io.Writer, text string, data interface{}) {
262 t := template.New("top")
263 t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
264 template.Must(t.Parse(text))
265 ew := &errWriter{w: w}
266 err := t.Execute(ew, data)
267 if ew.err != nil {
268
269 if strings.Contains(ew.err.Error(), "pipe") {
270 os.Exit(1)
271 }
272 fatalf("writing output: %v", ew.err)
273 }
274 if err != nil {
275 panic(err)
276 }
277 }
278
279 func capitalize(s string) string {
280 if s == "" {
281 return s
282 }
283 r, n := utf8.DecodeRuneInString(s)
284 return string(unicode.ToTitle(r)) + s[n:]
285 }
286
287 func printUsage(w io.Writer) {
288 bw := bufio.NewWriter(w)
289 tmpl(bw, usageTemplate, commands)
290 bw.Flush()
291 }
292
293 func usage() {
294 printUsage(os.Stderr)
295 os.Exit(2)
296 }
297
298
299 func help(args []string) {
300 if len(args) == 0 {
301 printUsage(os.Stdout)
302
303 return
304 }
305 if len(args) != 1 {
306 fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n")
307 os.Exit(2)
308 }
309
310 arg := args[0]
311
312
313 if strings.HasSuffix(arg, "documentation") {
314 w := &bytes.Buffer{}
315
316 fmt.Fprintln(w, "// Code generated by go generate. DO NOT EDIT.")
317 fmt.Fprintln(w)
318 buf := new(bytes.Buffer)
319 printUsage(buf)
320 usage := &Command{Long: buf.String()}
321 tmpl(&commentWriter{W: w}, documentationTemplate, append([]*Command{usage}, commands...))
322 fmt.Fprintln(w, "package main")
323 if arg == "gendocumentation" {
324 b, err := format.Source(w.Bytes())
325 if err != nil {
326 logf("Could not format generated docs: %v\n", err)
327 }
328 if err := os.WriteFile("doc.go", b, 0666); err != nil {
329 logf("Could not create file alldocs.go: %v\n", err)
330 }
331 } else {
332 fmt.Println(w.String())
333 }
334 return
335 }
336
337 for _, cmd := range commands {
338 if cmd.Name() == arg {
339 tmpl(os.Stdout, helpTemplate, cmd)
340
341 return
342 }
343 }
344
345 fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'gotext help'.\n", arg)
346 os.Exit(2)
347 }
348
349 func getLangs() (tags []language.Tag) {
350 if lang == nil {
351 return []language.Tag{language.AmericanEnglish}
352 }
353 for _, t := range strings.Split(*lang, ",") {
354 if t == "" {
355 continue
356 }
357 tag, err := language.Parse(t)
358 if err != nil {
359 fatalf("gotext: could not parse language %q: %v", t, err)
360 }
361 tags = append(tags, tag)
362 }
363 return tags
364 }
365
366 var atexitFuncs []func()
367
368 func atexit(f func()) {
369 atexitFuncs = append(atexitFuncs, f)
370 }
371
372 func exit() {
373 for _, f := range atexitFuncs {
374 f()
375 }
376 os.Exit(exitStatus)
377 }
378
379 func fatalf(format string, args ...interface{}) {
380 logf(format, args...)
381 exit()
382 }
383
384 func logf(format string, args ...interface{}) {
385 log.Printf(format, args...)
386 setExitStatus(1)
387 }
388
389 func exitIfErrors() {
390 if exitStatus != 0 {
391 exit()
392 }
393 }
394
View as plain text