Skip to content

Commit

Permalink
Merge pull request #25 from phelmkamp/sort
Browse files Browse the repository at this point in the history
Sort
  • Loading branch information
phelmkamp committed Dec 19, 2019
2 parents 8076578 + fb7f497 commit 444b962
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 15 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,24 @@ Uses value receiver by default.

`map:$Type` (slice only)

Generates a method that returns a copy of the slice, mapping elements to the specified type using the given function.
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.
Uses value receiver by default.

`sort` (slice only)

Generates `Len` and `Swap` methods to implement [sort.Interface](https://golang.org/pkg/sort/#Interface), along with a `Sort` convenience method. Include the `stringer` option to generate a `Less` method that compares elements by their string representations. Otherwise, a `Less` method must be implemented separately.
Uses value receivers by default.

Tip: Wrap the slice in a struct to take full advantage. See [person.Persons](internal/testdata/person/person.go) for an example.

`stringer`

Includes the field in the result of the generated `String` method. Uses value receiver by default.

`new`

Includes the field as an argument to the generated `NewType` method.
Includes the field as an argument to the generated `New$Type` method.

`ptr`

Expand Down
44 changes: 41 additions & 3 deletions directive/directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
)

const (
omitField = "omitfield"
optOmitField = "omitfield"
optStringer = "stringer"
)

// Target represents the target of the directive
Expand Down Expand Up @@ -82,7 +83,7 @@ func Filter(tgt *Target, opts []string) {

var isOmitField bool
for i := range opts {
if opts[i] == omitField {
if opts[i] == optOmitField {
isOmitField = true
break
}
Expand Down Expand Up @@ -121,7 +122,7 @@ func Map(tgt *Target, result string, opts []string) {

var isOmitField bool
for i := range opts {
if opts[i] == omitField {
if opts[i] == optOmitField {
isOmitField = true
break
}
Expand Down Expand Up @@ -153,6 +154,43 @@ 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) {
log.Print("Adding import: \"sort\"\n")
tgt.MetaFile.Imports["sort"] = struct{}{}

fldNm := tgt.FldNames[0]

log.Println("Adding method: Len")
log.Println("Adding method: Swap")
log.Println("Adding method: Sort")
sort := meta.Method{
RcvName: tgt.RcvName,
RcvType: tgt.RcvType,
FldName: fldNm,
Tmpl: "sort",
}
tgt.MetaFile.Methods = append(tgt.MetaFile.Methods, &sort)

var isStringer bool
for i := range opts {
if opts[i] == optStringer {
isStringer = true
break
}
}
if isStringer {
log.Println("Adding method: Less")
less := meta.Method{
RcvName: tgt.RcvName,
RcvType: tgt.RcvType,
FldName: fldNm,
Tmpl: "less",
}
tgt.MetaFile.Methods = append(tgt.MetaFile.Methods, &less)
}
}

// Stringer adds each name of the given field to the String() implementation
func Stringer(tgt *Target) {
log.Print("Adding import: \"fmt\"\n")
Expand Down
2 changes: 1 addition & 1 deletion internal/testdata/person/person.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ type Person struct {
}

type Persons struct {
Persons []Person `meta:"filter,omitfield;map:int,omitfield"`
Ps []Person `meta:"filter,omitfield;map:int,omitfield;sort,stringer"`
}
40 changes: 31 additions & 9 deletions internal/testdata/person/person_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,58 @@ package person

import (
"fmt"
"sort"
)

// String returns the "native" format of Person. Implements the fmt.Stringer interface.
func (p Person) String() string {
return fmt.Sprintf("%v", p.Name)
}

// Filter returns a copy of Persons, omitting elements that are rejected by the given function.
// Filter returns a copy of Ps, omitting elements that are rejected by the given function.
// The n argument determines the maximum number of elements to return (n < 1: all elements).
func (p Persons) Filter(fn func(Person) bool, n int) []Person {
cap := n
if n < 1 {
cap = len(p.Persons)
cap = len(p.Ps)
}
result := make([]Person, 0, cap)
for i := range p.Persons {
if fn(p.Persons[i]) {
if result = append(result, p.Persons[i]); len(result) >= cap {
for i := range p.Ps {
if fn(p.Ps[i]) {
if result = append(result, p.Ps[i]); len(result) >= cap {
return result
}
}
}
return result
}

// MapToInt returns a new slice with the results of calling the given function for each element of Persons.
// MapToInt returns a new slice with the results of calling the given function for each element of Ps.
func (p Persons) MapToInt(fn func(Person) int) []int {
result := make([]int, len(p.Persons))
for i := range p.Persons {
result[i] = fn(p.Persons[i])
result := make([]int, len(p.Ps))
for i := range p.Ps {
result[i] = fn(p.Ps[i])
}
return result
}

// Len is the number of elements in the collection.
func (p Persons) Len() int {
return len(p.Ps)
}

// Swap swaps the elements with indexes i and j.
func (p Persons) Swap(i, j int) {
p.Ps[i], p.Ps[j] = p.Ps[j], p.Ps[i]
}

// Sort is a convenience method.
func (p Persons) Sort() {
sort.Sort(p)
}

// Less reports whether the element with
// index i should sort before the element with index j.
func (p Persons) Less(i, j int) bool {
return p.Ps[i].String() < p.Ps[j].String()
}
32 changes: 32 additions & 0 deletions internal/testdata/person/person_meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package person

import (
"fmt"
"reflect"
"testing"
"time"
)

Expand Down Expand Up @@ -48,3 +50,33 @@ func Example() {
// [Ann Charlie]
// [36 12 25]
}

func TestPersons_Sort(t *testing.T) {
tests := []struct {
name string
p Persons
want []Person
}{
{
name: "abc",
p: Persons{[]Person{
{Name: "b"},
{Name: "c"},
{Name: "a"},
}},
want: []Person{
{Name: "a"},
{Name: "b"},
{Name: "c"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.p.Sort()
if !reflect.DeepEqual(tt.p.Ps, tt.want) {
t.Errorf("got = %v, want %v", tt.p.Ps, tt.want)
}
})
}
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ func main() {
continue
}
directive.Map(&fldTgt, dSubs[1], opts)
case "sort":
directive.Sort(&fldTgt, opts)
case "stringer":
directive.Stringer(&fldTgt)
case "new":
Expand Down
5 changes: 5 additions & 0 deletions meta/templates/less.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Less reports whether the element with
// index i should sort before the element with index j.
func ({{.RcvName}} {{.RcvType}}) Less(i, j int) bool {
return {{.RcvName}}.{{.FldName}}[i].String() < {{.RcvName}}.{{.FldName}}[j].String()
}
14 changes: 14 additions & 0 deletions meta/templates/sort.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Len is the number of elements in the collection.
func ({{.RcvName}} {{.RcvType}}) Len() int {
return len({{.RcvName}}.{{.FldName}})
}

// Swap swaps the elements with indexes i and j.
func ({{.RcvName}} {{.RcvType}}) Swap(i, j int) {
{{.RcvName}}.{{.FldName}}[i], {{.RcvName}}.{{.FldName}}[j] = {{.RcvName}}.{{.FldName}}[j], {{.RcvName}}.{{.FldName}}[i]
}

// Sort is a convenience method.
func ({{.RcvName}} {{.RcvType}}) Sort() {
sort.Sort({{.RcvName}})
}

0 comments on commit 444b962

Please sign in to comment.