<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package aws

import (
	"fmt"
	"regexp"
	"strings"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	"k8s.io/autoscaler/cluster-autoscaler/config/dynamic"
	"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
	"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
)

// awsCloudProvider implements CloudProvider interface.
type awsCloudProvider struct {
	awsManager *AwsManager
	asgs       []*Asg
}

// BuildAwsCloudProvider builds CloudProvider implementation for AWS.
func BuildAwsCloudProvider(awsManager *AwsManager, discoveryOpts cloudprovider.NodeGroupDiscoveryOptions) (cloudprovider.CloudProvider, error) {
	if err := discoveryOpts.Validate(); err != nil {
		return nil, fmt.Errorf("Failed to build an aws cloud provider: %v", err)
	}
	if discoveryOpts.StaticDiscoverySpecified() {
		return buildStaticallyDiscoveringProvider(awsManager, discoveryOpts.NodeGroupSpecs)
	}
	if discoveryOpts.AutoDiscoverySpecified() {
		return buildAutoDiscoveringProvider(awsManager, discoveryOpts.NodeGroupAutoDiscoverySpec)
	}
	return nil, fmt.Errorf("Failed to build an aws cloud provider: Either node group specs or node group auto discovery spec must be specified")
}

func buildAutoDiscoveringProvider(awsManager *AwsManager, spec string) (*awsCloudProvider, error) {
	tokens := strings.Split(spec, ":")
	if len(tokens) != 2 {
		return nil, fmt.Errorf("Invalid node group auto discovery spec specified via --node-group-auto-discovery: %s", spec)
	}
	discoverer := tokens[0]
	if discoverer != "asg" {
		return nil, fmt.Errorf("Unsupported discoverer specified: %s", discoverer)
	}
	param := tokens[1]
	paramTokens := strings.Split(param, "=")
	parameterKey := paramTokens[0]
	if parameterKey != "tag" {
		return nil, fmt.Errorf("Unsupported parameter key \"%s\" is specified for discoverer \"%s\". The only supported key is \"tag\"", parameterKey, discoverer)
	}
	tag := paramTokens[1]
	if tag == "" {
		return nil, fmt.Errorf("Invalid ASG tag for auto discovery specified: ASG tag must not be empty")
	}
	// Use the k8s cluster name tag to only discover asgs of the cluster denoted by clusterName
	// See https://github.com/kubernetes/kubernetes/blob/9ef85a7/pkg/cloudprovider/providers/aws/tags.go#L30-L34
	// for more information about the tag
	tags := strings.Split(tag, ",")
	asgs, err := awsManager.getAutoscalingGroupsByTags(tags)
	if err != nil {
		return nil, fmt.Errorf("Failed to get ASGs: %v", err)
	}

	aws := &awsCloudProvider{
		awsManager: awsManager,
		asgs:       make([]*Asg, 0),
	}
	for _, asg := range asgs {
		aws.addAsg(buildAsg(aws.awsManager, int(*asg.MinSize), int(*asg.MaxSize), *asg.AutoScalingGroupName))
	}
	return aws, nil
}

func buildStaticallyDiscoveringProvider(awsManager *AwsManager, specs []string) (*awsCloudProvider, error) {
	aws := &awsCloudProvider{
		awsManager: awsManager,
		asgs:       make([]*Asg, 0),
	}
	for _, spec := range specs {
		if err := aws.addNodeGroup(spec); err != nil {
			return nil, err
		}
	}
	return aws, nil
}

// addNodeGroup adds node group defined in string spec. Format:
// minNodes:maxNodes:asgName
func (aws *awsCloudProvider) addNodeGroup(spec string) error {
	asg, err := buildAsgFromSpec(spec, aws.awsManager)
	if err != nil {
		return err
	}
	aws.addAsg(asg)
	return nil
}

// addAsg adds and registers an asg to this cloud provider
func (aws *awsCloudProvider) addAsg(asg *Asg) {
	aws.asgs = append(aws.asgs, asg)
	aws.awsManager.RegisterAsg(asg)
}

// Name returns name of the cloud provider.
func (aws *awsCloudProvider) Name() string {
	return "aws"
}

// NodeGroups returns all node groups configured for this cloud provider.
func (aws *awsCloudProvider) NodeGroups() []cloudprovider.NodeGroup {
	result := make([]cloudprovider.NodeGroup, 0, len(aws.asgs))
	for _, asg := range aws.asgs {
		result = append(result, asg)
	}
	return result
}

// NodeGroupForNode returns the node group for the given node.
func (aws *awsCloudProvider) NodeGroupForNode(node *apiv1.Node) (cloudprovider.NodeGroup, error) {
	ref, err := AwsRefFromProviderId(node.Spec.ProviderID)
	if err != nil {
		return nil, err
	}
	asg, err := aws.awsManager.GetAsgForInstance(ref)
	return asg, err
}

// Pricing returns pricing model for this cloud provider or error if not available.
func (aws *awsCloudProvider) Pricing() (cloudprovider.PricingModel, errors.AutoscalerError) {
	return nil, cloudprovider.ErrNotImplemented
}

// GetAvilableMachineTypes get all machine types that can be requested from the cloud provider.
func (aws *awsCloudProvider) GetAvilableMachineTypes() ([]string, error) {
	return []string{}, nil
}

// NewNodeGroup builds a theoretical node group based on the node definition provided. The node group is not automatically
// created on the cloud provider side. The node group is not returned by NodeGroups() until it is created.
func (aws *awsCloudProvider) NewNodeGroup(machineType string, labels map[string]string, extraResources map[string]resource.Quantity) (cloudprovider.NodeGroup, error) {
	return nil, cloudprovider.ErrNotImplemented
}

// AwsRef contains a reference to some entity in AWS/GKE world.
type AwsRef struct {
	Name string
}

// AwsRefFromProviderId creates InstanceConfig object from provider id which
// must be in format: aws:///zone/name
func AwsRefFromProviderId(id string) (*AwsRef, error) {
	validIdRegex := regexp.MustCompile(`^aws\:\/\/\/[-0-9a-z]*\/[-0-9a-z]*$`)
	if validIdRegex.FindStringSubmatch(id) == nil {
		return nil, fmt.Errorf("Wrong id: expected format aws:///<zone>/<name>, got %v", id)
	}
	splitted := strings.Split(id[7:], "/")
	return &AwsRef{
		Name: splitted[1],
	}, nil
}

// Asg implements NodeGroup interface.
type Asg struct {
	AwsRef

	awsManager *AwsManager

	minSize int
	maxSize int
}

// MaxSize returns maximum size of the node group.
func (asg *Asg) MaxSize() int {
	return asg.maxSize
}

// MinSize returns minimum size of the node group.
func (asg *Asg) MinSize() int {
	return asg.minSize
}

// TargetSize returns the current TARGET size of the node group. It is possible that the
// number is different from the number of nodes registered in Kubernetes.
func (asg *Asg) TargetSize() (int, error) {
	size, err := asg.awsManager.GetAsgSize(asg)
	return int(size), err
}

// Exist checks if the node group really exists on the cloud provider side. Allows to tell the
// theoretical node group from the real one.
func (asg *Asg) Exist() bool {
	return true
}

// Create creates the node group on the cloud provider side.
func (asg *Asg) Create() error {
	return cloudprovider.ErrAlreadyExist
}

// Autoprovisioned returns true if the node group is autoprovisioned.
func (asg *Asg) Autoprovisioned() bool {
	return false
}

// Delete deletes the node group on the cloud provider side.
// This will be executed only for autoprovisioned node groups, once their size drops to 0.
func (asg *Asg) Delete() error {
	return cloudprovider.ErrNotImplemented
}

// IncreaseSize increases Asg size
func (asg *Asg) IncreaseSize(delta int) error {
	if delta <= 0 {
		return fmt.Errorf("size increase must be positive")
	}
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	if int(size)+delta > asg.MaxSize() {
		return fmt.Errorf("size increase too large - desired:%d max:%d", int(size)+delta, asg.MaxSize())
	}
	return asg.awsManager.SetAsgSize(asg, size+int64(delta))
}

// DecreaseTargetSize decreases the target size of the node group. This function
// doesn't permit to delete any existing node and can be used only to reduce the
// request for new nodes that have not been yet fulfilled. Delta should be negative.
// It is assumed that cloud provider will not delete the existing nodes if the size
// when there is an option to just decrease the target.
func (asg *Asg) DecreaseTargetSize(delta int) error {
	if delta >= 0 {
		return fmt.Errorf("size decrease size must be negative")
	}
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	nodes, err := asg.awsManager.GetAsgNodes(asg)
	if err != nil {
		return err
	}
	if int(size)+delta < len(nodes) {
		return fmt.Errorf("attempt to delete existing nodes targetSize:%d delta:%d existingNodes: %d",
			size, delta, len(nodes))
	}
	return asg.awsManager.SetAsgSize(asg, size+int64(delta))
}

// Belongs returns true if the given node belongs to the NodeGroup.
func (asg *Asg) Belongs(node *apiv1.Node) (bool, error) {
	ref, err := AwsRefFromProviderId(node.Spec.ProviderID)
	if err != nil {
		return false, err
	}
	targetAsg, err := asg.awsManager.GetAsgForInstance(ref)
	if err != nil {
		return false, err
	}
	if targetAsg == nil {
		return false, fmt.Errorf("%s doesn't belong to a known asg", node.Name)
	}
	if targetAsg.Id() != asg.Id() {
		return false, nil
	}
	return true, nil
}

// DeleteNodes deletes the nodes from the group.
func (asg *Asg) DeleteNodes(nodes []*apiv1.Node) error {
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	if int(size) <= asg.MinSize() {
		return fmt.Errorf("min size reached, nodes will not be deleted")
	}
	refs := make([]*AwsRef, 0, len(nodes))
	for _, node := range nodes {
		belongs, err := asg.Belongs(node)
		if err != nil {
			return err
		}
		if belongs != true {
			return fmt.Errorf("%s belongs to a different asg than %s", node.Name, asg.Id())
		}
		awsref, err := AwsRefFromProviderId(node.Spec.ProviderID)
		if err != nil {
			return err
		}
		refs = append(refs, awsref)
	}
	return asg.awsManager.DeleteInstances(refs)
}

// Id returns asg id.
func (asg *Asg) Id() string {
	return asg.Name
}

// Debug returns a debug string for the Asg.
func (asg *Asg) Debug() string {
	return fmt.Sprintf("%s (%d:%d)", asg.Id(), asg.MinSize(), asg.MaxSize())
}

