~evanj/cms

27de4d9c907e2258a828fbfcda8f22b910df0f63 — Evan M Jones 5 months ago 40c0e4f
Feat(spaces): Added the ability to copy an entire space.
M go.mod => go.mod +3 -0
@@ 4,9 4,12 @@ go 1.12

require (
	git.sr.ht/~evanj/security v0.0.0-20200228044358-9b9bc6682997
	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
	github.com/go-playground/assert/v2 v2.0.1
	github.com/go-sql-driver/mysql v1.5.0
	github.com/golang/mock v1.4.3
	github.com/kr/pretty v0.2.0 // indirect
	golang.org/x/crypto v0.0.0-20200320181102-891825fb96df // indirect
	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
	golang.org/x/text v0.3.2 // indirect

M go.sum => go.sum +15 -0
@@ 2,6 2,8 @@ git.sr.ht/~evanj/security v0.0.0-20200228044358-9b9bc6682997 h1:fdAj8fR4mpS/OAve
git.sr.ht/~evanj/security v0.0.0-20200228044358-9b9bc6682997/go.mod h1:40791KVgThT97CT6mTsF4NUNPeX2BAVlsuH1RiiSrAs=
github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg=
github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=


@@ 10,20 12,33 @@ github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBY
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

M internal/c/content/content.go => internal/c/content/content.go +14 -11
@@ 2,6 2,7 @@ package content

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"


@@ 22,17 23,19 @@ import (

var (
	contentHTML = tmpl.MustParse("html/content.html")

	ErrNoLogin = errors.New("must be logged in")
)

type Content struct {
	*c.Controller
	log  *log.Logger
	db   dber
	e3   e3er
	hook hooker
	db   DBer
	e3   E3er
	hook Hooker
}

type dber interface {
type DBer interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)


@@ 45,15 48,15 @@ type dber interface {
	ContentPerContentType(space space.Space, ct contenttype.ContentType, page int, order db.OrderType, sortField string) ([]content.Content, error)
}

type e3er interface {
type E3er interface {
	Upload(ctx context.Context, public bool, filename string, file io.Reader) (string, error)
}

type hooker interface {
type Hooker interface {
	Do(space space.Space, content content.Content, ht webhook.HookType)
}

func New(log *log.Logger, db dber, e3 e3er, hook hooker) *Content {
func New(log *log.Logger, db DBer, e3 E3er, hook Hooker) *Content {
	return &Content{
		c.New(log, db),
		log,


@@ 66,7 69,7 @@ func New(log *log.Logger, db dber, e3 e3er, hook hooker) *Content {
func (c *Content) tree(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID, contentID string) (user.User, space.Space, contenttype.ContentType, content.Content, error) {
	user, err := c.GetCookieUser(w, r)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("must be logged in")
		return nil, nil, nil, nil, ErrNoLogin
	}

	space, err := c.db.SpaceGet(user, spaceID)


@@ 93,7 96,7 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {

	user, err := c.GetCookieUser(w, r)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, "must be logged in to create content")
		c.Error(w, r, http.StatusBadRequest, ErrNoLogin.Error())
		return
	}



@@ 182,7 185,7 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
func (c *Content) serve(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID, contentID string) {
	user, err := c.GetCookieUser(w, r)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, "must be logged in")
		c.Error(w, r, http.StatusBadRequest, ErrNoLogin.Error())
		return
	}



@@ 390,7 393,7 @@ func (c *Content) search(w http.ResponseWriter, r *http.Request) {
	user, err := c.GetCookieUser(w, r)
	if err != nil {
		c.log.Println(err)
		c.Error(w, r, http.StatusInternalServerError, "must be logged in")
		c.Error(w, r, http.StatusInternalServerError, ErrNoLogin.Error())
		return
	}


A internal/c/content/content_mock.go => internal/c/content/content_mock.go +264 -0
@@ 0,0 1,264 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: git.sr.ht/~evanj/cms/internal/c/content (interfaces: DBer,E3er,Hooker)

// Package content is a generated GoMock package.
package content

import (
	context "context"
	io "io"
	reflect "reflect"

	content "git.sr.ht/~evanj/cms/internal/m/content"
	contenttype "git.sr.ht/~evanj/cms/internal/m/contenttype"
	space "git.sr.ht/~evanj/cms/internal/m/space"
	user "git.sr.ht/~evanj/cms/internal/m/user"
	db "git.sr.ht/~evanj/cms/internal/s/db"
	hook "git.sr.ht/~evanj/cms/internal/s/hook"
	gomock "github.com/golang/mock/gomock"
)

// MockDBer is a mock of DBer interface.
type MockDBer struct {
	ctrl     *gomock.Controller
	recorder *MockDBerMockRecorder
}

// MockDBerMockRecorder is the mock recorder for MockDBer.
type MockDBerMockRecorder struct {
	mock *MockDBer
}

// NewMockDBer creates a new mock instance.
func NewMockDBer(ctrl *gomock.Controller) *MockDBer {
	mock := &MockDBer{ctrl: ctrl}
	mock.recorder = &MockDBerMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDBer) EXPECT() *MockDBerMockRecorder {
	return m.recorder
}

// ContentDelete mocks base method.
func (m *MockDBer) ContentDelete(arg0 space.Space, arg1 contenttype.ContentType, arg2 content.Content) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentDelete", arg0, arg1, arg2)
	ret0, _ := ret[0].(error)
	return ret0
}

// ContentDelete indicates an expected call of ContentDelete.
func (mr *MockDBerMockRecorder) ContentDelete(arg0, arg1, arg2 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentDelete", reflect.TypeOf((*MockDBer)(nil).ContentDelete), arg0, arg1, arg2)
}

// ContentGet mocks base method.
func (m *MockDBer) ContentGet(arg0 space.Space, arg1 contenttype.ContentType, arg2 string) (content.Content, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentGet", arg0, arg1, arg2)
	ret0, _ := ret[0].(content.Content)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentGet indicates an expected call of ContentGet.
func (mr *MockDBerMockRecorder) ContentGet(arg0, arg1, arg2 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentGet", reflect.TypeOf((*MockDBer)(nil).ContentGet), arg0, arg1, arg2)
}

// ContentNew mocks base method.
func (m *MockDBer) ContentNew(arg0 space.Space, arg1 contenttype.ContentType, arg2 []db.ContentNewParam) (content.Content, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentNew", arg0, arg1, arg2)
	ret0, _ := ret[0].(content.Content)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentNew indicates an expected call of ContentNew.
func (mr *MockDBerMockRecorder) ContentNew(arg0, arg1, arg2 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentNew", reflect.TypeOf((*MockDBer)(nil).ContentNew), arg0, arg1, arg2)
}

// ContentPerContentType mocks base method.
func (m *MockDBer) ContentPerContentType(arg0 space.Space, arg1 contenttype.ContentType, arg2 int, arg3 db.OrderType, arg4 string) ([]content.Content, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentPerContentType", arg0, arg1, arg2, arg3, arg4)
	ret0, _ := ret[0].([]content.Content)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentPerContentType indicates an expected call of ContentPerContentType.
func (mr *MockDBerMockRecorder) ContentPerContentType(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentPerContentType", reflect.TypeOf((*MockDBer)(nil).ContentPerContentType), arg0, arg1, arg2, arg3, arg4)
}

// ContentSearch mocks base method.
func (m *MockDBer) ContentSearch(arg0 space.Space, arg1 contenttype.ContentType, arg2, arg3 string, arg4 int) ([]content.Content, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentSearch", arg0, arg1, arg2, arg3, arg4)
	ret0, _ := ret[0].([]content.Content)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentSearch indicates an expected call of ContentSearch.
func (mr *MockDBerMockRecorder) ContentSearch(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentSearch", reflect.TypeOf((*MockDBer)(nil).ContentSearch), arg0, arg1, arg2, arg3, arg4)
}

// ContentTypeGet mocks base method.
func (m *MockDBer) ContentTypeGet(arg0 space.Space, arg1 string) (contenttype.ContentType, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentTypeGet", arg0, arg1)
	ret0, _ := ret[0].(contenttype.ContentType)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentTypeGet indicates an expected call of ContentTypeGet.
func (mr *MockDBerMockRecorder) ContentTypeGet(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentTypeGet", reflect.TypeOf((*MockDBer)(nil).ContentTypeGet), arg0, arg1)
}

// ContentUpdate mocks base method.
func (m *MockDBer) ContentUpdate(arg0 space.Space, arg1 contenttype.ContentType, arg2 content.Content, arg3 []db.ContentNewParam, arg4 []db.ContentUpdateParam) (content.Content, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ContentUpdate", arg0, arg1, arg2, arg3, arg4)
	ret0, _ := ret[0].(content.Content)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// ContentUpdate indicates an expected call of ContentUpdate.
func (mr *MockDBerMockRecorder) ContentUpdate(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentUpdate", reflect.TypeOf((*MockDBer)(nil).ContentUpdate), arg0, arg1, arg2, arg3, arg4)
}

// SpaceGet mocks base method.
func (m *MockDBer) SpaceGet(arg0 user.User, arg1 string) (space.Space, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "SpaceGet", arg0, arg1)
	ret0, _ := ret[0].(space.Space)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// SpaceGet indicates an expected call of SpaceGet.
func (mr *MockDBerMockRecorder) SpaceGet(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpaceGet", reflect.TypeOf((*MockDBer)(nil).SpaceGet), arg0, arg1)
}

// UserGet mocks base method.
func (m *MockDBer) UserGet(arg0, arg1 string) (user.User, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "UserGet", arg0, arg1)
	ret0, _ := ret[0].(user.User)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// UserGet indicates an expected call of UserGet.
func (mr *MockDBerMockRecorder) UserGet(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserGet", reflect.TypeOf((*MockDBer)(nil).UserGet), arg0, arg1)
}

// UserGetFromToken mocks base method.
func (m *MockDBer) UserGetFromToken(arg0 string) (user.User, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "UserGetFromToken", arg0)
	ret0, _ := ret[0].(user.User)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// UserGetFromToken indicates an expected call of UserGetFromToken.
func (mr *MockDBerMockRecorder) UserGetFromToken(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserGetFromToken", reflect.TypeOf((*MockDBer)(nil).UserGetFromToken), arg0)
}

// MockE3er is a mock of E3er interface.
type MockE3er struct {
	ctrl     *gomock.Controller
	recorder *MockE3erMockRecorder
}

// MockE3erMockRecorder is the mock recorder for MockE3er.
type MockE3erMockRecorder struct {
	mock *MockE3er
}

// NewMockE3er creates a new mock instance.
func NewMockE3er(ctrl *gomock.Controller) *MockE3er {
	mock := &MockE3er{ctrl: ctrl}
	mock.recorder = &MockE3erMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockE3er) EXPECT() *MockE3erMockRecorder {
	return m.recorder
}

// Upload mocks base method.
func (m *MockE3er) Upload(arg0 context.Context, arg1 bool, arg2 string, arg3 io.Reader) (string, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Upload", arg0, arg1, arg2, arg3)
	ret0, _ := ret[0].(string)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// Upload indicates an expected call of Upload.
func (mr *MockE3erMockRecorder) Upload(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upload", reflect.TypeOf((*MockE3er)(nil).Upload), arg0, arg1, arg2, arg3)
}

// MockHooker is a mock of Hooker interface.
type MockHooker struct {
	ctrl     *gomock.Controller
	recorder *MockHookerMockRecorder
}

// MockHookerMockRecorder is the mock recorder for MockHooker.
type MockHookerMockRecorder struct {
	mock *MockHooker
}

// NewMockHooker creates a new mock instance.
func NewMockHooker(ctrl *gomock.Controller) *MockHooker {
	mock := &MockHooker{ctrl: ctrl}
	mock.recorder = &MockHookerMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHooker) EXPECT() *MockHookerMockRecorder {
	return m.recorder
}

// Do mocks base method.
func (m *MockHooker) Do(arg0 space.Space, arg1 content.Content, arg2 hook.HookType) {
	m.ctrl.T.Helper()
	m.ctrl.Call(m, "Do", arg0, arg1, arg2)
}

// Do indicates an expected call of Do.
func (mr *MockHookerMockRecorder) Do(arg0, arg1, arg2 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHooker)(nil).Do), arg0, arg1, arg2)
}

M internal/c/content/content_test.go => internal/c/content/content_test.go +82 -0
@@ 1,1 1,83 @@
package content_test

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"git.sr.ht/~evanj/cms/internal/c/content"
	"github.com/bmizerany/assert"
	gomock "github.com/golang/mock/gomock"
)

func TestContentNewNoAuth(t *testing.T) {
	t.Parallel()

	routes := []string{
		"/content/new",
		"/content/delete",
		"/content/search",
		"/content/update",
	}

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	c := content.New(
		log.New(bytes.NewBufferString(os.DevNull), "", 0),
		content.NewMockDBer(ctrl),
		content.NewMockE3er(ctrl),
		content.NewMockHooker(ctrl),
	)

	for _, route := range routes {
		// Create body of post request.
		requestBody := bytes.Buffer{}
		formwriter := multipart.NewWriter(&requestBody)

		// Add a field.
		fieldwriter, _ := formwriter.CreateFormField("access")
		fieldwriter.Write([]byte("public"))
		defer formwriter.Close()

		// Send req.
		req, _ := http.NewRequest(http.MethodPost, route, &requestBody)

		// Read req.
		w := httptest.NewRecorder()
		c.ServeHTTP(w, req)
		resp := w.Result()
		bod, _ := ioutil.ReadAll(resp.Body)

		assert.Equal(t, content.ErrNoLogin.Error(), string(bod))
		assert.NotEqual(t, http.StatusOK, resp.StatusCode)
	}
}

func TestContentNotFound(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	c := content.New(
		log.New(bytes.NewBufferString(os.DevNull), "", 0),
		content.NewMockDBer(ctrl),
		content.NewMockE3er(ctrl),
		content.NewMockHooker(ctrl),
	)

	// Send req.
	req, _ := http.NewRequest(http.MethodGet, "/content/whatever", nil)

	// Read req.
	w := httptest.NewRecorder()
	c.ServeHTTP(w, req)
	resp := w.Result()
	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}

M internal/c/space/space.go => internal/c/space/space.go +33 -0
@@ 30,6 30,7 @@ type dber interface {
	UserGetFromToken(token string) (user.User, error)
	SpaceNew(user user.User, name, desc string) (space.Space, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	SpaceCopy(user user.User, space space.Space, name, desc string) (space.Space, error)
	SpaceDelete(space space.Space) error
	ContentTypesPerSpace(space space.Space, page int) ([]contenttype.ContentType, error)
	HookPerSpace(space space.Space, page int) ([]hook.Hook, error)


@@ 106,6 107,35 @@ func (s *Space) create(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Space) copy(w http.ResponseWriter, r *http.Request) {
	spaceID := r.FormValue("space")
	name := r.FormValue("name")
	desc := r.FormValue("desc")

	user, err := s.GetCookieUser(w, r)
	if err != nil {
		s.Error(w, r, http.StatusBadRequest, "must be logged in to create space")
		return
	}

	spacePrev, err := s.db.SpaceGet(user, spaceID)
	if err != nil {
		s.Error(w, r, http.StatusNotFound, "failed to find space")
		return
	}

	spaceNext, err := s.db.SpaceCopy(user, spacePrev, name, desc)
	if err != nil {
		s.log.Println(err)
		s.Error(w, r, http.StatusBadRequest, err.Error())
		return
	}

	url := fmt.Sprintf("/space/%s", spaceNext.ID())
	s.log.Println("successfully created new space for user", user.Name(), "redirecting to", url)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (s *Space) delete(w http.ResponseWriter, r *http.Request) {
	spaceID := r.FormValue("space")



@@ 137,6 167,9 @@ func (s *Space) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	case "/space/new":
		s.create(w, r)
		return
	case "/space/copy":
		s.copy(w, r)
		return
	case "/space/delete":
		s.delete(w, r)
		return

M internal/s/db/content.go => internal/s/db/content.go +24 -0
@@ 993,3 993,27 @@ func (c *ContentValue) UnmarshalJSON(b []byte) error {

	return nil
}

type contentIter struct {
	db        *DB
	space     space.Space
	ct        contenttype.ContentType
	sortField string
	page      int
}

func (db *DB) ContentIter(space space.Space, ct contenttype.ContentType, sortField string) *contentIter {
	return &contentIter{db, space, ct, sortField, 0}
}

func (iter *contentIter) Next() (ret []content.Content, hasMore bool, err error) {
	ret, err = iter.db.ContentPerContentType(iter.space, iter.ct, iter.page, OrderAsc, iter.sortField)
	if err != nil {
		return
	}

	hasMore = len(ret) > 0
	iter.page++

	return
}

M internal/s/db/contenttype.go => internal/s/db/contenttype.go +41 -5
@@ 1,6 1,7 @@
package db

import (
	"database/sql"
	"fmt"
	"strconv"
	"strings"


@@ 187,16 188,14 @@ func (db *DB) ContentTypesPerSpace(space space.Space, page int) ([]contenttype.C
	return ret, nil
}

func (db *DB) ContentTypeGet(space space.Space, contenttypeID string) (contenttype.ContentType, error) {
func (db *DB) contentTypeGet(t *sql.Tx, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	var ct ContentType
	if err := db.QueryRow(queryFindContentTypeByIDAndSpace, contenttypeID, space.ID()).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
		db.log.Println(err)
	if err := t.QueryRow(queryFindContentTypeByIDAndSpace, contenttypeID, space.ID()).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
		return nil, fmt.Errorf("failed to find contenttype for space")
	}

	rows, err := db.Query(queryFindValueTypes, ct.ContentTypeID)
	rows, err := t.Query(queryFindValueTypes, ct.ContentTypeID)
	if err != nil {
		db.log.Println(err)
		return nil, fmt.Errorf("failed to find field(s)")
	}
	for rows.Next() {


@@ 210,6 209,21 @@ func (db *DB) ContentTypeGet(space space.Space, contenttypeID string) (contentty
	return &ct, nil
}

func (db *DB) ContentTypeGet(space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	ct, err := db.contentTypeGet(t, space, contenttypeID)
	if err != nil {
		return nil, err
	}

	return ct, t.Commit()
}

// TODO: Consolidate with other list function here. They are the same except for
// the query used.
func (db *DB) ContentTypeSearch(space space.Space, query string, page int) ([]contenttype.ContentType, error) {


@@ 276,3 290,25 @@ func (f *ContentTypeField) Name() string {
func (f *ContentTypeField) Type() string {
	return f.FieldType
}

type contentTypeIter struct {
	db    *DB
	space space.Space
	page  int
}

func (db *DB) ContentTypeIter(space space.Space) *contentTypeIter {
	return &contentTypeIter{db, space, 0}
}

func (iter *contentTypeIter) Next() (ret []contenttype.ContentType, hasMore bool, err error) {
	ret, err = iter.db.ContentTypesPerSpace(iter.space, iter.page)
	if err != nil {
		return
	}

	hasMore = len(ret) > 0
	iter.page++

	return
}

M internal/s/db/db.go => internal/s/db/db.go +5 -4
@@ 181,8 181,8 @@ func (db *DB) createTables() []error {
		CREATE TABLE cms_value (
			ID INTEGER PRIMARY KEY AUTO_INCREMENT,
			CONTENT_ID INTEGER NOT NULL,
			CONTENTTYPE_TO_VALUETYPE_ID INTEGER NOT NULL,
			VALUE_ID INTEGER NOT NULL, -- Should be a foreign key but impossible to make it for two+ tables.
			CONTENTTYPE_TO_VALUETYPE_ID INTEGER NOT NULL, -- Should be a foreign key but impossible to make it for two+ tables.
			VALUE_ID INTEGER NOT NULL, 
			FOREIGN KEY(CONTENT_ID) REFERENCES cms_content(ID) ON DELETE CASCADE,
			FOREIGN KEY(CONTENTTYPE_TO_VALUETYPE_ID) REFERENCES cms_contenttype_to_valuetype(ID) ON DELETE CASCADE
		);


@@ 263,9 263,10 @@ func (db *DB) createTables() []error {
	_, err = db.Exec(`
		CREATE TABLE cms_hooks (
			ID INTEGER PRIMARY KEY AUTO_INCREMENT,
			URL varchar(256) UNIQUE NOT NULL,
			URL varchar(256) NOT NULL,
			SPACE_ID INTEGER NOT NULL,
			FOREIGN KEY(SPACE_ID) REFERENCES cms_space(ID) ON DELETE CASCADE
			FOREIGN KEY(SPACE_ID) REFERENCES cms_space(ID) ON DELETE CASCADE,
			CONSTRAINT UNIQUEPERCONN UNIQUE(SPACE_ID, URL)
		);
	`)
	if err != nil {

M internal/s/db/space.go => internal/s/db/space.go +167 -16
@@ 1,7 1,9 @@
package db

import (
	"database/sql"
	"fmt"
	"strconv"

	"git.sr.ht/~evanj/cms/internal/m/space"
	"git.sr.ht/~evanj/cms/internal/m/user"


@@ 21,56 23,205 @@ var (
	queryCreateNewUserToSpace = `INSERT INTO cms_user_to_space (USER_ID, SPACE_ID) VALUES (?, ?);`
	queryFindUserToSpace      = `SELECT SPACE_ID FROM cms_user_to_space WHERE USER_ID = ? AND SPACE_ID = ?;`
	queryFindSpacesByUser     = `SELECT DISTINCT cms_space.ID, NAME, DESCRIPTION FROM cms_space JOIN cms_user_to_space ON cms_space.ID = cms_user_to_space.SPACE_ID WHERE USER_ID = ? LIMIT ? OFFSET ?;`

	copyUsersQuery = `
		INSERT INTO cms_user_to_space (USER_ID, SPACE_ID)
		SELECT USER_ID, ?
		FROM cms_user_to_space
		WHERE SPACE_ID=?
	`

	copyHooksQuery = `
		INSERT INTO cms_hooks (URL, SPACE_ID)
		SELECT URL, ?
		FROM cms_hooks 
		WHERE SPACE_ID=?
	`

	copyContentTypeQuery = `
		INSERT INTO cms_contenttype (NAME, SPACE_ID)
		SELECT NAME, ?
		FROM cms_contenttype 
		WHERE ID=?
	`

	copyValueTypeQuery = `
		INSERT INTO cms_contenttype_to_valuetype (NAME, CONTENTTYPE_ID, VALUETYPE_ID)
		SELECT NAME, ?, VALUETYPE_ID
		FROM cms_contenttype_to_valuetype 
		WHERE CONTENTTYPE_ID=?
	`
)

func (db *DB) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	res, err := db.Exec(queryCreateNewSpace, name, desc)
func (db *DB) spaceNew(t *sql.Tx, user user.User, name, desc string) (space.Space, error) {
	res, err := t.Exec(queryCreateNewSpace, name, desc)
	if err != nil {
		db.log.Println("db.Exec", err)
		return nil, fmt.Errorf("space '%s' already exists", name)
	}

	id, err := res.LastInsertId()
	if err != nil {
		db.log.Println("res.LastInsertId", err)
		return nil, fmt.Errorf("failed to create space")
	}

	var space Space
	if err := db.QueryRow(queryFindSpaceByID, id).Scan(&space.SpaceID, &space.SpaceName, &space.SpaceDesc); err != nil {
		db.log.Println("db.QueryRow", err)
	if err := t.QueryRow(queryFindSpaceByID, id).Scan(&space.SpaceID, &space.SpaceName, &space.SpaceDesc); err != nil {
		return nil, fmt.Errorf("failed to find space created")
	}

	if _, err := db.Exec(queryCreateNewUserToSpace, user.ID(), space.ID()); err != nil {
		db.log.Println("big problem, failed to connect user to space", err)
		if _, err := db.Exec(queryDeleteSpaceByID, space.ID()); err != nil {
			db.log.Println("even bigger problem, failed to delete orphan space", err)
		}
	if _, err := t.Exec(queryCreateNewUserToSpace, user.ID(), space.ID()); err != nil {
		return nil, fmt.Errorf("failed to attach space to user")
	}

	return &space, nil
}

func (db *DB) SpaceGet(user user.User, spaceID string) (space.Space, error) {
func (db *DB) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	space, err := db.spaceNew(t, user, name, desc)
	if err != nil {
		return nil, err
	}

	return space, t.Commit()
}

func (db *DB) SpaceCopy(user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	// TODO: NOTE: Normal transactions don't work for us, they are too nested.
	// Figure out a workaround for this.
	// t, err := db.Begin()
	// if err != nil {
	// 	return nil, err
	// }
	// defer t.Rollback()
	t := db

	res, err := t.Exec(queryCreateNewSpace, name, desc)
	if err != nil {
		return nil, err
	}

	nextID, err := res.LastInsertId()
	if err != nil {
		return nil, err
	}

	// Copy all users.
	if _, err := t.Exec(copyUsersQuery, nextID, prevS.ID()); err != nil {
		return nil, err
	}

	// Copy all webhooks.
	if _, err := t.Exec(copyHooksQuery, nextID, prevS.ID()); err != nil {
		return nil, err
	}

	next, err := db.SpaceGet(user, strconv.FormatInt(nextID, 10))
	if err != nil {
		return nil, err
	}

	// Copy all content types and their value types.
	// Copy all contents and their values.
	iter := db.ContentTypeIter(prevS)
	for {
		types, hasMore, err := iter.Next()
		if err != nil {
			return nil, err
		}

		if !hasMore {
			break
		}

		for _, prevCT := range types {
			// Copy content type.
			res, err := t.Exec(copyContentTypeQuery, nextID, prevCT.ID())
			if err != nil {
				return nil, err
			}

			ctID, err := res.LastInsertId()
			if err != nil {
				return nil, err
			}

			// Copy value type.
			if _, err := t.Exec(copyValueTypeQuery, ctID, prevCT.ID()); err != nil {
				return nil, err
			}

			ct, err := db.ContentTypeGet(next, strconv.FormatInt(ctID, 10))
			if err != nil {
				return nil, err
			}

			// Copy all contents and their values.
			iter := db.ContentIter(prevS, prevCT, "name") // TODO: What is this sort type for?
			for {
				contents, hasMore, err := iter.Next()
				if err != nil {
					return nil, err
				}

				if !hasMore {
					break
				}

				for _, prevC := range contents {
					params := make([]ContentNewParam, len(prevC.Values()))
					for i, prevV := range prevC.Values() {
						params[i] = ContentNewParam{prevV.Type(), prevV.Name(), prevV.Value()}
					}

					if _, err := db.ContentNew(next, ct, params); err != nil {
						return nil, err
					}
				} // Contents
			} // ContentsIter
		} // ContentTypes
	} // ContentTypesIter

	// return next, t.Commit()
	return next, nil
}

func (db *DB) spaceGet(t *sql.Tx, user user.User, spaceID string) (space.Space, error) {
	var id string

	if err := db.QueryRow(queryFindUserToSpace, user.ID(), spaceID).Scan(&id); err != nil {
		db.log.Println("db.QueryRow", err, "spaceID:", spaceID)
	if err := t.QueryRow(queryFindUserToSpace, user.ID(), spaceID).Scan(&id); err != nil {
		return nil, fmt.Errorf("failed to find space for user")
	}

	var space Space
	err := db.QueryRow(queryFindSpaceByID, id).Scan(&space.SpaceID, &space.SpaceName, &space.SpaceDesc)
	err := t.QueryRow(queryFindSpaceByID, id).Scan(&space.SpaceID, &space.SpaceName, &space.SpaceDesc)
	if err != nil {
		db.log.Println("db.Exec", err)
		return nil, fmt.Errorf("failed to find space")
	}

	return &space, nil
}

func (db *DB) SpaceGet(user user.User, spaceID string) (space.Space, error) {
	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	space, err := db.spaceGet(t, user, spaceID)
	if err != nil {
		return nil, err
	}

	return space, t.Commit()
}

func (db *DB) SpaceDelete(space space.Space) error {
	t, err := db.Begin()
	if err != nil {

M internal/s/tmpl/css/main.css => internal/s/tmpl/css/main.css +4 -0
@@ 63,3 63,7 @@ header nav li {
span.tox-statusbar__branding {
  display: none;
}

summary {
  cursor: pointer;
}

M internal/s/tmpl/html/index.html => internal/s/tmpl/html/index.html +1 -1
@@ 28,7 28,7 @@
              <input id='create-desc' required type=text name=desc placeholder=description />
              <br>
              <br>
            <input type=submit value=Create />
              <input type=submit value=Create />
            </fieldset>
            <br>
          </form>

M internal/s/tmpl/html/space.html => internal/s/tmpl/html/space.html +22 -0
@@ 81,6 81,28 @@
        </form>
      </details>

      <details>
        <summary>Copy Space</summary>
        <form method=POST action='/space/copy' enctype='multipart/form-data'>
          <input required type=hidden name=space value="{{ .Space.ID }}" />
          <br>
          <fieldset>
            <label for='create-name'>Name</label>
            <br>
            <input id='create-name' autofocus required type=text name=name placeholder=name />
            <br>
            <br>
            <label for='create-desc'>Description</label>
            <br>
            <input id='create-desc' required type=text name=desc placeholder=description />
            <br>
            <br>
            <input type=submit value=Create />
          </fieldset>
          <br>
        </form>
      </details>

      <h2>Browse Content By Type</h2>
      {{ if .ContentTypes }}
        <ul>

M internal/s/tmpl/tmpls_embed.go => internal/s/tmpl/tmpls_embed.go +27 -1
@@ 72,6 72,10 @@ header nav li {
span.tox-statusbar__branding {
  display: none;
}

summary {
  cursor: pointer;
}
`

	tmpls["css/mvp.css"] = `:root {


@@ 921,7 925,7 @@ blockquote footer {
              <input id='create-desc' required type=text name=desc placeholder=description />
              <br>
              <br>
            <input type=submit value=Create />
              <input type=submit value=Create />
            </fieldset>
            <br>
          </form>


@@ 1087,6 1091,28 @@ blockquote footer {
        </form>
      </details>

      <details>
        <summary>Copy Space</summary>
        <form method=POST action='/space/copy' enctype='multipart/form-data'>
          <input required type=hidden name=space value="{{ .Space.ID }}" />
          <br>
          <fieldset>
            <label for='create-name'>Name</label>
            <br>
            <input id='create-name' autofocus required type=text name=name placeholder=name />
            <br>
            <br>
            <label for='create-desc'>Description</label>
            <br>
            <input id='create-desc' required type=text name=desc placeholder=description />
            <br>
            <br>
            <input type=submit value=Create />
          </fieldset>
          <br>
        </form>
      </details>

      <h2>Browse Content By Type</h2>
      {{ if .ContentTypes }}
        <ul>

A vendor/github.com/bmizerany/assert/.gitignore => vendor/github.com/bmizerany/assert/.gitignore +7 -0
@@ 0,0 1,7 @@
_go_.*
_gotest_.*
_obj
_test
_testmain.go
*.out
*.[568]

A vendor/github.com/bmizerany/assert/README.md => vendor/github.com/bmizerany/assert/README.md +47 -0
@@ 0,0 1,47 @@
# NO LONGER MAINTAINED - Just use Go's testing package.

# Assert (c) Blake Mizerany and Keith Rarick -- MIT LICENCE

## Assertions for Go tests

## Install

    $ go get github.com/bmizerany/assert

## Use

**point.go**

    package point

    type Point struct {
        x, y int
    }

**point_test.go**


    package point

    import (
        "testing"
        "github.com/bmizerany/assert"
    )

    func TestAsserts(t *testing.T) {
        p1 := Point{1, 1}
        p2 := Point{2, 1}

        assert.Equal(t, p1, p2)
    }

**output**
    $ go test
     --- FAIL: TestAsserts (0.00 seconds)
	 assert.go:15: /Users/flavio.barbosa/dev/stewie/src/point_test.go:12
         assert.go:24: ! X: 1 != 2
	 FAIL

## Docs

    http://github.com/bmizerany/assert

A vendor/github.com/bmizerany/assert/assert.go => vendor/github.com/bmizerany/assert/assert.go +76 -0
@@ 0,0 1,76 @@
package assert
// Testing helpers for doozer.

import (
	"github.com/kr/pretty"
	"reflect"
	"testing"
	"runtime"
	"fmt"
)

func assert(t *testing.T, result bool, f func(), cd int) {
	if !result {
		_, file, line, _ := runtime.Caller(cd + 1)
		t.Errorf("%s:%d", file, line)
		f()
		t.FailNow()
	}
}

func equal(t *testing.T, exp, got interface{}, cd int, args ...interface{}) {
	fn := func() {
		for _, desc := range pretty.Diff(exp, got) {
			t.Error("!", desc)
		}
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	result := reflect.DeepEqual(exp, got)
	assert(t, result, fn, cd+1)
}

func tt(t *testing.T, result bool, cd int, args ...interface{}) {
	fn := func() {
		t.Errorf("!  Failure")
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	assert(t, result, fn, cd+1)
}

func T(t *testing.T, result bool, args ...interface{}) {
	tt(t, result, 1, args...)
}

func Tf(t *testing.T, result bool, format string, args ...interface{}) {
	tt(t, result, 1, fmt.Sprintf(format, args...))
}

func Equal(t *testing.T, exp, got interface{}, args ...interface{}) {
	equal(t, exp, got, 1, args...)
}

func Equalf(t *testing.T, exp, got interface{}, format string, args ...interface{}) {
	equal(t, exp, got, 1, fmt.Sprintf(format, args...))
}

func NotEqual(t *testing.T, exp, got interface{}, args ...interface{}) {
	fn := func() {
		t.Errorf("!  Unexpected: <%#v>", exp)
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	result := !reflect.DeepEqual(exp, got)
	assert(t, result, fn, 1)
}

func Panic(t *testing.T, err interface{}, fn func()) {
	defer func() {
		equal(t, err, recover(), 3)
	}()
	fn()
}

A vendor/github.com/golang/mock/AUTHORS => vendor/github.com/golang/mock/AUTHORS +12 -0
@@ 0,0 1,12 @@
# This is the official list of GoMock authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.

# Names should be added to this file as
#	Name or Organization <email address>
# The email address is not required for organizations.

# Please keep the list sorted.

Alex Reece <awreece@gmail.com>
Google Inc.

A vendor/github.com/golang/mock/CONTRIBUTORS => vendor/github.com/golang/mock/CONTRIBUTORS +37 -0
@@ 0,0 1,37 @@
# This is the official list of people who can contribute (and typically
# have contributed) code to the gomock repository.
# The AUTHORS file lists the copyright holders; this file
# lists people.  For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
#     http://code.google.com/legal/individual-cla-v1.0.html
#     http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.

# Names should be added to this file like so:
#     Name <email address>
#
# An entry with two email addresses specifies that the
# first address should be used in the submit logs and
# that the second address should be recognized as the
# same person when interacting with Rietveld.

# Please keep the list sorted.

Aaron Jacobs <jacobsa@google.com> <aaronjjacobs@gmail.com>
Alex Reece <awreece@gmail.com>
David Symonds <dsymonds@golang.org>
Ryan Barrett <ryanb@google.com>

A vendor/github.com/golang/mock/LICENSE => vendor/github.com/golang/mock/LICENSE +202 -0
@@ 0,0 1,202 @@

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

A vendor/github.com/golang/mock/gomock/call.go => vendor/github.com/golang/mock/gomock/call.go +427 -0
@@ 0,0 1,427 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gomock

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

// Call represents an expected call to a mock.
type Call struct {
	t TestHelper // for triggering test failures on invalid call setup

	receiver   interface{}  // the receiver of the method call
	method     string       // the name of the method
	methodType reflect.Type // the type of the method
	args       []Matcher    // the args
	origin     string       // file and line number of call setup

	preReqs []*Call // prerequisite calls

	// Expectations
	minCalls, maxCalls int

	numCalls int // actual number made

	// actions are called when this Call is called. Each action gets the args and
	// can set the return values by returning a non-nil slice. Actions run in the
	// order they are created.
	actions []func([]interface{}) []interface{}
}

// newCall creates a *Call. It requires the method type in order to support
// unexported methods.
func newCall(t TestHelper, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call {
	t.Helper()

	// TODO: check arity, types.
	margs := make([]Matcher, len(args))
	for i, arg := range args {
		if m, ok := arg.(Matcher); ok {
			margs[i] = m
		} else if arg == nil {
			// Handle nil specially so that passing a nil interface value
			// will match the typed nils of concrete args.
			margs[i] = Nil()
		} else {
			margs[i] = Eq(arg)
		}
	}

	origin := callerInfo(3)
	actions := []func([]interface{}) []interface{}{func([]interface{}) []interface{} {
		// Synthesize the zero value for each of the return args' types.
		rets := make([]interface{}, methodType.NumOut())
		for i := 0; i < methodType.NumOut(); i++ {
			rets[i] = reflect.Zero(methodType.Out(i)).Interface()
		}
		return rets
	}}
	return &Call{t: t, receiver: receiver, method: method, methodType: methodType,
		args: margs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions}
}

// AnyTimes allows the expectation to be called 0 or more times
func (c *Call) AnyTimes() *Call {
	c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity
	return c
}

// MinTimes requires the call to occur at least n times. If AnyTimes or MaxTimes have not been called or if MaxTimes
// was previously called with 1, MinTimes also sets the maximum number of calls to infinity.
func (c *Call) MinTimes(n int) *Call {
	c.minCalls = n
	if c.maxCalls == 1 {
		c.maxCalls = 1e8
	}
	return c
}

// MaxTimes limits the number of calls to n times. If AnyTimes or MinTimes have not been called or if MinTimes was
// previously called with 1, MaxTimes also sets the minimum number of calls to 0.
func (c *Call) MaxTimes(n int) *Call {
	c.maxCalls = n
	if c.minCalls == 1 {
		c.minCalls = 0
	}
	return c
}

// DoAndReturn declares the action to run when the call is matched.
// The return values from this function are returned by the mocked function.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) DoAndReturn(f interface{}) *Call {
	// TODO: Check arity and types here, rather than dying badly elsewhere.
	v := reflect.ValueOf(f)

	c.addAction(func(args []interface{}) []interface{} {
		vargs := make([]reflect.Value, len(args))
		ft := v.Type()
		for i := 0; i < len(args); i++ {
			if args[i] != nil {
				vargs[i] = reflect.ValueOf(args[i])
			} else {
				// Use the zero value for the arg.
				vargs[i] = reflect.Zero(ft.In(i))
			}
		}
		vrets := v.Call(vargs)
		rets := make([]interface{}, len(vrets))
		for i, ret := range vrets {
			rets[i] = ret.Interface()
		}
		return rets
	})
	return c
}

// Do declares the action to run when the call is matched. The function's
// return values are ignored to retain backward compatibility. To use the
// return values call DoAndReturn.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) Do(f interface{}) *Call {
	// TODO: Check arity and types here, rather than dying badly elsewhere.
	v := reflect.ValueOf(f)

	c.addAction(func(args []interface{}) []interface{} {
		vargs := make([]reflect.Value, len(args))
		ft := v.Type()
		for i := 0; i < len(args); i++ {
			if args[i] != nil {
				vargs[i] = reflect.ValueOf(args[i])
			} else {
				// Use the zero value for the arg.
				vargs[i] = reflect.Zero(ft.In(i))
			}
		}
		v.Call(vargs)
		return nil
	})
	return c
}

// Return declares the values to be returned by the mocked function call.
func (c *Call) Return(rets ...interface{}) *Call {
	c.t.Helper()

	mt := c.methodType
	if len(rets) != mt.NumOut() {
		c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]",
			c.receiver, c.method, len(rets), mt.NumOut(), c.origin)
	}
	for i, ret := range rets {
		if got, want := reflect.TypeOf(ret), mt.Out(i); got == want {
			// Identical types; nothing to do.
		} else if got == nil {
			// Nil needs special handling.
			switch want.Kind() {
			case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
				// ok
			default:
				c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]",
					i, c.receiver, c.method, want, c.origin)
			}
		} else if got.AssignableTo(want) {
			// Assignable type relation. Make the assignment now so that the generated code
			// can return the values with a type assertion.
			v := reflect.New(want).Elem()
			v.Set(reflect.ValueOf(ret))
			rets[i] = v.Interface()
		} else {
			c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]",
				i, c.receiver, c.method, got, want, c.origin)
		}
	}

	c.addAction(func([]interface{}) []interface{} {
		return rets
	})

	return c
}

// Times declares the exact number of times a function call is expected to be executed.
func (c *Call) Times(n int) *Call {
	c.minCalls, c.maxCalls = n, n
	return c
}

// SetArg declares an action that will set the nth argument's value,
// indirected through a pointer. Or, in the case of a slice, SetArg
// will copy value's elements into the nth argument.
func (c *Call) SetArg(n int, value interface{}) *Call {
	c.t.Helper()

	mt := c.methodType
	// TODO: This will break on variadic methods.
	// We will need to check those at invocation time.
	if n < 0 || n >= mt.NumIn() {
		c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]",
			n, mt.NumIn(), c.origin)
	}
	// Permit setting argument through an interface.
	// In the interface case, we don't (nay, can't) check the type here.
	at := mt.In(n)
	switch at.Kind() {
	case reflect.Ptr:
		dt := at.Elem()
		if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) {
			c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]",
				n, vt, dt, c.origin)
		}
	case reflect.Interface:
		// nothing to do
	case reflect.Slice:
		// nothing to do
	default:
		c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface non-slice type %v [%s]",
			n, at, c.origin)
	}

	c.addAction(func(args []interface{}) []interface{} {
		v := reflect.ValueOf(value)
		switch reflect.TypeOf(args[n]).Kind() {
		case reflect.Slice:
			setSlice(args[n], v)
		default:
			reflect.ValueOf(args[n]).Elem().Set(v)
		}
		return nil
	})
	return c
}

// isPreReq returns true if other is a direct or indirect prerequisite to c.
func (c *Call) isPreReq(other *Call) bool {
	for _, preReq := range c.preReqs {
		if other == preReq || preReq.isPreReq(other) {
			return true
		}
	}
	return false
}

// After declares that the call may only match after preReq has been exhausted.
func (c *Call) After(preReq *Call) *Call {
	c.t.Helper()

	if c == preReq {
		c.t.Fatalf("A call isn't allowed to be its own prerequisite")
	}
	if preReq.isPreReq(c) {
		c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq)
	}

	c.preReqs = append(c.preReqs, preReq)
	return c
}

// Returns true if the minimum number of calls have been made.
func (c *Call) satisfied() bool {
	return c.numCalls >= c.minCalls
}

// Returns true if the maximum number of calls have been made.
func (c *Call) exhausted() bool {
	return c.numCalls >= c.maxCalls
}

func (c *Call) String() string {
	args := make([]string, len(c.args))
	for i, arg := range c.args {
		args[i] = arg.String()
	}
	arguments := strings.Join(args, ", ")
	return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin)
}

// Tests if the given call matches the expected call.
// If yes, returns nil. If no, returns error with message explaining why it does not match.
func (c *Call) matches(args []interface{}) error {
	if !c.methodType.IsVariadic() {
		if len(args) != len(c.args) {
			return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d",
				c.origin, len(args), len(c.args))
		}

		for i, m := range c.args {
			if !m.Matches(args[i]) {
				got := fmt.Sprintf("%v", args[i])
				if gs, ok := m.(GotFormatter); ok {
					got = gs.Got(args[i])
				}

				return fmt.Errorf(
					"expected call at %s doesn't match the argument at index %d.\nGot: %v\nWant: %v",
					c.origin, i, got, m,
				)
			}
		}
	} else {
		if len(c.args) < c.methodType.NumIn()-1 {
			return fmt.Errorf("expected call at %s has the wrong number of matchers. Got: %d, want: %d",
				c.origin, len(c.args), c.methodType.NumIn()-1)
		}
		if len(c.args) != c.methodType.NumIn() && len(args) != len(c.args) {
			return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: %d",
				c.origin, len(args), len(c.args))
		}
		if len(args) < len(c.args)-1 {
			return fmt.Errorf("expected call at %s has the wrong number of arguments. Got: %d, want: greater than or equal to %d",
				c.origin, len(args), len(c.args)-1)
		}

		for i, m := range c.args {
			if i < c.methodType.NumIn()-1 {
				// Non-variadic args
				if !m.Matches(args[i]) {
					return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
						c.origin, strconv.Itoa(i), args[i], m)
				}
				continue
			}
			// The last arg has a possibility of a variadic argument, so let it branch

			// sample: Foo(a int, b int, c ...int)
			if i < len(c.args) && i < len(args) {
				if m.Matches(args[i]) {
					// Got Foo(a, b, c) want Foo(matcherA, matcherB, gomock.Any())
					// Got Foo(a, b, c) want Foo(matcherA, matcherB, someSliceMatcher)
					// Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC)
					// Got Foo(a, b) want Foo(matcherA, matcherB)
					// Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD)
					continue
				}
			}

			// The number of actual args don't match the number of matchers,
			// or the last matcher is a slice and the last arg is not.
			// If this function still matches it is because the last matcher
			// matches all the remaining arguments or the lack of any.
			// Convert the remaining arguments, if any, into a slice of the
			// expected type.
			vargsType := c.methodType.In(c.methodType.NumIn() - 1)
			vargs := reflect.MakeSlice(vargsType, 0, len(args)-i)
			for _, arg := range args[i:] {
				vargs = reflect.Append(vargs, reflect.ValueOf(arg))
			}
			if m.Matches(vargs.Interface()) {
				// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, gomock.Any())
				// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, someSliceMatcher)
				// Got Foo(a, b) want Foo(matcherA, matcherB, gomock.Any())
				// Got Foo(a, b) want Foo(matcherA, matcherB, someEmptySliceMatcher)
				break
			}
			// Wrong number of matchers or not match. Fail.
			// Got Foo(a, b) want Foo(matcherA, matcherB, matcherC, matcherD)
			// Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC, matcherD)
			// Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD, matcherE)
			// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, matcherC, matcherD)
			// Got Foo(a, b, c) want Foo(matcherA, matcherB)
			return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
				c.origin, strconv.Itoa(i), args[i:], c.args[i])

		}
	}

	// Check that all prerequisite calls have been satisfied.
	for _, preReqCall := range c.preReqs {
		if !preReqCall.satisfied() {
			return fmt.Errorf("Expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v",
				c.origin, preReqCall, c)
		}
	}

	// Check that the call is not exhausted.
	if c.exhausted() {
		return fmt.Errorf("expected call at %s has already been called the max number of times", c.origin)
	}

	return nil
}

// dropPrereqs tells the expected Call to not re-check prerequisite calls any
// longer, and to return its current set.
func (c *Call) dropPrereqs() (preReqs []*Call) {
	preReqs = c.preReqs
	c.preReqs = nil
	return
}

func (c *Call) call() []func([]interface{}) []interface{} {
	c.numCalls++
	return c.actions
}

// InOrder declares that the given calls should occur in order.
func InOrder(calls ...*Call) {
	for i := 1; i < len(calls); i++ {
		calls[i].After(calls[i-1])
	}
}

func setSlice(arg interface{}, v reflect.Value) {
	va := reflect.ValueOf(arg)
	for i := 0; i < v.Len(); i++ {
		va.Index(i).Set(v.Index(i))
	}
}

func (c *Call) addAction(action func([]interface{}) []interface{}) {
	c.actions = append(c.actions, action)
}

A vendor/github.com/golang/mock/gomock/callset.go => vendor/github.com/golang/mock/gomock/callset.go +108 -0
@@ 0,0 1,108 @@
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gomock

import (
	"bytes"
	"fmt"
)

// callSet represents a set of expected calls, indexed by receiver and method
// name.
type callSet struct {
	// Calls that are still expected.
	expected map[callSetKey][]*Call
	// Calls that have been exhausted.
	exhausted map[callSetKey][]*Call
}

// callSetKey is the key in the maps in callSet
type callSetKey struct {
	receiver interface{}
	fname    string
}

func newCallSet() *callSet {
	return &callSet{make(map[callSetKey][]*Call), make(map[callSetKey][]*Call)}
}

// Add adds a new expected call.
func (cs callSet) Add(call *Call) {
	key := callSetKey{call.receiver, call.method}
	m := cs.expected
	if call.exhausted() {
		m = cs.exhausted
	}
	m[key] = append(m[key], call)
}

// Remove removes an expected call.
func (cs callSet) Remove(call *Call) {
	key := callSetKey{call.receiver, call.method}
	calls := cs.expected[key]
	for i, c := range calls {
		if c == call {
			// maintain order for remaining calls
			cs.expected[key] = append(calls[:i], calls[i+1:]...)
			cs.exhausted[key] = append(cs.exhausted[key], call)
			break
		}
	}
}

// FindMatch searches for a matching call. Returns error with explanation message if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) {
	key := callSetKey{receiver, method}

	// Search through the expected calls.
	expected := cs.expected[key]
	var callsErrors bytes.Buffer
	for _, call := range expected {
		err := call.matches(args)
		if err != nil {
			_, _ = fmt.Fprintf(&callsErrors, "\n%v", err)
		} else {
			return call, nil
		}
	}

	// If we haven't found a match then search through the exhausted calls so we
	// get useful error messages.
	exhausted := cs.exhausted[key]
	for _, call := range exhausted {
		if err := call.matches(args); err != nil {
			_, _ = fmt.Fprintf(&callsErrors, "\n%v", err)
		}
	}

	if len(expected)+len(exhausted) == 0 {
		_, _ = fmt.Fprintf(&callsErrors, "there are no expected calls of the method %q for that receiver", method)
	}

	return nil, fmt.Errorf(callsErrors.String())
}

// Failures returns the calls that are not satisfied.
func (cs callSet) Failures() []*Call {
	failures := make([]*Call, 0, len(cs.expected))
	for _, calls := range cs.expected {
		for _, call := range calls {
			if !call.satisfied() {
				failures = append(failures, call)
			}
		}
	}
	return failures
}

A vendor/github.com/golang/mock/gomock/controller.go => vendor/github.com/golang/mock/gomock/controller.go +264 -0
@@ 0,0 1,264 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package gomock is a mock framework for Go.
//
// Standard usage:
//   (1) Define an interface that you wish to mock.
//         type MyInterface interface {
//           SomeMethod(x int64, y string)
//         }
//   (2) Use mockgen to generate a mock from the interface.
//   (3) Use the mock in a test:
//         func TestMyThing(t *testing.T) {
//           mockCtrl := gomock.NewController(t)
//           defer mockCtrl.Finish()
//
//           mockObj := something.NewMockMyInterface(mockCtrl)
//           mockObj.EXPECT().SomeMethod(4, "blah")
//           // pass mockObj to a real object and play with it.
//         }
//
// By default, expected calls are not enforced to run in any particular order.
// Call order dependency can be enforced by use of InOrder and/or Call.After.
// Call.After can create more varied call order dependencies, but InOrder is
// often more convenient.
//
// The following examples create equivalent call order dependencies.
//
// Example of using Call.After to chain expected call order:
//
//     firstCall := mockObj.EXPECT().SomeMethod(1, "first")
//     secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)
//     mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
//
// Example of using InOrder to declare expected call order:
//
//     gomock.InOrder(
//         mockObj.EXPECT().SomeMethod(1, "first"),
//         mockObj.EXPECT().SomeMethod(2, "second"),
//         mockObj.EXPECT().SomeMethod(3, "third"),
//     )
//
// TODO:
//	- Handle different argument/return types (e.g. ..., chan, map, interface).
package gomock

import (
	"context"
	"fmt"
	"reflect"
	"runtime"
	"sync"
)

// A TestReporter is something that can be used to report test failures.  It
// is satisfied by the standard library's *testing.T.
type TestReporter interface {
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
}

// TestHelper is a TestReporter that has the Helper method.  It is satisfied
// by the standard library's *testing.T.
type TestHelper interface {
	TestReporter
	Helper()
}

// A Controller represents the top-level control of a mock ecosystem.  It
// defines the scope and lifetime of mock objects, as well as their
// expectations.  It is safe to call Controller's methods from multiple
// goroutines. Each test should create a new Controller and invoke Finish via
// defer.
//
//   func TestFoo(t *testing.T) {
//     ctrl := gomock.NewController(t)
//     defer ctrl.Finish()
//     // ..
//   }
//
//   func TestBar(t *testing.T) {
//     t.Run("Sub-Test-1", st) {
//       ctrl := gomock.NewController(st)
//       defer ctrl.Finish()
//       // ..
//     })
//     t.Run("Sub-Test-2", st) {
//       ctrl := gomock.NewController(st)
//       defer ctrl.Finish()
//       // ..
//     })
//   })
type Controller struct {
	// T should only be called within a generated mock. It is not intended to
	// be used in user code and may be changed in future versions. T is the
	// TestReporter passed in when creating the Controller via NewController.
	// If the TestReporter does not implement a TestHelper it will be wrapped
	// with a nopTestHelper.
	T             TestHelper
	mu            sync.Mutex
	expectedCalls *callSet
	finished      bool
}

// NewController returns a new Controller. It is the preferred way to create a
// Controller.
func NewController(t TestReporter) *Controller {
	h, ok := t.(TestHelper)
	if !ok {
		h = nopTestHelper{t}
	}

	return &Controller{
		T:             h,
		expectedCalls: newCallSet(),
	}
}

type cancelReporter struct {
	TestHelper
	cancel func()
}

func (r *cancelReporter) Errorf(format string, args ...interface{}) {
	r.TestHelper.Errorf(format, args...)
}
func (r *cancelReporter) Fatalf(format string, args ...interface{}) {
	defer r.cancel()
	r.TestHelper.Fatalf(format, args...)
}

// WithContext returns a new Controller and a Context, which is cancelled on any
// fatal failure.
func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) {
	h, ok := t.(TestHelper)
	if !ok {
		h = nopTestHelper{t}
	}

	ctx, cancel := context.WithCancel(ctx)
	return NewController(&cancelReporter{h, cancel}), ctx
}

type nopTestHelper struct {
	TestReporter
}

func (h nopTestHelper) Helper() {}

// RecordCall is called by a mock. It should not be called by user code.
func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call {
	ctrl.T.Helper()

	recv := reflect.ValueOf(receiver)
	for i := 0; i < recv.Type().NumMethod(); i++ {
		if recv.Type().Method(i).Name == method {
			return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...)
		}
	}
	ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver)
	panic("unreachable")
}

// RecordCallWithMethodType is called by a mock. It should not be called by user code.
func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call {
	ctrl.T.Helper()

	call := newCall(ctrl.T, receiver, method, methodType, args...)

	ctrl.mu.Lock()
	defer ctrl.mu.Unlock()
	ctrl.expectedCalls.Add(call)

	return call
}

// Call is called by a mock. It should not be called by user code.
func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} {
	ctrl.T.Helper()

	// Nest this code so we can use defer to make sure the lock is released.
	actions := func() []func([]interface{}) []interface{} {
		ctrl.T.Helper()
		ctrl.mu.Lock()
		defer ctrl.mu.Unlock()

		expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args)
		if err != nil {
			origin := callerInfo(2)
			ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err)
		}

		// Two things happen here:
		// * the matching call no longer needs to check prerequite calls,
		// * and the prerequite calls are no longer expected, so remove them.
		preReqCalls := expected.dropPrereqs()
		for _, preReqCall := range preReqCalls {
			ctrl.expectedCalls.Remove(preReqCall)
		}

		actions := expected.call()
		if expected.exhausted() {
			ctrl.expectedCalls.Remove(expected)
		}
		return actions
	}()

	var rets []interface{}
	for _, action := range actions {
		if r := action(args); r != nil {
			rets = r
		}
	}

	return rets
}

// Finish checks to see if all the methods that were expected to be called
// were called. It should be invoked for each Controller. It is not idempotent
// and therefore can only be invoked once.
func (ctrl *Controller) Finish() {
	ctrl.T.Helper()

	ctrl.mu.Lock()
	defer ctrl.mu.Unlock()

	if ctrl.finished {
		ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
	}
	ctrl.finished = true

	// If we're currently panicking, probably because this is a deferred call,
	// pass through the panic.
	if err := recover(); err != nil {
		panic(err)
	}

	// Check that all remaining expected calls are satisfied.
	failures := ctrl.expectedCalls.Failures()
	for _, call := range failures {
		ctrl.T.Errorf("missing call(s) to %v", call)
	}
	if len(failures) != 0 {
		ctrl.T.Fatalf("aborting test due to missing call(s)")
	}
}

func callerInfo(skip int) string {
	if _, file, line, ok := runtime.Caller(skip + 1); ok {
		return fmt.Sprintf("%s:%d", file, line)
	}
	return "unknown file"
}

A vendor/github.com/golang/mock/gomock/matchers.go => vendor/github.com/golang/mock/gomock/matchers.go +255 -0
@@ 0,0 1,255 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gomock

import (
	"fmt"
	"reflect"
	"strings"
)

// A Matcher is a representation of a class of values.
// It is used to represent the valid or expected arguments to a mocked method.
type Matcher interface {
	// Matches returns whether x is a match.
	Matches(x interface{}) bool

	// String describes what the matcher matches.
	String() string
}

// WantFormatter modifies the given Matcher's String() method to the given
// Stringer. This allows for control on how the "Want" is formatted when
// printing .
func WantFormatter(s fmt.Stringer, m Matcher) Matcher {
	type matcher interface {
		Matches(x interface{}) bool
	}

	return struct {
		matcher
		fmt.Stringer
	}{
		matcher:  m,
		Stringer: s,
	}
}

// StringerFunc type is an adapter to allow the use of ordinary functions as
// a Stringer. If f is a function with the appropriate signature,
// StringerFunc(f) is a Stringer that calls f.
type StringerFunc func() string

// String implements fmt.Stringer.
func (f StringerFunc) String() string {
	return f()
}

// GotFormatter is used to better print failure messages. If a matcher
// implements GotFormatter, it will use the result from Got when printing
// the failure message.
type GotFormatter interface {
	// Got is invoked with the received value. The result is used when
	// printing the failure message.
	Got(got interface{}) string
}

// GotFormatterFunc type is an adapter to allow the use of ordinary
// functions as a GotFormatter. If f is a function with the appropriate
// signature, GotFormatterFunc(f) is a GotFormatter that calls f.
type GotFormatterFunc func(got interface{}) string

// Got implements GotFormatter.
func (f GotFormatterFunc) Got(got interface{}) string {
	return f(got)
}

// GotFormatterAdapter attaches a GotFormatter to a Matcher.
func GotFormatterAdapter(s GotFormatter, m Matcher) Matcher {
	return struct {
		GotFormatter
		Matcher
	}{
		GotFormatter: s,
		Matcher:      m,
	}
}

type anyMatcher struct{}

func (anyMatcher) Matches(interface{}) bool {
	return true
}

func (anyMatcher) String() string {
	return "is anything"
}

type eqMatcher struct {
	x interface{}
}

func (e eqMatcher) Matches(x interface{}) bool {
	return reflect.DeepEqual(e.x, x)
}

func (e eqMatcher) String() string {
	return fmt.Sprintf("is equal to %v", e.x)
}

type nilMatcher struct{}

func (nilMatcher) Matches(x interface{}) bool {
	if x == nil {
		return true
	}

	v := reflect.ValueOf(x)
	switch v.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
		reflect.Ptr, reflect.Slice:
		return v.IsNil()
	}

	return false
}

func (nilMatcher) String() string {
	return "is nil"
}

type notMatcher struct {
	m Matcher
}

func (n notMatcher) Matches(x interface{}) bool {
	return !n.m.Matches(x)
}

func (n notMatcher) String() string {
	// TODO: Improve this if we add a NotString method to the Matcher interface.
	return "not(" + n.m.String() + ")"
}

type assignableToTypeOfMatcher struct {
	targetType reflect.Type
}

func (m assignableToTypeOfMatcher) Matches(x interface{}) bool {
	return reflect.TypeOf(x).AssignableTo(m.targetType)
}

func (m assignableToTypeOfMatcher) String() string {
	return "is assignable to " + m.targetType.Name()
}

type allMatcher struct {
	matchers []Matcher
}

func (am allMatcher) Matches(x interface{}) bool {
	for _, m := range am.matchers {
		if !m.Matches(x) {
			return false
		}
	}
	return true
}

func (am allMatcher) String() string {
	ss := make([]string, 0, len(am.matchers))
	for _, matcher := range am.matchers {
		ss = append(ss, matcher.String())
	}
	return strings.Join(ss, "; ")
}

type lenMatcher struct {
	i int
}

func (m lenMatcher) Matches(x interface{}) bool {
	v := reflect.ValueOf(x)
	switch v.Kind() {
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == m.i
	default:
		return false
	}
}

func (m lenMatcher) String() string {
	return fmt.Sprintf("has length %d", m.i)
}

// Constructors

// All returns a composite Matcher that returns true if and only all of the
// matchers return true.
func All(ms ...Matcher) Matcher { return allMatcher{ms} }

// Any returns a matcher that always matches.
func Any() Matcher { return anyMatcher{} }

// Eq returns a matcher that matches on equality.
//
// Example usage:
//   Eq(5).Matches(5) // returns true
//   Eq(5).Matches(4) // returns false
func Eq(x interface{}) Matcher { return eqMatcher{x} }

// Len returns a matcher that matches on length. This matcher returns false if
// is compared to a type that is not an array, chan, map, slice, or string.
func Len(i int) Matcher {
	return lenMatcher{i}
}

// Nil returns a matcher that matches if the received value is nil.
//
// Example usage:
//   var x *bytes.Buffer
//   Nil().Matches(x) // returns true
//   x = &bytes.Buffer{}
//   Nil().Matches(x) // returns false
func Nil() Matcher { return nilMatcher{} }

// Not reverses the results of its given child matcher.
//
// Example usage:
//   Not(Eq(5)).Matches(4) // returns true
//   Not(Eq(5)).Matches(5) // returns false
func Not(x interface{}) Matcher {
	if m, ok := x.(Matcher); ok {
		return notMatcher{m}
	}
	return notMatcher{Eq(x)}
}

// AssignableToTypeOf is a Matcher that matches if the parameter to the mock
// function is assignable to the type of the parameter to this function.
//
// Example usage:
//   var s fmt.Stringer = &bytes.Buffer{}
//   AssignableToTypeOf(s).Matches(time.Second) // returns true
//   AssignableToTypeOf(s).Matches(99) // returns false
//
//   var ctx = reflect.TypeOf((*context.Context)).Elem()
//   AssignableToTypeOf(ctx).Matches(context.Background()) // returns true
func AssignableToTypeOf(x interface{}) Matcher {
	if xt, ok := x.(reflect.Type); ok {
		return assignableToTypeOfMatcher{xt}
	}
	return assignableToTypeOfMatcher{reflect.TypeOf(x)}
}

A vendor/github.com/kr/pretty/.gitignore => vendor/github.com/kr/pretty/.gitignore +4 -0
@@ 0,0 1,4 @@
[568].out
_go*
_test*
_obj

A vendor/github.com/kr/pretty/License => vendor/github.com/kr/pretty/License +19 -0
@@ 0,0 1,19 @@
Copyright 2012 Keith Rarick

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.

A vendor/github.com/kr/pretty/Readme => vendor/github.com/kr/pretty/Readme +9 -0
@@ 0,0 1,9 @@
package pretty

    import "github.com/kr/pretty"

    Package pretty provides pretty-printing for Go values.

Documentation

    http://godoc.org/github.com/kr/pretty

A vendor/github.com/kr/pretty/diff.go => vendor/github.com/kr/pretty/diff.go +265 -0
@@ 0,0 1,265 @@
package pretty

import (
	"fmt"
	"io"
	"reflect"
)

type sbuf []string

func (p *sbuf) Printf(format string, a ...interface{}) {
	s := fmt.Sprintf(format, a...)
	*p = append(*p, s)
}

// Diff returns a slice where each element describes
// a difference between a and b.
func Diff(a, b interface{}) (desc []string) {
	Pdiff((*sbuf)(&desc), a, b)
	return desc
}

// wprintfer calls Fprintf on w for each Printf call
// with a trailing newline.
type wprintfer struct{ w io.Writer }

func (p *wprintfer) Printf(format string, a ...interface{}) {
	fmt.Fprintf(p.w, format+"\n", a...)
}

// Fdiff writes to w a description of the differences between a and b.
func Fdiff(w io.Writer, a, b interface{}) {
	Pdiff(&wprintfer{w}, a, b)
}

type Printfer interface {
	Printf(format string, a ...interface{})
}

// Pdiff prints to p a description of the differences between a and b.
// It calls Printf once for each difference, with no trailing newline.
// The standard library log.Logger is a Printfer.
func Pdiff(p Printfer, a, b interface{}) {
	diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
}

type Logfer interface {
	Logf(format string, a ...interface{})
}

// logprintfer calls Fprintf on w for each Printf call
// with a trailing newline.
type logprintfer struct{ l Logfer }

func (p *logprintfer) Printf(format string, a ...interface{}) {
	p.l.Logf(format, a...)
}

// Ldiff prints to l a description of the differences between a and b.
// It calls Logf once for each difference, with no trailing newline.
// The standard library testing.T and testing.B are Logfers.
func Ldiff(l Logfer, a, b interface{}) {
	Pdiff(&logprintfer{l}, a, b)
}

type diffPrinter struct {
	w Printfer
	l string // label
}

func (w diffPrinter) printf(f string, a ...interface{}) {
	var l string
	if w.l != "" {
		l = w.l + ": "
	}
	w.w.Printf(l+f, a...)
}

func (w diffPrinter) diff(av, bv reflect.Value) {
	if !av.IsValid() && bv.IsValid() {
		w.printf("nil != %# v", formatter{v: bv, quote: true})
		return
	}
	if av.IsValid() && !bv.IsValid() {
		w.printf("%# v != nil", formatter{v: av, quote: true})
		return
	}
	if !av.IsValid() && !bv.IsValid() {
		return
	}

	at := av.Type()
	bt := bv.Type()
	if at != bt {
		w.printf("%v != %v", at, bt)
		return
	}

	switch kind := at.Kind(); kind {
	case reflect.Bool:
		if a, b := av.Bool(), bv.Bool(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if a, b := av.Int(), bv.Int(); a != b {
			w.printf("%d != %d", a, b)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		if a, b := av.Uint(), bv.Uint(); a != b {
			w.printf("%d != %d", a, b)
		}
	case reflect.Float32, reflect.Float64:
		if a, b := av.Float(), bv.Float(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Complex64, reflect.Complex128:
		if a, b := av.Complex(), bv.Complex(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Array:
		n := av.Len()
		for i := 0; i < n; i++ {
			w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
		}
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		if a, b := av.Pointer(), bv.Pointer(); a != b {
			w.printf("%#x != %#x", a, b)
		}
	case reflect.Interface:
		w.diff(av.Elem(), bv.Elem())
	case reflect.Map:
		ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
		for _, k := range ak {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.printf("%q != (missing)", av.MapIndex(k))
		}
		for _, k := range both {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.diff(av.MapIndex(k), bv.MapIndex(k))
		}
		for _, k := range bk {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.printf("(missing) != %q", bv.MapIndex(k))
		}
	case reflect.Ptr:
		switch {
		case av.IsNil() && !bv.IsNil():
			w.printf("nil != %# v", formatter{v: bv, quote: true})
		case !av.IsNil() && bv.IsNil():
			w.printf("%# v != nil", formatter{v: av, quote: true})
		case !av.IsNil() && !bv.IsNil():
			w.diff(av.Elem(), bv.Elem())
		}
	case reflect.Slice:
		lenA := av.Len()
		lenB := bv.Len()
		if lenA != lenB {
			w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
			break
		}
		for i := 0; i < lenA; i++ {
			w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
		}
	case reflect.String:
		if a, b := av.String(), bv.String(); a != b {
			w.printf("%q != %q", a, b)
		}
	case reflect.Struct:
		for i := 0; i < av.NumField(); i++ {
			w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
		}
	default:
		panic("unknown reflect Kind: " + kind.String())
	}
}

func (d diffPrinter) relabel(name string) (d1 diffPrinter) {
	d1 = d
	if d.l != "" && name[0] != '[' {
		d1.l += "."
	}
	d1.l += name
	return d1
}

// keyEqual compares a and b for equality.
// Both a and b must be valid map keys.
func keyEqual(av, bv reflect.Value) bool {
	if !av.IsValid() && !bv.IsValid() {
		return true
	}
	if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() {
		return false
	}
	switch kind := av.Kind(); kind {
	case reflect.Bool:
		a, b := av.Bool(), bv.Bool()
		return a == b
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		a, b := av.Int(), bv.Int()
		return a == b
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		a, b := av.Uint(), bv.Uint()
		return a == b
	case reflect.Float32, reflect.Float64:
		a, b := av.Float(), bv.Float()
		return a == b
	case reflect.Complex64, reflect.Complex128:
		a, b := av.Complex(), bv.Complex()
		return a == b
	case reflect.Array:
		for i := 0; i < av.Len(); i++ {
			if !keyEqual(av.Index(i), bv.Index(i)) {
				return false
			}
		}
		return true
	case reflect.Chan, reflect.UnsafePointer, reflect.Ptr:
		a, b := av.Pointer(), bv.Pointer()
		return a == b
	case reflect.Interface:
		return keyEqual(av.Elem(), bv.Elem())
	case reflect.String:
		a, b := av.String(), bv.String()
		return a == b
	case reflect.Struct:
		for i := 0; i < av.NumField(); i++ {
			if !keyEqual(av.Field(i), bv.Field(i)) {
				return false
			}
		}
		return true
	default:
		panic("invalid map key type " + av.Type().String())
	}
}

func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
	for _, av := range a {
		inBoth := false
		for _, bv := range b {
			if keyEqual(av, bv) {
				inBoth = true
				both = append(both, av)
				break
			}
		}
		if !inBoth {
			ak = append(ak, av)
		}
	}
	for _, bv := range b {
		inBoth := false
		for _, av := range a {
			if keyEqual(av, bv) {
				inBoth = true
				break
			}
		}
		if !inBoth {
			bk = append(bk, bv)
		}
	}
	return
}

A vendor/github.com/kr/pretty/formatter.go => vendor/github.com/kr/pretty/formatter.go +327 -0
@@ 0,0 1,327 @@
package pretty

import (
	"fmt"
	"io"
	"reflect"
	"strconv"
	"text/tabwriter"

	"github.com/kr/text"
)

type formatter struct {
	v     reflect.Value
	force bool
	quote bool
}

// Formatter makes a wrapper, f, that will format x as go source with line
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
// "#" and " " (space) flags are set, for example:
//
//     fmt.Sprintf("%# v", Formatter(x))
//
// If one of these two flags is not set, or any other verb is used, f will
// format x according to the usual rules of package fmt.
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
func Formatter(x interface{}) (f fmt.Formatter) {
	return formatter{v: reflect.ValueOf(x), quote: true}
}

func (fo formatter) String() string {
	return fmt.Sprint(fo.v.Interface()) // unwrap it
}

func (fo formatter) passThrough(f fmt.State, c rune) {
	s := "%"
	for i := 0; i < 128; i++ {
		if f.Flag(i) {
			s += string(i)
		}
	}
	if w, ok := f.Width(); ok {
		s += fmt.Sprintf("%d", w)
	}
	if p, ok := f.Precision(); ok {
		s += fmt.Sprintf(".%d", p)
	}
	s += string(c)
	fmt.Fprintf(f, s, fo.v.Interface())
}

func (fo formatter) Format(f fmt.State, c rune) {
	if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
		w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
		p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
		p.printValue(fo.v, true, fo.quote)
		w.Flush()
		return
	}
	fo.passThrough(f, c)
}

type printer struct {
	io.Writer
	tw      *tabwriter.Writer
	visited map[visit]int
	depth   int
}

func (p *printer) indent() *printer {
	q := *p
	q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
	q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
	return &q
}

func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
	if showType {
		io.WriteString(p, v.Type().String())
		fmt.Fprintf(p, "(%#v)", x)
	} else {
		fmt.Fprintf(p, "%#v", x)
	}
}

// printValue must keep track of already-printed pointer values to avoid
// infinite recursion.
type visit struct {
	v   uintptr
	typ reflect.Type
}

func (p *printer) printValue(v reflect.Value, showType, quote bool) {
	if p.depth > 10 {
		io.WriteString(p, "!%v(DEPTH EXCEEDED)")
		return
	}

	switch v.Kind() {
	case reflect.Bool:
		p.printInline(v, v.Bool(), showType)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		p.printInline(v, v.Int(), showType)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		p.printInline(v, v.Uint(), showType)
	case reflect.Float32, reflect.Float64:
		p.printInline(v, v.Float(), showType)
	case reflect.Complex64, reflect.Complex128:
		fmt.Fprintf(p, "%#v", v.Complex())
	case reflect.String:
		p.fmtString(v.String(), quote)
	case reflect.Map:
		t := v.Type()
		if showType {
			io.WriteString(p, t.String())
		}
		writeByte(p, '{')
		if nonzero(v) {
			expand := !canInline(v.Type())
			pp := p
			if expand {
				writeByte(p, '\n')
				pp = p.indent()
			}
			keys := v.MapKeys()
			for i := 0; i < v.Len(); i++ {
				k := keys[i]
				mv := v.MapIndex(k)
				pp.printValue(k, false, true)
				writeByte(pp, ':')
				if expand {
					writeByte(pp, '\t')
				}
				showTypeInStruct := t.Elem().Kind() == reflect.Interface
				pp.printValue(mv, showTypeInStruct, true)
				if expand {
					io.WriteString(pp, ",\n")
				} else if i < v.Len()-1 {
					io.WriteString(pp, ", ")
				}
			}
			if expand {
				pp.tw.Flush()
			}
		}
		writeByte(p, '}')
	case reflect.Struct:
		t := v.Type()
		if v.CanAddr() {
			addr := v.UnsafeAddr()
			vis := visit{addr, t}
			if vd, ok := p.visited[vis]; ok && vd < p.depth {
				p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
				break // don't print v again
			}
			p.visited[vis] = p.depth
		}

		if showType {
			io.WriteString(p, t.String())
		}
		writeByte(p, '{')
		if nonzero(v) {
			expand := !canInline(v.Type())
			pp := p
			if expand {
				writeByte(p, '\n')
				pp = p.indent()
			}
			for i := 0; i < v.NumField(); i++ {
				showTypeInStruct := true
				if f := t.Field(i); f.Name != "" {
					io.WriteString(pp, f.Name)
					writeByte(pp, ':')
					if expand {
						writeByte(pp, '\t')
					}
					showTypeInStruct = labelType(f.Type)
				}
				pp.printValue(getField(v, i), showTypeInStruct, true)
				if expand {
					io.WriteString(pp, ",\n")
				} else if i < v.NumField()-1 {
					io.WriteString(pp, ", ")
				}
			}
			if expand {
				pp.tw.Flush()
			}
		}
		writeByte(p, '}')
	case reflect.Interface:
		switch e := v.Elem(); {
		case e.Kind() == reflect.Invalid:
			io.WriteString(p, "nil")
		case e.IsValid():
			pp := *p
			pp.depth++
			pp.printValue(e, showType, true)
		default:
			io.WriteString(p, v.Type().String())
			io.WriteString(p, "(nil)")
		}
	case reflect.Array, reflect.Slice:
		t := v.Type()
		if showType {
			io.WriteString(p, t.String())
		}
		if v.Kind() == reflect.Slice && v.IsNil() && showType {
			io.WriteString(p, "(nil)")
			break
		}
		if v.Kind() == reflect.Slice && v.IsNil() {
			io.WriteString(p, "nil")
			break
		}
		writeByte(p, '{')
		expand := !canInline(v.Type())
		pp := p
		if expand {
			writeByte(p, '\n')
			pp = p.indent()
		}
		for i := 0; i < v.Len(); i++ {
			showTypeInSlice := t.Elem().Kind() == reflect.Interface
			pp.printValue(v.Index(i), showTypeInSlice, true)
			if expand {
				io.WriteString(pp, ",\n")
			} else if i < v.Len()-1 {
				io.WriteString(pp, ", ")
			}
		}
		if expand {
			pp.tw.Flush()
		}
		writeByte(p, '}')
	case reflect.Ptr:
		e := v.Elem()
		if !e.IsValid() {
			writeByte(p, '(')
			io.WriteString(p, v.Type().String())
			io.WriteString(p, ")(nil)")
		} else {
			pp := *p
			pp.depth++
			writeByte(pp, '&')
			pp.printValue(e, true, true)
		}
	case reflect.Chan:
		x := v.Pointer()
		if showType {
			writeByte(p, '(')
			io.WriteString(p, v.Type().String())
			fmt.Fprintf(p, ")(%#v)", x)
		} else {
			fmt.Fprintf(p, "%#v", x)
		}
	case reflect.Func:
		io.WriteString(p, v.Type().String())
		io.WriteString(p, " {...}")
	case reflect.UnsafePointer:
		p.printInline(v, v.Pointer(), showType)
	case reflect.Invalid:
		io.WriteString(p, "nil")
	}
}

func canInline(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Map:
		return !canExpand(t.Elem())
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			if canExpand(t.Field(i).Type) {
				return false
			}
		}
		return true
	case reflect.Interface:
		return false
	case reflect.Array, reflect.Slice:
		return !canExpand(t.Elem())
	case reflect.Ptr:
		return false
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		return false
	}
	return true
}

func canExpand(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Map, reflect.Struct,
		reflect.Interface, reflect.Array, reflect.Slice,
		reflect.Ptr:
		return true
	}
	return false
}

func labelType(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Interface, reflect.Struct:
		return true
	}
	return false
}

func (p *printer) fmtString(s string, quote bool) {
	if quote {
		s = strconv.Quote(s)
	}
	io.WriteString(p, s)
}

func writeByte(w io.Writer, b byte) {
	w.Write([]byte{b})
}

func getField(v reflect.Value, i int) reflect.Value {
	val := v.Field(i)
	if val.Kind() == reflect.Interface && !val.IsNil() {
		val = val.Elem()
	}
	return val
}

A vendor/github.com/kr/pretty/go.mod => vendor/github.com/kr/pretty/go.mod +5 -0
@@ 0,0 1,5 @@
module github.com/kr/pretty

go 1.12

require github.com/kr/text v0.1.0

A vendor/github.com/kr/pretty/go.sum => vendor/github.com/kr/pretty/go.sum +3 -0
@@ 0,0 1,3 @@
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

A vendor/github.com/kr/pretty/pretty.go => vendor/github.com/kr/pretty/pretty.go +108 -0
@@ 0,0 1,108 @@
// Package pretty provides pretty-printing for Go values. This is
// useful during debugging, to avoid wrapping long output lines in
// the terminal.
//
// It provides a function, Formatter, that can be used with any
// function that accepts a format string. It also provides
// convenience wrappers for functions in packages fmt and log.
package pretty

import (
	"fmt"
	"io"
	"log"
	"reflect"
)

// Errorf is a convenience wrapper for fmt.Errorf.
//
// Calling Errorf(f, x, y) is equivalent to
// fmt.Errorf(f, Formatter(x), Formatter(y)).
func Errorf(format string, a ...interface{}) error {
	return fmt.Errorf(format, wrap(a, false)...)
}

// Fprintf is a convenience wrapper for fmt.Fprintf.
//
// Calling Fprintf(w, f, x, y) is equivalent to
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
	return fmt.Fprintf(w, format, wrap(a, false)...)
}

// Log is a convenience wrapper for log.Printf.
//
// Calling Log(x, y) is equivalent to
// log.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Log(a ...interface{}) {
	log.Print(wrap(a, true)...)
}

// Logf is a convenience wrapper for log.Printf.
//
// Calling Logf(f, x, y) is equivalent to
// log.Printf(f, Formatter(x), Formatter(y)).
func Logf(format string, a ...interface{}) {
	log.Printf(format, wrap(a, false)...)
}

// Logln is a convenience wrapper for log.Printf.
//
// Calling Logln(x, y) is equivalent to
// log.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Logln(a ...interface{}) {
	log.Println(wrap(a, true)...)
}

// Print pretty-prints its operands and writes to standard output.
//
// Calling Print(x, y) is equivalent to
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Print(a ...interface{}) (n int, errno error) {
	return fmt.Print(wrap(a, true)...)
}

// Printf is a convenience wrapper for fmt.Printf.
//
// Calling Printf(f, x, y) is equivalent to
// fmt.Printf(f, Formatter(x), Formatter(y)).
func Printf(format string, a ...interface{}) (n int, errno error) {
	return fmt.Printf(format, wrap(a, false)...)
}

// Println pretty-prints its operands and writes to standard output.
//
// Calling Println(x, y) is equivalent to
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Println(a ...interface{}) (n int, errno error) {
	return fmt.Println(wrap(a, true)...)
}

// Sprint is a convenience wrapper for fmt.Sprintf.
//
// Calling Sprint(x, y) is equivalent to
// fmt.Sprint(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Sprint(a ...interface{}) string {
	return fmt.Sprint(wrap(a, true)...)
}

// Sprintf is a convenience wrapper for fmt.Sprintf.
//
// Calling Sprintf(f, x, y) is equivalent to
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
func Sprintf(format string, a ...interface{}) string {
	return fmt.Sprintf(format, wrap(a, false)...)
}

func wrap(a []interface{}, force bool) []interface{} {
	w := make([]interface{}, len(a))
	for i, x := range a {
		w[i] = formatter{v: reflect.ValueOf(x), force: force}
	}
	return w
}

A vendor/github.com/kr/pretty/zero.go => vendor/github.com/kr/pretty/zero.go +41 -0
@@ 0,0 1,41 @@
package pretty

import (
	"reflect"
)

func nonzero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool:
		return v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() != 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() != 0
	case reflect.Float32, reflect.Float64:
		return v.Float() != 0
	case reflect.Complex64, reflect.Complex128:
		return v.Complex() != complex(0, 0)
	case reflect.String:
		return v.String() != ""
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if nonzero(getField(v, i)) {
				return true
			}
		}
		return false
	case reflect.Array:
		for i := 0; i < v.Len(); i++ {
			if nonzero(v.Index(i)) {
				return true
			}
		}
		return false
	case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
		return !v.IsNil()
	case reflect.UnsafePointer:
		return v.Pointer() != 0
	}
	return true
}

