<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"regexp"
	"strings"
	"sync"
)

// ScalewayCache is used not to query the API to resolve full identifiers
type ScalewayCache struct {
	// Images contains names of Scaleway images indexed by identifier
	Images map[string]string `json:"images"`

	// Snapshots contains names of Scaleway snapshots indexed by identifier
	Snapshots map[string]string `json:"snapshots"`

	// Bootscripts contains names of Scaleway bootscripts indexed by identifier
	Bootscripts map[string]string `json:"bootscripts"`

	// Servers contains names of Scaleway C1 servers indexed by identifier
	Servers map[string]string `json:"servers"`

	// Path is the path to the cache file
	Path string `json:"-"`

	// Modified tells if the cache needs to be overwritten or not
	Modified bool `json:"-"`

	// Lock allows ScalewayCache to be used concurrently
	Lock sync.Mutex `json:"-"`
}

const (
	IDENTIFIER_SERVER = iota
	IDENTIFIER_IMAGE
	IDENTIFIER_SNAPSHOT
	IDENTIFIER_BOOTSCRIPT
)

// ScalewayIdentifier is a unique identifier on Scaleway
type ScalewayIdentifier struct {
	// Identifier is a unique identifier on
	Identifier string

	// Type of the identifier
	Type int
}

// NewScalewayCache loads a per-user cache
func NewScalewayCache() (*ScalewayCache, error) {
	u, err := user.Current()
	if err != nil {
		return nil, err
	}
	cache_path := fmt.Sprintf("%s/.scw-cache.db", u.HomeDir)
	_, err = os.Stat(cache_path)
	if os.IsNotExist(err) {
		return &ScalewayCache{
			Images:      make(map[string]string),
			Snapshots:   make(map[string]string),
			Bootscripts: make(map[string]string),
			Servers:     make(map[string]string),
			Path:        cache_path,
		}, nil
	} else if err != nil {
		return nil, err
	}
	file, err := ioutil.ReadFile(cache_path)
	if err != nil {
		return nil, err
	}
	var cache ScalewayCache
	cache.Path = cache_path
	err = json.Unmarshal(file, &cache)
	if err != nil {
		return nil, err
	}
	if cache.Images == nil {
		cache.Images = make(map[string]string)
	}
	if cache.Snapshots == nil {
		cache.Snapshots = make(map[string]string)
	}
	if cache.Servers == nil {
		cache.Servers = make(map[string]string)
	}
	if cache.Bootscripts == nil {
		cache.Bootscripts = make(map[string]string)
	}
	return &cache, nil
}

// Save atomically overwrites the current cache database
func (c *ScalewayCache) Save() error {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	if c.Modified {
		file, err := ioutil.TempFile("", "")
		if err != nil {
			return err
		}
		encoder := json.NewEncoder(file)
		err = encoder.Encode(*c)
		if err != nil {
			return err
		}
		return os.Rename(file.Name(), c.Path)
	}
	return nil
}

// LookupImages attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpImages(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Images {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupSnapshots attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpSnapshots(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Snapshots {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupBootscripts attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpBootscripts(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Bootscripts {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupServers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpServers(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Servers {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupIdentifier attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpIdentifiers(needle string) []ScalewayIdentifier {
	result := []ScalewayIdentifier{}

	for _, identifier := range c.LookUpServers(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_SERVER,
		})
	}

	for _, identifier := range c.LookUpImages(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_IMAGE,
		})
	}

	for _, identifier := range c.LookUpSnapshots(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_SNAPSHOT,
		})
	}

	for _, identifier := range c.LookUpBootscripts(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_BOOTSCRIPT,
		})
	}

	return result
}

// InsertServer registers a server in the cache
func (c *ScalewayCache) InsertServer(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Servers[identifier]
	if !exists || current_name != name {
		c.Servers[identifier] = name
		c.Modified = true
	}
}

// InsertImage registers an image in the cache
func (c *ScalewayCache) InsertImage(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Images[identifier]
	if !exists || current_name != name {
		c.Images[identifier] = name
		c.Modified = true
	}
}

// InsertSnapshot registers an snapshot in the cache
func (c *ScalewayCache) InsertSnapshot(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Snapshots[identifier]
	if !exists || current_name != name {
		c.Snapshots[identifier] = name
		c.Modified = true
	}
}

// InsertBootscript registers an bootscript in the cache
func (c *ScalewayCache) InsertBootscript(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Bootscripts[identifier]
	if !exists || current_name != name {
		c.Bootscripts[identifier] = name
		c.Modified = true
	}
}

// GetNbServers returns the number of servers in the cache
func (c *ScalewayCache) GetNbServers() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Servers)
}

// GetNbImages returns the number of images in the cache
func (c *ScalewayCache) GetNbImages() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Images)
}

// GetNbSnapshots returns the number of snapshots in the cache
func (c *ScalewayCache) GetNbSnapshots() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Snapshots)
}

// GetNbBootscripts returns the number of bootscripts in the cache
func (c *ScalewayCache) GetNbBootscripts() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Bootscripts)
}
<commit_msg>Automatically remove 'user/' from needle when resolving cache<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"regexp"
	"strings"
	"sync"
)

// ScalewayCache is used not to query the API to resolve full identifiers
type ScalewayCache struct {
	// Images contains names of Scaleway images indexed by identifier
	Images map[string]string `json:"images"`

	// Snapshots contains names of Scaleway snapshots indexed by identifier
	Snapshots map[string]string `json:"snapshots"`

	// Bootscripts contains names of Scaleway bootscripts indexed by identifier
	Bootscripts map[string]string `json:"bootscripts"`

	// Servers contains names of Scaleway C1 servers indexed by identifier
	Servers map[string]string `json:"servers"`

	// Path is the path to the cache file
	Path string `json:"-"`

	// Modified tells if the cache needs to be overwritten or not
	Modified bool `json:"-"`

	// Lock allows ScalewayCache to be used concurrently
	Lock sync.Mutex `json:"-"`
}

const (
	IDENTIFIER_SERVER = iota
	IDENTIFIER_IMAGE
	IDENTIFIER_SNAPSHOT
	IDENTIFIER_BOOTSCRIPT
)

// ScalewayIdentifier is a unique identifier on Scaleway
type ScalewayIdentifier struct {
	// Identifier is a unique identifier on
	Identifier string

	// Type of the identifier
	Type int
}

// NewScalewayCache loads a per-user cache
func NewScalewayCache() (*ScalewayCache, error) {
	u, err := user.Current()
	if err != nil {
		return nil, err
	}
	cache_path := fmt.Sprintf("%s/.scw-cache.db", u.HomeDir)
	_, err = os.Stat(cache_path)
	if os.IsNotExist(err) {
		return &ScalewayCache{
			Images:      make(map[string]string),
			Snapshots:   make(map[string]string),
			Bootscripts: make(map[string]string),
			Servers:     make(map[string]string),
			Path:        cache_path,
		}, nil
	} else if err != nil {
		return nil, err
	}
	file, err := ioutil.ReadFile(cache_path)
	if err != nil {
		return nil, err
	}
	var cache ScalewayCache
	cache.Path = cache_path
	err = json.Unmarshal(file, &cache)
	if err != nil {
		return nil, err
	}
	if cache.Images == nil {
		cache.Images = make(map[string]string)
	}
	if cache.Snapshots == nil {
		cache.Snapshots = make(map[string]string)
	}
	if cache.Servers == nil {
		cache.Servers = make(map[string]string)
	}
	if cache.Bootscripts == nil {
		cache.Bootscripts = make(map[string]string)
	}
	return &cache, nil
}

// Save atomically overwrites the current cache database
func (c *ScalewayCache) Save() error {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	if c.Modified {
		file, err := ioutil.TempFile("", "")
		if err != nil {
			return err
		}
		encoder := json.NewEncoder(file)
		err = encoder.Encode(*c)
		if err != nil {
			return err
		}
		return os.Rename(file.Name(), c.Path)
	}
	return nil
}

// LookupImages attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpImages(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
	// FIXME: if 'user/' is in needle, only watch for a user image
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Images {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupSnapshots attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpSnapshots(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Snapshots {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupBootscripts attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpBootscripts(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Bootscripts {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupServers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpServers(needle string) []string {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res []string
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, name := range c.Servers {
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(name) {
			res = append(res, identifier)
		}
	}
	return res
}

// LookupIdentifier attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpIdentifiers(needle string) []ScalewayIdentifier {
	result := []ScalewayIdentifier{}

	for _, identifier := range c.LookUpServers(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_SERVER,
		})
	}

	for _, identifier := range c.LookUpImages(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_IMAGE,
		})
	}

	for _, identifier := range c.LookUpSnapshots(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_SNAPSHOT,
		})
	}

	for _, identifier := range c.LookUpBootscripts(needle) {
		result = append(result, ScalewayIdentifier{
			Identifier: identifier,
			Type:       IDENTIFIER_BOOTSCRIPT,
		})
	}

	return result
}

// InsertServer registers a server in the cache
func (c *ScalewayCache) InsertServer(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Servers[identifier]
	if !exists || current_name != name {
		c.Servers[identifier] = name
		c.Modified = true
	}
}

// InsertImage registers an image in the cache
func (c *ScalewayCache) InsertImage(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Images[identifier]
	if !exists || current_name != name {
		c.Images[identifier] = name
		c.Modified = true
	}
}

// InsertSnapshot registers an snapshot in the cache
func (c *ScalewayCache) InsertSnapshot(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Snapshots[identifier]
	if !exists || current_name != name {
		c.Snapshots[identifier] = name
		c.Modified = true
	}
}

// InsertBootscript registers an bootscript in the cache
func (c *ScalewayCache) InsertBootscript(identifier, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	current_name, exists := c.Bootscripts[identifier]
	if !exists || current_name != name {
		c.Bootscripts[identifier] = name
		c.Modified = true
	}
}

// GetNbServers returns the number of servers in the cache
func (c *ScalewayCache) GetNbServers() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Servers)
}

// GetNbImages returns the number of images in the cache
func (c *ScalewayCache) GetNbImages() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Images)
}

// GetNbSnapshots returns the number of snapshots in the cache
func (c *ScalewayCache) GetNbSnapshots() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Snapshots)
}

// GetNbBootscripts returns the number of bootscripts in the cache
func (c *ScalewayCache) GetNbBootscripts() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Bootscripts)
}
<|endoftext|>


<commit_before>package url

import (
	"bytes"
	"fmt"
	"github.com/viant/toolbox/cred"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/storage"
)

//Resource represents a URL based resource, with enriched meta info
type Resource struct {
	URL           string   //URL of resource
	Credential    string   //name of file or alias to the file defined via credential service
	ParsedURL     *url.URL //parsed URL resource
	Cache         string   //Cache path for the resource, if specified resource will be cached in the specified path
	CacheExpiryMs int      //CacheExpiryMs expiry time in ms

	Name          string   //name of a resource
	Version       string   //version of resource
	Type          string   //resource type
}

//Clone creates a clone of the resource
func (r *Resource) Clone() *Resource {
	return &Resource{
		Name:          r.Name,
		Version:       r.Version,
		URL:           r.URL,
		Type:          r.Type,
		Credential:    r.Credential,
		ParsedURL:     r.ParsedURL,
		Cache:         r.Cache,
		CacheExpiryMs: r.CacheExpiryMs,
	}
}

var defaultSchemePorts = map[string]int{
	"ssh":   22,
	"http":  80,
	"https": 443,
}

//Host returns url's host name with user name if user name is part of url
func (r *Resource) Host() string {
	result := r.ParsedURL.Hostname() + ":" + r.Port()
	if r.ParsedURL.User != nil {
		result = r.ParsedURL.User.Username() + "@" + result
	}
	return result
}

//Port returns url's port
func (r *Resource) Port() string {
	port := r.ParsedURL.Port()
	if port == "" && r.ParsedURL != nil {
		if value, ok := defaultSchemePorts[r.ParsedURL.Scheme]; ok {
			port = toolbox.AsString(value)
		}
	}
	return port
}