// Nodes returns a list of all nodes that belong to this node group.
func (asg *Asg) Nodes() ([]string, error) {
	return asg.awsManager.GetAsgNodes(asg)
}

// TemplateNodeInfo returns a node template for this node group.
func (asg *Asg) TemplateNodeInfo() (*schedulercache.NodeInfo, error) {
	template, err := asg.awsManager.getAsgTemplate(asg.Name)
	if err != nil {
		return nil, err
	}

	node, err := asg.awsManager.buildNodeFromTemplate(asg, template)
	if err != nil {
		return nil, err
	}

	nodeInfo := schedulercache.NewNodeInfo(cloudprovider.BuildKubeProxy(asg.Name))
	nodeInfo.SetNode(node)
	return nodeInfo, nil
}

func buildAsgFromSpec(value string, awsManager *AwsManager) (*Asg, error) {
	spec, err := dynamic.SpecFromString(value, true)

	if err != nil {
		return nil, fmt.Errorf("failed to parse node group spec: %v", err)
	}

	asg := buildAsg(awsManager, spec.MinSize, spec.MaxSize, spec.Name)

	return asg, nil
}

func buildAsg(awsManager *AwsManager, minSize int, maxSize int, name string) *Asg {
	return &Asg{
		awsManager: awsManager,
		minSize:    minSize,
		maxSize:    maxSize,
		AwsRef: AwsRef{
			Name: name,
		},
	}
}
<commit_msg>Move regexp.MustCompile in AWS provider to global variable<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package aws

import (
	"fmt"
	"regexp"
	"strings"

	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
	"k8s.io/autoscaler/cluster-autoscaler/config/dynamic"
	"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
	"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
)

// awsCloudProvider implements CloudProvider interface.
type awsCloudProvider struct {
	awsManager *AwsManager
	asgs       []*Asg
}

// BuildAwsCloudProvider builds CloudProvider implementation for AWS.
func BuildAwsCloudProvider(awsManager *AwsManager, discoveryOpts cloudprovider.NodeGroupDiscoveryOptions) (cloudprovider.CloudProvider, error) {
	if err := discoveryOpts.Validate(); err != nil {
		return nil, fmt.Errorf("Failed to build an aws cloud provider: %v", err)
	}
	if discoveryOpts.StaticDiscoverySpecified() {
		return buildStaticallyDiscoveringProvider(awsManager, discoveryOpts.NodeGroupSpecs)
	}
	if discoveryOpts.AutoDiscoverySpecified() {
		return buildAutoDiscoveringProvider(awsManager, discoveryOpts.NodeGroupAutoDiscoverySpec)
	}
	return nil, fmt.Errorf("Failed to build an aws cloud provider: Either node group specs or node group auto discovery spec must be specified")
}

func buildAutoDiscoveringProvider(awsManager *AwsManager, spec string) (*awsCloudProvider, error) {
	tokens := strings.Split(spec, ":")
	if len(tokens) != 2 {
		return nil, fmt.Errorf("Invalid node group auto discovery spec specified via --node-group-auto-discovery: %s", spec)
	}
	discoverer := tokens[0]
	if discoverer != "asg" {
		return nil, fmt.Errorf("Unsupported discoverer specified: %s", discoverer)
	}
	param := tokens[1]
	paramTokens := strings.Split(param, "=")
	parameterKey := paramTokens[0]
	if parameterKey != "tag" {
		return nil, fmt.Errorf("Unsupported parameter key \"%s\" is specified for discoverer \"%s\". The only supported key is \"tag\"", parameterKey, discoverer)
	}
	tag := paramTokens[1]
	if tag == "" {
		return nil, fmt.Errorf("Invalid ASG tag for auto discovery specified: ASG tag must not be empty")
	}
	// Use the k8s cluster name tag to only discover asgs of the cluster denoted by clusterName
	// See https://github.com/kubernetes/kubernetes/blob/9ef85a7/pkg/cloudprovider/providers/aws/tags.go#L30-L34
	// for more information about the tag
	tags := strings.Split(tag, ",")
	asgs, err := awsManager.getAutoscalingGroupsByTags(tags)
	if err != nil {
		return nil, fmt.Errorf("Failed to get ASGs: %v", err)
	}

	aws := &awsCloudProvider{
		awsManager: awsManager,
		asgs:       make([]*Asg, 0),
	}
	for _, asg := range asgs {
		aws.addAsg(buildAsg(aws.awsManager, int(*asg.MinSize), int(*asg.MaxSize), *asg.AutoScalingGroupName))
	}
	return aws, nil
}

func buildStaticallyDiscoveringProvider(awsManager *AwsManager, specs []string) (*awsCloudProvider, error) {
	aws := &awsCloudProvider{
		awsManager: awsManager,
		asgs:       make([]*Asg, 0),
	}
	for _, spec := range specs {
		if err := aws.addNodeGroup(spec); err != nil {
			return nil, err
		}
	}
	return aws, nil
}

// addNodeGroup adds node group defined in string spec. Format:
// minNodes:maxNodes:asgName
func (aws *awsCloudProvider) addNodeGroup(spec string) error {
	asg, err := buildAsgFromSpec(spec, aws.awsManager)
	if err != nil {
		return err
	}
	aws.addAsg(asg)
	return nil
}

// addAsg adds and registers an asg to this cloud provider
func (aws *awsCloudProvider) addAsg(asg *Asg) {
	aws.asgs = append(aws.asgs, asg)
	aws.awsManager.RegisterAsg(asg)
}

// Name returns name of the cloud provider.
func (aws *awsCloudProvider) Name() string {
	return "aws"
}

// NodeGroups returns all node groups configured for this cloud provider.
func (aws *awsCloudProvider) NodeGroups() []cloudprovider.NodeGroup {
	result := make([]cloudprovider.NodeGroup, 0, len(aws.asgs))
	for _, asg := range aws.asgs {
		result = append(result, asg)
	}
	return result
}

// NodeGroupForNode returns the node group for the given node.
func (aws *awsCloudProvider) NodeGroupForNode(node *apiv1.Node) (cloudprovider.NodeGroup, error) {
	ref, err := AwsRefFromProviderId(node.Spec.ProviderID)
	if err != nil {
		return nil, err
	}
	asg, err := aws.awsManager.GetAsgForInstance(ref)
	return asg, err
}

// Pricing returns pricing model for this cloud provider or error if not available.
func (aws *awsCloudProvider) Pricing() (cloudprovider.PricingModel, errors.AutoscalerError) {
	return nil, cloudprovider.ErrNotImplemented
}

// GetAvilableMachineTypes get all machine types that can be requested from the cloud provider.
func (aws *awsCloudProvider) GetAvilableMachineTypes() ([]string, error) {
	return []string{}, nil
}

// NewNodeGroup builds a theoretical node group based on the node definition provided. The node group is not automatically
// created on the cloud provider side. The node group is not returned by NodeGroups() until it is created.
func (aws *awsCloudProvider) NewNodeGroup(machineType string, labels map[string]string, extraResources map[string]resource.Quantity) (cloudprovider.NodeGroup, error) {
	return nil, cloudprovider.ErrNotImplemented
}

// AwsRef contains a reference to some entity in AWS/GKE world.
type AwsRef struct {
	Name string
}

var validAwsRefIdRegex = regexp.MustCompile(`^aws\:\/\/\/[-0-9a-z]*\/[-0-9a-z]*$`)

// AwsRefFromProviderId creates InstanceConfig object from provider id which
// must be in format: aws:///zone/name
func AwsRefFromProviderId(id string) (*AwsRef, error) {
	if validAwsRefIdRegex.FindStringSubmatch(id) == nil {
		return nil, fmt.Errorf("Wrong id: expected format aws:///<zone>/<name>, got %v", id)
	}
	splitted := strings.Split(id[7:], "/")
	return &AwsRef{
		Name: splitted[1],
	}, nil
}

// Asg implements NodeGroup interface.
type Asg struct {
	AwsRef

	awsManager *AwsManager

	minSize int
	maxSize int
}

// MaxSize returns maximum size of the node group.
func (asg *Asg) MaxSize() int {
	return asg.maxSize
}

// MinSize returns minimum size of the node group.
func (asg *Asg) MinSize() int {
	return asg.minSize
}

// TargetSize returns the current TARGET size of the node group. It is possible that the
// number is different from the number of nodes registered in Kubernetes.
func (asg *Asg) TargetSize() (int, error) {
	size, err := asg.awsManager.GetAsgSize(asg)
	return int(size), err
}

// Exist checks if the node group really exists on the cloud provider side. Allows to tell the
// theoretical node group from the real one.
func (asg *Asg) Exist() bool {
	return true
}

// Create creates the node group on the cloud provider side.
func (asg *Asg) Create() error {
	return cloudprovider.ErrAlreadyExist
}

// Autoprovisioned returns true if the node group is autoprovisioned.
func (asg *Asg) Autoprovisioned() bool {
	return false
}

// Delete deletes the node group on the cloud provider side.
// This will be executed only for autoprovisioned node groups, once their size drops to 0.
func (asg *Asg) Delete() error {
	return cloudprovider.ErrNotImplemented
}

// IncreaseSize increases Asg size
func (asg *Asg) IncreaseSize(delta int) error {
	if delta <= 0 {
		return fmt.Errorf("size increase must be positive")
	}
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	if int(size)+delta > asg.MaxSize() {
		return fmt.Errorf("size increase too large - desired:%d max:%d", int(size)+delta, asg.MaxSize())
	}
	return asg.awsManager.SetAsgSize(asg, size+int64(delta))
}

// DecreaseTargetSize decreases the target size of the node group. This function
// doesn't permit to delete any existing node and can be used only to reduce the
// request for new nodes that have not been yet fulfilled. Delta should be negative.
// It is assumed that cloud provider will not delete the existing nodes if the size
// when there is an option to just decrease the target.
func (asg *Asg) DecreaseTargetSize(delta int) error {
	if delta >= 0 {
		return fmt.Errorf("size decrease size must be negative")
	}
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	nodes, err := asg.awsManager.GetAsgNodes(asg)
	if err != nil {
		return err
	}
	if int(size)+delta < len(nodes) {
		return fmt.Errorf("attempt to delete existing nodes targetSize:%d delta:%d existingNodes: %d",
			size, delta, len(nodes))
	}
	return asg.awsManager.SetAsgSize(asg, size+int64(delta))
}

