<commit_before>// Package rehttp implements an HTTP transport that handles retries.
//
// An HTTP Client can be created with a *rehttp.Transport as Transport
// and it will apply the retry strategy to its requests, e.g.:
//
//     client := &http.Client{
//       Transport: rehttp.NewTransport(
//         nil,                            // will use http.DefaultTransport
//         rehttp.RetryTemporaryErr(3),    // max 3 retries
//         rehttp.ConstDelay(time.Second), // wait 1s between retries
//       ),
//     }
//
// The retry strategy is provided by the Transport.Retry field, a function
// that returns whether or not the request should be retried, and if so, what
// delay to apply before retrying.
//
// For convenience, ToRetryFn combines two functions - a ShouldRetryFn
// that returns whether or not the request should be retried, and a DelayFn
// that returns the delay to apply - into a RetryFn that can be used as
// value for Transport.Retry.
//
// The package offers common delay strategies as ready-made functions that
// return a DelayFn:
//     - ConstDelay(delay time.Duration) DelayFn
//     - NoDelay() DelayFn
//     - ExponentialDelay(initialDelay time.Duration) DelayFn
//     - LinearDelay(initialDelay time.Duration) DelayFn
//
// It also provides common retry predicates that return a ShouldRetryFn:
//     - RetryTemporaryErr(maxRetries int) ShouldRetryFn
//     - RetryStatus500(maxRetries int) ShouldRetryFn
//
// Those can be combined with RetryAny as needed.
//
// The Transport will buffer the request's body in order to be able to
// retry the request, as a request attempt will consume and close the
// existing body. Sometimes this is not desirable, so it can be prevented
// by setting PreventRetryWithBody to true on the Transport. Doing so
// will disable retries when a request has a non-nil body, the RetryFn
// won't get called in those cases.
//
package rehttp

import (
	"bytes"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"time"
)

// terribly named interface to detect errors that support Temporary.
type temporaryer interface {
	Temporary() bool
}

// CancelRoundTripper is a RoundTripper that supports CancelRequest.
// The *http.Transport type implements this interface.
type CancelRoundTripper interface {
	http.RoundTripper
	CancelRequest(*http.Request)
}

// RetryFn is the signature for functions that implement retry strategies.
// The attempt parameter is the attempt number corresponding to the response
// and error received, starting at 0.
type RetryFn func(req *http.Request, res *http.Response, attempt int, err error) (bool, time.Duration)

// DelayFn is the signature for functions that return the delay to apply
// before the next retry. The attempt parameter is the attempt number
// corresponding to the response and error received, starting at 0.
type DelayFn func(req *http.Request, res *http.Response, attempt int, err error) time.Duration

// ShouldRetryFn is the signature for functions that return whether a
// retry should be done for the request. The attempt parameter is the
// attempt number corresponding to the response and error received,
// starting at 0.
type ShouldRetryFn func(req *http.Request, res *http.Response, attempt int, err error) bool

// NewTransport creates a Transport with a retry strategy based on
// shouldRetry and delay to control the retry logic. It uses the provided
// CancelRoundTripper to execute the requests. If rt is nil,
// http.DefaultTransport is used, but the package panics if it isn't set
// to a CancelRoundTripper (which it is by default).
func NewTransport(rt CancelRoundTripper, shouldRetry ShouldRetryFn, delay DelayFn) *Transport {
	if rt == nil {
		rt = http.DefaultTransport.(CancelRoundTripper)
	}
	return &Transport{
		rt:    rt,
		Retry: ToRetryFn(shouldRetry, delay),
	}
}

// ToRetryFn combines shouldRetry and delay into a RetryFn that can be used
// as Transport.Retry value.
func ToRetryFn(shouldRetry ShouldRetryFn, delay DelayFn) RetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) (bool, time.Duration) {
		retry := shouldRetry(req, res, attempt, err)
		if !retry {
			return false, 0
		}
		return true, delay(req, res, attempt, err)
	}
}

// RetryAny returns a ShouldRetryFn that allows a retry as long as one of
// the retryFns returns true.
func RetryAny(retryFns ...ShouldRetryFn) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		for _, fn := range retryFns {
			if fn(req, res, attempt, err) {
				return true
			}
		}
		return false
	}
}

// RetryTemporaryErr returns a ShouldRetryFn that retries up to maxRetries
// times for a temporary error. A temporary error is one that implements
// the Temporary() bool method. Most errors from the net package implement
// this.
func RetryTemporaryErr(maxRetries int) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		if attempt >= maxRetries {
			return false
		}
		if terr, ok := err.(temporaryer); ok {
			return terr.Temporary()
		}
		return false
	}
}

// RetryStatus500 returns a ShouldRetryFn that retries up to maxRetries times
// for a status code 5xx.
func RetryStatus500(maxRetries int) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		if attempt >= maxRetries {
			return false
		}
		return res != nil && res.StatusCode >= 500 && res.StatusCode < 600 // who knows
	}
}

// ConstDelay returns a DelayFn that always returns the same delay.
func ConstDelay(delay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return delay
	}
}

// NoDelay returns a DelayFn that always returns 0.
func NoDelay() DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return 0
	}
}

// ExponentialDelay returns a DelayFn that returns an exponential delay based
// on initialDelay at the power of attempt + 1 (so the first delay is
// initialDelay, then initialDelay ** 2, etc.).
func ExponentialDelay(initialDelay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return time.Duration(math.Pow(float64(initialDelay), float64(attempt+1)))
	}
}

// LinearDelay returns a DelayFn that returns a delay that grows linearly
// based on the number of attempts.
func LinearDelay(initialDelay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return time.Duration(attempt+1) * initialDelay
	}
}

// Transport wraps a CancelRoundTripper such as *http.Transport and adds
// retry logic.
type Transport struct {
	rt CancelRoundTripper

	// PreventRetryWithBody prevents retrying if the request has a body. Since
	// the body is consumed on a request attempt, in order to retry a request
	// with a body, the body has to be buffered in memory. Setting this
	// to true avoids this buffering, the retry logic is bypassed if a body
	// is present.
	PreventRetryWithBody bool

	// Retry is a function that determines if the request should be retried.
	// Unless a retry is prevented based on PreventRetryWithBody, all requests
	// go through that function, even those that are typically considered
	// successful.
	//
	// If it returns false, no retry is attempted, otherwise a retry is
	// attempted after the specified duration.
	Retry RetryFn
}

// Per Go's doc: "CancelRequest should only be called after RoundTrip
// has returned."
// So it should not have an impact on this Transport (it doesn't return before
// the retries are done).

// RoundTrip implements http.RoundTripper for the Transport type.
// It calls its underlying RoundTripper to execute the request, and
// adds retry logic as per its configuration.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	var attempt int
	preventRetry := req.Body != nil && t.PreventRetryWithBody

	// buffer the body if needed
	var br *bytes.Reader
	if req.Body != nil && !preventRetry {
		var buf bytes.Buffer
		if _, err := io.Copy(&buf, req.Body); err != nil {
			// cannot even try the first attempt, body has been consumed
			req.Body.Close()
			return nil, err
		}
		req.Body.Close()

		br = bytes.NewReader(buf.Bytes())
		req.Body = ioutil.NopCloser(br)
	}

	for {
		res, err := t.rt.RoundTrip(req)
		if preventRetry {
			return res, err
		}

		retry, delay := t.Retry(req, res, attempt, err)
		if !retry {
			return res, err
		}

		if br != nil {
			// Per Go's doc: "RoundTrip should not modify the request,
			// except for consuming and closing the Body", so the only thing
			// to reset on the request is the body, if any.
			if _, err := br.Seek(0, 0); err != nil {
				// failed to retry, return the results
				return res, err
			}
			req.Body = ioutil.NopCloser(br)
		}
		time.Sleep(delay)
		attempt++
	}
}
<commit_msg>rehttp: add retry strategy based on HTTP methods, prevent retry if request is cancelled<commit_after>// Package rehttp implements an HTTP transport that handles retries.
//
// An HTTP Client can be created with a *rehttp.Transport as Transport
// and it will apply the retry strategy to its requests, e.g.:
//
//     client := &http.Client{
//       Transport: rehttp.NewTransport(
//         nil,                            // will use http.DefaultTransport
//         rehttp.RetryTemporaryErr(3),    // max 3 retries
//         rehttp.ConstDelay(time.Second), // wait 1s between retries
//       ),
//     }
//
// The retry strategy is provided by the Transport.Retry field, which holds
// a function that returns whether or not the request should be retried,
// and if so, what delay to apply before retrying.
//
// For convenience, ToRetryFn combines two functions - a ShouldRetryFn
// that returns whether or not the request should be retried, and a DelayFn
// that returns the delay to apply - into a RetryFn that can be used as
// value for Transport.Retry. As seen in the example above, NewTransport
// also accepts the retry strategy as a pair of ShouldRetryFn and DelayFn.
//
// The package offers common delay strategies as ready-made functions that
// return a DelayFn:
//     - ConstDelay(delay time.Duration) DelayFn
//     - NoDelay() DelayFn
//     - ExponentialDelay(initialDelay time.Duration) DelayFn
//     - LinearDelay(initialDelay time.Duration) DelayFn
//
// It also provides common retry predicates that return a ShouldRetryFn:
//     - RetryTemporaryErr(maxRetries int) ShouldRetryFn
//     - RetryStatus500(maxRetries int) ShouldRetryFn
//     - RetryHTTPMethods(maxRetries int, methods ...string) ShouldRetryFn
//
// Those can be combined with RetryAny or RetryAll as needed.
//
// The Transport will buffer the request's body in order to be able to
// retry the request, as a request attempt will consume and close the
// existing body. Sometimes this is not desirable, so it can be prevented
// by setting PreventRetryWithBody to true on the Transport. Doing so
// will disable retries when a request has a non-nil body, the RetryFn
// won't get called in those cases.
//
package rehttp

import (
	"bytes"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"strings"
	"time"
)

// terribly named interface to detect errors that support Temporary.
type temporaryer interface {
	Temporary() bool
}

// CancelRoundTripper is a RoundTripper that supports CancelRequest.
// The *http.Transport type implements this interface.
type CancelRoundTripper interface {
	http.RoundTripper
	CancelRequest(*http.Request)
}

// RetryFn is the signature for functions that implement retry strategies.
// The attempt parameter is the attempt number corresponding to the response
// and error received, starting at 0.
type RetryFn func(req *http.Request, res *http.Response, attempt int, err error) (bool, time.Duration)

// DelayFn is the signature for functions that return the delay to apply
// before the next retry. The attempt parameter is the attempt number
// corresponding to the response and error received, starting at 0.
type DelayFn func(req *http.Request, res *http.Response, attempt int, err error) time.Duration

// ShouldRetryFn is the signature for functions that return whether a
// retry should be done for the request. The attempt parameter is the
// attempt number corresponding to the response and error received,
// starting at 0.
type ShouldRetryFn func(req *http.Request, res *http.Response, attempt int, err error) bool