//LoadCredentialload credential, returns username, password. It takes errorIfEmpty flag to return an error if there is issue with credential
func (r *Resource) LoadCredential(errorIfEmpty bool) (string, string, error) {
	if r.Credential == "" {
		if errorIfEmpty {
			return "", "", fmt.Errorf("Credential was empty: %v", r.Credential)
		}
		return "", "", nil
	}
	credential, err := cred.NewConfig(r.Credential)
	if err != nil {
		return "", "", fmt.Errorf("Failed to load credential: %v %v", r.Credential, err)
	}
	return credential.Username, credential.Password, nil
}

//Download downloads data from URL, it returns data as []byte, or error, if resource is cacheable it first look into cache
func (r *Resource) Download() ([]byte, error) {
	if r == nil {
		return nil, fmt.Errorf("Fail to download content on empty resource")
	}

	if r.Cachable() {
		content := r.readFromCache()
		if content != nil {
			return content, nil
		}
	}

	service, err := storage.NewServiceForURL(r.URL, r.Credential)
	if err != nil {
		return nil, err
	}
	object, err := service.StorageObject(r.URL)
	if err != nil {
		return nil, err
	}
	reader, err := service.Download(object)
	if err != nil {
		return nil, err
	}
	content, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	if r.Cachable() {
		_ = ioutil.WriteFile(r.Cache, content, 0666)
	}
	return content, err
}

//DownloadText returns a text downloaded from url
func (r *Resource) DownloadText() (string, error) {
	var result, err = r.Download()
	if err != nil {
		return "", err
	}
	return string(result), err
}

//Decode decodes url's data into target, it takes decoderFactory which decodes data into target
func (r *Resource) Decode(target interface{}, decoderFactory toolbox.DecoderFactory) error {
	if r == nil {
		return fmt.Errorf("Fail to %T decode on empty resource",decoderFactory)
	}
	if r == nil {
		return fmt.Errorf("Fail to decode %v,  decoderFactory was empty", r.URL, decoderFactory)
	}
	var content, err= r.Download()
	if err != nil {
		return err
	}
	return decoderFactory.Create(bytes.NewReader(content)).Decode(target)
}

//JSONDecode decodes json resource into target
func (r *Resource) JSONDecode(target interface{}) error {
	return r.Decode(target, toolbox.NewJSONDecoderFactory())
}

//JSONDecode decodes yaml resource into target
func (r *Resource) YAMLDecode(target interface{}) error {
	return r.Decode(target, toolbox.NewYamlDecoderFactory())
}

func (r *Resource) readFromCache() []byte {
	if toolbox.FileExists(r.Cache) {
		info, err := os.Stat(r.Cache)
		var isExpired = false
		if err == nil && r.CacheExpiryMs > 0 {
			elapsed := time.Now().Sub(info.ModTime())
			isExpired = elapsed > time.Second*time.Duration(r.CacheExpiryMs)
		}
		content, err := ioutil.ReadFile(r.Cache)
		if err == nil && !isExpired {
			return content
		}
	}
	return nil
}

//Cachable returns true if resource is cachable
func (r *Resource) Cachable() bool {
	return r.Cache != ""
}

func normalizeURL(URL string) string {
	if strings.Contains(URL, "://") {
		return URL
	}
	if !strings.HasPrefix(URL, "/") {
		currentDirectory, err := os.Getwd()
		if err == nil {
			candidate := path.Join(currentDirectory, URL)
			if toolbox.FileExists(candidate) {
				URL = candidate
			}
		}
	}
	return toolbox.FileSchema + URL
}

//NewResource returns a new resource for provided URL, followed by optional credential, cache and cache expiryMs.
func NewResource(Params ... interface{}) *Resource {
	if len(Params) == 0{
		return nil
	}
	var URL = toolbox.AsString(Params[0])
	URL = normalizeURL(URL)

	var credential string
	if len(Params) > 1 {
		credential = toolbox.AsString(Params[1])
	}
	var cache string
	if len(Params) > 2 {
		cache = toolbox.AsString(Params[2])
	}
	var cacheExpiryMs int
	if len(Params) > 3 {
		cacheExpiryMs = toolbox.AsInt(Params[3])
	}
	parsedURL, _ := url.Parse(URL)
	return &Resource{
		ParsedURL: parsedURL,
		URL:       URL,
		Credential:credential,
		Cache:cache,
		CacheExpiryMs:cacheExpiryMs,
	}
}<commit_msg>reformated<commit_after>package url

import (
	"bytes"
	"fmt"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/cred"
	"github.com/viant/toolbox/storage"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

//Resource represents a URL based resource, with enriched meta info
type Resource struct {
	URL           string   //URL of resource
	Credential    string   //name of file or alias to the file defined via credential service
	ParsedURL     *url.URL //parsed URL resource
	Cache         string   //Cache path for the resource, if specified resource will be cached in the specified path
	CacheExpiryMs int      //CacheExpiryMs expiry time in ms

	Name    string //name of a resource
	Version string //version of resource
	Type    string //resource type
}

//Clone creates a clone of the resource
func (r *Resource) Clone() *Resource {
	return &Resource{
		Name:          r.Name,
		Version:       r.Version,
		URL:           r.URL,
		Type:          r.Type,
		Credential:    r.Credential,
		ParsedURL:     r.ParsedURL,
		Cache:         r.Cache,
		CacheExpiryMs: r.CacheExpiryMs,
	}
}

var defaultSchemePorts = map[string]int{
	"ssh":   22,
	"http":  80,
	"https": 443,
}

//Host returns url's host name with user name if user name is part of url
func (r *Resource) Host() string {
	result := r.ParsedURL.Hostname() + ":" + r.Port()
	if r.ParsedURL.User != nil {
		result = r.ParsedURL.User.Username() + "@" + result
	}
	return result
}

//Port returns url's port
func (r *Resource) Port() string {
	port := r.ParsedURL.Port()
	if port == "" && r.ParsedURL != nil {
		if value, ok := defaultSchemePorts[r.ParsedURL.Scheme]; ok {
			port = toolbox.AsString(value)
		}
	}
	return port
}

//LoadCredentialload credential, returns username, password. It takes errorIfEmpty flag to return an error if there is issue with credential
func (r *Resource) LoadCredential(errorIfEmpty bool) (string, string, error) {
	if r.Credential == "" {
		if errorIfEmpty {
			return "", "", fmt.Errorf("Credential was empty: %v", r.Credential)
		}
		return "", "", nil
	}
	credential, err := cred.NewConfig(r.Credential)
	if err != nil {
		return "", "", fmt.Errorf("Failed to load credential: %v %v", r.Credential, err)
	}
	return credential.Username, credential.Password, nil
}

//Download downloads data from URL, it returns data as []byte, or error, if resource is cacheable it first look into cache
func (r *Resource) Download() ([]byte, error) {
	if r == nil {
		return nil, fmt.Errorf("Fail to download content on empty resource")
	}

	if r.Cachable() {
		content := r.readFromCache()
		if content != nil {
			return content, nil
		}
	}

	service, err := storage.NewServiceForURL(r.URL, r.Credential)
	if err != nil {
		return nil, err
	}
	object, err := service.StorageObject(r.URL)
	if err != nil {
		return nil, err
	}
	reader, err := service.Download(object)
	if err != nil {
		return nil, err
	}
	content, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	if r.Cachable() {
		_ = ioutil.WriteFile(r.Cache, content, 0666)
	}
	return content, err
}

//DownloadText returns a text downloaded from url
func (r *Resource) DownloadText() (string, error) {
	var result, err = r.Download()
	if err != nil {
		return "", err
	}
	return string(result), err
}

//Decode decodes url's data into target, it takes decoderFactory which decodes data into target
func (r *Resource) Decode(target interface{}, decoderFactory toolbox.DecoderFactory) error {
	if r == nil {
		return fmt.Errorf("Fail to %T decode on empty resource", decoderFactory)
	}
	if r == nil {
		return fmt.Errorf("Fail to decode %v,  decoderFactory was empty", r.URL, decoderFactory)
	}
	var content, err = r.Download()
	if err != nil {
		return err
	}
	return decoderFactory.Create(bytes.NewReader(content)).Decode(target)
}

//JSONDecode decodes json resource into target
func (r *Resource) JSONDecode(target interface{}) error {
	return r.Decode(target, toolbox.NewJSONDecoderFactory())
}

//JSONDecode decodes yaml resource into target
func (r *Resource) YAMLDecode(target interface{}) error {
	return r.Decode(target, toolbox.NewYamlDecoderFactory())
}

func (r *Resource) readFromCache() []byte {
	if toolbox.FileExists(r.Cache) {
		info, err := os.Stat(r.Cache)
		var isExpired = false
		if err == nil && r.CacheExpiryMs > 0 {
			elapsed := time.Now().Sub(info.ModTime())
			isExpired = elapsed > time.Second*time.Duration(r.CacheExpiryMs)
		}
		content, err := ioutil.ReadFile(r.Cache)
		if err == nil && !isExpired {
			return content
		}
	}
	return nil
}

//Cachable returns true if resource is cachable
func (r *Resource) Cachable() bool {
	return r.Cache != ""
}

func normalizeURL(URL string) string {
	if strings.Contains(URL, "://") {
		return URL
	}
	if !strings.HasPrefix(URL, "/") {
		currentDirectory, err := os.Getwd()
		if err == nil {
			candidate := path.Join(currentDirectory, URL)
			if toolbox.FileExists(candidate) {
				URL = candidate
			}
		}
	}
	return toolbox.FileSchema + URL
}

//NewResource returns a new resource for provided URL, followed by optional credential, cache and cache expiryMs.
func NewResource(Params ...interface{}) *Resource {
	if len(Params) == 0 {
		return nil
	}
	var URL = toolbox.AsString(Params[0])
	URL = normalizeURL(URL)

	var credential string
	if len(Params) > 1 {
		credential = toolbox.AsString(Params[1])
	}
	var cache string
	if len(Params) > 2 {
		cache = toolbox.AsString(Params[2])
	}
	var cacheExpiryMs int
	if len(Params) > 3 {
		cacheExpiryMs = toolbox.AsInt(Params[3])
	}
	parsedURL, _ := url.Parse(URL)
	return &Resource{
		ParsedURL:     parsedURL,
		URL:           URL,
		Credential:    credential,
		Cache:         cache,
		CacheExpiryMs: cacheExpiryMs,
	}
}
<|endoftext|>


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

package main

import (
	"io"
	"log"
	"sort"
	"time"
)

type page struct {
	n      offset
	body   []byte
	expire time.Time
	cached bool
}

func newPage(n offset, body []byte) *page {
	return &page{
		n:    n,
		body: body,
	}
}

func (p *page) WriteTo(w io.Writer) (int64, error) {
	n, err := w.Write(p.body)
	return int64(n), err
}

type group string

type entry struct {
	deadline time.Time
	data     []byte
}

func newEntry(data []byte, d time.Duration) *entry {
	return &entry{
		deadline: time.Now().Add(d),
		data:     data,
	}
}

func (ce *entry) invalid(t time.Time) bool {
	return !ce.deadline.After(t)
}

func (ce *entry) asPage(n offset) *page {
	p := newPage(n, ce.data)
	p.expire = ce.deadline
	return p
}

type entries struct {
	ents map[group]map[offset]*entry
}

func newEntries() *entries {
	return &entries{
		ents: make(map[group]map[offset]*entry),
	}
}

func (e *entries) count() int {
	tot := 0
	for k := range e.ents {
		tot += len(e.ents[k])
	}
	return tot
}

func (e *entries) get(cg group, n offset) (*entry, bool) {
	pages, ok := e.ents[cg]
	if !ok {
		return nil, false
	}
	ce, ok := pages[n]
	return ce, ok
}

func (e *entries) put(cg group, n offset, ce *entry) {
	_, ok := e.ents[cg]
	if !ok {
		e.ents[cg] = make(map[offset]*entry)
	}
	e.ents[cg][n] = ce
}

func (e *entries) has(cg group, n offset) bool {
	_, ok := e.ents[cg]
	if !ok {
		return false
	}
	_, ok = e.ents[cg][n]
	return ok
}

// remove assumes the entry exists
func (e *entries) remove(cg group, n offset) {
	delete(e.ents[cg], n)
	if len(e.ents[cg]) == 0 {
		delete(e.ents, cg)
	}
}

func (e *entries) purge(cg group, st *stats) {
	st.mem(-e.sizeof(cg))
	delete(e.ents, cg)
}

func (e *entries) sizeof(cg group) int {
	var s int
	for n := range e.ents[cg] {
		s += len(e.ents[cg][n].data)
	}
	return s
}

func (e *entries) oldestDeadline(cg group) time.Time {
	var t time.Time
	for off := range e.ents[cg] {
		entry := e.ents[cg][off]
		if t.IsZero() || t.After(entry.deadline) {
			t = entry.deadline
		}
	}
	return t
}

func (e *entries) gc(t time.Time, st *stats) {
	for cg, ents := range e.ents {
		for n := range ents {
			if ents[n].invalid(t) {
				st.mem(-len(ents[n].data))
				e.remove(cg, n)
			}
		}
	}
}

type cacheFunc func() error

type cache struct {
	entries *entries
	waits   *waiters
	fetcher *fetcher
	config  *config
	stat    *stats
	events  chan cacheFunc
}

func newCache(f *fetcher, cf *config) *cache {
	c := &cache{
		fetcher: f,
		config:  cf,
		events:  make(chan cacheFunc),
		entries: newEntries(),
		waits:   newWaiters(),
		stat:    newStats(),
	}
	go c.gc(cf.gcpause)
	go c.run()
	return c
}

func (c *cache) run() {
	for f := range c.events {
		if err := f(); err != nil {
			log.Print("cache: ", err)
		}
	}
}

func (c *cache) gc(d time.Duration) {
	done := make(chan struct{})
	for {
		time.Sleep(d)
		c.events <- func() error {
			debug("running garbage collector cycle, memory is %d", c.stat.Mem)
			c.entries.gc(time.Now(), c.stat)
			debug("garbage collection done, memory is %d", c.stat.Mem)
			done <- struct{}{}
			return nil
		}
		<-done
	}
}

type timeGroup struct {
	t  time.Time
	cg group
}

type timeGroups struct {
	entries []timeGroup
}

type byTime []timeGroup

func (a byTime) Len() int           { return len(a) }
func (a byTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byTime) Less(i, j int) bool { return a[i].t.After(a[j].t) }

func makeTimeGroups(e *entries) timeGroups {
	var i int
	tg := make([]timeGroup, len(e.ents))
	for cg := range e.ents {
		tg[i] = timeGroup{
			t:  e.oldestDeadline(cg),
			cg: cg,
		}
		i++
	}
	sort.Sort(byTime(tg))
	return timeGroups{tg}
}

func (tg *timeGroups) purgeOldest(c *cache) {
	entry, ts := tg.entries[len(tg.entries)-1], tg.entries[0:len(tg.entries)-1]
	c.entries.purge(entry.cg, c.stat)
	tg.entries = ts
}

func (tg *timeGroups) empty() bool {
	return len(tg.entries) == 0
}

func (c *cache) oom(target int64) {
	debug("OOM called: using %d, limit is %d", c.stat.Mem, target)
	tg := makeTimeGroups(c.entries)
	for {
		tg.purgeOldest(c)
		// Repeat unless we have no more entries or we are using less memory than target
		if tg.empty() || !c.stat.above(target) {
			break
		}
	}
	debug("OOM: mem now %d", c.stat.Mem)
}

// put inserts a page into the cache (after it was fetched).
func (c *cache) put(cg group, p *page, err error) {
	c.events <- func() error {
		ce := newEntry(p.body, c.config.lifetime)
		c.entries.put(cg, p.n, ce)
		debug("added page %s/%d", cg, p.n)
		c.stat.mem(len(p.body))
		if c.stat.above(c.config.maxMemory) {
			go func() {
				c.events <- func() error {
					c.oom(c.config.maxMemory)
					return nil
				}
			}()
		}
		// If there were waiters, signal that the wait is over
		c.waits.done(cg, p.n)
		return err
	}
}

// prefetch requests pages (n-m, n) and (n, n+m) if not already fetched
func (c *cache) prefetch(cg group, n, m int) {
	i := n - m
	if i < 0 {
		i = 0
	}
	for ; i < n+m; i++ {
		if i == n {
			// just fetched
			continue
		}
		off := offset(i * c.config.incr)
		if c.entries.has(cg, off) || c.waits.has(cg, off) {
			// already fetched or requested
			continue
		}
		c.waits.wait(cg, off)
		res := newResource(c.config.tmpl, cg, off)
		c.fetcher.request(newJob(res, c))
	}
}

func (c *cache) request(cg group, n int) chan struct{} {
	off := offset(n * c.config.incr)
	wait := c.waits.wait(cg, off)
	res := newResource(c.config.tmpl, cg, off)
	c.fetcher.request(newJob(res, c))
	c.prefetch(cg, n, c.config.npref)
	return wait
}

func (c *cache) stats() *stats {
	var st *stats
	wait := make(chan struct{})
	c.events <- func() error {
		c.stat.Entries = c.entries.count()
		c.stat.Waiters = c.waits.count()
		st = c.stat.clone()
		wait <- struct{}{}
		return nil
	}
	<-wait
	return st
}

func (c *cache) get(cg group, n int) *page {
	var (
		page *page
		wait chan struct{}
	)
	cached := true
	requested := make(chan struct{})
	off := offset(n * c.config.incr)
	debug("%s/%d: requesting from cache", cg, off)
	for {
		wait = nil
		c.events <- func() error {
			defer func() { requested <- struct{}{} }()
			var now time.Time
			ce, ok := c.entries.get(cg, off)
			if ok {
				now = time.Now()
			}
			if !ok || ce.invalid(now) {
				debug("%s/%d: not cached, requested", cg, off)
				wait = c.request(cg, n)
				return nil
			}
			debug("%s/%d: found", cg, off)
			c.prefetch(cg, n, c.config.npref)
			c.stat.hit(cached)
			page = ce.asPage(off)
			return nil
		}
		<-requested
		// content was already in cache, return it
		if wait == nil {
			page.cached = cached
			return page
		}
		// We needed to request the object, it was not cached
		cached = false
		// content is being fetched, wait and try to get again
		<-wait
	}
}
<commit_msg>cache: prefetch refetches data that is expired<commit_after>// Copyright 2017 Giulio Iotti. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"io"
	"log"
	"sort"
	"time"
)