// Belongs returns true if the given node belongs to the NodeGroup.
func (asg *Asg) Belongs(node *apiv1.Node) (bool, error) {
	ref, err := AwsRefFromProviderId(node.Spec.ProviderID)
	if err != nil {
		return false, err
	}
	targetAsg, err := asg.awsManager.GetAsgForInstance(ref)
	if err != nil {
		return false, err
	}
	if targetAsg == nil {
		return false, fmt.Errorf("%s doesn't belong to a known asg", node.Name)
	}
	if targetAsg.Id() != asg.Id() {
		return false, nil
	}
	return true, nil
}

// DeleteNodes deletes the nodes from the group.
func (asg *Asg) DeleteNodes(nodes []*apiv1.Node) error {
	size, err := asg.awsManager.GetAsgSize(asg)
	if err != nil {
		return err
	}
	if int(size) <= asg.MinSize() {
		return fmt.Errorf("min size reached, nodes will not be deleted")
	}
	refs := make([]*AwsRef, 0, len(nodes))
	for _, node := range nodes {
		belongs, err := asg.Belongs(node)
		if err != nil {
			return err
		}
		if belongs != true {
			return fmt.Errorf("%s belongs to a different asg than %s", node.Name, asg.Id())
		}
		awsref, err := AwsRefFromProviderId(node.Spec.ProviderID)
		if err != nil {
			return err
		}
		refs = append(refs, awsref)
	}
	return asg.awsManager.DeleteInstances(refs)
}

// Id returns asg id.
func (asg *Asg) Id() string {
	return asg.Name
}

// Debug returns a debug string for the Asg.
func (asg *Asg) Debug() string {
	return fmt.Sprintf("%s (%d:%d)", asg.Id(), asg.MinSize(), asg.MaxSize())
}

// Nodes returns a list of all nodes that belong to this node group.
func (asg *Asg) Nodes() ([]string, error) {
	return asg.awsManager.GetAsgNodes(asg)
}

// TemplateNodeInfo returns a node template for this node group.
func (asg *Asg) TemplateNodeInfo() (*schedulercache.NodeInfo, error) {
	template, err := asg.awsManager.getAsgTemplate(asg.Name)
	if err != nil {
		return nil, err
	}

	node, err := asg.awsManager.buildNodeFromTemplate(asg, template)
	if err != nil {
		return nil, err
	}

	nodeInfo := schedulercache.NewNodeInfo(cloudprovider.BuildKubeProxy(asg.Name))
	nodeInfo.SetNode(node)
	return nodeInfo, nil
}

func buildAsgFromSpec(value string, awsManager *AwsManager) (*Asg, error) {
	spec, err := dynamic.SpecFromString(value, true)

	if err != nil {
		return nil, fmt.Errorf("failed to parse node group spec: %v", err)
	}

	asg := buildAsg(awsManager, spec.MinSize, spec.MaxSize, spec.Name)

	return asg, nil
}

func buildAsg(awsManager *AwsManager, minSize int, maxSize int, name string) *Asg {
	return &Asg{
		awsManager: awsManager,
		minSize:    minSize,
		maxSize:    maxSize,
		AwsRef: AwsRef{
			Name: name,
		},
	}
}
<|endoftext|>


<commit_before>package gonline

import (
	"bufio"
	"math"
	"os"
	"strconv"
	"strings"
)

type Classifier struct {
	Weight    [][]float64
	FtDict    Dict
	LabelDict Dict
}

func NewClassifier() Classifier {
	c := Classifier{
		Weight:    make([][]float64, 0, 100),
		FtDict:    NewDict(),
		LabelDict: NewDict(),
	}
	return c
}

func (this *Classifier) Predict(x *map[string]float64) int {
	argmax := -1
	max := math.Inf(-1)

	for labelid := 0; labelid < len(this.Weight); labelid++ {
		dot := 0.
		w := this.Weight[labelid]
		for ft, val := range *x {
			ftid := this.FtDict.Elem2id[ft]
			if ftid >= len(w) {
				continue
			}
			dot += w[ftid] * val
		}
		if dot > max {
			max = dot
			argmax = labelid
		}
	}
	return argmax
}

func LoadClassifier(fname string) Classifier {
	cls := NewClassifier()
	model_f, err := os.OpenFile(fname, os.O_RDONLY, 0644)
	if err != nil {
		panic("Failed to load model")
	}
	reader := bufio.NewReaderSize(model_f, 4096*32)
	line, err := reader.ReadString('\n')
	if err != nil {
		panic(err)
	}
	labelsize, _ := strconv.Atoi(strings.Trim(strings.Split(line, "\t")[1], "\n"))

	line, err = reader.ReadString('\n')
	ftsize, _ := strconv.Atoi(strings.Trim(strings.Split(line, "\t")[1], "\n"))
	for i := 0; i < labelsize; i++ {
		line, err = reader.ReadString('\n')
		cls.LabelDict.AddElem(strings.Trim(line, "\n"))
	}
	for i := 0; i < ftsize; i++ {
		line, err = reader.ReadString('\n')
		cls.FtDict.AddElem(strings.Trim(line, "\n"))
	}

	cls.Weight = make([][]float64, labelsize, labelsize)
	for labelid := 0; labelid < labelsize; labelid++ {
		cls.Weight[labelid] = make([]float64, ftsize, ftsize)
		for ftid := 0; ftid < ftsize; ftid++ {
			line, err = reader.ReadString('\n')
			line = strings.Trim(line, "\n")
			w, _ := strconv.ParseFloat(line, 64)
			cls.Weight[labelid][ftid] = w
		}
	}
	return cls
}
<commit_msg>fix Predict<commit_after>package gonline

import (
	"bufio"
	"math"
	"os"
	"strconv"
	"strings"
)

type Classifier struct {
	Weight    [][]float64
	FtDict    Dict
	LabelDict Dict
}

func NewClassifier() Classifier {
	c := Classifier{
		Weight:    make([][]float64, 0, 100),
		FtDict:    NewDict(),
		LabelDict: NewDict(),
	}
	return c
}

func (this *Classifier) Predict(x *map[string]float64) int {
	argmax := -1
	max := math.Inf(-1)

	for labelid := 0; labelid < len(this.Weight); labelid++ {
		dot := 0.
		w := this.Weight[labelid]
		for ft, val := range *x {
			if !this.FtDict.HasElem(ft) {
				continue
			}
			ftid := this.FtDict.Elem2id[ft]
			dot += w[ftid] * val
		}
		if dot > max {
			max = dot
			argmax = labelid
		}
	}
	return argmax
}

func LoadClassifier(fname string) Classifier {
	cls := NewClassifier()
	model_f, err := os.OpenFile(fname, os.O_RDONLY, 0644)
	if err != nil {
		panic("Failed to load model")
	}
	reader := bufio.NewReaderSize(model_f, 4096*32)
	line, err := reader.ReadString('\n')
	if err != nil {
		panic(err)
	}
	labelsize, _ := strconv.Atoi(strings.Trim(strings.Split(line, "\t")[1], "\n"))

	line, err = reader.ReadString('\n')
	ftsize, _ := strconv.Atoi(strings.Trim(strings.Split(line, "\t")[1], "\n"))
	for i := 0; i < labelsize; i++ {
		line, err = reader.ReadString('\n')
		cls.LabelDict.AddElem(strings.Trim(line, "\n"))
	}
	for i := 0; i < ftsize; i++ {
		line, err = reader.ReadString('\n')
		cls.FtDict.AddElem(strings.Trim(line, "\n"))
	}

	cls.Weight = make([][]float64, labelsize, labelsize)
	for labelid := 0; labelid < labelsize; labelid++ {
		cls.Weight[labelid] = make([]float64, ftsize, ftsize)
		for ftid := 0; ftid < ftsize; ftid++ {
			line, err = reader.ReadString('\n')
			line = strings.Trim(line, "\n")
			w, _ := strconv.ParseFloat(line, 64)
			cls.Weight[labelid][ftid] = w
		}
	}
	return cls
}
<|endoftext|>


<commit_before>package runkeeper

import (
	"log"
	"time"

	runkeeper "github.com/svdberg/syncmysport-runkeeper/Godeps/_workspace/src/github.com/c9s/go-runkeeper"
	dm "github.com/svdberg/syncmysport-runkeeper/datamodel"
)

const API = "API"

func ConvertToActivity(rkActivity *runkeeper.FitnessActivity) *dm.Activity {
	returnActivity := dm.CreateActivity()
	if rkActivity.Type == "Other" {
		returnActivity.Type = "Activity"
	} else {
		returnActivity.Type = rkActivity.Type
	}

	//RK time is 'Local', convert to UTC
	sourceLocation := time.FixedZone("RKSourceLocation", rkActivity.UtcOffset*60*60)
	correctedTime := time.Time(rkActivity.StartTime).In(sourceLocation)
	log.Printf("RK Local date: %s, start date: %s, unix: %d, offset: %d", time.Time(rkActivity.StartTime), correctedTime, time.Time(rkActivity.StartTime).Unix(), rkActivity.UtcOffset)
	returnActivity.StartTime = int(time.Time(correctedTime).Unix())
	returnActivity.UtcOffSet = rkActivity.UtcOffset
	returnActivity.Duration = int(rkActivity.Duration)
	returnActivity.Name = rkActivity.Notes
	returnActivity.Notes = rkActivity.Notes
	returnActivity.Private = false
	returnActivity.Stationary = rkActivity.HasMap
	returnActivity.AverageHeartRate = rkActivity.AverageHeartRate
	returnActivity.Calories = rkActivity.TotalCalories
	returnActivity.Distance = rkActivity.TotalDistance
	returnActivity.GPS = convertFromPath(rkActivity.Path)
	returnActivity.HeartRate = convertFromHR(rkActivity.HeartRate)

	// log.Printf("INPUT: %s, OUTPUT: %s", rkActivity, returnActivity)
	return returnActivity
}

