<commit_before>// Package httpcache provides a http.RoundTripper implementation that works as a
// mostly RFC-compliant cache for http responses.
//
// It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client
// and not for a shared proxy).
//
// 'max-stale' set on a request is not currently respected. (max-age and min-fresh both are.)
package httpcache

import (
	"bufio"
	"bytes"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"
)

const (
	stale = iota
	fresh
	transparent
	// Header added to responses that are returned from the cache
	XFromCache = "X-From-Cache"
)

// A Cache interface is used by the Transport to store and retrieve responses.
type Cache interface {
	// Get returns the []byte representation of a cached response and a bool
	// set to true if the value isn't empty
	Get(key string) (responseBytes []byte, ok bool)
	// Set stores the []byte representation of a response against a key
	Set(key string, responseBytes []byte)
	// Delete removes the value associated with the key
	Delete(key string)
}

// MemoryCache is an implemtation of Cache that stores responses in an in-memory map.
type MemoryCache struct {
	mu sync.RWMutex
	items map[string][]byte
}

func (c *MemoryCache) Get(key string) (resp []byte, ok bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	resp, ok = c.items[key]
	return resp, ok
}

func (c *MemoryCache) Set(key string, resp []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[key] = resp
}

func (c *MemoryCache) Delete(key string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.items, key)
}

// NewMemoryCache returns a new Cache that will store items in an in-memory map
func NewMemoryCache() *MemoryCache {
	c := &MemoryCache{items: map[string][]byte{}}
	return c
}

// Transport is an implementation of http.RoundTripper that will return values from a cache
// where possible (avoiding a network request) and will additionally add validators (etag/if-modified-since)
// to repeated requests allowing servers to return 304 / Not Modified
type Transport struct {
	// The RoundTripper interface actually used to make requests
	// If this follows redirects, then only the final response's cache-control will be taken into account
	transport http.RoundTripper
	cache     Cache
	// If true, responses returned from the cache will be given an extra header, X-From-Cache
	MarkCachedResponses bool
}

// NewTransport returns a new Transport using the default HTTP Transport and the
// provided Cache implementation, with MarkCachedResponses set to true
func NewTransport(c Cache) *Transport {
	t := &Transport{transport: http.DefaultTransport, cache: c, MarkCachedResponses: true}
	return t
}

// varyMatches will return false unless all of the cached values for the headers listed in Vary
// match the new request
func varyMatches(cachedResp *http.Response, req *http.Request) bool {
	respVarys := cachedResp.Header.Get("vary")
	for _, header := range strings.Split(respVarys, ",") {
		header = http.CanonicalHeaderKey(strings.Trim(header, " "))
		if header != "" && req.Header.Get(header) != cachedResp.Header.Get("X-Varied-"+header) {
			return false
		}
	}
	return true
}

// RoundTrip takes a Request and returns a Response
//
// If there is a fresh Response already in cache, then it will be returned without connecting to
// the server.
//
// If there is a stale Response, then any validators it contains will be set on the new request
// to give the server a chance to respond with NotModified. If this happens, then the cached Response
// will be returned.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req = cloneRequest(req)
	cacheKey := req.URL.String()
	cachedVal, ok := t.cache.Get(cacheKey)
	cacheableMethod := req.Method == "GET" || req.Method == "HEAD"
	if !cacheableMethod {
		// Need to invalidate an existing value
		t.cache.Delete(cacheKey)
	}
	if ok && cacheableMethod && req.Header.Get("range") == "" {
		cachedResp, err := responseFromCache(cachedVal, req)
		if err == nil {
			if t.MarkCachedResponses {
				cachedResp.Header.Set(XFromCache, "1")
			}

			if varyMatches(cachedResp, req) {
				// Can only use cached value if the new request doesn't Vary significantly
				freshness := getFreshness(cachedResp.Header, req.Header)
				if freshness == fresh {
					return cachedResp, nil
				}

				if freshness == stale {
					// Add validators if caller hasn't already done so
					etag := cachedResp.Header.Get("etag")
					if etag != "" && req.Header.Get("etag") == "" {
						req.Header.Set("if-none-match", etag)
					}
					lastModified := cachedResp.Header.Get("last-modified")
					if lastModified != "" && req.Header.Get("last-modified") == "" {
						req.Header.Set("if-modified-since", lastModified)
					}
				}
			}

			resp, err = t.transport.RoundTrip(req)
			if err == nil && req.Method == "GET" && resp.StatusCode == http.StatusNotModified {
				// Replace the 304 response with the one from cache, but update with some new headers
				headersToMerge := getHopByHopHeaders(resp)
				for _, headerKey := range headersToMerge {
					cachedResp.Header.Set(headerKey, resp.Header.Get(headerKey))
				}
				cachedResp.Status = http.StatusText(http.StatusOK)
				cachedResp.StatusCode = http.StatusOK

				resp = cachedResp
			} else {
				if err != nil || resp.StatusCode != http.StatusOK {
					t.cache.Delete(cacheKey)
				}
			}
		}
	} else {
		reqCacheControl := parseCacheControl(req.Header)
		if _, ok := reqCacheControl["only-if-cached"]; ok {
			resp = newGatewayTimeoutResponse(req)
		} else {
			resp, err = t.transport.RoundTrip(req)
		}
	}
	reqCacheControl := parseCacheControl(req.Header)
	respCacheControl := parseCacheControl(resp.Header)

	if canStore(reqCacheControl, respCacheControl) {
		vary := resp.Header.Get("Vary")
		for _, varyKey := range strings.Split(vary, ",") {
			varyKey = http.CanonicalHeaderKey(strings.Trim(varyKey, " "))
			fakeHeader := "X-Varied-" + varyKey
			reqValue := req.Header.Get(varyKey)
			if reqValue != "" {
				resp.Header.Set(fakeHeader, reqValue)
			}
		}
		respBytes, err := httputil.DumpResponse(resp, true)
		if err == nil {
			// fmt.Println("Set cache", string(respBytes))
			t.cache.Set(cacheKey, respBytes)
		}
	} else {
		t.cache.Delete(cacheKey)
	}
	return resp, nil
}

// getFreshness will return one of fresh/stale/transparent based on the cache-control
// values of the request and the response
//
// fresh indicates the response can be returned
// stale indicates that the response needs validating before it is returned
// transparent indicates the response should not be used to fulfil the request
//
// Because this is only a private cache, 'public' and 'private' in cache-control aren't
// signficant. Similarly, smax-age isn't used.
//
// Limitation: max-stale is not taken into account. It should be.
func getFreshness(respHeaders, reqHeaders http.Header) (freshness int) {
	respCacheControl := parseCacheControl(respHeaders)
	reqCacheControl := parseCacheControl(reqHeaders)
	if _, ok := reqCacheControl["no-cache"]; ok {
		return transparent
	}
	if _, ok := respCacheControl["no-cache"]; ok {
		return stale
	}
	if _, ok := reqCacheControl["only-if-cached"]; ok {
		return fresh
	}
	dateHeader := respHeaders.Get("date")
	if dateHeader != "" {
		date, err := time.Parse(time.RFC1123, dateHeader)
		if err != nil {
			return stale
		}
		currentAge := time.Since(date)
		var lifetime time.Duration
		zeroDuration, _ := time.ParseDuration("0s")
		// If a response includes both an Expires header and a max-age directive,
		// the max-age directive overrides the Expires header, even if the Expires header is more restrictive.
		if maxAge, ok := respCacheControl["max-age"]; ok {
			lifetime, err = time.ParseDuration(maxAge + "s")
			if err != nil {
				lifetime = zeroDuration
			}
		} else {
			expiresHeader := respHeaders.Get("Expires")
			if expiresHeader != "" {
				expires, err := time.Parse(time.RFC1123, expiresHeader)
				if err != nil {
					lifetime = zeroDuration
				} else {
					lifetime = expires.Sub(date)
				}
			}
		}

		if maxAge, ok := reqCacheControl["max-age"]; ok {
			// the client is willing to accept a response whose age is no greater than the specified time in seconds
			lifetime, err = time.ParseDuration(maxAge + "s")
			if err != nil {
				lifetime = zeroDuration
			}
		}
		if minfresh, ok := reqCacheControl["min-fresh"]; ok {
			//  the client wants a response that will still be fresh for at least the specified number of seconds.
			minfreshDuration, err := time.ParseDuration(minfresh + "s")
			if err == nil {
				currentAge = time.Duration(currentAge + minfreshDuration)
			}
		}

		if lifetime > currentAge {
			return fresh
		}

	}
	return stale
}

func getHopByHopHeaders(resp *http.Response) []string {
	// These headers are always hop-by-hop
	headers := []string{"connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade"}

	for _, extra := range strings.Split(resp.Header.Get("connection"), ",") {
		// any header listed in connection, if present, is also considered hop-by-hop
		if strings.Trim(extra, " ") != "" {
			headers = append(headers, extra)
		}
	}
	return headers
}

func canStore(reqCacheControl, respCacheControl cacheControl) (canStore bool) {
	if _, ok := respCacheControl["no-store"]; ok {
		return false
	}
	if _, ok := reqCacheControl["no-store"]; ok {
		return false
	}
	return true
}

func responseFromCache(cachedVal []byte, req *http.Request) (*http.Response, error) {
	b := bytes.NewBuffer(cachedVal)
	resp, err := http.ReadResponse(bufio.NewReader(b), req)
	return resp, err
}

func newGatewayTimeoutResponse(req *http.Request) *http.Response {
	var braw bytes.Buffer
	braw.WriteString("HTTP/1.1 504 Gateway Timeout\r\n\r\n")
	resp, err := http.ReadResponse(bufio.NewReader(&braw), req)
	if err != nil {
		panic(err)
	}
	return resp
}

// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
// (This function copyright goauth2 authors: https://code.google.com/p/goauth2)
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

type cacheControl map[string]string

func parseCacheControl(headers http.Header) cacheControl {
	cc := cacheControl{}
	ccHeader := headers.Get("Cache-Control")
	for _, part := range strings.Split(ccHeader, ",") {
		part = strings.Trim(part, " ")
		if part == "" {
			continue
		}
		if strings.ContainsRune(part, '=') {
			keyval := strings.Split(part, "=")
			cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
		} else {
			cc[part] = "1"
		}
	}
	return cc
}

// NewMemoryCacheTransport returns a new Transport using the in-memory cache implementation
func NewMemoryCacheTransport() *Transport {
	c := NewMemoryCache()
	t := NewTransport(c)
	return t
}
<commit_msg>run gofmt<commit_after>// Package httpcache provides a http.RoundTripper implementation that works as a
// mostly RFC-compliant cache for http responses.
//
// It is only suitable for use as a 'private' cache (i.e. for a web-browser or an API-client
// and not for a shared proxy).
//
// 'max-stale' set on a request is not currently respected. (max-age and min-fresh both are.)
package httpcache

import (
	"bufio"
	"bytes"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"
)

const (
	stale = iota
	fresh
	transparent
	// Header added to responses that are returned from the cache
	XFromCache = "X-From-Cache"
)

// A Cache interface is used by the Transport to store and retrieve responses.
type Cache interface {
	// Get returns the []byte representation of a cached response and a bool
	// set to true if the value isn't empty
	Get(key string) (responseBytes []byte, ok bool)
	// Set stores the []byte representation of a response against a key
	Set(key string, responseBytes []byte)
	// Delete removes the value associated with the key
	Delete(key string)
}

// MemoryCache is an implemtation of Cache that stores responses in an in-memory map.
type MemoryCache struct {
	mu    sync.RWMutex
	items map[string][]byte
}

func (c *MemoryCache) Get(key string) (resp []byte, ok bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	resp, ok = c.items[key]
	return resp, ok
}

func (c *MemoryCache) Set(key string, resp []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[key] = resp
}

func (c *MemoryCache) Delete(key string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.items, key)
}

// NewMemoryCache returns a new Cache that will store items in an in-memory map
func NewMemoryCache() *MemoryCache {
	c := &MemoryCache{items: map[string][]byte{}}
	return c
}