// NewTransport creates a Transport with a retry strategy based on
// shouldRetry and delay to control the retry logic. It uses the provided
// CancelRoundTripper to execute the requests. If rt is nil,
// http.DefaultTransport is used, but the package panics if it isn't set
// to a CancelRoundTripper (which it is by default).
func NewTransport(rt CancelRoundTripper, shouldRetry ShouldRetryFn, delay DelayFn) *Transport {
	if rt == nil {
		rt = http.DefaultTransport.(CancelRoundTripper)
	}
	return &Transport{
		rt:    rt,
		Retry: ToRetryFn(shouldRetry, delay),
	}
}

// ToRetryFn combines shouldRetry and delay into a RetryFn that can be used
// as Transport.Retry value.
func ToRetryFn(shouldRetry ShouldRetryFn, delay DelayFn) RetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) (bool, time.Duration) {
		retry := shouldRetry(req, res, attempt, err)
		if !retry {
			return false, 0
		}
		return true, delay(req, res, attempt, err)
	}
}

// RetryAny returns a ShouldRetryFn that allows a retry as long as one of
// the retryFns returns true.
func RetryAny(retryFns ...ShouldRetryFn) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		for _, fn := range retryFns {
			if fn(req, res, attempt, err) {
				return true
			}
		}
		return false
	}
}

// RetryAll returns a ShouldRetryFn that allows a retry if all retryFns
// return true.
func RetryAll(retryFns ...ShouldRetryFn) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		for _, fn := range retryFns {
			if !fn(req, res, attempt, err) {
				return false
			}
		}
		return true
	}
}

// RetryTemporaryErr returns a ShouldRetryFn that retries up to maxRetries
// times for a temporary error. A temporary error is one that implements
// the Temporary() bool method. Most errors from the net package implement
// this.
func RetryTemporaryErr(maxRetries int) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		if attempt >= maxRetries {
			return false
		}
		if terr, ok := err.(temporaryer); ok {
			return terr.Temporary()
		}
		return false
	}
}

// RetryStatus500 returns a ShouldRetryFn that retries up to maxRetries times
// for a status code 5xx.
func RetryStatus500(maxRetries int) ShouldRetryFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		if attempt >= maxRetries {
			return false
		}
		return res != nil && res.StatusCode >= 500 && res.StatusCode < 600 // who knows
	}
}

// RetryHTTPMethods returns a ShouldRetryFn that retries up to maxRetries
// times if the request's HTTP method is one of the provided methods.
func RetryHTTPMethods(maxRetries int, methods ...string) ShouldRetryFn {
	for i, m := range methods {
		methods[i] = strings.ToUpper(m)
	}

	return func(req *http.Request, res *http.Response, attempt int, err error) bool {
		if attempt >= maxRetries {
			return false
		}
		curMeth := strings.ToUpper(req.Method)
		for _, m := range methods {
			if curMeth == m {
				return true
			}
		}
		return false
	}
}

// ConstDelay returns a DelayFn that always returns the same delay.
func ConstDelay(delay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return delay
	}
}

// NoDelay returns a DelayFn that always returns 0.
func NoDelay() DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return 0
	}
}

// ExponentialDelay returns a DelayFn that returns an exponential delay based
// on initialDelay at the power of attempt + 1 (so the first delay is
// initialDelay, then initialDelay ** 2, etc.).
func ExponentialDelay(initialDelay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return time.Duration(math.Pow(float64(initialDelay), float64(attempt+1)))
	}
}

// LinearDelay returns a DelayFn that returns a delay that grows linearly
// based on the number of attempts.
func LinearDelay(initialDelay time.Duration) DelayFn {
	return func(req *http.Request, res *http.Response, attempt int, err error) time.Duration {
		return time.Duration(attempt+1) * initialDelay
	}
}

// Transport wraps a CancelRoundTripper such as *http.Transport and adds
// retry logic.
type Transport struct {
	rt CancelRoundTripper

	// PreventRetryWithBody prevents retrying if the request has a body. Since
	// the body is consumed on a request attempt, in order to retry a request
	// with a body, the body has to be buffered in memory. Setting this
	// to true avoids this buffering, the retry logic is bypassed if a body
	// is present.
	PreventRetryWithBody bool

	// Retry is a function that determines if the request should be retried.
	// Unless a retry is prevented based on PreventRetryWithBody, all requests
	// go through that function, even those that are typically considered
	// successful.
	//
	// If it returns false, no retry is attempted, otherwise a retry is
	// attempted after the specified duration.
	Retry RetryFn
}

// Per Go's doc: "CancelRequest should only be called after RoundTrip
// has returned."
// So it should not have an impact on this Transport (it doesn't return before
// the retries are done).

// RoundTrip implements http.RoundTripper for the Transport type.
// It calls its underlying RoundTripper to execute the request, and
// adds retry logic as per its configuration.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	var attempt int
	preventRetry := req.Body != nil && t.PreventRetryWithBody

	// buffer the body if needed
	var br *bytes.Reader
	if req.Body != nil && !preventRetry {
		var buf bytes.Buffer
		if _, err := io.Copy(&buf, req.Body); err != nil {
			// cannot even try the first attempt, body has been consumed
			req.Body.Close()
			return nil, err
		}
		req.Body.Close()

		br = bytes.NewReader(buf.Bytes())
		req.Body = ioutil.NopCloser(br)
	}

	for {
		res, err := t.rt.RoundTrip(req)
		if preventRetry {
			return res, err
		}

		retry, delay := t.Retry(req, res, attempt, err)
		if !retry {
			return res, err
		}

		if br != nil {
			// Per Go's doc: "RoundTrip should not modify the request,
			// except for consuming and closing the Body", so the only thing
			// to reset on the request is the body, if any.
			if _, serr := br.Seek(0, 0); serr != nil {
				// failed to retry, return the results
				return res, err
			}
			req.Body = ioutil.NopCloser(br)
		}
		select {
		case <-time.After(delay):
			attempt++
		case <-req.Cancel:
			return res, err
		}
	}
}
<|endoftext|>


<commit_before>package epp

import (
	"fmt"

	"github.com/nbio/xx"
)

// Result represents an EPP <result> element.
type Result struct {
	Code    int    `xml:"code,attr"`
	Message string `xml:"msg"`
}

// IsError determines whether an EPP status code is an error.
// https://tools.ietf.org/html/rfc5730#section-3
func (r Result) IsError() bool {
	return r.Code >= 2000
}

// IsFatal determines whether an EPP status code is a fatal response,
// and the connection should be closed.
// https://tools.ietf.org/html/rfc5730#section-3
func (r Result) IsFatal() bool {
	return r.Code >= 2500
}

// Error implements the error interface.
func (r Result) Error() string {
	return fmt.Sprintf("EPP result code %d: %s", r.Code, r.Message)
}

func init() {
	path := "epp>response>result"
	scanResponse.MustHandleStartElement(path, func(c *xx.Context) error {
		res := c.Value.(*response_)
		res.Result.Code = c.AttrInt("", "code")
		return nil
	})
	scanResponse.MustHandleCharData(path+">msg", func(c *xx.Context) error {
		c.Value.(*response_).Result.Message = string(c.CharData)
		return nil
	})
}
<commit_msg>result: methods receive result pointer type<commit_after>package epp

import (
	"fmt"

	"github.com/nbio/xx"
)

// Result represents an EPP <result> element.
type Result struct {
	Code    int    `xml:"code,attr"`
	Message string `xml:"msg"`
}

// IsError determines whether an EPP status code is an error.
// https://tools.ietf.org/html/rfc5730#section-3
func (r *Result) IsError() bool {
	return r.Code >= 2000
}

// IsFatal determines whether an EPP status code is a fatal response,
// and the connection should be closed.
// https://tools.ietf.org/html/rfc5730#section-3
func (r *Result) IsFatal() bool {
	return r.Code >= 2500
}

// Error implements the error interface.
func (r *Result) Error() string {
	return fmt.Sprintf("EPP result code %d: %s", r.Code, r.Message)
}

func init() {
	path := "epp>response>result"
	scanResponse.MustHandleStartElement(path, func(c *xx.Context) error {
		res := c.Value.(*response_)
		res.Result.Code = c.AttrInt("", "code")
		return nil
	})
	scanResponse.MustHandleCharData(path+">msg", func(c *xx.Context) error {
		c.Value.(*response_).Result.Message = string(c.CharData)
		return nil
	})
}
<|endoftext|>


<commit_before>package redis

import "time"