func ConvertToRkActivity(activity *dm.Activity) *runkeeper.FitnessActivityNew {
	rkActivity := runkeeper.CreateNewFitnessActivity(activity.Name, float64(activity.Duration))

	rkActivity.Type = activity.Type
	//runkeeper only nows the following types:
	//Running, Cycling, Mountain Biking, Walking,
	//Hiking, Downhill Skiing, Cross-Country Skiing,
	//Snowboarding, Skating, Swimming, Wheelchair, Rowing, Elliptical, Other
	//
	//check if Type is one of these, otherwise Other.
	rkKnownTypes := map[string]string{
		"Running":              "Running",
		"Cycling":              "Cycling",
		"Mountain Biking":      "Mountain Biking",
		"Walking":              "Walking",
		"Hiking":               "Hiking",
		"Downhill Skiing":      "Downhill Skiing",
		"Cross-Country Skiing": "Cross-Country Skiing",
		"Snowboarding":         "Snowboarding",
		"Skating":              "Skating",
		"Swimming":             "Swimming",
		"Wheelchair":           "Wheelchair",
		"Rowing":               "Rowing",
		"Elliptical":           "Elliptical",
		"Other":                "Other"}

	_, ok := rkKnownTypes[activity.Type]
	if !ok {
		rkActivity.Type = "Other"
	}

	//runkeeper times are in local timezones, so covert back to the local time
	rkLocalLocation := time.FixedZone("rkZone", activity.UtcOffSet*60*60)
	rkActivity.StartTime = runkeeper.Time(time.Unix(int64(activity.StartTime), 0).In(rkLocalLocation))
	log.Printf("SMS time: %s, converted to RK time: %s for offset: %d", activity.StartTime, rkActivity.StartTime, activity.UtcOffSet)
	rkActivity.Notes = activity.Name
	rkActivity.TotalDistance = activity.Distance
	rkActivity.AverageHeartRate = activity.AverageHeartRate
	rkActivity.TotalCalories = activity.Calories
	rkActivity.Source = activity.Source
	rkActivity.EntryMode = API

	rkActivity.Path = convertToPath(activity.GPS)
	rkActivity.HeartRate = convertToHR(activity.HeartRate)
	return rkActivity
}

func convertToPath(gps []dm.GPS) []runkeeper.Path {
	rkPath := make([]runkeeper.Path, len(gps))
	for i, gp := range gps {
		rkPath[i] = runkeeper.Path{gp.Altitude, gp.Longitude, "gps", gp.Latitude, gp.Timestamp}
	}
	return rkPath
}

func convertFromPath(path []runkeeper.Path) []dm.GPS {
	dmPath := make([]dm.GPS, len(path))
	for i, rp := range path {
		dmPath[i] = dm.GPS{rp.Timestamp, rp.Altitude, rp.Longitude, rp.Latitude}
	}
	return dmPath
}

func convertToHR(hr []dm.HeartRate) []runkeeper.HeartRate {
	rkHr := make([]runkeeper.HeartRate, len(hr))
	for i, h := range hr {
		rkHr[i] = runkeeper.HeartRate{h.Timestamp, h.Heartrate}
	}
	return rkHr
}

func convertFromHR(rkHr []runkeeper.HeartRate) []dm.HeartRate {
	dmHr := make([]dm.HeartRate, len(rkHr))
	for i, h := range rkHr {
		dmHr[i] = dm.HeartRate{h.TimeStamp, h.HearRateNr}
	}
	return dmHr
}
<commit_msg>Fix time logic error for RK<commit_after>package runkeeper

import (
	"log"
	"time"

	runkeeper "github.com/svdberg/syncmysport-runkeeper/Godeps/_workspace/src/github.com/c9s/go-runkeeper"
	dm "github.com/svdberg/syncmysport-runkeeper/datamodel"
)

const API = "API"

func ConvertToActivity(rkActivity *runkeeper.FitnessActivity) *dm.Activity {
	returnActivity := dm.CreateActivity()
	if rkActivity.Type == "Other" {
		returnActivity.Type = "Activity"
	} else {
		returnActivity.Type = rkActivity.Type
	}

	//RK time is 'Local', convert to UTC
	//TODO. This might be wrong and perhaps should be the negative zone (e.g. -1 * ..)
	negativeOffset := -1 * rkActivity.UtcOffset * 60 * 60
	sourceLocation := time.FixedZone("RKSourceLocation", negativeOffset)
	correctedTime := time.Time(rkActivity.StartTime).In(sourceLocation)
	log.Printf("RK Local date: %s, start date: %s, unix: %d, offset: %d", time.Time(rkActivity.StartTime), correctedTime, time.Time(rkActivity.StartTime).Unix(), rkActivity.UtcOffset)
	returnActivity.StartTime = int(time.Time(correctedTime).Unix())
	returnActivity.UtcOffSet = rkActivity.UtcOffset
	returnActivity.Duration = int(rkActivity.Duration)
	returnActivity.Name = rkActivity.Notes
	returnActivity.Notes = rkActivity.Notes
	returnActivity.Private = false
	returnActivity.Stationary = rkActivity.HasMap
	returnActivity.AverageHeartRate = rkActivity.AverageHeartRate
	returnActivity.Calories = rkActivity.TotalCalories
	returnActivity.Distance = rkActivity.TotalDistance
	returnActivity.GPS = convertFromPath(rkActivity.Path)
	returnActivity.HeartRate = convertFromHR(rkActivity.HeartRate)

	// log.Printf("INPUT: %s, OUTPUT: %s", rkActivity, returnActivity)
	return returnActivity
}

func ConvertToRkActivity(activity *dm.Activity) *runkeeper.FitnessActivityNew {
	rkActivity := runkeeper.CreateNewFitnessActivity(activity.Name, float64(activity.Duration))

	rkActivity.Type = activity.Type
	//runkeeper only nows the following types:
	//Running, Cycling, Mountain Biking, Walking,
	//Hiking, Downhill Skiing, Cross-Country Skiing,
	//Snowboarding, Skating, Swimming, Wheelchair, Rowing, Elliptical, Other
	//
	//check if Type is one of these, otherwise Other.
	rkKnownTypes := map[string]string{
		"Running":              "Running",
		"Cycling":              "Cycling",
		"Mountain Biking":      "Mountain Biking",
		"Walking":              "Walking",
		"Hiking":               "Hiking",
		"Downhill Skiing":      "Downhill Skiing",
		"Cross-Country Skiing": "Cross-Country Skiing",
		"Snowboarding":         "Snowboarding",
		"Skating":              "Skating",
		"Swimming":             "Swimming",
		"Wheelchair":           "Wheelchair",
		"Rowing":               "Rowing",
		"Elliptical":           "Elliptical",
		"Other":                "Other"}

	_, ok := rkKnownTypes[activity.Type]
	if !ok {
		rkActivity.Type = "Other"
	}

	//runkeeper times are in local timezones, so covert back to the local time
	rkLocalLocation := time.FixedZone("rkZone", activity.UtcOffSet*60*60)
	rkActivity.StartTime = runkeeper.Time(time.Unix(int64(activity.StartTime), 0).In(rkLocalLocation))
	log.Printf("SMS time: %s, converted to RK time: %s for offset: %d", activity.StartTime, rkActivity.StartTime, activity.UtcOffSet)
	rkActivity.Notes = activity.Name
	rkActivity.TotalDistance = activity.Distance
	rkActivity.AverageHeartRate = activity.AverageHeartRate
	rkActivity.TotalCalories = activity.Calories
	rkActivity.Source = activity.Source
	rkActivity.EntryMode = API

	rkActivity.Path = convertToPath(activity.GPS)
	rkActivity.HeartRate = convertToHR(activity.HeartRate)
	return rkActivity
}

func convertToPath(gps []dm.GPS) []runkeeper.Path {
	rkPath := make([]runkeeper.Path, len(gps))
	for i, gp := range gps {
		rkPath[i] = runkeeper.Path{gp.Altitude, gp.Longitude, "gps", gp.Latitude, gp.Timestamp}
	}
	return rkPath
}

func convertFromPath(path []runkeeper.Path) []dm.GPS {
	dmPath := make([]dm.GPS, len(path))
	for i, rp := range path {
		dmPath[i] = dm.GPS{rp.Timestamp, rp.Altitude, rp.Longitude, rp.Latitude}
	}
	return dmPath
}

func convertToHR(hr []dm.HeartRate) []runkeeper.HeartRate {
	rkHr := make([]runkeeper.HeartRate, len(hr))
	for i, h := range hr {
		rkHr[i] = runkeeper.HeartRate{h.Timestamp, h.Heartrate}
	}
	return rkHr
}

func convertFromHR(rkHr []runkeeper.HeartRate) []dm.HeartRate {
	dmHr := make([]dm.HeartRate, len(rkHr))
	for i, h := range rkHr {
		dmHr[i] = dm.HeartRate{h.TimeStamp, h.HearRateNr}
	}
	return dmHr
}
<|endoftext|>


<commit_before>package cli

import "log"
import "os"

var stderr = log.New(os.Stderr, "", 0)
var stdout = log.New(os.Stdout, "", 0)

func exitIfError(err error) {
	if err != nil {
		stderr.Fatalln(err)
	}
}
<commit_msg>dont use log package<commit_after>package cli

import (
	"fmt"
	"os"
)

func exitIfError(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(2)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/go-fsnotify/fsnotify"
)

const defaultPort = "2703"

func server(arguments map[string]interface{}) {
	watch := arguments["--watch"].(int)

	if watch == 1 {
		watcher, _ := fsnotify.NewWatcher()
		defer watcher.Close()

		go func() {
			for {
				select {
				case <-watcher.Events:
					build(nil)
				}
			}
		}()

		watcher.Add(postsDir)
		watcher.Add(templatesDir)
	}

	port, ok := arguments["--port"].([]string)
	if !ok {
		port[0] = defaultPort
	}

	fmt.Printf("Running on http://localhost:%s\n", port[0])
	if watch == 1 {
		fmt.Println("Auto rebuilding when posts or templates change")
	}
	fmt.Println("Ctrl+C to quit")

	http.ListenAndServe(":"+port[0], http.FileServer(http.Dir(outputDir)))
}
<commit_msg>Added a TODO<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/go-fsnotify/fsnotify"
)

const defaultPort = "2703"

func server(arguments map[string]interface{}) {
	watch := arguments["--watch"].(int)

	if watch == 1 {
		watcher, _ := fsnotify.NewWatcher()
		defer watcher.Close()

		go func() {
			for {
				select {
				// FIXME rebuilding occurs twice
				case <-watcher.Events:
					build(nil)
				}
			}
		}()

		watcher.Add(postsDir)
		watcher.Add(templatesDir)
	}

	port, ok := arguments["--port"].([]string)
	if !ok {
		port[0] = defaultPort
	}

	fmt.Printf("Running on http://localhost:%s\n", port[0])
	if watch == 1 {
		fmt.Println("Auto rebuilding when posts or templates change")
	}
	fmt.Println("Ctrl+C to quit")

	http.ListenAndServe(":"+port[0], http.FileServer(http.Dir(outputDir)))
}
<|endoftext|>


