<commit_before>// The retryablehttp package provides a familiar HTTP client interface with
// automatic retries and exponential backoff. It is a thin wrapper over the
// standard net/http client library and exposes nearly the same public API.
// This makes retryablehttp very easy to drop into existing programs.
//
// retryablehttp performs automatic retries under certain conditions. Mainly, if
// an error is returned by the client (connection errors etc), or if a 500-range
// response is received, then a retry is invoked. Otherwise, the response is
// returned and left to the caller to interpret.
//
// The main difference from net/http is that requests which take a request body
// (POST/PUT et. al) require an io.ReadSeeker to be provided. This enables the
// request body to be "rewound" if the initial request fails so that the full
// request can be attempted again.
package retryablehttp

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/hashicorp/go-cleanhttp"
)

var (
	// Default retry configuration
	defaultRetryWaitMin = 1 * time.Second
	defaultRetryWaitMax = 30 * time.Second
	defaultRetryMax     = 4

	// defaultClient is used for performing requests without explicitly making
	// a new client. It is purposely private to avoid modifications.
	defaultClient = NewClient()

	// We need to consume response bodies to maintain http connections, but
	// limit the size we consume to respReadLimit.
	respReadLimit = int64(4096)
)

// LenReader is an interface implemented by many in-memory io.Reader's. Used
// for automatically sending the right Content-Length header when possible.
type LenReader interface {
	Len() int
}

// Request wraps the metadata needed to create HTTP requests.
type Request struct {
	// body is a seekable reader over the request body payload. This is
	// used to rewind the request data in between retries.
	body io.ReadSeeker

	// Embed an HTTP request directly. This makes a *Request act exactly
	// like an *http.Request so that all meta methods are supported.
	*http.Request
}

// NewRequest creates a new wrapped request.
func NewRequest(method, url string, body io.ReadSeeker) (*Request, error) {
	// Wrap the body in a noop ReadCloser if non-nil. This prevents the
	// reader from being closed by the HTTP client.
	var rcBody io.ReadCloser
	if body != nil {
		rcBody = ioutil.NopCloser(body)
	}

	// Make the request with the noop-closer for the body.
	httpReq, err := http.NewRequest(method, url, rcBody)
	if err != nil {
		return nil, err
	}

	// Check if we can set the Content-Length automatically.
	if lr, ok := body.(LenReader); ok {
		httpReq.ContentLength = int64(lr.Len())
	}

	return &Request{body, httpReq}, nil
}

// RequestLogHook allows a function to run before each retry. The HTTP
// request which will be made, and the retry number (0 for the initial
// request) are available to users. The internal logger is exposed to
// consumers.
type RequestLogHook func(*log.Logger, *http.Request, int)

// ResponseLogHook is like RequestLogHook, but allows running a function
// on each HTTP response. This function will be invoked at the end of
// every HTTP request executed, regardless of whether a subsequent retry
// needs to be performed or not. If the response body is read or closed
// from this method, this will affect the response returned from Do().
type ResponseLogHook func(*log.Logger, *http.Response)

// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
// the http.Client. If CheckRetry returns false, the Client stops retrying
// and returns the response to the caller. If CheckRetry returns an error,
// that error value is returned in lieu of the error from the request. The
// Client will close any response body when retrying, but if the retry is
// aborted it is up to the CheckResponse callback to properly close any
// response body before returning.
type CheckRetry func(resp *http.Response, err error) (bool, error)

// Backoff specifies a policy for how long to wait between retries.
// It is called after a failing request to determine the amount of time
// that should pass before trying again.
type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration

// ErrorHandler is called if retries are expired, containing the last status
// from the http library. If not specified, default behavior for the library is
// to close the body and return an error indicating how many tries were
// attempted. If overriding this, be sure to close the body if needed.
type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Response, error)

// Client is used to make HTTP requests. It adds additional functionality
// like automatic retries to tolerate minor outages.
type Client struct {
	HTTPClient *http.Client // Internal HTTP client.
	Logger     *log.Logger  // Customer logger instance.

	RetryWaitMin time.Duration // Minimum time to wait
	RetryWaitMax time.Duration // Maximum time to wait
	RetryMax     int           // Maximum number of retries

	// RequestLogHook allows a user-supplied function to be called
	// before each retry.
	RequestLogHook RequestLogHook

	// ResponseLogHook allows a user-supplied function to be called
	// with the response from each HTTP request executed.
	ResponseLogHook ResponseLogHook

	// CheckRetry specifies the policy for handling retries, and is called
	// after each request. The default policy is DefaultRetryPolicy.
	CheckRetry CheckRetry

	// Backoff specifies the policy for how long to wait between retries
	Backoff Backoff

	// ErrorHandler specifies the custom error handler to use, if any
	ErrorHandler ErrorHandler
}

// NewClient creates a new Client with default settings.
func NewClient() *Client {
	return &Client{
		HTTPClient:   cleanhttp.DefaultClient(),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
		RetryWaitMin: defaultRetryWaitMin,
		RetryWaitMax: defaultRetryWaitMax,
		RetryMax:     defaultRetryMax,
		CheckRetry:   DefaultRetryPolicy,
		Backoff:      DefaultBackoff,
	}
}

// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which
// will retry on connection errors and server errors.
func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) {
	if err != nil {
		return true, err
	}
	// Check the response code. We retry on 500-range responses to allow
	// the server time to recover, as 500's are typically not permanent
	// errors and may relate to outages on the server side. This will catch
	// invalid response codes as well, like 0 and 999.
	if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) {
		return true, nil
	}

	return false, nil
}

// DefaultBackoff provides a default callback for Client.Backoff which
// will perform exponential backoff based on the attempt number and limited
// by the provided minimum and maximum durations.
func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	mult := math.Pow(2, float64(attemptNum)) * float64(min)
	sleep := time.Duration(mult)
	if float64(sleep) != mult || sleep > max {
		sleep = max
	}
	return sleep
}

// LinearJitterBackoff provides a callback for Client.Backoff which will
// perform linear backoff based on the attempt number and with jitter to
// prevent a thundering herd.
//
// min and max here are *not* absolute values. The number to be multipled by
// the attempt number will be chosen at random from between them, thus they are
// bounding the jitter.
//
// For instance:
// * To get strictly linear backoff of one second increasing each retry, set
// both to one second (1s, 2s, 3s, 4s, ...)
// * To get a small amount of jitter centered around one second increasing each
// retry, set to around one second, such as a min of 800ms and max of 1200ms
// (892ms, 2102ms, 2945ms, 4312ms, ...)
// * To get extreme jitter, set to a very wide spread, such as a min of 100ms
// and a max of 20s (15382ms, 292ms, 51321ms, 35234ms, ...)
func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	// attemptNum always starts at zero but we want to start at 1 for multiplication
	attemptNum++

	if max <= min {
		// Unclear what to do here, or they are the same, so return min *
		// attemptNum
		return min * time.Duration(attemptNum)
	}

	// Seed rand; doing this every time is fine
	rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))

	// Pick a random number that lies somewhere between the min and max and
	// multiply by the attemptNum. attemptNum starts at zero so we always
	// increment here. We first get a random percentage, then apply that to the
	// difference between min and max, and add to min.
	jitter := rand.Float64() * float64(max-min)
	jitterMin := int64(jitter) + int64(min)
	return time.Duration(jitterMin * int64(attemptNum))
}

// PassthroughErrorHandler is an ErrorHandler that directly passes through the
// values from the net/http library for the final request. The body is not
// closed.
func PassthroughErrorHandler(resp *http.Response, err error, _ int) (*http.Response, error) {
	return resp, err
}

// Do wraps calling an HTTP method with retries.
func (c *Client) Do(req *Request) (*http.Response, error) {
	if c.Logger != nil {
		c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
	}

	var resp *http.Response
	var err error

	for i := 0; ; i++ {
		var code int // HTTP response code

		// Always rewind the request body when non-nil.
		if req.body != nil {
			if _, err := req.body.Seek(0, 0); err != nil {
				return nil, fmt.Errorf("failed to seek body: %v", err)
			}
		}

		if c.RequestLogHook != nil {
			c.RequestLogHook(c.Logger, req.Request, i)
		}

		// Attempt the request
		resp, err = c.HTTPClient.Do(req.Request)
		if resp != nil {
			code = resp.StatusCode
		}

		// Check if we should continue with retries.
		checkOK, checkErr := c.CheckRetry(resp, err)

		if err != nil {
			if c.Logger != nil {
				c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
			}
		} else {
			// Call this here to maintain the behavior of logging all requests,
			// even if CheckRetry signals to stop.
			if c.ResponseLogHook != nil {
				// Call the response logger function if provided.
				c.ResponseLogHook(c.Logger, resp)
			}
		}

		// Now decide if we should continue.
		if !checkOK {
			if checkErr != nil {
				err = checkErr
			}
			return resp, err
		}

		// We do this before drainBody beause there's no need for the I/O if
		// we're breaking out
		remain := c.RetryMax - i
		if remain == 0 {
			break
		}

		// We're going to retry, consume any response to reuse the connection.
		if err == nil && resp != nil {
			c.drainBody(resp.Body)
		}

		wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
		desc := fmt.Sprintf("%s %s", req.Method, req.URL)
		if code > 0 {
			desc = fmt.Sprintf("%s (status: %d)", desc, code)
		}
		if c.Logger != nil {
			c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
		}
		time.Sleep(wait)
	}

	if c.ErrorHandler != nil {
		return c.ErrorHandler(resp, err, c.RetryMax+1)
	}

	// By default, we close the response body and return an error without
	// returning the response
	if resp != nil {
		resp.Body.Close()
	}
	return nil, fmt.Errorf("%s %s giving up after %d attempts",
		req.Method, req.URL, c.RetryMax+1)
}

// Try to read the response body so we can reuse this connection.
func (c *Client) drainBody(body io.ReadCloser) {
	defer body.Close()
	_, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
	if err != nil {
		if c.Logger != nil {
			c.Logger.Printf("[ERR] error reading response body: %v", err)
		}
	}
}

// Get is a shortcut for doing a GET request without making a new client.
func Get(url string) (*http.Response, error) {
	return defaultClient.Get(url)
}