// NewCmdResult returns a Cmd initialised with val and err for testing
func NewCmdResult(val interface{}, err error) *Cmd {
	var cmd Cmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewSliceResult returns a SliceCmd initialised with val and err for testing
func NewSliceResult(val []interface{}, err error) *SliceCmd {
	var cmd SliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStatusResult returns a StatusCmd initialised with val and err for testing
func NewStatusResult(val string, err error) *StatusCmd {
	var cmd StatusCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewIntResult returns an IntCmd initialised with val and err for testing
func NewIntResult(val int64, err error) *IntCmd {
	var cmd IntCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewDurationResult returns a DurationCmd initialised with val and err for testing
func NewDurationResult(val time.Duration, err error) *DurationCmd {
	var cmd DurationCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewBoolResult returns a BoolCmd initialised with val and err for testing
func NewBoolResult(val bool, err error) *BoolCmd {
	var cmd BoolCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringResult returns a StringCmd initialised with val and err for testing
func NewStringResult(val string, err error) *StringCmd {
	var cmd StringCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewFloatResult returns a FloatCmd initialised with val and err for testing
func NewFloatResult(val float64, err error) *FloatCmd {
	var cmd FloatCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
	var cmd StringSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
	var cmd BoolSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing
func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd {
	var cmd StringStringMapCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing
func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd {
	var cmd StringIntMapCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
	var cmd ZSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewZWithKeyCmdResult returns a NewZWithKeyCmd initialised with val and err for testing
func NewZWithKeyCmdResult(val *ZWithKey, err error) *ZWithKeyCmd {
	var cmd ZWithKeyCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewScanCmdResult returns a ScanCmd initialised with val and err for testing
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
	var cmd ScanCmd
	cmd.page = keys
	cmd.cursor = cursor
	cmd.setErr(err)
	return &cmd
}

// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
	var cmd ClusterSlotsCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
	var cmd GeoLocationCmd
	cmd.locations = val
	cmd.setErr(err)
	return &cmd
}

// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
	var cmd CommandsInfoCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewXMessageSliceCmdResult returns a XMessageSliceCmd initialised with val and err for testing
func NewXMessageSliceCmdResult(val []XMessage, err error) *XMessageSliceCmd {
	var cmd XMessageSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}
<commit_msg>add NewXStreamSliceCmdResult to initialise XStreamSliceCmd for testing<commit_after>package redis

import "time"

// NewCmdResult returns a Cmd initialised with val and err for testing
func NewCmdResult(val interface{}, err error) *Cmd {
	var cmd Cmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewSliceResult returns a SliceCmd initialised with val and err for testing
func NewSliceResult(val []interface{}, err error) *SliceCmd {
	var cmd SliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStatusResult returns a StatusCmd initialised with val and err for testing
func NewStatusResult(val string, err error) *StatusCmd {
	var cmd StatusCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewIntResult returns an IntCmd initialised with val and err for testing
func NewIntResult(val int64, err error) *IntCmd {
	var cmd IntCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewDurationResult returns a DurationCmd initialised with val and err for testing
func NewDurationResult(val time.Duration, err error) *DurationCmd {
	var cmd DurationCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewBoolResult returns a BoolCmd initialised with val and err for testing
func NewBoolResult(val bool, err error) *BoolCmd {
	var cmd BoolCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringResult returns a StringCmd initialised with val and err for testing
func NewStringResult(val string, err error) *StringCmd {
	var cmd StringCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewFloatResult returns a FloatCmd initialised with val and err for testing
func NewFloatResult(val float64, err error) *FloatCmd {
	var cmd FloatCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
	var cmd StringSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
	var cmd BoolSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing
func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd {
	var cmd StringStringMapCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing
func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd {
	var cmd StringIntMapCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
	var cmd ZSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewZWithKeyCmdResult returns a NewZWithKeyCmd initialised with val and err for testing
func NewZWithKeyCmdResult(val *ZWithKey, err error) *ZWithKeyCmd {
	var cmd ZWithKeyCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewScanCmdResult returns a ScanCmd initialised with val and err for testing
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
	var cmd ScanCmd
	cmd.page = keys
	cmd.cursor = cursor
	cmd.setErr(err)
	return &cmd
}

// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
	var cmd ClusterSlotsCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
	var cmd GeoLocationCmd
	cmd.locations = val
	cmd.setErr(err)
	return &cmd
}

// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
	var cmd CommandsInfoCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewXMessageSliceCmdResult returns a XMessageSliceCmd initialised with val and err for testing
func NewXMessageSliceCmdResult(val []XMessage, err error) *XMessageSliceCmd {
	var cmd XMessageSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}

// NewXStreamSliceCmdResult returns a XStreamSliceCmd initialised with val and err for testing
func NewXStreamSliceCmdResult(val []XStream, err error) *XStreamSliceCmd {
	var cmd XStreamSliceCmd
	cmd.val = val
	cmd.setErr(err)
	return &cmd
}
<|endoftext|>


<commit_before>package router

import (
	"github.com/gorilla/context"
	"net/http"
	"net/url"
	"strings"
)

type Handler func(http.ResponseWriter, *http.Request)

type Router struct {
	tree    *node
	handler Handler
}

func New(handler Handler) *Router {
	root := newNode()
	return &Router{tree: root, handler: handler}
}

func (r *Router) Add(method, path string, handlers ...Handler) {
	r.tree.insert(method, path, handlers...)
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	params := url.Values{}
	context.Set(req, "params", params)
	keys := strings.Split(req.URL.Path, "/")[1:]
	node, _ := r.tree.search(strings.Join(keys, "/"), params)
	handlers := node.handlers[req.Method]
	if handlers == nil {
		r.handler(w, req)
		return
	}
	for _, h := range handlers {
		h(w, req)
	}
	//	context.Clear(req)
}
<commit_msg>added comments to public methods<commit_after>package router

import (
	"github.com/gorilla/context"
	"net/http"
	"net/url"
	"strings"
)

type Handler func(http.ResponseWriter, *http.Request)

type Router struct {
	tree    *node
	handler Handler
}

// New - this method creates a new instance of a router
func New(handler Handler) *Router {
	root := newNode()
	return &Router{tree: root, handler: handler}
}

// Add - this method adds a path and it's handlers to the router
func (r *Router) Add(method, path string, handlers ...Handler) {
	r.tree.insert(method, path, handlers...)
}

// ServeHTTP - this method is called every time a new request comes in
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	params := url.Values{}
	context.Set(req, "params", params)
	keys := strings.Split(req.URL.Path, "/")[1:]
	node, _ := r.tree.search(strings.Join(keys, "/"), params)
	handlers := node.handlers[req.Method]
	if handlers == nil {
		r.handler(w, req)
		return
	}
	for _, h := range handlers {
		h(w, req)
	}
	//	context.Clear(req)
}
<|endoftext|>


<commit_before>package mars

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/robfig/pathtree"
)

type Route struct {
	Method         string   // e.g. GET
	Path           string   // e.g. /app/:id
	Action         string   // e.g. "Application.ShowApp", "404"
	ControllerName string   // e.g. "Application", ""
	MethodName     string   // e.g. "ShowApp", ""
	FixedParams    []string // e.g. "arg1","arg2","arg3" (CSV formatting)
	TreePath       string   // e.g. "/GET/app/:id"

	routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
	line       int    // e.g. 3
}

type RouteMatch struct {
	Action         string // e.g. 404
	ControllerName string // e.g. Application
	MethodName     string // e.g. ShowApp
	FixedParams    []string
	Params         map[string][]string // e.g. {id: 123}
}

type arg struct {
	name       string
	index      int
	constraint *regexp.Regexp
}

// Prepares the route to be used in matching.
func NewRoute(method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
	// Handle fixed arguments
	argsReader := strings.NewReader(fixedArgs)
	csv := csv.NewReader(argsReader)
	csv.TrimLeadingSpace = true
	fargs, err := csv.Read()
	if err != nil && err != io.EOF {
		ERROR.Printf("Invalid fixed parameters (%v): for string '%v'", err.Error(), fixedArgs)
	}

	r = &Route{
		Method:      strings.ToUpper(method),
		Path:        path,
		Action:      action,
		FixedParams: fargs,
		TreePath:    treePath(strings.ToUpper(method), path),
		routesPath:  routesPath,
		line:        line,
	}

	// URL pattern
	if !strings.HasPrefix(r.Path, "/") {
		ERROR.Print("Absolute URL required.")
		return
	}

	actionSplit := strings.Split(action, ".")
	if len(actionSplit) == 2 {
		r.ControllerName = actionSplit[0]
		r.MethodName = actionSplit[1]
	}

	return
}

func treePath(method, path string) string {
	if method == "*" {
		method = ":METHOD"
	}
	return "/" + method + path
}

type Router struct {
	Routes []*Route
	Tree   *pathtree.Node
	path   string // path to the routes file
}

var notFound = &RouteMatch{Action: "404"}

func (router *Router) Route(req *http.Request) *RouteMatch {
	// Override method if set in header
	if method := req.Header.Get("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
		req.Method = method
	}

	leaf, expansions := router.Tree.Find(treePath(req.Method, req.URL.Path))
	if leaf == nil {
		return nil
	}
	route := leaf.Value.(*Route)

	// Create a map of the route parameters.
	var params url.Values
	if len(expansions) > 0 {
		params = make(url.Values)
		for i, v := range expansions {
			params[leaf.Wildcards[i]] = []string{v}
		}
	}

	// Special handling for explicit 404's.
	if route.Action == "404" {
		return notFound
	}

	// If the action is variablized, replace into it with the captured args.
	controllerName, methodName := route.ControllerName, route.MethodName
	if controllerName[0] == ':' {
		controllerName = params[controllerName[1:]][0]
	}
	if methodName[0] == ':' {
		methodName = params[methodName[1:]][0]
	}

	return &RouteMatch{
		ControllerName: controllerName,
		MethodName:     methodName,
		Params:         params,
		FixedParams:    route.FixedParams,
	}
}

// Refresh re-reads the routes file and re-calculates the routing table.
// Returns an error if a specified action could not be found.
func (router *Router) Refresh() (err *Error) {
	router.Routes, err = parseRoutesFile(router.path, "", true)
	if err != nil {
		return
	}
	err = router.updateTree()
	return
}

func (router *Router) updateTree() *Error {
	router.Tree = pathtree.New()
	for _, route := range router.Routes {
		err := router.Tree.Add(route.TreePath, route)

		// Allow GETs to respond to HEAD requests.
		if err == nil && route.Method == "GET" {
			err = router.Tree.Add(treePath("HEAD", route.Path), route)
		}

		// Error adding a route to the pathtree.
		if err != nil {
			return routeError(err, route.routesPath, "", route.line)
		}
	}
	return nil
}

// parseRoutesFile reads the given routes file and returns the contained routes.
func parseRoutesFile(routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
	contentBytes, err := ioutil.ReadFile(routesPath)
	if err != nil {
		return nil, &Error{
			Title:       "Failed to load routes file",
			Description: err.Error(),
		}
	}
	return parseRoutes(routesPath, joinedPath, string(contentBytes), validate)
}

// parseRoutes reads the content of a routes file into the routing table.
func parseRoutes(routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
	var routes []*Route

	// For each line..
	for n, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}

		// A single route
		method, path, action, fixedArgs, found := parseRouteLine(line)
		if !found {
			continue
		}

		// this will avoid accidental double forward slashes in a route.
		// this also avoids pathtree freaking out and causing a runtime panic
		// because of the double slashes
		if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
			joinedPath = joinedPath[0 : len(joinedPath)-1]
		}
		path = strings.Join([]string{AppRoot, joinedPath, path}, "")

		route := NewRoute(method, path, action, fixedArgs, routesPath, n)
		routes = append(routes, route)

		if validate {
			if err := validateRoute(route); err != nil {
				return nil, routeError(err, routesPath, content, n)
			}
		}
	}

	return routes, nil
}

// validateRoute checks that every specified action exists.
func validateRoute(route *Route) error {
	// Skip 404s
	if route.Action == "404" {
		return nil
	}

	// We should be able to load the action.
	parts := strings.Split(route.Action, ".")
	if len(parts) != 2 {
		return fmt.Errorf("Expected two parts (Controller.Action), but got %d: %s",
			len(parts), route.Action)
	}

	// Skip variable routes.
	if parts[0][0] == ':' || parts[1][0] == ':' {
		return nil
	}

	var c Controller
	if err := c.SetAction(parts[0], parts[1]); err != nil {
		return err
	}

	return nil
}

// routeError adds context to a simple error message.
func routeError(err error, routesPath, content string, n int) *Error {
	if marsError, ok := err.(*Error); ok {
		return marsError
	}
	// Load the route file content if necessary
	if content == "" {
		contentBytes, err := ioutil.ReadFile(routesPath)
		if err != nil {
			ERROR.Printf("Failed to read route file %s: %s\n", routesPath, err)
		} else {
			content = string(contentBytes)
		}
	}
	return &Error{
		Title:       "Route validation error",
		Description: err.Error(),
		Path:        routesPath,
		Line:        n + 1,
		SourceLines: strings.Split(content, "\n"),
	}
}

// Groups:
// 1: method
// 4: path
// 5: action
// 6: fixedargs
var routePattern *regexp.Regexp = regexp.MustCompile(
	"(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|\\*)" +
		"[(]?([^)]*)(\\))?[ \t]+" +
		"(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
		`\(?([^)]*)\)?[ \t]*$`)

func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
	var matches []string = routePattern.FindStringSubmatch(line)
	if matches == nil {
		return
	}
	method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
	found = true
	return
}

func NewRouter(routesPath string) *Router {
	return &Router{
		Tree: pathtree.New(),
		path: routesPath,
	}
}

type ActionDefinition struct {
	Host, Method, Url, Action string
	Star                      bool
	Args                      map[string]string
}

func (a *ActionDefinition) String() string {
	return a.Url
}

func (router *Router) Reverse(action string, argValues map[string]string) *ActionDefinition {
	actionSplit := strings.Split(action, ".")
	if len(actionSplit) != 2 {
		ERROR.Print("mars/router: reverse router got invalid action ", action)
		return nil
	}
	controllerName, methodName := actionSplit[0], actionSplit[1]

	for _, route := range router.Routes {
		// Skip routes without either a ControllerName or MethodName
		if route.ControllerName == "" || route.MethodName == "" {
			continue
		}

		// Check that the action matches or is a wildcard.
		controllerWildcard := route.ControllerName[0] == ':'
		methodWildcard := route.MethodName[0] == ':'
		if (!controllerWildcard && route.ControllerName != controllerName) ||
			(!methodWildcard && route.MethodName != methodName) {
			continue
		}
		if controllerWildcard {
			argValues[route.ControllerName[1:]] = controllerName
		}
		if methodWildcard {
			argValues[route.MethodName[1:]] = methodName
		}

		// Build up the URL.
		var (
			queryValues  = make(url.Values)
			pathElements = strings.Split(route.Path, "/")
		)
		for i, el := range pathElements {
			if el == "" || (el[0] != ':' && el[0] != '*') {
				continue
			}

			val, ok := argValues[el[1:]]
			if !ok {
				val = "<nil>"
				ERROR.Print("mars/router: reverse route missing route arg ", el[1:])
			}
			if el[0] == '*' {
				pathElements[i] = (&url.URL{Path: val}).RequestURI()
			} else {
				pathElements[i] = url.PathEscape(val)
			}
			delete(argValues, el[1:])
			continue
		}

		// Add any args that were not inserted into the path into the query string.
		for k, v := range argValues {
			queryValues.Set(k, v)
		}

		// Calculate the final URL and Method
		url := strings.Join(pathElements, "/")
		if len(queryValues) > 0 {
			url += "?" + queryValues.Encode()
		}

		method := route.Method
		star := false
		if route.Method == "*" {
			method = "GET"
			star = true
		}

		return &ActionDefinition{
			Url:    url,
			Method: method,
			Star:   star,
			Action: action,
			Args:   argValues,
			Host:   "TODO",
		}
	}
	ERROR.Println("Failed to find reverse route:", action, argValues)
	return nil
}

func RouterFilter(c *Controller, fc []Filter) {
	// Figure out the Controller/Action
	var route *RouteMatch = MainRouter.Route(c.Request.Request)
	if route == nil {
		c.Result = c.NotFound("No matching route found: " + c.Request.RequestURI)
		return
	}

	// The route may want to explicitly return a 404.
	if route.Action == "404" {
		c.Result = c.NotFound("(intentionally)")
		return
	}

	// Set the action.
	if err := c.SetAction(route.ControllerName, route.MethodName); err != nil {
		c.Result = c.NotFound(err.Error())
		return
	}

	// Add the route and fixed params to the Request Params.
	c.Params.Route = route.Params

	// Add the fixed parameters mapped by name.
	// TODO: Pre-calculate this mapping.
	for i, value := range route.FixedParams {
		if c.Params.Fixed == nil {
			c.Params.Fixed = make(url.Values)
		}
		if i < len(c.MethodType.Args) {
			arg := c.MethodType.Args[i]
			c.Params.Fixed.Set(arg.Name, value)
		} else {
			WARN.Println("Too many parameters to", route.Action, "trying to add", value)
			break
		}
	}

	fc[0](c, fc[1:])
}

// Override allowed http methods via form or browser param
func HttpMethodOverride(c *Controller, fc []Filter) {
	// An array of HTTP verbs allowed.
	verbs := []string{"POST", "PUT", "PATCH", "DELETE"}

	method := strings.ToUpper(c.Request.Request.Method)

	if method == "POST" {
		param := strings.ToUpper(c.Request.Request.PostFormValue("_method"))

		if len(param) > 0 {
			override := false
			// Check if param is allowed
			for _, verb := range verbs {
				if verb == param {
					override = true
					break
				}
			}

			if override {
				c.Request.Request.Method = param
			} else {
				c.Response.Status = 405
				c.Result = c.RenderError(&Error{
					Title:       "Method not allowed",
					Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
				})
				return
			}

		}
	}

	fc[0](c, fc[1:]) // Execute the next filter stage.
}
<commit_msg>router: Fix path escaping for Go<1.8. #7<commit_after>package mars

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/robfig/pathtree"
)

type Route struct {
	Method         string   // e.g. GET
	Path           string   // e.g. /app/:id
	Action         string   // e.g. "Application.ShowApp", "404"
	ControllerName string   // e.g. "Application", ""
	MethodName     string   // e.g. "ShowApp", ""
	FixedParams    []string // e.g. "arg1","arg2","arg3" (CSV formatting)
	TreePath       string   // e.g. "/GET/app/:id"

	routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
	line       int    // e.g. 3
}

type RouteMatch struct {
	Action         string // e.g. 404
	ControllerName string // e.g. Application
	MethodName     string // e.g. ShowApp
	FixedParams    []string
	Params         map[string][]string // e.g. {id: 123}
}

type arg struct {
	name       string
	index      int
	constraint *regexp.Regexp
}

// Prepares the route to be used in matching.
func NewRoute(method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
	// Handle fixed arguments
	argsReader := strings.NewReader(fixedArgs)
	csv := csv.NewReader(argsReader)
	csv.TrimLeadingSpace = true
	fargs, err := csv.Read()
	if err != nil && err != io.EOF {
		ERROR.Printf("Invalid fixed parameters (%v): for string '%v'", err.Error(), fixedArgs)
	}

	r = &Route{
		Method:      strings.ToUpper(method),
		Path:        path,
		Action:      action,
		FixedParams: fargs,
		TreePath:    treePath(strings.ToUpper(method), path),
		routesPath:  routesPath,
		line:        line,
	}

	// URL pattern
	if !strings.HasPrefix(r.Path, "/") {
		ERROR.Print("Absolute URL required.")
		return
	}

	actionSplit := strings.Split(action, ".")
	if len(actionSplit) == 2 {
		r.ControllerName = actionSplit[0]
		r.MethodName = actionSplit[1]
	}

	return
}

func treePath(method, path string) string {
	if method == "*" {
		method = ":METHOD"
	}
	return "/" + method + path
}

type Router struct {
	Routes []*Route
	Tree   *pathtree.Node
	path   string // path to the routes file
}

var notFound = &RouteMatch{Action: "404"}

func (router *Router) Route(req *http.Request) *RouteMatch {
	// Override method if set in header
	if method := req.Header.Get("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
		req.Method = method
	}

	leaf, expansions := router.Tree.Find(treePath(req.Method, req.URL.Path))
	if leaf == nil {
		return nil
	}
	route := leaf.Value.(*Route)

	// Create a map of the route parameters.
	var params url.Values
	if len(expansions) > 0 {
		params = make(url.Values)
		for i, v := range expansions {
			params[leaf.Wildcards[i]] = []string{v}
		}
	}

	// Special handling for explicit 404's.
	if route.Action == "404" {
		return notFound
	}

	// If the action is variablized, replace into it with the captured args.
	controllerName, methodName := route.ControllerName, route.MethodName
	if controllerName[0] == ':' {
		controllerName = params[controllerName[1:]][0]
	}
	if methodName[0] == ':' {
		methodName = params[methodName[1:]][0]
	}

	return &RouteMatch{
		ControllerName: controllerName,
		MethodName:     methodName,
		Params:         params,
		FixedParams:    route.FixedParams,
	}
}

// Refresh re-reads the routes file and re-calculates the routing table.
// Returns an error if a specified action could not be found.
func (router *Router) Refresh() (err *Error) {
	router.Routes, err = parseRoutesFile(router.path, "", true)
	if err != nil {
		return
	}
	err = router.updateTree()
	return
}

func (router *Router) updateTree() *Error {
	router.Tree = pathtree.New()
	for _, route := range router.Routes {
		err := router.Tree.Add(route.TreePath, route)

		// Allow GETs to respond to HEAD requests.
		if err == nil && route.Method == "GET" {
			err = router.Tree.Add(treePath("HEAD", route.Path), route)
		}

		// Error adding a route to the pathtree.
		if err != nil {
			return routeError(err, route.routesPath, "", route.line)
		}
	}
	return nil
}

// parseRoutesFile reads the given routes file and returns the contained routes.
func parseRoutesFile(routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
	contentBytes, err := ioutil.ReadFile(routesPath)
	if err != nil {
		return nil, &Error{
			Title:       "Failed to load routes file",
			Description: err.Error(),
		}
	}
	return parseRoutes(routesPath, joinedPath, string(contentBytes), validate)
}

// parseRoutes reads the content of a routes file into the routing table.
func parseRoutes(routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
	var routes []*Route

	// For each line..
	for n, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 0 || line[0] == '#' {
			continue
		}

		// A single route
		method, path, action, fixedArgs, found := parseRouteLine(line)
		if !found {
			continue
		}

		// this will avoid accidental double forward slashes in a route.
		// this also avoids pathtree freaking out and causing a runtime panic
		// because of the double slashes
		if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
			joinedPath = joinedPath[0 : len(joinedPath)-1]
		}
		path = strings.Join([]string{AppRoot, joinedPath, path}, "")

		route := NewRoute(method, path, action, fixedArgs, routesPath, n)
		routes = append(routes, route)

		if validate {
			if err := validateRoute(route); err != nil {
				return nil, routeError(err, routesPath, content, n)
			}
		}
	}

	return routes, nil
}

// validateRoute checks that every specified action exists.
func validateRoute(route *Route) error {
	// Skip 404s
	if route.Action == "404" {
		return nil
	}

	// We should be able to load the action.
	parts := strings.Split(route.Action, ".")
	if len(parts) != 2 {
		return fmt.Errorf("Expected two parts (Controller.Action), but got %d: %s",
			len(parts), route.Action)
	}

	// Skip variable routes.
	if parts[0][0] == ':' || parts[1][0] == ':' {
		return nil
	}

	var c Controller
	if err := c.SetAction(parts[0], parts[1]); err != nil {
		return err
	}

	return nil
}

// routeError adds context to a simple error message.
func routeError(err error, routesPath, content string, n int) *Error {
	if marsError, ok := err.(*Error); ok {
		return marsError
	}
	// Load the route file content if necessary
	if content == "" {
		contentBytes, err := ioutil.ReadFile(routesPath)
		if err != nil {
			ERROR.Printf("Failed to read route file %s: %s\n", routesPath, err)
		} else {
			content = string(contentBytes)
		}
	}
	return &Error{
		Title:       "Route validation error",
		Description: err.Error(),
		Path:        routesPath,
		Line:        n + 1,
		SourceLines: strings.Split(content, "\n"),
	}
}

// Groups:
// 1: method
// 4: path
// 5: action
// 6: fixedargs
var routePattern *regexp.Regexp = regexp.MustCompile(
	"(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|\\*)" +
		"[(]?([^)]*)(\\))?[ \t]+" +
		"(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
		`\(?([^)]*)\)?[ \t]*$`)

func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
	var matches []string = routePattern.FindStringSubmatch(line)
	if matches == nil {
		return
	}
	method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
	found = true
	return
}

func NewRouter(routesPath string) *Router {
	return &Router{
		Tree: pathtree.New(),
		path: routesPath,
	}
}

type ActionDefinition struct {
	Host, Method, Url, Action string
	Star                      bool
	Args                      map[string]string
}

func (a *ActionDefinition) String() string {
	return a.Url
}

func shouldEscape(c byte) bool {
	if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
		return false
	}

	switch c {
	case '-', '_', '.', '~', '$', '&', '+', ':', '=', '@':
		return false
	}

	return true
}

func encodePathSegment(s string) string {
	// From Go 1.8+: return url.PathEscape(segment)
	spaceCount, hexCount := 0, 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if shouldEscape(c) {
			hexCount++
		}
	}

	if spaceCount == 0 && hexCount == 0 {
		return s
	}

	t := make([]byte, len(s)+2*hexCount)
	j := 0
	for i := 0; i < len(s); i++ {
		switch c := s[i]; {
		case shouldEscape(c):
			t[j] = '%'
			t[j+1] = "0123456789ABCDEF"[c>>4]
			t[j+2] = "0123456789ABCDEF"[c&15]
			j += 3
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

func (router *Router) Reverse(action string, argValues map[string]string) *ActionDefinition {
	actionSplit := strings.Split(action, ".")
	if len(actionSplit) != 2 {
		ERROR.Print("mars/router: reverse router got invalid action ", action)
		return nil
	}
	controllerName, methodName := actionSplit[0], actionSplit[1]

	for _, route := range router.Routes {
		// Skip routes without either a ControllerName or MethodName
		if route.ControllerName == "" || route.MethodName == "" {
			continue
		}

		// Check that the action matches or is a wildcard.
		controllerWildcard := route.ControllerName[0] == ':'
		methodWildcard := route.MethodName[0] == ':'
		if (!controllerWildcard && route.ControllerName != controllerName) ||
			(!methodWildcard && route.MethodName != methodName) {
			continue
		}
		if controllerWildcard {
			argValues[route.ControllerName[1:]] = controllerName
		}
		if methodWildcard {
			argValues[route.MethodName[1:]] = methodName
		}

		// Build up the URL.
		var (
			queryValues  = make(url.Values)
			pathElements = strings.Split(route.Path, "/")
		)
		for i, el := range pathElements {
			if el == "" || (el[0] != ':' && el[0] != '*') {
				continue
			}

			val, ok := argValues[el[1:]]
			if !ok {
				val = "<nil>"
				ERROR.Print("mars/router: reverse route missing route arg ", el[1:])
			}
			if el[0] == '*' {
				pathElements[i] = (&url.URL{Path: val}).RequestURI()
			} else {
				pathElements[i] = encodePathSegment(val)
			}
			delete(argValues, el[1:])
			continue
		}

		// Add any args that were not inserted into the path into the query string.
		for k, v := range argValues {
			queryValues.Set(k, v)
		}

		// Calculate the final URL and Method
		url := strings.Join(pathElements, "/")
		if len(queryValues) > 0 {
			url += "?" + queryValues.Encode()
		}

		method := route.Method
		star := false
		if route.Method == "*" {
			method = "GET"
			star = true
		}

		return &ActionDefinition{
			Url:    url,
			Method: method,
			Star:   star,
			Action: action,
			Args:   argValues,
			Host:   "TODO",
		}
	}
	ERROR.Println("Failed to find reverse route:", action, argValues)
	return nil
}

func RouterFilter(c *Controller, fc []Filter) {
	// Figure out the Controller/Action
	var route *RouteMatch = MainRouter.Route(c.Request.Request)
	if route == nil {
		c.Result = c.NotFound("No matching route found: " + c.Request.RequestURI)
		return
	}

	// The route may want to explicitly return a 404.
	if route.Action == "404" {
		c.Result = c.NotFound("(intentionally)")
		return
	}

	// Set the action.
	if err := c.SetAction(route.ControllerName, route.MethodName); err != nil {
		c.Result = c.NotFound(err.Error())
		return
	}

	// Add the route and fixed params to the Request Params.
	c.Params.Route = route.Params

	// Add the fixed parameters mapped by name.
	// TODO: Pre-calculate this mapping.
	for i, value := range route.FixedParams {
		if c.Params.Fixed == nil {
			c.Params.Fixed = make(url.Values)
		}
		if i < len(c.MethodType.Args) {
			arg := c.MethodType.Args[i]
			c.Params.Fixed.Set(arg.Name, value)
		} else {
			WARN.Println("Too many parameters to", route.Action, "trying to add", value)
			break
		}
	}

	fc[0](c, fc[1:])
}

// Override allowed http methods via form or browser param
func HttpMethodOverride(c *Controller, fc []Filter) {
	// An array of HTTP verbs allowed.
	verbs := []string{"POST", "PUT", "PATCH", "DELETE"}

	method := strings.ToUpper(c.Request.Request.Method)

	if method == "POST" {
		param := strings.ToUpper(c.Request.Request.PostFormValue("_method"))

		if len(param) > 0 {
			override := false
			// Check if param is allowed
			for _, verb := range verbs {
				if verb == param {
					override = true
					break
				}
			}

			if override {
				c.Request.Request.Method = param
			} else {
				c.Response.Status = 405
				c.Result = c.RenderError(&Error{
					Title:       "Method not allowed",
					Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
				})
				return
			}

		}
	}

	fc[0](c, fc[1:]) // Execute the next filter stage.
}
<|endoftext|>


<commit_before>// Copyright 2015-2016 Zack Scholl. All rights reserved.
// Use of this source code is governed by a AGPL
// license that can be found in the LICENSE file.

// routes.go contains the functions that handle the web page views

package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/gin-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

// slash returns the dashboard, if logged in, else it redirects to login page.
func slash(c *gin.Context) {
	var group string
	loginGroup := sessions.Default(c)
	groupCookie := loginGroup.Get("group")
	if groupCookie == nil {
		c.Redirect(302, "/login")
	} else {
		group = groupCookie.(string)
		c.Redirect(302, "/dashboard/"+group)
	}
}

// slashLogin handles a login with url parameter and returns dashboard if successful, else login.
func slashLogin(c *gin.Context) {
	loginGroup := sessions.Default(c)
	group := c.DefaultQuery("group", "noneasdf")
	if group == "noneasdf" {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{})
	} else {
		loginGroup.Set("group", group)
		loginGroup.Save()
		c.Redirect(302, "/dashboard/"+group)
	}
}

// slashLogin handles a POST login and returns dashboard if successful, else login.
func slashLoginPOST(c *gin.Context) {
	loginGroup := sessions.Default(c)
	group := strings.ToLower(c.PostForm("group"))
	if _, err := os.Stat(path.Join("data", group+".db")); err == nil {
		loginGroup.Set("group", group)
		loginGroup.Save()
		c.Redirect(302, "/dashboard/"+group)
	} else {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "Incorrect login.",
		})
	}
}

// slashLogout handles a logout
func slashLogout(c *gin.Context) {
	var group string
	loginGroup := sessions.Default(c)
	groupCookie := loginGroup.Get("group")
	if groupCookie == nil {
		c.Redirect(302, "/login")
	} else {
		group = groupCookie.(string)
		fmt.Println(group)
		loginGroup.Clear()
		loginGroup.Save()
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"Message": "You are now logged out.",
		})
	}
}

// slashDashboard displays the dashboard
func slashDashboard(c *gin.Context) {
	filterUser := c.DefaultQuery("user", "")
	filterUsers := c.DefaultQuery("users", "")
	filterUserMap := make(map[string]bool)
	if len(filterUser) > 0 {
		u := strings.Replace(strings.TrimSpace(filterUser), ":", "", -1)
		filterUserMap[u] = true
	}
	if len(filterUsers) > 0 {
		for _, user := range strings.Split(filterUsers, ",") {
			u := strings.Replace(strings.TrimSpace(user), ":", "", -1)
			filterUserMap[u] = true
		}
	}
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}
	ps, _ := openParameters(group)
	users := getUsers(group)
	people := make(map[string]UserPositionJSON)
	for _, user := range users {
		_, ok := filterUserMap[user]
		if len(filterUserMap) == 0 || ok {
			people[user] = getCurrentPositionOfUser(group, user)
		}
	}
	type DashboardData struct {
		Networks         []string
		Locations        map[string][]string
		LocationAccuracy map[string]int
		LocationCount    map[string]int
		Mixin            map[string]float64
		VarabilityCutoff map[string]float64
		Users            map[string]UserPositionJSON
	}
	var dash DashboardData
	dash.Networks = []string{}
	dash.Locations = make(map[string][]string)
	dash.LocationAccuracy = make(map[string]int)
	dash.LocationCount = make(map[string]int)
	dash.Mixin = make(map[string]float64)
	dash.VarabilityCutoff = make(map[string]float64)
	for n := range ps.NetworkLocs {
		dash.Mixin[n] = ps.Priors[n].Special["MixIn"]
		dash.VarabilityCutoff[n] = ps.Priors[n].Special["VarabilityCutoff"]
		dash.Networks = append(dash.Networks, n)
		dash.Locations[n] = []string{}
		for loc := range ps.NetworkLocs[n] {
			dash.Locations[n] = append(dash.Locations[n], loc)
			dash.LocationAccuracy[loc] = ps.Results[n].Accuracy[loc]
			dash.LocationCount[loc] = ps.Results[n].TotalLocations[loc]
		}
	}
	c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
		"Message": RuntimeArgs.Message,
		"Group":   group,
		"Dash":    dash,
		"Users":   people,
	})
}

// slash Location returns location (to be deprecated)
func slashLocation(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.JSON(http.StatusOK, gin.H{"success": "false", "message": "First download the app or CLI program to insert some fingerprints."})
		return
	}
	user := c.Param("user")
	userJSON := getCurrentPositionOfUser(group, user)
	c.JSON(http.StatusOK, userJSON)
}