<commit_before>// **********************************************************************
//    Copyright (c) 2017 Henry Seurer
//
//   Permission is hereby granted, free of charge, to any person
//    obtaining a copy of this software and associated documentation
//    files (the "Software"), to deal in the Software without
//    restriction, including without limitation the rights to use,
//    copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the
//    Software is furnished to do so, subject to the following
//    conditions:
//
//   The above copyright notice and this permission notice shall be
//   included in all copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//    OTHER DEALINGS IN THE SOFTWARE.
//
// **********************************************************************

package wiringpi

import (
	"os"
	"fmt"
)

var (
	board2pin = []int{
		-1,
		-1,
		-1,
		8,
		-1,
		9,
		-1,
		7,
		15,
		-1,
		16,
		0,
		1,
		2,
		-1,
		-1,
		4,
		-1,
		5,
		12,
		-1,
		13,
		6,
		14,
		10,
		-1,
		11,
	}
	gpio2pin = []int{
		8,
		9,
		-1,
		-1,
		7,
		-1,
		-1,
		11,
		10,
		13,
		12,
		14,
		-1,
		-1,
		15,
		16,
		-1,
		0,
		1,
		-1,
		-1,
		2,
		3,
		4,
		5,
		6,
		-1,
		-1,
		17,
		18,
		19,
		20,
	}

	gpioModes = []string  {"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"}
)

//noinspection GoUnusedConst
const (
	PIN_GPIO_0  = 0
	PIN_GPIO_1  = 1
	PIN_GPIO_2  = 2
	PIN_GPIO_3  = 3
	PIN_GPIO_4  = 4
	PIN_GPIO_5  = 5
	PIN_GPIO_6  = 6
	PIN_GPIO_7  = 7
	PIN_SDA     = 8
	PIN_SCL     = 9
	PIN_CE0     = 10
	PIN_CE1     = 11
	PIN_MOSI    = 12
	PIN_MOSO    = 13
	PIN_SCLK    = 14
	PIN_TXD     = 15
	PIN_RXD     = 16
	PIN_GPIO_8  = 17
	PIN_GPIO_9  = 18
	PIN_GPIO_10 = 19
	PIN_GPIO_11 = 20

	MODE_IN  = 0
	MODE_OUT = 1
	MODE_ALT5 = 2
	MODE_ALT4 = 3
	MODE_ALT0 = 4
	MODE_ALT1 = 5
	MODE_ALT2 = 6
	MODE_ALT3 = 7
)

//use RPi.GPIO's BOARD numbering
//noinspection GoUnusedExportedFunction
func BoardToPin(pin int) int {
	if pin < 1 || pin >= len(board2pin) {
		panic(fmt.Sprintf("Invalid board pin number: %d", pin))
	}
	return board2pin[pin]
}

//noinspection GoUnusedExportedFunction
func GpioToPin(pin int) int {
	if pin < 0 || pin >= len(gpio2pin) {
		panic(fmt.Sprintf("Invalid bcm gpio number: %d", pin))
	}
	return gpio2pin[pin]
}

//noinspection GoUnusedExportedFunction
func PinToGpio(pin int) int {
	return internalPinToGpio(pin)
}

// This initialises wiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme.
// This is a simplified numbering scheme which provides a mapping from virtual pin numbers 0 through 16 to the real
// underlying Broadcom GPIO pin numbers. See the pins page for a table which maps the wiringPi pin number to the
// Broadcom GPIO pin number to the physical location on the edge connector.
//
// This function needs to be called with root privileges.
//
//noinspection GoUnusedExportedFunction
func Setup() int {
	return internalSetup()
}

//This is identical to above, however it allows the calling programs to use the Broadcom GPIO pin numbers
// directly with no re-mapping.
//
// As above, this function needs to be called with root privileges, and note that some pins are different
// from revision 1 to revision 2 boards.
//
//noinspection GoUnusedExportedFunction
func SetupGpio() int {
	return internalSetupGpio()
}

// Identical to above, however it allows the calling programs to use the physical pin numbers on the P1 connector only.
//
// As above, this function needs to be called with root privileges.
//
//noinspection GoUnusedExportedFunction
func SetupPhys() int {
	return internalSetupPhys()
}

// This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly.
// This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program.
// Pin numbering in this mode is the native Broadcom GPIO numbers – the same as wiringPiSetupGpio() above, so be
// aware of the differences between Rev 1 and Rev 2 boards.
//
// Note: In this mode you can only use the pins which have been exported via the /sys/class/gpio interface
// before you run your program. You can do this in a separate shell-script, or by using the system() function
// from inside your program to call the gpio program.
//
//Also note that some functions have no effect when using this mode as they’re not currently possible to action unless called with root privileges. (although you can use system() to call gpio to set/change modes if needed)
//
//noinspection GoUnusedExportedFunction
func SetupSys() int {
	return internalSetupSys()
}

// This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK. Note that only wiringPi pin 1
// (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4) supports CLOCK output modes.
//
// This function has no effect when in Sys mode. If you need to change the pin mode, then you can do it with the
// gpio program in a script before you start your program.
//
//noinspection GoUnusedExportedFunction
func PinMode(pin int, mode int) {
	internalPinMode(pin, mode)
}

// This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input. Unlike the
// Arduino, the BCM2835 has both pull-up an down internal resistors. The parameter pud should be; PUD_OFF, (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) The internal pull up/down resistors have a value of approximately 50KΩ on the Raspberry Pi.
//
// This function has no effect on the Raspberry Pi’s GPIO pins when in Sys mode. If you need to activate a
// pull-up/pull-down, then you can do it with the gpio program in a script before you start your program.
//
//noinspection GoUnusedExportedFunction
func PullUpDnControl (pin int, pud int) {
	internalPullUpDnControl(pin, pud)
}

//Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output.
//
//WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW.
//
//noinspection GoUnusedExportedFunction
func DigitalWrite(pin int, mode int) {
	internalDigitalWrite(pin, mode)
}

// Writes the value to the PWM register for the given pin. The Raspberry Pi has one on-board PWM pin, pin 1
// (BMC_GPIO 18, Phys 12) and the range is 0-1024. Other PWM devices may have other PWM ranges.
//
// This function is not able to control the Pi’s on-board PWM when in Sys mode.
//
//noinspection GoUnusedExportedFunction
func PwmWrite (pin int, value int) {
	internalPwmWrite(pin, value)
}

//noinspection GoUnusedExportedFunction
func DigitalRead(pin int) int {
	return internalDigitalRead(pin)
}

//noinspection GoUnusedExportedFunction
func DigitalReadStr(pin int) string {
	if internalDigitalRead(pin) == LOW {
		return "LOW"
	}
	return "HIGH"
}

func GetMode(pin int) int {
	return internalGetMode(pin)
}

//noinspection GoUnusedExportedFunction
func GetModeStr(pin int) string {
	var mode = internalGetMode(pin)

	if mode > len(gpioModes) {
		return "INVALID"
	}

	return gpioModes[GetMode(pin)]
}

//noinspection GoUnusedExportedFunction
func Delay(ms int) {
	internalDelay(ms)
}

//noinspection GoUnusedExportedFunction
func DelayMicroseconds(microSec int) {
	internalDelayMicroseconds(microSec)
}

//noinspection GoUnusedExportedFunction
func WiringISR(pin int, mode int) chan int {
	return internalWiringISR(pin, mode)
}

//noinspection GoUnusedExportedFunction
func IsRaspberryPi() bool{
	_, err := os.Stat("/opt/vc/include/bcm_host.h")
	return os.IsExist(err)
}

//noinspection GoUnusedExportedFunction
func SetupI2C(devId int) int {
	return internalSetupI2C(devId)
}

//noinspection GoUnusedExportedFunction
func I2cRead(fd int) int {
	return internalI2CRead(fd)
}<commit_msg>Better test to see if file exists.<commit_after>// **********************************************************************
//    Copyright (c) 2017 Henry Seurer
//
//   Permission is hereby granted, free of charge, to any person
//    obtaining a copy of this software and associated documentation
//    files (the "Software"), to deal in the Software without
//    restriction, including without limitation the rights to use,
//    copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the
//    Software is furnished to do so, subject to the following
//    conditions:
//
//   The above copyright notice and this permission notice shall be
//   included in all copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//    OTHER DEALINGS IN THE SOFTWARE.
//
// **********************************************************************

package wiringpi

import (
	"fmt"
	"os"
)

var (
	board2pin = []int{
		-1,
		-1,
		-1,
		8,
		-1,
		9,
		-1,
		7,
		15,
		-1,
		16,
		0,
		1,
		2,
		-1,
		-1,
		4,
		-1,
		5,
		12,
		-1,
		13,
		6,
		14,
		10,
		-1,
		11,
	}
	gpio2pin = []int{
		8,
		9,
		-1,
		-1,
		7,
		-1,
		-1,
		11,
		10,
		13,
		12,
		14,
		-1,
		-1,
		15,
		16,
		-1,
		0,
		1,
		-1,
		-1,
		2,
		3,
		4,
		5,
		6,
		-1,
		-1,
		17,
		18,
		19,
		20,
	}

	gpioModes = []string{"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"}
)

//noinspection GoUnusedConst
const (
	PIN_GPIO_0  = 0
	PIN_GPIO_1  = 1
	PIN_GPIO_2  = 2
	PIN_GPIO_3  = 3
	PIN_GPIO_4  = 4
	PIN_GPIO_5  = 5
	PIN_GPIO_6  = 6
	PIN_GPIO_7  = 7
	PIN_SDA     = 8
	PIN_SCL     = 9
	PIN_CE0     = 10
	PIN_CE1     = 11
	PIN_MOSI    = 12
	PIN_MOSO    = 13
	PIN_SCLK    = 14
	PIN_TXD     = 15
	PIN_RXD     = 16
	PIN_GPIO_8  = 17
	PIN_GPIO_9  = 18
	PIN_GPIO_10 = 19
	PIN_GPIO_11 = 20

	MODE_IN   = 0
	MODE_OUT  = 1
	MODE_ALT5 = 2
	MODE_ALT4 = 3
	MODE_ALT0 = 4
	MODE_ALT1 = 5
	MODE_ALT2 = 6
	MODE_ALT3 = 7
)

//use RPi.GPIO's BOARD numbering
//noinspection GoUnusedExportedFunction
func BoardToPin(pin int) int {
	if pin < 1 || pin >= len(board2pin) {
		panic(fmt.Sprintf("Invalid board pin number: %d", pin))
	}
	return board2pin[pin]
}

//noinspection GoUnusedExportedFunction
func GpioToPin(pin int) int {
	if pin < 0 || pin >= len(gpio2pin) {
		panic(fmt.Sprintf("Invalid bcm gpio number: %d", pin))
	}
	return gpio2pin[pin]
}