A vendor/github.com/kr/text/License => vendor/github.com/kr/text/License +19 -0
@@ 0,0 1,19 @@
Copyright 2012 Keith Rarick

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.

A vendor/github.com/kr/text/Readme => vendor/github.com/kr/text/Readme +3 -0
@@ 0,0 1,3 @@
This is a Go package for manipulating paragraphs of text.

See http://go.pkgdoc.org/github.com/kr/text for full documentation.

A vendor/github.com/kr/text/doc.go => vendor/github.com/kr/text/doc.go +3 -0
@@ 0,0 1,3 @@
// Package text provides rudimentary functions for manipulating text in
// paragraphs.
package text

A vendor/github.com/kr/text/go.mod => vendor/github.com/kr/text/go.mod +3 -0
@@ 0,0 1,3 @@
module "github.com/kr/text"

require "github.com/kr/pty" v1.1.1

A vendor/github.com/kr/text/indent.go => vendor/github.com/kr/text/indent.go +74 -0
@@ 0,0 1,74 @@
package text

import (
	"io"
)

// Indent inserts prefix at the beginning of each non-empty line of s. The
// end-of-line marker is NL.
func Indent(s, prefix string) string {
	return string(IndentBytes([]byte(s), []byte(prefix)))
}

// IndentBytes inserts prefix at the beginning of each non-empty line of b.
// The end-of-line marker is NL.
func IndentBytes(b, prefix []byte) []byte {
	var res []byte
	bol := true
	for _, c := range b {
		if bol && c != '\n' {
			res = append(res, prefix...)
		}
		res = append(res, c)
		bol = c == '\n'
	}
	return res
}