// slashExplore returns a chart of the data
func slashExplore(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}
	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	// TODO: check if network and location exists in the ps, if not return 404
	datas := []template.JS{}
	names := []template.JS{}
	indexNames := []template.JS{}
	// Sort locations
	macs := []string{}
	for m := range ps.Priors[network].P[location] {
		if float64(ps.MacVariability[m]) > ps.Priors[network].Special["VarabilityCutoff"] {
			macs = append(macs, m)
		}
	}
	sort.Strings(macs)
	it := 0
	for _, m := range macs {
		n := ps.Priors[network].P[location][m]
		names = append(names, template.JS(string(m)))
		jsonByte, _ := json.Marshal(n)
		datas = append(datas, template.JS(string(jsonByte)))
		indexNames = append(indexNames, template.JS(strconv.Itoa(it)))
		it++
	}
	rsiRange, _ := json.Marshal(RssiRange)
	c.HTML(http.StatusOK, "plot.tmpl", gin.H{
		"RssiRange":  template.JS(string(rsiRange)),
		"Datas":      datas,
		"Names":      names,
		"IndexNames": indexNames,
	})
}

// slashExplore returns a chart of the data (canvas.js)
func slashExplore2(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}

	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	lookUpLocation := true
	if strings.Count(location, ":") > 4 {
		lookUpLocation = false // location is actuall mac
		Debug.Println("GOT LOCATION")
	}

	type macDatum struct {
		Name   string    `json:"name"`
		Points []float32 `json:"data"`
	}

	type macDatas struct {
		Macs []macDatum `json:"macs"`
	}

	var data macDatas
	data.Macs = []macDatum{}

	if lookUpLocation {
		// Sort locations
		macs := []string{}
		for m := range ps.Priors[network].P[location] {
			if float64(ps.MacVariability[m]) > ps.Priors[network].Special["VarabilityCutoff"] {
				macs = append(macs, m)
			}
		}
		sort.Strings(macs)

		for _, m := range macs {
			n := ps.Priors[network].P[location][m]
			data.Macs = append(data.Macs, macDatum{Name: m, Points: n})
		}
	} else {
		m := location
		for loc := range ps.Priors[network].P {
			Debug.Println(loc, m)
			n := ps.Priors[network].P[loc][m]
			data.Macs = append(data.Macs, macDatum{Name: strings.Replace(loc, " ", "%20", -1), Points: n})
		}
	}

	c.HTML(http.StatusOK, "plot2.tmpl", gin.H{
		"Data":    data,
		"Rssi":    RssiRange,
		"Title":   group + "/" + network + "/" + location,
		"Group":   strings.Replace(group, " ", "%20", -1),
		"Network": strings.Replace(network, " ", "%20", -1),
		"Legend":  !lookUpLocation,
	})
}