// Get is a convenience helper for doing simple GET requests.
func (c *Client) Get(url string) (*http.Response, error) {
	req, err := NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

// Head is a shortcut for doing a HEAD request without making a new client.
func Head(url string) (*http.Response, error) {
	return defaultClient.Head(url)
}

// Head is a convenience method for doing simple HEAD requests.
func (c *Client) Head(url string) (*http.Response, error) {
	req, err := NewRequest("HEAD", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

// Post is a shortcut for doing a POST request without making a new client.
func Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
	return defaultClient.Post(url, bodyType, body)
}

// Post is a convenience method for doing simple POST requests.
func (c *Client) Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
	req, err := NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", bodyType)
	return c.Do(req)
}

// PostForm is a shortcut to perform a POST with form data without creating
// a new client.
func PostForm(url string, data url.Values) (*http.Response, error) {
	return defaultClient.PostForm(url, data)
}

// PostForm is a convenience method for doing simple POST operations using
// pre-filled url.Values form data.
func (c *Client) PostForm(url string, data url.Values) (*http.Response, error) {
	return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
<commit_msg>Fix Bad breaking condition (#28)<commit_after>// The retryablehttp package provides a familiar HTTP client interface with
// automatic retries and exponential backoff. It is a thin wrapper over the
// standard net/http client library and exposes nearly the same public API.
// This makes retryablehttp very easy to drop into existing programs.
//
// retryablehttp performs automatic retries under certain conditions. Mainly, if
// an error is returned by the client (connection errors etc), or if a 500-range
// response is received, then a retry is invoked. Otherwise, the response is
// returned and left to the caller to interpret.
//
// The main difference from net/http is that requests which take a request body
// (POST/PUT et. al) require an io.ReadSeeker to be provided. This enables the
// request body to be "rewound" if the initial request fails so that the full
// request can be attempted again.
package retryablehttp

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/hashicorp/go-cleanhttp"
)

var (
	// Default retry configuration
	defaultRetryWaitMin = 1 * time.Second
	defaultRetryWaitMax = 30 * time.Second
	defaultRetryMax     = 4

	// defaultClient is used for performing requests without explicitly making
	// a new client. It is purposely private to avoid modifications.
	defaultClient = NewClient()

	// We need to consume response bodies to maintain http connections, but
	// limit the size we consume to respReadLimit.
	respReadLimit = int64(4096)
)

// LenReader is an interface implemented by many in-memory io.Reader's. Used
// for automatically sending the right Content-Length header when possible.
type LenReader interface {
	Len() int
}

// Request wraps the metadata needed to create HTTP requests.
type Request struct {
	// body is a seekable reader over the request body payload. This is
	// used to rewind the request data in between retries.
	body io.ReadSeeker

	// Embed an HTTP request directly. This makes a *Request act exactly
	// like an *http.Request so that all meta methods are supported.
	*http.Request
}

// NewRequest creates a new wrapped request.
func NewRequest(method, url string, body io.ReadSeeker) (*Request, error) {
	// Wrap the body in a noop ReadCloser if non-nil. This prevents the
	// reader from being closed by the HTTP client.
	var rcBody io.ReadCloser
	if body != nil {
		rcBody = ioutil.NopCloser(body)
	}

	// Make the request with the noop-closer for the body.
	httpReq, err := http.NewRequest(method, url, rcBody)
	if err != nil {
		return nil, err
	}

	// Check if we can set the Content-Length automatically.
	if lr, ok := body.(LenReader); ok {
		httpReq.ContentLength = int64(lr.Len())
	}

	return &Request{body, httpReq}, nil
}

// RequestLogHook allows a function to run before each retry. The HTTP
// request which will be made, and the retry number (0 for the initial
// request) are available to users. The internal logger is exposed to
// consumers.
type RequestLogHook func(*log.Logger, *http.Request, int)

// ResponseLogHook is like RequestLogHook, but allows running a function
// on each HTTP response. This function will be invoked at the end of
// every HTTP request executed, regardless of whether a subsequent retry
// needs to be performed or not. If the response body is read or closed
// from this method, this will affect the response returned from Do().
type ResponseLogHook func(*log.Logger, *http.Response)

// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
// the http.Client. If CheckRetry returns false, the Client stops retrying
// and returns the response to the caller. If CheckRetry returns an error,
// that error value is returned in lieu of the error from the request. The
// Client will close any response body when retrying, but if the retry is
// aborted it is up to the CheckResponse callback to properly close any
// response body before returning.
type CheckRetry func(resp *http.Response, err error) (bool, error)

// Backoff specifies a policy for how long to wait between retries.
// It is called after a failing request to determine the amount of time
// that should pass before trying again.
type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration

// ErrorHandler is called if retries are expired, containing the last status
// from the http library. If not specified, default behavior for the library is
// to close the body and return an error indicating how many tries were
// attempted. If overriding this, be sure to close the body if needed.
type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Response, error)

// Client is used to make HTTP requests. It adds additional functionality
// like automatic retries to tolerate minor outages.
type Client struct {
	HTTPClient *http.Client // Internal HTTP client.
	Logger     *log.Logger  // Customer logger instance.

	RetryWaitMin time.Duration // Minimum time to wait
	RetryWaitMax time.Duration // Maximum time to wait
	RetryMax     int           // Maximum number of retries

	// RequestLogHook allows a user-supplied function to be called
	// before each retry.
	RequestLogHook RequestLogHook

	// ResponseLogHook allows a user-supplied function to be called
	// with the response from each HTTP request executed.
	ResponseLogHook ResponseLogHook

	// CheckRetry specifies the policy for handling retries, and is called
	// after each request. The default policy is DefaultRetryPolicy.
	CheckRetry CheckRetry

	// Backoff specifies the policy for how long to wait between retries
	Backoff Backoff

	// ErrorHandler specifies the custom error handler to use, if any
	ErrorHandler ErrorHandler
}

// NewClient creates a new Client with default settings.
func NewClient() *Client {
	return &Client{
		HTTPClient:   cleanhttp.DefaultClient(),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
		RetryWaitMin: defaultRetryWaitMin,
		RetryWaitMax: defaultRetryWaitMax,
		RetryMax:     defaultRetryMax,
		CheckRetry:   DefaultRetryPolicy,
		Backoff:      DefaultBackoff,
	}
}

// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which
// will retry on connection errors and server errors.
func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) {
	if err != nil {
		return true, err
	}
	// Check the response code. We retry on 500-range responses to allow
	// the server time to recover, as 500's are typically not permanent
	// errors and may relate to outages on the server side. This will catch
	// invalid response codes as well, like 0 and 999.
	if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) {
		return true, nil
	}

	return false, nil
}

// DefaultBackoff provides a default callback for Client.Backoff which
// will perform exponential backoff based on the attempt number and limited
// by the provided minimum and maximum durations.
func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	mult := math.Pow(2, float64(attemptNum)) * float64(min)
	sleep := time.Duration(mult)
	if float64(sleep) != mult || sleep > max {
		sleep = max
	}
	return sleep
}

// LinearJitterBackoff provides a callback for Client.Backoff which will
// perform linear backoff based on the attempt number and with jitter to
// prevent a thundering herd.
//
// min and max here are *not* absolute values. The number to be multipled by
// the attempt number will be chosen at random from between them, thus they are
// bounding the jitter.
//
// For instance:
// * To get strictly linear backoff of one second increasing each retry, set
// both to one second (1s, 2s, 3s, 4s, ...)
// * To get a small amount of jitter centered around one second increasing each
// retry, set to around one second, such as a min of 800ms and max of 1200ms
// (892ms, 2102ms, 2945ms, 4312ms, ...)
// * To get extreme jitter, set to a very wide spread, such as a min of 100ms
// and a max of 20s (15382ms, 292ms, 51321ms, 35234ms, ...)
func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
	// attemptNum always starts at zero but we want to start at 1 for multiplication
	attemptNum++

	if max <= min {
		// Unclear what to do here, or they are the same, so return min *
		// attemptNum
		return min * time.Duration(attemptNum)
	}

	// Seed rand; doing this every time is fine
	rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))

	// Pick a random number that lies somewhere between the min and max and
	// multiply by the attemptNum. attemptNum starts at zero so we always
	// increment here. We first get a random percentage, then apply that to the
	// difference between min and max, and add to min.
	jitter := rand.Float64() * float64(max-min)
	jitterMin := int64(jitter) + int64(min)
	return time.Duration(jitterMin * int64(attemptNum))
}

// PassthroughErrorHandler is an ErrorHandler that directly passes through the
// values from the net/http library for the final request. The body is not
// closed.
func PassthroughErrorHandler(resp *http.Response, err error, _ int) (*http.Response, error) {
	return resp, err
}

// Do wraps calling an HTTP method with retries.
func (c *Client) Do(req *Request) (*http.Response, error) {
	if c.Logger != nil {
		c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
	}

	var resp *http.Response
	var err error

	for i := 0; ; i++ {
		var code int // HTTP response code

		// Always rewind the request body when non-nil.
		if req.body != nil {
			if _, err := req.body.Seek(0, 0); err != nil {
				return nil, fmt.Errorf("failed to seek body: %v", err)
			}
		}

		if c.RequestLogHook != nil {
			c.RequestLogHook(c.Logger, req.Request, i)
		}

		// Attempt the request
		resp, err = c.HTTPClient.Do(req.Request)
		if resp != nil {
			code = resp.StatusCode
		}

		// Check if we should continue with retries.
		checkOK, checkErr := c.CheckRetry(resp, err)

		if err != nil {
			if c.Logger != nil {
				c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
			}
		} else {
			// Call this here to maintain the behavior of logging all requests,
			// even if CheckRetry signals to stop.
			if c.ResponseLogHook != nil {
				// Call the response logger function if provided.
				c.ResponseLogHook(c.Logger, resp)
			}
		}

		// Now decide if we should continue.
		if !checkOK {
			if checkErr != nil {
				err = checkErr
			}
			return resp, err
		}

		// We do this before drainBody beause there's no need for the I/O if
		// we're breaking out
		remain := c.RetryMax - i
		if remain <= 0 {
			break
		}

		// We're going to retry, consume any response to reuse the connection.
		if err == nil && resp != nil {
			c.drainBody(resp.Body)
		}

		wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
		desc := fmt.Sprintf("%s %s", req.Method, req.URL)
		if code > 0 {
			desc = fmt.Sprintf("%s (status: %d)", desc, code)
		}
		if c.Logger != nil {
			c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
		}
		time.Sleep(wait)
	}

	if c.ErrorHandler != nil {
		return c.ErrorHandler(resp, err, c.RetryMax+1)
	}

	// By default, we close the response body and return an error without
	// returning the response
	if resp != nil {
		resp.Body.Close()
	}
	return nil, fmt.Errorf("%s %s giving up after %d attempts",
		req.Method, req.URL, c.RetryMax+1)
}