// Writer indents each line of its input.
type indentWriter struct {
	w   io.Writer
	bol bool
	pre [][]byte
	sel int
	off int
}

// NewIndentWriter makes a new write filter that indents the input
// lines. Each line is prefixed in order with the corresponding
// element of pre. If there are more lines than elements, the last
// element of pre is repeated for each subsequent line.
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
	return &indentWriter{
		w:   w,
		pre: pre,
		bol: true,
	}
}

// The only errors returned are from the underlying indentWriter.
func (w *indentWriter) Write(p []byte) (n int, err error) {
	for _, c := range p {
		if w.bol {
			var i int
			i, err = w.w.Write(w.pre[w.sel][w.off:])
			w.off += i
			if err != nil {
				return n, err
			}
		}
		_, err = w.w.Write([]byte{c})
		if err != nil {
			return n, err
		}
		n++
		w.bol = c == '\n'
		if w.bol {
			w.off = 0
			if w.sel < len(w.pre)-1 {
				w.sel++
			}
		}
	}
	return n, nil
}

A vendor/github.com/kr/text/wrap.go => vendor/github.com/kr/text/wrap.go +86 -0
@@ 0,0 1,86 @@
package text

import (
	"bytes"
	"math"
)

var (
	nl = []byte{'\n'}
	sp = []byte{' '}
)