// Transport is an implementation of http.RoundTripper that will return values from a cache
// where possible (avoiding a network request) and will additionally add validators (etag/if-modified-since)
// to repeated requests allowing servers to return 304 / Not Modified
type Transport struct {
	// The RoundTripper interface actually used to make requests
	// If this follows redirects, then only the final response's cache-control will be taken into account
	transport http.RoundTripper
	cache     Cache
	// If true, responses returned from the cache will be given an extra header, X-From-Cache
	MarkCachedResponses bool
}

// NewTransport returns a new Transport using the default HTTP Transport and the
// provided Cache implementation, with MarkCachedResponses set to true
func NewTransport(c Cache) *Transport {
	t := &Transport{transport: http.DefaultTransport, cache: c, MarkCachedResponses: true}
	return t
}

// varyMatches will return false unless all of the cached values for the headers listed in Vary
// match the new request
func varyMatches(cachedResp *http.Response, req *http.Request) bool {
	respVarys := cachedResp.Header.Get("vary")
	for _, header := range strings.Split(respVarys, ",") {
		header = http.CanonicalHeaderKey(strings.Trim(header, " "))
		if header != "" && req.Header.Get(header) != cachedResp.Header.Get("X-Varied-"+header) {
			return false
		}
	}
	return true
}

// RoundTrip takes a Request and returns a Response
//
// If there is a fresh Response already in cache, then it will be returned without connecting to
// the server.
//
// If there is a stale Response, then any validators it contains will be set on the new request
// to give the server a chance to respond with NotModified. If this happens, then the cached Response
// will be returned.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req = cloneRequest(req)
	cacheKey := req.URL.String()
	cachedVal, ok := t.cache.Get(cacheKey)
	cacheableMethod := req.Method == "GET" || req.Method == "HEAD"
	if !cacheableMethod {
		// Need to invalidate an existing value
		t.cache.Delete(cacheKey)
	}
	if ok && cacheableMethod && req.Header.Get("range") == "" {
		cachedResp, err := responseFromCache(cachedVal, req)
		if err == nil {
			if t.MarkCachedResponses {
				cachedResp.Header.Set(XFromCache, "1")
			}

			if varyMatches(cachedResp, req) {
				// Can only use cached value if the new request doesn't Vary significantly
				freshness := getFreshness(cachedResp.Header, req.Header)
				if freshness == fresh {
					return cachedResp, nil
				}

				if freshness == stale {
					// Add validators if caller hasn't already done so
					etag := cachedResp.Header.Get("etag")
					if etag != "" && req.Header.Get("etag") == "" {
						req.Header.Set("if-none-match", etag)
					}
					lastModified := cachedResp.Header.Get("last-modified")
					if lastModified != "" && req.Header.Get("last-modified") == "" {
						req.Header.Set("if-modified-since", lastModified)
					}
				}
			}

			resp, err = t.transport.RoundTrip(req)
			if err == nil && req.Method == "GET" && resp.StatusCode == http.StatusNotModified {
				// Replace the 304 response with the one from cache, but update with some new headers
				headersToMerge := getHopByHopHeaders(resp)
				for _, headerKey := range headersToMerge {
					cachedResp.Header.Set(headerKey, resp.Header.Get(headerKey))
				}
				cachedResp.Status = http.StatusText(http.StatusOK)
				cachedResp.StatusCode = http.StatusOK

				resp = cachedResp
			} else {
				if err != nil || resp.StatusCode != http.StatusOK {
					t.cache.Delete(cacheKey)
				}
			}
		}
	} else {
		reqCacheControl := parseCacheControl(req.Header)
		if _, ok := reqCacheControl["only-if-cached"]; ok {
			resp = newGatewayTimeoutResponse(req)
		} else {
			resp, err = t.transport.RoundTrip(req)
		}
	}
	reqCacheControl := parseCacheControl(req.Header)
	respCacheControl := parseCacheControl(resp.Header)

	if canStore(reqCacheControl, respCacheControl) {
		vary := resp.Header.Get("Vary")
		for _, varyKey := range strings.Split(vary, ",") {
			varyKey = http.CanonicalHeaderKey(strings.Trim(varyKey, " "))
			fakeHeader := "X-Varied-" + varyKey
			reqValue := req.Header.Get(varyKey)
			if reqValue != "" {
				resp.Header.Set(fakeHeader, reqValue)
			}
		}
		respBytes, err := httputil.DumpResponse(resp, true)
		if err == nil {
			// fmt.Println("Set cache", string(respBytes))
			t.cache.Set(cacheKey, respBytes)
		}
	} else {
		t.cache.Delete(cacheKey)
	}
	return resp, nil
}

// getFreshness will return one of fresh/stale/transparent based on the cache-control
// values of the request and the response
//
// fresh indicates the response can be returned
// stale indicates that the response needs validating before it is returned
// transparent indicates the response should not be used to fulfil the request
//
// Because this is only a private cache, 'public' and 'private' in cache-control aren't
// signficant. Similarly, smax-age isn't used.
//
// Limitation: max-stale is not taken into account. It should be.
func getFreshness(respHeaders, reqHeaders http.Header) (freshness int) {
	respCacheControl := parseCacheControl(respHeaders)
	reqCacheControl := parseCacheControl(reqHeaders)
	if _, ok := reqCacheControl["no-cache"]; ok {
		return transparent
	}
	if _, ok := respCacheControl["no-cache"]; ok {
		return stale
	}
	if _, ok := reqCacheControl["only-if-cached"]; ok {
		return fresh
	}
	dateHeader := respHeaders.Get("date")
	if dateHeader != "" {
		date, err := time.Parse(time.RFC1123, dateHeader)
		if err != nil {
			return stale
		}
		currentAge := time.Since(date)
		var lifetime time.Duration
		zeroDuration, _ := time.ParseDuration("0s")
		// If a response includes both an Expires header and a max-age directive,
		// the max-age directive overrides the Expires header, even if the Expires header is more restrictive.
		if maxAge, ok := respCacheControl["max-age"]; ok {
			lifetime, err = time.ParseDuration(maxAge + "s")
			if err != nil {
				lifetime = zeroDuration
			}
		} else {
			expiresHeader := respHeaders.Get("Expires")
			if expiresHeader != "" {
				expires, err := time.Parse(time.RFC1123, expiresHeader)
				if err != nil {
					lifetime = zeroDuration
				} else {
					lifetime = expires.Sub(date)
				}
			}
		}

		if maxAge, ok := reqCacheControl["max-age"]; ok {
			// the client is willing to accept a response whose age is no greater than the specified time in seconds
			lifetime, err = time.ParseDuration(maxAge + "s")
			if err != nil {
				lifetime = zeroDuration
			}
		}
		if minfresh, ok := reqCacheControl["min-fresh"]; ok {
			//  the client wants a response that will still be fresh for at least the specified number of seconds.
			minfreshDuration, err := time.ParseDuration(minfresh + "s")
			if err == nil {
				currentAge = time.Duration(currentAge + minfreshDuration)
			}
		}

		if lifetime > currentAge {
			return fresh
		}

	}
	return stale
}

func getHopByHopHeaders(resp *http.Response) []string {
	// These headers are always hop-by-hop
	headers := []string{"connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade"}

	for _, extra := range strings.Split(resp.Header.Get("connection"), ",") {
		// any header listed in connection, if present, is also considered hop-by-hop
		if strings.Trim(extra, " ") != "" {
			headers = append(headers, extra)
		}
	}
	return headers
}

func canStore(reqCacheControl, respCacheControl cacheControl) (canStore bool) {
	if _, ok := respCacheControl["no-store"]; ok {
		return false
	}
	if _, ok := reqCacheControl["no-store"]; ok {
		return false
	}
	return true
}

func responseFromCache(cachedVal []byte, req *http.Request) (*http.Response, error) {
	b := bytes.NewBuffer(cachedVal)
	resp, err := http.ReadResponse(bufio.NewReader(b), req)
	return resp, err
}

func newGatewayTimeoutResponse(req *http.Request) *http.Response {
	var braw bytes.Buffer
	braw.WriteString("HTTP/1.1 504 Gateway Timeout\r\n\r\n")
	resp, err := http.ReadResponse(bufio.NewReader(&braw), req)
	if err != nil {
		panic(err)
	}
	return resp
}

// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
// (This function copyright goauth2 authors: https://code.google.com/p/goauth2)
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

type cacheControl map[string]string

func parseCacheControl(headers http.Header) cacheControl {
	cc := cacheControl{}
	ccHeader := headers.Get("Cache-Control")
	for _, part := range strings.Split(ccHeader, ",") {
		part = strings.Trim(part, " ")
		if part == "" {
			continue
		}
		if strings.ContainsRune(part, '=') {
			keyval := strings.Split(part, "=")
			cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
		} else {
			cc[part] = "1"
		}
	}
	return cc
}

// NewMemoryCacheTransport returns a new Transport using the in-memory cache implementation
func NewMemoryCacheTransport() *Transport {
	c := NewMemoryCache()
	t := NewTransport(c)
	return t
}
<|endoftext|>


<commit_before>package fetch

import (
	"bytes"
	"fmt"
	"github.com/disintegration/imaging"
	"image"
	"image/jpeg"
	"image/png"
	"io"
)

func Resize(src io.Reader, c *CacheContext) (io.Reader, error) {
	image, format, err := image.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	factor := float64(c.Width) / float64(image.Bounds().Size().X)
	height := int(float64(image.Bounds().Size().Y) * factor)

	image = imaging.Resize(image, c.Width, height, imaging.Linear)

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}

func CenterCrop(src io.Reader, c *CacheContext) (io.Reader, error) {
	image, format, err := image.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	height := image.Bounds().Size().Y
	width := image.Bounds().Size().X

	if width < height {
		image = imaging.CropCenter(image, width, width)
	} else if width > height {
		image = imaging.CropCenter(image, height, height)
	} else {
		image = imaging.CropCenter(image, width, height)
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}
<commit_msg>Add check for rotation, and rotate on resize<commit_after>package fetch

import (
	"bytes"
	"fmt"
	"github.com/disintegration/imaging"
	"github.com/rwcarlsen/goexif/exif"
	"image"
	"image/jpeg"
	"image/png"
	"io"
)

func needsRotation(src io.Reader) (bool, int) {
	metadata, err := exif.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return false, 0
	}

	orientation, err := x.Get(exif.Orientation)
	if err != nil {
		fmt.Println(err.Error())
		return false, 0
	}

	angle := 0
	rotate := false

	switch orientation.String() {
	case "6":
		angle = 90
		rotate = true
	case "3":
		angle = 180
		rotate = true
	case "8":
		angle = 270
		rotate = true
	}

	return rotate, angle
}