// Try to read the response body so we can reuse this connection.
func (c *Client) drainBody(body io.ReadCloser) {
	defer body.Close()
	_, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
	if err != nil {
		if c.Logger != nil {
			c.Logger.Printf("[ERR] error reading response body: %v", err)
		}
	}
}

// Get is a shortcut for doing a GET request without making a new client.
func Get(url string) (*http.Response, error) {
	return defaultClient.Get(url)
}

// Get is a convenience helper for doing simple GET requests.
func (c *Client) Get(url string) (*http.Response, error) {
	req, err := NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

// Head is a shortcut for doing a HEAD request without making a new client.
func Head(url string) (*http.Response, error) {
	return defaultClient.Head(url)
}

// Head is a convenience method for doing simple HEAD requests.
func (c *Client) Head(url string) (*http.Response, error) {
	req, err := NewRequest("HEAD", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

// Post is a shortcut for doing a POST request without making a new client.
func Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
	return defaultClient.Post(url, bodyType, body)
}

// Post is a convenience method for doing simple POST requests.
func (c *Client) Post(url, bodyType string, body io.ReadSeeker) (*http.Response, error) {
	req, err := NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", bodyType)
	return c.Do(req)
}

// PostForm is a shortcut to perform a POST with form data without creating
// a new client.
func PostForm(url string, data url.Values) (*http.Response, error) {
	return defaultClient.PostForm(url, data)
}

// PostForm is a convenience method for doing simple POST operations using
// pre-filled url.Values form data.
func (c *Client) PostForm(url string, data url.Values) (*http.Response, error) {
	return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
<|endoftext|>


<commit_before>package schyntax

import (
	"github.com/schyntax/go-schyntax/internals"
	"math"
	"time"
)

type Schedule struct {
	OriginalText string
	ir           *internals.IrProgram
}

func New(schedule string) (sch *Schedule, err error) {
	defer func() {
		if e := recover(); e != nil {
			sch = nil
			switch e.(type) {
			case string:
				err = newInternalError(e.(string), sch.OriginalText)
			default:
				if er, ok := e.(error); ok {
					err = er
				} else {
					panic(e) // no idea what was passed to panic, just pass it along
				}
			}
		}
	}()

	sch = &Schedule{}
	sch.OriginalText = schedule

	parser := internals.NewParser(schedule)
	ast := parser.Parse()

	validator := internals.Validator{schedule, ast}
	validator.AssertValid()

	sch.ir = internals.CompileAst(ast)

	return
}

func (s *Schedule) Next() (time.Time, error) {
	return s.getEvent(time.Now(), searchModeAfter)
}

func (s *Schedule) NextAfter(after time.Time) (time.Time, error) {
	return s.getEvent(after, searchModeAfter)
}

func (s *Schedule) Previous() (time.Time, error) {
	return s.getEvent(time.Now(), searchModeAtOrBefore)
}

func (s *Schedule) PreviousAtOrBefore(atOrBefore time.Time) (time.Time, error) {
	return s.getEvent(atOrBefore, searchModeAtOrBefore)
}

type searchMode int8

const (
	searchModeAtOrBefore searchMode = iota
	searchModeAfter
)

func (s *Schedule) getEvent(start time.Time, mode searchMode) (result time.Time, err error) {
	start = start.UTC()
	found := false

	for _, group := range s.ir.Groups {
		if e, good := tryGetGroupEvent(group, start, mode); good {
			if !found || (mode == searchModeAfter && e.Before(result)) || (mode == searchModeAtOrBefore && e.After(result)) {
				result = e
				found = true
			}
		}
	}

	if !found {
		err = &ValidTimeNotFoundError{s.OriginalText}
	}

	return
}

func tryGetGroupEvent(group *internals.IrGroup, start time.Time, mode searchMode) (result time.Time, found bool) {
	after := mode == searchModeAfter
	inc := 1 // used for incrementing values up or down depending on the direction we're searching
	initHour := 0
	initMinute := 0
	initSecond := 0

	if !after {
		inc = -1
		initHour = 23
		initMinute = 59
		initSecond = 59
	}

	var hourCount, minuteCount, secondCount int

	// todo: make the length of the search configurable
	for d := 0; d < 367; d++ {
		var date time.Time
		var hour, minute, second int
		if d == 0 {
			// "after" events must be in the future
			if after {
				date = start.Add(time.Second)
			} else {
				date = start
			}

			hour = date.Hour()
			minute = date.Minute()
			second = date.Second()
		} else {
			date = start.AddDate(0, 0, 1)

			hour = initHour
			minute = initMinute
			second = initSecond
		}

		year := date.Year()
		month := int(date.Month())
		dayOfWeek := int(date.Weekday()) + 1 // Weekday is zero-indexed
		dayOfMonth := date.Day()

		// check if today is an applicable date
		if group.HasDates() {
			applicable := false
			for _, r := range group.Dates {
				if inDateRange(r, year, month, dayOfMonth) {
					applicable = true
					break
				}
			}

			if !applicable {
				goto CONTINUE_DATE_LOOP
			}
		}

		if group.HasDatesExcluded() {
			for _, r := range group.DatesExcluded {
				if inDateRange(r, year, month, dayOfMonth) {
					goto CONTINUE_DATE_LOOP
				}
			}
		}

		// check if date is an applicable day of month
		if group.HasDaysOfMonth() {
			applicable := false
			for _, r := range group.DaysOfMonth {
				if inDayOfMonthRange(r, year, month, dayOfMonth) {
					applicable = true
					break
				}
			}

			if !applicable {
				goto CONTINUE_DATE_LOOP
			}
		}

		if group.HasDaysOfMonthExcluded() {
			for _, r := range group.DaysOfMonthExcluded {
				if inDayOfMonthRange(r, year, month, dayOfMonth) {
					goto CONTINUE_DATE_LOOP
				}
			}
		}

		// check if date is an applicable day of week
		if group.HasDaysOfWeek() && !inRule(7, group.DaysOfWeek, dayOfWeek) {
			goto CONTINUE_DATE_LOOP
		}

		if group.HasDaysOfWeekExcluded() && inRule(7, group.DaysOfWeekExcluded, dayOfWeek) {
			goto CONTINUE_DATE_LOOP
		}

		// if we've gotten this far, then today is an applicable day, let's keep going with hour checks
		if after {
			hourCount = 24 - hour
		} else {
			hourCount = hour + 1
		}

		for hourCount > 0 {
			if group.HasHours() && !inRule(24, group.Hours, hour) {
				goto CONTINUE_HOUR_LOOP
			}

			if group.HasHoursExcluded() && inRule(24, group.HoursExcluded, hour) {
				goto CONTINUE_HOUR_LOOP
			}

			// if we've gotten here, the date and hour are valid. Let's check for minutes
			if after {
				minuteCount = 60 - minute
			} else {
				minuteCount = minute + 1
			}

			for minuteCount > 0 {
				if group.HasMinutes() && !inRule(60, group.Minutes, minute) {
					goto CONTINUE_MINUTE_LOOP
				}

				if group.HasMinutesExcluded() && inRule(60, group.MinutesExcluded, minute) {
					goto CONTINUE_MINUTE_LOOP
				}

				// check for valid seconds
				if after {
					secondCount = 60 - second
				} else {
					secondCount = second + 1
				}

				for secondCount > 0 {
					if group.HasSeconds() && !inRule(60, group.Seconds, second) {
						goto CONTINUE_SECOND_LOOP
					}

					if group.HasSecondsExcluded() && inRule(60, group.SecondsExcluded, second) {
						goto CONTINUE_SECOND_LOOP
					}

					// we've found our event
					result = time.Date(year, time.Month(month), dayOfMonth, hour, minute, second, 0, time.UTC)
					found = true
					return

				CONTINUE_SECOND_LOOP:
					secondCount--
					second += inc
				}

			CONTINUE_MINUTE_LOOP:
				minuteCount--
				minute += inc
				second = initSecond
			}

		CONTINUE_HOUR_LOOP:
			hourCount--
			hour += inc
			minute = initMinute
			second = initSecond
		}

	CONTINUE_DATE_LOOP:
	}

	// we didn't find an applicable date
	return
}

func inRule(lengthOfUnit int, ranges []*internals.IrIntegerRange, value int) bool {
	for _, r := range ranges {
		if inIntegerRange(r, value, lengthOfUnit) {
			return true
		}
	}

	return false
}

func inDateRange(r *internals.IrDateRange, year, month, dayOfMonth int) bool {
	// first, check if this is actually a range
	if !r.IsRange {
		// not a range, so just do a straight comparison
		if r.Start.Month != month || r.Start.Day != dayOfMonth {
			return false
		}

		if r.DatesHaveYear && r.Start.Year != year {
			return false
		}

		return true
	}

	if r.IsHalfOpen {
		// check if this is the last date in a half-open range
		end := r.End
		if end.Day == dayOfMonth && end.Month == month && (!r.DatesHaveYear || end.Year == year) {
			return false
		}
	}

	// check if in-between start and end dates
	if r.DatesHaveYear {
		// when we have a year, the check is much simpler because the range can't be split
		if year < r.Start.Year || year > r.End.Year {
			return false
		}

		if year == r.Start.Year && compareMonthAndDay(month, dayOfMonth, r.Start.Month, r.Start.Day) == -1 {
			return false
		}

		if year == r.End.Year && compareMonthAndDay(month, dayOfMonth, r.End.Month, r.End.Day) == 1 {
			return false
		}

	} else if r.IsSplit { // split ranges aren't allowed to have years (it wouldn't make any sense)
		if month == r.Start.Month || month == r.End.Month {
			if month == r.Start.Month && dayOfMonth < r.Start.Day {
				return false
			}

			if month == r.End.Month && dayOfMonth > r.End.Day {
				return false
			}

		} else if !(month < r.End.Month || month > r.Start.Month) {
			return false
		}

	} else {
		// not a split range, and no year information - just month and day to go on
		if compareMonthAndDay(month, dayOfMonth, r.Start.Month, r.Start.Day) == -1 {
			return false
		}

		if compareMonthAndDay(month, dayOfMonth, r.End.Month, r.End.Day) == 1 {
			return false
		}
	}

	// If we get here, then we're definitely somewhere within the range.
	// If there's no interval, then there's nothing else we need to check
	if !r.HasInterval {
		return true
	}

	// figure out the actual date of the low date so we know whether we're on the desired interval
	var startYear int
	if r.DatesHaveYear {
		startYear = r.Start.Year
	} else if r.IsSplit && month <= r.End.Month {
		// start date is from the previous year
		startYear = year - 1
	} else {
		startYear = year
	}

	startDay := r.Start.Day

	// check if start date was actually supposed to be February 29th, but isn't because of non-leap-year.
	if r.Start.Month == 2 && r.Start.Day == 29 && internals.DaysInMonth(startYear, 2) != 29 {
		// bump the start day back to February 28th so that interval schemes work based on that imaginary date
		// but seriously, people should probably just expect weird results if they're doing something that stupid.
		startDay = 28
	}

	start := time.Date(startYear, time.Month(r.Start.Month), startDay, 0, 0, 0, 0, time.UTC)
	current := time.Date(year, time.Month(month), dayOfMonth, 0, 0, 0, 0, time.UTC)
	dayCount := roundPositiveFloat(current.Sub(start).Hours() / 24.0)

	return (dayCount % r.Interval) == 0
}

// returns 0 if A and B are equal, -1 if A is before B, or 1 if A is after B
func compareMonthAndDay(monthA, dayA, monthB, dayB int) int {
	if monthA == monthB {
		if dayA == dayB {
			return 0
		}

		if dayA > dayB {
			return 1
		}

		return -1
	}

	if monthA > monthB {
		return 1
	}

	return -1
}

func inDayOfMonthRange(r *internals.IrIntegerRange, year, month, dayOfMonth int) bool {
	if r.Start < 0 || (r.IsRange && r.End < 0) {
		// one of the range values is negative, so we need to convert it to a positive by counting back from the end of the month
		daysInMonth := internals.DaysInMonth(year, month)

		revisedStart := r.Start
		if revisedStart < 0 {
			revisedStart = daysInMonth + revisedStart + 1
		}

		revisedEnd := r.End
		if revisedEnd < 0 {
			revisedEnd = daysInMonth + revisedEnd + 1
		}

		r = r.CloneWithRevisedRange(revisedStart, revisedEnd)
	}

	return inIntegerRange(r, dayOfMonth, daysInPreviousMonth(year, month))
}

func inIntegerRange(r *internals.IrIntegerRange, value, lengthOfUnit int) bool {
	if !r.IsRange {
		return value == r.Start
	}

	if r.IsHalfOpen && value == r.End {
		return false
	}

	if r.IsSplit { // range spans across the max value and loops back around
		if value <= r.End || value >= r.Start {
			if r.HasInterval {
				if value >= r.Start {
					return (value-r.Start)%r.Interval == 0
				}

				return (value+lengthOfUnit-r.Start)%r.Interval == 0
			}

			return true
		}

	} else { // not a split range (easier case)
		if value >= r.Start && value <= r.End {
			if r.HasInterval {
				return (value-r.Start)%r.Interval == 0
			}

			return true
		}
	}

	return false
}

func daysInPreviousMonth(year, month int) int {
	month--
	if month == 0 {
		year--
		month = 12
	}

	return internals.DaysInMonth(year, month)
}

func roundPositiveFloat(f float64) int {
	return int(math.Floor(f + 0.5))
}
<commit_msg>Add alternate next/previous methods which panic.<commit_after>package schyntax

import (
	"github.com/schyntax/go-schyntax/internals"
	"math"
	"time"
)

type Schedule struct {
	OriginalText string
	ir           *internals.IrProgram
}

func New(schedule string) (sch *Schedule, err error) {
	defer func() {
		if e := recover(); e != nil {
			sch = nil
			switch e.(type) {
			case string:
				err = newInternalError(e.(string), sch.OriginalText)
			default:
				if er, ok := e.(error); ok {
					err = er
				} else {
					panic(e) // no idea what was passed to panic, just pass it along
				}
			}
		}
	}()

	sch = &Schedule{}
	sch.OriginalText = schedule

	parser := internals.NewParser(schedule)
	ast := parser.Parse()

	validator := internals.Validator{schedule, ast}
	validator.AssertValid()

	sch.ir = internals.CompileAst(ast)

	return
}

func (s *Schedule) Next() time.Time {
	t, err := s.TryNext()
	if err != nil {
		panic(err)
	}

	return t
}

func (s *Schedule) TryNext() (time.Time, error) {
	return s.getEvent(time.Now(), searchModeAfter)
}

func (s *Schedule) NextAfter(after time.Time) time.Time {
	t, err := s.TryNextAfter(after)
	if err != nil {
		panic(err)
	}

	return t
}

func (s *Schedule) TryNextAfter(after time.Time) (time.Time, error) {
	return s.getEvent(after, searchModeAfter)
}

func (s *Schedule) Previous() time.Time {
	t, err := s.TryPrevious()
	if err != nil {
		panic(err)
	}

	return t
}

func (s *Schedule) TryPrevious() (time.Time, error) {
	return s.getEvent(time.Now(), searchModeAtOrBefore)
}

func (s *Schedule) PreviousAtOrBefore(atOrBefore time.Time) time.Time {
	t, err := s.TryPreviousAtOrBefore(atOrBefore)
	if err != nil {
		panic(err)
	}

	return t
}

func (s *Schedule) TryPreviousAtOrBefore(atOrBefore time.Time) (time.Time, error) {
	return s.getEvent(atOrBefore, searchModeAtOrBefore)
}

type searchMode int8

const (
	searchModeAtOrBefore searchMode = iota
	searchModeAfter
)

func (s *Schedule) getEvent(start time.Time, mode searchMode) (result time.Time, err error) {
	start = start.UTC()
	found := false

	for _, group := range s.ir.Groups {
		if e, good := tryGetGroupEvent(group, start, mode); good {
			if !found || (mode == searchModeAfter && e.Before(result)) || (mode == searchModeAtOrBefore && e.After(result)) {
				result = e
				found = true
			}
		}
	}

	if !found {
		err = &ValidTimeNotFoundError{s.OriginalText}
	}

	return
}

func tryGetGroupEvent(group *internals.IrGroup, start time.Time, mode searchMode) (result time.Time, found bool) {
	after := mode == searchModeAfter
	inc := 1 // used for incrementing values up or down depending on the direction we're searching
	initHour := 0
	initMinute := 0
	initSecond := 0

	if !after {
		inc = -1
		initHour = 23
		initMinute = 59
		initSecond = 59
	}

	var hourCount, minuteCount, secondCount int

	// todo: make the length of the search configurable
	for d := 0; d < 367; d++ {
		var date time.Time
		var hour, minute, second int
		if d == 0 {
			// "after" events must be in the future
			if after {
				date = start.Add(time.Second)
			} else {
				date = start
			}

			hour = date.Hour()
			minute = date.Minute()
			second = date.Second()
		} else {
			date = start.AddDate(0, 0, 1)

			hour = initHour
			minute = initMinute
			second = initSecond
		}

		year := date.Year()
		month := int(date.Month())
		dayOfWeek := int(date.Weekday()) + 1 // Weekday is zero-indexed
		dayOfMonth := date.Day()

		// check if today is an applicable date
		if group.HasDates() {
			applicable := false
			for _, r := range group.Dates {
				if inDateRange(r, year, month, dayOfMonth) {
					applicable = true
					break
				}
			}

			if !applicable {
				goto CONTINUE_DATE_LOOP
			}
		}

		if group.HasDatesExcluded() {
			for _, r := range group.DatesExcluded {
				if inDateRange(r, year, month, dayOfMonth) {
					goto CONTINUE_DATE_LOOP
				}
			}
		}

		// check if date is an applicable day of month
		if group.HasDaysOfMonth() {
			applicable := false
			for _, r := range group.DaysOfMonth {
				if inDayOfMonthRange(r, year, month, dayOfMonth) {
					applicable = true
					break
				}
			}

			if !applicable {
				goto CONTINUE_DATE_LOOP
			}
		}

		if group.HasDaysOfMonthExcluded() {
			for _, r := range group.DaysOfMonthExcluded {
				if inDayOfMonthRange(r, year, month, dayOfMonth) {
					goto CONTINUE_DATE_LOOP
				}
			}
		}

		// check if date is an applicable day of week
		if group.HasDaysOfWeek() && !inRule(7, group.DaysOfWeek, dayOfWeek) {
			goto CONTINUE_DATE_LOOP
		}

		if group.HasDaysOfWeekExcluded() && inRule(7, group.DaysOfWeekExcluded, dayOfWeek) {
			goto CONTINUE_DATE_LOOP
		}

		// if we've gotten this far, then today is an applicable day, let's keep going with hour checks
		if after {
			hourCount = 24 - hour
		} else {
			hourCount = hour + 1
		}

		for hourCount > 0 {
			if group.HasHours() && !inRule(24, group.Hours, hour) {
				goto CONTINUE_HOUR_LOOP
			}

			if group.HasHoursExcluded() && inRule(24, group.HoursExcluded, hour) {
				goto CONTINUE_HOUR_LOOP
			}

			// if we've gotten here, the date and hour are valid. Let's check for minutes
			if after {
				minuteCount = 60 - minute
			} else {
				minuteCount = minute + 1
			}

			for minuteCount > 0 {
				if group.HasMinutes() && !inRule(60, group.Minutes, minute) {
					goto CONTINUE_MINUTE_LOOP
				}

				if group.HasMinutesExcluded() && inRule(60, group.MinutesExcluded, minute) {
					goto CONTINUE_MINUTE_LOOP
				}

				// check for valid seconds
				if after {
					secondCount = 60 - second
				} else {
					secondCount = second + 1
				}

				for secondCount > 0 {
					if group.HasSeconds() && !inRule(60, group.Seconds, second) {
						goto CONTINUE_SECOND_LOOP
					}

					if group.HasSecondsExcluded() && inRule(60, group.SecondsExcluded, second) {
						goto CONTINUE_SECOND_LOOP
					}

					// we've found our event
					result = time.Date(year, time.Month(month), dayOfMonth, hour, minute, second, 0, time.UTC)
					found = true
					return

				CONTINUE_SECOND_LOOP:
					secondCount--
					second += inc
				}

			CONTINUE_MINUTE_LOOP:
				minuteCount--
				minute += inc
				second = initSecond
			}

		CONTINUE_HOUR_LOOP:
			hourCount--
			hour += inc
			minute = initMinute
			second = initSecond
		}

	CONTINUE_DATE_LOOP:
	}

	// we didn't find an applicable date
	return
}

func inRule(lengthOfUnit int, ranges []*internals.IrIntegerRange, value int) bool {
	for _, r := range ranges {
		if inIntegerRange(r, value, lengthOfUnit) {
			return true
		}
	}

	return false
}

func inDateRange(r *internals.IrDateRange, year, month, dayOfMonth int) bool {
	// first, check if this is actually a range
	if !r.IsRange {
		// not a range, so just do a straight comparison
		if r.Start.Month != month || r.Start.Day != dayOfMonth {
			return false
		}

		if r.DatesHaveYear && r.Start.Year != year {
			return false
		}

		return true
	}

	if r.IsHalfOpen {
		// check if this is the last date in a half-open range
		end := r.End
		if end.Day == dayOfMonth && end.Month == month && (!r.DatesHaveYear || end.Year == year) {
			return false
		}
	}

	// check if in-between start and end dates
	if r.DatesHaveYear {
		// when we have a year, the check is much simpler because the range can't be split
		if year < r.Start.Year || year > r.End.Year {
			return false
		}

		if year == r.Start.Year && compareMonthAndDay(month, dayOfMonth, r.Start.Month, r.Start.Day) == -1 {
			return false
		}

		if year == r.End.Year && compareMonthAndDay(month, dayOfMonth, r.End.Month, r.End.Day) == 1 {
			return false
		}

	} else if r.IsSplit { // split ranges aren't allowed to have years (it wouldn't make any sense)
		if month == r.Start.Month || month == r.End.Month {
			if month == r.Start.Month && dayOfMonth < r.Start.Day {
				return false
			}

			if month == r.End.Month && dayOfMonth > r.End.Day {
				return false
			}

		} else if !(month < r.End.Month || month > r.Start.Month) {
			return false
		}

	} else {
		// not a split range, and no year information - just month and day to go on
		if compareMonthAndDay(month, dayOfMonth, r.Start.Month, r.Start.Day) == -1 {
			return false
		}

		if compareMonthAndDay(month, dayOfMonth, r.End.Month, r.End.Day) == 1 {
			return false
		}
	}

	// If we get here, then we're definitely somewhere within the range.
	// If there's no interval, then there's nothing else we need to check
	if !r.HasInterval {
		return true
	}

	// figure out the actual date of the low date so we know whether we're on the desired interval
	var startYear int
	if r.DatesHaveYear {
		startYear = r.Start.Year
	} else if r.IsSplit && month <= r.End.Month {
		// start date is from the previous year
		startYear = year - 1
	} else {
		startYear = year
	}

	startDay := r.Start.Day

	// check if start date was actually supposed to be February 29th, but isn't because of non-leap-year.
	if r.Start.Month == 2 && r.Start.Day == 29 && internals.DaysInMonth(startYear, 2) != 29 {
		// bump the start day back to February 28th so that interval schemes work based on that imaginary date
		// but seriously, people should probably just expect weird results if they're doing something that stupid.
		startDay = 28
	}

	start := time.Date(startYear, time.Month(r.Start.Month), startDay, 0, 0, 0, 0, time.UTC)
	current := time.Date(year, time.Month(month), dayOfMonth, 0, 0, 0, 0, time.UTC)
	dayCount := roundPositiveFloat(current.Sub(start).Hours() / 24.0)

	return (dayCount % r.Interval) == 0
}

// returns 0 if A and B are equal, -1 if A is before B, or 1 if A is after B
func compareMonthAndDay(monthA, dayA, monthB, dayB int) int {
	if monthA == monthB {
		if dayA == dayB {
			return 0
		}

		if dayA > dayB {
			return 1
		}

		return -1
	}

	if monthA > monthB {
		return 1
	}

	return -1
}

func inDayOfMonthRange(r *internals.IrIntegerRange, year, month, dayOfMonth int) bool {
	if r.Start < 0 || (r.IsRange && r.End < 0) {
		// one of the range values is negative, so we need to convert it to a positive by counting back from the end of the month
		daysInMonth := internals.DaysInMonth(year, month)

		revisedStart := r.Start
		if revisedStart < 0 {
			revisedStart = daysInMonth + revisedStart + 1
		}

		revisedEnd := r.End
		if revisedEnd < 0 {
			revisedEnd = daysInMonth + revisedEnd + 1
		}

		r = r.CloneWithRevisedRange(revisedStart, revisedEnd)
	}

	return inIntegerRange(r, dayOfMonth, daysInPreviousMonth(year, month))
}

func inIntegerRange(r *internals.IrIntegerRange, value, lengthOfUnit int) bool {
	if !r.IsRange {
		return value == r.Start
	}

	if r.IsHalfOpen && value == r.End {
		return false
	}

	if r.IsSplit { // range spans across the max value and loops back around
		if value <= r.End || value >= r.Start {
			if r.HasInterval {
				if value >= r.Start {
					return (value-r.Start)%r.Interval == 0
				}

				return (value+lengthOfUnit-r.Start)%r.Interval == 0
			}

			return true
		}

	} else { // not a split range (easier case)
		if value >= r.Start && value <= r.End {
			if r.HasInterval {
				return (value-r.Start)%r.Interval == 0
			}

			return true
		}
	}

	return false
}

func daysInPreviousMonth(year, month int) int {
	month--
	if month == 0 {
		year--
		month = 12
	}

	return internals.DaysInMonth(year, month)
}

func roundPositiveFloat(f float64) int {
	return int(math.Floor(f + 0.5))
}
<|endoftext|>


<commit_before>package scanner

import (
	"strings"
	"time"
)

const (
	// ScannerAddScanEndpoint is a string representation of the current endpoint for scanner add scan
	ScannerAddScanEndpoint = "v1/scanner/addScanResult"
)

//ScanStatus identifies the state of a scan performed by the Ion system
type ScanStatus struct {
	ID               string    `json:"id"`
	AnalysisStatusID string    `json:"analysis_status_id"`
	ProjectID        string    `json:"project_id"`
	TeamID           string    `json:"team_id"`
	Message          string    `json:"message"`
	Name             string    `json:"name"`
	Read             string    `json:"read"`
	Status           string    `json:"status"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
}

// Errored encapsulates the concerns of whether a ScanStatus is in an errored
// state or not. It returns true or false based on whether the ScanStatus is
// errored.
func (s *ScanStatus) Errored() bool {
	if strings.ToLower(s.Status) == "errored" {
		return true
	}

	return false
}
<commit_msg>adding constants for known states<commit_after>package scanner

import (
	"strings"
	"time"
)

const (
	// ScannerAddScanEndpoint is a string representation of the current endpoint for scanner add scan
	ScannerAddScanEndpoint = "v1/scanner/addScanResult"

	// ScanStatusErrored denotes a request for scan has errored during
	// the run, the message field will have more details
	ScanStatusErrored = "errored"
	// ScanStatusFinished denotes a request for scan has been
	// completed, view the passed field from an Scan and the scan details for
	// more information
	ScanStatusFinished = "finished"
	// ScanStatusStarted denotes a request for scan has been
	// accepted and has begun
	ScanStatusStarted = "started"
)

//ScanStatus identifies the state of a scan performed by the Ion system
type ScanStatus struct {
	ID               string    `json:"id"`
	AnalysisStatusID string    `json:"analysis_status_id"`
	ProjectID        string    `json:"project_id"`
	TeamID           string    `json:"team_id"`
	Message          string    `json:"message"`
	Name             string    `json:"name"`
	Read             string    `json:"read"`
	Status           string    `json:"status"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
}

// Errored encapsulates the concerns of whether a ScanStatus is in an errored
// state or not. It returns true or false based on whether the ScanStatus is
// errored.
func (s *ScanStatus) Errored() bool {
	if strings.ToLower(s.Status) == ScanStatusErrored {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/hectane/hectane/email"
	"github.com/hectane/hectane/queue"

	"encoding/json"
	"net/http"
)

type rawParams struct {
	From string   `json:"from"`
	To   []string `json:"to"`
	Body string   `json:"body"`
}

// Send a raw MIME message.
func (a *API) raw(r *http.Request) interface{} {
	var p rawParams
	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
		return err
	}
	w, body, err := a.queue.Storage.NewBody()
	if err != nil {
		return err
	}
	if _, err := w.Write([]byte(p.Body)); err != nil {
		return err
	}
	if err := w.Close(); err != nil {
		return err
	}
	hostMap, err := email.GroupAddressesByHost(p.To)
	if err == nil {
		return err
	}
	for h, to := range hostMap {
		m := &queue.Message{
			Host: h,
			From: p.From,
			To:   to,
		}
		if err := a.queue.Storage.SaveMessage(m, body); err != nil {
			return err
		}
		a.queue.Deliver(m)
	}
	return struct{}{}
}

// Send an email with the specified parameters.
func (a *API) send(r *http.Request) interface{} {
	var e email.Email
	if err := json.NewDecoder(r.Body).Decode(&e); err != nil {
		return err
	}
	messages, err := e.Messages(a.queue.Storage)
	if err != nil {
		return map[string]string{
			"error": err.Error(),
		}
	}
	for _, m := range messages {
		a.queue.Deliver(m)
	}
	return struct{}{}
}

// Retrieve status information.
func (a *API) status(r *http.Request) interface{} {
	return a.queue.Status()
}

// Retrieve version information, including the current version of the
// application.
func (a *API) version(r *http.Request) interface{} {
	return map[string]string{
		"version": "0.3.0",
	}
}
<commit_msg>Bumped version returned by the API.<commit_after>package api

import (
	"github.com/hectane/hectane/email"
	"github.com/hectane/hectane/queue"

	"encoding/json"
	"net/http"
)

type rawParams struct {
	From string   `json:"from"`
	To   []string `json:"to"`
	Body string   `json:"body"`
}

// Send a raw MIME message.
func (a *API) raw(r *http.Request) interface{} {
	var p rawParams
	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
		return err
	}
	w, body, err := a.queue.Storage.NewBody()
	if err != nil {
		return err
	}
	if _, err := w.Write([]byte(p.Body)); err != nil {
		return err
	}
	if err := w.Close(); err != nil {
		return err
	}
	hostMap, err := email.GroupAddressesByHost(p.To)
	if err == nil {
		return err
	}
	for h, to := range hostMap {
		m := &queue.Message{
			Host: h,
			From: p.From,
			To:   to,
		}
		if err := a.queue.Storage.SaveMessage(m, body); err != nil {
			return err
		}
		a.queue.Deliver(m)
	}
	return struct{}{}
}

// Send an email with the specified parameters.
func (a *API) send(r *http.Request) interface{} {
	var e email.Email
	if err := json.NewDecoder(r.Body).Decode(&e); err != nil {
		return err
	}
	messages, err := e.Messages(a.queue.Storage)
	if err != nil {
		return map[string]string{
			"error": err.Error(),
		}
	}
	for _, m := range messages {
		a.queue.Deliver(m)
	}
	return struct{}{}
}

// Retrieve status information.
func (a *API) status(r *http.Request) interface{} {
	return a.queue.Status()
}

// Retrieve version information, including the current version of the
// application.
func (a *API) version(r *http.Request) interface{} {
	return map[string]string{
		"version": "0.3.1",
	}
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"path"
	"syscall"
)

const (
	masterWorkloadName = "k8s master"
	workerWorkloadName = "k8s worker"
)

type usageError string

func (e usageError) Error() string {
	return string(e)
}

type vmOptions struct {
	vCPUs int
	mem   int
	disk  int
}

type options struct {
	masterVM      vmOptions
	workerVM      vmOptions
	user          string
	publicKeyPath string
	workers       int
	imageUUID     string
	externalIP    string
	keep          bool
}

type baseConfig struct {
	VCPUs        int
	RAM          int
	Disk         int
	User         string
	ImageUUID    string
	HTTPSProxy   string
	HTTPProxy    string
	NoProxy      string
	Token        string
	PublicKey    string
	UserDataFile string
	Description  string
}

type proxyConfig struct {
	httpProxy  string
	httpsProxy string
	noProxy    string
}

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s create image-uuid [options]\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s delete\n\n", os.Args[0])
		fmt.Fprintln(os.Stderr, "- create : creates a k8s cluster")
		fmt.Fprintln(os.Stderr, "- delete : removes kubicle created instances and workloads")
	}
}

