add -trimprefix and -autotrimprefix

This commit is contained in:
Steve Atkins 2017-04-10 11:37:23 -07:00
parent fbb114e61f
commit 51d23ad531
6 changed files with 99 additions and 6 deletions

View File

@ -104,10 +104,16 @@ the JSON related methods will be generated. Similarly if the yaml flag is set to
the YAML related methods will be generated. And if the sql flag is set to true, the Scanner and Valuer interface will
be implemented to seamlessly use the enum in a database model.
For enum string representation transformation `transform` flag was added (i.e. `enumer -type=MyType -json -transform=snake`).
Possible values are `snake` and `kebab` for transformation to snake_case and kebab-case accordingly.
For enum string representation transformation the `transform`, `trimprefix` and `autotrimprefix` flags
were added (i.e. `enumer -type=MyType -json -transform=snake`).
Possible transform values are `snake` and `kebab` for transformation to snake_case and kebab-case accordingly.
The default value for `transform` flag is `noop` which means no transformation will be performed.
If a prefix is provided via the `trimprefix` flag that will be trimmed from the start of each name (before
it is transformed). If a name doesn't have the prefix it will be passed unchanged.
If the `autotrimprefix` flag is set then if all the names in an enum have a common prefix that prefix will be removed.
## Inspiring projects
* [Stringer](https://godoc.org/golang.org/x/tools/cmd/stringer)
* [jsonenums](https://github.com/campoy/jsonenums)

View File

@ -33,7 +33,7 @@ func TestEndToEnd(t *testing.T) {
defer os.RemoveAll(dir)
// Create stringer in temporary directory.
stringer := filepath.Join(dir, "stringer.exe")
err = run("go", "build", "-o", stringer, "enumer.go", "sql.go", "stringer.go", "transformer.go")
err = run("go", "build", "-o", stringer, "enumer.go", "sql.go", "stringer.go", "transformer.go", "trim.go")
if err != nil {
t.Fatalf("building stringer: %s", err)
}

View File

@ -759,7 +759,7 @@ func runGoldenTest(t *testing.T, test Golden, generateJSON, generateYAML, genera
if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name)
}
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, "noop")
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, "noop", "", false)
got := string(g.format())
if got != test.output {
t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output)

View File

@ -87,6 +87,8 @@ var (
yaml = flag.Bool("yaml", false, "if true, yaml marshaling methods will be generated. Default: false")
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
transformMethod = flag.String("transform", "noop", "enum item name transformation method. Default: noop")
trimPrefix = flag.String("trimprefix", "", "transform each item name by removing a prefix. Default: \"\"")
autoTrimPrefix = flag.Bool("autotrimprefix", false, "if true, remove a common prefix from each item name. Default: false")
)
// Usage is a replacement usage function for the flags package.
@ -149,7 +151,7 @@ func main() {
// Run generate for each type.
for _, typeName := range types {
g.generate(typeName, *json, *yaml, *sql, *transformMethod)
g.generate(typeName, *json, *yaml, *sql, *transformMethod, *trimPrefix, *autoTrimPrefix)
}
// Format the output.
@ -301,7 +303,7 @@ func (g *Generator) transformValueNames(values []Value, transformMethod string)
}
// generate produces the String method for the named type.
func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL bool, transformMethod string) {
func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL bool, transformMethod string, trimPrefix string, autoTrimPrefix bool) {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
// Set the state for this run of the walker.
@ -317,6 +319,12 @@ func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeS
log.Fatalf("no values defined for type %s", typeName)
}
g.trimValueNames(values, trimPrefix)
if autoTrimPrefix {
g.autoTrimValueNames(values)
}
g.transformValueNames(values, transformMethod)
runs := splitIntoRuns(values)

47
trim.go Normal file
View File

@ -0,0 +1,47 @@
package main
import "strings"
func longestCommonPrefix(values []Value) string {
// LCP of min and max (lexigraphically)
// is the LCP of the whole set.
min := values[0].name
max := min
for _, s := range values[1:] {
switch {
case s.name < min:
min = s.name
case s.name > max:
max = s.name
}
}
for i := 0; i < len(min) && i < len(max); i++ {
if min[i] != max[i] {
return min[:i]
}
}
// If all the bytes are equal but the lengths aren't then
// min is a prefix of max, and hence the lcp
return min
}
func autoPrefix(values []Value) string {
// Can't trim a single value
if len(values) < 2 {
return ""
}
prefix := longestCommonPrefix(values)
return prefix
}
func (g *Generator) trimValueNames(values []Value, prefix string) {
for i := range values {
values[i].name = strings.TrimPrefix(values[i].name, prefix)
}
}
func (g *Generator) autoTrimValueNames(values []Value) {
prefix := autoPrefix(values)
g.trimValueNames(values, prefix)
}

32
trim_test.go Normal file
View File

@ -0,0 +1,32 @@
package main
import "testing"
var lcpTests = []struct {
expected string
in []string
}{
{"Proto", []string{"ProtoOne", "ProtoTwo", "ProtoThree"}},
// An empty string is OK when one value is the prefix
{"Proto", []string{"Proto", "ProtoLonger"}},
{"", []string{}},
{"", []string{"aardvark"}},
{"", []string{"abc", "def", "deg"}},
{"ab", []string{"ab", "abc", "abcd"}},
}
// TestLcp checks that the longest common prefix is generated correctly
func TestLcp(t *testing.T) {
for _, tt := range lcpTests {
values := make([]Value, len(tt.in))
for i := range tt.in {
values[i] = Value{
name: tt.in[i],
}
}
prefix := autoPrefix(values)
if prefix != tt.expected {
t.Errorf("%q => %s, expected %s", tt.in, tt.expected, prefix)
}
}
}