func Resize(src io.Reader, c *CacheContext) (io.Reader, error) {
	image, format, err := image.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	factor := float64(c.Width) / float64(image.Bounds().Size().X)
	height := int(float64(image.Bounds().Size().Y) * factor)

	image = imaging.Resize(image, c.Width, height, imaging.Linear)

	if rotate, angle := needsRotation(src); rotate {
		switch angle {
		case 90:
			image = imaging.Rotate90(image)
		case 180:
			image = imaging.Rotate180(image)
		case 270:
			image = imaging.Rotate270(image)
		}
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}

func CenterCrop(src io.Reader, c *CacheContext) (io.Reader, error) {
	image, format, err := image.Decode(src)
	if err != nil {
		fmt.Println(err.Error())
		return nil, err
	}

	buf := new(bytes.Buffer)

	height := image.Bounds().Size().Y
	width := image.Bounds().Size().X

	if width < height {
		image = imaging.CropCenter(image, width, width)
	} else if width > height {
		image = imaging.CropCenter(image, height, height)
	} else {
		image = imaging.CropCenter(image, width, height)
	}

	switch format {
	case "jpeg":
		jpeg.Encode(buf, image, nil)
	case "png":
		err = png.Encode(buf, image)
	}

	return buf, err
}
<|endoftext|>


<commit_before>package affinity

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var exampleLongComment = `On the site documentation section, links to documentation sections always point to the jekyllrb.com website, this means that users testing changes might get confused because they will see the official external website page instead of their local website upon clicking those links.

**Please check if this change doesn't break the official website on https://jekyllrb.com before accepting the pull request.**

----------

@jekyll/documentation`

func TestFindAffinityTeam(t *testing.T) {
	allTeams := []Team{
		{ID: 456, Mention: "@jekyll/documentation"},
		{ID: 789, Mention: "@jekyll/ecosystem"},
		{ID: 101, Mention: "@jekyll/performance"},
		{ID: 213, Mention: "@jekyll/stability"},
		{ID: 141, Mention: "@jekyll/windows"},
		{ID: 123, Mention: "@jekyll/build"},
	}

	examples := []struct {
		body           string
		matchingTeamID int
	}{
		{exampleLongComment, 456},
		{"@jekyll/documentation @jekyll/build", 456},
		{"@jekyll/windows @jekyll/documentation", 456},
		{"@jekyll/windows", 141},
	}
	for _, example := range examples {
		matchingTeam, err := findAffinityTeam(example.body, allTeams)
		assert.NoError(t, err)
		assert.Equal(t, matchingTeam.ID, example.matchingTeamID,
			"expected the following to match %d team: `%s`", example.matchingTeamID, example.body)
	}
}
<commit_msg>It's an int64, now<commit_after>package affinity

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var exampleLongComment = `On the site documentation section, links to documentation sections always point to the jekyllrb.com website, this means that users testing changes might get confused because they will see the official external website page instead of their local website upon clicking those links.

**Please check if this change doesn't break the official website on https://jekyllrb.com before accepting the pull request.**

----------

@jekyll/documentation`

func TestFindAffinityTeam(t *testing.T) {
	allTeams := []Team{
		{ID: 456, Mention: "@jekyll/documentation"},
		{ID: 789, Mention: "@jekyll/ecosystem"},
		{ID: 101, Mention: "@jekyll/performance"},
		{ID: 213, Mention: "@jekyll/stability"},
		{ID: 141, Mention: "@jekyll/windows"},
		{ID: 123, Mention: "@jekyll/build"},
	}

	examples := []struct {
		body           string
		matchingTeamID int64
	}{
		{exampleLongComment, 456},
		{"@jekyll/documentation @jekyll/build", 456},
		{"@jekyll/windows @jekyll/documentation", 456},
		{"@jekyll/windows", 141},
	}
	for _, example := range examples {
		matchingTeam, err := findAffinityTeam(example.body, allTeams)
		assert.NoError(t, err)
		assert.Equal(t, matchingTeam.ID, example.matchingTeamID,
			"expected the following to match %d team: `%s`", example.matchingTeamID, example.body)
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 The ebml-go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package ebml decodes EBML data.
//
// EBML is short for Extensible Binary Meta Language. EBML specifies a
// binary and octet (byte) aligned format inspired by the principle of
// XML. EBML itself is a generalized description of the technique of
// binary markup. Like XML, it is completely agnostic to any data that it
// might contain.
// For a specification, see http://ebml.sourceforge.net/specs/
package ebml

import (
	"errors"
	"io"
	"log"
	"math"
	"reflect"
	"strconv"
)

// ReachedPayloadError is generated when a field tagged with
// ebmlstop:"1" is reached.
type ReachedPayloadError struct {
	First *Element
	Rest  *Element
}

func (r ReachedPayloadError) Error() string {
	return "Reached payload"
}

// Element represents an EBML-encoded chunk of data.
type Element struct {
	R  io.Reader
	Id uint
}

// Size returns the size of the element.
func (e *Element) Size() int64 {
	lr := e.R.(*io.LimitedReader)
	return lr.N
}

// Creates the root element corresponding to the data available in r.
func RootElement(r io.Reader) (*Element, error) {
	e := &Element{io.LimitReader(r, math.MaxInt64), 0}
	return e, nil
}

func remaining(x int8) (rem int) {
	for x > 0 {
		rem++
		x += x
	}
	return
}

func readVint(r io.Reader) (val uint64, err error, rem int) {
	v := make([]uint8, 1)
	_, err = io.ReadFull(r, v)
	if err == nil {
		val = uint64(v[0])
		rem = remaining(int8(val))
		for i := 0; err == nil && i < rem; i++ {
			_, err = io.ReadFull(r, v)
			val <<= 8
			val += uint64(v[0])
		}
	}
	return
}

func readSize(r io.Reader) (int64, error) {
	val, err, rem := readVint(r)
	return int64(val & ^(128 << uint(rem*8-rem))), err
}

// Next returns the next child element in an element.
func (e *Element) Next() (*Element, error) {
	var ne Element
	id, err, _ := readVint(e.R)
	if err != nil {
		return nil, err
	}
	var sz int64
	sz, err = readSize(e.R)
	if err != nil {
		return nil, err
	}
	ne.R = io.LimitReader(e.R, sz)
	ne.Id = uint(id)
	return &ne, err
}

func (e *Element) readUint64() (uint64, error) {
	d, err := e.ReadData()
	var i int
	sz := len(d)
	var val uint64
	for i = 0; i < sz; i++ {
		val <<= 8
		val += uint64(d[i])
	}
	return val, err
}

func (e *Element) readUint() (uint, error) {
	val, err := e.readUint64()
	return uint(val), err
}

func (e *Element) readString() (string, error) {
	s, err := e.ReadData()
	return string(s), err
}

func (e *Element) ReadData() (d []byte, err error) {
	sz := e.Size()
	d = make([]uint8, sz, sz)
	_, err = io.ReadFull(e.R, d)
	return
}

func (e *Element) readFloat() (val float64, err error) {
	var uval uint64
	uval, err = e.readUint64()
	if e.Size() == 8 {
		val = math.Float64frombits(uval)
	} else {
		val = float64(math.Float32frombits(uint32(uval)))
	}
	return
}

func (e *Element) skip() (err error) {
	_, err = e.ReadData()
	return
}

// Unmarshal reads EBML data from r into data. Data must be a pointer
// to a struct. Fields present in the struct but absent in the stream
// will just keep their zero value.
// Returns an error that can be an io.Error or a ReachedPayloadError
// containing the first element and the the parent element containing
// the rest of the elements.
func (e *Element) Unmarshal(val interface{}) error {
	return e.readStruct(reflect.Indirect(reflect.ValueOf(val)))
}

func getTag(f reflect.StructField, s string) uint {
	sid := f.Tag.Get(s)
	id, _ := strconv.ParseUint(sid, 16, 0)
	return uint(id)
}

func lookup(reqid uint, t reflect.Type) int {
	for i, l := 0, t.NumField(); i < l; i++ {
		f := t.Field(i)
		if getTag(f, "ebml") == reqid {
			return i - 1000000*int(getTag(f, "ebmlstop"))
		}
	}
	return -1
}

func setDefaults(v reflect.Value) {
	t := v.Type()
	for i, l := 0, t.NumField(); i < l; i++ {
		fv := v.Field(i)
		switch fv.Kind() {
		case reflect.Int:
			fallthrough
		case reflect.Int8:
			fallthrough
		case reflect.Int16:
			fallthrough
		case reflect.Int32:
			fallthrough
		case reflect.Int64:
			fallthrough
		case reflect.Uint:
			fallthrough
		case reflect.Uint8:
			fallthrough
		case reflect.Uint16:
			fallthrough
		case reflect.Uint32:
			fallthrough
		case reflect.Uint64:
			fallthrough
		case reflect.Float32:
			fallthrough
		case reflect.Float64:
			fallthrough
		case reflect.String:
			setFieldDefaults(fv, t.Field(i), v)
		case reflect.Array:
			fallthrough
		case reflect.Struct:
			fallthrough
		case reflect.Slice:
			break
		default:
			log.Panic("Unsupported type")
		}
	}
}

func setFieldDefaults(v reflect.Value, sf reflect.StructField, s reflect.Value) {
	if v.CanInterface() && reflect.DeepEqual(
		v.Interface(), reflect.Zero(v.Type()).Interface()) {
		tag := sf.Tag.Get("ebmldef")
		if tag != "" {
			switch v.Kind() {
			case reflect.Int:
				fallthrough
			case reflect.Int64:
				u, _ := strconv.ParseInt(tag, 10, 0)
				v.SetInt(int64(u))
			case reflect.Uint:
				fallthrough
			case reflect.Uint64:
				u, _ := strconv.ParseUint(tag, 10, 0)
				v.SetUint(u)
			case reflect.Float32:
				fallthrough
			case reflect.Float64:
				f, _ := strconv.ParseFloat(tag, 64)
				v.SetFloat(f)
			case reflect.String:
				v.SetString(tag)
			default:
				log.Panic("Unsupported default value")
			}
		}
		ltag := sf.Tag.Get("ebmldeflink")
		if ltag != "" {
			v.Set(s.FieldByName(ltag))
		}
	}
}

func (e *Element) readStruct(v reflect.Value) (err error) {
	t := v.Type()
	for err == nil {
		var ne *Element
		ne, err = e.Next()
		if err == io.EOF {
			err = nil
			break
		}
		i := lookup(ne.Id, t)
		if i >= 0 {
			err = ne.readField(v.Field(i))
		} else if i == -1 {
			err = ne.skip()
		} else {
			err = ReachedPayloadError{ne, e}
		}
	}
	setDefaults(v)
	return
}

func (e *Element) readField(v reflect.Value) (err error) {
	switch v.Kind() {
	case reflect.Struct:
		err = e.readStruct(v)
	case reflect.Slice:
		err = e.readSlice(v)
	case reflect.Array:
		for i, l := 0, v.Len(); i < l && err == nil; i++ {
			err = e.readStruct(v.Index(i))
		}
	case reflect.String:
		var s string
		s, err = e.readString()
		v.SetString(s)
	case reflect.Int:
		fallthrough
	case reflect.Int64:
		var u uint64
		u, err = e.readUint64()
		v.SetInt(int64(u))
	case reflect.Uint:
		fallthrough
	case reflect.Uint64:
		var u uint64
		u, err = e.readUint64()
		v.SetUint(u)
	case reflect.Float32:
		fallthrough
	case reflect.Float64:
		var f float64
		f, err = e.readFloat()
		v.SetFloat(f)
	default:
		err = errors.New("Unknown type: " + v.String())
	}
	return
}

func (e *Element) readSlice(v reflect.Value) (err error) {
	switch v.Type().Elem().Kind() {
	case reflect.Uint8:
		var sl []uint8
		sl, err = e.ReadData()
		if err == nil {
			v.Set(reflect.ValueOf(sl))
		}
	case reflect.Struct:
		vl := v.Len()
		ne := reflect.New(v.Type().Elem())
		nsl := reflect.Append(v, reflect.Indirect(ne))
		v.Set(nsl)
		err = e.readStruct(v.Index(vl))
	default:
		err = errors.New("Unknown slice type: " + v.String())
	}
	return
}
<commit_msg>String()<commit_after>// Copyright 2012 The ebml-go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package ebml decodes EBML data.
//
// EBML is short for Extensible Binary Meta Language. EBML specifies a
// binary and octet (byte) aligned format inspired by the principle of
// XML. EBML itself is a generalized description of the technique of
// binary markup. Like XML, it is completely agnostic to any data that it
// might contain.
// For a specification, see http://ebml.sourceforge.net/specs/
package ebml

import (
	"errors"
	"io"
	"log"
	"math"
	"reflect"
	"strconv"
	"fmt"
)

// ReachedPayloadError is generated when a field tagged with
// ebmlstop:"1" is reached.
type ReachedPayloadError struct {
	First *Element
	Rest  *Element
}

func (r ReachedPayloadError) Error() string {
	return "Reached payload"
}

// Element represents an EBML-encoded chunk of data.
type Element struct {
	R  io.Reader
	Id uint
}

func (e *Element) String() string {
	return fmt.Sprintf("{%+v %x}", e.R, e.Id)
}

// Size returns the size of the element.
func (e *Element) Size() int64 {
	lr := e.R.(*io.LimitedReader)
	return lr.N
}

// Creates the root element corresponding to the data available in r.
func RootElement(r io.Reader) (*Element, error) {
	e := &Element{io.LimitReader(r, math.MaxInt64), 0}
	return e, nil
}

func remaining(x int8) (rem int) {
	for x > 0 {
		rem++
		x += x
	}
	return
}

func readVint(r io.Reader) (val uint64, err error, rem int) {
	v := make([]uint8, 1)
	_, err = io.ReadFull(r, v)
	if err == nil {
		val = uint64(v[0])
		rem = remaining(int8(val))
		for i := 0; err == nil && i < rem; i++ {
			_, err = io.ReadFull(r, v)
			val <<= 8
			val += uint64(v[0])
		}
	}
	return
}

func readSize(r io.Reader) (int64, error) {
	val, err, rem := readVint(r)
	return int64(val & ^(128 << uint(rem*8-rem))), err
}

// Next returns the next child element in an element.
func (e *Element) Next() (*Element, error) {
	var ne Element
	id, err, _ := readVint(e.R)
	if err != nil {
		return nil, err
	}
	var sz int64
	sz, err = readSize(e.R)
	if err != nil {
		return nil, err
	}
	ne.R = io.LimitReader(e.R, sz)
	ne.Id = uint(id)
	return &ne, err
}

func (e *Element) readUint64() (uint64, error) {
	d, err := e.ReadData()
	var i int
	sz := len(d)
	var val uint64
	for i = 0; i < sz; i++ {
		val <<= 8
		val += uint64(d[i])
	}
	return val, err
}

func (e *Element) readUint() (uint, error) {
	val, err := e.readUint64()
	return uint(val), err
}

func (e *Element) readString() (string, error) {
	s, err := e.ReadData()
	return string(s), err
}

func (e *Element) ReadData() (d []byte, err error) {
	sz := e.Size()
	d = make([]uint8, sz, sz)
	_, err = io.ReadFull(e.R, d)
	return
}

func (e *Element) readFloat() (val float64, err error) {
	var uval uint64
	uval, err = e.readUint64()
	if e.Size() == 8 {
		val = math.Float64frombits(uval)
	} else {
		val = float64(math.Float32frombits(uint32(uval)))
	}
	return
}

func (e *Element) skip() (err error) {
	_, err = e.ReadData()
	return
}

// Unmarshal reads EBML data from r into data. Data must be a pointer
// to a struct. Fields present in the struct but absent in the stream
// will just keep their zero value.
// Returns an error that can be an io.Error or a ReachedPayloadError
// containing the first element and the the parent element containing
// the rest of the elements.
func (e *Element) Unmarshal(val interface{}) error {
	return e.readStruct(reflect.Indirect(reflect.ValueOf(val)))
}

func getTag(f reflect.StructField, s string) uint {
	sid := f.Tag.Get(s)
	id, _ := strconv.ParseUint(sid, 16, 0)
	return uint(id)
}

func lookup(reqid uint, t reflect.Type) int {
	for i, l := 0, t.NumField(); i < l; i++ {
		f := t.Field(i)
		if getTag(f, "ebml") == reqid {
			return i - 1000000*int(getTag(f, "ebmlstop"))
		}
	}
	return -1
}

func setDefaults(v reflect.Value) {
	t := v.Type()
	for i, l := 0, t.NumField(); i < l; i++ {
		fv := v.Field(i)
		switch fv.Kind() {
		case reflect.Int:
			fallthrough
		case reflect.Int8:
			fallthrough
		case reflect.Int16:
			fallthrough
		case reflect.Int32:
			fallthrough
		case reflect.Int64:
			fallthrough
		case reflect.Uint:
			fallthrough
		case reflect.Uint8:
			fallthrough
		case reflect.Uint16:
			fallthrough
		case reflect.Uint32:
			fallthrough
		case reflect.Uint64:
			fallthrough
		case reflect.Float32:
			fallthrough
		case reflect.Float64:
			fallthrough
		case reflect.String:
			setFieldDefaults(fv, t.Field(i), v)
		case reflect.Array:
			fallthrough
		case reflect.Struct:
			fallthrough
		case reflect.Slice:
			break
		default:
			log.Panic("Unsupported type")
		}
	}
}

func setFieldDefaults(v reflect.Value, sf reflect.StructField, s reflect.Value) {
	if v.CanInterface() && reflect.DeepEqual(
		v.Interface(), reflect.Zero(v.Type()).Interface()) {
		tag := sf.Tag.Get("ebmldef")
		if tag != "" {
			switch v.Kind() {
			case reflect.Int:
				fallthrough
			case reflect.Int64:
				u, _ := strconv.ParseInt(tag, 10, 0)
				v.SetInt(int64(u))
			case reflect.Uint:
				fallthrough
			case reflect.Uint64:
				u, _ := strconv.ParseUint(tag, 10, 0)
				v.SetUint(u)
			case reflect.Float32:
				fallthrough
			case reflect.Float64:
				f, _ := strconv.ParseFloat(tag, 64)
				v.SetFloat(f)
			case reflect.String:
				v.SetString(tag)
			default:
				log.Panic("Unsupported default value")
			}
		}
		ltag := sf.Tag.Get("ebmldeflink")
		if ltag != "" {
			v.Set(s.FieldByName(ltag))
		}
	}
}

func (e *Element) readStruct(v reflect.Value) (err error) {
	t := v.Type()
	for err == nil {
		var ne *Element
		ne, err = e.Next()
		if err == io.EOF {
			err = nil
			break
		}
		i := lookup(ne.Id, t)
		if i >= 0 {
			err = ne.readField(v.Field(i))
		} else if i == -1 {
			err = ne.skip()
		} else {
			err = ReachedPayloadError{ne, e}
		}
	}
	setDefaults(v)
	return
}

func (e *Element) readField(v reflect.Value) (err error) {
	switch v.Kind() {
	case reflect.Struct:
		err = e.readStruct(v)
	case reflect.Slice:
		err = e.readSlice(v)
	case reflect.Array:
		for i, l := 0, v.Len(); i < l && err == nil; i++ {
			err = e.readStruct(v.Index(i))
		}
	case reflect.String:
		var s string
		s, err = e.readString()
		v.SetString(s)
	case reflect.Int:
		fallthrough
	case reflect.Int64:
		var u uint64
		u, err = e.readUint64()
		v.SetInt(int64(u))
	case reflect.Uint:
		fallthrough
	case reflect.Uint64:
		var u uint64
		u, err = e.readUint64()
		v.SetUint(u)
	case reflect.Float32:
		fallthrough
	case reflect.Float64:
		var f float64
		f, err = e.readFloat()
		v.SetFloat(f)
	default:
		err = errors.New("Unknown type: " + v.String())
	}
	return
}

func (e *Element) readSlice(v reflect.Value) (err error) {
	switch v.Type().Elem().Kind() {
	case reflect.Uint8:
		var sl []uint8
		sl, err = e.ReadData()
		if err == nil {
			v.Set(reflect.ValueOf(sl))
		}
	case reflect.Struct:
		vl := v.Len()
		ne := reflect.New(v.Type().Elem())
		nsl := reflect.Append(v, reflect.Indirect(ne))
		v.Set(nsl)
		err = e.readStruct(v.Index(vl))
	default:
		err = errors.New("Unknown slice type: " + v.String())
	}
	return
}
<|endoftext|>


<commit_before>package information

import (
	"errors"
	"strings"

	"Fantasia/system"

	"github.com/Necroforger/dream"
	"github.com/bwmarrin/discordgo"
)

// Module ...
type Module struct{}

// Build adds this modules commands to the system's router
func (m *Module) Build(s *system.System) {
	r := s.CommandRouter
	r.On("help", m.Help).Set("", "Displays a help menu with the available commands")
}

// Help maps a list of available commands and descends into subrouters.
func (m *Module) Help(ctx *system.Context) {

	if cmd := ctx.Args.After(); cmd != "" {
		if route, _ := ctx.System.CommandRouter.FindMatch(cmd); route != nil {
			ctx.ReplyEmbed(dream.NewEmbed().
				SetTitle(route.Name).
				SetDescription(route.Desc).
				SetColor(system.StatusNotify).
				MessageEmbed)
			return
		}
		ctx.ReplyError(errors.New("Command not found"))
		return
	}

	_, err := ctx.ReplyEmbed(depthcharge(ctx.System.CommandRouter, nil, 0).
		SetColor(system.StatusNotify).
		SetThumbnail(ctx.Ses.DG.State.User.AvatarURL("2048")).
		InlineAllFields().
		SetDescription("`Bot prefix: " + ctx.System.Config.Prefix + "` type `help [command]` for more information\nCommands separated with `|` represent alternative names.\nIndented commands are subroutes of their parent commands").
		MessageEmbed)
	if err != nil {
		ctx.ReplyError(err)
	}
}

// Depthcharge recursively generates a help embed from a CommandRouter and its subrouters
func depthcharge(r *system.CommandRouter, embed *dream.Embed, depth int) *dream.Embed {
	if embed == nil {
		embed = dream.NewEmbed()
	}

	depthString := func(text string, depth int, subrouter bool) string {
		quote := ""
		if subrouter {
			quote = "`"
		}
		return strings.Repeat("\t", depth) + quote + text + quote + "\n"
	}

	getField := func(name string) *discordgo.MessageEmbedField {
		for _, v := range embed.Fields {
			if v.Name == name {
				return v
			}
		}
		if name == "" {
			name = "undefined"
		}
		field := &discordgo.MessageEmbedField{Name: name}
		embed.Fields = append(embed.Fields, field)
		return field
	}

	removeField := func(name string) {
		for i, v := range embed.Fields {
			if v.Name == name {
				embed.Fields = append(embed.Fields[:i], embed.Fields[i+1:]...)
				return
			}
		}
	}

	for _, v := range r.Routes {
		field := getField(v.Category)

		var tag string
		if !v.Disabled {
			field.Value += depthString(tag+v.Name+tag, depth, false)
		}

		if field.Value == "" {
			removeField(v.Category)
		}
	}

	for _, v := range r.Subrouters {
		if v.Disabled || (v.CommandRoute != nil && v.CommandRoute.Disabled) {
			continue
		}
		field := getField(v.Category())
		field.Value += depthString(v.Name, depth, true)
		embed = depthcharge(v.Router, embed, depth+1)
	}

	return embed
}
<commit_msg>Help menu is now more organized<commit_after>package information

import (
	"errors"
	"sort"
	"strings"

	"Fantasia/system"

	"github.com/Necroforger/dream"
	"github.com/bwmarrin/discordgo"
)

// Module ...
type Module struct{}

// Build adds this modules commands to the system's router
func (m *Module) Build(s *system.System) {
	r := s.CommandRouter
	r.On("help", m.Help).Set("", "Displays a help menu with the available commands")
}

// CategoriesBySize sorts categories by size
type CategoriesBySize []*discordgo.MessageEmbedField

// Size ...
func (c CategoriesBySize) Size() int {
	return len(c)
}

// Len ...
func (c CategoriesBySize) Len() int {
	return len(c)
}

// Less ...
func (c CategoriesBySize) Less(a, b int) bool {
	return len(c[a].Value) < len(c[b].Value)
}

// Swap ...
func (c CategoriesBySize) Swap(a, b int) {
	c[a], c[b] = c[b], c[a]
}

// Help maps a list of available commands and descends into subrouters.
func (m *Module) Help(ctx *system.Context) {

	if cmd := ctx.Args.After(); cmd != "" {
		if route, _ := ctx.System.CommandRouter.FindMatch(cmd); route != nil {
			ctx.ReplyEmbed(dream.NewEmbed().
				SetTitle(route.Name).
				SetDescription(route.Desc).
				SetColor(system.StatusNotify).
				MessageEmbed)
			return
		}
		ctx.ReplyError(errors.New("Command not found"))
		return
	}

	embed := depthcharge(ctx.System.CommandRouter, nil, 0)

	for _, v := range embed.Fields {
		v.Value = "```" + v.Value + "```"
	}

	sort.Sort(sort.Reverse(CategoriesBySize(embed.Fields)))

	_, err := ctx.ReplyEmbed(embed.
		SetColor(system.StatusNotify).
		// SetThumbnail(ctx.Ses.DG.State.User.AvatarURL("512")).
		InlineAllFields().
		SetDescription("`Bot prefix: " + ctx.System.Config.Prefix + "` type `help [command]` for more information\nCommands separated with `|` represent alternative names.\nIndented commands are subroutes of their parent commands").
		MessageEmbed)
	if err != nil {
		ctx.ReplyError(err)
	}
}

// Depthcharge recursively generates a help embed from a CommandRouter and its subrouters
func depthcharge(r *system.CommandRouter, embed *dream.Embed, depth int) *dream.Embed {
	if embed == nil {
		embed = dream.NewEmbed()
	}

	depthString := func(text string, depth int, subrouter bool) string {
		quote := ""
		if subrouter {
			quote = "`"
		}
		return strings.Repeat("\t", depth) + quote + text + quote + "\n"
	}

	getField := func(name string) *discordgo.MessageEmbedField {
		for _, v := range embed.Fields {
			if v.Name == name {
				return v
			}
		}
		if name == "" {
			name = "undefined"
		}
		field := &discordgo.MessageEmbedField{Name: name}
		embed.Fields = append(embed.Fields, field)
		return field
	}

	removeField := func(name string) {
		for i, v := range embed.Fields {
			if v.Name == name {
				embed.Fields = append(embed.Fields[:i], embed.Fields[i+1:]...)
				return
			}
		}
	}

	for _, v := range r.Routes {
		field := getField(v.Category)

		var tag string
		if !v.Disabled {
			field.Value += depthString(tag+v.Name+tag, depth, false)
		}

		if field.Value == "" {
			removeField(v.Category)
		}
	}

	for _, v := range r.Subrouters {
		if v.Disabled || (v.CommandRoute != nil && v.CommandRoute.Disabled) {
			continue
		}
		field := getField(v.Category())
		field.Value += depthString(v.Name, depth, true)
		embed = depthcharge(v.Router, embed, depth+1)
	}

	return embed
}
<|endoftext|>


<commit_before>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"

	"github.com/elves/elvish/util"
)

type renderer interface {
	render(b *buffer)
}

func render(r renderer, width int) *buffer {
	if r == nil {
		return nil
	}
	b := newBuffer(width)
	r.render(b)
	return b
}

type modeLineRenderer struct {
	title  string
	filter string
}

func (ml modeLineRenderer) render(b *buffer) {
	b.writes(ml.title, styleForMode.String())
	b.writes(" ", "")
	b.writes(ml.filter, styleForFilter.String())
	b.dot = b.cursor()
}

type modeLineWithScrollBarRenderer struct {
	modeLineRenderer
	n, low, high int
}

func (ml modeLineWithScrollBarRenderer) render(b *buffer) {
	ml.modeLineRenderer.render(b)

	scrollbarWidth := b.width - lineWidth(b.cells[len(b.cells)-1]) - 2
	if scrollbarWidth >= 3 {
		b.writes(" ", "")
		writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth)
	}
}

type placeholderRenderer string

func (lp placeholderRenderer) render(b *buffer) {
	b.writes(util.TrimWcwidth(string(lp), b.width), "")
}

type listingRenderer struct {
	// A List of styled items.
	list.List
}

func (ls listingRenderer) render(b *buffer) {
	for p := ls.Front(); p != nil; p = p.Next() {
		line := p.Value.(styled)
		if p != ls.Front() {
			b.newline()
		}
		b.writes(util.ForceWcwidth(line.text, b.width), line.styles.String())
	}
}

type listingWithScrollBarRenderer struct {
	listingRenderer
	n, low, high, height int
}

func (ls listingWithScrollBarRenderer) render(b *buffer) {
	b1 := render(ls.listingRenderer, b.width-1)
	b.extendHorizontal(b1, 0)

	scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height)
	b.extendHorizontal(scrollbar, b.width-1)
}