func createFlags() (*options, error) {
	opts := options{
		masterVM: vmOptions{
			vCPUs: 1,
			mem:   1024,
			disk:  10,
		},
		workerVM: vmOptions{
			vCPUs: 1,
			mem:   2048,
			disk:  10,
		},
		workers: 1,
	}

	opts.user = os.Getenv("USER")
	home := os.Getenv("HOME")
	if home != "" {
		opts.publicKeyPath = path.Join(home, "local", "testkey.pub")
	}

	fs := flag.NewFlagSet("create", flag.ExitOnError)

	fs.IntVar(&opts.masterVM.mem, "mmem", opts.masterVM.mem,
		"Megabytes of RAM allocated to master VM")
	fs.IntVar(&opts.masterVM.vCPUs, "mcpus", opts.masterVM.vCPUs, "VCPUs assignged to master VM")
	fs.IntVar(&opts.masterVM.disk, "mdisk", opts.masterVM.disk,
		"Gigabytes of disk allocated to master VM")

	fs.IntVar(&opts.workerVM.mem, "wmem", opts.workerVM.mem,
		"Megabytes of RAM allocated to worker VMs")
	fs.IntVar(&opts.workerVM.vCPUs, "wcpus", opts.workerVM.vCPUs, "VCPUs assignged to worker VM")
	fs.IntVar(&opts.workerVM.disk, "wdisk", opts.workerVM.disk,
		"Gigabytes of disk allocated to worker VM")

	fs.IntVar(&opts.workers, "workers", opts.workers, "Number of worker nodes to create")

	fs.StringVar(&opts.publicKeyPath, "key", opts.publicKeyPath, "Path to public key used to ssh into nodes")
	fs.StringVar(&opts.user, "user", opts.user, "Name of user account to create on the nodes")
	fs.StringVar(&opts.externalIP, "external-ip", opts.externalIP,
		"External-ip to associate with the master node")
	fs.BoolVar(&opts.keep, "keep", false, "Retains workload definition files if set to true")

	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return nil, err
	}

	if len(fs.Args()) < 1 {
		return nil, usageError("No image-uuid specified!")
	}
	opts.imageUUID = fs.Args()[0]

	return &opts, nil
}
func runCommand(signalCh <-chan os.Signal) error {
	var err error

	errCh := make(chan error)
	ctx, cancelFunc := context.WithCancel(context.Background())
	switch os.Args[1] {
	case "create":
		go create(ctx, errCh)
	case "delete":
		go destroy(ctx, errCh)
	}
	select {
	case <-signalCh:
		cancelFunc()
		err = <-errCh
	case err = <-errCh:
		cancelFunc()
	}

	return err
}