//noinspection GoUnusedExportedFunction
func PinToGpio(pin int) int {
	return internalPinToGpio(pin)
}

// This initialises wiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme.
// This is a simplified numbering scheme which provides a mapping from virtual pin numbers 0 through 16 to the real
// underlying Broadcom GPIO pin numbers. See the pins page for a table which maps the wiringPi pin number to the
// Broadcom GPIO pin number to the physical location on the edge connector.
//
// This function needs to be called with root privileges.
//
//noinspection GoUnusedExportedFunction
func Setup() int {
	return internalSetup()
}

//This is identical to above, however it allows the calling programs to use the Broadcom GPIO pin numbers
// directly with no re-mapping.
//
// As above, this function needs to be called with root privileges, and note that some pins are different
// from revision 1 to revision 2 boards.
//
//noinspection GoUnusedExportedFunction
func SetupGpio() int {
	return internalSetupGpio()
}

// Identical to above, however it allows the calling programs to use the physical pin numbers on the P1 connector only.
//
// As above, this function needs to be called with root privileges.
//
//noinspection GoUnusedExportedFunction
func SetupPhys() int {
	return internalSetupPhys()
}

// This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly.
// This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program.
// Pin numbering in this mode is the native Broadcom GPIO numbers – the same as wiringPiSetupGpio() above, so be
// aware of the differences between Rev 1 and Rev 2 boards.
//
// Note: In this mode you can only use the pins which have been exported via the /sys/class/gpio interface
// before you run your program. You can do this in a separate shell-script, or by using the system() function
// from inside your program to call the gpio program.
//
//Also note that some functions have no effect when using this mode as they’re not currently possible to action unless called with root privileges. (although you can use system() to call gpio to set/change modes if needed)
//
//noinspection GoUnusedExportedFunction
func SetupSys() int {
	return internalSetupSys()
}

// This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK. Note that only wiringPi pin 1
// (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4) supports CLOCK output modes.
//
// This function has no effect when in Sys mode. If you need to change the pin mode, then you can do it with the
// gpio program in a script before you start your program.
//
//noinspection GoUnusedExportedFunction
func PinMode(pin int, mode int) {
	internalPinMode(pin, mode)
}

// This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input. Unlike the
// Arduino, the BCM2835 has both pull-up an down internal resistors. The parameter pud should be; PUD_OFF, (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) The internal pull up/down resistors have a value of approximately 50KΩ on the Raspberry Pi.
//
// This function has no effect on the Raspberry Pi’s GPIO pins when in Sys mode. If you need to activate a
// pull-up/pull-down, then you can do it with the gpio program in a script before you start your program.
//
//noinspection GoUnusedExportedFunction
func PullUpDnControl(pin int, pud int) {
	internalPullUpDnControl(pin, pud)
}

//Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output.
//
//WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW.
//
//noinspection GoUnusedExportedFunction
func DigitalWrite(pin int, mode int) {
	internalDigitalWrite(pin, mode)
}

// Writes the value to the PWM register for the given pin. The Raspberry Pi has one on-board PWM pin, pin 1
// (BMC_GPIO 18, Phys 12) and the range is 0-1024. Other PWM devices may have other PWM ranges.
//
// This function is not able to control the Pi’s on-board PWM when in Sys mode.
//
//noinspection GoUnusedExportedFunction
func PwmWrite(pin int, value int) {
	internalPwmWrite(pin, value)
}

//noinspection GoUnusedExportedFunction
func DigitalRead(pin int) int {
	return internalDigitalRead(pin)
}

//noinspection GoUnusedExportedFunction
func DigitalReadStr(pin int) string {
	if internalDigitalRead(pin) == LOW {
		return "LOW"
	}
	return "HIGH"
}

func GetMode(pin int) int {
	return internalGetMode(pin)
}

//noinspection GoUnusedExportedFunction
func GetModeStr(pin int) string {
	var mode = internalGetMode(pin)

	if mode > len(gpioModes) {
		return "INVALID"
	}

	return gpioModes[GetMode(pin)]
}

//noinspection GoUnusedExportedFunction
func Delay(ms int) {
	internalDelay(ms)
}

//noinspection GoUnusedExportedFunction
func DelayMicroseconds(microSec int) {
	internalDelayMicroseconds(microSec)
}

//noinspection GoUnusedExportedFunction
func WiringISR(pin int, mode int) chan int {
	return internalWiringISR(pin, mode)
}

//noinspection GoUnusedExportedFunction
func IsRaspberryPi() bool {
	_, err := os.Stat("/opt/vc/include/bcm_host.h")
	return !os.IsNotExist(err)
}

//noinspection GoUnusedExportedFunction
func SetupI2C(devId int) int {
	return internalSetupI2C(devId)
}

//noinspection GoUnusedExportedFunction
func I2cRead(fd int) int {
	return internalI2CRead(fd)
}
<|endoftext|>


<commit_before>package feedfactory

import (
    "time"
    "fmt"
    "github.com/gorilla/feeds"
    "../couchdbrest"
    "../jsonconvert"
)

func convertToFeed()(*feeds.Feed, error){

    body, err := couchdbrest.GetLastBlogArticles("127.0.0.1", "10")
    fmt.Println("err: ", err)

    if err != nil {
        fmt.Println("CouchDB rest call faild. Is database all ready running?")
//        body := []byte{}
        return nil, fmt.Errorf("CouchDB rest call faild. Is database all ready running?")
    }
    article_list, err := jsonconvert.JsonToObject(body)
    fmt.Println("============================\n")
    fmt.Println(article_list.Rows[0].Value["title"])
    fmt.Println("============================\n")
    fmt.Println("Anzahl Einträge:", len(article_list.Rows))

    now := time.Now()
    feed := &feeds.Feed{
        Title:       "THE INDEPENDENT FRIEND",
        Link:        &feeds.Link{Href: "https://the-independent-friend.de/"},
        Description: "Weblog von Olaf Radicke",
        Author:      &feeds.Author{"Olaf Radicke", "briefkasten@olaf-radicke.de"},
        Created:     now,
    }

    feed.Items = []*feeds.Item{}
    for index,element := range article_list.Rows {
        fmt.Println("============================\n")
        fmt.Println( "No.:" , index ,  " Value: " , element.Value["title"] )
        fmt.Println("============================\n")

        articleTitle := element.Value["title"]
        feed.Items = append(
            feed.Items,
            &feeds.Item{
                Title:       articleTitle.(string),
                Link:        &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
                Description: "A discussion on controlled parallelism in golang",
                Author:      &feeds.Author{"Jason Moiron", "jmoiron@jmoiron.net"},
                Created:     now,
            } )
    }

    return feed, nil
}

func GetAtom()(string){
    var feed = convertToFeed()
    atom, _ := feed.ToAtom()
//    fmt.Println("============================\n")
//    fmt.Println("atom:", atom)
//    fmt.Println("============================\n")
    return atom
}

func GetRss()(string){
    var feedObject = convertToFeed()
    rss, _ := feedObject.ToRss()
//    fmt.Println("atom:", rss)
    return rss
}
<commit_msg>Workin on error handling<commit_after>package feedfactory

import (
    "time"
    "fmt"
    "github.com/gorilla/feeds"
    "../couchdbrest"
    "../jsonconvert"
)

func convertToFeed()(*feeds.Feed, error){

    body, err := couchdbrest.GetLastBlogArticles("127.0.0.1", "10")
    fmt.Println("err: ", err)

    if err != nil {
        fmt.Println("CouchDB rest call faild. Is database all ready running?")
//        body := []byte{}
        return nil, fmt.Errorf("CouchDB rest call faild. Is database all ready running?")
    }
    article_list, err := jsonconvert.JsonToObject(body)
    fmt.Println("============================\n")
    fmt.Println(article_list.Rows[0].Value["title"])
    fmt.Println("============================\n")
    fmt.Println("Anzahl Einträge:", len(article_list.Rows))

    now := time.Now()
    feed := &feeds.Feed{
        Title:       "THE INDEPENDENT FRIEND",
        Link:        &feeds.Link{Href: "https://the-independent-friend.de/"},
        Description: "Weblog von Olaf Radicke",
        Author:      &feeds.Author{"Olaf Radicke", "briefkasten@olaf-radicke.de"},
        Created:     now,
    }

    feed.Items = []*feeds.Item{}
    for index,element := range article_list.Rows {
        fmt.Println("============================\n")
        fmt.Println( "No.:" , index ,  " Value: " , element.Value["title"] )
        fmt.Println("============================\n")

        articleTitle := element.Value["title"]
        feed.Items = append(
            feed.Items,
            &feeds.Item{
                Title:       articleTitle.(string),
                Link:        &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
                Description: "A discussion on controlled parallelism in golang",
                Author:      &feeds.Author{"Jason Moiron", "jmoiron@jmoiron.net"},
                Created:     now,
            } )
    }

    return feed, nil
}

func GetAtom()(string, error){
    var feed, err = convertToFeed()
    if err != nil {
        fmt.Println("Can't create feed!")
        return "", fmt.Errorf("Can't create feed!")
    }
    atom, _ := feed.ToAtom()
//    fmt.Println("============================\n")
//    fmt.Println("atom:", atom)
//    fmt.Println("============================\n")
    return atom, nil
}

func GetRss()(string){
    var feedObject, _ = convertToFeed()
    rss, _ := feedObject.ToRss()
//    fmt.Println("atom:", rss)
    return rss
}
<|endoftext|>


<commit_before>package file

import (
	"io/ioutil"
	"os"
	"sort"
	"testing"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"

	"github.com/antham/doc-hunt/util"
)