type navRenderer struct {
	maxHeight                      int
	fwParent, fwCurrent, fwPreview int
	parent, current, preview       renderer
}

func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 renderer) renderer {
	return &navRenderer{h, w1, w2, w3, r1, r2, r3}
}

func (nr *navRenderer) render(b *buffer) {
	margin := navigationListingColMargin

	w := b.width - margin*2
	ws := distributeWidths(w,
		[]float64{parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{nr.fwParent, nr.fwCurrent, nr.fwPreview},
	)
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	bParent := render(nr.parent, wParent)
	b.extendHorizontal(bParent, 0)

	bCurrent := render(nr.current, wCurrent)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := render(nr.preview, wPreview)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}
}

// linesRenderer renders lines with a uniform style.
type linesRenderer struct {
	lines []string
	style string
}

func (nr linesRenderer) render(b *buffer) {
	b.writes(strings.Join(nr.lines, "\n"), "")
}

// cmdlineRenderer renders the command line, including the prompt, the user's
// input and the rprompt.
type cmdlineRenderer struct {
	prompt  []*styled
	tokens  []Token
	dot     int
	rprompt []*styled

	hasComp   bool
	compBegin int
	compEnd   int
	compText  string

	hasHist   bool
	histBegin int
	histText  string
}

func newCmdlineRenderer(p []*styled, t []Token, d int, rp []*styled) *cmdlineRenderer {
	return &cmdlineRenderer{prompt: p, tokens: t, dot: d, rprompt: rp}
}