func main() {
	flag.Parse()
	if len(flag.Args()) < 1 {
		flag.Usage()
		os.Exit(1)
	}

	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	if err := runCommand(signalCh); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		if _, ok := err.(usageError); ok {
			fmt.Fprintln(os.Stderr)
			flag.Usage()
		}
		os.Exit(1)
	}
}
<commit_msg>kubicle: Handle unknown commands<commit_after>//
// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"path"
	"syscall"
)

const (
	masterWorkloadName = "k8s master"
	workerWorkloadName = "k8s worker"
)

type usageError string

func (e usageError) Error() string {
	return string(e)
}

type vmOptions struct {
	vCPUs int
	mem   int
	disk  int
}

type options struct {
	masterVM      vmOptions
	workerVM      vmOptions
	user          string
	publicKeyPath string
	workers       int
	imageUUID     string
	externalIP    string
	keep          bool
}

type baseConfig struct {
	VCPUs        int
	RAM          int
	Disk         int
	User         string
	ImageUUID    string
	HTTPSProxy   string
	HTTPProxy    string
	NoProxy      string
	Token        string
	PublicKey    string
	UserDataFile string
	Description  string
}

type proxyConfig struct {
	httpProxy  string
	httpsProxy string
	noProxy    string
}

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s create image-uuid [options]\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s delete\n\n", os.Args[0])
		fmt.Fprintln(os.Stderr, "- create : creates a k8s cluster")
		fmt.Fprintln(os.Stderr, "- delete : removes kubicle created instances and workloads")
	}
}