type page struct {
	n      offset
	body   []byte
	expire time.Time
	cached bool
}

func newPage(n offset, body []byte) *page {
	return &page{
		n:    n,
		body: body,
	}
}

func (p *page) WriteTo(w io.Writer) (int64, error) {
	n, err := w.Write(p.body)
	return int64(n), err
}

type group string

type entry struct {
	deadline time.Time
	data     []byte
}

func newEntry(data []byte, d time.Duration) *entry {
	return &entry{
		deadline: time.Now().Add(d),
		data:     data,
	}
}

func (ce *entry) invalid(t time.Time) bool {
	return !ce.deadline.After(t)
}

func (ce *entry) asPage(n offset) *page {
	p := newPage(n, ce.data)
	p.expire = ce.deadline
	return p
}

type entries struct {
	ents map[group]map[offset]*entry
}

func newEntries() *entries {
	return &entries{
		ents: make(map[group]map[offset]*entry),
	}
}

func (e *entries) count() int {
	tot := 0
	for k := range e.ents {
		tot += len(e.ents[k])
	}
	return tot
}

func (e *entries) get(cg group, n offset) (*entry, bool) {
	pages, ok := e.ents[cg]
	if !ok {
		return nil, false
	}
	ce, ok := pages[n]
	return ce, ok
}

func (e *entries) put(cg group, n offset, ce *entry) {
	_, ok := e.ents[cg]
	if !ok {
		e.ents[cg] = make(map[offset]*entry)
	}
	e.ents[cg][n] = ce
}

func (e *entries) has(cg group, n offset, t time.Time) bool {
	_, ok := e.ents[cg]
	if !ok {
		return false
	}
	ent, ok := e.ents[cg][n]
	if !ok {
		return false
	}
	return !ent.invalid(t)
}

// remove assumes the entry exists
func (e *entries) remove(cg group, n offset) {
	delete(e.ents[cg], n)
	if len(e.ents[cg]) == 0 {
		delete(e.ents, cg)
	}
}

func (e *entries) purge(cg group, st *stats) {
	st.mem(-e.sizeof(cg))
	delete(e.ents, cg)
}

func (e *entries) sizeof(cg group) int {
	var s int
	for n := range e.ents[cg] {
		s += len(e.ents[cg][n].data)
	}
	return s
}

func (e *entries) oldestDeadline(cg group) time.Time {
	var t time.Time
	for off := range e.ents[cg] {
		entry := e.ents[cg][off]
		if t.IsZero() || t.After(entry.deadline) {
			t = entry.deadline
		}
	}
	return t
}

func (e *entries) gc(t time.Time, st *stats) {
	for cg, ents := range e.ents {
		for n := range ents {
			if ents[n].invalid(t) {
				st.mem(-len(ents[n].data))
				e.remove(cg, n)
			}
		}
	}
}

type cacheFunc func() error

type cache struct {
	entries *entries
	waits   *waiters
	fetcher *fetcher
	config  *config
	stat    *stats
	events  chan cacheFunc
}

func newCache(f *fetcher, cf *config) *cache {
	c := &cache{
		fetcher: f,
		config:  cf,
		events:  make(chan cacheFunc),
		entries: newEntries(),
		waits:   newWaiters(),
		stat:    newStats(),
	}
	go c.gc(cf.gcpause)
	go c.run()
	return c
}

func (c *cache) run() {
	for f := range c.events {
		if err := f(); err != nil {
			log.Print("cache: ", err)
		}
	}
}

func (c *cache) gc(d time.Duration) {
	done := make(chan struct{})
	for {
		time.Sleep(d)
		c.events <- func() error {
			debug("running garbage collector cycle, memory is %d", c.stat.Mem)
			c.entries.gc(time.Now(), c.stat)
			debug("garbage collection done, memory is %d", c.stat.Mem)
			done <- struct{}{}
			return nil
		}
		<-done
	}
}

type timeGroup struct {
	t  time.Time
	cg group
}

type timeGroups struct {
	entries []timeGroup
}

type byTime []timeGroup

func (a byTime) Len() int           { return len(a) }
func (a byTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byTime) Less(i, j int) bool { return a[i].t.After(a[j].t) }

func makeTimeGroups(e *entries) timeGroups {
	var i int
	tg := make([]timeGroup, len(e.ents))
	for cg := range e.ents {
		tg[i] = timeGroup{
			t:  e.oldestDeadline(cg),
			cg: cg,
		}
		i++
	}
	sort.Sort(byTime(tg))
	return timeGroups{tg}
}

func (tg *timeGroups) purgeOldest(c *cache) {
	entry, ts := tg.entries[len(tg.entries)-1], tg.entries[0:len(tg.entries)-1]
	c.entries.purge(entry.cg, c.stat)
	tg.entries = ts
}

func (tg *timeGroups) empty() bool {
	return len(tg.entries) == 0
}

func (c *cache) oom(target int64) {
	debug("OOM called: using %d, limit is %d", c.stat.Mem, target)
	tg := makeTimeGroups(c.entries)
	for {
		tg.purgeOldest(c)
		// Repeat unless we have no more entries or we are using less memory than target
		if tg.empty() || !c.stat.above(target) {
			break
		}
	}
	debug("OOM: mem now %d", c.stat.Mem)
}

// put inserts a page into the cache (after it was fetched).
func (c *cache) put(cg group, p *page, err error) {
	c.events <- func() error {
		ce := newEntry(p.body, c.config.lifetime)
		c.entries.put(cg, p.n, ce)
		debug("added page %s/%d", cg, p.n)
		c.stat.mem(len(p.body))
		if c.stat.above(c.config.maxMemory) {
			go func() {
				c.events <- func() error {
					c.oom(c.config.maxMemory)
					return nil
				}
			}()
		}
		// If there were waiters, signal that the wait is over
		c.waits.done(cg, p.n)
		return err
	}
}