func (clr *cmdlineRenderer) setComp(b, e int, t string) {
	clr.hasComp = true
	clr.compBegin, clr.compEnd, clr.compText = b, e, t
}

func (clr *cmdlineRenderer) setHist(b int, t string) {
	clr.hasHist = true
	clr.histBegin, clr.histText = b, t
}

func (clr *cmdlineRenderer) render(b *buffer) {
	b.newlineWhenFull = true

	b.writeStyleds(clr.prompt)

	if b.line() == 0 && b.col*2 < b.width {
		b.indent = b.col
	}

	// i keeps track of number of bytes written.
	i := 0

	// nowAt is called at every rune boundary.
	nowAt := func(i int) {
		if clr.hasComp && i == clr.compBegin {
			b.writes(clr.compText, styleForCompleted.String())
		}
		if i == clr.dot {
			b.dot = b.cursor()
		}
	}
	nowAt(0)
tokens:
	for _, token := range clr.tokens {
		for _, r := range token.Text {
			if clr.hasComp && clr.compBegin <= i && i < clr.compEnd {
				// Do nothing. This part is replaced by the completion candidate.
			} else {
				b.write(r, joinStyles(styleForType[token.Type], token.MoreStyle).String())
			}
			i += utf8.RuneLen(r)

			nowAt(i)
			if clr.hasHist && i == clr.histBegin {
				// Put the rest of current history, position the cursor at the
				// end of the line, and finish writing
				b.writes(clr.histText, styleForCompletedHistory.String())
				b.dot = b.cursor()
				break tokens
			}
		}
	}

	// Write rprompt
	if len(clr.rprompt) > 0 {
		padding := b.width - b.col
		for _, s := range clr.rprompt {
			padding -= util.Wcswidth(s.text)
		}
		if padding >= 1 {
			b.newlineWhenFull = false
			b.writePadding(padding, "")
			b.writeStyleds(clr.rprompt)
		}
	}
}

// editorRenderer renders the entire editor.
type editorRenderer struct {
	*editorState
	height  int
	bufNoti *buffer
}

func (er *editorRenderer) render(buf *buffer) {
	height, width, es := er.height, buf.width, er.editorState

	mode := es.mode.Mode()

	var bufNoti, bufLine, bufMode, bufTips, bufListing *buffer
	// butNoti
	if len(es.notifications) > 0 {
		bufNoti = render(linesRenderer{es.notifications, ""}, width)
		es.notifications = nil
	}

	// bufLine
	clr := newCmdlineRenderer(es.promptContent, es.tokens, es.dot, es.rpromptContent)
	switch mode {
	case modeCompletion:
		c := es.completion
		clr.setComp(c.begin, c.end, c.selectedCandidate().text)
	case modeHistory:
		begin := len(es.hist.prefix)
		clr.setHist(begin, es.hist.line[begin:])
	}
	bufLine = render(clr, width)

	// bufMode
	bufMode = render(es.mode.ModeLine(), width)

	// bufTips
	// TODO tips is assumed to contain no newlines.
	if len(es.tips) > 0 {
		bufTips = render(linesRenderer{es.tips, styleForTip.String()}, width)
	}

	hListing := 0
	// Trim lines and determine the maximum height for bufListing
	// TODO come up with a UI to tell the user that something is not shown.
	switch {
	case height >= lines(bufNoti, bufLine, bufMode, bufTips):
		hListing = height - lines(bufLine, bufMode, bufTips)
	case height >= lines(bufNoti, bufLine, bufTips):
		bufMode = nil
	case height >= lines(bufNoti, bufLine):
		bufMode = nil
		if bufTips != nil {
			bufTips.trimToLines(0, height-lines(bufNoti, bufLine))
		}
	case height >= lines(bufLine):
		bufTips, bufMode = nil, nil
		if bufNoti != nil {
			n := len(bufNoti.cells)
			bufNoti.trimToLines(n-(height-lines(bufLine)), n)
		}
	case height >= 1:
		bufNoti, bufTips, bufMode = nil, nil, nil
		dotLine := bufLine.dot.line
		bufLine.trimToLines(dotLine+1-height, dotLine+1)
	default:
		// Broken terminal. Still try to render one line of bufLine.
		bufNoti, bufTips, bufMode = nil, nil, nil
		dotLine := bufLine.dot.line
		bufLine.trimToLines(dotLine, dotLine+1)
	}

	// bufListing.
	if hListing > 0 {
		if lister, ok := es.mode.(ListRenderer); ok {
			bufListing = lister.ListRender(width, hListing)
		} else if lister, ok := es.mode.(Lister); ok {
			bufListing = render(lister.List(hListing), width)
		}
		// XXX When in completion mode, we re-render the mode line, since the
		// scrollbar in the mode line depends on completion.lastShown which is
		// only known after the listing has been rendered. Since rendering the
		// scrollbar never adds additional lines to bufMode, we may do this
		// without recalculating the layout.
		if mode == modeCompletion {
			bufMode = render(es.mode.ModeLine(), width)
		}
	}

	if logWriterDetail {
		Logger.Printf("bufLine %d, bufMode %d, bufTips %d, bufListing %d",
			lines(bufLine), lines(bufMode), lines(bufTips), lines(bufListing))
	}

	// XXX
	buf.cells = nil
	// Combine buffers (reusing bufLine)
	buf.extend(bufLine, true)
	cursorOnModeLine := false
	if coml, ok := es.mode.(CursorOnModeLiner); ok {
		cursorOnModeLine = coml.CursorOnModeLine()
	}
	buf.extend(bufMode, cursorOnModeLine)
	buf.extend(bufTips, false)
	buf.extend(bufListing, false)

	er.bufNoti = bufNoti
}
<commit_msg>Fix rendering of history when prefix is empty.<commit_after>package edit

import (
	"container/list"
	"strings"
	"unicode/utf8"

	"github.com/elves/elvish/util"
)

type renderer interface {
	render(b *buffer)
}

func render(r renderer, width int) *buffer {
	if r == nil {
		return nil
	}
	b := newBuffer(width)
	r.render(b)
	return b
}

type modeLineRenderer struct {
	title  string
	filter string
}