// slashPie returns a Pie chart
func slashPie(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}

	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	vals := []int{}
	names := []string{}
	fmt.Println(ps.Results[network].Guess[location])
	for guessloc, val := range ps.Results[network].Guess[location] {
		names = append(names, guessloc)
		vals = append(vals, val)
	}
	namesJSON, _ := json.Marshal(names)
	valsJSON, _ := json.Marshal(vals)
	c.HTML(http.StatusOK, "pie.tmpl", gin.H{
		"Names": template.JS(namesJSON),
		"Vals":  template.JS(valsJSON),
	})
}
<commit_msg>Fixed bug where explore doesn't work with non-mac names<commit_after>// Copyright 2015-2016 Zack Scholl. All rights reserved.
// Use of this source code is governed by a AGPL
// license that can be found in the LICENSE file.

// routes.go contains the functions that handle the web page views

package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"

	"github.com/gin-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

// slash returns the dashboard, if logged in, else it redirects to login page.
func slash(c *gin.Context) {
	var group string
	loginGroup := sessions.Default(c)
	groupCookie := loginGroup.Get("group")
	if groupCookie == nil {
		c.Redirect(302, "/login")
	} else {
		group = groupCookie.(string)
		c.Redirect(302, "/dashboard/"+group)
	}
}

// slashLogin handles a login with url parameter and returns dashboard if successful, else login.
func slashLogin(c *gin.Context) {
	loginGroup := sessions.Default(c)
	group := c.DefaultQuery("group", "noneasdf")
	if group == "noneasdf" {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{})
	} else {
		loginGroup.Set("group", group)
		loginGroup.Save()
		c.Redirect(302, "/dashboard/"+group)
	}
}

