From 6ee8be15d5956175a25c1bbb5c96e28e5acb7a5c Mon Sep 17 00:00:00 2001 From: Vadim Petrov Date: Wed, 8 Jun 2016 18:08:55 +0300 Subject: [PATCH] Added camel_case_functions flag which make CamelCase function names (according to default Go naming convention) --- Makefile | 6 ++- bootstrap/bootstrap.go | 14 ++++--- easyjson/main.go | 28 +++++++------ gen/decoder.go | 8 ++-- gen/encoder.go | 18 ++++---- gen/generator.go | 79 ++++++++++++++++++++++++++++------- gen/generator_test.go | 37 ++++++++++++++++ tests/basic_test.go | 1 + tests/camel_case_functions.go | 9 ++++ 9 files changed, 153 insertions(+), 47 deletions(-) create mode 100644 tests/camel_case_functions.go diff --git a/Makefile b/Makefile index 420f306..b98f8b1 100644 --- a/Makefile +++ b/Makefile @@ -20,11 +20,13 @@ generate: root build .root/bin/easyjson -stubs \ .root/src/$(PKG)/tests/snake.go \ .root/src/$(PKG)/tests/data.go \ - .root/src/$(PKG)/tests/omitempty.go + .root/src/$(PKG)/tests/omitempty.go \ + .root/src/$(PKG)/tests/camel_case_functions.go .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go .root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go - .root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go + .root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go + .root/bin/easyjson -camel_case_functions .root/src/$(PKG)/tests/camel_case_functions.go .root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go test: generate root diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index e9a1d21..8bca39a 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -21,9 +21,10 @@ type Generator struct { PkgPath, PkgName string Types []string - NoStdMarshalers bool - SnakeCase bool - OmitEmpty bool + NoStdMarshalers bool + SnakeCaseFields bool + CamelCaseFunctions bool + OmitEmpty bool OutName string BuildTags string @@ -100,8 +101,11 @@ func (g *Generator) writeMain() (path string, err error) { if g.BuildTags != "" { fmt.Fprintf(f, " g.SetBuildTags(%q)\n", g.BuildTags) } - if g.SnakeCase { - fmt.Fprintln(f, " g.UseSnakeCase()") + if g.SnakeCaseFields { + fmt.Fprintln(f, " g.UseSnakeCaseFieldNamer()") + } + if g.CamelCaseFunctions { + fmt.Fprintln(f, " g.UseCamelCaseFunctionNamer()") } if g.OmitEmpty { fmt.Fprintln(f, " g.OmitEmpty()") diff --git a/easyjson/main.go b/easyjson/main.go index f82e3f7..e6d6689 100644 --- a/easyjson/main.go +++ b/easyjson/main.go @@ -15,7 +15,8 @@ import ( ) var buildTags = flag.String("build_tags", "", "build tags to add to generated file") -var snakeCase = flag.Bool("snake_case", false, "use snake_case names instead of CamelCase by default") +var snakeCaseFields = flag.Bool("snake_case", false, "use snake_case names instead of CamelCase by default") +var camelCaseFunctions = flag.Bool("camel_case_functions", false, "create functions with CamelCase names instead of under_score by default") var noStdMarshalers = flag.Bool("no_std_marshalers", false, "don't generate MarshalJSON/UnmarshalJSON methods") var omitEmpty = flag.Bool("omit_empty", false, "omit empty fields by default") var allStructs = flag.Bool("all", false, "generate un-/marshallers for all structs in a file") @@ -40,19 +41,20 @@ func generate(fname string) (err error) { if *specifiedName != "" { outName = *specifiedName } - + g := bootstrap.Generator{ - BuildTags: *buildTags, - PkgPath: p.PkgPath, - PkgName: p.PkgName, - Types: p.StructNames, - SnakeCase: *snakeCase, - NoStdMarshalers: *noStdMarshalers, - OmitEmpty: *omitEmpty, - LeaveTemps: *leaveTemps, - OutName: outName, - StubsOnly: *stubs, - NoFormat: *noformat, + BuildTags: *buildTags, + PkgPath: p.PkgPath, + PkgName: p.PkgName, + Types: p.StructNames, + SnakeCaseFields: *snakeCaseFields, + CamelCaseFunctions: *camelCaseFunctions, + NoStdMarshalers: *noStdMarshalers, + OmitEmpty: *omitEmpty, + LeaveTemps: *leaveTemps, + OutName: outName, + StubsOnly: *stubs, + NoFormat: *noformat, } if err := g.Run(); err != nil { diff --git a/gen/decoder.go b/gen/decoder.go index a1291fc..706f77b 100644 --- a/gen/decoder.go +++ b/gen/decoder.go @@ -14,7 +14,7 @@ import ( const minSliceBytes = 64 func (g *Generator) getStructDecoderName(t reflect.Type) string { - return g.functionName("decode_", t) + return g.functionName("decode", t) } var primitiveDecoders = map[reflect.Kind]string{ @@ -162,7 +162,7 @@ func (g *Generator) genTypeDecoder(t reflect.Type, out string, tags fieldTags, i } func (g *Generator) genStructFieldDecoder(t reflect.Type, f reflect.StructField) error { - jsonName := g.namer.GetJSONFieldName(t, f) + jsonName := g.fieldNamer.GetJSONFieldName(t, f) tags := parseFieldTags(f) if tags.omit { @@ -192,7 +192,7 @@ func (g *Generator) genRequiredFieldSet(t reflect.Type, f reflect.StructField) { } func (g *Generator) genRequiredFieldCheck(t reflect.Type, f reflect.StructField) { - jsonName := g.namer.GetJSONFieldName(t, f) + jsonName := g.fieldNamer.GetJSONFieldName(t, f) tags := parseFieldTags(f) if !tags.required { @@ -334,6 +334,7 @@ func (g *Generator) genStructUnmarshaller(t reflect.Type) error { typ := g.getType(t) if !g.noStdMarshalers { + fmt.Fprintln(g.out, "// UnmarshalJSON supports json.Unmarshaler interface") fmt.Fprintln(g.out, "func (v *"+typ+") UnmarshalJSON(data []byte) error {") fmt.Fprintln(g.out, " r := jlexer.Lexer{Data: data}") fmt.Fprintln(g.out, " "+fname+"(&r, v)") @@ -341,6 +342,7 @@ func (g *Generator) genStructUnmarshaller(t reflect.Type) error { fmt.Fprintln(g.out, "}") } + fmt.Fprintln(g.out, "// UnmarshalEasyJSON supports easyjson.Unmarshaler interface") fmt.Fprintln(g.out, "func (v *"+typ+") UnmarshalEasyJSON(l *jlexer.Lexer) {") fmt.Fprintln(g.out, " "+fname+"(l, v)") fmt.Fprintln(g.out, "}") diff --git a/gen/encoder.go b/gen/encoder.go index c263c21..f860e79 100644 --- a/gen/encoder.go +++ b/gen/encoder.go @@ -11,7 +11,7 @@ import ( ) func (g *Generator) getStructEncoderName(t reflect.Type) string { - return g.functionName("encode_", t) + return g.functionName("encode", t) } var primitiveEncoders = map[reflect.Kind]string{ @@ -147,14 +147,14 @@ func (g *Generator) genTypeEncoder(t reflect.Type, in string, tags fieldTags, in fmt.Fprintln(g.out, ws+" out.RawString(`null`)") fmt.Fprintln(g.out, ws+"} else {") fmt.Fprintln(g.out, ws+" out.RawByte('{')") - fmt.Fprintln(g.out, ws+" "+tmpVar+"_first := true") - fmt.Fprintln(g.out, ws+" for "+tmpVar+"_name, "+tmpVar+"_value := range "+in+" {") - fmt.Fprintln(g.out, ws+" if !"+tmpVar+"_first { out.RawByte(',') }") - fmt.Fprintln(g.out, ws+" "+tmpVar+"_first = false") - fmt.Fprintln(g.out, ws+" out.String(string("+tmpVar+"_name))") + fmt.Fprintln(g.out, ws+" "+tmpVar+"First := true") + fmt.Fprintln(g.out, ws+" for "+tmpVar+"Name, "+tmpVar+"Value := range "+in+" {") + fmt.Fprintln(g.out, ws+" if !"+tmpVar+"First { out.RawByte(',') }") + fmt.Fprintln(g.out, ws+" "+tmpVar+"First = false") + fmt.Fprintln(g.out, ws+" out.String(string("+tmpVar+"Name))") fmt.Fprintln(g.out, ws+" out.RawByte(':')") - g.genTypeEncoder(t.Elem(), tmpVar+"_value", tags, indent+2) + g.genTypeEncoder(t.Elem(), tmpVar+"Value", tags, indent+2) fmt.Fprintln(g.out, ws+" }") fmt.Fprintln(g.out, ws+" out.RawByte('}')") @@ -199,7 +199,7 @@ func (g *Generator) notEmptyCheck(t reflect.Type, v string) string { } func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField) error { - jsonName := g.namer.GetJSONFieldName(t, f) + jsonName := g.fieldNamer.GetJSONFieldName(t, f) tags := parseFieldTags(f) if tags.omit { @@ -262,6 +262,7 @@ func (g *Generator) genStructMarshaller(t reflect.Type) error { typ := g.getType(t) if !g.noStdMarshalers { + fmt.Fprintln(g.out, "// MarshalJSON supports json.Marshaler interface") fmt.Fprintln(g.out, "func (v "+typ+") MarshalJSON() ([]byte, error) {") fmt.Fprintln(g.out, " w := jwriter.Writer{}") fmt.Fprintln(g.out, " "+fname+"(&w, v)") @@ -269,6 +270,7 @@ func (g *Generator) genStructMarshaller(t reflect.Type) error { fmt.Fprintln(g.out, "}") } + fmt.Fprintln(g.out, "// MarshalEasyJSON supports easyjson.Marshaler interface") fmt.Fprintln(g.out, "func (v "+typ+") MarshalEasyJSON(w *jwriter.Writer) {") fmt.Fprintln(g.out, " "+fname+"(w, v)") fmt.Fprintln(g.out, "}") diff --git a/gen/generator.go b/gen/generator.go index 07e19fc..90d0743 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -20,6 +20,11 @@ type FieldNamer interface { GetJSONFieldName(t reflect.Type, f reflect.StructField) string } +// FuncNamer defines a policy for generating function names +type FunctionNamer interface { + GetName(keepFirst bool, parts ...string) string +} + // Generator generates the requested marshallers/unmarshallers. type Generator struct { out *bytes.Buffer @@ -27,13 +32,14 @@ type Generator struct { pkgName string pkgPath string buildTags string - funcPrefix string + hashString string varCounter int noStdMarshalers bool omitEmpty bool - namer FieldNamer + fieldNamer FieldNamer + functionNamer FunctionNamer // package path to local alias map for tracking imports imports map[string]string @@ -60,7 +66,9 @@ func NewGenerator(filename string) *Generator { pkgLexer: "jlexer", "encoding/json": "json", }, - namer: DefaultFieldNamer{}, + fieldNamer: DefaultFieldNamer{}, + functionNamer: UnderScoreFunctionNamer{}, + //functionNamer: CamelCaseFunctionNamer{}, marshallers: make(map[reflect.Type]bool), typesSeen: make(map[reflect.Type]bool), functionNames: make(map[string]reflect.Type), @@ -70,7 +78,7 @@ func NewGenerator(filename string) *Generator { // name clashes. hash := fnv.New32() hash.Write([]byte(filename)) - ret.funcPrefix = fmt.Sprintf("easyjson_%x_", hash.Sum32()) + ret.hashString = fmt.Sprintf("%x", hash.Sum32()) return ret } @@ -88,12 +96,22 @@ func (g *Generator) SetBuildTags(tags string) { // SetFieldNamer sets field naming strategy. func (g *Generator) SetFieldNamer(n FieldNamer) { - g.namer = n + g.fieldNamer = n } -// UseSnakeCase sets snake_case field naming strategy. -func (g *Generator) UseSnakeCase() { - g.namer = SnakeCaseFieldNamer{} +// UseSnakeCaseFieldNamer sets snake_case field naming strategy. +func (g *Generator) UseSnakeCaseFieldNamer() { + g.fieldNamer = SnakeCaseFieldNamer{} +} + +// SetFunctionNamer sets function naming strategy. +func (g *Generator) SetFunctionNamer(n FunctionNamer) { + g.functionNamer = n +} + +// UseCamelCaseFunctionNamer sets CamelCase function naming strategy. +func (g *Generator) UseCamelCaseFunctionNamer() { + g.functionNamer = CamelCaseFunctionNamer{} } // NoStdMarshalers instructs not to generate standard MarshalJSON/UnmarshalJSON @@ -243,7 +261,7 @@ func (g *Generator) uniqueVarName() string { // safeName escapes unsafe characters in pkg/type name and returns a string that can be used // in encoder/decoder names for the type. -func safeName(t reflect.Type) string { +func (g *Generator) safeName(t reflect.Type) string { name := t.PkgPath() if t.Name() == "" { name += "anonymous" @@ -251,15 +269,17 @@ func safeName(t reflect.Type) string { name += "." + t.Name() } - var ret []rune + parts := []string{} + part := []rune{} for _, c := range name { if unicode.IsLetter(c) || unicode.IsDigit(c) { - ret = append(ret, c) - } else { - ret = append(ret, '_') + part = append(part, c) + } else if len(part) > 0 { + parts = append(parts, string(part)) + part = []rune{} } } - return string(ret) + return g.functionNamer.GetName(false, parts...) } // functionName returns a function name for a given type with a given prefix. If a function @@ -267,8 +287,8 @@ func safeName(t reflect.Type) string { // // Method is used to track encoder/decoder names for the type. func (g *Generator) functionName(prefix string, t reflect.Type) string { - prefix = g.funcPrefix + prefix - name := prefix + safeName(t) + prefix = g.functionNamer.GetName(true, "easyjson", g.hashString, prefix) + name := g.functionNamer.GetName(true, prefix, g.safeName(t)) // Most of the names will be unique, try a shortcut first. if e, ok := g.functionNames[name]; !ok || e == t { @@ -362,3 +382,30 @@ func (SnakeCaseFieldNamer) GetJSONFieldName(t reflect.Type, f reflect.StructFiel return camelToSnake(f.Name) } + +// CamelCaseFunctionNamer implements FunctionNamer interface with CamelCase format +type CamelCaseFunctionNamer struct{} + +func (CamelCaseFunctionNamer) GetName(keepFirst bool, parts ...string) string { + buf := bytes.NewBufferString("") + for i, part := range parts { + if i == 0 && keepFirst { + buf.WriteString(part) + } else { + if len(part) > 0 { + buf.WriteString(strings.ToUpper(string(part[0]))) + } + if len(part) > 1 { + buf.WriteString(part[1:]) + } + } + } + return buf.String() +} + +// UnderScoreFunctionNamer implements FunctionNamer interface with under_score format +type UnderScoreFunctionNamer struct{} + +func (UnderScoreFunctionNamer) GetName(keepFirst bool, parts ...string) string { + return strings.Join(parts, "_") +} diff --git a/gen/generator_test.go b/gen/generator_test.go index 7388bec..64e164d 100644 --- a/gen/generator_test.go +++ b/gen/generator_test.go @@ -4,6 +4,13 @@ import ( "testing" ) +type functionNamerCase struct { + keepFirst bool + parts []string + camelCaseOut string + underScoreOut string +} + func TestCamelToSnake(t *testing.T) { for i, test := range []struct { In, Out string @@ -26,5 +33,35 @@ func TestCamelToSnake(t *testing.T) { t.Errorf("[%d] camelToSnake(%s) = %s; want %s", i, test.In, got, test.Out) } } +} +func getFunctionNamerCases() []functionNamerCase { + return []functionNamerCase{ + functionNamerCase{false, []string{}, "", ""}, + functionNamerCase{false, []string{"a"}, "A", "a"}, + functionNamerCase{false, []string{"simple", "example"}, "SimpleExample", "simple_example"}, + functionNamerCase{true, []string{"first", "example"}, "firstExample", "first_example"}, + functionNamerCase{false, []string{"some", "UPPER", "case"}, "SomeUPPERCase", "some_UPPER_case"}, + functionNamerCase{false, []string{"number", "123"}, "Number123", "number_123"}, + } +} + +func TestCamelCaseFunctionNamer(t *testing.T) { + namer := CamelCaseFunctionNamer{} + for i, test := range getFunctionNamerCases() { + got := namer.GetName(test.keepFirst, test.parts...) + if got != test.camelCaseOut { + t.Errorf("[%d] CamelCaseFunctionNamer.GetName(%v) = %s; want %s", i, test.parts, got, test.camelCaseOut) + } + } +} + +func TestUnderScoreFunctionNamer(t *testing.T) { + namer := UnderScoreFunctionNamer{} + for i, test := range getFunctionNamerCases() { + got := namer.GetName(test.keepFirst, test.parts...) + if got != test.underScoreOut { + t.Errorf("[%d] UnderScoreFunctionNamer.GetName(%v) = %s; want %s", i, test.parts, got, test.underScoreOut) + } + } } diff --git a/tests/basic_test.go b/tests/basic_test.go index a6a0413..7d18e3b 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -30,6 +30,7 @@ var testCases = []struct { {&unexportedStructValue, unexportedStructString}, {&excludedFieldValue, excludedFieldString}, {&mapsValue, mapsString}, + {&camelCasesFunctionsValue, camelCasesFunctionsString}, } func TestMarshal(t *testing.T) { diff --git a/tests/camel_case_functions.go b/tests/camel_case_functions.go new file mode 100644 index 0000000..94a44e8 --- /dev/null +++ b/tests/camel_case_functions.go @@ -0,0 +1,9 @@ +package tests + +//easyjson:json +type CamelCasesFunctions struct { + Field string +} + +var camelCasesFunctionsValue = CamelCasesFunctions{Field: "test"} +var camelCasesFunctionsString = `{"Field":"test"}`