func (ml modeLineRenderer) render(b *buffer) {
	b.writes(ml.title, styleForMode.String())
	b.writes(" ", "")
	b.writes(ml.filter, styleForFilter.String())
	b.dot = b.cursor()
}

type modeLineWithScrollBarRenderer struct {
	modeLineRenderer
	n, low, high int
}

func (ml modeLineWithScrollBarRenderer) render(b *buffer) {
	ml.modeLineRenderer.render(b)

	scrollbarWidth := b.width - lineWidth(b.cells[len(b.cells)-1]) - 2
	if scrollbarWidth >= 3 {
		b.writes(" ", "")
		writeHorizontalScrollbar(b, ml.n, ml.low, ml.high, scrollbarWidth)
	}
}

type placeholderRenderer string

func (lp placeholderRenderer) render(b *buffer) {
	b.writes(util.TrimWcwidth(string(lp), b.width), "")
}

type listingRenderer struct {
	// A List of styled items.
	list.List
}

func (ls listingRenderer) render(b *buffer) {
	for p := ls.Front(); p != nil; p = p.Next() {
		line := p.Value.(styled)
		if p != ls.Front() {
			b.newline()
		}
		b.writes(util.ForceWcwidth(line.text, b.width), line.styles.String())
	}
}

type listingWithScrollBarRenderer struct {
	listingRenderer
	n, low, high, height int
}

func (ls listingWithScrollBarRenderer) render(b *buffer) {
	b1 := render(ls.listingRenderer, b.width-1)
	b.extendHorizontal(b1, 0)

	scrollbar := renderScrollbar(ls.n, ls.low, ls.high, ls.height)
	b.extendHorizontal(scrollbar, b.width-1)
}

type navRenderer struct {
	maxHeight                      int
	fwParent, fwCurrent, fwPreview int
	parent, current, preview       renderer
}

func makeNavRenderer(h int, w1, w2, w3 int, r1, r2, r3 renderer) renderer {
	return &navRenderer{h, w1, w2, w3, r1, r2, r3}
}

func (nr *navRenderer) render(b *buffer) {
	margin := navigationListingColMargin

	w := b.width - margin*2
	ws := distributeWidths(w,
		[]float64{parentColumnWeight, currentColumnWeight, previewColumnWeight},
		[]int{nr.fwParent, nr.fwCurrent, nr.fwPreview},
	)
	wParent, wCurrent, wPreview := ws[0], ws[1], ws[2]

	bParent := render(nr.parent, wParent)
	b.extendHorizontal(bParent, 0)

	bCurrent := render(nr.current, wCurrent)
	b.extendHorizontal(bCurrent, wParent+margin)

	if wPreview > 0 {
		bPreview := render(nr.preview, wPreview)
		b.extendHorizontal(bPreview, wParent+wCurrent+2*margin)
	}
}

// linesRenderer renders lines with a uniform style.
type linesRenderer struct {
	lines []string
	style string
}

func (nr linesRenderer) render(b *buffer) {
	b.writes(strings.Join(nr.lines, "\n"), "")
}

// cmdlineRenderer renders the command line, including the prompt, the user's
// input and the rprompt.
type cmdlineRenderer struct {
	prompt  []*styled
	tokens  []Token
	dot     int
	rprompt []*styled

	hasComp   bool
	compBegin int
	compEnd   int
	compText  string

	hasHist   bool
	histBegin int
	histText  string
}

func newCmdlineRenderer(p []*styled, t []Token, d int, rp []*styled) *cmdlineRenderer {
	return &cmdlineRenderer{prompt: p, tokens: t, dot: d, rprompt: rp}
}

func (clr *cmdlineRenderer) setComp(b, e int, t string) {
	clr.hasComp = true
	clr.compBegin, clr.compEnd, clr.compText = b, e, t
}

func (clr *cmdlineRenderer) setHist(b int, t string) {
	clr.hasHist = true
	clr.histBegin, clr.histText = b, t
}

func (clr *cmdlineRenderer) render(b *buffer) {
	b.newlineWhenFull = true

	b.writeStyleds(clr.prompt)

	if b.line() == 0 && b.col*2 < b.width {
		b.indent = b.col
	}

	// i keeps track of number of bytes written.
	i := 0

	// nowAt is called at every rune boundary.
	nowAt := func(i int) {
		if clr.hasComp && i == clr.compBegin {
			b.writes(clr.compText, styleForCompleted.String())
		}
		if i == clr.dot {
			b.dot = b.cursor()
		}
	}
	nowAt(0)
tokens:
	for _, token := range clr.tokens {
		for _, r := range token.Text {
			if clr.hasComp && clr.compBegin <= i && i < clr.compEnd {
				// Do nothing. This part is replaced by the completion candidate.
			} else {
				b.write(r, joinStyles(styleForType[token.Type], token.MoreStyle).String())
			}
			i += utf8.RuneLen(r)

			nowAt(i)
			if clr.hasHist && i == clr.histBegin {
				break tokens
			}
		}
	}

	if clr.hasHist {
		// Put the rest of current history and position the cursor at the
		// end of the line.
		b.writes(clr.histText, styleForCompletedHistory.String())
		b.dot = b.cursor()
	}

	// Write rprompt
	if len(clr.rprompt) > 0 {
		padding := b.width - b.col
		for _, s := range clr.rprompt {
			padding -= util.Wcswidth(s.text)
		}
		if padding >= 1 {
			b.newlineWhenFull = false
			b.writePadding(padding, "")
			b.writeStyleds(clr.rprompt)
		}
	}
}

// editorRenderer renders the entire editor.
type editorRenderer struct {
	*editorState
	height  int
	bufNoti *buffer
}

func (er *editorRenderer) render(buf *buffer) {
	height, width, es := er.height, buf.width, er.editorState

	mode := es.mode.Mode()

	var bufNoti, bufLine, bufMode, bufTips, bufListing *buffer
	// butNoti
	if len(es.notifications) > 0 {
		bufNoti = render(linesRenderer{es.notifications, ""}, width)
		es.notifications = nil
	}

	// bufLine
	clr := newCmdlineRenderer(es.promptContent, es.tokens, es.dot, es.rpromptContent)
	switch mode {
	case modeCompletion:
		c := es.completion
		clr.setComp(c.begin, c.end, c.selectedCandidate().text)
	case modeHistory:
		begin := len(es.hist.prefix)
		clr.setHist(begin, es.hist.line[begin:])
	}
	bufLine = render(clr, width)

	// bufMode
	bufMode = render(es.mode.ModeLine(), width)

	// bufTips
	// TODO tips is assumed to contain no newlines.
	if len(es.tips) > 0 {
		bufTips = render(linesRenderer{es.tips, styleForTip.String()}, width)
	}

	hListing := 0
	// Trim lines and determine the maximum height for bufListing
	// TODO come up with a UI to tell the user that something is not shown.
	switch {
	case height >= lines(bufNoti, bufLine, bufMode, bufTips):
		hListing = height - lines(bufLine, bufMode, bufTips)
	case height >= lines(bufNoti, bufLine, bufTips):
		bufMode = nil
	case height >= lines(bufNoti, bufLine):
		bufMode = nil
		if bufTips != nil {
			bufTips.trimToLines(0, height-lines(bufNoti, bufLine))
		}
	case height >= lines(bufLine):
		bufTips, bufMode = nil, nil
		if bufNoti != nil {
			n := len(bufNoti.cells)
			bufNoti.trimToLines(n-(height-lines(bufLine)), n)
		}
	case height >= 1:
		bufNoti, bufTips, bufMode = nil, nil, nil
		dotLine := bufLine.dot.line
		bufLine.trimToLines(dotLine+1-height, dotLine+1)
	default:
		// Broken terminal. Still try to render one line of bufLine.
		bufNoti, bufTips, bufMode = nil, nil, nil
		dotLine := bufLine.dot.line
		bufLine.trimToLines(dotLine, dotLine+1)
	}

	// bufListing.
	if hListing > 0 {
		if lister, ok := es.mode.(ListRenderer); ok {
			bufListing = lister.ListRender(width, hListing)
		} else if lister, ok := es.mode.(Lister); ok {
			bufListing = render(lister.List(hListing), width)
		}
		// XXX When in completion mode, we re-render the mode line, since the
		// scrollbar in the mode line depends on completion.lastShown which is
		// only known after the listing has been rendered. Since rendering the
		// scrollbar never adds additional lines to bufMode, we may do this
		// without recalculating the layout.
		if mode == modeCompletion {
			bufMode = render(es.mode.ModeLine(), width)
		}
	}

	if logWriterDetail {
		Logger.Printf("bufLine %d, bufMode %d, bufTips %d, bufListing %d",
			lines(bufLine), lines(bufMode), lines(bufTips), lines(bufListing))
	}

	// XXX
	buf.cells = nil
	// Combine buffers (reusing bufLine)
	buf.extend(bufLine, true)
	cursorOnModeLine := false
	if coml, ok := es.mode.(CursorOnModeLiner); ok {
		cursorOnModeLine = coml.CursorOnModeLine()
	}
	buf.extend(bufMode, cursorOnModeLine)
	buf.extend(bufTips, false)
	buf.extend(bufListing, false)

	er.bufNoti = bufNoti
}
<|endoftext|>


<commit_before>package gonx

import (
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

func TestDatetimeFilter(t *testing.T) {
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	assert.Implements(t, (*Filter)(nil), filter)

	entry := NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
	})
	assert.Nil(t, filter.Filter(entry))

	entry = NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
	})
	assert.Equal(t, filter.Filter(entry), entry)
}

func TestDatetimeFilterStart(t *testing.T) {
	// filter contains lower border only
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
	}
	assert.Implements(t, (*Filter)(nil), filter)

	entry := NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
	})
	assert.Nil(t, filter.Filter(entry))

	entry = NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
	})
	assert.Equal(t, filter.Filter(entry), entry)
}

func TestDatetimeFilterEnd(t *testing.T) {
	// filter contains upper border only
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	assert.Implements(t, (*Filter)(nil), filter)

	entry := NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
	})
	assert.Equal(t, filter.Filter(entry), entry)

	entry = NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
	})
	assert.Nil(t, filter.Filter(entry))
}

func TestDatetimeReduce(t *testing.T) {
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	assert.Implements(t, (*Reducer)(nil), filter)

	// Prepare input channel
	input := make(chan *Entry, 5)
	input <- NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
		"foo":       "12",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
		"foo":       "34",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-03-03T03:03:03Z",
		"foo":       "56",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-04-04T04:04:04Z",
		"foo":       "78",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
		"foo":       "90",
	})
	close(input)

	output := make(chan *Entry, 5) // Make it buffered to avoid deadlock
	filter.Reduce(input, output)

	expected := []string{
		"'timestamp'=2015-02-02T02:02:02Z;'foo'=34",
		"'timestamp'=2015-03-03T03:03:03Z;'foo'=56",
		"'timestamp'=2015-04-04T04:04:04Z;'foo'=78",
	}
	results := []string{}

	for result := range output {
		results = append(
			results,
			result.FieldsHash([]string{"timestamp", "foo"}),
		)
	}
	assert.Equal(t, results, expected)
}

func TestChainFilterWithRedicer(t *testing.T) {
	// Prepare input channel
	input := make(chan *Entry, 5)
	input <- NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
		"foo":       "12",
		"bar":       "34",
		"baz":       "56",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
		"foo":       "34",
		"bar":       "56",
		"baz":       "78",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-04-04T04:04:04Z",
		"foo":       "78",
		"bar":       "90",
		"baz":       "12",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
		"foo":       "90",
		"bar":       "34",
		"baz":       "56",
	})
	close(input)

	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	chain := NewChain(filter, &Avg{[]string{"foo", "bar"}}, &Count{})

	output := make(chan *Entry, 5) // Make it buffered to avoid deadlock
	chain.Reduce(input, output)

	result, ok := <-output
	assert.True(t, ok)

	value, err := result.FloatField("foo")
	assert.NoError(t, err)
	assert.Equal(t, value, (34.0+78)/2.0)

	value, err = result.FloatField("bar")
	assert.NoError(t, err)
	assert.Equal(t, value, (56.0+90)/2.0)

	count, err := result.Field("count")
	assert.NoError(t, err)
	assert.Equal(t, count, "2")

	_, err = result.Field("buz")
	assert.Error(t, err)
}
<commit_msg>test Datetime filter with goconvey<commit_after>package gonx