func createFlags() (*options, error) {
	opts := options{
		masterVM: vmOptions{
			vCPUs: 1,
			mem:   1024,
			disk:  10,
		},
		workerVM: vmOptions{
			vCPUs: 1,
			mem:   2048,
			disk:  10,
		},
		workers: 1,
	}

	opts.user = os.Getenv("USER")
	home := os.Getenv("HOME")
	if home != "" {
		opts.publicKeyPath = path.Join(home, "local", "testkey.pub")
	}

	fs := flag.NewFlagSet("create", flag.ExitOnError)

	fs.IntVar(&opts.masterVM.mem, "mmem", opts.masterVM.mem,
		"Megabytes of RAM allocated to master VM")
	fs.IntVar(&opts.masterVM.vCPUs, "mcpus", opts.masterVM.vCPUs, "VCPUs assignged to master VM")
	fs.IntVar(&opts.masterVM.disk, "mdisk", opts.masterVM.disk,
		"Gigabytes of disk allocated to master VM")

	fs.IntVar(&opts.workerVM.mem, "wmem", opts.workerVM.mem,
		"Megabytes of RAM allocated to worker VMs")
	fs.IntVar(&opts.workerVM.vCPUs, "wcpus", opts.workerVM.vCPUs, "VCPUs assignged to worker VM")
	fs.IntVar(&opts.workerVM.disk, "wdisk", opts.workerVM.disk,
		"Gigabytes of disk allocated to worker VM")

	fs.IntVar(&opts.workers, "workers", opts.workers, "Number of worker nodes to create")

	fs.StringVar(&opts.publicKeyPath, "key", opts.publicKeyPath, "Path to public key used to ssh into nodes")
	fs.StringVar(&opts.user, "user", opts.user, "Name of user account to create on the nodes")
	fs.StringVar(&opts.externalIP, "external-ip", opts.externalIP,
		"External-ip to associate with the master node")
	fs.BoolVar(&opts.keep, "keep", false, "Retains workload definition files if set to true")

	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return nil, err
	}

	if len(fs.Args()) < 1 {
		return nil, usageError("No image-uuid specified!")
	}
	opts.imageUUID = fs.Args()[0]

	return &opts, nil
}
func runCommand(signalCh <-chan os.Signal) error {
	var err error

	errCh := make(chan error)
	ctx, cancelFunc := context.WithCancel(context.Background())
	switch os.Args[1] {
	case "create":
		go create(ctx, errCh)
	case "delete":
		go destroy(ctx, errCh)
	}
	select {
	case <-signalCh:
		cancelFunc()
		err = <-errCh
	case err = <-errCh:
		cancelFunc()
	}

	return err
}

func main() {
	flag.Parse()
	if len(flag.Args()) < 1 ||
		!(os.Args[1] == "create" || os.Args[1] == "delete") {
		flag.Usage()
		os.Exit(1)
	}

	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	if err := runCommand(signalCh); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		if _, ok := err.(usageError); ok {
			fmt.Fprintln(os.Stderr)
			flag.Usage()
		}
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"log"
	"net/http"
	"strings"
	"time"
)

func doScrape(request *http.Request) {
	client := &http.Client{}
	id := request.Header.Get("id") // Needed so they can be linked.

	scrapeResp, err := client.Do(request)
	if err != nil {
		log.Printf("Failed to scrape %s: %s", request.URL.String(), err)
		return
	}
	log.Printf("Scraped %s", request.URL.String())
	scrapeResp.Header.Set("id", id)
	buf := &bytes.Buffer{}
	scrapeResp.Write(buf)
	log.Println(buf.Len())

	_, err = client.Post("http://localhost:1234/push", "", buf)
	if err != nil {
		log.Printf("Failed to push scrape result for %s: %s", request.URL.String(), err)
		return
	}
	log.Printf("Pushed scrape result for %s", request.URL.String())
}

func loop() {
	client := &http.Client{}
	fqdn := "localhost"

	resp, err := client.Post("http://localhost:1234/poll", "", strings.NewReader(fqdn))
	if err != nil {
		log.Printf("Error polling: %s", err)
		time.Sleep(time.Second) // Don't pound the server.
		return
	}
	defer resp.Body.Close()
	request, _ := http.ReadRequest(bufio.NewReader(resp.Body))
	log.Printf("Got request for %s", request.URL.String())
	request.RequestURI = ""

	go doScrape(request)
}

func main() {
	for {
		loop()
	}
}
<commit_msg>Report scrape failures back to proxy<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