// slashLogin handles a POST login and returns dashboard if successful, else login.
func slashLoginPOST(c *gin.Context) {
	loginGroup := sessions.Default(c)
	group := strings.ToLower(c.PostForm("group"))
	if _, err := os.Stat(path.Join("data", group+".db")); err == nil {
		loginGroup.Set("group", group)
		loginGroup.Save()
		c.Redirect(302, "/dashboard/"+group)
	} else {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "Incorrect login.",
		})
	}
}

// slashLogout handles a logout
func slashLogout(c *gin.Context) {
	var group string
	loginGroup := sessions.Default(c)
	groupCookie := loginGroup.Get("group")
	if groupCookie == nil {
		c.Redirect(302, "/login")
	} else {
		group = groupCookie.(string)
		fmt.Println(group)
		loginGroup.Clear()
		loginGroup.Save()
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"Message": "You are now logged out.",
		})
	}
}

// slashDashboard displays the dashboard
func slashDashboard(c *gin.Context) {
	filterUser := c.DefaultQuery("user", "")
	filterUsers := c.DefaultQuery("users", "")
	filterUserMap := make(map[string]bool)
	if len(filterUser) > 0 {
		u := strings.Replace(strings.TrimSpace(filterUser), ":", "", -1)
		filterUserMap[u] = true
	}
	if len(filterUsers) > 0 {
		for _, user := range strings.Split(filterUsers, ",") {
			u := strings.Replace(strings.TrimSpace(user), ":", "", -1)
			filterUserMap[u] = true
		}
	}
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}
	ps, _ := openParameters(group)
	users := getUsers(group)
	people := make(map[string]UserPositionJSON)
	for _, user := range users {
		_, ok := filterUserMap[user]
		if len(filterUserMap) == 0 || ok {
			people[user] = getCurrentPositionOfUser(group, user)
		}
	}
	type DashboardData struct {
		Networks         []string
		Locations        map[string][]string
		LocationAccuracy map[string]int
		LocationCount    map[string]int
		Mixin            map[string]float64
		VarabilityCutoff map[string]float64
		Users            map[string]UserPositionJSON
	}
	var dash DashboardData
	dash.Networks = []string{}
	dash.Locations = make(map[string][]string)
	dash.LocationAccuracy = make(map[string]int)
	dash.LocationCount = make(map[string]int)
	dash.Mixin = make(map[string]float64)
	dash.VarabilityCutoff = make(map[string]float64)
	for n := range ps.NetworkLocs {
		dash.Mixin[n] = ps.Priors[n].Special["MixIn"]
		dash.VarabilityCutoff[n] = ps.Priors[n].Special["VarabilityCutoff"]
		dash.Networks = append(dash.Networks, n)
		dash.Locations[n] = []string{}
		for loc := range ps.NetworkLocs[n] {
			dash.Locations[n] = append(dash.Locations[n], loc)
			dash.LocationAccuracy[loc] = ps.Results[n].Accuracy[loc]
			dash.LocationCount[loc] = ps.Results[n].TotalLocations[loc]
		}
	}
	c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
		"Message": RuntimeArgs.Message,
		"Group":   group,
		"Dash":    dash,
		"Users":   people,
	})
}

// slash Location returns location (to be deprecated)
func slashLocation(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.JSON(http.StatusOK, gin.H{"success": "false", "message": "First download the app or CLI program to insert some fingerprints."})
		return
	}
	user := c.Param("user")
	userJSON := getCurrentPositionOfUser(group, user)
	c.JSON(http.StatusOK, userJSON)
}

// slashExplore returns a chart of the data
func slashExplore(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}
	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	// TODO: check if network and location exists in the ps, if not return 404
	datas := []template.JS{}
	names := []template.JS{}
	indexNames := []template.JS{}
	// Sort locations
	macs := []string{}
	for m := range ps.Priors[network].P[location] {
		if float64(ps.MacVariability[m]) > ps.Priors[network].Special["VarabilityCutoff"] {
			macs = append(macs, m)
		}
	}
	sort.Strings(macs)
	it := 0
	for _, m := range macs {
		n := ps.Priors[network].P[location][m]
		names = append(names, template.JS(string(m)))
		jsonByte, _ := json.Marshal(n)
		datas = append(datas, template.JS(string(jsonByte)))
		indexNames = append(indexNames, template.JS(strconv.Itoa(it)))
		it++
	}
	rsiRange, _ := json.Marshal(RssiRange)
	c.HTML(http.StatusOK, "plot.tmpl", gin.H{
		"RssiRange":  template.JS(string(rsiRange)),
		"Datas":      datas,
		"Names":      names,
		"IndexNames": indexNames,
	})
}

// slashExplore returns a chart of the data (canvas.js)
func slashExplore2(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}

	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	fmt.Println(ps.UniqueLocs)
	lookUpLocation := false

	for _, loc := range ps.UniqueLocs {
		if location == loc {
			lookUpLocation = true
		}
	}

	type macDatum struct {
		Name   string    `json:"name"`
		Points []float32 `json:"data"`
	}

	type macDatas struct {
		Macs []macDatum `json:"macs"`
	}

	var data macDatas
	data.Macs = []macDatum{}

	if lookUpLocation {
		// Sort locations
		macs := []string{}
		for m := range ps.Priors[network].P[location] {
			if float64(ps.MacVariability[m]) > ps.Priors[network].Special["VarabilityCutoff"] {
				macs = append(macs, m)
			}
		}
		sort.Strings(macs)

		for _, m := range macs {
			n := ps.Priors[network].P[location][m]
			data.Macs = append(data.Macs, macDatum{Name: m, Points: n})
		}
	} else {
		m := location
		for loc := range ps.Priors[network].P {
			Debug.Println(loc, m)
			n := ps.Priors[network].P[loc][m]
			data.Macs = append(data.Macs, macDatum{Name: strings.Replace(loc, " ", "%20", -1), Points: n})
		}
	}

	c.HTML(http.StatusOK, "plot2.tmpl", gin.H{
		"Data":    data,
		"Rssi":    RssiRange,
		"Title":   group + "/" + network + "/" + location,
		"Group":   strings.Replace(group, " ", "%20", -1),
		"Network": strings.Replace(network, " ", "%20", -1),
		"Legend":  !lookUpLocation,
	})
}

// slashPie returns a Pie chart
func slashPie(c *gin.Context) {
	group := c.Param("group")
	if _, err := os.Stat(path.Join(RuntimeArgs.SourcePath, group+".db")); os.IsNotExist(err) {
		c.HTML(http.StatusOK, "login.tmpl", gin.H{
			"ErrorMessage": "First download the app or CLI program to insert some fingerprints.",
		})
		return
	}

	network := c.Param("network")
	location := c.Param("location")
	ps, _ := openParameters(group)
	vals := []int{}
	names := []string{}
	fmt.Println(ps.Results[network].Guess[location])
	for guessloc, val := range ps.Results[network].Guess[location] {
		names = append(names, guessloc)
		vals = append(vals, val)
	}
	namesJSON, _ := json.Marshal(names)
	valsJSON, _ := json.Marshal(vals)
	c.HTML(http.StatusOK, "pie.tmpl", gin.H{
		"Names": template.JS(namesJSON),
		"Vals":  template.JS(valsJSON),
	})
}
<|endoftext|>


<commit_before>package autotee

import (
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/juju/errors"
	"github.com/kr/pty"
)

type Screen struct {
	Name string
	File *os.File
}

type ScreenService interface {
	Screen() (*Screen, error)
	Done() error
	Stop()
}

type BaseScreenService struct {
	name     string
	cmd      *Cmd
	pty, tty *os.File

	screen    *Screen
	hasScreen bool
}

type ExclusiveScreenService struct {
	BaseScreenService
}

type SharedScreenService struct {
	BaseScreenService
}

func NewExclusiveScreenService(name string) ScreenService {
	return &ExclusiveScreenService{BaseScreenService{name: name}}
}

func NewSharedScreenService(name string) ScreenService {
	return &SharedScreenService{BaseScreenService{name: name}}
}

func (s *BaseScreenService) spawn() error {
	var err error

	// Create PTY/TTY pair
	s.pty, s.tty, err = pty.Open()
	if err != nil {
		return errors.Annotate(err, "Failed to create PTY")
	}

	// Attach screen to PTY
	s.cmd = Command("screen", "-DmUS", s.name, s.tty.Name())
	s.cmd.SetStdin(s.pty)
	err = s.cmd.Start()
	if err != nil {
		return errors.Annotate(err, "Failed to start screen")
	}

	s.hasScreen = true
	s.screen = &Screen{s.name, s.pty}
	return nil
}