import (
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
	"testing"
	"time"
)

func TestFilter(t *testing.T) {
	Convey("Test filter input channel", t, func() {
		Convey("Datetime filter", func() {
			filter := &Datetime{
				Field:  "timestamp",
				Format: time.RFC3339,
				Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
				End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
			}

			// entry is out of datetime range
			entry := NewEntry(Fields{
				"timestamp": "2015-01-01T01:01:01Z",
			})
			So(filter.Filter(entry), ShouldBeNil)

			// entry's timestamp meets filter condition
			entry = NewEntry(Fields{
				"timestamp": "2015-02-02T02:02:02Z",
			})
			So(filter.Filter(entry), ShouldEqual, entry)
		})
	})
}

func TestDatetimeFilterStart(t *testing.T) {
	// filter contains lower border only
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
	}
	assert.Implements(t, (*Filter)(nil), filter)

	entry := NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
	})
	assert.Nil(t, filter.Filter(entry))

	entry = NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
	})
	assert.Equal(t, filter.Filter(entry), entry)
}

func TestDatetimeFilterEnd(t *testing.T) {
	// filter contains upper border only
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	assert.Implements(t, (*Filter)(nil), filter)

	entry := NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
	})
	assert.Equal(t, filter.Filter(entry), entry)

	entry = NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
	})
	assert.Nil(t, filter.Filter(entry))
}

func TestDatetimeReduce(t *testing.T) {
	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	assert.Implements(t, (*Reducer)(nil), filter)

	// Prepare input channel
	input := make(chan *Entry, 5)
	input <- NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
		"foo":       "12",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
		"foo":       "34",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-03-03T03:03:03Z",
		"foo":       "56",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-04-04T04:04:04Z",
		"foo":       "78",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
		"foo":       "90",
	})
	close(input)

	output := make(chan *Entry, 5) // Make it buffered to avoid deadlock
	filter.Reduce(input, output)

	expected := []string{
		"'timestamp'=2015-02-02T02:02:02Z;'foo'=34",
		"'timestamp'=2015-03-03T03:03:03Z;'foo'=56",
		"'timestamp'=2015-04-04T04:04:04Z;'foo'=78",
	}
	results := []string{}

	for result := range output {
		results = append(
			results,
			result.FieldsHash([]string{"timestamp", "foo"}),
		)
	}
	assert.Equal(t, results, expected)
}

func TestChainFilterWithRedicer(t *testing.T) {
	// Prepare input channel
	input := make(chan *Entry, 5)
	input <- NewEntry(Fields{
		"timestamp": "2015-01-01T01:01:01Z",
		"foo":       "12",
		"bar":       "34",
		"baz":       "56",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-02-02T02:02:02Z",
		"foo":       "34",
		"bar":       "56",
		"baz":       "78",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-04-04T04:04:04Z",
		"foo":       "78",
		"bar":       "90",
		"baz":       "12",
	})
	input <- NewEntry(Fields{
		"timestamp": "2015-05-05T05:05:05Z",
		"foo":       "90",
		"bar":       "34",
		"baz":       "56",
	})
	close(input)

	filter := &Datetime{
		Field:  "timestamp",
		Format: time.RFC3339,
		Start:  time.Date(2015, time.February, 2, 2, 2, 2, 0, time.UTC),
		End:    time.Date(2015, time.May, 5, 5, 5, 5, 0, time.UTC),
	}
	chain := NewChain(filter, &Avg{[]string{"foo", "bar"}}, &Count{})

	output := make(chan *Entry, 5) // Make it buffered to avoid deadlock
	chain.Reduce(input, output)

	result, ok := <-output
	assert.True(t, ok)

	value, err := result.FloatField("foo")
	assert.NoError(t, err)
	assert.Equal(t, value, (34.0+78)/2.0)

	value, err = result.FloatField("bar")
	assert.NoError(t, err)
	assert.Equal(t, value, (56.0+90)/2.0)

	count, err := result.Field("count")
	assert.NoError(t, err)
	assert.Equal(t, count, "2")

	_, err = result.Field("buz")
	assert.Error(t, err)
}
<|endoftext|>


<commit_before>package expr

import (
	"fmt"
	"regexp"
	"strings"
)

//go:generate stringer -type=exprType
type exprType int

// the following types let the parser express the type it parsed from the input targets
const (
	etName   exprType = iota // a string without quotes, e.g. metric.name, metric.*.query.patt* or special values like None which some functions expect
	etBool                   // True or False
	etFunc                   // a function call like movingAverage(foo, bar)
	etInt                    // any number with no decimal numbers, parsed as a float64 value
	etFloat                  // any number with decimals, parsed as a float64 value
	etString                 // anything that was between '' or ""
)

// expr represents a parsed expression
type expr struct {
	etype     exprType
	float     float64          // for etFloat
	int       int64            // for etInt
	str       string           // for etName, etFunc (func name), etString, etBool, etInt and etFloat (unparsed input value)
	bool      bool             // for etBool
	args      []*expr          // for etFunc: positional args which itself are expressions
	namedArgs map[string]*expr // for etFunc: named args which itself are expressions
	argsStr   string           // for etFunc: literal string of how all the args were specified
}

func (e expr) Print(indent int) string {
	space := strings.Repeat(" ", indent)
	switch e.etype {
	case etName:
		return fmt.Sprintf("%sexpr-target %q", space, e.str)
	case etFunc:
		var args string
		for _, a := range e.args {
			args += a.Print(indent+2) + ",\n"
		}
		for k, v := range e.namedArgs {
			args += strings.Repeat(" ", indent+2) + k + "=" + v.Print(0) + ",\n"
		}
		return fmt.Sprintf("%sexpr-func %s(\n%s%s)", space, e.str, args, space)
	case etFloat:
		return fmt.Sprintf("%sexpr-float %v", space, e.float)
	case etInt:
		return fmt.Sprintf("%sexpr-int %v", space, e.int)
	case etString:
		return fmt.Sprintf("%sexpr-string %q", space, e.str)
	}
	return "HUH-SHOULD-NEVER-HAPPEN"
}