const defaultPenalty = 1e5

// Wrap wraps s into a paragraph of lines of length lim, with minimal
// raggedness.
func Wrap(s string, lim int) string {
	return string(WrapBytes([]byte(s), lim))
}

// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
// raggedness.
func WrapBytes(b []byte, lim int) []byte {
	words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
	var lines [][]byte
	for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
		lines = append(lines, bytes.Join(line, sp))
	}
	return bytes.Join(lines, nl)
}

// WrapWords is the low-level line-breaking algorithm, useful if you need more
// control over the details of the text wrapping process. For most uses, either
// Wrap or WrapBytes will be sufficient and more convenient.
//
// WrapWords splits a list of words into lines with minimal "raggedness",
// treating each byte as one unit, accounting for spc units between adjacent
// words on each line, and attempting to limit lines to lim units. Raggedness
// is the total error over all lines, where error is the square of the
// difference of the length of the line and lim. Too-long lines (which only
// happen when a single word is longer than lim units) have pen penalty units
// added to the error.
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
	n := len(words)

	length := make([][]int, n)
	for i := 0; i < n; i++ {
		length[i] = make([]int, n)
		length[i][i] = len(words[i])
		for j := i + 1; j < n; j++ {
			length[i][j] = length[i][j-1] + spc + len(words[j])
		}
	}

	nbrk := make([]int, n)
	cost := make([]int, n)
	for i := range cost {
		cost[i] = math.MaxInt32
	}
	for i := n - 1; i >= 0; i-- {
		if length[i][n-1] <= lim || i == n-1 {
			cost[i] = 0
			nbrk[i] = n
		} else {
			for j := i + 1; j < n; j++ {
				d := lim - length[i][j-1]
				c := d*d + cost[j]
				if length[i][j-1] > lim {
					c += pen // too-long lines get a worse penalty
				}
				if c < cost[i] {
					cost[i] = c
					nbrk[i] = j
				}
			}
		}
	}

	var lines [][][]byte
	i := 0
	for i < n {
		lines = append(lines, words[i:nbrk[i]])
		i = nbrk[i]
	}
	return lines
}

M vendor/modules.txt => vendor/modules.txt +8 -0
@@ 1,5 1,7 @@
# git.sr.ht/~evanj/security v0.0.0-20200228044358-9b9bc6682997
git.sr.ht/~evanj/security
# github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/bmizerany/assert
# github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/bradfitz/gomemcache/memcache
# github.com/dgrijalva/jwt-go v3.2.0+incompatible


@@ 8,6 10,12 @@ github.com/dgrijalva/jwt-go
github.com/go-playground/assert/v2
# github.com/go-sql-driver/mysql v1.5.0
github.com/go-sql-driver/mysql
# github.com/golang/mock v1.4.3
github.com/golang/mock/gomock
# github.com/kr/pretty v0.2.0
github.com/kr/pretty
# github.com/kr/text v0.1.0
github.com/kr/text
# golang.org/x/crypto v0.0.0-20200320181102-891825fb96df
golang.org/x/crypto/bcrypt
golang.org/x/crypto/blowfish