func doScrape(request *http.Request) {
	client := &http.Client{}

	scrapeResp, err := client.Do(request)
	if err != nil {
		msg := fmt.Sprintf("Failed to scrape %s: %s", request.URL.String(), err)
		log.Print(msg)
		resp := &http.Response{
			StatusCode: 500,
			Header:     http.Header{},
			Body:       ioutil.NopCloser(bytes.NewBufferString(msg)),
		}
		err = doPush(resp, request)
		if err != nil {
			log.Printf("Failed to push failed scrape result for %s: %s", request.URL.String(), err)
			return
		}
		log.Printf("Pushed failed scrape result for %s", request.URL.String())
		return
	}
	log.Printf("Scraped %s", request.URL.String())

	err = doPush(scrapeResp, request)
	if err != nil {
		log.Printf("Failed to push scrape result for %s: %s", request.URL.String(), err)
		return
	}
	log.Printf("Pushed scrape result for %s", request.URL.String())
}

func doPush(resp *http.Response, request *http.Request) error {
	resp.Header.Set("id", request.Header.Get("id")) // Link the request and response

	buf := &bytes.Buffer{}
	resp.Write(buf)
	client := &http.Client{}
	_, err := client.Post("http://localhost:1234/push", "", buf)
	if err != nil {
		return err
	}
	return nil
}

func loop() {
	client := &http.Client{}
	fqdn := "localhost"

	resp, err := client.Post("http://localhost:1234/poll", "", strings.NewReader(fqdn))
	if err != nil {
		log.Printf("Error polling: %s", err)
		time.Sleep(time.Second) // Don't pound the server.
		return
	}
	defer resp.Body.Close()
	request, _ := http.ReadRequest(bufio.NewReader(resp.Body))
	log.Printf("Got request for %s", request.URL.String())
	request.RequestURI = ""

	go doScrape(request)
}

func main() {
	for {
		loop()
	}
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/boltdb/bolt"
)

var globalDbPath string

type dbHelper func(string) error

// Db is a single instance of dbHelper
var Db dbHelper = func(dbPath string) error {
	db, err := bolt.Open(dbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	globalDbPath = dbPath
	return nil
}

func (this dbHelper) Get(bucket string, key string) ([]byte, error) {
	db, err := bolt.Open(globalDbPath, 0600, nil)
	if err != nil {
		return nil, err
	}
	defer db.Close()

	var value []byte
	err = db.View(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(bucket))
		if bk == nil {
			return ErrorBucketNotFound
		}
		buf := bk.Get([]byte(key))
		if buf == nil {
			return nil
		}
		value = make([]byte, len(buf))
		copy(value, buf)

		return nil
	})

	if err != nil {
		return nil, err
	}

	return value, nil
}

func (this dbHelper) Put(bucket string, key string, value []byte) error {
	db, err := bolt.Open(globalDbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	err = db.Update(func(tx *bolt.Tx) error {
		bk, err := tx.CreateBucketIfNotExists([]byte(bucket))
		if err != nil {
			return err
		}
		err = bk.Put([]byte(key), value)
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return err
	}

	return nil
}

func (this dbHelper) Delete(bucket string, key string) error {
	db, err := bolt.Open(globalDbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	err = db.Update(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(bucket))
		if bk == nil {
			return ErrorBucketNotFound
		}
		err = bk.Delete([]byte(key))
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Db set<commit_after>package app

import (
	"github.com/boltdb/bolt"
)

// Db is a single instance of dbHelper
var Db *dbHelper

type dbHelper struct {
	dbPath string
}

func initDb(dbPath string) error {
	// check can open?
	db, err := bolt.Open(dbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	Db = &dbHelper{dbPath: dbPath}
	return nil
}

func (this *dbHelper) Get(bucket string, key string) ([]byte, error) {
	db, err := bolt.Open(this.dbPath, 0600, nil)
	if err != nil {
		return nil, err
	}
	defer db.Close()

	var value []byte
	err = db.View(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(bucket))
		if bk == nil {
			return ErrorBucketNotFound
		}
		buf := bk.Get([]byte(key))
		if buf == nil {
			return nil
		}
		value = make([]byte, len(buf))
		copy(value, buf)

		return nil
	})

	if err != nil {
		return nil, err
	}

	return value, nil
}

func (this *dbHelper) Put(bucket string, key string, value []byte) error {
	db, err := bolt.Open(this.dbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	err = db.Update(func(tx *bolt.Tx) error {
		bk, err := tx.CreateBucketIfNotExists([]byte(bucket))
		if err != nil {
			return err
		}
		err = bk.Put([]byte(key), value)
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return err
	}

	return nil
}

func (this *dbHelper) Delete(bucket string, key string) error {
	db, err := bolt.Open(this.dbPath, 0600, nil)
	if err != nil {
		return err
	}
	defer db.Close()

	err = db.Update(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(bucket))
		if bk == nil {
			return ErrorBucketNotFound
		}
		err = bk.Delete([]byte(key))
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file tests the Json<->Lisp support.

package golisp

import . "gopkg.in/check.v1"

type JsonLispSuite struct {
}

var _ = Suite(&JsonLispSuite{})

func (s *JsonLispSuite) TestJsonToLispMap(c *C) {
	jsonData := `{"map": 1}`
	sexpr := JsonStringToLisp(jsonData)
	expected := Acons(StringWithValue("map"), IntegerWithValue(1), nil)

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMapWithFrames(c *C) {
	jsonData := `{"map": 1}`
	sexpr := JsonStringToLispWithFrames(jsonData)
	expected, _ := ParseAndEval("{map: 1}")

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispArray(c *C) {
	jsonData := `[1, 2, "hi"]`
	sexpr := JsonStringToLisp(jsonData)
	expected := InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), StringWithValue("hi"))

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispBool(c *C) {
	jsonData := `{"map": true}`
	sexpr := JsonStringToLisp(jsonData)
	expected := Acons(StringWithValue("map"), BooleanWithValue(true), nil)

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMixed(c *C) {
	jsonData := `{"map": {"f1": [47, 75], "f2": 185}, "f3": 85}`
	sexpr := JsonStringToLisp(jsonData)

	expected := Acons(StringWithValue("map"),
		Acons(StringWithValue("f1"),
			InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)),
			Acons(StringWithValue("f2"),
				IntegerWithValue(185), nil)),
		Acons(StringWithValue("f3"),
			IntegerWithValue(85), nil))

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMixedWithFrames(c *C) {
	jsonData := `{"map": {"f1": [47, 75], "f2": 185}, "f3": 85, "f4": 2.2}`
	sexpr := JsonStringToLispWithFrames(jsonData)
	expected, _ := ParseAndEval("{map: {f1: '(47 75) f2: 185} f3: 85 f4: 2.2}")
	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispIllegal(c *C) {
	c.Assert(func() { JsonStringToLisp("hello") }, PanicMatches, `Badly formed json: 'hello'`)
}

func (s *JsonLispSuite) TestLispToJsonMap(c *C) {
	alist := Acons(StringWithValue("map"), IntegerWithValue(1), nil)
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `{"map":1}`)
}

func (s *JsonLispSuite) TestLispToJsonArray(c *C) {
	alist := InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), StringWithValue("hi"))
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `[1,2,"hi"]`)
}

func (s *JsonLispSuite) TestLispToJsonMixed(c *C) {
	alist := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `{"f3":85,"map":{"f1":[47,75],"f2":185}}`)
}

func (s *JsonLispSuite) TestLispWithFramesToJsonMixed(c *C) {
	structure, _ := ParseAndEval("{map: {f1: '(47 75) f2: 185} f3: 85 f4: 2.2}")
	data := LispWithFramesToJsonString(structure)
	c.Assert(data, Equals, `{"f3":85,"f4":2.2,"map":{"f1":[47,75],"f2":185}}`)
}

func (s *JsonLispSuite) TestLispToJsonNil(c *C) {
	data := LispToJsonString(nil)
	c.Assert(data, Equals, `""`)
}

func (s *JsonLispSuite) TestJsonToLispWithFramesMap(c *C) {
	a := int(63)
	b := &a
	_c := (*int)(nil)
	d_p := (*int)(nil)
	d := &d_p
	testMap := map[string]interface{}{
		"float64":    float64(1.1),
		"float64int": float64(1.0),
		"float32":    float32(1.1),
		"float32int": float32(1.0),

		"int":    int(1),
		"int8":   int8(1),
		"int16":  int16(1),
		"int32":  int32(1),
		"int64":  int64(1),
		"uint":   uint(1),
		"uint8":  uint8(1),
		"uint16": uint16(1),
		"uint32": uint32(1),
		"uint64": uint64(1),

		"int_ptr":    &a,
		"int_ptrptr": &b,

		"bool":   true,
		"string": "asdf",

		"nil":       nil,
		"nilptr":    &_c,
		"nilptrptr": &d,

		"bad_map":       map[int]int{1: 2},
		"good_map":      map[string]int{"1": 2},
		"interface_map": map[string]interface{}{"abc": 123},

		"slice_interface": []interface{}{1, 2, true},
		"slice_typed":     []string{"a", "b", "c"},
		"slice_2d":        [][]string{{"a", "b"}, {"c", "d"}},
		"slice_pointer":   &[]interface{}{1, 2, true},
	}
	expected, _ := ParseAndEval(`
		{bad_map: () bool: #t float32: 1.1 float32int: 1 float64: 1.1 float64int: 1 good_map: {(intern "1:") 2} int16: 1 int32: 1 int64: 1 int8: 1 int: 1 int_ptr: 63 int_ptrptr: 63
		interface_map: {abc: 123} nil: () nilptr: () nilptrptr: () slice_2d: '(("a" "b") ("c" "d")) slice_interface: '(1 2 #t) slice_pointer: '(1 2 #t)
		slice_typed: '("a" "b" "c") string: "asdf" uint16: 1 uint32: 1 uint64: 1 uint8: 1 uint: 1}`)
	data := JsonToLispWithFrames(testMap)
	c.Assert(IsEqual(data, expected), Equals, true)
}

type imageSyncFrame struct {
	Image []interface{} `json:"image,omitempty"`
	Delay int           `json:",string"`
}

type imageSyncGif struct {
	Frames []imageSyncFrame `json:"frames"`
}

type imageSync struct {
	Gif     imageSyncGif `json:"gif_data"`
	Enabled int          `json:"enabled,string,omitempty"`
	Str     string       `json:",omitempty"`
	private string
	Inter   interface{} `json:"inter,omitempty"`
}

func (s *JsonLispSuite) TestJsonToLispWithFramesStruct(c *C) {
	jsonData := imageSync{
		private: "sss",
		Str:     "",
		Enabled: 6,
		Inter:   struct{}{},
		Gif: imageSyncGif{
			Frames: []imageSyncFrame{
				imageSyncFrame{
					Delay: 1,
					Image: []interface{}{1, 3, 4},
				},
				imageSyncFrame{
					Delay: 2,
					Image: []interface{}{},
				},
				imageSyncFrame{
					Delay: 3,
					Image: nil,
				},
			},
		},
	}
	data := JsonToLispWithFrames(jsonData)
	expected, _ := ParseAndEval(`{enabled: "6" gif_data: {frames: (list {Delay: "1" image: '(1 3 4)} {Delay: "2"} {Delay: "3"})} inter: {}}`)
	c.Assert(IsEqual(data, expected), Equals, true)
}

// func (s *JsonLispSuite) TestSimpleJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" 42 parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	c.Assert(IntegerValue(Cdr(newNode)), Equals, int64(42))
// }

// func (s *JsonLispSuite) TestSimpleJsonTransformationReturnsNewValue(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (+ node 5))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	newValue, err := TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	c.Assert(IntegerValue(newValue), Equals, int64(90))
// }

// func (s *JsonLispSuite) TestMoreComplexJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" '(1 2 3) parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	c.Assert(IsEqual(Cdr(newNode), InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), IntegerWithValue(3))), Equals, true)
// }

// func (s *JsonLispSuite) TestEvenMoreComplexJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" (acons "a" (+ node 1) nil) parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	var newerNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	newerNode, err = Assoc(StringWithValue("a"), Cdr(newNode))
// 	c.Assert(err, IsNil)
// 	c.Assert(IntegerValue(Cdr(newerNode)), Equals, int64(86))
// }
<commit_msg>update json tests to cover more cases<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file tests the Json<->Lisp support.

package golisp

import . "gopkg.in/check.v1"

type JsonLispSuite struct {
}

var _ = Suite(&JsonLispSuite{})

func (s *JsonLispSuite) TestJsonToLispMap(c *C) {
	jsonData := `{"map": 1}`
	sexpr := JsonStringToLisp(jsonData)
	expected := Acons(StringWithValue("map"), IntegerWithValue(1), nil)

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMapWithFrames(c *C) {
	jsonData := `{"map": 1}`
	sexpr := JsonStringToLispWithFrames(jsonData)
	expected, _ := ParseAndEval("{map: 1}")

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispArray(c *C) {
	jsonData := `[1, 2, "hi"]`
	sexpr := JsonStringToLisp(jsonData)
	expected := InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), StringWithValue("hi"))

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispBool(c *C) {
	jsonData := `{"map": true}`
	sexpr := JsonStringToLisp(jsonData)
	expected := Acons(StringWithValue("map"), BooleanWithValue(true), nil)

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMixed(c *C) {
	jsonData := `{"map": {"f1": [47, 75], "f2": 185}, "f3": 85}`
	sexpr := JsonStringToLisp(jsonData)

	expected := Acons(StringWithValue("map"),
		Acons(StringWithValue("f1"),
			InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)),
			Acons(StringWithValue("f2"),
				IntegerWithValue(185), nil)),
		Acons(StringWithValue("f3"),
			IntegerWithValue(85), nil))

	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispMixedWithFrames(c *C) {
	jsonData := `{"map": {"f1": [47, 75], "f2": 185}, "f3": 85, "f4": 2.2}`
	sexpr := JsonStringToLispWithFrames(jsonData)
	expected, _ := ParseAndEval("{map: {f1: '(47 75) f2: 185} f3: 85 f4: 2.2}")
	c.Assert(IsEqual(sexpr, expected), Equals, true)
}

func (s *JsonLispSuite) TestJsonToLispIllegal(c *C) {
	c.Assert(func() { JsonStringToLisp("hello") }, PanicMatches, `Badly formed json: 'hello'`)
}

func (s *JsonLispSuite) TestLispToJsonMap(c *C) {
	alist := Acons(StringWithValue("map"), IntegerWithValue(1), nil)
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `{"map":1}`)
}

func (s *JsonLispSuite) TestLispToJsonArray(c *C) {
	alist := InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), StringWithValue("hi"))
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `[1,2,"hi"]`)
}