// prefetch requests pages (n-m, n) and (n, n+m) if not already fetched
func (c *cache) prefetch(cg group, n, m int, t time.Time) {
	i := n - m
	if i < 0 {
		i = 0
	}
	for ; i < n+m; i++ {
		if i == n {
			// just fetched
			continue
		}
		off := offset(i * c.config.incr)
		if c.entries.has(cg, off, t) || c.waits.has(cg, off) {
			// already fetched or requested
			continue
		}
		c.waits.wait(cg, off)
		res := newResource(c.config.tmpl, cg, off)
		c.fetcher.request(newJob(res, c))
	}
}

func (c *cache) request(cg group, n int, t time.Time) chan struct{} {
	off := offset(n * c.config.incr)
	wait := c.waits.wait(cg, off)
	res := newResource(c.config.tmpl, cg, off)
	c.fetcher.request(newJob(res, c))
	c.prefetch(cg, n, c.config.npref, t)
	return wait
}

func (c *cache) stats() *stats {
	var st *stats
	wait := make(chan struct{})
	c.events <- func() error {
		c.stat.Entries = c.entries.count()
		c.stat.Waiters = c.waits.count()
		st = c.stat.clone()
		wait <- struct{}{}
		return nil
	}
	<-wait
	return st
}

func (c *cache) get(cg group, n int) *page {
	var (
		page *page
		wait chan struct{}
	)
	cached := true
	requested := make(chan struct{})
	off := offset(n * c.config.incr)
	debug("%s/%d: requesting from cache", cg, off)
	for {
		wait = nil
		c.events <- func() error {
			defer func() { requested <- struct{}{} }()
			var now time.Time
			ce, ok := c.entries.get(cg, off)
			if ok {
				now = time.Now()
			}
			if !ok || ce.invalid(now) {
				debug("%s/%d: not cached, requested", cg, off)
				wait = c.request(cg, n, now)
				return nil
			}
			debug("%s/%d: found", cg, off)
			c.prefetch(cg, n, c.config.npref, now)
			c.stat.hit(cached)
			page = ce.asPage(off)
			return nil
		}
		<-requested
		// content was already in cache, return it
		if wait == nil {
			page.cached = cached
			return page
		}
		// We needed to request the object, it was not cached
		cached = false
		// content is being fetched, wait and try to get again
		<-wait
	}
}
<|endoftext|>


<commit_before>package cf

import (
	"fmt"
	"time"

	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	helpersCF "github.com/pivotal-cf-experimental/cf-test-helpers/cf"
	"github.com/pivotal-cf/on-demand-service-broker/system_tests/cf_helpers"
)

//CF is a testing wrapper around the cf cli
type CF struct {
	ShortTimeout time.Duration
	LongTimeout  time.Duration
}

//API is equivalent to `cf api {endpoint} [--skip-ssl-validation]`
func (cf *CF) API(endpoint string, skipSSLValidation bool) func() {
	return func() {
		apiCmd := []string{"api", endpoint}

		if skipSSLValidation {
			apiCmd = append(apiCmd, "--skip-ssl-validation")
		}

		Eventually(helpersCF.Cf(apiCmd...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target Cloud Foundry"}`,
		)
	}
}

//Auth is equivalent to `cf auth {user} {password}`
func (cf *CF) Auth(user, password string) func() {
	return func() {
		Eventually(helpersCF.Cf("auth", user, password), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf auth` with target Cloud Foundry\"}",
		)
	}
}

//CreateQuota is equivalent to `cf create-quota {name} [args...]`
func (cf *CF) CreateQuota(name string, args ...string) func() {
	return func() {
		cfArgs := []string{"create-quota", name}
		cfArgs = append(cfArgs, args...)
		Eventually(helpersCF.Cf(cfArgs...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf create-quota` with target Cloud Foundry\"}",
		)
	}
}

//CreateOrg is equivalent to `cf create-org {org} -q {quota}`
func (cf *CF) CreateOrg(org, quota string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-org", org, "-q", quota), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create CF test org"}`,
		)
	}
}

//EnableServiceAccess is equivalent to `cf enable-service-access -o {org} {service-offering}`
//In order to run enable-service-access idempotently we disable-service-access before.
func (cf *CF) EnableServiceAccess(org, service string) func() {
	return func() {
		Eventually(helpersCF.Cf("disable-service-access", "-o", org, service), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to disable service access for CF test org"}`,
		)
		Eventually(helpersCF.Cf("enable-service-access", "-o", org, service), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to enable service access for CF test org"}`,
		)
	}
}

//TargetOrg is equivalent to `cf target -o {org}`
func (cf *CF) TargetOrg(org string) func() {
	return func() {
		Eventually(helpersCF.Cf("target", "-o", org), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target test org"}`,
		)
	}
}

//TargetOrgAndSpace is equivalent to `cf target -o {org} -s {space}`
func (cf *CF) TargetOrgAndSpace(org, space string) func() {
	return func() {
		Eventually(helpersCF.Cf("target", "-o", org, "-s", space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target test org"}`,
		)
	}
}

//CreateSpace is equivalent to `cf create-space {space}`
func (cf *CF) CreateSpace(space string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-space", space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create CF test space"}`,
		)
	}
}

//CreateSecurityGroup is equivalent to `cf create-security-group {securityGroup} {configPath}`
func (cf *CF) CreateSecurityGroup(securityGroup, configPath string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-security-group", securityGroup, configPath), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create security group"}`,
		)
	}
}

//DeleteSecurityGroup is equivalent to `cf delete-security-group {securityGroup} -f`
func (cf *CF) DeleteSecurityGroup(securityGroup string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-security-group", securityGroup, "-f"), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to delete security group"}`,
		)
	}
}

//BindSecurityGroup is equivalent to `cf bind-security-group {securityGroup} {org} {space}`
func (cf *CF) BindSecurityGroup(securityGroup, org, space string) func() {
	return func() {
		Eventually(helpersCF.Cf("bind-security-group", securityGroup, org, space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to bind security group to space"}`,
		)
	}
}

//CreateUser is equivalent to `cf create-user {name} {password}`
func (cf *CF) CreateUser(name, password string) func() {
	return func() {
		createUserCmd := helpersCF.Cf("create-user", name, password)
		Eventually(createUserCmd, cf.ShortTimeout).Should(gexec.Exit())
		if createUserCmd.ExitCode() != 0 {
			Expect(createUserCmd.Out).To(
				gbytes.Say("scim_resource_already_exists"),
				`{"FailReason": "Failed to create user"}`,
			)
		}
	}
}

//DeleteUser is equivalent to `cf delete-user -f {name}`
func (cf *CF) DeleteUser(name string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-user", "-f", name), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to delete user"}`,
		)
	}
}

//SetSpaceRole is equivalent to `cf set-space-role {name} {org} {space} {role}`
func (cf *CF) SetSpaceRole(name, org, space, role string) func() {
	return func() {
		Eventually(helpersCF.Cf("set-space-role", name, org, space, role), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to set space role"}`,
		)
	}
}

//Push is equivalent to `cf push {appName} [args...]`
func (cf *CF) Push(appName string, args ...string) func() {
	pushArgs := []string{"push", appName}
	pushArgs = append(pushArgs, args...)
	return func() {
		Eventually(helpersCF.Cf(pushArgs...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf push` test app\"}",
		)
	}
}

//Delete is equivalent to `cf delete {appName} -f`
func (cf *CF) Delete(appName string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete", appName, "-f", "-r"), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf delete` test app\"}",
		)
	}
}

//CreateService is equivalent to `cf create-service {serviceName} {planName} {instanceName}`
func (cf *CF) CreateService(serviceName, planName, instanceName string, skip *bool) func() {
	return func() {
		session := helpersCF.Cf("create-service", serviceName, planName, instanceName)
		session.Wait(cf.ShortTimeout)
		createServiceStdout := session.Out

		defer createServiceStdout.CancelDetects()
		select {
		case <-createServiceStdout.Detect("FAILED"):
			Eventually(session, cf.ShortTimeout).Should(
				gbytes.Say("instance limit for this service has been reached"),
				`{"FailReason": "Failed to bind Redis service instance to test app"}`,
			)
			Eventually(session, cf.ShortTimeout).Should(gexec.Exit(1))
			fmt.Printf("No Plan Instances available for testing %s plan\n", planName)
			*skip = true
		case <-createServiceStdout.Detect("OK"):
			Eventually(session, cf.ShortTimeout).Should(
				gexec.Exit(0),
				`{"FailReason": "Failed to create Redis service instance"}`,
			)
			cf_helpers.AwaitServiceCreation(serviceName)
		}
	}
}

//DeleteService is equivalent to `cf delete-service {instanceName} -f`
func (cf *CF) DeleteService(instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-service", "-f", instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			fmt.Sprintf(`{"FailReason": "Failed to delete service %s"}`, instanceName),
		)
	}
}

//BindService is equivalent to `cf bind-service {appName} {instanceName}`
func (cf *CF) BindService(appName, instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("bind-service", appName, instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to bind Redis service instance to test app"}`,
		)
	}
}

//UnbindService is equivalent to `cf unbind-service {appName} {instanceName}`
func (cf *CF) UnbindService(appName, instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("unbind-service", appName, instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			fmt.Sprintf(`{"FailReason": "Failed to unbind %s instance from %s"}`, instanceName, appName),
		)
	}
}

//Start is equivalent to `cf start {appName}`
func (cf *CF) Start(appName string) func() {
	return func() {
		Eventually(helpersCF.Cf("start", appName), cf.LongTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to start test app"}`,
		)
	}
}

//Logout is equivalent to `cf logout`
func (cf *CF) Logout() func() {
	return func() {
		Eventually(helpersCF.Cf("logout")).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to logout"}`,
		)
	}
}
<commit_msg>Fix incorrect reference to service name<commit_after>package cf

import (
	"fmt"
	"time"

	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	helpersCF "github.com/pivotal-cf-experimental/cf-test-helpers/cf"
	"github.com/pivotal-cf/on-demand-service-broker/system_tests/cf_helpers"
)

//CF is a testing wrapper around the cf cli
type CF struct {
	ShortTimeout time.Duration
	LongTimeout  time.Duration
}

//API is equivalent to `cf api {endpoint} [--skip-ssl-validation]`
func (cf *CF) API(endpoint string, skipSSLValidation bool) func() {
	return func() {
		apiCmd := []string{"api", endpoint}

		if skipSSLValidation {
			apiCmd = append(apiCmd, "--skip-ssl-validation")
		}

		Eventually(helpersCF.Cf(apiCmd...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target Cloud Foundry"}`,
		)
	}
}

//Auth is equivalent to `cf auth {user} {password}`
func (cf *CF) Auth(user, password string) func() {
	return func() {
		Eventually(helpersCF.Cf("auth", user, password), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf auth` with target Cloud Foundry\"}",
		)
	}
}

//CreateQuota is equivalent to `cf create-quota {name} [args...]`
func (cf *CF) CreateQuota(name string, args ...string) func() {
	return func() {
		cfArgs := []string{"create-quota", name}
		cfArgs = append(cfArgs, args...)
		Eventually(helpersCF.Cf(cfArgs...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf create-quota` with target Cloud Foundry\"}",
		)
	}
}

//CreateOrg is equivalent to `cf create-org {org} -q {quota}`
func (cf *CF) CreateOrg(org, quota string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-org", org, "-q", quota), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create CF test org"}`,
		)
	}
}

//EnableServiceAccess is equivalent to `cf enable-service-access -o {org} {service-offering}`
//In order to run enable-service-access idempotently we disable-service-access before.
func (cf *CF) EnableServiceAccess(org, service string) func() {
	return func() {
		Eventually(helpersCF.Cf("disable-service-access", "-o", org, service), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to disable service access for CF test org"}`,
		)
		Eventually(helpersCF.Cf("enable-service-access", "-o", org, service), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to enable service access for CF test org"}`,
		)
	}
}

//TargetOrg is equivalent to `cf target -o {org}`
func (cf *CF) TargetOrg(org string) func() {
	return func() {
		Eventually(helpersCF.Cf("target", "-o", org), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target test org"}`,
		)
	}
}

