1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 package main
69
70 import (
71 "bytes"
72 "flag"
73 "fmt"
74 "go/ast"
75 "go/constant"
76 "go/format"
77 "go/token"
78 "go/types"
79 "log"
80 "os"
81 "path/filepath"
82 "sort"
83 "strings"
84
85 "golang.org/x/tools/go/packages"
86 )
87
88 var (
89 typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
90 output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
91 trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
92 linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
93 buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
94 )
95
96
97 func Usage() {
98 fmt.Fprintf(os.Stderr, "Usage of stringer:\n")
99 fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
100 fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n")
101 fmt.Fprintf(os.Stderr, "For more information, see:\n")
102 fmt.Fprintf(os.Stderr, "\thttps://pkg.go.dev/golang.org/x/tools/cmd/stringer\n")
103 fmt.Fprintf(os.Stderr, "Flags:\n")
104 flag.PrintDefaults()
105 }
106
107 func main() {
108 log.SetFlags(0)
109 log.SetPrefix("stringer: ")
110 flag.Usage = Usage
111 flag.Parse()
112 if len(*typeNames) == 0 {
113 flag.Usage()
114 os.Exit(2)
115 }
116 types := strings.Split(*typeNames, ",")
117 var tags []string
118 if len(*buildTags) > 0 {
119 tags = strings.Split(*buildTags, ",")
120 }
121
122
123 args := flag.Args()
124 if len(args) == 0 {
125
126 args = []string{"."}
127 }
128
129
130 var dir string
131 g := Generator{
132 trimPrefix: *trimprefix,
133 lineComment: *linecomment,
134 }
135
136 if len(args) == 1 && isDirectory(args[0]) {
137 dir = args[0]
138 } else {
139 if len(tags) != 0 {
140 log.Fatal("-tags option applies only to directories, not when files are specified")
141 }
142 dir = filepath.Dir(args[0])
143 }
144
145 g.parsePackage(args, tags)
146
147
148 g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
149 g.Printf("\n")
150 g.Printf("package %s", g.pkg.name)
151 g.Printf("\n")
152 g.Printf("import \"strconv\"\n")
153
154
155 for _, typeName := range types {
156 g.generate(typeName)
157 }
158
159
160 src := g.format()
161
162
163 outputName := *output
164 if outputName == "" {
165 baseName := fmt.Sprintf("%s_string.go", types[0])
166 outputName = filepath.Join(dir, strings.ToLower(baseName))
167 }
168 err := os.WriteFile(outputName, src, 0644)
169 if err != nil {
170 log.Fatalf("writing output: %s", err)
171 }
172 }
173
174
175 func isDirectory(name string) bool {
176 info, err := os.Stat(name)
177 if err != nil {
178 log.Fatal(err)
179 }
180 return info.IsDir()
181 }
182
183
184
185 type Generator struct {
186 buf bytes.Buffer
187 pkg *Package
188
189 trimPrefix string
190 lineComment bool
191
192 logf func(format string, args ...interface{})
193 }
194
195 func (g *Generator) Printf(format string, args ...interface{}) {
196 fmt.Fprintf(&g.buf, format, args...)
197 }
198
199
200 type File struct {
201 pkg *Package
202 file *ast.File
203
204 typeName string
205 values []Value
206
207 trimPrefix string
208 lineComment bool
209 }
210
211 type Package struct {
212 name string
213 defs map[*ast.Ident]types.Object
214 files []*File
215 }
216
217
218
219 func (g *Generator) parsePackage(patterns []string, tags []string) {
220 cfg := &packages.Config{
221 Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax,
222
223
224 Tests: false,
225 BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
226 Logf: g.logf,
227 }
228 pkgs, err := packages.Load(cfg, patterns...)
229 if err != nil {
230 log.Fatal(err)
231 }
232 if len(pkgs) != 1 {
233 log.Fatalf("error: %d packages matching %v", len(pkgs), strings.Join(patterns, " "))
234 }
235 g.addPackage(pkgs[0])
236 }
237
238
239 func (g *Generator) addPackage(pkg *packages.Package) {
240 g.pkg = &Package{
241 name: pkg.Name,
242 defs: pkg.TypesInfo.Defs,
243 files: make([]*File, len(pkg.Syntax)),
244 }
245
246 for i, file := range pkg.Syntax {
247 g.pkg.files[i] = &File{
248 file: file,
249 pkg: g.pkg,
250 trimPrefix: g.trimPrefix,
251 lineComment: g.lineComment,
252 }
253 }
254 }
255
256
257 func (g *Generator) generate(typeName string) {
258 values := make([]Value, 0, 100)
259 for _, file := range g.pkg.files {
260
261 file.typeName = typeName
262 file.values = nil
263 if file.file != nil {
264 ast.Inspect(file.file, file.genDecl)
265 values = append(values, file.values...)
266 }
267 }
268
269 if len(values) == 0 {
270 log.Fatalf("no values defined for type %s", typeName)
271 }
272
273 g.Printf("func _() {\n")
274 g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")
275 g.Printf("\t// Re-run the stringer command to generate them again.\n")
276 g.Printf("\tvar x [1]struct{}\n")
277 for _, v := range values {
278 g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str)
279 }
280 g.Printf("}\n")
281 runs := splitIntoRuns(values)
282
283
284
285
286
287
288
289
290
291
292
293
294 switch {
295 case len(runs) == 1:
296 g.buildOneRun(runs, typeName)
297 case len(runs) <= 10:
298 g.buildMultipleRuns(runs, typeName)
299 default:
300 g.buildMap(runs, typeName)
301 }
302 }
303
304
305
306
307 func splitIntoRuns(values []Value) [][]Value {
308
309 sort.Stable(byValue(values))
310
311
312
313
314
315 j := 1
316 for i := 1; i < len(values); i++ {
317 if values[i].value != values[i-1].value {
318 values[j] = values[i]
319 j++
320 }
321 }
322 values = values[:j]
323 runs := make([][]Value, 0, 10)
324 for len(values) > 0 {
325
326 i := 1
327 for i < len(values) && values[i].value == values[i-1].value+1 {
328 i++
329 }
330 runs = append(runs, values[:i])
331 values = values[i:]
332 }
333 return runs
334 }
335
336
337 func (g *Generator) format() []byte {
338 src, err := format.Source(g.buf.Bytes())
339 if err != nil {
340
341
342 log.Printf("warning: internal error: invalid Go generated: %s", err)
343 log.Printf("warning: compile the package to analyze the error")
344 return g.buf.Bytes()
345 }
346 return src
347 }
348
349
350 type Value struct {
351 originalName string
352 name string
353
354
355
356
357
358 value uint64
359 signed bool
360 str string
361 }
362
363 func (v *Value) String() string {
364 return v.str
365 }
366
367
368
369
370 type byValue []Value
371
372 func (b byValue) Len() int { return len(b) }
373 func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
374 func (b byValue) Less(i, j int) bool {
375 if b[i].signed {
376 return int64(b[i].value) < int64(b[j].value)
377 }
378 return b[i].value < b[j].value
379 }
380
381
382 func (f *File) genDecl(node ast.Node) bool {
383 decl, ok := node.(*ast.GenDecl)
384 if !ok || decl.Tok != token.CONST {
385
386 return true
387 }
388
389
390 typ := ""
391
392
393
394
395 for _, spec := range decl.Specs {
396 vspec := spec.(*ast.ValueSpec)
397 if vspec.Type == nil && len(vspec.Values) > 0 {
398
399
400 typ = ""
401
402
403
404
405
406
407 ce, ok := vspec.Values[0].(*ast.CallExpr)
408 if !ok {
409 continue
410 }
411 id, ok := ce.Fun.(*ast.Ident)
412 if !ok {
413 continue
414 }
415 typ = id.Name
416 }
417 if vspec.Type != nil {
418
419 ident, ok := vspec.Type.(*ast.Ident)
420 if !ok {
421 continue
422 }
423 typ = ident.Name
424 }
425 if typ != f.typeName {
426
427 continue
428 }
429
430
431
432 for _, name := range vspec.Names {
433 if name.Name == "_" {
434 continue
435 }
436
437
438
439 obj, ok := f.pkg.defs[name]
440 if !ok {
441 log.Fatalf("no value for constant %s", name)
442 }
443 info := obj.Type().Underlying().(*types.Basic).Info()
444 if info&types.IsInteger == 0 {
445 log.Fatalf("can't handle non-integer constant type %s", typ)
446 }
447 value := obj.(*types.Const).Val()
448 if value.Kind() != constant.Int {
449 log.Fatalf("can't happen: constant is not an integer %s", name)
450 }
451 i64, isInt := constant.Int64Val(value)
452 u64, isUint := constant.Uint64Val(value)
453 if !isInt && !isUint {
454 log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String())
455 }
456 if !isInt {
457 u64 = uint64(i64)
458 }
459 v := Value{
460 originalName: name.Name,
461 value: u64,
462 signed: info&types.IsUnsigned == 0,
463 str: value.String(),
464 }
465 if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
466 v.name = strings.TrimSpace(c.Text())
467 } else {
468 v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
469 }
470 f.values = append(f.values, v)
471 }
472 }
473 return false
474 }
475
476
477
478
479
480
481 func usize(n int) int {
482 switch {
483 case n < 1<<8:
484 return 8
485 case n < 1<<16:
486 return 16
487 default:
488
489 return 32
490 }
491 }
492
493
494
495 func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) {
496 var indexes, names []string
497 for i, run := range runs {
498 index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i))
499 if len(run) != 1 {
500 indexes = append(indexes, index)
501 }
502 names = append(names, name)
503 }
504 g.Printf("const (\n")
505 for _, name := range names {
506 g.Printf("\t%s\n", name)
507 }
508 g.Printf(")\n\n")
509
510 if len(indexes) > 0 {
511 g.Printf("var (")
512 for _, index := range indexes {
513 g.Printf("\t%s\n", index)
514 }
515 g.Printf(")\n\n")
516 }
517 }
518
519
520 func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) {
521 index, name := g.createIndexAndNameDecl(run, typeName, "")
522 g.Printf("const %s\n", name)
523 g.Printf("var %s\n", index)
524 }
525
526
527 func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) {
528 b := new(bytes.Buffer)
529 indexes := make([]int, len(run))
530 for i := range run {
531 b.WriteString(run[i].name)
532 indexes[i] = b.Len()
533 }
534 nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String())
535 nameLen := b.Len()
536 b.Reset()
537 fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen))
538 for i, v := range indexes {
539 if i > 0 {
540 fmt.Fprintf(b, ", ")
541 }
542 fmt.Fprintf(b, "%d", v)
543 }
544 fmt.Fprintf(b, "}")
545 return b.String(), nameConst
546 }
547
548
549 func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) {
550 g.Printf("const _%s_name%s = \"", typeName, suffix)
551 for _, run := range runs {
552 for i := range run {
553 g.Printf("%s", run[i].name)
554 }
555 }
556 g.Printf("\"\n")
557 }
558
559
560 func (g *Generator) buildOneRun(runs [][]Value, typeName string) {
561 values := runs[0]
562 g.Printf("\n")
563 g.declareIndexAndNameVar(values, typeName)
564
565 lessThanZero := ""
566 if values[0].signed {
567 lessThanZero = "i < 0 || "
568 }
569 if values[0].value == 0 {
570 g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero)
571 } else {
572 g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero)
573 }
574 }
575
576
577
578
579
580
581 const stringOneRun = `func (i %[1]s) String() string {
582 if %[3]si >= %[1]s(len(_%[1]s_index)-1) {
583 return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
584 }
585 return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]]
586 }
587 `
588
589
590
591
592
593
594
596 const stringOneRunWithOffset = `func (i %[1]s) String() string {
597 i -= %[2]s
598 if %[4]si >= %[1]s(len(_%[1]s_index)-1) {
599 return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")"
600 }
601 return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]]
602 }
603 `
604
605
606
607 func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {
608 g.Printf("\n")
609 g.declareIndexAndNameVars(runs, typeName)
610 g.Printf("func (i %s) String() string {\n", typeName)
611 g.Printf("\tswitch {\n")
612 for i, values := range runs {
613 if len(values) == 1 {
614 g.Printf("\tcase i == %s:\n", &values[0])
615 g.Printf("\t\treturn _%s_name_%d\n", typeName, i)
616 continue
617 }
618 if values[0].value == 0 && !values[0].signed {
619
620 g.Printf("\tcase i <= %s:\n", &values[len(values)-1])
621 } else {
622 g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1])
623 }
624 if values[0].value != 0 {
625 g.Printf("\t\ti -= %s\n", &values[0])
626 }
627 g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n",
628 typeName, i, typeName, i, typeName, i)
629 }
630 g.Printf("\tdefault:\n")
631 g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName)
632 g.Printf("\t}\n")
633 g.Printf("}\n")
634 }
635
636
637
638 func (g *Generator) buildMap(runs [][]Value, typeName string) {
639 g.Printf("\n")
640 g.declareNameVars(runs, typeName, "")
641 g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName)
642 n := 0
643 for _, values := range runs {
644 for _, value := range values {
645 g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name))
646 n += len(value.name)
647 }
648 }
649 g.Printf("}\n\n")
650 g.Printf(stringMap, typeName)
651 }
652
653
654 const stringMap = `func (i %[1]s) String() string {
655 if str, ok := _%[1]s_map[i]; ok {
656 return str
657 }
658 return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
659 }
660 `
661
View as plain text