func (s *BaseScreenService) getScreen() *Screen {
	return &Screen{s.name, s.tty}
}

func (s *SharedScreenService) Stop() {
	if s.hasScreen {
		s.hasScreen = false
		s.pty.Close()
		s.tty.Close()

		err := s.cmd.End()
		if !IsExit(err) {
			log.WithError(err).Warn("Failed to stop screen")
		}
	}
}

func (s *ExclusiveScreenService) Stop() {
	if s.hasScreen {
		err := s.Done()
		if !IsExit(err) {
			log.WithError(err).Warn("Failed to stop screen")
		}
	}
}

func (s *SharedScreenService) Screen() (*Screen, error) {
	if !s.hasScreen {
		if err := s.spawn(); err != nil {
			return nil, err
		}
	}
	return s.screen, nil
}

func (s *ExclusiveScreenService) Screen() (*Screen, error) {
	if s.hasScreen {
		log.Warn("Bug: Done() not called")
		s.Done()
	}
	s.spawn()
	return s.screen, nil
}

func (s *SharedScreenService) Done() error {
	// do nothing
	return nil
}

func (s *ExclusiveScreenService) Done() error {
	s.hasScreen = false
	s.pty.Close()
	s.tty.Close()
	return s.cmd.End()
}
<commit_msg>Limit screen name length<commit_after>package autotee

import (
	"os"
	"os/user"

	log "github.com/Sirupsen/logrus"
	"github.com/juju/errors"
	"github.com/kr/pty"
)

type Screen struct {
	Name string
	File *os.File
}

type ScreenService interface {
	Screen() (*Screen, error)
	Done() error
	Stop()
}

type BaseScreenService struct {
	name     string
	cmd      *Cmd
	pty, tty *os.File

	screen    *Screen
	hasScreen bool
}

type ExclusiveScreenService struct {
	BaseScreenService
}

type SharedScreenService struct {
	BaseScreenService
}

func NewExclusiveScreenService(name string) ScreenService {
	return &ExclusiveScreenService{BaseScreenService{name: safeScreenName(name)}}
}

func NewSharedScreenService(name string) ScreenService {
	return &SharedScreenService{BaseScreenService{name: safeScreenName(name)}}
}

func (s *BaseScreenService) spawn() error {
	var err error

	// Create PTY/TTY pair
	s.pty, s.tty, err = pty.Open()
	if err != nil {
		return errors.Annotate(err, "Failed to create PTY")
	}

	// Attach screen to PTY
	s.cmd = Command("screen", "-DmUS", s.name, s.tty.Name())
	s.cmd.SetStdin(s.pty)
	err = s.cmd.Start()
	if err != nil {
		return errors.Annotate(err, "Failed to start screen")
	}

	s.hasScreen = true
	s.screen = &Screen{s.name, s.pty}
	return nil
}

func (s *BaseScreenService) getScreen() *Screen {
	return &Screen{s.name, s.tty}
}

func (s *SharedScreenService) Stop() {
	if s.hasScreen {
		s.hasScreen = false
		s.pty.Close()
		s.tty.Close()

		err := s.cmd.End()
		if !IsExit(err) {
			log.WithError(err).Warn("Failed to stop screen")
		}
	}
}

func (s *ExclusiveScreenService) Stop() {
	if s.hasScreen {
		err := s.Done()
		if !IsExit(err) {
			log.WithError(err).Warn("Failed to stop screen")
		}
	}
}

func (s *SharedScreenService) Screen() (*Screen, error) {
	if !s.hasScreen {
		if err := s.spawn(); err != nil {
			return nil, err
		}
	}
	return s.screen, nil
}

func (s *ExclusiveScreenService) Screen() (*Screen, error) {
	if s.hasScreen {
		log.Warn("Bug: Done() not called")
		s.Done()
	}
	s.spawn()
	return s.screen, nil
}

func (s *SharedScreenService) Done() error {
	// do nothing
	return nil
}

func (s *ExclusiveScreenService) Done() error {
	s.hasScreen = false
	s.pty.Close()
	s.tty.Close()
	return s.cmd.End()
}

// safeScreenName shortens a string so it can be used as a gnu screen instance name ("-S").

// So how long can a screen name be?
//
// Screen creates sockets like /var/run/screen/S-$user/$pid.$name
// According to unix(7), socket paths can not be longer than 108 characters.
// This includes 1 character for null termination.
// The directory part is 16 bytes long. (By default, anyway, it's configurable...)
// Usernames can have up to 32 bytes.
// But since we really need the space, we try to look up the actual name.
// PIDs in decimal notation have up to 5 bytes.
// Screen also adds a '.' character.
func safeScreenName(screenName string) string {

	userNameLength := 32
	if currentUser, err := user.Current(); err == nil {
		userNameLength = len(currentUser.Name)
	}

	maxSafeishScreenNameLength := 108 - 1 - userNameLength - 5 - 1
	if len(screenName) > maxSafeishScreenNameLength {
		screenName = screenName[:maxSafeishScreenNameLength]
	}

	return screenName
}
<|endoftext|>


<commit_before>// In seeded races, the silhouettes of other races are drawn onto the screen
// This is accomplished via UDP datagrams that are sent to the client, and then to the server

// The "shadow server" declared in this file is simply a UDP listener
// It expects two different kinds of UDP datagrams:
// 1) beacons, for player initialization and for keeping the connection alive
// 2) shadow data, for transmitting the actual shadow positions to the other players

// The shadow server simply echos incoming non-beacon UDP datagrams back to all of the other players
// in the race without any other processing (besides verifying that the server is coming from the
// right IP address)

package server

import (
	"fmt"
	"net"
	"sync"
	"time"
	"unsafe"
)

const (
	maxBufferSize = 1024
	beaconSize    = len("HELLO")
	port          = 9113
	purgeInterval = 1 * time.Second
)

var (
	shadowRaces = ShadowRaces{
		mutex: sync.Mutex{},
		races: make(map[uint32]map[uint32]*PlayerUDPConn),
	}
)

func shadowInit() {
	address := fmt.Sprintf(":%d", port)
	packetConn, err := net.ListenPacket("udp4", address)
	if err != nil {
		logger.Fatal("Failed to start the UDP server:", err)
	}
	logger.Info("Listening for UDP connections on port:", port)

	go UDPServerLoop(packetConn)
	go purgeOldSessionsLoop()
}

func UDPServerLoop(packetConn net.PacketConn) {
	buffer := make([]byte, maxBufferSize)

	for {
		n, addr, err := packetConn.ReadFrom(buffer)
		if err != nil {
			logger.Warning("Failed to read UDP datagram from \""+addr.String()+"\":", err)
			continue
		}

		mh := MessageHeader{}
		if err := mh.Unmarshall(buffer); err != nil {
			logger.Warning("Failed to unmarshall a UDP datagram from \""+addr.String()+"\":", err)
			continue
		}

		payloadSize := n - int(unsafe.Sizeof(MessageHeader{}))
		if payloadSize < beaconSize {
			// No message should ever be smaller than a beacon message
			continue
		} else if payloadSize == beaconSize {
			handleBeaconMessage(mh, addr)
		} else {
			handleOtherMessage(mh, addr, packetConn, buffer)
		}
	}
}

func handleBeaconMessage(mh MessageHeader, addr net.Addr) {
	// Since we have lazy player initialization,
	// updating the TTL will also instantiate the entry in the map for the respective player
	shadowRaces.updatePlayerTTL(mh, addr)
	logger.Debugf("Got beacon from user %d at address: %s", mh.UserID, addr.String())
}

func handleOtherMessage(mh MessageHeader, addr net.Addr, pc net.PacketConn, buffer []byte) {
	if !verifySender(mh, addr) {
		return
	}

	otherPlayerConnections := shadowRaces.getOtherPlayerConnections(mh)
	logger.Debug("Number of other connections:", len(otherPlayerConnections))
	for _, conn := range otherPlayerConnections {
		if n, err := pc.WriteTo(buffer, conn.addr); err != nil {
			logger.Errorf("Failed to send a UDP message to \"%v\": %w", conn.addr.String(), err)
		} else {
			logger.Debugf("Sent shadow message to %s with bytes: %d", conn.addr.String(), n)
		}
	}
}

func verifySender(mh MessageHeader, addr net.Addr) bool {
	conn := shadowRaces.getConnection(mh)

	if conn == nil {
		return false
	}

	return conn.addr.String() == addr.String()
}

func purgeOldSessionsLoop() {
	tick := time.Tick(purgeInterval) // nolint: staticcheck

	for {
		select {
		case <-tick:
			shadowRaces.purgeOldSessions()
		default:
			time.Sleep(purgeInterval)
		}
	}
}
<commit_msg>better server logging<commit_after>// In seeded races, the silhouettes of other races are drawn onto the screen
// This is accomplished via UDP datagrams that are sent to the client, and then to the server

// The "shadow server" declared in this file is simply a UDP listener
// It expects two different kinds of UDP datagrams:
// 1) beacons, for player initialization and for keeping the connection alive
// 2) shadow data, for transmitting the actual shadow positions to the other players

// The shadow server simply echos incoming non-beacon UDP datagrams back to all of the other players
// in the race without any other processing (besides verifying that the server is coming from the
// right IP address)

package server

import (
	"fmt"
	"net"
	"sync"
	"time"
	"unsafe"
)

const (
	maxBufferSize = 1024
	beaconSize    = len("HELLO")
	port          = 9113
	purgeInterval = 1 * time.Second
)

var (
	shadowRaces = ShadowRaces{
		mutex: sync.Mutex{},
		races: make(map[uint32]map[uint32]*PlayerUDPConn),
	}
)

func shadowInit() {
	address := fmt.Sprintf(":%d", port)
	packetConn, err := net.ListenPacket("udp4", address)
	if err != nil {
		logger.Fatal("Failed to start the UDP server:", err)
	}
	logger.Info("Listening for UDP connections on port:", port)

	go UDPServerLoop(packetConn)
	go purgeOldSessionsLoop()
}

func UDPServerLoop(packetConn net.PacketConn) {
	buffer := make([]byte, maxBufferSize)

	for {
		n, addr, err := packetConn.ReadFrom(buffer)
		if err != nil {
			logger.Warning("Failed to read UDP datagram from \""+addr.String()+"\":", err)
			continue
		}

		mh := MessageHeader{}
		if err := mh.Unmarshall(buffer); err != nil {
			logger.Warning("Failed to unmarshall a UDP datagram from \""+addr.String()+"\":", err)
			continue
		}

		payloadSize := n - int(unsafe.Sizeof(MessageHeader{}))
		if payloadSize < beaconSize {
			// No message should ever be smaller than a beacon message
			continue
		} else if payloadSize == beaconSize {
			handleBeaconMessage(mh, addr)
		} else {
			handleOtherMessage(mh, addr, packetConn, buffer)
		}
	}
}

func handleBeaconMessage(mh MessageHeader, addr net.Addr) {
	logger.Debugf("Got beacon from user %d at address: %s", mh.UserID, addr.String())

	// Since we have lazy player initialization,
	// updating the TTL will also instantiate the entry in the map for the respective player
	shadowRaces.updatePlayerTTL(mh, addr)
}