//TargetOrgAndSpace is equivalent to `cf target -o {org} -s {space}`
func (cf *CF) TargetOrgAndSpace(org, space string) func() {
	return func() {
		Eventually(helpersCF.Cf("target", "-o", org, "-s", space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to target test org"}`,
		)
	}
}

//CreateSpace is equivalent to `cf create-space {space}`
func (cf *CF) CreateSpace(space string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-space", space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create CF test space"}`,
		)
	}
}

//CreateSecurityGroup is equivalent to `cf create-security-group {securityGroup} {configPath}`
func (cf *CF) CreateSecurityGroup(securityGroup, configPath string) func() {
	return func() {
		Eventually(helpersCF.Cf("create-security-group", securityGroup, configPath), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to create security group"}`,
		)
	}
}

//DeleteSecurityGroup is equivalent to `cf delete-security-group {securityGroup} -f`
func (cf *CF) DeleteSecurityGroup(securityGroup string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-security-group", securityGroup, "-f"), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to delete security group"}`,
		)
	}
}

//BindSecurityGroup is equivalent to `cf bind-security-group {securityGroup} {org} {space}`
func (cf *CF) BindSecurityGroup(securityGroup, org, space string) func() {
	return func() {
		Eventually(helpersCF.Cf("bind-security-group", securityGroup, org, space), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to bind security group to space"}`,
		)
	}
}

//CreateUser is equivalent to `cf create-user {name} {password}`
func (cf *CF) CreateUser(name, password string) func() {
	return func() {
		createUserCmd := helpersCF.Cf("create-user", name, password)
		Eventually(createUserCmd, cf.ShortTimeout).Should(gexec.Exit())
		if createUserCmd.ExitCode() != 0 {
			Expect(createUserCmd.Out).To(
				gbytes.Say("scim_resource_already_exists"),
				`{"FailReason": "Failed to create user"}`,
			)
		}
	}
}

//DeleteUser is equivalent to `cf delete-user -f {name}`
func (cf *CF) DeleteUser(name string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-user", "-f", name), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to delete user"}`,
		)
	}
}

//SetSpaceRole is equivalent to `cf set-space-role {name} {org} {space} {role}`
func (cf *CF) SetSpaceRole(name, org, space, role string) func() {
	return func() {
		Eventually(helpersCF.Cf("set-space-role", name, org, space, role), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to set space role"}`,
		)
	}
}

//Push is equivalent to `cf push {appName} [args...]`
func (cf *CF) Push(appName string, args ...string) func() {
	pushArgs := []string{"push", appName}
	pushArgs = append(pushArgs, args...)
	return func() {
		Eventually(helpersCF.Cf(pushArgs...), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf push` test app\"}",
		)
	}
}

//Delete is equivalent to `cf delete {appName} -f`
func (cf *CF) Delete(appName string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete", appName, "-f", "-r"), cf.ShortTimeout).Should(
			gexec.Exit(0),
			"{\"FailReason\": \"Failed to `cf delete` test app\"}",
		)
	}
}

//CreateService is equivalent to `cf create-service {serviceName} {planName} {instanceName}`
func (cf *CF) CreateService(serviceName, planName, instanceName string, skip *bool) func() {
	return func() {
		session := helpersCF.Cf("create-service", serviceName, planName, instanceName)
		session.Wait(cf.ShortTimeout)
		createServiceStdout := session.Out

		defer createServiceStdout.CancelDetects()
		select {
		case <-createServiceStdout.Detect("FAILED"):
			Eventually(session, cf.ShortTimeout).Should(
				gbytes.Say("instance limit for this service has been reached"),
				`{"FailReason": "Failed to bind Redis service instance to test app"}`,
			)
			Eventually(session, cf.ShortTimeout).Should(gexec.Exit(1))
			fmt.Printf("No Plan Instances available for testing %s plan\n", planName)
			*skip = true
		case <-createServiceStdout.Detect("OK"):
			Eventually(session, cf.ShortTimeout).Should(
				gexec.Exit(0),
				`{"FailReason": "Failed to create Redis service instance"}`,
			)
			cf_helpers.AwaitServiceCreation(instanceName)
		}
	}
}

//DeleteService is equivalent to `cf delete-service {instanceName} -f`
func (cf *CF) DeleteService(instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("delete-service", "-f", instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			fmt.Sprintf(`{"FailReason": "Failed to delete service %s"}`, instanceName),
		)
	}
}

//BindService is equivalent to `cf bind-service {appName} {instanceName}`
func (cf *CF) BindService(appName, instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("bind-service", appName, instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to bind Redis service instance to test app"}`,
		)
	}
}

//UnbindService is equivalent to `cf unbind-service {appName} {instanceName}`
func (cf *CF) UnbindService(appName, instanceName string) func() {
	return func() {
		Eventually(helpersCF.Cf("unbind-service", appName, instanceName), cf.ShortTimeout).Should(
			gexec.Exit(0),
			fmt.Sprintf(`{"FailReason": "Failed to unbind %s instance from %s"}`, instanceName, appName),
		)
	}
}

//Start is equivalent to `cf start {appName}`
func (cf *CF) Start(appName string) func() {
	return func() {
		Eventually(helpersCF.Cf("start", appName), cf.LongTimeout).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to start test app"}`,
		)
	}
}

//Logout is equivalent to `cf logout`
func (cf *CF) Logout() func() {
	return func() {
		Eventually(helpersCF.Cf("logout")).Should(
			gexec.Exit(0),
			`{"FailReason": "Failed to logout"}`,
		)
	}
}
<|endoftext|>


<commit_before>package chart

import (
	"errors"
	"io"
	"math"

	"github.com/golang/freetype/truetype"
)

// Chart is what we're drawing.
type Chart struct {
	Title      string
	TitleStyle Style

	Width  int
	Height int

	Background      Style
	Canvas          Style
	Axes            Style
	FinalValueLabel Style

	XRange Range
	YRange Range

	Font   *truetype.Font
	Series []Series
}

// GetFont returns the text font.
func (c Chart) GetFont() (*truetype.Font, error) {
	if c.Font == nil {
		f, err := GetDefaultFont()
		if err != nil {
			return nil, err
		}
		c.Font = f
	}
	return c.Font
}

// Render renders the chart with the given renderer to the given io.Writer.
func (c *Chart) Render(provider RendererProvider, w io.Writer) error {
	if len(c.Series) == 0 {
		return errors.New("Please provide at least one series")
	}
	r := provider(c.Width, c.Height)
	if c.hasText() {
		font, err := c.GetFont()
		if err != nil {
			return err
		}
		r.SetFont(font)
	}

	canvasBox := c.calculateCanvasBox(r)
	xrange, yrange := c.initRanges(canvasBox)

	c.drawBackground(r)
	c.drawCanvas(r, canvasBox)
	c.drawAxes(r, canvasBox, xrange, yrange)
	for index, series := range c.Series {
		c.drawSeries(r, canvasBox, index, series, xrange, yrange)
	}
	c.drawTitle(r)
	return r.Save(w)
}

func (c Chart) hasText() bool {
	return c.TitleStyle.Show || c.Axes.Show || c.FinalValueLabel.Show
}

func (c Chart) getAxisWidth() int {
	asw := 0
	if c.Axes.Show {
		asw = int(c.Axes.GetStrokeWidth(DefaultAxisLineWidth))
	}
	return asw
}

func (c Chart) calculateCanvasBox(r Renderer) Box {
	dpr := DefaultBackgroundPadding.Right
	finalLabelWidth := c.calculateFinalLabelWidth(r)
	if finalLabelWidth > dpr {
		dpr = finalLabelWidth
	}
	axisBottomHeight := c.calculateBottomLabelHeight()
	dpb := DefaultBackgroundPadding.Bottom
	if dpb < axisBottomHeight {
		dpb = axisBottomHeight
	}

	cb := Box{
		Top:    c.Background.Padding.GetTop(DefaultBackgroundPadding.Top),
		Left:   c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left),
		Right:  c.Width - c.Background.Padding.GetRight(dpr),
		Bottom: c.Height - c.Background.Padding.GetBottom(dpb),
	}
	cb.Height = cb.Bottom - cb.Top
	cb.Width = cb.Right - cb.Left
	return cb
}

func (c Chart) calculateFinalLabelWidth(r Renderer) int {
	if !c.FinalValueLabel.Show {
		return 0
	}
	var finalLabelText string
	for _, s := range c.Series {
		_, lv := s.GetValue(s.Len() - 1)
		ll := s.GetYFormatter()(lv)
		if len(finalLabelText) < len(ll) {
			finalLabelText = ll
		}
	}

	r.SetFontSize(c.FinalValueLabel.GetFontSize(DefaultFinalLabelFontSize))
	textWidth := r.MeasureText(finalLabelText)
	asw := c.getAxisWidth()

	pl := c.FinalValueLabel.Padding.GetLeft(DefaultFinalLabelPadding.Left)
	pr := c.FinalValueLabel.Padding.GetRight(DefaultFinalLabelPadding.Right)
	lsw := int(c.FinalValueLabel.GetStrokeWidth(DefaultAxisLineWidth))

	return DefaultFinalLabelDeltaWidth +
		pl + pr +
		textWidth + asw + 2*lsw
}

func (c Chart) calculateBottomLabelHeight() int {
	if c.Axes.Show {
		return c.getAxisWidth() + int(math.Ceil(c.Axes.GetFontSize(DefaultAxisFontSize))) + DefaultXAxisMargin
	}
	return 0
}

func (c Chart) initRanges(canvasBox Box) (xrange Range, yrange Range) {
	//iterate over each series, pull out the min/max for x,y
	var didSetFirstValues bool
	var globalMinY, globalMinX float64
	var globalMaxY, globalMaxX float64
	for _, s := range c.Series {
		seriesLength := s.Len()
		for index := 0; index < seriesLength; index++ {
			vx, vy := s.GetValue(index)
			if didSetFirstValues {
				if globalMinX > vx {
					globalMinX = vx
				}
				if globalMinY > vy {
					globalMinY = vy
				}
				if globalMaxX < vx {
					globalMaxX = vx
				}
				if globalMaxY < vy {
					globalMaxY = vy
				}
			} else {
				globalMinX, globalMaxX = vx, vx
				globalMinY, globalMaxY = vy, vy
				didSetFirstValues = true
			}
		}
		xrange.Formatter = s.GetXFormatter()
		yrange.Formatter = s.GetYFormatter()
	}

	if c.XRange.IsZero() {
		xrange.Min = globalMinX
		xrange.Max = globalMaxX
	} else {
		xrange.Min = c.XRange.Min
		xrange.Max = c.XRange.Max
	}
	xrange.Domain = canvasBox.Width

	if c.YRange.IsZero() {
		yrange.Min = globalMinY
		yrange.Max = globalMaxY
	} else {
		yrange.Min = c.YRange.Min
		yrange.Max = c.YRange.Max
	}
	yrange.Domain = canvasBox.Height

	return
}

func (c Chart) drawBackground(r Renderer) {
	r.SetFillColor(c.Background.GetFillColor(DefaultBackgroundColor))
	r.SetStrokeColor(c.Background.GetStrokeColor(DefaultBackgroundStrokeColor))
	r.SetLineWidth(c.Background.GetStrokeWidth(DefaultStrokeWidth))
	r.MoveTo(0, 0)
	r.LineTo(c.Width, 0)
	r.LineTo(c.Width, c.Height)
	r.LineTo(0, c.Height)
	r.LineTo(0, 0)
	r.Close()
	r.FillStroke()
}

func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
	r.SetFillColor(c.Canvas.GetFillColor(DefaultCanvasColor))
	r.SetStrokeColor(c.Canvas.GetStrokeColor(DefaultCanvasStrokColor))
	r.SetLineWidth(c.Canvas.GetStrokeWidth(DefaultStrokeWidth))
	r.MoveTo(canvasBox.Left, canvasBox.Top)
	r.LineTo(canvasBox.Right, canvasBox.Top)
	r.LineTo(canvasBox.Right, canvasBox.Bottom)
	r.LineTo(canvasBox.Left, canvasBox.Bottom)
	r.LineTo(canvasBox.Left, canvasBox.Top)
	r.Close()
	r.FillStroke()
}