func TestBuildStatusWithFilesUntouched(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[INONE], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithUpdatedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	content := []byte("whatever")
	err = ioutil.WriteFile(util.GetAbsPath("source1.php"), content, 0644)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[IUPDATED], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[IUPDATED], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithDeletedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	err = os.Remove(util.GetAbsPath("source1.php"))

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[IDELETED], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[IDELETED], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithRegexpDescribingAFolder(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")
	createSourceFile([]byte("test"), "test1/source2.php")
	createSourceFile([]byte("test"), "test1/source3.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[INONE])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[INONE], 3, "Three file items must be found")
	assert.Equal(t, []string{"test1/source1.php", "test1/source2.php", "test1/source3.php"}, (*results)[0].Status[INONE], "Three file items must be found")
}

func TestBuildStatusWithRegexpDescribingAFolderAndAddedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")
	createSourceFile([]byte("test"), "test1/source2.php")
	createSourceFile([]byte("test"), "test1/source3.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	createSourceFile([]byte("test"), "test1/source4.php")

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[INONE])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")

	assert.Len(t, (*results)[0].Status[INONE], 3, "Three file items must be found")
	assert.Equal(t, []string{"test1/source1.php", "test1/source2.php", "test1/source3.php"}, (*results)[0].Status[INONE], "Three files items must be found")

	assert.Len(t, (*results)[0].Status[IADDED], 1, "One file items must be found")
	assert.Equal(t, []string{"test1/source4.php"}, (*results)[0].Status[IADDED], "One file item must be found")
}

func TestBuildStatusWithRegexpDescribingAFolderAndFolderDeleted(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	err = os.RemoveAll(util.GetAbsPath("test1"))

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[IDELETED])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")

	assert.Len(t, (*results)[0].Status[IDELETED], 1, "One file item must be found")
	assert.Equal(t, []string{"test1/source1.php"}, (*results)[0].Status[IDELETED], "One file item must be found")
}
<commit_msg>test(file) : fix error check<commit_after>package file

import (
	"io/ioutil"
	"os"
	"sort"
	"testing"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"

	"github.com/antham/doc-hunt/util"
)

func TestBuildStatusWithFilesUntouched(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[INONE], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithUpdatedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	content := []byte("whatever")
	err = ioutil.WriteFile(util.GetAbsPath("source1.php"), content, 0644)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[IUPDATED], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[IUPDATED], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithDeletedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "source1.php", SFILEREG),
		*NewSource(doc, "source2.php", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	err = os.Remove(util.GetAbsPath("source1.php"))

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	assert.Len(t, *results, 2, "Two results must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source1.php", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[IDELETED], 1, "One file item must be found")
	assert.Equal(t, []string{"source1.php"}, (*results)[0].Status[IDELETED], "One file item must be found")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[1].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "source2.php", (*results)[1].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[1].Status[INONE], 1, "One file item must be found")
	assert.Equal(t, []string{"source2.php"}, (*results)[1].Status[INONE], "One file item must be found")
}

func TestBuildStatusWithRegexpDescribingAFolder(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")
	createSourceFile([]byte("test"), "test1/source2.php")
	createSourceFile([]byte("test"), "test1/source3.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[INONE])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")
	assert.Len(t, (*results)[0].Status[INONE], 3, "Three file items must be found")
	assert.Equal(t, []string{"test1/source1.php", "test1/source2.php", "test1/source3.php"}, (*results)[0].Status[INONE], "Three file items must be found")
}

func TestBuildStatusWithRegexpDescribingAFolderAndAddedFile(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")
	createSourceFile([]byte("test"), "test1/source2.php")
	createSourceFile([]byte("test"), "test1/source3.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source4.php")

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[INONE])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")

	assert.Len(t, (*results)[0].Status[INONE], 3, "Three file items must be found")
	assert.Equal(t, []string{"test1/source1.php", "test1/source2.php", "test1/source3.php"}, (*results)[0].Status[INONE], "Three files items must be found")

	assert.Len(t, (*results)[0].Status[IADDED], 1, "One file items must be found")
	assert.Equal(t, []string{"test1/source4.php"}, (*results)[0].Status[IADDED], "One file item must be found")
}

func TestBuildStatusWithRegexpDescribingAFolderAndFolderDeleted(t *testing.T) {
	createMocks()
	deleteDatabase()
	createSubTestDirectory("test1")
	err := Initialize()

	if err != nil {
		logrus.Fatal(err)
	}

	createSourceFile([]byte("test"), "test1/source1.php")

	doc := NewDoc("doc_file_to_track.txt", DFILE)

	sources := []Source{
		*NewSource(doc, "test1", SFILEREG),
	}

	err = CreateConfig(doc, &sources)

	if err != nil {
		logrus.Fatal(err)
	}

	err = os.RemoveAll(util.GetAbsPath("test1"))

	if err != nil {
		logrus.Fatal(err)
	}

	results, err := BuildStatus()

	assert.NoError(t, err, "Must returns no errors")

	sort.Strings((*results)[0].Status[IDELETED])

	assert.Len(t, *results, 1, "One result must be returned")

	assert.Equal(t, "doc_file_to_track.txt", (*results)[0].Doc.Identifier, "Wrong doc identifier returned")
	assert.Equal(t, "test1", (*results)[0].Source.Identifier, "Wrong source identifier returned")

	assert.Len(t, (*results)[0].Status[IDELETED], 1, "One file item must be found")
	assert.Equal(t, []string{"test1/source1.php"}, (*results)[0].Status[IDELETED], "One file item must be found")
}
<|endoftext|>


<commit_before>package lexer

import (
	"errors"
	"fmt"
	"io"
)

// TokenType indicates the type of token
type TokenType int

const (
	// EmptyToken is token with no content (just the delim byte)
	EmptyToken TokenType = iota

	// BitsToken is a token composed of 0 and 1
	BitsToken

	// DigitsToken is a token composed of digits (0-9)
	DigitsToken

	// HexToken is a token composed of hex digits (0-9a-fA-F)
	HexToken

	// FloatToken is a token composed of digits and, at most, one dot
	FloatToken

	// DataToken is token with arbitrary content
	DataToken
)

// Token is a single token recognized by the lexer
type Token struct {
	// Type indicates the type of the token (a broad category of the literal bytes)
	Type TokenType

	// Literal holds the token bytes, plus the separator at the end
	Literal []byte

	state dfaState
}

// OnlyDigits returns true if the token contains only digits (is a BitsToken or a DigitsToken)
func (t *Token) OnlyDigits() bool {
	return t.Type == BitsToken || t.Type == DigitsToken
}

// IsHex returns true if the token contains only hex digits (is a BitsToken, DigitsToken or HexToken)
func (t *Token) IsHex() bool {
	return t.Type == BitsToken || t.Type == DigitsToken || t.Type == HexToken
}

// byte changes the Type field according to the byte c (make the internal dfa state to change).
func (t *Token) byte(c byte) {
	t.state = t.state.next(c)
	switch t.state {
	case emptyState:
		panic("impossibru!")
	case bitsState:
		t.Type = BitsToken
	case digitsState:
		t.Type = DigitsToken
	case hexState:
		t.Type = HexToken
	case signState:
		t.Type = DataToken
	case intState:
		t.Type = DataToken
	case dotState:
		t.Type = DataToken
	case floatState:
		t.Type = FloatToken
	case dataState:
		t.Type = DataToken
	default:
		panic(fmt.Errorf("unknown token type: %v", t.Type))
	}
}

// ErrTokenTooLong is returned by NextToken when the maximum length is reached without finding the delimiter byte
var ErrTokenTooLong = errors.New("token too long")

// Lexer is a very simple lexer, able to scan a reader using a delimiter byte and a maximum token length.
type Lexer struct {
	Reader io.Reader

	buf []byte
}

// Next scans the next token from the underlying reader, using a maximum length and a delimiter byte. If the maximum
// length is reached, an ErrTokenTooLong is returned.
// The delimiter byte is included in the Token literal and in the byte count.
func (l *Lexer) Next(max int, delim byte) (Token, error) {
	if max < 1 {
		return Token{}, fmt.Errorf("invalid max value, should be greater than 0")
	}
	if l.buf == nil {
		l.buf = make([]byte, 1)
	}
	t := Token{
		Type: EmptyToken,
	}
	for i := 0; i < max; i++ {
		_, err := io.ReadFull(l.Reader, l.buf)
		if err != nil {
			return t, err
		}
		c := l.buf[0]
		t.Literal = append(t.Literal, c)
		if c == delim {
			return t, nil
		}
		t.byte(c)
	}
	return t, ErrTokenTooLong
}

// NextFixed scans the next token from the underlying reader using a fixed length. If EOF is found before reading the
// token completely, an io.EOF is returned, along the resulting token (with a shorted literal obviously).
func (l *Lexer) NextFixed(length int) (Token, error) {
	if length < 1 {
		return Token{}, fmt.Errorf("invalid length value, should be greater than 0")
	}
	t := Token{
		Type:    EmptyToken,
		Literal: make([]byte, length),
	}
	_, err := io.ReadFull(l.Reader, t.Literal)
	switch err {
	case io.ErrUnexpectedEOF:
		for _, c := range t.Literal[:length-1] {
			t.byte(c)
		}
		t.Literal = t.Literal[:len(t.Literal)-1]
		return t, io.EOF
	case io.EOF:
		t.Literal = t.Literal[:len(t.Literal)-1]
		return t, err
	case nil:
	default:
		return t, err
	}

	for _, c := range t.Literal[:length-1] {
		t.byte(c)
	}
	return t, nil
}
<commit_msg>Helper function to check if a token literal ends with a byte value<commit_after>package lexer

import (
	"errors"
	"fmt"
	"io"
)

// TokenType indicates the type of token
type TokenType int

const (
	// EmptyToken is token with no content (just the delim byte)
	EmptyToken TokenType = iota

	// BitsToken is a token composed of 0 and 1
	BitsToken

	// DigitsToken is a token composed of digits (0-9)
	DigitsToken

	// HexToken is a token composed of hex digits (0-9a-fA-F)
	HexToken

	// FloatToken is a token composed of digits and, at most, one dot
	FloatToken

	// DataToken is token with arbitrary content
	DataToken
)

// Token is a single token recognized by the lexer
type Token struct {
	// Type indicates the type of the token (a broad category of the literal bytes)
	Type TokenType

	// Literal holds the token bytes, plus the separator at the end
	Literal []byte

	state dfaState
}

// OnlyDigits returns true if the token contains only digits (is a BitsToken or a DigitsToken)
func (t *Token) OnlyDigits() bool {
	return t.Type == BitsToken || t.Type == DigitsToken
}

// IsHex returns true if the token contains only hex digits (is a BitsToken, DigitsToken or HexToken)
func (t *Token) IsHex() bool {
	return t.Type == BitsToken || t.Type == DigitsToken || t.Type == HexToken
}

// EndsWith returns true if the last byte of the Literal is equals to the given delim byte.
func (t *Token) EndsWith(delim byte) bool {
	ll := len(t.Literal)
	if ll > 0 {
		return t.Literal[ll-1] == delim
	}
	return false
}

