Skip to content

cmd/stringer: add more control over generated String output through comment #255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 72 additions & 4 deletions cmd/stringer/stringer.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@
// PillAspirin // Aspirin
//
// to suppress it in the output.
//
// Without the -linecomment flag, regular line comment will be searched for a sequence, mocking struct tag, at any part of the
// comment, in the following form (notice required `):
//
// // `stringer:"[<name>],[fn]"`
//
// where <name> is a name to generate and fn is one of the following: "title", "de(un)title", "lower", "upper".
// Absent <name> means to use default constant name and pass it through the function, if any. If <name> is provided,
// it will still be passed through the function for consistency. To completely suppress any output, provide `stringer:""`.
package main // import "golang.org/x/tools/cmd/stringer"

import (
Expand All @@ -74,14 +83,17 @@ import (
"go/format"
"go/token"
"go/types"
"golang.org/x/tools/go/packages"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"sort"
"strings"

"golang.org/x/tools/go/packages"
"unicode"
"unicode/utf8"
)

var (
Expand Down Expand Up @@ -458,9 +470,7 @@ func (f *File) genDecl(node ast.Node) bool {
signed: info&types.IsUnsigned == 0,
str: value.String(),
}
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
v.name = strings.TrimSpace(c.Text())
} else {
if !useComments(vspec.Comment, f, &v) {
v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
}
f.values = append(f.values, v)
Expand All @@ -469,6 +479,64 @@ func (f *File) genDecl(node ast.Node) bool {
return false
}

// useComments takes the vspec's comment and modifies v.name if lineComment flag is set or there's a `stringer:"[<name>],[<fn>]"`
// sequence found anywhere in the comment. This will modify v.name according to its directive, substituting the name (if provided) and passing
// the value through function fn which is one of: "title", "de(un)title", "lower", "upper". `stringer:""` yields empty name.
func useComments(c *ast.CommentGroup, f *File, v *Value) bool {
if c != nil {
if f.lineComment && len(c.List) == 1 {
v.name = strings.TrimSpace(c.Text())
return true
} else {
// Lookup a comment portion between `stringer:" and ` that would compile as reflect.StructTag
comment := c.Text()
idx := strings.Index(comment, `stringer:"`)
if idx >= 0 {
comment := comment[idx:]
idx = strings.Index(comment, "`")
if idx >= 0 {
comment = comment[:idx]
tag := reflect.StructTag(comment)
if st, ok := tag.Lookup("stringer"); ok {
st = strings.TrimSpace(st)
split := strings.Split(st, ",")
switch {
case len(split) == 1:
v.name = split[0] // This allows an empty value
return true
case len(split) > 1:
if len(split[0]) == 0 {
v.name = v.originalName // Empty value means original
} else {
v.name = split[0]
}
switch split[1] {
case "title":
v.name = strings.Title(v.name)
return true
case "detitle", "untitle":
// We only care about first rune
r, size := utf8.DecodeRuneInString(v.name)
if size > 0 {
v.name = string(unicode.ToLower(r)) + v.name[size:]
}
return true
case "lower":
v.name = strings.ToLower(v.name)
return true
case "upper":
v.name = strings.ToUpper(v.name)
return true
}
}
}
}
}
}
}
return false
}

// Helpers

// usize returns the number of bits of the smallest unsigned integer
Expand Down