func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange Range) {
	if c.Axes.Show {
		r.SetStrokeColor(c.Axes.GetStrokeColor(DefaultAxisColor))
		r.SetLineWidth(c.Axes.GetStrokeWidth(DefaultStrokeWidth))
		r.MoveTo(canvasBox.Left, canvasBox.Bottom)
		r.LineTo(canvasBox.Right, canvasBox.Bottom)
		r.LineTo(canvasBox.Right, canvasBox.Top)
		r.Stroke()

		c.drawXAxisLabels(r, canvasBox, xrange)
		c.drawYAxisLabels(r, canvasBox, yrange)
	}
}

func (c Chart) drawYAxisLabels(r Renderer, canvasBox Box, yrange Range) {
	tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)

	r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
	r.SetFontSize(tickFontSize)

	minimumTickHeight := tickFontSize + DefaultMinimumTickVerticalSpacing
	tickCount := int(math.Floor(float64(yrange.Domain) / float64(minimumTickHeight)))

	if tickCount > DefaultMaxTickCount {
		tickCount = DefaultMaxTickCount
	}

	rangeTicks := Slices(tickCount, yrange.Max-yrange.Min)
	domainTicks := Slices(tickCount, float64(yrange.Domain))

	asw := c.getAxisWidth()
	tx := canvasBox.Right + DefaultFinalLabelDeltaWidth + asw

	count := len(rangeTicks)
	if len(domainTicks) < count {
		count = len(domainTicks) //guard against mismatched array sizes.
	}

	for index := 0; index < count; index++ {
		v := rangeTicks[index] + yrange.Min
		y := domainTicks[index]
		ty := canvasBox.Bottom - int(y)
		r.Text(yrange.Format(v), tx, ty)
	}
}

func (c Chart) drawXAxisLabels(r Renderer, canvasBox Box, xrange Range) {
	tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)

	r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
	r.SetFontSize(tickFontSize)

	maxLabelWidth := 60

	minimumTickWidth := maxLabelWidth + DefaultMinimumTickHorizontalSpacing
	tickCount := int(math.Floor(float64(xrange.Domain) / float64(minimumTickWidth)))

	if tickCount > DefaultMaxTickCount {
		tickCount = DefaultMaxTickCount
	}

	rangeTicks := Slices(tickCount, xrange.Max-xrange.Min)
	domainTicks := Slices(tickCount, float64(xrange.Domain))

	ty := canvasBox.Bottom + DefaultXAxisMargin + int(tickFontSize)

	count := len(rangeTicks)
	if len(domainTicks) < count {
		count = len(domainTicks) //guard against mismatched array sizes.
	}

	for index := 0; index < count; index++ {
		v := rangeTicks[index] + xrange.Min
		x := domainTicks[index]
		tx := canvasBox.Left + int(x)
		r.Text(xrange.Format(v), tx, ty)
	}
}

func (c Chart) drawSeries(r Renderer, canvasBox Box, index int, s Series, xrange, yrange Range) {
	r.SetStrokeColor(s.GetStyle().GetStrokeColor(GetDefaultSeriesStrokeColor(index)))
	r.SetLineWidth(s.GetStyle().GetStrokeWidth(DefaultStrokeWidth))

	if s.Len() == 0 {
		return
	}

	cx := canvasBox.Left
	cy := canvasBox.Top
	cw := canvasBox.Width

	v0x, v0y := s.GetValue(0)
	x0 := cw - xrange.Translate(v0x)
	y0 := yrange.Translate(v0y)
	r.MoveTo(x0+cx, y0+cy)

	var vx, vy float64
	var x, y int
	for i := 1; i < s.Len(); i++ {
		vx, vy = s.GetValue(i)
		x = cw - xrange.Translate(vx)
		y = yrange.Translate(vy)
		r.LineTo(x+cx, y+cy)
	}
	r.Stroke()

	c.drawFinalValueLabel(r, canvasBox, index, s, yrange)
}

func (c Chart) drawFinalValueLabel(r Renderer, canvasBox Box, index int, s Series, yrange Range) {
	if c.FinalValueLabel.Show {
		_, lv := s.GetValue(s.Len() - 1)
		ll := s.GetYFormatter()(lv)

		py := canvasBox.Top
		ly := yrange.Translate(lv) + py

		r.SetFontSize(c.FinalValueLabel.GetFontSize(DefaultFinalLabelFontSize))
		textWidth := r.MeasureText(ll)
		textHeight := int(math.Floor(DefaultFinalLabelFontSize))
		halfTextHeight := textHeight >> 1

		asw := 0
		if c.Axes.Show {
			asw = int(c.Axes.GetStrokeWidth(DefaultAxisLineWidth))
		}

		cx := canvasBox.Right + asw

		pt := c.FinalValueLabel.Padding.GetTop(DefaultFinalLabelPadding.Top)
		pl := c.FinalValueLabel.Padding.GetLeft(DefaultFinalLabelPadding.Left)
		pr := c.FinalValueLabel.Padding.GetRight(DefaultFinalLabelPadding.Right)
		pb := c.FinalValueLabel.Padding.GetBottom(DefaultFinalLabelPadding.Bottom)

		textX := cx + pl + DefaultFinalLabelDeltaWidth
		textY := ly + halfTextHeight

		ltlx := cx + pl + DefaultFinalLabelDeltaWidth
		ltly := ly - (pt + halfTextHeight)

		ltrx := cx + pl + pr + textWidth
		ltry := ly - (pt + halfTextHeight)

		lbrx := cx + pl + pr + textWidth
		lbry := ly + (pb + halfTextHeight)

		lblx := cx + DefaultFinalLabelDeltaWidth
		lbly := ly + (pb + halfTextHeight)

		//draw the shape...
		r.SetFillColor(c.FinalValueLabel.GetFillColor(DefaultFinalLabelBackgroundColor))
		r.SetStrokeColor(c.FinalValueLabel.GetStrokeColor(s.GetStyle().GetStrokeColor(GetDefaultSeriesStrokeColor(index))))
		r.SetLineWidth(c.FinalValueLabel.GetStrokeWidth(DefaultAxisLineWidth))
		r.MoveTo(cx, ly)
		r.LineTo(ltlx, ltly)
		r.LineTo(ltrx, ltry)
		r.LineTo(lbrx, lbry)
		r.LineTo(lblx, lbly)
		r.LineTo(cx, ly)
		r.Close()
		r.FillStroke()

		r.SetFontColor(c.FinalValueLabel.GetFontColor(DefaultTextColor))
		r.Text(ll, textX, textY)
	}
}

func (c Chart) drawTitle(r Renderer) error {
	if len(c.Title) > 0 && c.TitleStyle.Show {
		r.SetFontColor(c.Canvas.GetFontColor(DefaultTextColor))
		titleFontSize := c.Canvas.GetFontSize(DefaultTitleFontSize)
		r.SetFontSize(titleFontSize)
		textWidth := r.MeasureText(c.Title)
		titleX := (c.Width >> 1) - (textWidth >> 1)
		titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + int(titleFontSize)
		r.Text(c.Title, titleX, titleY)
	}
	return nil
}
<commit_msg>build bust.<commit_after>package chart

import (
	"errors"
	"io"
	"math"

	"github.com/golang/freetype/truetype"
)

// Chart is what we're drawing.
type Chart struct {
	Title      string
	TitleStyle Style

	Width  int
	Height int

	Background      Style
	Canvas          Style
	Axes            Style
	FinalValueLabel Style

	XRange Range
	YRange Range

	Font   *truetype.Font
	Series []Series
}

// GetFont returns the text font.
func (c Chart) GetFont() (*truetype.Font, error) {
	if c.Font == nil {
		f, err := GetDefaultFont()
		if err != nil {
			return nil, err
		}
		c.Font = f
	}
	return c.Font, nil
}

// Render renders the chart with the given renderer to the given io.Writer.
func (c *Chart) Render(provider RendererProvider, w io.Writer) error {
	if len(c.Series) == 0 {
		return errors.New("Please provide at least one series")
	}
	r := provider(c.Width, c.Height)
	if c.hasText() {
		font, err := c.GetFont()
		if err != nil {
			return err
		}
		r.SetFont(font)
	}

	canvasBox := c.calculateCanvasBox(r)
	xrange, yrange := c.initRanges(canvasBox)

	c.drawBackground(r)
	c.drawCanvas(r, canvasBox)
	c.drawAxes(r, canvasBox, xrange, yrange)
	for index, series := range c.Series {
		c.drawSeries(r, canvasBox, index, series, xrange, yrange)
	}
	c.drawTitle(r)
	return r.Save(w)
}

func (c Chart) hasText() bool {
	return c.TitleStyle.Show || c.Axes.Show || c.FinalValueLabel.Show
}

func (c Chart) getAxisWidth() int {
	asw := 0
	if c.Axes.Show {
		asw = int(c.Axes.GetStrokeWidth(DefaultAxisLineWidth))
	}
	return asw
}

func (c Chart) calculateCanvasBox(r Renderer) Box {
	dpr := DefaultBackgroundPadding.Right
	finalLabelWidth := c.calculateFinalLabelWidth(r)
	if finalLabelWidth > dpr {
		dpr = finalLabelWidth
	}
	axisBottomHeight := c.calculateBottomLabelHeight()
	dpb := DefaultBackgroundPadding.Bottom
	if dpb < axisBottomHeight {
		dpb = axisBottomHeight
	}

	cb := Box{
		Top:    c.Background.Padding.GetTop(DefaultBackgroundPadding.Top),
		Left:   c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left),
		Right:  c.Width - c.Background.Padding.GetRight(dpr),
		Bottom: c.Height - c.Background.Padding.GetBottom(dpb),
	}
	cb.Height = cb.Bottom - cb.Top
	cb.Width = cb.Right - cb.Left
	return cb
}

func (c Chart) calculateFinalLabelWidth(r Renderer) int {
	if !c.FinalValueLabel.Show {
		return 0
	}
	var finalLabelText string
	for _, s := range c.Series {
		_, lv := s.GetValue(s.Len() - 1)
		ll := s.GetYFormatter()(lv)
		if len(finalLabelText) < len(ll) {
			finalLabelText = ll
		}
	}

	r.SetFontSize(c.FinalValueLabel.GetFontSize(DefaultFinalLabelFontSize))
	textWidth := r.MeasureText(finalLabelText)
	asw := c.getAxisWidth()

	pl := c.FinalValueLabel.Padding.GetLeft(DefaultFinalLabelPadding.Left)
	pr := c.FinalValueLabel.Padding.GetRight(DefaultFinalLabelPadding.Right)
	lsw := int(c.FinalValueLabel.GetStrokeWidth(DefaultAxisLineWidth))

	return DefaultFinalLabelDeltaWidth +
		pl + pr +
		textWidth + asw + 2*lsw
}

func (c Chart) calculateBottomLabelHeight() int {
	if c.Axes.Show {
		return c.getAxisWidth() + int(math.Ceil(c.Axes.GetFontSize(DefaultAxisFontSize))) + DefaultXAxisMargin
	}
	return 0
}

func (c Chart) initRanges(canvasBox Box) (xrange Range, yrange Range) {
	//iterate over each series, pull out the min/max for x,y
	var didSetFirstValues bool
	var globalMinY, globalMinX float64
	var globalMaxY, globalMaxX float64
	for _, s := range c.Series {
		seriesLength := s.Len()
		for index := 0; index < seriesLength; index++ {
			vx, vy := s.GetValue(index)
			if didSetFirstValues {
				if globalMinX > vx {
					globalMinX = vx
				}
				if globalMinY > vy {
					globalMinY = vy
				}
				if globalMaxX < vx {
					globalMaxX = vx
				}
				if globalMaxY < vy {
					globalMaxY = vy
				}
			} else {
				globalMinX, globalMaxX = vx, vx
				globalMinY, globalMaxY = vy, vy
				didSetFirstValues = true
			}
		}
		xrange.Formatter = s.GetXFormatter()
		yrange.Formatter = s.GetYFormatter()
	}

	if c.XRange.IsZero() {
		xrange.Min = globalMinX
		xrange.Max = globalMaxX
	} else {
		xrange.Min = c.XRange.Min
		xrange.Max = c.XRange.Max
	}
	xrange.Domain = canvasBox.Width

	if c.YRange.IsZero() {
		yrange.Min = globalMinY
		yrange.Max = globalMaxY
	} else {
		yrange.Min = c.YRange.Min
		yrange.Max = c.YRange.Max
	}
	yrange.Domain = canvasBox.Height

	return
}

