From ccee3754c7b5d49ba2f4ca2bb0c944e2d95ad388 Mon Sep 17 00:00:00 2001 From: Phil Helmkamp Date: Sun, 22 Dec 2019 16:08:34 -0500 Subject: [PATCH 1/2] rename `map` -> `mapper` * move some logic from main.go to directive.go --- README.md | 6 +- directive/directive.go | 91 ++++++++++++++++++++++-------- internal/testdata/foobar/foo.go | 6 +- internal/testdata/person/person.go | 2 +- main.go | 38 +------------ 5 files changed, 74 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 509b284..7f3e292 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,12 @@ Go metaprogramming using struct tags + generate 1. Define struct tags - Format is `meta:"[directive1][;directive2]"`. For example: + Format is `meta:"directive[,option][;directive2]"`. For example: ```go type Foo struct { name, Desc string `meta:"getter"` size int `meta:"ptr;getter;setter"` - labels []string `meta:"setter;getter;filter"` + labels []string `meta:"setter;getter;mapper,int"` } ``` @@ -56,7 +56,7 @@ Generates a method that returns a copy of the slice, omitting elements that are Method name is `Filter`, followed by the name of the field unless `omitfield` is specified. Uses value receiver by default. -`map:$Type` (slice only) +`mapper,$type` (slice only) Generates a method that returns the result of mapping all elements to the specified type using the given function. Method name is of the form `MapFieldTo$Type`, or just `MapTo$Type` if `omitfield` is specified. diff --git a/directive/directive.go b/directive/directive.go index 2f1c0ca..7460e0f 100644 --- a/directive/directive.go +++ b/directive/directive.go @@ -15,7 +15,21 @@ const ( optReflect = "reflect" ) -// Target represents the target of the directive +var ( + runFuncs = map[string]runFunc{ + "ptr": ptr, + "getter": getter, + "setter": setter, + "filter": filter, + "mapper": mapper, + "sort": sort, + "stringer": stringer, + "new": new, + "equal": equal, + } +) + +// Target represents the target of the directive. type Target struct { MetaFile *meta.File RcvName, RcvType string @@ -23,14 +37,38 @@ type Target struct { FldType string } -// Ptr converts the receiver to a pointer for all subsequent directives -func Ptr(tgt *Target) { +type runFunc func(*Target, []string) + +// RunAll runs all of the given directives. +func RunAll(ds []string, tgt *Target) { + for i := range ds { + Run(ds[i], tgt) + } +} + +// Run runs the given directive. +func Run(d string, tgt *Target) { + opts := strings.Split(d, ",") + d = opts[0] + opts = opts[1:] + + run, ok := runFuncs[d] + if !ok { + log.Printf("Unknown directive: %s\n", d) + return + } + + run(tgt, opts) +} + +// ptr converts the receiver to a pointer for all subsequent directives. +func ptr(tgt *Target, opts []string) { tgt.RcvType = "*" + tgt.RcvType log.Printf("Using pointer receiver: %s\n", tgt.RcvType) } -// Getter generates a getter method for each name of the given field -func Getter(tgt *Target) { +// getter generates a getter method for each name of the given field. +func getter(tgt *Target, opts []string) { for _, fldNm := range tgt.FldNames { method := upperFirst(fldNm) if method == fldNm { @@ -50,8 +88,8 @@ func Getter(tgt *Target) { } } -// Setter generates a setter method for each name of the given field -func Setter(tgt *Target) { +// setter generates a setter method for each name of the given field. +func setter(tgt *Target, opts []string) { elemType := strings.TrimPrefix(tgt.FldType, "[]") arg := argName(tgt.RcvName, elemType) @@ -78,8 +116,8 @@ func Setter(tgt *Target) { } } -// Filter generates a filter method for each name of the given field -func Filter(tgt *Target, opts []string) { +// filter generates a filter method for each name of the given field. +func filter(tgt *Target, opts []string) { elemType := strings.TrimPrefix(tgt.FldType, "[]") var isOmitField bool @@ -112,15 +150,23 @@ func Filter(tgt *Target, opts []string) { } } -// Map generates a mapper method for each name of the given field -func Map(tgt *Target, result string, opts []string) { - elemType := strings.TrimPrefix(tgt.FldType, "[]") +// mapper generates a mapper method for each name of the given field. +func mapper(tgt *Target, opts []string) { + if len(opts) < 1 { + log.Print("skipping 'mapper' - must specify target type as first option\n") + return + } + + result := opts[0] + opts = opts[1:] sel := result if resSubs := strings.SplitN(result, ".", 2); len(resSubs) > 1 { sel = resSubs[1] } + elemType := strings.TrimPrefix(tgt.FldType, "[]") + var isOmitField bool for i := range opts { if opts[i] == optOmitField { @@ -130,11 +176,6 @@ func Map(tgt *Target, result string, opts []string) { } for _, fldNm := range tgt.FldNames { - if elemType == tgt.FldType { - log.Printf("'map' not valid for field %s.%s - must be a slice\n", tgt.RcvName, fldNm) - continue - } - var fldPart string if !isOmitField { fldPart = upperFirst(fldNm) @@ -155,8 +196,8 @@ func Map(tgt *Target, result string, opts []string) { } } -// Sort generates sort methods for the first name of the given field -func Sort(tgt *Target, opts []string) { +// sort generates sort methods for the first name of the given field. +func sort(tgt *Target, opts []string) { log.Print("Adding import: \"sort\"\n") tgt.MetaFile.Imports["sort"] = struct{}{} @@ -192,8 +233,8 @@ func Sort(tgt *Target, opts []string) { } } -// Stringer adds each name of the given field to the String() implementation -func Stringer(tgt *Target) { +// stringer adds each name of the given field to the String() implementation. +func stringer(tgt *Target, opts []string) { log.Print("Adding import: \"fmt\"\n") tgt.MetaFile.Imports["fmt"] = struct{}{} @@ -227,8 +268,8 @@ func Stringer(tgt *Target) { } } -// New adds each name of the given field to the New() implementation -func New(tgt *Target) { +// new adds each name of the given field to the New() implementation. +func new(tgt *Target, opts []string) { method := "New" + upperFirst(tgt.RcvType) for _, fldNm := range tgt.FldNames { log.Printf("Adding to method: %s\n", method) @@ -256,8 +297,8 @@ func New(tgt *Target) { } } -// Equal adds each name of the given field to the Equal() implementation -func Equal(tgt *Target, opts []string) { +// equal adds each name of the given field to the Equal() implementation. +func equal(tgt *Target, opts []string) { for _, fldNm := range tgt.FldNames { log.Print("Adding to method: Equal\n") found := tgt.MetaFile.FilterMethods( diff --git a/internal/testdata/foobar/foo.go b/internal/testdata/foobar/foo.go index 144e16f..00c3122 100644 --- a/internal/testdata/foobar/foo.go +++ b/internal/testdata/foobar/foo.go @@ -10,14 +10,14 @@ type Foo struct { NoMetaJSON string `json:"omitempty"` name, Desc string `meta:"new;getter;stringer"` size int `meta:"stringer;ptr;getter;setter"` - labels []string `meta:"new;setter;getter;filter;map:time.Time"` + labels []string `meta:"new;setter;getter;filter;mapper,time.Time"` stringer fmt.Stringer `meta:"setter"` } type Bar struct { name string `meta:"stringer;equal"` - foos []Foo `meta:"getter;setter;map:string"` + foos []Foo `meta:"getter;setter;mapper,string"` pairs map[string]float64 `meta:"getter;setter"` - times []time.Time `meta:"getter;setter;filter;map:int64;equal,reflect"` + times []time.Time `meta:"getter;setter;filter;mapper,int64;equal,reflect"` baz bool `meta:"setter"` } diff --git a/internal/testdata/person/person.go b/internal/testdata/person/person.go index 9510d35..78939dd 100644 --- a/internal/testdata/person/person.go +++ b/internal/testdata/person/person.go @@ -8,5 +8,5 @@ type Person struct { } type Persons struct { - Ps []Person `meta:"filter,omitfield;map:int,omitfield;sort,stringer"` + Ps []Person `meta:"filter,omitfield;mapper,int,omitfield;sort,stringer"` } diff --git a/main.go b/main.go index ac07bdc..4330997 100644 --- a/main.go +++ b/main.go @@ -149,43 +149,7 @@ func main() { fldTgt.FldNames[i] = f.Names[i].Name } - directives := strings.Split(metaTag, ";") - for _, d := range directives { - dSubs := strings.SplitN(d, ":", 2) - if len(dSubs) < 1 { - continue - } - - opts := strings.Split(dSubs[len(dSubs)-1], ",") - dSubs[len(dSubs)-1] = opts[0] - opts = opts[1:] - - switch dSubs[0] { - case "ptr": - directive.Ptr(&fldTgt) - case "getter": - directive.Getter(&fldTgt) - case "setter": - directive.Setter(&fldTgt) - case "filter": - directive.Filter(&fldTgt, opts) - case "map": - if len(dSubs) < 2 { - continue - } - directive.Map(&fldTgt, dSubs[1], opts) - case "sort": - directive.Sort(&fldTgt, opts) - case "stringer": - directive.Stringer(&fldTgt) - case "new": - directive.New(&fldTgt) - case "equal": - directive.Equal(&fldTgt, opts) - default: - log.Printf("Unknown directive: %s\n", d) - } - } + directive.RunAll(strings.Split(metaTag, ";"), &fldTgt) if fldPkg != "" { var importPath string From 563c362d6cb32748d7e1afdfc4d9e6cf696f6eb1 Mon Sep 17 00:00:00 2001 From: Phil Helmkamp Date: Sun, 22 Dec 2019 16:13:39 -0500 Subject: [PATCH 2/2] directive.go: rename new -> runNew --- directive/directive.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/directive/directive.go b/directive/directive.go index 7460e0f..a933a92 100644 --- a/directive/directive.go +++ b/directive/directive.go @@ -24,7 +24,7 @@ var ( "mapper": mapper, "sort": sort, "stringer": stringer, - "new": new, + "new": runNew, "equal": equal, } ) @@ -268,8 +268,8 @@ func stringer(tgt *Target, opts []string) { } } -// new adds each name of the given field to the New() implementation. -func new(tgt *Target, opts []string) { +// runNew adds each name of the given field to the New() implementation. +func runNew(tgt *Target, opts []string) { method := "New" + upperFirst(tgt.RcvType) for _, fldNm := range tgt.FldNames { log.Printf("Adding to method: %s\n", method)