// byte changes the Type field according to the byte c (make the internal dfa state to change).
func (t *Token) byte(c byte) {
	t.state = t.state.next(c)
	switch t.state {
	case emptyState:
		panic("impossibru!")
	case bitsState:
		t.Type = BitsToken
	case digitsState:
		t.Type = DigitsToken
	case hexState:
		t.Type = HexToken
	case signState:
		t.Type = DataToken
	case intState:
		t.Type = DataToken
	case dotState:
		t.Type = DataToken
	case floatState:
		t.Type = FloatToken
	case dataState:
		t.Type = DataToken
	default:
		panic(fmt.Errorf("unknown token type: %v", t.Type))
	}
}

// ErrTokenTooLong is returned by NextToken when the maximum length is reached without finding the delimiter byte
var ErrTokenTooLong = errors.New("token too long")

// Lexer is a very simple lexer, able to scan a reader using a delimiter byte and a maximum token length.
type Lexer struct {
	Reader io.Reader

	buf []byte
}

// Next scans the next token from the underlying reader, using a maximum length and a delimiter byte. If the maximum
// length is reached, an ErrTokenTooLong is returned.
// The delimiter byte is included in the Token literal and in the byte count.
func (l *Lexer) Next(max int, delim byte) (Token, error) {
	if max < 1 {
		return Token{}, fmt.Errorf("invalid max value, should be greater than 0")
	}
	if l.buf == nil {
		l.buf = make([]byte, 1)
	}
	t := Token{
		Type: EmptyToken,
	}
	for i := 0; i < max; i++ {
		_, err := io.ReadFull(l.Reader, l.buf)
		if err != nil {
			return t, err
		}
		c := l.buf[0]
		t.Literal = append(t.Literal, c)
		if c == delim {
			return t, nil
		}
		t.byte(c)
	}
	return t, ErrTokenTooLong
}

// NextFixed scans the next token from the underlying reader using a fixed length. If EOF is found before reading the
// token completely, an io.EOF is returned, along the resulting token (with a shorted literal obviously).
func (l *Lexer) NextFixed(length int) (Token, error) {
	if length < 1 {
		return Token{}, fmt.Errorf("invalid length value, should be greater than 0")
	}
	t := Token{
		Type:    EmptyToken,
		Literal: make([]byte, length),
	}
	_, err := io.ReadFull(l.Reader, t.Literal)
	switch err {
	case io.ErrUnexpectedEOF:
		for _, c := range t.Literal[:length-1] {
			t.byte(c)
		}
		t.Literal = t.Literal[:len(t.Literal)-1]
		return t, io.EOF
	case io.EOF:
		t.Literal = t.Literal[:len(t.Literal)-1]
		return t, err
	case nil:
	default:
		return t, err
	}

	for _, c := range t.Literal[:length-1] {
		t.byte(c)
	}
	return t, nil
}
<|endoftext|>


<commit_before>package lib

import (
	"encoding/json"
	"log"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

type QuickBg struct {
	Details *Details
	lang    struct {
		Comment  string `json:"comment"`
		Question string `json:"question"`
		Review   yesNo  `json:"review"`
		Above    option `json:"above"`
		In       option `json:"in"`
		Below    option `json:"below"`
		Thank    string `json:"thank"`
	}
	Parts
}

func (this *QuickBg) loadLanguage() {

	encoded := `{
        "comment": "OK lets save that blood sugar for you.",
        "question": "So your last blood sugar was ... ",
        "review" : {
        	"yes":"Sure thing!",
        	"no": "No thanks"
        },
        "above": {
            "text": "Above what I would like",
            "feedback":["Remember to keep your fuilds up","Just remember it happens to the best of us"],
            "followUp" :["Has it been high for a while?"]
        },
        "in": {
            "text": "About right",
            "feedback":["Awesome work!!","Its never as easy as its made out aye :)","How does it feel to be perfect :)"],
            "followUp" :["Did you feel you could do this again and again?"]
        },
        "below":{
            "text": "Below what I would like",
            "feedback":["Damn lows, they always happen at the wrost time.","I Hope you keep your low supplies stocked up!!"],
            "followUp" :["Do you have any idea why you went low?"]
        },
        "thank":"You rock! Just had to say that before you go :)"
    }`

	err := json.Unmarshal([]byte(encoded), &this.lang)
	if err != nil {
		log.Panic("could not load BG language ", err.Error())
	}
}
func NewQuickBg(d *Details) *QuickBg {
	bg := &QuickBg{Details: d}
	bg.loadLanguage()
	bg.Parts = append(
		bg.Parts,
		&Part{Func: bg.selectBg, ToBeRun: true},
		&Part{Func: bg.questionReview, ToBeRun: true},
		&Part{Func: bg.answerReview, ToBeRun: true},
	)
	return bg
}

func (this *QuickBg) GetParts() Parts {
	return this.Parts
}

func (this *QuickBg) selectBg(msg telebot.Message) {
	this.Details.sendWithKeyboard(this.lang.Question, makeKeyBoard(this.lang.Above.Text, this.lang.In.Text, this.lang.Below.Text))
	return
}

func (this *QuickBg) questionReview(msg telebot.Message) {
	this.Details.sendWithKeyboard(getLangText(this.lang.Above.FollowUpQuestion), makeKeyBoard(this.lang.Review.Yes, this.lang.Review.No))
	return
}

func (this *QuickBg) answerReview(msg telebot.Message) {
	switch {
	case msg.Text == this.lang.Review.Yes:
		this.Parts = append(
			this.Parts,
			&Part{Func: this.doReview, ToBeRun: true},
			&Part{Func: this.onYa, ToBeRun: true},
		)
	case msg.Text == this.lang.Review.No:
		this.Parts = append(
			this.Parts,
			&Part{Func: this.onYa, ToBeRun: true},
		)
	}
}

func (this *QuickBg) doReview(msg telebot.Message) {
	switch {
	case msg.Text == this.lang.Above.Text:
		this.Details.send(getLangText(this.lang.Above.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.Above.FollowUpQuestion), makeKeyBoard("Sure has", "Nope"))
		return
	case msg.Text == this.lang.In.Text:
		this.Details.send(getLangText(this.lang.In.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.In.FollowUpQuestion), makeKeyBoard("Totally!", "Not so sure"))
		return
	case msg.Text == this.lang.Below.Text:
		this.Details.send(getLangText(this.lang.Below.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.Below.FollowUpQuestion), makeKeyBoard("Yeah I have a hunch", "No, I just don't get it"))
		return
	}
	return
}

func (this *QuickBg) onYa(msg telebot.Message) {
	this.Details.sendWithKeyboard(this.lang.Thank, makeKeyBoard("It does aye. See you!"))
	return
}
<commit_msg>change flow - try again<commit_after>package lib

import (
	"encoding/json"
	"log"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

type QuickBg struct {
	Details        *Details
	SelectedAnswer string
	lang           struct {
		Comment     string `json:"comment"`
		Question    string `json:"question"`
		Review      string `json:"review"`
		ReviewYesNo yesNo  `json:"reviewYesNo"`
		Above       option `json:"above"`
		In          option `json:"in"`
		Below       option `json:"below"`
		Thank       string `json:"thank"`
	}
	Parts
}

func (this *QuickBg) loadLanguage() {

	encoded := `{
        "comment": "OK lets save that blood sugar for you.",
        "question": "So your last blood sugar was ... ",
        "review": "Would you like to review your result?",
        "reviewYesNo" : {
        	"yes":"Sure thing!",
        	"no": "No thanks"
        },
        "above": {
            "text": "Above what I would like",
            "feedback":["Remember to keep your fuilds up","Just remember it happens to the best of us"],
            "followUp" :["Has it been high for a while?"]
        },
        "in": {
            "text": "About right",
            "feedback":["Awesome work!!","Its never as easy as its made out aye :)","How does it feel to be perfect :)"],
            "followUp" :["Did you feel you could do this again and again?"]
        },
        "below":{
            "text": "Below what I would like",
            "feedback":["Damn lows, they always happen at the wrost time.","I Hope you keep your low supplies stocked up!!"],
            "followUp" :["Do you have any idea why you went low?"]
        },
        "thank":"You rock! Just had to say that before you go :)"
    }`

	err := json.Unmarshal([]byte(encoded), &this.lang)
	if err != nil {
		log.Panic("could not load BG language ", err.Error())
	}
}
func NewQuickBg(d *Details) *QuickBg {
	bg := &QuickBg{Details: d}
	bg.loadLanguage()
	bg.Parts = append(
		bg.Parts,
		&Part{Func: bg.askBg, ToBeRun: true},
		&Part{Func: bg.askReview, ToBeRun: true},
		&Part{Func: bg.replyReview, ToBeRun: true},
	)
	return bg
}

func (this *QuickBg) GetParts() Parts {
	return this.Parts
}

func (this *QuickBg) askBg(msg telebot.Message) {
	this.Details.sendWithKeyboard(this.lang.Question, makeKeyBoard(this.lang.Above.Text, this.lang.In.Text, this.lang.Below.Text))
	return
}

func (this *QuickBg) askReview(msg telebot.Message) {
	this.SelectedAnswer = msg.Text
	this.Details.sendWithKeyboard(this.lang.Review, makeKeyBoard(this.lang.ReviewYesNo.Yes, this.lang.ReviewYesNo.No))
	return
}

func (this *QuickBg) replyReview(msg telebot.Message) {
	switch {
	case msg.Text == this.lang.ReviewYesNo.Yes:
		this.Parts = append(
			this.Parts,
			&Part{Func: this.doReview, ToBeRun: true},
			&Part{Func: this.onYa, ToBeRun: true},
		)
	case msg.Text == this.lang.ReviewYesNo.No:
		this.Parts = append(
			this.Parts,
			&Part{Func: this.onYa, ToBeRun: true},
		)
	}
}

func (this *QuickBg) doReview(msg telebot.Message) {
	switch {
	case this.SelectedAnswer == this.lang.Above.Text:
		this.Details.send(getLangText(this.lang.Above.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.Above.FollowUpQuestion), makeKeyBoard("Sure has", "Nope"))
		return
	case this.SelectedAnswer == this.lang.In.Text:
		this.Details.send(getLangText(this.lang.In.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.In.FollowUpQuestion), makeKeyBoard("Totally!", "Not so sure"))
		return
	case this.SelectedAnswer == this.lang.Below.Text:
		this.Details.send(getLangText(this.lang.Below.Feedback))
		this.Details.sendWithKeyboard(getLangText(this.lang.Below.FollowUpQuestion), makeKeyBoard("Yeah I have a hunch", "No, I just don't get it"))
		return
	}
	return
}

func (this *QuickBg) onYa(msg telebot.Message) {
	this.Details.sendWithKeyboard(this.lang.Thank, makeKeyBoard("It does aye. See you!"))
	return
}
<|endoftext|>