func (c Chart) drawBackground(r Renderer) {
	r.SetFillColor(c.Background.GetFillColor(DefaultBackgroundColor))
	r.SetStrokeColor(c.Background.GetStrokeColor(DefaultBackgroundStrokeColor))
	r.SetLineWidth(c.Background.GetStrokeWidth(DefaultStrokeWidth))
	r.MoveTo(0, 0)
	r.LineTo(c.Width, 0)
	r.LineTo(c.Width, c.Height)
	r.LineTo(0, c.Height)
	r.LineTo(0, 0)
	r.Close()
	r.FillStroke()
}

func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
	r.SetFillColor(c.Canvas.GetFillColor(DefaultCanvasColor))
	r.SetStrokeColor(c.Canvas.GetStrokeColor(DefaultCanvasStrokColor))
	r.SetLineWidth(c.Canvas.GetStrokeWidth(DefaultStrokeWidth))
	r.MoveTo(canvasBox.Left, canvasBox.Top)
	r.LineTo(canvasBox.Right, canvasBox.Top)
	r.LineTo(canvasBox.Right, canvasBox.Bottom)
	r.LineTo(canvasBox.Left, canvasBox.Bottom)
	r.LineTo(canvasBox.Left, canvasBox.Top)
	r.Close()
	r.FillStroke()
}

func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange Range) {
	if c.Axes.Show {
		r.SetStrokeColor(c.Axes.GetStrokeColor(DefaultAxisColor))
		r.SetLineWidth(c.Axes.GetStrokeWidth(DefaultStrokeWidth))
		r.MoveTo(canvasBox.Left, canvasBox.Bottom)
		r.LineTo(canvasBox.Right, canvasBox.Bottom)
		r.LineTo(canvasBox.Right, canvasBox.Top)
		r.Stroke()

		c.drawXAxisLabels(r, canvasBox, xrange)
		c.drawYAxisLabels(r, canvasBox, yrange)
	}
}

func (c Chart) drawYAxisLabels(r Renderer, canvasBox Box, yrange Range) {
	tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)

	r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
	r.SetFontSize(tickFontSize)

	minimumTickHeight := tickFontSize + DefaultMinimumTickVerticalSpacing
	tickCount := int(math.Floor(float64(yrange.Domain) / float64(minimumTickHeight)))

	if tickCount > DefaultMaxTickCount {
		tickCount = DefaultMaxTickCount
	}

	rangeTicks := Slices(tickCount, yrange.Max-yrange.Min)
	domainTicks := Slices(tickCount, float64(yrange.Domain))

	asw := c.getAxisWidth()
	tx := canvasBox.Right + DefaultFinalLabelDeltaWidth + asw

	count := len(rangeTicks)
	if len(domainTicks) < count {
		count = len(domainTicks) //guard against mismatched array sizes.
	}

	for index := 0; index < count; index++ {
		v := rangeTicks[index] + yrange.Min
		y := domainTicks[index]
		ty := canvasBox.Bottom - int(y)
		r.Text(yrange.Format(v), tx, ty)
	}
}

func (c Chart) drawXAxisLabels(r Renderer, canvasBox Box, xrange Range) {
	tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)

	r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
	r.SetFontSize(tickFontSize)

	maxLabelWidth := 60

	minimumTickWidth := maxLabelWidth + DefaultMinimumTickHorizontalSpacing
	tickCount := int(math.Floor(float64(xrange.Domain) / float64(minimumTickWidth)))

	if tickCount > DefaultMaxTickCount {
		tickCount = DefaultMaxTickCount
	}

	rangeTicks := Slices(tickCount, xrange.Max-xrange.Min)
	domainTicks := Slices(tickCount, float64(xrange.Domain))

	ty := canvasBox.Bottom + DefaultXAxisMargin + int(tickFontSize)

	count := len(rangeTicks)
	if len(domainTicks) < count {
		count = len(domainTicks) //guard against mismatched array sizes.
	}

	for index := 0; index < count; index++ {
		v := rangeTicks[index] + xrange.Min
		x := domainTicks[index]
		tx := canvasBox.Left + int(x)
		r.Text(xrange.Format(v), tx, ty)
	}
}

func (c Chart) drawSeries(r Renderer, canvasBox Box, index int, s Series, xrange, yrange Range) {
	r.SetStrokeColor(s.GetStyle().GetStrokeColor(GetDefaultSeriesStrokeColor(index)))
	r.SetLineWidth(s.GetStyle().GetStrokeWidth(DefaultStrokeWidth))

	if s.Len() == 0 {
		return
	}

	cx := canvasBox.Left
	cy := canvasBox.Top
	cw := canvasBox.Width

	v0x, v0y := s.GetValue(0)
	x0 := cw - xrange.Translate(v0x)
	y0 := yrange.Translate(v0y)
	r.MoveTo(x0+cx, y0+cy)

	var vx, vy float64
	var x, y int
	for i := 1; i < s.Len(); i++ {
		vx, vy = s.GetValue(i)
		x = cw - xrange.Translate(vx)
		y = yrange.Translate(vy)
		r.LineTo(x+cx, y+cy)
	}
	r.Stroke()

	c.drawFinalValueLabel(r, canvasBox, index, s, yrange)
}

func (c Chart) drawFinalValueLabel(r Renderer, canvasBox Box, index int, s Series, yrange Range) {
	if c.FinalValueLabel.Show {
		_, lv := s.GetValue(s.Len() - 1)
		ll := s.GetYFormatter()(lv)

		py := canvasBox.Top
		ly := yrange.Translate(lv) + py

		r.SetFontSize(c.FinalValueLabel.GetFontSize(DefaultFinalLabelFontSize))
		textWidth := r.MeasureText(ll)
		textHeight := int(math.Floor(DefaultFinalLabelFontSize))
		halfTextHeight := textHeight >> 1

		asw := 0
		if c.Axes.Show {
			asw = int(c.Axes.GetStrokeWidth(DefaultAxisLineWidth))
		}

		cx := canvasBox.Right + asw

		pt := c.FinalValueLabel.Padding.GetTop(DefaultFinalLabelPadding.Top)
		pl := c.FinalValueLabel.Padding.GetLeft(DefaultFinalLabelPadding.Left)
		pr := c.FinalValueLabel.Padding.GetRight(DefaultFinalLabelPadding.Right)
		pb := c.FinalValueLabel.Padding.GetBottom(DefaultFinalLabelPadding.Bottom)

		textX := cx + pl + DefaultFinalLabelDeltaWidth
		textY := ly + halfTextHeight

		ltlx := cx + pl + DefaultFinalLabelDeltaWidth
		ltly := ly - (pt + halfTextHeight)

		ltrx := cx + pl + pr + textWidth
		ltry := ly - (pt + halfTextHeight)

		lbrx := cx + pl + pr + textWidth
		lbry := ly + (pb + halfTextHeight)

		lblx := cx + DefaultFinalLabelDeltaWidth
		lbly := ly + (pb + halfTextHeight)

		//draw the shape...
		r.SetFillColor(c.FinalValueLabel.GetFillColor(DefaultFinalLabelBackgroundColor))
		r.SetStrokeColor(c.FinalValueLabel.GetStrokeColor(s.GetStyle().GetStrokeColor(GetDefaultSeriesStrokeColor(index))))
		r.SetLineWidth(c.FinalValueLabel.GetStrokeWidth(DefaultAxisLineWidth))
		r.MoveTo(cx, ly)
		r.LineTo(ltlx, ltly)
		r.LineTo(ltrx, ltry)
		r.LineTo(lbrx, lbry)
		r.LineTo(lblx, lbly)
		r.LineTo(cx, ly)
		r.Close()
		r.FillStroke()

		r.SetFontColor(c.FinalValueLabel.GetFontColor(DefaultTextColor))
		r.Text(ll, textX, textY)
	}
}

func (c Chart) drawTitle(r Renderer) error {
	if len(c.Title) > 0 && c.TitleStyle.Show {
		r.SetFontColor(c.Canvas.GetFontColor(DefaultTextColor))
		titleFontSize := c.Canvas.GetFontSize(DefaultTitleFontSize)
		r.SetFontSize(titleFontSize)
		textWidth := r.MeasureText(c.Title)
		titleX := (c.Width >> 1) - (textWidth >> 1)
		titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + int(titleFontSize)
		r.Text(c.Title, titleX, titleY)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url     string
	text    string
	color   string
	channel string
}

type connection struct {
	protocol string
	address  string
}

func checkError(err error) bool {
	if err == nil {
		return false
	}
	log.Printf("error: %s", err)
	return true
}

func (c *connection) conn() (net.Conn, bool) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	errBool := checkError(err)
	return conn, errBool
}

func (a *alertMessage) sentSlack() {
	msg := slackhookgo.NewSlackMessage(
		"Alert",
		a.channel,
	).AddAttachment(
		slackhookgo.MessageAttachment{
			Color: a.color,
			Text:  a.text,
			Title: "<!channel>",
		},
	)
	msg.IconEmoji = ":exclamation:"
	err := slackhookgo.Send(a.url, msg)
	checkError(err)
}

