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 return &pipeline.Config{
52 SourceLanguage: tag,
53 Supported: getLangs(),
54 TranslationsPattern: `messages\.(.*)\.json$`,
55 GenFile: *out,
56 Dir: *dir,
57 }, nil
58 }
59
60
61
62
63
64 type Command struct {
65
66 Init func(cmd *Command)
67
68
69
70 Run func(cmd *Command, c *pipeline.Config, args []string) error
71
72
73
74 UsageLine string
75
76
77 Short string
78
79
80 Long string
81
82
83 Flag flag.FlagSet
84 }
85
86
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
103
104 func (c *Command) Runnable() bool {
105 return c.Run != nil
106 }
107
108
109
110 var commands = []*Command{
111 cmdUpdate,
112 cmdExtract,
113 cmdRewrite,
114 cmdGenerate,
115
116
117
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
208
209 type commentWriter struct {
210 W io.Writer
211 wroteSlashes bool
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
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
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
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
292 func help(args []string) {
293 if len(args) == 0 {
294 printUsage(os.Stdout)
295
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)
301 }
302
303 arg := args[0]
304
305
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
334 return
335 }
336 }
337
338 fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'gotext help'.\n", arg)
339 os.Exit(2)
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 }
387
View as plain text