Add enum value string representation transformation feature

This commit is contained in:
achernov 2017-02-01 10:01:12 +03:00
parent a13305de81
commit 432dffaa95
12 changed files with 354 additions and 13 deletions

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.idea

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")
err = run("go", "build", "-o", stringer, "enumer.go", "sql.go", "stringer.go", "transformer.go")
if err != nil {
t.Fatalf("building stringer: %s", err)
}
@ -59,13 +59,20 @@ func TestEndToEnd(t *testing.T) {
}
// Names are known to be ASCII and long enough.
typeName := fmt.Sprintf("%c%s", name[0]+'A'-'a', name[1:len(name)-len(".go")])
stringerCompileAndRun(t, dir, stringer, typeName, name)
transformNameMethod := "noop"
if name == "transform.go" {
typeName = "CamelCaseValue"
transformNameMethod = "snake"
}
stringerCompileAndRun(t, dir, stringer, typeName, name, transformNameMethod)
}
}
// stringerCompileAndRun runs stringer for the named file and compiles and
// runs the target binary in directory dir. That binary will panic if the String method is incorrect.
func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) {
func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName, transformNameMethod string) {
t.Logf("run: %s %s\n", fileName, typeName)
source := filepath.Join(dir, fileName)
err := copy(source, filepath.Join("testdata", fileName))
@ -74,7 +81,7 @@ func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName strin
}
stringSource := filepath.Join(dir, typeName+"_string.go")
// Run stringer in temporary directory.
err = run(stringer, "-type", typeName, "-output", stringSource, source)
err = run(stringer, "-type", typeName, "-output", stringSource, "-transform", transformNameMethod, source)
if err != nil {
t.Fatal(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)
g.generate(tokens[1], generateJSON, generateYAML, generateSQL, "noop")
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

@ -81,11 +81,12 @@ import (
)
var (
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
sql = flag.Bool("sql", false, "if true, the Scanner and Valuer interface will be implemented.")
json = flag.Bool("json", false, "if true, json marshaling methods will be generated. Default: false")
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")
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
sql = flag.Bool("sql", false, "if true, the Scanner and Valuer interface will be implemented.")
json = flag.Bool("json", false, "if true, json marshaling methods will be generated. Default: false")
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")
)
// Usage is a replacement usage function for the flags package.
@ -122,6 +123,7 @@ func main() {
dir string
g Generator
)
if len(args) == 1 && isDirectory(args[0]) {
dir = args[0]
g.parsePackageDir(args[0])
@ -147,7 +149,7 @@ func main() {
// Run generate for each type.
for _, typeName := range types {
g.generate(typeName, *json, *yaml, *sql)
g.generate(typeName, *json, *yaml, *sql, *transformMethod)
}
// Format the output.
@ -282,8 +284,24 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
pkg.typesPkg = typesPkg
}
func (g *Generator) transformValueNames(values []Value, transformMethod string) {
var transform func(string) string
switch transformMethod {
case "snake":
transform = toSnakeCase
case "kebab":
transform = toKebabCase
default:
return
}
for i := range values {
values[i].name = transform(values[i].name)
}
}
// generate produces the String method for the named type.
func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL bool) {
func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeSQL bool, transformMethod string) {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
// Set the state for this run of the walker.
@ -298,6 +316,9 @@ func (g *Generator) generate(typeName string, includeJSON, includeYAML, includeS
if len(values) == 0 {
log.Fatalf("no values defined for type %s", typeName)
}
g.transformValueNames(values, transformMethod)
runs := splitIntoRuns(values)
// The decision of which pattern to use depends on the number of
// runs in the numbers. If there's only one, it's easy. For more than
@ -380,7 +401,7 @@ func (g *Generator) format() []byte {
// Value represents a declared constant.
type Value struct {
name string // The name of the constant.
name string // The name of the constant after transformation (i.e. camel case => snake case)
// The value is stored as a bit pattern alone. The boolean tells us
// whether to interpret it as an int64 or a uint64; the only place
// this matters is when sorting.

25
testdata/transform.go vendored Normal file
View File

@ -0,0 +1,25 @@
package main
import "fmt"
type CamelCaseValue int
const (
CamelCaseValueOne CamelCaseValue = iota
CamelCaseValueTwo
CamelCaseValueThree
)
func main() {
ck(CamelCaseValueOne, "camel_case_value_one")
ck(CamelCaseValueTwo, "camel_case_value_two")
ck(CamelCaseValueThree, "camel_case_value_three")
ck(-127, "CamelCaseValue(-127)")
ck(127, "CamelCaseValue(127)")
}
func ck(value CamelCaseValue, str string) {
if fmt.Sprint(value) != str {
panic("transform.go: " + str)
}
}

28
transformer.go Normal file
View File

@ -0,0 +1,28 @@
package main
import (
"strings"
"github.com/fatih/camelcase"
)
func transform(src, delim string) string {
entries := camelcase.Split(src)
if len(entries) <= 1 {
return strings.ToLower(src)
}
result := strings.ToLower(entries[0])
for i := 1; i < len(entries); i++ {
result += delim + strings.ToLower(entries[i])
}
return result
}
func toSnakeCase(src string) string {
return transform(src, "_")
}
func toKebabCase(src string) string {
return transform(src, "-")
}

14
transformer_test.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"testing"
)
func TestTransform(t *testing.T) {
result := transform("CamelCaseStringValue", ".")
const expected = "camel.case.string.value"
if result != expected {
t.Errorf("\ngot: %s\n====\nexpected: %s", result, expected)
}
}

3
vendor/github.com/fatih/camelcase/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,3 @@
language: go
go: 1.4

20
vendor/github.com/fatih/camelcase/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

58
vendor/github.com/fatih/camelcase/README.md generated vendored Normal file
View File

@ -0,0 +1,58 @@
# CamelCase [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/camelcase) [![Build Status](http://img.shields.io/travis/fatih/camelcase.svg?style=flat-square)](https://travis-ci.org/fatih/camelcase)
CamelCase is a Golang (Go) package to split the words of a camelcase type
string into a slice of words. It can be used to convert a camelcase word (lower
or upper case) into any type of word.
## Splitting rules:
1. If string is not valid UTF-8, return it without splitting as
single item array.
2. Assign all unicode characters into one of 4 sets: lower case
letters, upper case letters, numbers, and all other characters.
3. Iterate through characters of string, introducing splits
between adjacent characters that belong to different sets.
4. Iterate through array of split strings, and if a given string
is upper case:
* if subsequent string is lower case:
* move last character of upper case string to beginning of
lower case string
## Install
```bash
go get github.com/fatih/camelcase
```
## Usage and examples
```go
splitted := camelcase.Split("GolangPackage")
fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package"
```
Both lower camel case and upper camel case are supported. For more info please
check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase)
Below are some example cases:
```
"" => []
"lowercase" => ["lowercase"]
"Class" => ["Class"]
"MyClass" => ["My", "Class"]
"MyC" => ["My", "C"]
"HTML" => ["HTML"]
"PDFLoader" => ["PDF", "Loader"]
"AString" => ["A", "String"]
"SimpleXMLParser" => ["Simple", "XML", "Parser"]
"vimRPCPlugin" => ["vim", "RPC", "Plugin"]
"GL11Version" => ["GL", "11", "Version"]
"99Bottles" => ["99", "Bottles"]
"May5" => ["May", "5"]
"BFG9000" => ["BFG", "9000"]
"BöseÜberraschung" => ["Böse", "Überraschung"]
"Two spaces" => ["Two", " ", "spaces"]
"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
```

90
vendor/github.com/fatih/camelcase/camelcase.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
// Package camelcase is a micro package to split the words of a camelcase type
// string into a slice of words.
package camelcase
import (
"unicode"
"unicode/utf8"
)
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1) If string is not valid UTF-8, return it without splitting as
// single item array.
// 2) Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3) Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4) Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func Split(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch true {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return
}

47
vendor/github.com/fatih/camelcase/camelcase_test.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package camelcase
import "fmt"
func ExampleSplit() {
for _, c := range []string{
"",
"lowercase",
"Class",
"MyClass",
"MyC",
"HTML",
"PDFLoader",
"AString",
"SimpleXMLParser",
"vimRPCPlugin",
"GL11Version",
"99Bottles",
"May5",
"BFG9000",
"BöseÜberraschung",
"Two spaces",
"BadUTF8\xe2\xe2\xa1",
} {
fmt.Printf("%#v => %#v\n", c, Split(c))
}
// Output:
// "" => []string{}
// "lowercase" => []string{"lowercase"}
// "Class" => []string{"Class"}
// "MyClass" => []string{"My", "Class"}
// "MyC" => []string{"My", "C"}
// "HTML" => []string{"HTML"}
// "PDFLoader" => []string{"PDF", "Loader"}
// "AString" => []string{"A", "String"}
// "SimpleXMLParser" => []string{"Simple", "XML", "Parser"}
// "vimRPCPlugin" => []string{"vim", "RPC", "Plugin"}
// "GL11Version" => []string{"GL", "11", "Version"}
// "99Bottles" => []string{"99", "Bottles"}
// "May5" => []string{"May", "5"}
// "BFG9000" => []string{"BFG", "9000"}
// "BöseÜberraschung" => []string{"Böse", "Überraschung"}
// "Two spaces" => []string{"Two", " ", "spaces"}
// "BadUTF8\xe2\xe2\xa1" => []string{"BadUTF8\xe2\xe2\xa1"}
}