func handleOtherMessage(mh MessageHeader, addr net.Addr, pc net.PacketConn, buffer []byte) {
	logger.Debugf("Got shadow message from user %d at address: %s", mh.UserID, addr.String())

	if !verifySender(mh, addr) {
		return
	}

	otherPlayerConnections := shadowRaces.getOtherPlayerConnections(mh)
	logger.Debug("Number of other connections:", len(otherPlayerConnections))
	for _, conn := range otherPlayerConnections {
		if n, err := pc.WriteTo(buffer, conn.addr); err != nil {
			logger.Errorf("Failed to send a UDP message to \"%v\": %w", conn.addr.String(), err)
		} else {
			logger.Debugf("Sent shadow message to address: %s (%d bytes)", conn.addr.String(), n)
		}
	}
}

func verifySender(mh MessageHeader, addr net.Addr) bool {
	conn := shadowRaces.getConnection(mh)

	if conn == nil {
		return false
	}

	return conn.addr.String() == addr.String()
}

func purgeOldSessionsLoop() {
	tick := time.Tick(purgeInterval) // nolint: staticcheck

	for {
		select {
		case <-tick:
			shadowRaces.purgeOldSessions()
		default:
			time.Sleep(purgeInterval)
		}
	}
}
<|endoftext|>


<commit_before>package stack_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"testing"

	"gopkg.in/stack.v0"
)

type testType struct{}

func (tt testType) testMethod() (c stack.Call, pc uintptr, file string, line int, ok bool) {
	c = stack.Caller(0)
	pc, file, line, ok = runtime.Caller(0)
	line--
	return
}

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

	c := stack.Caller(0)
	pc, file, line, ok := runtime.Caller(0)
	line--
	if !ok {
		t.Fatal("runtime.Caller(0) failed")
	}

	gopathSrc := filepath.Join(os.Getenv("GOPATH"), "src")
	relFile, err := filepath.Rel(gopathSrc, file)
	if err != nil {
		t.Fatalf("failed to determine path relative to GOPATH: %v", err)
	}
	relFile = filepath.ToSlash(relFile)

	c2, pc2, file2, line2, ok2 := testType{}.testMethod()
	if !ok2 {
		t.Fatal("runtime.Caller(0) failed")
	}
	relFile2, err := filepath.Rel(gopathSrc, file)
	if err != nil {
		t.Fatalf("failed to determine path relative to GOPATH: %v", err)
	}
	relFile2 = filepath.ToSlash(relFile2)

	data := []struct {
		c    stack.Call
		desc string
		fmt  string
		out  string
	}{
		{stack.Call{}, "error", "%s", "%!s(NOFUNC)"},

		{c, "func", "%s", path.Base(file)},
		{c, "func", "%+s", relFile},
		{c, "func", "%#s", file},
		{c, "func", "%d", fmt.Sprint(line)},
		{c, "func", "%n", "TestCallFormat"},
		{c, "func", "%+n", runtime.FuncForPC(pc - 1).Name()},
		{c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)},
		{c, "func", "%+v", fmt.Sprint(relFile, ":", line)},
		{c, "func", "%#v", fmt.Sprint(file, ":", line)},
		{c, "func", "%v|%[1]n()", fmt.Sprint(path.Base(file), ":", line, "|", "TestCallFormat()")},

		{c2, "meth", "%s", path.Base(file2)},
		{c2, "meth", "%+s", relFile2},
		{c2, "meth", "%#s", file2},
		{c2, "meth", "%d", fmt.Sprint(line2)},
		{c2, "meth", "%n", "testType.testMethod"},
		{c2, "meth", "%+n", runtime.FuncForPC(pc2).Name()},
		{c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)},
		{c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)},
		{c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)},
		{c2, "meth", "%v|%[1]n()", fmt.Sprint(path.Base(file2), ":", line2, "|", "testType.testMethod()")},
	}

	for _, d := range data {
		got := fmt.Sprintf(d.fmt, d.c)
		if got != d.out {
			t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out)
		}
	}
}

func TestTrimAbove(t *testing.T) {
	trace := trimAbove()
	if got, want := len(trace), 2; got != want {
		t.Errorf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
	}
	if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func trimAbove() stack.CallStack {
	call := stack.Caller(1)
	trace := stack.Trace()
	return trace.TrimAbove(call)
}

func TestTrimBelow(t *testing.T) {
	trace := trimBelow()
	if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func trimBelow() stack.CallStack {
	call := stack.Caller(1)
	trace := stack.Trace()
	return trace.TrimBelow(call)
}

func TestTrimRuntime(t *testing.T) {
	trace := stack.Trace().TrimRuntime()
	if got, want := len(trace), 1; got != want {
		t.Errorf("got len(trace) == %v, want %v, trace: %#v", got, want, trace)
	}
}

func BenchmarkCallVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprint(ioutil.Discard, c)
	}
}

func BenchmarkCallerAndVFmt(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fmt.Fprint(ioutil.Discard, stack.Caller(0))
	}
}

func BenchmarkCallPlusVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+v", c)
	}
}

func BenchmarkCallSharpVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%#v", c)
	}
}

func BenchmarkCallSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%s", c)
	}
}

func BenchmarkCallPlusSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+s", c)
	}
}

func BenchmarkCallSharpSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%#s", c)
	}
}

func BenchmarkCallDFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%d", c)
	}
}

func BenchmarkCallNFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%n", c)
	}
}

func BenchmarkCallPlusNFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+n", c)
	}
}

func BenchmarkCaller(b *testing.B) {
	for i := 0; i < b.N; i++ {
		stack.Caller(0)
	}
}

func BenchmarkTrace(b *testing.B) {
	for i := 0; i < b.N; i++ {
		stack.Trace()
	}
}

func BenchmarkFuncForPC(b *testing.B) {
	pc, _, _, _ := runtime.Caller(0)
	pc--
	var fn *runtime.Func
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fn = runtime.FuncForPC(pc)
	}
	b.StopTimer()
	b.Log(fn.FileLine(pc))
}

func deepStack(depth int, b *testing.B) stack.CallStack {
	if depth > 0 {
		return deepStack(depth-1, b)
	}
	b.StartTimer()
	s := stack.Trace()
	b.StopTimer()
	return s
}

func BenchmarkTrace10(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(10, b)
	}
}

func BenchmarkTrace50(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(50, b)
	}
}

func BenchmarkTrace100(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(100, b)
	}
}
<commit_msg>Replace fragile code with a constant.<commit_after>package stack_test

import (
	"fmt"
	"io/ioutil"
	"path"
	"path/filepath"
	"runtime"
	"testing"

	"gopkg.in/stack.v0"
)

const importPath = "github.com/go-stack/stack"

type testType struct{}

func (tt testType) testMethod() (c stack.Call, pc uintptr, file string, line int, ok bool) {
	c = stack.Caller(0)
	pc, file, line, ok = runtime.Caller(0)
	line--
	return
}

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

	c := stack.Caller(0)
	pc, file, line, ok := runtime.Caller(0)
	line--
	if !ok {
		t.Fatal("runtime.Caller(0) failed")
	}
	relFile := path.Join(importPath, filepath.Base(file))

	c2, pc2, file2, line2, ok2 := testType{}.testMethod()
	if !ok2 {
		t.Fatal("runtime.Caller(0) failed")
	}
	relFile2 := path.Join(importPath, filepath.Base(file2))

	data := []struct {
		c    stack.Call
		desc string
		fmt  string
		out  string
	}{
		{stack.Call{}, "error", "%s", "%!s(NOFUNC)"},

		{c, "func", "%s", path.Base(file)},
		{c, "func", "%+s", relFile},
		{c, "func", "%#s", file},
		{c, "func", "%d", fmt.Sprint(line)},
		{c, "func", "%n", "TestCallFormat"},
		{c, "func", "%+n", runtime.FuncForPC(pc - 1).Name()},
		{c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)},
		{c, "func", "%+v", fmt.Sprint(relFile, ":", line)},
		{c, "func", "%#v", fmt.Sprint(file, ":", line)},
		{c, "func", "%v|%[1]n()", fmt.Sprint(path.Base(file), ":", line, "|", "TestCallFormat()")},

		{c2, "meth", "%s", path.Base(file2)},
		{c2, "meth", "%+s", relFile2},
		{c2, "meth", "%#s", file2},
		{c2, "meth", "%d", fmt.Sprint(line2)},
		{c2, "meth", "%n", "testType.testMethod"},
		{c2, "meth", "%+n", runtime.FuncForPC(pc2).Name()},
		{c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)},
		{c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)},
		{c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)},
		{c2, "meth", "%v|%[1]n()", fmt.Sprint(path.Base(file2), ":", line2, "|", "testType.testMethod()")},
	}

	for _, d := range data {
		got := fmt.Sprintf(d.fmt, d.c)
		if got != d.out {
			t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out)
		}
	}
}

func TestTrimAbove(t *testing.T) {
	trace := trimAbove()
	if got, want := len(trace), 2; got != want {
		t.Errorf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
	}
	if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func trimAbove() stack.CallStack {
	call := stack.Caller(1)
	trace := stack.Trace()
	return trace.TrimAbove(call)
}

func TestTrimBelow(t *testing.T) {
	trace := trimBelow()
	if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func trimBelow() stack.CallStack {
	call := stack.Caller(1)
	trace := stack.Trace()
	return trace.TrimBelow(call)
}

func TestTrimRuntime(t *testing.T) {
	trace := stack.Trace().TrimRuntime()
	if got, want := len(trace), 1; got != want {
		t.Errorf("got len(trace) == %v, want %v, trace: %#v", got, want, trace)
	}
}

func BenchmarkCallVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprint(ioutil.Discard, c)
	}
}

func BenchmarkCallerAndVFmt(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fmt.Fprint(ioutil.Discard, stack.Caller(0))
	}
}

func BenchmarkCallPlusVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+v", c)
	}
}

func BenchmarkCallSharpVFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%#v", c)
	}
}

func BenchmarkCallSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%s", c)
	}
}

func BenchmarkCallPlusSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+s", c)
	}
}

func BenchmarkCallSharpSFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%#s", c)
	}
}

func BenchmarkCallDFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%d", c)
	}
}

func BenchmarkCallNFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%n", c)
	}
}

func BenchmarkCallPlusNFmt(b *testing.B) {
	c := stack.Caller(0)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(ioutil.Discard, "%+n", c)
	}
}

func BenchmarkCaller(b *testing.B) {
	for i := 0; i < b.N; i++ {
		stack.Caller(0)
	}
}

func BenchmarkTrace(b *testing.B) {
	for i := 0; i < b.N; i++ {
		stack.Trace()
	}
}

func BenchmarkFuncForPC(b *testing.B) {
	pc, _, _, _ := runtime.Caller(0)
	pc--
	var fn *runtime.Func
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fn = runtime.FuncForPC(pc)
	}
	b.StopTimer()
	b.Log(fn.FileLine(pc))
}

func deepStack(depth int, b *testing.B) stack.CallStack {
	if depth > 0 {
		return deepStack(depth-1, b)
	}
	b.StartTimer()
	s := stack.Trace()
	b.StopTimer()
	return s
}

func BenchmarkTrace10(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(10, b)
	}
}

func BenchmarkTrace50(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(50, b)
	}
}

func BenchmarkTrace100(b *testing.B) {
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		deepStack(100, b)
	}
}
<|endoftext|>