// consumeBasicArg verifies that the argument at given pos matches the expected arg
// it's up to the caller to assure that given pos is valid before calling.
// if arg allows for multiple arguments, pos is advanced to cover all accepted arguments.
// if the arg is a "basic" arg (meaning not a series, seriesList or seriesLists) the
// appropriate value(s) will be assigned to exp.val
// for non-basic args, see consumeSeriesArg which should be called after deducing the required from/to.
// the returned pos is always the index where the next argument should be.
func (e expr) consumeBasicArg(pos int, exp Arg) (int, error) {
	got := e.args[pos]
	if got.etype == etName && got.str == "None" && !exp.Optional() {
		return 0, ErrMissingArg
	}
	switch v := exp.(type) {
	case ArgSeries, ArgSeriesList:
		if got.etype != etName && got.etype != etFunc {
			return 0, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
	case ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return 0, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		// special case! consume all subsequent args (if any) in args that will also yield a seriesList
		for len(e.args) > pos+1 && (e.args[pos+1].etype == etName || e.args[pos+1].etype == etFunc) {
			pos++
		}
	case ArgIn:
		for _, a := range v.args {
			p, err := e.consumeBasicArg(pos, a)
			if err == nil {
				return p, err
			}
		}
		expStr := []string{}
		for _, a := range v.args {
			expStr = append(expStr, fmt.Sprintf("%T", a))
		}
		return 0, ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
	case ArgInt:
		if got.etype != etInt {
			return 0, ErrBadArgumentStr{"int", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		*v.val = got.int
	case ArgInts:
		// consume all args (if any) in args that will yield an integer
		for ; pos < len(e.args) && e.args[pos].etype == etInt; pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, e.args[pos].int)
		}
		return pos, nil
	case ArgFloat:
		// integer is also a valid float, just happened to have no decimals
		if got.etype != etFloat && got.etype != etInt {
			return 0, ErrBadArgumentStr{"float", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		if got.etype == etInt {
			*v.val = float64(got.int)
		} else {
			*v.val = got.float
		}
	case ArgString:
		if got.etype != etString {
			return 0, ErrBadArgumentStr{"string", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		*v.val = got.str
	case ArgStrings:
		// consume all args (if any) in args that will yield a string
		for ; pos < len(e.args) && e.args[pos].etype == etString; pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, e.args[pos].str)
		}
		return pos, nil
	case ArgRegex:
		if got.etype != etString {
			return 0, ErrBadArgumentStr{"string (regex)", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		re, err := regexp.Compile(got.str)
		if err != nil {
			return 0, err
		}
		*v.val = re
	case ArgBool:
		if got.etype == etBool {
			*v.val = got.bool
			break
		}
		if got.etype == etString {
			if val, ok := strToBool(got.str); ok {
				*v.val = val
				break
			}
		}
		return 0, ErrBadArgumentStr{"boolean", got.etype.String()}
	case ArgStringsOrInts:
		// consume all args (if any) in args that will yield a string or int
		for ; len(e.args) > pos && (e.args[pos].etype == etString || e.args[pos].etype == etInt); pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, *e.args[pos])
		}
		return pos, nil
	default:
		return 0, fmt.Errorf("unsupported type %T for consumeBasicArg", exp)
	}
	pos++
	return pos, nil
}

func generateValidatorError(key string, err error) error {
	if len(key) == 0 {
		return err
	}
	return fmt.Errorf("%s: %s", key, err.Error())
}

// consumeSeriesArg verifies that the argument at given pos matches the expected arg
// it's up to the caller to assure that given pos is valid before calling.
// if arg allows for multiple arguments, pos is advanced to cover all accepted arguments.
// if the arg is a "basic", no value is saved (it's up to consumeBasicArg to do that)
// but for non-basic args (meaning a series, seriesList or seriesLists) the
// appropriate value(s) will be assigned to exp.val
// the returned pos is always the index where the next argument should be.
func (e expr) consumeSeriesArg(pos int, exp Arg, context Context, stable bool, reqs []Req) (int, []Req, error) {
	got := e.args[pos]
	var err error
	var fn GraphiteFunc
	if got.etype == etName && got.str == "None" {
		pos++
		return pos, reqs, nil
	}
	switch v := exp.(type) {
	case ArgIn:
		if got.etype == etName || got.etype == etFunc {
			for _, a := range v.args {
				switch v := a.(type) {
				case ArgSeries, ArgSeriesList, ArgSeriesLists:
					p, reqs, err := e.consumeSeriesArg(pos, v, context, stable, reqs)
					if err != nil {
						return 0, nil, err
					}
					return p, reqs, err
				}
			}
			expStr := []string{}
			for _, a := range v.args {
				expStr = append(expStr, fmt.Sprintf("%T", a))
			}
			return 0, nil, ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
		}

	case ArgSeries:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = fn
	case ArgSeriesList:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = fn
	case ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = append(*v.val, fn)
		// special case! consume all subsequent args (if any) in args that will also yield a seriesList
		for len(e.args) > pos+1 && (e.args[pos+1].etype == etName || e.args[pos+1].etype == etFunc) {
			pos++
			fn, reqs, err = newplan(e.args[pos], context, stable, reqs)
			if err != nil {
				return 0, nil, err
			}
			*v.val = append(*v.val, fn)
		}
	default:
		return 0, nil, fmt.Errorf("unsupported type %T for consumeSeriesArg", exp)
	}
	pos++
	return pos, reqs, nil
}

// consumeKwarg consumes the kwarg (by key k) and verifies it
// if the specified argument is valid, it is saved in exp.val
// where exp is the arg specified by the function that has the given key
func (e expr) consumeKwarg(key string, optArgs []Arg, got *expr) error {
	var found bool
	var exp Arg
	for _, exp = range optArgs {
		if exp.Key() == key {
			found = true
			break
		}
	}
	if !found {
		return ErrUnknownKwarg{key}
	}
	switch v := exp.(type) {
	case ArgIn:
		for _, a := range v.args {
			// interesting little trick here.. when using ArgIn you only have to set the key on ArgIn,
			// not for every individual sub-arg so to make sure we pass the key matching requirement,
			// we just call consumeKwarg with whatever the key is set to (typically "")
			err := e.consumeKwarg(a.Key(), []Arg{a}, got)
			if err == nil {
				return err
			}
		}
		expStr := []string{}
		for _, a := range v.args {
			expStr = append(expStr, fmt.Sprintf("%T", a))
		}
		return ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
	case ArgInt:
		if got.etype != etInt {
			return ErrBadKwarg{key, exp, got.etype}
		}
		*v.val = got.int
	case ArgFloat:
		switch got.etype {
		case etInt:
			// integer is also a valid float, just happened to have no decimals
			*v.val = float64(got.int)
		case etFloat:
			*v.val = got.float
		default:
			return ErrBadKwarg{key, exp, got.etype}
		}
	case ArgString:
		if got.etype != etString {
			return ErrBadKwarg{key, exp, got.etype}
		}
		*v.val = got.str
	case ArgBool:
		if got.etype == etBool {
			*v.val = got.bool
			break
		}
		if got.etype == etString {
			if val, ok := strToBool(got.str); ok {
				*v.val = val
				break
			}
		}
		return ErrBadKwarg{key, exp, got.etype}
	case ArgSeries, ArgSeriesList, ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		// TODO consume series arg
	default:
		return fmt.Errorf("unsupported type %T for consumeKwarg", exp)
	}
	return nil
}
<commit_msg>skip arg if optional None<commit_after>package expr

import (
	"fmt"
	"regexp"
	"strings"
)

//go:generate stringer -type=exprType
type exprType int

// the following types let the parser express the type it parsed from the input targets
const (
	etName   exprType = iota // a string without quotes, e.g. metric.name, metric.*.query.patt* or special values like None which some functions expect
	etBool                   // True or False
	etFunc                   // a function call like movingAverage(foo, bar)
	etInt                    // any number with no decimal numbers, parsed as a float64 value
	etFloat                  // any number with decimals, parsed as a float64 value
	etString                 // anything that was between '' or ""
)

// expr represents a parsed expression
type expr struct {
	etype     exprType
	float     float64          // for etFloat
	int       int64            // for etInt
	str       string           // for etName, etFunc (func name), etString, etBool, etInt and etFloat (unparsed input value)
	bool      bool             // for etBool
	args      []*expr          // for etFunc: positional args which itself are expressions
	namedArgs map[string]*expr // for etFunc: named args which itself are expressions
	argsStr   string           // for etFunc: literal string of how all the args were specified
}

func (e expr) Print(indent int) string {
	space := strings.Repeat(" ", indent)
	switch e.etype {
	case etName:
		return fmt.Sprintf("%sexpr-target %q", space, e.str)
	case etFunc:
		var args string
		for _, a := range e.args {
			args += a.Print(indent+2) + ",\n"
		}
		for k, v := range e.namedArgs {
			args += strings.Repeat(" ", indent+2) + k + "=" + v.Print(0) + ",\n"
		}
		return fmt.Sprintf("%sexpr-func %s(\n%s%s)", space, e.str, args, space)
	case etFloat:
		return fmt.Sprintf("%sexpr-float %v", space, e.float)
	case etInt:
		return fmt.Sprintf("%sexpr-int %v", space, e.int)
	case etString:
		return fmt.Sprintf("%sexpr-string %q", space, e.str)
	}
	return "HUH-SHOULD-NEVER-HAPPEN"
}

// consumeBasicArg verifies that the argument at given pos matches the expected arg
// it's up to the caller to assure that given pos is valid before calling.
// if arg allows for multiple arguments, pos is advanced to cover all accepted arguments.
// if the arg is a "basic" arg (meaning not a series, seriesList or seriesLists) the
// appropriate value(s) will be assigned to exp.val
// for non-basic args, see consumeSeriesArg which should be called after deducing the required from/to.
// the returned pos is always the index where the next argument should be.
func (e expr) consumeBasicArg(pos int, exp Arg) (int, error) {
	got := e.args[pos]
	if got.etype == etName && got.str == "None" {
		if !exp.Optional() {
			return 0, ErrMissingArg
		} else {
			pos++
			return pos, nil
		}
	}
	switch v := exp.(type) {
	case ArgSeries, ArgSeriesList:
		if got.etype != etName && got.etype != etFunc {
			return 0, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
	case ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return 0, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		// special case! consume all subsequent args (if any) in args that will also yield a seriesList
		for len(e.args) > pos+1 && (e.args[pos+1].etype == etName || e.args[pos+1].etype == etFunc) {
			pos++
		}
	case ArgIn:
		for _, a := range v.args {

			p, err := e.consumeBasicArg(pos, a)
			if err == nil {
				return p, err
			}
		}
		expStr := []string{}
		for _, a := range v.args {
			expStr = append(expStr, fmt.Sprintf("%T", a))
		}
		return 0, ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
	case ArgInt:
		if got.etype != etInt {
			return 0, ErrBadArgumentStr{"int", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		*v.val = got.int
	case ArgInts:
		// consume all args (if any) in args that will yield an integer
		for ; pos < len(e.args) && e.args[pos].etype == etInt; pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, e.args[pos].int)
		}
		return pos, nil
	case ArgFloat:
		// integer is also a valid float, just happened to have no decimals
		if got.etype != etFloat && got.etype != etInt {
			return 0, ErrBadArgumentStr{"float", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		if got.etype == etInt {
			*v.val = float64(got.int)
		} else {
			*v.val = got.float
		}
	case ArgString:
		if got.etype != etString {
			return 0, ErrBadArgumentStr{"string", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		*v.val = got.str
	case ArgStrings:
		// consume all args (if any) in args that will yield a string
		for ; pos < len(e.args) && e.args[pos].etype == etString; pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, e.args[pos].str)
		}
		return pos, nil
	case ArgRegex:
		if got.etype != etString {
			return 0, ErrBadArgumentStr{"string (regex)", got.etype.String()}
		}
		for _, va := range v.validator {
			if err := va(got); err != nil {
				return 0, generateValidatorError(v.key, err)
			}
		}
		re, err := regexp.Compile(got.str)
		if err != nil {
			return 0, err
		}
		*v.val = re
	case ArgBool:
		if got.etype == etBool {
			*v.val = got.bool
			break
		}
		if got.etype == etString {
			if val, ok := strToBool(got.str); ok {
				*v.val = val
				break
			}
		}
		return 0, ErrBadArgumentStr{"boolean", got.etype.String()}
	case ArgStringsOrInts:
		// consume all args (if any) in args that will yield a string or int
		for ; len(e.args) > pos && (e.args[pos].etype == etString || e.args[pos].etype == etInt); pos++ {
			for _, va := range v.validator {
				if err := va(e.args[pos]); err != nil {
					return 0, generateValidatorError(v.key, err)
				}
			}
			*v.val = append(*v.val, *e.args[pos])
		}
		return pos, nil
	default:
		return 0, fmt.Errorf("unsupported type %T for consumeBasicArg", exp)
	}
	pos++
	return pos, nil
}

func generateValidatorError(key string, err error) error {
	if len(key) == 0 {
		return err
	}
	return fmt.Errorf("%s: %s", key, err.Error())
}

// consumeSeriesArg verifies that the argument at given pos matches the expected arg
// it's up to the caller to assure that given pos is valid before calling.
// if arg allows for multiple arguments, pos is advanced to cover all accepted arguments.
// if the arg is a "basic", no value is saved (it's up to consumeBasicArg to do that)
// but for non-basic args (meaning a series, seriesList or seriesLists) the
// appropriate value(s) will be assigned to exp.val
// the returned pos is always the index where the next argument should be.
func (e expr) consumeSeriesArg(pos int, exp Arg, context Context, stable bool, reqs []Req) (int, []Req, error) {
	got := e.args[pos]
	var err error
	var fn GraphiteFunc
	if got.etype == etName && got.str == "None" {
		pos++
		return pos, reqs, nil
	}
	switch v := exp.(type) {
	case ArgIn:
		if got.etype == etName || got.etype == etFunc {
			for _, a := range v.args {
				switch v := a.(type) {
				case ArgSeries, ArgSeriesList, ArgSeriesLists:
					p, reqs, err := e.consumeSeriesArg(pos, v, context, stable, reqs)
					if err != nil {
						return 0, nil, err
					}
					return p, reqs, err
				}
			}
			expStr := []string{}
			for _, a := range v.args {
				expStr = append(expStr, fmt.Sprintf("%T", a))
			}
			return 0, nil, ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
		}

	case ArgSeries:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = fn
	case ArgSeriesList:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = fn
	case ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return 0, nil, ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		fn, reqs, err = newplan(got, context, stable, reqs)
		if err != nil {
			return 0, nil, err
		}
		*v.val = append(*v.val, fn)
		// special case! consume all subsequent args (if any) in args that will also yield a seriesList
		for len(e.args) > pos+1 && (e.args[pos+1].etype == etName || e.args[pos+1].etype == etFunc) {
			pos++
			fn, reqs, err = newplan(e.args[pos], context, stable, reqs)
			if err != nil {
				return 0, nil, err
			}
			*v.val = append(*v.val, fn)
		}
	default:
		return 0, nil, fmt.Errorf("unsupported type %T for consumeSeriesArg", exp)
	}
	pos++
	return pos, reqs, nil
}

// consumeKwarg consumes the kwarg (by key k) and verifies it
// if the specified argument is valid, it is saved in exp.val
// where exp is the arg specified by the function that has the given key
func (e expr) consumeKwarg(key string, optArgs []Arg, got *expr) error {
	var found bool
	var exp Arg
	for _, exp = range optArgs {
		if exp.Key() == key {
			found = true
			break
		}
	}
	if !found {
		return ErrUnknownKwarg{key}
	}
	switch v := exp.(type) {
	case ArgIn:
		for _, a := range v.args {
			// interesting little trick here.. when using ArgIn you only have to set the key on ArgIn,
			// not for every individual sub-arg so to make sure we pass the key matching requirement,
			// we just call consumeKwarg with whatever the key is set to (typically "")
			err := e.consumeKwarg(a.Key(), []Arg{a}, got)
			if err == nil {
				return err
			}
		}
		expStr := []string{}
		for _, a := range v.args {
			expStr = append(expStr, fmt.Sprintf("%T", a))
		}
		return ErrBadArgumentStr{strings.Join(expStr, ","), got.etype.String()}
	case ArgInt:
		if got.etype != etInt {
			return ErrBadKwarg{key, exp, got.etype}
		}
		*v.val = got.int
	case ArgFloat:
		switch got.etype {
		case etInt:
			// integer is also a valid float, just happened to have no decimals
			*v.val = float64(got.int)
		case etFloat:
			*v.val = got.float
		default:
			return ErrBadKwarg{key, exp, got.etype}
		}
	case ArgString:
		if got.etype != etString {
			return ErrBadKwarg{key, exp, got.etype}
		}
		*v.val = got.str
	case ArgBool:
		if got.etype == etBool {
			*v.val = got.bool
			break
		}
		if got.etype == etString {
			if val, ok := strToBool(got.str); ok {
				*v.val = val
				break
			}
		}
		return ErrBadKwarg{key, exp, got.etype}
	case ArgSeries, ArgSeriesList, ArgSeriesLists:
		if got.etype != etName && got.etype != etFunc {
			return ErrBadArgumentStr{"func or name", got.etype.String()}
		}
		// TODO consume series arg
	default:
		return fmt.Errorf("unsupported type %T for consumeKwarg", exp)
	}
	return nil
}
<|endoftext|>