func main() {
	var (
		color     string
		status    string
		lastState bool
		protocol  = flag.String("protocol", "tcp", "protocol tcp/udp")
		host      = flag.String("host", "", "destination host")
		port      = flag.Uint("port", 80, "destination port")
		interval  = flag.Uint("interval", 15, "interval check seconds")
		url       = flag.String("url", "", "hook url")
		slack     = flag.Bool("slack", false, "use -slack for send alert in slack")
		channel   = flag.String("channel", "general", "slack channel")
	)

        flag.StringVar(protocol, "p", "tcp", "protocol tcp/udp")
        flag.StringVar(host, "h", "", "destination host")
        flag.UintVar(interval, "i", 15, "interval check seconds")

	flag.Parse()

	if len(os.Args) == 1 {
		flag.PrintDefaults()
		os.Exit(1)
	} else if *host == "" {
		log.Fatal("param '-host' is empty")
	} else if *slack == true {
		if *url == "" {
			log.Fatal("param '-url' is empty")
		}
	}

	c := connection{
		protocol: *protocol,
		address:  fmt.Sprintf("%s:%v", *host, *port),
	}

	for {
		conn, err := c.conn()
		if err != lastState {
			if err == false { // normal
				conn.Close()
				color = "good"
				status = "reachable"
			} else { // not normal
				color = "danger"
				status = "unreachable"
			}
			lastState = err // key of success
			if *slack == true {
				am := alertMessage{
					channel: *channel,
					color:   color,
					text:    fmt.Sprintf("Destination host %s:%v %s\n", *host, *port, status),
					url:     *url,
				}
				am.sentSlack()
			}
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<commit_msg>upd flag -help<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url     string
	text    string
	color   string
	channel string
}

type connection struct {
	protocol string
	address  string
}

func checkError(err error) bool {
	if err == nil {
		return false
	}
	log.Printf("error: %s", err)
	return true
}

func (c *connection) conn() (net.Conn, bool) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	errBool := checkError(err)
	return conn, errBool
}

func (a *alertMessage) sentSlack() {
	msg := slackhookgo.NewSlackMessage(
		"Alert",
		a.channel,
	).AddAttachment(
		slackhookgo.MessageAttachment{
			Color: a.color,
			Text:  a.text,
			Title: "<!channel>",
		},
	)
	msg.IconEmoji = ":exclamation:"
	err := slackhookgo.Send(a.url, msg)
	checkError(err)
}

func main() {
	var (
		color     string
		status    string
		lastState bool
		protocol  = flag.String("protocol", "tcp", "protocol tcp/udp")
		host      = flag.String("host", "", "destination host")
		port      = flag.Uint("port", 80, "destination port")
		interval  = flag.Uint("interval", 15, "interval check seconds")
		url       = flag.String("url", "", "hook url")
		slack     = flag.Bool("slack", false, "use -slack for send alert in slack")
		channel   = flag.String("channel", "general", "slack channel")
		help	  = flag.Bool("help", false ,"use -help to see this information")
	)

        flag.StringVar(protocol, "p", "tcp", "protocol tcp/udp")
        flag.StringVar(host, "h", "", "destination host")
        flag.UintVar(interval, "i", 15, "interval check seconds")

	flag.Parse()

	if len(os.Args) == 1 {
		flag.PrintDefaults()
		os.Exit(1)
	} else if *help == true {
		flag.PrintDefaults()
		os.Exit(0)
	} else if *host == "" {
		log.Fatal("param '-host' is empty")
	} else if *slack == true {
		if *url == "" {
			log.Fatal("param '-url' is empty")
		}
	}

	c := connection{
		protocol: *protocol,
		address:  fmt.Sprintf("%s:%v", *host, *port),
	}

	for {
		conn, err := c.conn()
		if err != lastState {
			if err == false { // normal
				conn.Close()
				color = "good"
				status = "reachable"
			} else { // not normal
				color = "danger"
				status = "unreachable"
			}
			lastState = err // key of success
			if *slack == true {
				am := alertMessage{
					channel: *channel,
					color:   color,
					text:    fmt.Sprintf("Destination host %s:%v %s\n", *host, *port, status),
					url:     *url,
				}
				am.sentSlack()
			}
		}
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<|endoftext|>


<commit_before>// `chloe` is a cli binary which serves as a companion to `bower`. Its
// single purpose is to list and delete any files not required as part of
// the `bower_dependencies`.
//
// `chloe` will scan your `bower.json` file for ignore and must-preserve
// files and directories, and cull any extra junk fetched by `bower`.
// Do remember that if you delete even the `README.md` file from a bower
// package - it will prompt bower to re-fetch it on the next update.
package main

import (
    "log"
    "os"
    "strings"
    "io/ioutil"
    "path/filepath"

    "github.com/sabhiram/colorize"
    "github.com/sabhiram/go-git-ignore"

    "github.com/jessevdk/go-flags"
)

// Define application constants
const (
    // Set `debugLoggingEnabled` to `true` if you want debug spew
    debugLoggingEnabled = true

    // Set `traceLoggingEnabled` to `true` if you want function entry spew
    traceLoggingEnabled = true
)

var _ = ignore.CompileIgnoreFile

// Define application globals
var (
    // Trace is used for function enter exit logging
    Trace *log.Logger

    // Debug is enabled for arbitary logging
    Debug *log.Logger

    // Warning and error speak for themselves
    Warn  *log.Logger
    Error *log.Logger

    // Output is any stuff we wish to print to the screen
    Output *log.Logger

    // Define holders for the cli arguments we wish to parse
    Options struct {
        Version bool   `short:"v" long:"version" description:"Print application version"`
        Help    bool   `short:"h" long:"help" description:"Prints this help menu"`
        File    string `short:"f" long:"file" description:"Set the file to be read. Default bower.json" default:"bower.json"`
    }
)

// Sets up any application logging, and any other startup-y
// things we might need to do when this package is used (first-time)
func init() {
    var debugWriter = ioutil.Discard
    if debugLoggingEnabled {
        debugWriter = os.Stdout
    }

    var traceWriter = ioutil.Discard
    if traceLoggingEnabled {
        traceWriter = os.Stdout
    }

    Trace = log.New(traceWriter,
        colorize.ColorString("TRACE: ", "magenta"),
        log.Ldate|log.Ltime)

    Debug = log.New(debugWriter,
        colorize.ColorString("DEBUG: ", "green"),
        log.Ldate|log.Ltime)

    Warn = log.New(os.Stdout,
        colorize.ColorString("WARN:  ", "yellow"),
        log.Ldate|log.Ltime)

    Error = log.New(os.Stderr,
        colorize.ColorString("ERROR: ", "red"),
        log.Ldate|log.Ltime)

    Output = log.New(os.Stdout, "", 0)
}

func getIgnoreObjectFromJSONFile(f string) *ignore.GitIgnore {
    Trace.Printf("getIgnoreObjectFromJSONFile(%s)\n", f)
    lines := []string{".git"}
    object, _ := ignore.CompileIgnoreLines(lines...)
    return object
}

// Executes the `chloe list` command
func chloeList() {
    Trace.Println("chloeList()")
    ignoreObject := getIgnoreObjectFromJSONFile(Options.File)

    // TODO: Walk script dir, ignoreObject must be valid
    if ignoreObject == nil {
        Error.Println("Ignore object is null")
    }

    workingDir, err := os.Getwd()
    if err == nil {
        visit := func(path string, fileInfo os.FileInfo, err error) error {
            relPath, _ := filepath.Rel(workingDir, path)
            if ignoreObject.IgnoresPath(relPath) {
                //Debug.Println(relPath + " is ignored")
            } else {
                Debug.Println(relPath + " is included")
            }
            return nil
        }
        err = filepath.Walk(workingDir, visit)
        if err != nil {
            Debug.Println("Error is: " + err.Error())
        }
    } else {
        Debug.Println("Error is: " + err.Error())
    }
}

// Executes the `chloe dispatch` command
func chloeDispatch() {
    Trace.Println("chloeDispatch()")
    ignoreObject := getIgnoreObjectFromJSONFile(Options.File)

    // TODO: Walk script dir, ignoreObject must be valid
    if ignoreObject == nil {
        Error.Println("Ignore object is null")
    }
}

// Application entry-point for `chloe`. Responsible for parsing
// the cli args and invoking the appropriate action
func main() {
    Trace.Println("main()")

    // Parse arguments which might get passed to `chloe`
    parser := flags.NewParser(&Options, flags.Default & ^flags.HelpFlag)
    args, error := parser.Parse()
    command := strings.Join(args, " ")

    exitCode := 0
    switch {

    // Parse Error, print usage
    case error != nil:
        Output.Printf(getAppUsageString())
        exitCode = 1

    // No args, or help requested, print usage
    case len(os.Args) == 1 || Options.Help:
        Output.Printf(getAppUsageString())

    // `--version` requested
    case Options.Version:
        Output.Println(Version)

    // `list` command invoked
    case strings.ToLower(command) == "list":
        chloeList()

    // `dispatch` command invoked
    case strings.ToLower(command) == "dispatch":
        chloeDispatch()

    // All other cases go here!
    case true:
        Output.Printf("Unknown command %s, see usage:\n", colorize.ColorString(command, "red"))
        Output.Printf(getAppUsageString())
        exitCode = 1
    }
    os.Exit(exitCode)
}
<commit_msg>Fix spelling errors<commit_after>// `chloe` is a cli binary which serves as a companion to `bower`. Its
// single purpose is to list and delete any files not required as part of
// the `bower_dependencies`.
//
// `chloe` will scan your `bower.json` file for ignore and must-preserve
// files and directories, and cull any extra junk fetched by `bower`.
// Do remember that if you delete even the `README.md` file from a bower
// package - it will prompt bower to re-fetch it on the next update.
package main

import (
    "log"
    "os"
    "strings"
    "io/ioutil"
    "path/filepath"

    "github.com/sabhiram/colorize"
    "github.com/sabhiram/go-git-ignore"

    "github.com/jessevdk/go-flags"
)

// Define application constants
const (
    // Set `debugLoggingEnabled` to `true` if you want debug spew
    debugLoggingEnabled = true

    // Set `traceLoggingEnabled` to `true` if you want function entry spew
    traceLoggingEnabled = true
)

var _ = ignore.CompileIgnoreFile

// Define application globals
var (
    // Trace is used for function enter exit logging
    Trace *log.Logger

    // Debug is enabled for arbitrary logging
    Debug *log.Logger

    // Warning and error speak for themselves
    Warn  *log.Logger
    Error *log.Logger

    // Output is any stuff we wish to print to the screen
    Output *log.Logger

    // Define holders for the cli arguments we wish to parse
    Options struct {
        Version bool   `short:"v" long:"version" description:"Print application version"`
        Help    bool   `short:"h" long:"help" description:"Prints this help menu"`
        File    string `short:"f" long:"file" description:"Set the file to be read. Default bower.json" default:"bower.json"`
    }
)

// Sets up any application logging, and any other startup-y
// things we might need to do when this package is used (first-time)
func init() {
    var debugWriter = ioutil.Discard
    if debugLoggingEnabled {
        debugWriter = os.Stdout
    }

    var traceWriter = ioutil.Discard
    if traceLoggingEnabled {
        traceWriter = os.Stdout
    }

    Trace = log.New(traceWriter,
        colorize.ColorString("TRACE: ", "magenta"),
        log.Ldate|log.Ltime)

    Debug = log.New(debugWriter,
        colorize.ColorString("DEBUG: ", "green"),
        log.Ldate|log.Ltime)

    Warn = log.New(os.Stdout,
        colorize.ColorString("WARN:  ", "yellow"),
        log.Ldate|log.Ltime)

    Error = log.New(os.Stderr,
        colorize.ColorString("ERROR: ", "red"),
        log.Ldate|log.Ltime)

    Output = log.New(os.Stdout, "", 0)
}

func getIgnoreObjectFromJSONFile(f string) *ignore.GitIgnore {
    Trace.Printf("getIgnoreObjectFromJSONFile(%s)\n", f)
    lines := []string{".git"}
    object, _ := ignore.CompileIgnoreLines(lines...)
    return object
}

// Executes the `chloe list` command
func chloeList() {
    Trace.Println("chloeList()")
    ignoreObject := getIgnoreObjectFromJSONFile(Options.File)

    // TODO: Walk script directory, ignoreObject must be valid
    if ignoreObject == nil {
        Error.Println("Ignore object is null")
    }

    workingDir, err := os.Getwd()
    if err == nil {
        visit := func(path string, fileInfo os.FileInfo, err error) error {
            relPath, _ := filepath.Rel(workingDir, path)
            if ignoreObject.IgnoresPath(relPath) {
                //Debug.Println(relPath + " is ignored")
            } else {
                Debug.Println(relPath + " is included")
            }
            return nil
        }
        err = filepath.Walk(workingDir, visit)
        if err != nil {
            Debug.Println("Error is: " + err.Error())
        }
    } else {
        Debug.Println("Error is: " + err.Error())
    }
}

// Executes the `chloe dispatch` command
func chloeDispatch() {
    Trace.Println("chloeDispatch()")
    ignoreObject := getIgnoreObjectFromJSONFile(Options.File)

    // TODO: Walk script dir, ignoreObject must be valid
    if ignoreObject == nil {
        Error.Println("Ignore object is null")
    }
}

// Application entry-point for `chloe`. Responsible for parsing
// the cli arguments and invoking the appropriate action
func main() {
    Trace.Println("main()")

    // Parse arguments which might get passed to `chloe`
    parser := flags.NewParser(&Options, flags.Default & ^flags.HelpFlag)
    args, error := parser.Parse()
    command := strings.Join(args, " ")

    exitCode := 0
    switch {

    // Parse Error, print usage
    case error != nil:
        Output.Printf(getAppUsageString())
        exitCode = 1

    // No arguments, or help requested, print usage
    case len(os.Args) == 1 || Options.Help:
        Output.Printf(getAppUsageString())

    // `--version` requested
    case Options.Version:
        Output.Println(Version)

    // `list` command invoked
    case strings.ToLower(command) == "list":
        chloeList()

    // `dispatch` command invoked
    case strings.ToLower(command) == "dispatch":
        chloeDispatch()

    // All other cases go here!
    case true:
        Output.Printf("Unknown command %s, see usage:\n", colorize.ColorString(command, "red"))
        Output.Printf(getAppUsageString())
        exitCode = 1
    }
    os.Exit(exitCode)
}
<|endoftext|>