func (s *JsonLispSuite) TestLispToJsonMixed(c *C) {
	alist := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))
	data := LispToJsonString(alist)
	c.Assert(data, Equals, `{"f3":85,"map":{"f1":[47,75],"f2":185}}`)
}

func (s *JsonLispSuite) TestLispWithFramesToJsonMixed(c *C) {
	structure, _ := ParseAndEval("{map: {f1: '(47 75) f2: 185} f3: 85 f4: 2.2}")
	data := LispWithFramesToJsonString(structure)
	c.Assert(data, Equals, `{"f3":85,"f4":2.2,"map":{"f1":[47,75],"f2":185}}`)
}

func (s *JsonLispSuite) TestLispToJsonNil(c *C) {
	data := LispToJsonString(nil)
	c.Assert(data, Equals, `""`)
}

type intType int

func (s *JsonLispSuite) TestJsonToLispWithFramesMap(c *C) {
	a := int(63)
	b := &a
	_c := (*int)(nil)
	d_p := (*int)(nil)
	d := &d_p
	testMap := map[string]interface{}{
		"float64":    float64(1.1),
		"float64int": float64(1.0),
		"float32":    float32(1.1),
		"float32int": float32(1.0),

		"int":     int(1),
		"int8":    int8(1),
		"int16":   int16(1),
		"int32":   int32(1),
		"int64":   int64(1),
		"uint":    uint(1),
		"uint8":   uint8(1),
		"uint16":  uint16(1),
		"uint32":  uint32(1),
		"uint64":  uint64(1),
		"uintptr": uintptr(1),

		"int_ptr":    &a,
		"int_ptrptr": &b,

		"int_type": intType(1),

		"bool":   true,
		"string": "asdf",

		"nil":       nil,
		"nilptr":    &_c,
		"nilptrptr": &d,

		"bad_map":       map[int]int{1: 2},
		"good_map":      map[string]int{"1": 2},
		"interface_map": map[string]interface{}{"abc": 123},

		"slice_interface": []interface{}{1, 2, true},
		"slice_typed":     []string{"a", "b", "c"},
		"slice_2d":        [][]string{{"a", "b"}, {"c", "d"}},
		"slice_pointer":   &[]interface{}{1, 2, true},
	}
	expected, _ := ParseAndEval(`
		{bad_map: () bool: #t float32: 1.1 float32int: 1 float64: 1.1 float64int: 1 good_map: {(intern "1:") 2} int16: 1 int32: 1 int64: 1 int8: 1 int: 1 int_ptr: 63 int_ptrptr: 63
		int_type: 1 interface_map: {abc: 123} nil: () nilptr: () nilptrptr: () slice_2d: '(("a" "b") ("c" "d")) slice_interface: '(1 2 #t) slice_pointer: '(1 2 #t)
		slice_typed: '("a" "b" "c") string: "asdf" uint16: 1 uint32: 1 uint64: 1 uint8: 1 uint: 1 uintptr: 1}`)
	data := JsonToLispWithFrames(testMap)
	c.Assert(IsEqual(data, expected), Equals, true)
}

type imageSyncFrame struct {
	Image []interface{} `json:"image,omitempty"`
	Delay int           `json:",string"`
}

type imageSyncGif struct {
	Frames []imageSyncFrame `json:"frames"`
}

type imageSync struct {
	Gif      imageSyncGif `json:"gif_data"`
	Enabled  int          `json:"enabled,string,omitempty"`
	Str      string       `json:",omitempty"`
	private  string
	Inter    interface{}   `json:"inter,omitempty"`
	NilSlice []interface{} `json:"nilslice"`
}

func (s *JsonLispSuite) TestJsonToLispWithFramesStruct(c *C) {
	jsonData := imageSync{
		private:  "sss",
		Str:      "",
		Enabled:  6,
		Inter:    struct{}{},
		NilSlice: nil,
		Gif: imageSyncGif{
			Frames: []imageSyncFrame{
				imageSyncFrame{
					Delay: 1,
					Image: []interface{}{1, 3, 4},
				},
				imageSyncFrame{
					Delay: 2,
					Image: []interface{}{},
				},
				imageSyncFrame{
					Delay: 3,
					Image: nil,
				},
			},
		},
	}
	data := JsonToLispWithFrames(jsonData)
	expected, _ := ParseAndEval(`{enabled: "6" gif_data: {frames: (list {Delay: "1" image: '(1 3 4)} {Delay: "2"} {Delay: "3"})} inter: {} nilslice: ()}`)
	c.Assert(IsEqual(data, expected), Equals, true)
}

// func (s *JsonLispSuite) TestSimpleJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" 42 parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	c.Assert(IntegerValue(Cdr(newNode)), Equals, int64(42))
// }

// func (s *JsonLispSuite) TestSimpleJsonTransformationReturnsNewValue(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (+ node 5))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	newValue, err := TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	c.Assert(IntegerValue(newValue), Equals, int64(90))
// }

// func (s *JsonLispSuite) TestMoreComplexJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" '(1 2 3) parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	c.Assert(IsEqual(Cdr(newNode), InternalMakeList(IntegerWithValue(1), IntegerWithValue(2), IntegerWithValue(3))), Equals, true)
// }

// func (s *JsonLispSuite) TestEvenMoreComplexJsonTransformation(c *C) {
// 	jsonData := Acons(StringWithValue("map"), Acons(StringWithValue("f1"), InternalMakeList(IntegerWithValue(47), IntegerWithValue(75)), Acons(StringWithValue("f2"), IntegerWithValue(185), nil)), Acons(StringWithValue("f3"), IntegerWithValue(85), nil))

// 	xform, err := Parse(`(lambda (node parent) (acons "f3" (acons "a" (+ node 1) nil) parent))`)
// 	c.Assert(err, IsNil)
// 	parent := jsonData
// 	pair, _ := Assoc(StringWithValue("f3"), jsonData)
// 	_, err = TransformJson(xform, Cdr(pair), parent)
// 	c.Assert(err, IsNil)

// 	var newNode *Data
// 	var newerNode *Data
// 	newNode, err = Assoc(StringWithValue("f3"), jsonData)
// 	c.Assert(err, IsNil)
// 	newerNode, err = Assoc(StringWithValue("a"), Cdr(newNode))
// 	c.Assert(err, IsNil)
// 	c.Assert(IntegerValue(Cdr(newerNode)), Equals, int64(86))
// }
<|endoftext|>