<commit_before>package util

import (
	"fmt"
	"strings"

	"github.com/sirupsen/logrus"
)

// RemoveNode removes all the NB DB objects created for that node.
func RemoveNode(nodeName string) error {
	// Get the cluster router
	clusterRouter, err := GetK8sClusterRouter()
	if err != nil {
		return fmt.Errorf("failed to get cluster router")
	}

	// Remove the logical switch associated with nodeName
	_, stderr, err := RunOVNNbctl("--if-exist", "ls-del", nodeName)
	if err != nil {
		return fmt.Errorf("Failed to delete logical switch %s, "+
			"stderr: %q, error: %v", nodeName, stderr, err)
	}

	gatewayRouter := fmt.Sprintf("GR_%s", nodeName)

	// Get the gateway router port's IP address (connected to join switch)
	var routerIP string
	routerIPNetwork, stderr, err := RunOVNNbctl("--if-exist", "get",
		"logical_router_port", "rtoj-"+gatewayRouter, "networks")
	if err != nil {
		return fmt.Errorf("Failed to get logical router port, stderr: %q, "+
			"error: %v", stderr, err)
	}

	if routerIPNetwork != "" {
		routerIPNetwork = strings.Trim(routerIPNetwork, "[]\"")
		if routerIPNetwork != "" {
			routerIP = strings.Split(routerIPNetwork, "/")[0]
		}
	}

	if routerIP != "" {
		// Get a list of all the routes in cluster router with this gateway
		// Router as the next hop.
		var uuids string
		uuids, stderr, err = RunOVNNbctl("--data=bare", "--no-heading",
			"--columns=_uuid", "find", "logical_router_static_route",
			"nexthop="+routerIP)
		if err != nil {
			return fmt.Errorf("Failed to fetch all routes with gateway "+
				"router %s as nexthop, stderr: %q, "+
				"error: %v", gatewayRouter, stderr, err)
		}

		// Remove all the routes in cluster router with this gateway Router
		// as the nexthop.
		routes := strings.Fields(uuids)
		for _, route := range routes {
			_, stderr, err = RunOVNNbctl("--if-exists", "remove",
				"logical_router", clusterRouter, "static_routes", route)
			if err != nil {
				logrus.Errorf("Failed to delete static route %s"+
					", stderr: %q, err = %v", route, stderr, err)
				continue
			}
		}
	}

	// Remove any gateway routers associated with nodeName
	_, stderr, err = RunOVNNbctl("--if-exist", "lr-del",
		gatewayRouter)
	if err != nil {
		return fmt.Errorf("Failed to delete gateway router %s, stderr: %q, "+
			"error: %v", gatewayRouter, stderr, err)
	}

	// Remove external switch
	externalSwitch := "ext_" + nodeName
	_, stderr, err = RunOVNNbctl("--if-exist", "ls-del",
		externalSwitch)
	if err != nil {
		return fmt.Errorf("Failed to delete external switch %s, stderr: %q, "+
			"error: %v", externalSwitch, stderr, err)
	}

	return nil
}
<commit_msg>Not all OVN resources cleaned up on deletion of K8s nodes<commit_after>package util

import (
	"fmt"
	"strings"

	"github.com/sirupsen/logrus"
)

// RemoveNode removes all the NB DB objects created for that node.
func RemoveNode(nodeName string) error {
	// Get the cluster router
	clusterRouter, err := GetK8sClusterRouter()
	if err != nil {
		return fmt.Errorf("failed to get cluster router")
	}

	// Remove the logical switch associated with nodeName
	_, stderr, err := RunOVNNbctl("--if-exist", "ls-del", nodeName)
	if err != nil {
		return fmt.Errorf("Failed to delete logical switch %s, "+
			"stderr: %q, error: %v", nodeName, stderr, err)
	}

	gatewayRouter := fmt.Sprintf("GR_%s", nodeName)

	// Get the gateway router port's IP address (connected to join switch)
	var routerIP string
	routerIPNetwork, stderr, err := RunOVNNbctl("--if-exist", "get",
		"logical_router_port", "rtoj-"+gatewayRouter, "networks")
	if err != nil {
		return fmt.Errorf("Failed to get logical router port, stderr: %q, "+
			"error: %v", stderr, err)
	}

	if routerIPNetwork != "" {
		routerIPNetwork = strings.Trim(routerIPNetwork, "[]\"")
		if routerIPNetwork != "" {
			routerIP = strings.Split(routerIPNetwork, "/")[0]
		}
	}

	if routerIP != "" {
		// Get a list of all the routes in cluster router with this gateway
		// Router as the next hop.
		var uuids string
		uuids, stderr, err = RunOVNNbctl("--data=bare", "--no-heading",
			"--columns=_uuid", "find", "logical_router_static_route",
			"nexthop="+routerIP)
		if err != nil {
			return fmt.Errorf("Failed to fetch all routes with gateway "+
				"router %s as nexthop, stderr: %q, "+
				"error: %v", gatewayRouter, stderr, err)
		}

		// Remove all the routes in cluster router with this gateway Router
		// as the nexthop.
		routes := strings.Fields(uuids)
		for _, route := range routes {
			_, stderr, err = RunOVNNbctl("--if-exists", "remove",
				"logical_router", clusterRouter, "static_routes", route)
			if err != nil {
				logrus.Errorf("Failed to delete static route %s"+
					", stderr: %q, err = %v", route, stderr, err)
				continue
			}
		}
	}

	// Remove the patch port that connects join switch to gateway router
	_, stderr, err = RunOVNNbctl("--if-exist", "lsp-del", "jtor-"+gatewayRouter)
	if err != nil {
		return fmt.Errorf("Failed to delete logical switch port jtor-%s, "+
			"stderr: %q, error: %v", gatewayRouter, stderr, err)
	}

	// Remove the patch port that connects distributed router to node's logical switch
	_, stderr, err = RunOVNNbctl("--if-exist", "lrp-del", "rtos-"+nodeName)
	if err != nil {
		return fmt.Errorf("Failed to delete logical router port rtos-%s, "+
			"stderr: %q, error: %v", nodeName, stderr, err)
	}

	// Remove any gateway routers associated with nodeName
	_, stderr, err = RunOVNNbctl("--if-exist", "lr-del",
		gatewayRouter)
	if err != nil {
		return fmt.Errorf("Failed to delete gateway router %s, stderr: %q, "+
			"error: %v", gatewayRouter, stderr, err)
	}

	// Remove external switch
	externalSwitch := "ext_" + nodeName
	_, stderr, err = RunOVNNbctl("--if-exist", "ls-del",
		externalSwitch)
	if err != nil {
		return fmt.Errorf("Failed to delete external switch %s, stderr: %q, "+
			"error: %v", externalSwitch, stderr, err)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"koding/artifact"
	"koding/db/mongodb/modelhelper"
	"log"
	"net"
	"net/http"
	"socialapi/config"
	"time"

	kiteConfig "github.com/koding/kite/config"
	"github.com/koding/logging"
	"github.com/koding/runner"
	"github.com/robfig/cron"

	"github.com/koding/kite"
)

const (
	WorkerName    = "janitor"
	WorkerVersion = "0.0.1"

	// DefaultRangeForQuery defines the range of interval for the queries.
	DefaultRangeForQuery = 3

	// DefaultLimitPerRun defines how many users to be processed in a day by one worker.
	DefaultLimitPerRun = 500

	// DailyAtFourPM specifies interval; cron runs at utc, 23 UTC is 4pm PST
	// with daylight savings time
	DailyAtFourPM = "0 0 23 * * *"
)

type janitor struct {
	runner     *runner.Runner
	log        logging.Logger
	kiteClient *kite.Client
}

var j = &janitor{}

func main() {
	j.initializeRunner()

	conf := config.MustRead(j.runner.Conf.Path)
	port := conf.Janitor.Port
	konf := conf.Kloud

	kloudSecretKey := conf.Janitor.SecretKey

	go j.runner.Listen()

	err := j.initializeKiteClient(kloudSecretKey, konf.Address)
	if err != nil {
		j.log.Fatal("Error initializing kite: %s", err.Error())
	}

	// warnings contains list of warnings to be iterated upon in a certain
	// interval.
	warnings := []*Warning{
		VMDeletionWarning1, VMDeletionWarning2, DeleteInactiveUserVM, DeleteBlockedUserVM,
	}

	c := cron.New()
	c.AddFunc(DailyAtFourPM, func() {
		for _, w := range warnings {

			// clone warning so local changes don't affect next run
			warning := *w

			result := warning.Run()
			j.log.Info(result.String())
		}
	})

	c.Start()

	mux := http.NewServeMux()
	mux.HandleFunc("/version", artifact.VersionHandler())
	mux.HandleFunc("/healthCheck", artifact.HealthCheckHandler(WorkerName))

	listener, err := net.Listen("tcp", ":"+port)
	if err != nil {
		j.log.Fatal("Error opening tcp connection: %s", err.Error())
	}

	j.log.Info("Listening on port: %s", port)

	j.runner.ShutdownHandler = func() {
		listener.Close()
		j.runner.Kite.Close()
		modelhelper.Close()
	}

	if err := http.Serve(listener, mux); err != nil {
		j.log.Fatal("Error starting http server: %s", err.Error())
	}
}

func (j *janitor) initializeRunner() {
	r := runner.New(WorkerName)
	if err := r.Init(); err != nil {
		log.Fatal("Error starting runner: %s", err.Error())
	}

	appConfig := config.MustRead(r.Conf.Path)
	modelhelper.Initialize(appConfig.Mongo)

	j.runner = r
	j.log = r.Log
}

func (j *janitor) initializeKiteClient(kloudKey, kloudAddr string) error {
	config, err := kiteConfig.Get()
	if err != nil {
		return err
	}

	r := j.runner

	// set skeleton config
	r.Kite.Config = config

	// create a new connection to the cloud
	kiteClient := r.Kite.NewClient(kloudAddr)
	kiteClient.Auth = &kite.Auth{Type: WorkerName, Key: kloudKey}
	kiteClient.Reconnect = true

	// dial the kloud address
	if err := kiteClient.DialTimeout(time.Second * 10); err != nil {
		return fmt.Errorf("%s. Is kloud running?", err.Error())
	}

	j.log.Debug("Connected to klient: %s", kloudAddr)

	j.kiteClient = kiteClient

	return nil
}
<commit_msg>janitor: switch time to make it run today<commit_after>package main

import (
	"fmt"
	"koding/artifact"
	"koding/db/mongodb/modelhelper"
	"log"
	"net"
	"net/http"
	"socialapi/config"
	"time"

	kiteConfig "github.com/koding/kite/config"
	"github.com/koding/logging"
	"github.com/koding/runner"
	"github.com/robfig/cron"

	"github.com/koding/kite"
)

const (
	WorkerName    = "janitor"
	WorkerVersion = "0.0.1"

	// DefaultRangeForQuery defines the range of interval for the queries.
	DefaultRangeForQuery = 3

	// DefaultLimitPerRun defines how many users to be processed in a day by one worker.
	DefaultLimitPerRun = 500

	// DailyAtTenPM specifies interval; cron runs at utc, 5 UTC is 10pm PST
	// with daylight savings time.
	DailyAtTenPM = "0 0 5 * * *"
)

type janitor struct {
	runner     *runner.Runner
	log        logging.Logger
	kiteClient *kite.Client
}

var j = &janitor{}

func main() {
	j.initializeRunner()

	conf := config.MustRead(j.runner.Conf.Path)
	port := conf.Janitor.Port
	konf := conf.Kloud

	kloudSecretKey := conf.Janitor.SecretKey

	go j.runner.Listen()

	err := j.initializeKiteClient(kloudSecretKey, konf.Address)
	if err != nil {
		j.log.Fatal("Error initializing kite: %s", err.Error())
	}

	// warnings contains list of warnings to be iterated upon in a certain
	// interval.
	warnings := []*Warning{
		VMDeletionWarning1, VMDeletionWarning2, DeleteInactiveUserVM, DeleteBlockedUserVM,
	}

	c := cron.New()
	c.AddFunc(DailyAtTenPM, func() {
		for _, w := range warnings {

			// clone warning so local changes don't affect next run
			warning := *w

			result := warning.Run()
			j.log.Info(result.String())
		}
	})

	c.Start()

	mux := http.NewServeMux()
	mux.HandleFunc("/version", artifact.VersionHandler())
	mux.HandleFunc("/healthCheck", artifact.HealthCheckHandler(WorkerName))

	listener, err := net.Listen("tcp", ":"+port)
	if err != nil {
		j.log.Fatal("Error opening tcp connection: %s", err.Error())
	}

	j.log.Info("Listening on port: %s", port)

	j.runner.ShutdownHandler = func() {
		listener.Close()
		j.runner.Kite.Close()
		modelhelper.Close()
	}

	if err := http.Serve(listener, mux); err != nil {
		j.log.Fatal("Error starting http server: %s", err.Error())
	}
}

func (j *janitor) initializeRunner() {
	r := runner.New(WorkerName)
	if err := r.Init(); err != nil {
		log.Fatal("Error starting runner: %s", err.Error())
	}

	appConfig := config.MustRead(r.Conf.Path)
	modelhelper.Initialize(appConfig.Mongo)

	j.runner = r
	j.log = r.Log
}

func (j *janitor) initializeKiteClient(kloudKey, kloudAddr string) error {
	config, err := kiteConfig.Get()
	if err != nil {
		return err
	}

	r := j.runner

	// set skeleton config
	r.Kite.Config = config

	// create a new connection to the cloud
	kiteClient := r.Kite.NewClient(kloudAddr)
	kiteClient.Auth = &kite.Auth{Type: WorkerName, Key: kloudKey}
	kiteClient.Reconnect = true

	// dial the kloud address
	if err := kiteClient.DialTimeout(time.Second * 10); err != nil {
		return fmt.Errorf("%s. Is kloud running?", err.Error())
	}

	j.log.Debug("Connected to klient: %s", kloudAddr)

	j.kiteClient = kiteClient

	return nil
}
<|endoftext|>


<commit_before>package imgmatching

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"go.skia.org/infra/go/skerr"
	"go.skia.org/infra/gold-client/go/imgmatching/fuzzy"
	"go.skia.org/infra/gold-client/go/imgmatching/sobel"
)

// MakeMatcher takes a map of optional keys and returns the specified image matching algorithm
// name, and the corresponding Matcher instance (or nil if none is specified).
//
// It returns a non-nil error if the specified image matching algorithm is invalid, or if any
// required parameters are not found, or if the parameter values are not valid.
func MakeMatcher(optionalKeys map[string]string) (AlgorithmName, Matcher, error) {
	algorithmNameStr, ok := optionalKeys[AlgorithmNameOptKey]
	algorithmName := AlgorithmName(algorithmNameStr)

	// Exact matching by default.
	if !ok {
		algorithmName = ExactMatching
	}

	switch algorithmName {
	case ExactMatching:
		// No Matcher implementation necessary for exact matching as this is done ad-hoc.
		return ExactMatching, nil, nil

	case FuzzyMatching:
		matcher, err := makeFuzzyMatcher(optionalKeys)
		if err != nil {
			return "", nil, skerr.Wrap(err)
		}
		return FuzzyMatching, matcher, nil

	case SobelFuzzyMatching:
		matcher, err := makeSobelFuzzyMatcher(optionalKeys)
		if err != nil {
			return "", nil, skerr.Wrap(err)
		}
		return SobelFuzzyMatching, matcher, nil

	default:
		return "", nil, skerr.Fmt("unrecognized image matching algorithm: %q", algorithmName)
	}
}

// makeFuzzyMatcher returns a fuzzy.Matcher instance set up with the parameter values in the
// given optional keys map.
func makeFuzzyMatcher(optionalKeys map[string]string) (*fuzzy.Matcher, error) {
	maxDifferentPixels, err := getAndValidateIntParameter(MaxDifferentPixels, 0, math.MaxInt32, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	// The maximum value corresponds to the maximum possible per-channel delta sum. This assumes four
	// channels (R, G, B, A), each represented with 8 bits; hence 1020 = 255*4.
	pixelDeltaThreshold, err := getAndValidateIntParameter(PixelDeltaThreshold, 0, 1020, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	return &fuzzy.Matcher{
		MaxDifferentPixels:  maxDifferentPixels,
		PixelDeltaThreshold: pixelDeltaThreshold,
	}, nil
}

// makeSobelFuzzyMatcher returns a sobel.Matcher instance set up with the parameter
// values in the given optional keys map.
func makeSobelFuzzyMatcher(optionalKeys map[string]string) (*sobel.Matcher, error) {
	// Instantiate the fuzzy.Matcher that will be embedded in the sobel.Matcher.
	fuzzyMatcher, err := makeFuzzyMatcher(optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	// This assumes the Sobel operator returns an 8-bit per-pixel value indicating how likely a pixel
	// is to be part of an edge.
	edgeThreshold, err := getAndValidateIntParameter(EdgeThreshold, 0, 255, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	return &sobel.Matcher{
		Matcher:       *fuzzyMatcher,
		EdgeThreshold: edgeThreshold,
	}, nil
}

// getAndValidateIntParameter extracts and validates the given required integer parameter from the
// given map of optional keys.
//
// Minimum and maximum value validation can be disabled by setting parameters min and max to
// math.MinInt32 and math.MaxInt32, respectively.
func getAndValidateIntParameter(name AlgorithmParamOptKey, min, max int, optionalKeys map[string]string) (int, error) {
	// Validate bounds.
	if min >= max {
		// This is almost surely a programming error.
		panic(fmt.Sprintf("min must be strictly less than max, min was %d, max was %d", min, max))
	}

	// Validate presence.
	stringVal, ok := optionalKeys[string(name)]
	if !ok {
		return 0, skerr.Fmt("required image matching parameter not found: %q", name)
	}

	// Value cannot be empty.
	if strings.TrimSpace(stringVal) == "" {
		return 0, skerr.Fmt("image matching parameter %q cannot be empty", name)
	}

	// Value must be a valid 32-bit integer.
	//
	// Note: The "int" type in Go has a platform-specific bit size of *at least* 32 bits, so we
	// explicitly parse the value as a 32-bit int to keep things deterministic across platforms.
	// Additionally, this ensures the math.MinInt32 and math.MaxInt32 sentinel values for the mix and
	// max parameters work as expected.
	int64Val, err := strconv.ParseInt(stringVal, 0, 32)
	if err != nil {
		return 0, skerr.Fmt("parsing integer value for image matching parameter %q: %q", name, err.Error())
	}
	intVal := int(int64Val)

	// Value must be between bounds.
	if intVal < min || intVal > max {
		// No lower bound, so value must be violating the upper bound.
		if min == math.MinInt32 {
			return 0, skerr.Fmt("image matching parameter %q must be at most %d, was: %d", name, max, int64Val)
		}

		// No upper bound, so value must be violating the lower bound.
		if max == math.MaxInt32 {
			return 0, skerr.Fmt("image matching parameter %q must be at least %d, was: %d", name, min, int64Val)
		}

		// Value has both an upper and lower bound.
		return 0, skerr.Fmt("image matching parameter %q must be between %d and %d, was: %d", name, min, max, int64Val)
	}

	return intVal, nil
}
<commit_msg>[goldctl] imgmatching/factory.go: Add support for optional int parameters.<commit_after>package imgmatching

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"go.skia.org/infra/go/skerr"
	"go.skia.org/infra/gold-client/go/imgmatching/fuzzy"
	"go.skia.org/infra/gold-client/go/imgmatching/sobel"
)

// MakeMatcher takes a map of optional keys and returns the specified image matching algorithm
// name, and the corresponding Matcher instance (or nil if none is specified).
//
// It returns a non-nil error if the specified image matching algorithm is invalid, or if any
// required parameters are not found, or if the parameter values are not valid.
func MakeMatcher(optionalKeys map[string]string) (AlgorithmName, Matcher, error) {
	algorithmNameStr, ok := optionalKeys[AlgorithmNameOptKey]
	algorithmName := AlgorithmName(algorithmNameStr)

	// Exact matching by default.
	if !ok {
		algorithmName = ExactMatching
	}

	switch algorithmName {
	case ExactMatching:
		// No Matcher implementation necessary for exact matching as this is done ad-hoc.
		return ExactMatching, nil, nil

	case FuzzyMatching:
		matcher, err := makeFuzzyMatcher(optionalKeys)
		if err != nil {
			return "", nil, skerr.Wrap(err)
		}
		return FuzzyMatching, matcher, nil

	case SobelFuzzyMatching:
		matcher, err := makeSobelFuzzyMatcher(optionalKeys)
		if err != nil {
			return "", nil, skerr.Wrap(err)
		}
		return SobelFuzzyMatching, matcher, nil

	default:
		return "", nil, skerr.Fmt("unrecognized image matching algorithm: %q", algorithmName)
	}
}

// makeFuzzyMatcher returns a fuzzy.Matcher instance set up with the parameter values in the
// given optional keys map.
func makeFuzzyMatcher(optionalKeys map[string]string) (*fuzzy.Matcher, error) {
	maxDifferentPixels, err := getAndValidateIntParameter(MaxDifferentPixels, 0, math.MaxInt32, true /* =required */, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	// The maximum value corresponds to the maximum possible per-channel delta sum. This assumes four
	// channels (R, G, B, A), each represented with 8 bits; hence 1020 = 255*4.
	pixelDeltaThreshold, err := getAndValidateIntParameter(PixelDeltaThreshold, 0, 1020, true /* =required */, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	return &fuzzy.Matcher{
		MaxDifferentPixels:  maxDifferentPixels,
		PixelDeltaThreshold: pixelDeltaThreshold,
	}, nil
}

// makeSobelFuzzyMatcher returns a sobel.Matcher instance set up with the parameter
// values in the given optional keys map.
func makeSobelFuzzyMatcher(optionalKeys map[string]string) (*sobel.Matcher, error) {
	// Instantiate the fuzzy.Matcher that will be embedded in the sobel.Matcher.
	fuzzyMatcher, err := makeFuzzyMatcher(optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	// This assumes the Sobel operator returns an 8-bit per-pixel value indicating how likely a pixel
	// is to be part of an edge.
	edgeThreshold, err := getAndValidateIntParameter(EdgeThreshold, 0, 255, true /* =required */, optionalKeys)
	if err != nil {
		return nil, skerr.Wrap(err)
	}

	return &sobel.Matcher{
		Matcher:       *fuzzyMatcher,
		EdgeThreshold: edgeThreshold,
	}, nil
}

// getAndValidateIntParameter extracts and validates the given required integer parameter from the
// given map of optional keys.
//
// Minimum and maximum value validation can be disabled by setting parameters min and max to
// math.MinInt32 and math.MaxInt32, respectively.
//
// If required is false and the parameter is not present in the map of optional keys, a value of 0
// will be returned.
func getAndValidateIntParameter(name AlgorithmParamOptKey, min, max int, required bool, optionalKeys map[string]string) (int, error) {
	// Validate bounds.
	if min >= max {
		// This is almost surely a programming error.
		panic(fmt.Sprintf("min must be strictly less than max, min was %d, max was %d", min, max))
	}

	// Validate presence.
	stringVal, ok := optionalKeys[string(name)]
	if !ok {
		if required {
			return 0, skerr.Fmt("required image matching parameter not found: %q", name)
		}
		return 0, nil
	}

	// Value cannot be empty.
	if strings.TrimSpace(stringVal) == "" {
		return 0, skerr.Fmt("image matching parameter %q cannot be empty", name)
	}

	// Value must be a valid 32-bit integer.
	//
	// Note: The "int" type in Go has a platform-specific bit size of *at least* 32 bits, so we
	// explicitly parse the value as a 32-bit int to keep things deterministic across platforms.
	// Additionally, this ensures the math.MinInt32 and math.MaxInt32 sentinel values for the mix and
	// max parameters work as expected.
	int64Val, err := strconv.ParseInt(stringVal, 0, 32)
	if err != nil {
		return 0, skerr.Fmt("parsing integer value for image matching parameter %q: %q", name, err.Error())
	}
	intVal := int(int64Val)

	// Value must be between bounds.
	if intVal < min || intVal > max {
		// No lower bound, so value must be violating the upper bound.
		if min == math.MinInt32 {
			return 0, skerr.Fmt("image matching parameter %q must be at most %d, was: %d", name, max, int64Val)
		}

		// No upper bound, so value must be violating the lower bound.
		if max == math.MaxInt32 {
			return 0, skerr.Fmt("image matching parameter %q must be at least %d, was: %d", name, min, int64Val)
		}

		// Value has both an upper and lower bound.
		return 0, skerr.Fmt("image matching parameter %q must be between %d and %d, was: %d", name, min, max, int64Val)
	}

	return intVal, nil
}
<|endoftext|>


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

package websocket

import (
	"compress/flate"
	"errors"
	"io"
	"strings"
	"sync"
)

const (
	minCompressionLevel     = flate.HuffmanOnly
	maxCompressionLevel     = flate.BestCompression
	defaultCompressionLevel = 1
)

var (
	flateWriterPools [maxCompressionLevel - minCompressionLevel]sync.Pool
	flateReaderPool  = sync.Pool{New: func() interface{} {
		return flate.NewReader(nil)
	}}
)

func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
	const tail =
	// Add four bytes as specified in RFC
	"\x00\x00\xff\xff" +
		// Add final block to squelch unexpected EOF error from flate reader.
		"\x01\x00\x00\xff\xff"

	fr, _ := flateReaderPool.Get().(io.ReadCloser)
	fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
	return &flateReadWrapper{fr}
}

func isValidCompressionLevel(level int) bool {
	return minCompressionLevel <= level && level <= maxCompressionLevel
}

func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
	p := &flateWriterPools[level-minCompressionLevel]
	tw := &truncWriter{w: w}
	fw, _ := p.Get().(*flate.Writer)
	if fw == nil {
		fw, _ = flate.NewWriter(tw, level)
	} else {
		fw.Reset(tw)
	}
	return &flateWriteWrapper{fw: fw, tw: tw, p: p}
}

// truncWriter is an io.Writer that writes all but the last four bytes of the
// stream to another io.Writer.
type truncWriter struct {
	w io.WriteCloser
	n int
	p [4]byte
}

func (w *truncWriter) Write(p []byte) (int, error) {
	n := 0

	// fill buffer first for simplicity.
	if w.n < len(w.p) {
		n = copy(w.p[w.n:], p)
		p = p[n:]
		w.n += n
		if len(p) == 0 {
			return n, nil
		}
	}

	m := len(p)
	if m > len(w.p) {
		m = len(w.p)
	}

	if nn, err := w.w.Write(w.p[:m]); err != nil {
		return n + nn, err
	}

	copy(w.p[:], w.p[m:])
	copy(w.p[len(w.p)-m:], p[len(p)-m:])
	nn, err := w.w.Write(p[:len(p)-m])
	return n + nn, err
}

type flateWriteWrapper struct {
	fw *flate.Writer
	tw *truncWriter
	p  *sync.Pool
}

func (w *flateWriteWrapper) Write(p []byte) (int, error) {
	if w.fw == nil {
		return 0, errWriteClosed
	}
	return w.fw.Write(p)
}

func (w *flateWriteWrapper) Close() error {
	if w.fw == nil {
		return errWriteClosed
	}
	err1 := w.fw.Flush()
	w.p.Put(w.fw)
	w.fw = nil
	if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
		return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
	}
	err2 := w.tw.w.Close()
	if err1 != nil {
		return err1
	}
	return err2
}

type flateReadWrapper struct {
	fr io.ReadCloser
}

func (r *flateReadWrapper) Read(p []byte) (int, error) {
	if r.fr == nil {
		return 0, io.ErrClosedPipe
	}
	n, err := r.fr.Read(p)
	if err == io.EOF {
		// Preemptively place the reader back in the pool. This helps with
		// scenarios where the application does not call NextReader() soon after
		// this final read.
		r.Close()
	}
	return n, err
}

func (r *flateReadWrapper) Close() error {
	if r.fr == nil {
		return io.ErrClosedPipe
	}
	err := r.fr.Close()
	flateReaderPool.Put(r.fr)
	r.fr = nil
	return err
}
<commit_msg>Fix to compile on Go < 1.6<commit_after>// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"compress/flate"
	"errors"
	"io"
	"strings"
	"sync"
)

const (
	minCompressionLevel     = -2 // flate.HuffmanOnly
	maxCompressionLevel     = flate.BestCompression
	defaultCompressionLevel = 1
)

var (
	flateWriterPools [maxCompressionLevel - minCompressionLevel]sync.Pool
	flateReaderPool  = sync.Pool{New: func() interface{} {
		return flate.NewReader(nil)
	}}
)

func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
	const tail =
	// Add four bytes as specified in RFC
	"\x00\x00\xff\xff" +
		// Add final block to squelch unexpected EOF error from flate reader.
		"\x01\x00\x00\xff\xff"

	fr, _ := flateReaderPool.Get().(io.ReadCloser)
	fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
	return &flateReadWrapper{fr}
}

func isValidCompressionLevel(level int) bool {
	return minCompressionLevel <= level && level <= maxCompressionLevel
}

func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
	p := &flateWriterPools[level-minCompressionLevel]
	tw := &truncWriter{w: w}
	fw, _ := p.Get().(*flate.Writer)
	if fw == nil {
		fw, _ = flate.NewWriter(tw, level)
	} else {
		fw.Reset(tw)
	}
	return &flateWriteWrapper{fw: fw, tw: tw, p: p}
}

// truncWriter is an io.Writer that writes all but the last four bytes of the
// stream to another io.Writer.
type truncWriter struct {
	w io.WriteCloser
	n int
	p [4]byte
}

func (w *truncWriter) Write(p []byte) (int, error) {
	n := 0

	// fill buffer first for simplicity.
	if w.n < len(w.p) {
		n = copy(w.p[w.n:], p)
		p = p[n:]
		w.n += n
		if len(p) == 0 {
			return n, nil
		}
	}

	m := len(p)
	if m > len(w.p) {
		m = len(w.p)
	}

	if nn, err := w.w.Write(w.p[:m]); err != nil {
		return n + nn, err
	}

	copy(w.p[:], w.p[m:])
	copy(w.p[len(w.p)-m:], p[len(p)-m:])
	nn, err := w.w.Write(p[:len(p)-m])
	return n + nn, err
}

type flateWriteWrapper struct {
	fw *flate.Writer
	tw *truncWriter
	p  *sync.Pool
}

func (w *flateWriteWrapper) Write(p []byte) (int, error) {
	if w.fw == nil {
		return 0, errWriteClosed
	}
	return w.fw.Write(p)
}

func (w *flateWriteWrapper) Close() error {
	if w.fw == nil {
		return errWriteClosed
	}
	err1 := w.fw.Flush()
	w.p.Put(w.fw)
	w.fw = nil
	if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
		return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
	}
	err2 := w.tw.w.Close()
	if err1 != nil {
		return err1
	}
	return err2
}

type flateReadWrapper struct {
	fr io.ReadCloser
}

func (r *flateReadWrapper) Read(p []byte) (int, error) {
	if r.fr == nil {
		return 0, io.ErrClosedPipe
	}
	n, err := r.fr.Read(p)
	if err == io.EOF {
		// Preemptively place the reader back in the pool. This helps with
		// scenarios where the application does not call NextReader() soon after
		// this final read.
		r.Close()
	}
	return n, err
}

func (r *flateReadWrapper) Close() error {
	if r.fr == nil {
		return io.ErrClosedPipe
	}
	err := r.fr.Close()
	flateReaderPool.Put(r.fr)
	r.fr = nil
	return err
}
<|endoftext|>


<commit_before>package git

import (
	"os"
	"testing"
)

var tempConfig = "./temp.gitconfig"

func setupConfig() (*Config, error) {
	var (
		c   *Config
		err error
	)

	c, err = OpenOndisk(nil, tempConfig)
	if err != nil {
		return nil, err
	}

	err = c.SetString("foo.bar", "baz")
	if err != nil {
		return nil, err
	}
	err = c.SetBool("foo.bool", true)
	if err != nil {
		return nil, err
	}
	err = c.SetInt32("foo.int32", 32)
	if err != nil {
		return nil, err
	}
	err = c.SetInt64("foo.int64", 64)
	if err != nil {
		return nil, err
	}

	return c, err
}

func cleanupConfig() {
	os.Remove(tempConfig)
}

type TestRunner func(*Config, *testing.T)

var tests = []TestRunner{
	// LookupString
	func(c *Config, t *testing.T) {
		val, err := c.LookupString("foo.bar")
		if err != nil {
			t.Errorf("Got LookupString error: '%v', expected none\n", err)
		}
		if val != "baz" {
			t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val)
		}
	},
	// LookupBool
	func(c *Config, t *testing.T) {
		val, err := c.LookupBool("foo.bool")
		if err != nil {
			t.Errorf("Got LookupBool error: '%v', expected none\n", err)
		}
		if !val {
			t.Errorf("Got %t from LookupBool, expected 'false'\n", val)
		}
	},
	// LookupInt32
	func(c *Config, t *testing.T) {
		val, err := c.LookupInt32("foo.int32")
		if err != nil {
			t.Errorf("Got LookupInt32 error: '%v', expected none\n", err)
		}
		if val != 32 {
			t.Errorf("Got %v, expected 32\n", val)
		}
	},
	// LookupInt64
	func(c *Config, t *testing.T) {
		val, err := c.LookupInt64("foo.int64")
		if err != nil {
			t.Errorf("Got LookupInt64 error: '%v', expected none\n", err)
		}
		if val != 64 {
			t.Errorf("Got %v, expected 64\n", val)
		}
	},
}

func TestConfigLookups(t *testing.T) {
	t.Parallel()
	var (
		err error
		c   *Config
	)

	c, err = setupConfig()
	defer cleanupConfig()

	if err != nil {
		t.Errorf("Setup error: '%v'. Expected none\n", err)
		return
	}
	defer c.Free()

	for _, test := range tests {
		test(c, t)
	}
}
<commit_msg>Fixed issues with tests<commit_after>package git

import (
	"os"
	"testing"
)

var tempConfig = "./temp.gitconfig"

func setupConfig() (*Config, error) {
	var (
		c   *Config
		err error
	)

	c, err = OpenOndisk(tempConfig)
	if err != nil {
		return nil, err
	}

	err = c.SetString("foo.bar", "baz")
	if err != nil {
		return nil, err
	}
	err = c.SetBool("foo.bool", true)
	if err != nil {
		return nil, err
	}
	err = c.SetInt32("foo.int32", 32)
	if err != nil {
		return nil, err
	}
	err = c.SetInt64("foo.int64", 64)
	if err != nil {
		return nil, err
	}

	return c, err
}

func cleanupConfig() {
	os.Remove(tempConfig)
}

type TestRunner func(*Config, *testing.T)

var tests = []TestRunner{
	// LookupString
	func(c *Config, t *testing.T) {
		val, err := c.LookupString("foo.bar")
		if err != nil {
			t.Errorf("Got LookupString error: '%v', expected none\n", err)
		}
		if val != "baz" {
			t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val)
		}
	},
	// LookupBool
	func(c *Config, t *testing.T) {
		val, err := c.LookupBool("foo.bool")
		if err != nil {
			t.Errorf("Got LookupBool error: '%v', expected none\n", err)
		}
		if !val {
			t.Errorf("Got %t from LookupBool, expected 'false'\n", val)
		}
	},
	// LookupInt32
	func(c *Config, t *testing.T) {
		val, err := c.LookupInt32("foo.int32")
		if err != nil {
			t.Errorf("Got LookupInt32 error: '%v', expected none\n", err)
		}
		if val != 32 {
			t.Errorf("Got %v, expected 32\n", val)
		}
	},
	// LookupInt64
	func(c *Config, t *testing.T) {
		val, err := c.LookupInt64("foo.int64")
		if err != nil {
			t.Errorf("Got LookupInt64 error: '%v', expected none\n", err)
		}
		if val != 64 {
			t.Errorf("Got %v, expected 64\n", val)
		}
	},
}

func TestConfigLookups(t *testing.T) {
	t.Parallel()
	var (
		err error
		c   *Config
	)

	c, err = setupConfig()
	defer cleanupConfig()

	if err != nil {
		t.Errorf("Setup error: '%v'. Expected none\n", err)
		return
	}
	defer c.Free()

	for _, test := range tests {
		test(c, t)
	}
}
<|endoftext|>


<commit_before>package shgf

import "testing"

type confTest struct {
	host  string
	port  int
	valid bool
}

type checkTest struct {
	conf  Config
	valid bool
}

func TestLocalConf(t *testing.T) {
	var tests = []confTest{
		{"127.0.0.1", 8080, true},
		{"127.0.0.1", 9090, true},
		{"127.0.0.1", 0, false},
		{"127.0.0.1", 100000000, false},
	}

	for _, test := range tests {
		if c, err := LocalConf(test.port); test.valid && err != nil {
			t.Errorf("expected nil, got %s", err)
		} else if c.Hostname != test.host {
			t.Errorf("expected %s, got %s", test.host, c.Hostname)
		} else if c.Port != test.port {
			t.Errorf("expected %d, got %d", test.port, c.Port)
		} else if !test.valid && err == nil {
			t.Error("expected error, got nil")
		}
	}
}

func TestBasicConf(t *testing.T) {
	var tests = []confTest{
		{"127.0.0.1", 8080, true},
		{"1", 8080, false},
		{"127.0.0.1", 0, false},
		{"wrong", 100000000, false},
	}

	for _, test := range tests {
		if _, err := BasicConf(test.host, test.port); test.valid && err != nil {
			t.Errorf("expected nil, got %s", err)
		} else if !test.valid && err == nil {
			t.Error("expected error, got nil")
		}
	}
}

func TestConfig_check(t *testing.T) {
	var tests = []checkTest{
		{Config{Hostname: "127.0.0.1", Port: 8080}, true},
		{Config{Hostname: "1", Port: 8080}, false},
		{Config{Hostname: "127.0.0.1", Port: 0}, false},
		{Config{Hostname: "wrong", Port: 100000000}, false},
	}

	for _, test := range tests {
		if err := test.conf.check(); test.valid && err != nil {
			t.Errorf("expected nil, got %s", err)
		} else if !test.valid && err == nil {
			t.Error("expected error, got nil")
		}
	}
}
<commit_msg>useless test<commit_after>package shgf

import "testing"

type confTest struct {
	host  string
	port  int
	valid bool
}

type checkTest struct {
	conf  Config
	valid bool
}

func TestBasicConf(t *testing.T) {
	var tests = []confTest{
		{"127.0.0.1", 8080, true},
		{"1", 8080, false},
		{"127.0.0.1", 0, false},
		{"wrong", 100000000, false},
	}

	for _, test := range tests {
		if _, err := BasicConf(test.host, test.port); test.valid && err != nil {
			t.Errorf("expected nil, got %s", err)
		} else if !test.valid && err == nil {
			t.Error("expected error, got nil")
		}
	}
}

func TestConfig_check(t *testing.T) {
	var tests = []checkTest{
		{Config{Hostname: "127.0.0.1", Port: 8080}, true},
		{Config{Hostname: "1", Port: 8080}, false},
		{Config{Hostname: "127.0.0.1", Port: 0}, false},
		{Config{Hostname: "wrong", Port: 100000000}, false},
	}

	for _, test := range tests {
		if err := test.conf.check(); test.valid && err != nil {
			t.Errorf("expected nil, got %s", err)
		} else if !test.valid && err == nil {
			t.Error("expected error, got nil")
		}
	}
}
<|endoftext|>


<commit_before>package semver

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// Constraints is one or more constraint that a semantic version can be
// checked against.
type Constraints struct {
	constraints [][]*constraint
}

// NewConstraint returns a Constraints instance that a Version instance can
// be checked against. If there is a parse error it will be returned.
func NewConstraint(c string) (*Constraints, error) {

	// Rewrite the constraint string to convert things like ranges
	// into something the checks can handle.
	for _, rwf := range rewriteFuncs {
		c = rwf(c)
	}

	ors := strings.Split(c, "||")
	or := make([][]*constraint, len(ors))
	for k, v := range ors {
		cs := strings.Split(v, ",")
		result := make([]*constraint, len(cs))
		for i, s := range cs {
			pc, err := parseConstraint(s)
			if err != nil {
				return nil, err
			}

			result[i] = pc
		}
		or[k] = result
	}

	o := &Constraints{constraints: or}
	return o, nil
}

// Check tests if a version satisfies the constraints.
func (cs Constraints) Check(v *Version) bool {
	// loop over the ORs and check the inner ANDs
	for _, o := range cs.constraints {
		joy := true
		for _, c := range o {
			if !c.check(v) {
				joy = false
				break
			}
		}

		if joy {
			return true
		}
	}

	return false
}

var constraintOps map[string]cfunc
var constraintRegex *regexp.Regexp

func init() {
	constraintOps = map[string]cfunc{
		"":   constraintEqual,
		"=":  constraintEqual,
		"!=": constraintNotEqual,
		">":  constraintGreaterThan,
		"<":  constraintLessThan,
		">=": constraintGreaterThanEqual,
		"=>": constraintGreaterThanEqual,
		"<=": constraintLessThanEqual,
		"=<": constraintLessThanEqual,
	}

	ops := make([]string, 0, len(constraintOps))
	for k := range constraintOps {
		ops = append(ops, regexp.QuoteMeta(k))
	}

	constraintRegex = regexp.MustCompile(fmt.Sprintf(
		`^\s*(%s)\s*(%s)\s*$`,
		strings.Join(ops, "|"),
		SemVerRegex))

	constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
		`\s*(%s)\s*-\s*(%s)\s*`,
		SemVerRegex, SemVerRegex))

	constraintCaretRegex = regexp.MustCompile(`\^` + cvRegex)

	constraintTildeRegex = regexp.MustCompile(`~>?` + cvRegex)
}

// An individual constraint
type constraint struct {
	// The callback function for the restraint. It performs the logic for
	// the constraint.
	function cfunc

	// The version used in the constraint check. For example, if a constraint
	// is '<= 2.0.0' the con a version instance representing 2.0.0.
	con *Version
}

// Check if a version meets the constraint
func (c *constraint) check(v *Version) bool {
	return c.function(v, c.con)
}

type cfunc func(v, c *Version) bool

func parseConstraint(c string) (*constraint, error) {
	m := constraintRegex.FindStringSubmatch(c)
	if m == nil {
		return nil, fmt.Errorf("improper constraint: %s", c)
	}

	con, err := NewVersion(m[2])
	if err != nil {

		// The constraintRegex should catch any regex parsing errors. So,
		// we should never get here.
		return nil, errors.New("constraint Parser Error")
	}

	cs := &constraint{
		function: constraintOps[m[1]],
		con:      con,
	}
	return cs, nil
}

// Constraint functions
func constraintEqual(v, c *Version) bool {
	return v.Equal(c)
}

func constraintNotEqual(v, c *Version) bool {
	return !v.Equal(c)
}

func constraintGreaterThan(v, c *Version) bool {
	return v.Compare(c) == 1
}

func constraintLessThan(v, c *Version) bool {
	return v.Compare(c) == -1
}

func constraintGreaterThanEqual(v, c *Version) bool {
	return v.Compare(c) >= 0
}

func constraintLessThanEqual(v, c *Version) bool {
	return v.Compare(c) <= 0
}

type rwfunc func(i string) string

var constraintRangeRegex *regexp.Regexp
var rewriteFuncs = []rwfunc{
	rewriteRange,
	rewriteCarets,
	rewriteTilde,
}

const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`

func isX(x string) bool {
	l := strings.ToLower(x)
	return l == "x" || l == "*"
}

func rewriteRange(i string) string {
	m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
		o = strings.Replace(o, v[0], t, 1)
	}

	return o
}

// ^ --> * (any)
// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
// ^1.2.3 --> >=1.2.3 <2.0.0
// ^1.2.0 --> >=1.2.0 <2.0.0
var constraintCaretRegex *regexp.Regexp

func rewriteCarets(i string) string {
	m := constraintCaretRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		if isX(v[1]) {
			o = strings.Replace(o, v[0], ">=0.0.0", 1)
		} else if isX(strings.TrimPrefix(v[2], ".")) {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s.0%s, < %d", v[1], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else if isX(strings.TrimPrefix(v[3], ".")) {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s%s.0%s, < %d", v[1], v[2], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else {
			ii, err := strconv.ParseInt(v[1], 10, 32)
			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}

			t := fmt.Sprintf(">= %s%s%s%s, < %d", v[1], v[2], v[3], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		}
	}

	return o
}

// ~, ~> --> * (any)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
var constraintTildeRegex *regexp.Regexp

func rewriteTilde(i string) string {
	m := constraintTildeRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		if isX(v[1]) {
			o = strings.Replace(o, v[0], ">=0.0.0", 1)
		} else if isX(strings.TrimPrefix(v[2], ".")) || v[2] == "" {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s.0.0%s, < %d.0.0", v[1], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else if isX(strings.TrimPrefix(v[3], ".")) {
			ii, err := strconv.ParseInt(strings.TrimPrefix(v[2], "."), 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s%s.0%s, < %s.%d.0", v[1], v[2], v[4], v[1], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else {
			ii, err := strconv.ParseInt(strings.TrimPrefix(v[2], "."), 10, 32)
			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}

			t := fmt.Sprintf(">= %s%s%s%s, < %s.%d.0", v[1], v[2], v[3], v[4], v[1], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		}
	}

	return o
}
<commit_msg>Added the ability to detect a .x at the top level. For use later.<commit_after>package semver

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// Constraints is one or more constraint that a semantic version can be
// checked against.
type Constraints struct {
	constraints [][]*constraint
}

// NewConstraint returns a Constraints instance that a Version instance can
// be checked against. If there is a parse error it will be returned.
func NewConstraint(c string) (*Constraints, error) {

	// Rewrite the constraint string to convert things like ranges
	// into something the checks can handle.
	for _, rwf := range rewriteFuncs {
		c = rwf(c)
	}

	ors := strings.Split(c, "||")
	or := make([][]*constraint, len(ors))
	for k, v := range ors {
		cs := strings.Split(v, ",")
		result := make([]*constraint, len(cs))
		for i, s := range cs {
			pc, err := parseConstraint(s)
			if err != nil {
				return nil, err
			}

			result[i] = pc
		}
		or[k] = result
	}

	o := &Constraints{constraints: or}
	return o, nil
}

// Check tests if a version satisfies the constraints.
func (cs Constraints) Check(v *Version) bool {
	// loop over the ORs and check the inner ANDs
	for _, o := range cs.constraints {
		joy := true
		for _, c := range o {
			if !c.check(v) {
				joy = false
				break
			}
		}

		if joy {
			return true
		}
	}

	return false
}

var constraintOps map[string]cfunc
var constraintRegex *regexp.Regexp

func init() {
	constraintOps = map[string]cfunc{
		"":   constraintEqual,
		"=":  constraintEqual,
		"!=": constraintNotEqual,
		">":  constraintGreaterThan,
		"<":  constraintLessThan,
		">=": constraintGreaterThanEqual,
		"=>": constraintGreaterThanEqual,
		"<=": constraintLessThanEqual,
		"=<": constraintLessThanEqual,
	}

	ops := make([]string, 0, len(constraintOps))
	for k := range constraintOps {
		ops = append(ops, regexp.QuoteMeta(k))
	}

	constraintRegex = regexp.MustCompile(fmt.Sprintf(
		`^\s*(%s)\s*(%s)\s*$`,
		strings.Join(ops, "|"),
		cvRegex))

	constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
		`\s*(%s)\s*-\s*(%s)\s*`,
		SemVerRegex, SemVerRegex))

	constraintCaretRegex = regexp.MustCompile(`\^` + cvRegex)

	constraintTildeRegex = regexp.MustCompile(`~>?` + cvRegex)
}

// An individual constraint
type constraint struct {
	// The callback function for the restraint. It performs the logic for
	// the constraint.
	function cfunc

	// The version used in the constraint check. For example, if a constraint
	// is '<= 2.0.0' the con a version instance representing 2.0.0.
	con *Version

	// When an x is used as part of the version (e.g., 1.x)
	dirty bool
}

// Check if a version meets the constraint
func (c *constraint) check(v *Version) bool {
	return c.function(v, c.con)
}

type cfunc func(v, c *Version) bool

func parseConstraint(c string) (*constraint, error) {
	m := constraintRegex.FindStringSubmatch(c)
	if m == nil {
		return nil, fmt.Errorf("improper constraint: %s", c)
	}

	ver := m[2]
	dirty := false
	if isX(strings.TrimPrefix(m[4], ".")) {
		dirty = true
		ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
	} else if isX(strings.TrimPrefix(m[5], ".")) {
		dirty = true
		ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
	}

	con, err := NewVersion(ver)
	if err != nil {

		// The constraintRegex should catch any regex parsing errors. So,
		// we should never get here.
		return nil, errors.New("constraint Parser Error")
	}

	cs := &constraint{
		function: constraintOps[m[1]],
		con:      con,
		dirty:    dirty,
	}
	return cs, nil
}

// Constraint functions
func constraintEqual(v, c *Version) bool {
	return v.Equal(c)
}

func constraintNotEqual(v, c *Version) bool {
	return !v.Equal(c)
}

func constraintGreaterThan(v, c *Version) bool {
	return v.Compare(c) == 1
}

func constraintLessThan(v, c *Version) bool {
	return v.Compare(c) == -1
}

func constraintGreaterThanEqual(v, c *Version) bool {
	return v.Compare(c) >= 0
}

func constraintLessThanEqual(v, c *Version) bool {
	return v.Compare(c) <= 0
}

type rwfunc func(i string) string

var constraintRangeRegex *regexp.Regexp
var rewriteFuncs = []rwfunc{
	rewriteRange,
	rewriteCarets,
	rewriteTilde,
}

const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`

func isX(x string) bool {
	l := strings.ToLower(x)
	return l == "x" || l == "*"
}

func rewriteRange(i string) string {
	m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
		o = strings.Replace(o, v[0], t, 1)
	}

	return o
}

// ^ --> * (any)
// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
// ^1.2.3 --> >=1.2.3 <2.0.0
// ^1.2.0 --> >=1.2.0 <2.0.0
var constraintCaretRegex *regexp.Regexp

func rewriteCarets(i string) string {
	m := constraintCaretRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		if isX(v[1]) {
			o = strings.Replace(o, v[0], ">=0.0.0", 1)
		} else if isX(strings.TrimPrefix(v[2], ".")) {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s.0%s, < %d", v[1], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else if isX(strings.TrimPrefix(v[3], ".")) {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s%s.0%s, < %d", v[1], v[2], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else {
			ii, err := strconv.ParseInt(v[1], 10, 32)
			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}

			t := fmt.Sprintf(">= %s%s%s%s, < %d", v[1], v[2], v[3], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		}
	}

	return o
}

// ~, ~> --> * (any)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
var constraintTildeRegex *regexp.Regexp

func rewriteTilde(i string) string {
	m := constraintTildeRegex.FindAllStringSubmatch(i, -1)
	if m == nil {
		return i
	}
	o := i
	for _, v := range m {
		if isX(v[1]) {
			o = strings.Replace(o, v[0], ">=0.0.0", 1)
		} else if isX(strings.TrimPrefix(v[2], ".")) || v[2] == "" {
			ii, err := strconv.ParseInt(v[1], 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s.0.0%s, < %d.0.0", v[1], v[4], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else if isX(strings.TrimPrefix(v[3], ".")) {
			ii, err := strconv.ParseInt(strings.TrimPrefix(v[2], "."), 10, 32)

			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}
			t := fmt.Sprintf(">= %s%s.0%s, < %s.%d.0", v[1], v[2], v[4], v[1], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		} else {
			ii, err := strconv.ParseInt(strings.TrimPrefix(v[2], "."), 10, 32)
			// The regular expression and isX checking should already make this
			// safe so something is broken in the lib.
			if err != nil {
				panic("Error converting string to Int. Should not occur.")
			}

			t := fmt.Sprintf(">= %s%s%s%s, < %s.%d.0", v[1], v[2], v[3], v[4], v[1], ii+1)
			o = strings.Replace(o, v[0], t, 1)
		}
	}

	return o
}
<|endoftext|>


<commit_before>package reform_test

import (
	"errors"
	"time"

	"github.com/AlekSi/pointer"
	"github.com/enodata/faker"

	"gopkg.in/reform.v1"
	"gopkg.in/reform.v1/dialects/postgresql"
	. "gopkg.in/reform.v1/internal/test/models"
)

func (s *ReformSuite) TestInsert() {
	newEmail := faker.Internet().Email()
	person := &Person{Email: &newEmail}
	err := s.q.Insert(person)
	s.NoError(err)
	s.NotEqual(int32(0), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(time.Now(), person.CreatedAt, time.Second)
	s.Nil(person.UpdatedAt)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertWithValues() {
	t := time.Now()
	newEmail := faker.Internet().Email()
	person := &Person{Email: &newEmail, CreatedAt: t, UpdatedAt: &t}
	err := s.q.Insert(person)
	s.NoError(err)
	s.NotEqual(int32(0), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(t, person.CreatedAt, time.Second)
	s.WithinDuration(t, *person.UpdatedAt, time.Second)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertWithPrimaryKey() {
	newEmail := faker.Internet().Email()
	person := &Person{ID: 50, Email: &newEmail}
	err := s.q.Insert(person)
	s.NoError(err)
	s.Equal(int32(50), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(time.Now(), person.CreatedAt, time.Second)
	s.Nil(person.UpdatedAt)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertReturning() {
	if s.q.Dialect != postgresql.Dialect {
		s.T().Skip("only PostgreSQL supports RETURNING syntax, other dialects support only integers from LastInsertId")
	}

	project := &Project{ID: "new", End: pointer.ToTime(time.Now().Truncate(24 * time.Hour))}
	err := s.q.Insert(project)
	s.NoError(err)
	s.Equal("new", project.ID)

	project2, err := s.q.FindByPrimaryKeyFrom(ProjectTable, project.ID)
	s.NoError(err)
	s.Equal(project, project2)

	err = s.q.Insert(project)
	s.Error(err)
}

func (s *ReformSuite) TestInsertIntoView() {
	pp := &PersonProject{PersonID: 1, ProjectID: "baron"}
	err := s.q.Insert(pp)
	s.NoError(err)

	err = s.q.Insert(pp)
	s.Error(err)

	s.RestartTransaction()

	pp = &PersonProject{PersonID: 1, ProjectID: "no_such_project"}
	err = s.q.Insert(pp)
	s.Error(err)
}

func (s *ReformSuite) TestInsertMulti() {
	newEmail := faker.Internet().Email()
	newName := faker.Name().Name()
	person1, person2 := &Person{Email: &newEmail}, &Person{Name: newName}
	err := s.q.InsertMulti(person1, person2)
	s.NoError(err)

	s.Equal(int32(0), person1.ID)
	s.Equal("", person1.Name)
	s.Equal(&newEmail, person1.Email)
	s.WithinDuration(time.Now(), person1.CreatedAt, time.Second)
	s.Nil(person1.UpdatedAt)

	s.Equal(int32(0), person2.ID)
	s.Equal(newName, person2.Name)
	s.Nil(person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Nil(person2.UpdatedAt)
}

func (s *ReformSuite) TestInsertMultiWithPrimaryKeys() {
	newEmail := faker.Internet().Email()
	newName := faker.Name().Name()
	person1, person2 := &Person{ID: 50, Email: &newEmail}, &Person{ID: 51, Name: newName}
	err := s.q.InsertMulti(person1, person2)
	s.NoError(err)

	s.Equal(int32(50), person1.ID)
	s.Equal("", person1.Name)
	s.Equal(&newEmail, person1.Email)
	s.WithinDuration(time.Now(), person1.CreatedAt, time.Second)
	s.Nil(person1.UpdatedAt)

	s.Equal(int32(51), person2.ID)
	s.Equal(newName, person2.Name)
	s.Nil(person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Nil(person2.UpdatedAt)

	person, err := s.q.FindByPrimaryKeyFrom(PersonTable, person1.ID)
	s.NoError(err)
	s.Equal(person1, person)

	person, err = s.q.FindByPrimaryKeyFrom(PersonTable, person2.ID)
	s.NoError(err)
	s.Equal(person2, person)
}

func (s *ReformSuite) TestUpdate() {
	var person Person
	err := s.q.Update(&person)
	s.Equal(reform.ErrNoPK, err)

	person.ID = 99
	err = s.q.Update(&person)
	s.Equal(reform.ErrNoRows, err)

	err = s.q.FindByPrimaryKeyTo(&person, 102)
	s.NoError(err)

	person.Email = pointer.ToString(faker.Internet().Email())
	err = s.q.Update(&person)
	s.NoError(err)
	s.Equal(personCreated, person.CreatedAt)
	s.Require().NotNil(person.UpdatedAt)
	s.WithinDuration(time.Now(), *person.UpdatedAt, time.Second)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(&person, person2)
}

func (s *ReformSuite) TestUpdateOverwrite() {
	newEmail := faker.Internet().Email()
	person := Person{ID: 102, Email: pointer.ToString(newEmail)}
	err := s.q.Update(&person)
	s.NoError(err)

	var person2 Person
	err = s.q.FindByPrimaryKeyTo(&person2, person.ID)
	s.NoError(err)
	s.Equal("", person2.Name)
	s.Equal(&newEmail, person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Require().NotNil(person2.UpdatedAt)
	s.WithinDuration(time.Now(), *person2.UpdatedAt, time.Second)
}

func (s *ReformSuite) TestUpdateColumns() {
	newName := faker.Name().Name()
	newEmail := faker.Internet().Email()

	for p, columns := range map[*Person][]string{
		&Person{Name: "Elfrieda Abbott", Email: &newEmail}:                             {"email", "updated_at"},
		&Person{Name: newName, Email: pointer.ToString("elfrieda_abbott@example.org")}: {"name", "name", "updated_at"},
		&Person{Name: newName, Email: &newEmail}:                                       {"name", "email", "id", "id", "updated_at"},
	} {
		var person Person
		err := s.q.FindByPrimaryKeyTo(&person, 102)
		s.NoError(err)

		person.Name = p.Name
		person.Email = p.Email
		err = s.q.UpdateColumns(&person, columns...)
		s.NoError(err)
		s.Equal(personCreated, person.CreatedAt)
		s.Require().NotNil(person.UpdatedAt)
		s.WithinDuration(time.Now(), *person.UpdatedAt, time.Second)

		person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
		s.NoError(err)
		s.Equal(&person, person2)

		s.RestartTransaction()
	}

	person := &Person{ID: 102, Name: newName, Email: &newEmail, CreatedAt: personCreated}
	for e, columns := range map[error][]string{
		errors.New("reform: unexpected columns: [foo]"): {"foo"},
		errors.New("reform: nothing to update"):         {},
	} {
		err := s.q.UpdateColumns(person, columns...)
		s.Error(err)
		s.Equal(e, err)

		s.RestartTransaction()
	}
}

func (s *ReformSuite) TestSave() {
	newName := faker.Name().Name()
	person := &Person{Name: newName}
	err := s.q.Save(person)
	s.NoError(err)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(newName, person2.(*Person).Name)
	s.Nil(person2.(*Person).Email)
	s.Equal(person, person2)

	newEmail := faker.Internet().Email()
	person.Email = &newEmail
	err = s.q.Save(person)
	s.NoError(err)

	person2, err = s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(newName, person2.(*Person).Name)
	s.Equal(&newEmail, person2.(*Person).Email)
	s.Equal(person, person2)
}

func (s *ReformSuite) TestDelete() {
	person := &Person{ID: 1}
	err := s.q.Delete(person)
	s.NoError(err)
	err = s.q.Reload(person)
	s.Equal(reform.ErrNoRows, err)

	project := &Project{ID: "baron"}
	err = s.q.Delete(project)
	s.NoError(err)
	err = s.q.Reload(project)
	s.Equal(reform.ErrNoRows, err)

	project = &Project{}
	err = s.q.Delete(project)
	s.Equal(reform.ErrNoPK, err)

	project = &Project{ID: "no_such_project"}
	err = s.q.Delete(project)
	s.Equal(reform.ErrNoRows, err)
}

func (s *ReformSuite) TestDeleteFrom() {
	ra, err := s.q.DeleteFrom(PersonTable, "WHERE email IS NULL")
	s.NoError(err)
	s.Equal(uint(3), ra)

	ra, err = s.q.DeleteFrom(PersonTable, "WHERE email IS NULL")
	s.NoError(err)
	s.Equal(uint(0), ra)

	// -1 second for SQLite3, otherwise it also deletes queen itself ¯\_(ツ)_/¯
	ra, err = s.q.DeleteFrom(ProjectTable, "WHERE start < "+s.q.Placeholder(1), queenStart.Add(-time.Second))
	s.NoError(err)
	s.Equal(uint(3), ra)

	ra, err = s.q.DeleteFrom(ProjectTable, "")
	s.NoError(err)
	s.Equal(uint(2), ra)

	ra, err = s.q.DeleteFrom(ProjectTable, "WHERE invalid_tail")
	s.Error(err)
	s.Equal(uint(0), ra)
}

func (s *ReformSuite) TestCommandsSchema() {
	if s.q.Dialect != postgresql.Dialect {
		s.T().Skip("only PostgreSQL supports schemas")
	}

	legacyPerson := &LegacyPerson{Name: pointer.ToString(faker.Name().Name())}
	err := s.q.Save(legacyPerson)
	s.NoError(err)
	err = s.q.Save(legacyPerson)
	s.NoError(err)
	err = s.q.Delete(legacyPerson)
	s.NoError(err)
}
<commit_msg>Add test for corner cases.<commit_after>package reform_test

import (
	"errors"
	"time"

	"github.com/AlekSi/pointer"
	"github.com/enodata/faker"

	"gopkg.in/reform.v1"
	"gopkg.in/reform.v1/dialects/postgresql"
	. "gopkg.in/reform.v1/internal/test/models"
)

func (s *ReformSuite) TestInsert() {
	newEmail := faker.Internet().Email()
	person := &Person{Email: &newEmail}
	err := s.q.Insert(person)
	s.NoError(err)
	s.NotEqual(int32(0), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(time.Now(), person.CreatedAt, time.Second)
	s.Nil(person.UpdatedAt)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertWithValues() {
	t := time.Now()
	newEmail := faker.Internet().Email()
	person := &Person{Email: &newEmail, CreatedAt: t, UpdatedAt: &t}
	err := s.q.Insert(person)
	s.NoError(err)
	s.NotEqual(int32(0), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(t, person.CreatedAt, time.Second)
	s.WithinDuration(t, *person.UpdatedAt, time.Second)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertWithPrimaryKey() {
	newEmail := faker.Internet().Email()
	person := &Person{ID: 50, Email: &newEmail}
	err := s.q.Insert(person)
	s.NoError(err)
	s.Equal(int32(50), person.ID)
	s.Equal("", person.Name)
	s.Equal(&newEmail, person.Email)
	s.WithinDuration(time.Now(), person.CreatedAt, time.Second)
	s.Nil(person.UpdatedAt)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(person, person2)

	err = s.q.Insert(person)
	s.Error(err)
}

func (s *ReformSuite) TestInsertReturning() {
	if s.q.Dialect != postgresql.Dialect {
		s.T().Skip("only PostgreSQL supports RETURNING syntax, other dialects support only integers from LastInsertId")
	}

	project := &Project{ID: "new", End: pointer.ToTime(time.Now().Truncate(24 * time.Hour))}
	err := s.q.Insert(project)
	s.NoError(err)
	s.Equal("new", project.ID)

	project2, err := s.q.FindByPrimaryKeyFrom(ProjectTable, project.ID)
	s.NoError(err)
	s.Equal(project, project2)

	err = s.q.Insert(project)
	s.Error(err)
}

func (s *ReformSuite) TestInsertIntoView() {
	pp := &PersonProject{PersonID: 1, ProjectID: "baron"}
	err := s.q.Insert(pp)
	s.NoError(err)

	err = s.q.Insert(pp)
	s.Error(err)

	s.RestartTransaction()

	pp = &PersonProject{PersonID: 1, ProjectID: "no_such_project"}
	err = s.q.Insert(pp)
	s.Error(err)
}

func (s *ReformSuite) TestInsertMulti() {
	newEmail := faker.Internet().Email()
	newName := faker.Name().Name()
	person1, person2 := &Person{Email: &newEmail}, &Person{Name: newName}
	err := s.q.InsertMulti(person1, person2)
	s.NoError(err)

	s.Equal(int32(0), person1.ID)
	s.Equal("", person1.Name)
	s.Equal(&newEmail, person1.Email)
	s.WithinDuration(time.Now(), person1.CreatedAt, time.Second)
	s.Nil(person1.UpdatedAt)

	s.Equal(int32(0), person2.ID)
	s.Equal(newName, person2.Name)
	s.Nil(person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Nil(person2.UpdatedAt)
}

func (s *ReformSuite) TestInsertMultiWithPrimaryKeys() {
	newEmail := faker.Internet().Email()
	newName := faker.Name().Name()
	person1, person2 := &Person{ID: 50, Email: &newEmail}, &Person{ID: 51, Name: newName}
	err := s.q.InsertMulti(person1, person2)
	s.NoError(err)

	s.Equal(int32(50), person1.ID)
	s.Equal("", person1.Name)
	s.Equal(&newEmail, person1.Email)
	s.WithinDuration(time.Now(), person1.CreatedAt, time.Second)
	s.Nil(person1.UpdatedAt)

	s.Equal(int32(51), person2.ID)
	s.Equal(newName, person2.Name)
	s.Nil(person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Nil(person2.UpdatedAt)

	person, err := s.q.FindByPrimaryKeyFrom(PersonTable, person1.ID)
	s.NoError(err)
	s.Equal(person1, person)

	person, err = s.q.FindByPrimaryKeyFrom(PersonTable, person2.ID)
	s.NoError(err)
	s.Equal(person2, person)
}

func (s *ReformSuite) TestInsertMultiMixes() {
	err := s.q.InsertMulti()
	s.NoError(err)

	err = s.q.InsertMulti(&Person{}, &Project{})
	s.Error(err)

	err = s.q.InsertMulti(&Person{ID: 1}, &Person{})
	s.Error(err)
}

func (s *ReformSuite) TestUpdate() {
	var person Person
	err := s.q.Update(&person)
	s.Equal(reform.ErrNoPK, err)

	person.ID = 99
	err = s.q.Update(&person)
	s.Equal(reform.ErrNoRows, err)

	err = s.q.FindByPrimaryKeyTo(&person, 102)
	s.NoError(err)

	person.Email = pointer.ToString(faker.Internet().Email())
	err = s.q.Update(&person)
	s.NoError(err)
	s.Equal(personCreated, person.CreatedAt)
	s.Require().NotNil(person.UpdatedAt)
	s.WithinDuration(time.Now(), *person.UpdatedAt, time.Second)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(&person, person2)
}

func (s *ReformSuite) TestUpdateOverwrite() {
	newEmail := faker.Internet().Email()
	person := Person{ID: 102, Email: pointer.ToString(newEmail)}
	err := s.q.Update(&person)
	s.NoError(err)

	var person2 Person
	err = s.q.FindByPrimaryKeyTo(&person2, person.ID)
	s.NoError(err)
	s.Equal("", person2.Name)
	s.Equal(&newEmail, person2.Email)
	s.WithinDuration(time.Now(), person2.CreatedAt, time.Second)
	s.Require().NotNil(person2.UpdatedAt)
	s.WithinDuration(time.Now(), *person2.UpdatedAt, time.Second)
}

func (s *ReformSuite) TestUpdateColumns() {
	newName := faker.Name().Name()
	newEmail := faker.Internet().Email()

	for p, columns := range map[*Person][]string{
		&Person{Name: "Elfrieda Abbott", Email: &newEmail}:                             {"email", "updated_at"},
		&Person{Name: newName, Email: pointer.ToString("elfrieda_abbott@example.org")}: {"name", "name", "updated_at"},
		&Person{Name: newName, Email: &newEmail}:                                       {"name", "email", "id", "id", "updated_at"},
	} {
		var person Person
		err := s.q.FindByPrimaryKeyTo(&person, 102)
		s.NoError(err)

		person.Name = p.Name
		person.Email = p.Email
		err = s.q.UpdateColumns(&person, columns...)
		s.NoError(err)
		s.Equal(personCreated, person.CreatedAt)
		s.Require().NotNil(person.UpdatedAt)
		s.WithinDuration(time.Now(), *person.UpdatedAt, time.Second)

		person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
		s.NoError(err)
		s.Equal(&person, person2)

		s.RestartTransaction()
	}

	person := &Person{ID: 102, Name: newName, Email: &newEmail, CreatedAt: personCreated}
	for e, columns := range map[error][]string{
		errors.New("reform: unexpected columns: [foo]"): {"foo"},
		errors.New("reform: nothing to update"):         {},
	} {
		err := s.q.UpdateColumns(person, columns...)
		s.Error(err)
		s.Equal(e, err)

		s.RestartTransaction()
	}
}

func (s *ReformSuite) TestSave() {
	newName := faker.Name().Name()
	person := &Person{Name: newName}
	err := s.q.Save(person)
	s.NoError(err)

	person2, err := s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(newName, person2.(*Person).Name)
	s.Nil(person2.(*Person).Email)
	s.Equal(person, person2)

	newEmail := faker.Internet().Email()
	person.Email = &newEmail
	err = s.q.Save(person)
	s.NoError(err)

	person2, err = s.q.FindByPrimaryKeyFrom(PersonTable, person.ID)
	s.NoError(err)
	s.Equal(newName, person2.(*Person).Name)
	s.Equal(&newEmail, person2.(*Person).Email)
	s.Equal(person, person2)
}

func (s *ReformSuite) TestDelete() {
	person := &Person{ID: 1}
	err := s.q.Delete(person)
	s.NoError(err)
	err = s.q.Reload(person)
	s.Equal(reform.ErrNoRows, err)

	project := &Project{ID: "baron"}
	err = s.q.Delete(project)
	s.NoError(err)
	err = s.q.Reload(project)
	s.Equal(reform.ErrNoRows, err)

	project = &Project{}
	err = s.q.Delete(project)
	s.Equal(reform.ErrNoPK, err)

	project = &Project{ID: "no_such_project"}
	err = s.q.Delete(project)
	s.Equal(reform.ErrNoRows, err)
}

func (s *ReformSuite) TestDeleteFrom() {
	ra, err := s.q.DeleteFrom(PersonTable, "WHERE email IS NULL")
	s.NoError(err)
	s.Equal(uint(3), ra)

	ra, err = s.q.DeleteFrom(PersonTable, "WHERE email IS NULL")
	s.NoError(err)
	s.Equal(uint(0), ra)

	// -1 second for SQLite3, otherwise it also deletes queen itself ¯\_(ツ)_/¯
	ra, err = s.q.DeleteFrom(ProjectTable, "WHERE start < "+s.q.Placeholder(1), queenStart.Add(-time.Second))
	s.NoError(err)
	s.Equal(uint(3), ra)

	ra, err = s.q.DeleteFrom(ProjectTable, "")
	s.NoError(err)
	s.Equal(uint(2), ra)

	ra, err = s.q.DeleteFrom(ProjectTable, "WHERE invalid_tail")
	s.Error(err)
	s.Equal(uint(0), ra)
}

func (s *ReformSuite) TestCommandsSchema() {
	if s.q.Dialect != postgresql.Dialect {
		s.T().Skip("only PostgreSQL supports schemas")
	}

	legacyPerson := &LegacyPerson{Name: pointer.ToString(faker.Name().Name())}
	err := s.q.Save(legacyPerson)
	s.NoError(err)
	err = s.q.Save(legacyPerson)
	s.NoError(err)
	err = s.q.Delete(legacyPerson)
	s.NoError(err)
}
<|endoftext|>


<commit_before>package bots

import (
	"bitbucket.com/debtstracker/gae_app/debtstracker/emoji"
	"fmt"
	"github.com/strongo/measurement-protocol"
	"net/http"
	"runtime/debug"
	"strings"
)

// The driver is doing initial request & final response processing
// That includes logging, creating input messages in a general format, sending response
type WebhookDriver interface {
	HandleWebhook(w http.ResponseWriter, r *http.Request, webhookHandler WebhookHandler)
}

type BotDriver struct {
	GaTrackingID string
	botHost      BotHost
	appContext   BotAppContext
	router       *WebhooksRouter
}

var _ WebhookDriver = (*BotDriver)(nil) // Ensure BotDriver is implementing interface WebhookDriver

func NewBotDriver(gaTrackingID string, appContext BotAppContext, host BotHost, router *WebhooksRouter) WebhookDriver {
	return BotDriver{GaTrackingID: gaTrackingID, appContext: appContext, botHost: host, router: router}
}

func (d BotDriver) HandleWebhook(w http.ResponseWriter, r *http.Request, webhookHandler WebhookHandler) {
	logger := d.botHost.Logger(r)
	c := d.botHost.Context(r) // TODO: It's wrong to have dependency on appengine here
	logger.Infof(c, "HandleWebhook() => webhookHandler: %T", webhookHandler)

	botContext, entriesWithInputs, err := webhookHandler.GetBotContextAndInputs(r)

	if err != nil {
		if _, ok := err.(AuthFailedError); ok {
			logger.Warningf(c, "Auth failed: %v", err)
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
		} else {
			logger.Errorf(c, "Failed to call webhookHandler.GetBotContext(r): %v", err)
			//http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return
	}
	logger.Infof(c, "Got %v entries", len(entriesWithInputs))

	var whc WebhookContext
	var gaMeasurement *measurement.BufferedSender
	gaMeasurement = measurement.NewBufferedSender([]string{d.GaTrackingID}, true, botContext.BotHost.GetHttpClient(r))

	defer func() {
		logger.Debugf(c, "driver.deferred(recover) - checking for panic & flush GA")
		if recovered := recover(); recovered != nil {
			messageText := fmt.Sprintf("Server error (panic): %v", recovered)
			logger.Criticalf(c, "Panic recovered: %s\n%s", messageText, debug.Stack())

			gaMessage := measurement.NewException(messageText, true)

			if whc != nil {
				gaMessage.Common = whc.GaCommon()
			} else {
				gaMessage.Common.ClientID = "c7ea15eb-3333-4d47-a002-9d1a14996371"
				gaMessage.Common.DataSource = "bot"
			}

			if err := gaMeasurement.Queue(gaMessage); err != nil {
				logger.Errorf(c, "Failed to queue exception details for GA: %v", err)
			} else {
				logger.Debugf(c, "Exception details queued for GA.")
			}
			logger.Debugf(c, "Flushing gaMeasurement (with exeception, len(queue): %v)...", gaMeasurement.QueueDepth())
			if err = gaMeasurement.Flush(); err != nil {
				logger.Errorf(c, "Failed to send exception details to GA: %v", err)
			} else {
				logger.Debugf(c, "Exception details sent to GA.")
			}
			if whc != nil {
				if whc.BotChatID() != nil {
					whc.Responder().SendMessage(c, whc.NewMessage(emoji.ERROR_ICON+" "+messageText), BotApiSendMessageOverResponse)
				}
			}
		} else {
			logger.Debugf(c, "Flushing gaMeasurement (len(queue): %v)...", gaMeasurement.QueueDepth())
			gaMeasurement.Flush()
		}
	}()

	if err != nil {
		logger.Errorf(c, "Failed to create new WebhookContext: %v", err)
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	botCoreStores := webhookHandler.CreateBotCoreStores(d.appContext, r)
	defer func() {
		logger.Debugf(c, "Closing BotChatStore...")
		chatEntity := whc.ChatEntity()
		if chatEntity.GetPreferredLanguage() == "" {
			chatEntity.SetPreferredLanguage(whc.Locale().Code5)
		}
		if err := botCoreStores.BotChatStore.Close(); err != nil {
			logger.Errorf(c, "Failed to close BotChatStore: %v", err)
		}
	}()

	for i, entryWithInputs := range entriesWithInputs {
		logger.Infof(c, "Entry[%v]: %v, %v inputs", i, entryWithInputs.Entry.GetID(), len(entryWithInputs.Inputs))
		for j, input := range entryWithInputs.Inputs {
			inputType := input.InputType()
			switch inputType {
			case WebhookInputMessage, WebhookInputInlineQuery, WebhookInputCallbackQuery, WebhookInputChosenInlineResult:
				switch inputType {
				case WebhookInputMessage:
					logger.Infof(c, "Input[%v].Message().Text(): %v", j, input.InputMessage().Text())
				case WebhookInputCallbackQuery:
					callbackQuery := input.InputCallbackQuery()
					callbackData := callbackQuery.GetData()
					logger.Infof(c, "Input[%v].InputCallbackQuery().GetData(): %v", j, callbackData)
				case WebhookInputInlineQuery:
					logger.Infof(c, "Input[%v].InputInlineQuery().GetQuery(): %v", j, input.InputInlineQuery().GetQuery())
				case WebhookInputChosenInlineResult:
					logger.Infof(c, "Input[%v].InputChosenInlineResult().GetInlineMessageID(): %v", j, input.InputChosenInlineResult().GetInlineMessageID())
				}
				whc = webhookHandler.CreateWebhookContext(d.appContext, r, botContext, input, botCoreStores, gaMeasurement.New(botContext.BotSettings.Mode != Production))
				if whc.GetBotSettings().Mode == Development && !strings.Contains(r.Host, "dev") {
					logger.Warningf(c, "whc.GetBotSettings().Mode == Development && !strings.Contains(r.Host, 'dev')")
					w.WriteHeader(http.StatusBadRequest)
					return
				}
				if whc.GetBotSettings().Mode == Staging && !strings.Contains(r.Host, "st1") {
					logger.Warningf(c, "whc.GetBotSettings().Mode == Staging && !strings.Contains(r.Host, 'st1')")
					w.WriteHeader(http.StatusBadRequest)
					return
				}
				responder := webhookHandler.GetResponder(w, whc)
				d.router.Dispatch(responder, whc)
			case WebhookInputUnknown:
				logger.Warningf(c, "Unknown input[%v] type", j)
			default:
				logger.Warningf(c, "Unhandled input[%v] type: %v=%v", j, inputType, WebhookInputTypeNames[inputType])
			}
		}
	}
}
<commit_msg>Transfers - added specific due date<commit_after>package bots

import (
	"bitbucket.com/debtstracker/gae_app/debtstracker/emoji"
	"fmt"
	"github.com/strongo/measurement-protocol"
	"net/http"
	"runtime/debug"
	"strings"
)

// The driver is doing initial request & final response processing
// That includes logging, creating input messages in a general format, sending response
type WebhookDriver interface {
	HandleWebhook(w http.ResponseWriter, r *http.Request, webhookHandler WebhookHandler)
}

type BotDriver struct {
	GaTrackingID string
	botHost      BotHost
	appContext   BotAppContext
	router       *WebhooksRouter
}

var _ WebhookDriver = (*BotDriver)(nil) // Ensure BotDriver is implementing interface WebhookDriver

func NewBotDriver(gaTrackingID string, appContext BotAppContext, host BotHost, router *WebhooksRouter) WebhookDriver {
	return BotDriver{GaTrackingID: gaTrackingID, appContext: appContext, botHost: host, router: router}
}

func (d BotDriver) HandleWebhook(w http.ResponseWriter, r *http.Request, webhookHandler WebhookHandler) {
	logger := d.botHost.Logger(r)
	c := d.botHost.Context(r) // TODO: It's wrong to have dependency on appengine here
	logger.Infof(c, "HandleWebhook() => webhookHandler: %T", webhookHandler)

	botContext, entriesWithInputs, err := webhookHandler.GetBotContextAndInputs(r)

	if err != nil {
		if _, ok := err.(AuthFailedError); ok {
			logger.Warningf(c, "Auth failed: %v", err)
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
		} else {
			logger.Errorf(c, "Failed to call webhookHandler.GetBotContext(r): %v", err)
			//http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return
	}
	logger.Infof(c, "Got %v entries", len(entriesWithInputs))

	var whc WebhookContext
	var gaMeasurement *measurement.BufferedSender
	gaMeasurement = measurement.NewBufferedSender([]string{d.GaTrackingID}, true, botContext.BotHost.GetHttpClient(r))

	defer func() {
		logger.Debugf(c, "driver.deferred(recover) - checking for panic & flush GA")
		if recovered := recover(); recovered != nil {
			messageText := fmt.Sprintf("Server error (panic): %v", recovered)
			logger.Criticalf(c, "Panic recovered: %s\n%s", messageText, debug.Stack())

			gaMessage := measurement.NewException(messageText, true)

			if whc != nil {
				gaMessage.Common = whc.GaCommon()
			} else {
				gaMessage.Common.ClientID = "c7ea15eb-3333-4d47-a002-9d1a14996371"
				gaMessage.Common.DataSource = "bot"
			}

			if err := gaMeasurement.Queue(gaMessage); err != nil {
				logger.Errorf(c, "Failed to queue exception details for GA: %v", err)
			} else {
				logger.Debugf(c, "Exception details queued for GA.")
			}
			logger.Debugf(c, "Flushing gaMeasurement (with exeception, len(queue): %v)...", gaMeasurement.QueueDepth())
			if err = gaMeasurement.Flush(); err != nil {
				logger.Errorf(c, "Failed to send exception details to GA: %v", err)
			} else {
				logger.Debugf(c, "Exception details sent to GA.")
			}
			if whc != nil {
				if whc.BotChatID() != nil {
					whc.Responder().SendMessage(c, whc.NewMessage(emoji.ERROR_ICON+" "+messageText), BotApiSendMessageOverResponse)
				}
			}
		} else {
			logger.Debugf(c, "Flushing gaMeasurement (len(queue): %v)...", gaMeasurement.QueueDepth())
			gaMeasurement.Flush()
		}
	}()

	if err != nil {
		logger.Errorf(c, "Failed to create new WebhookContext: %v", err)
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	botCoreStores := webhookHandler.CreateBotCoreStores(d.appContext, r)
	defer func() {
		logger.Debugf(c, "Closing BotChatStore...")
		chatEntity := whc.ChatEntity()
		if chatEntity != nil && chatEntity.GetPreferredLanguage() == "" {
			chatEntity.SetPreferredLanguage(whc.Locale().Code5)
		}
		if err := botCoreStores.BotChatStore.Close(); err != nil {
			logger.Errorf(c, "Failed to close BotChatStore: %v", err)
		}
	}()

	for i, entryWithInputs := range entriesWithInputs {
		logger.Infof(c, "Entry[%v]: %v, %v inputs", i, entryWithInputs.Entry.GetID(), len(entryWithInputs.Inputs))
		for j, input := range entryWithInputs.Inputs {
			inputType := input.InputType()
			switch inputType {
			case WebhookInputMessage, WebhookInputInlineQuery, WebhookInputCallbackQuery, WebhookInputChosenInlineResult:
				switch inputType {
				case WebhookInputMessage:
					logger.Infof(c, "Input[%v].Message().Text(): %v", j, input.InputMessage().Text())
				case WebhookInputCallbackQuery:
					callbackQuery := input.InputCallbackQuery()
					callbackData := callbackQuery.GetData()
					logger.Infof(c, "Input[%v].InputCallbackQuery().GetData(): %v", j, callbackData)
				case WebhookInputInlineQuery:
					logger.Infof(c, "Input[%v].InputInlineQuery().GetQuery(): %v", j, input.InputInlineQuery().GetQuery())
				case WebhookInputChosenInlineResult:
					logger.Infof(c, "Input[%v].InputChosenInlineResult().GetInlineMessageID(): %v", j, input.InputChosenInlineResult().GetInlineMessageID())
				}
				whc = webhookHandler.CreateWebhookContext(d.appContext, r, botContext, input, botCoreStores, gaMeasurement.New(botContext.BotSettings.Mode != Production))
				if whc.GetBotSettings().Mode == Development && !strings.Contains(r.Host, "dev") {
					logger.Warningf(c, "whc.GetBotSettings().Mode == Development && !strings.Contains(r.Host, 'dev')")
					w.WriteHeader(http.StatusBadRequest)
					return
				}
				if whc.GetBotSettings().Mode == Staging && !strings.Contains(r.Host, "st1") {
					logger.Warningf(c, "whc.GetBotSettings().Mode == Staging && !strings.Contains(r.Host, 'st1')")
					w.WriteHeader(http.StatusBadRequest)
					return
				}
				responder := webhookHandler.GetResponder(w, whc)
				d.router.Dispatch(responder, whc)
			case WebhookInputUnknown:
				logger.Warningf(c, "Unknown input[%v] type", j)
			default:
				logger.Warningf(c, "Unhandled input[%v] type: %v=%v", j, inputType, WebhookInputTypeNames[inputType])
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright © 2018 Mikael Berthe <mikael@lilotux.net>
//
// Licensed under the MIT license.
// Please see the LICENSE file is this directory.

package cmd

import (
	"os"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/McKael/madon"
)

var listsOpts struct {
	listID     int64
	accountID  int64
	accountIDs string
	title      string

	// Used for several subcommands to limit the number of results
	limit, keep uint
	all         bool
}

//listsCmd represents the lists command
var listsCmd = &cobra.Command{
	Use:     "lists",
	Aliases: []string{"list"},
	Short:   "Manage lists",
	Example: `  madonctl lists create --title "Friends"
  madonctl lists show
  madonctl lists show --list-id 3
  madonctl lists update --list-id 3 --title "Family"
  madonctl lists delete --list-id 3
  madonctl lists accounts --list-id 2
  madonctl lists add-accounts --list-id 2 --account-ids 123,456
  madonctl lists remove-accounts --list-id 2 --account-ids 456`,
}

func init() {
	RootCmd.AddCommand(listsCmd)

	// Subcommands
	listsCmd.AddCommand(listsSubcommands...)

	listsCmd.PersistentFlags().UintVarP(&listsOpts.limit, "limit", "l", 0, "Limit number of API results")
	listsCmd.PersistentFlags().UintVarP(&listsOpts.keep, "keep", "k", 0, "Limit number of results")
	listsCmd.PersistentFlags().BoolVar(&listsOpts.all, "all", false, "Fetch all results")

	listsCmd.PersistentFlags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID")

	listsGetSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
	// XXX accountUID?

	listsGetAccountsSubcommand.Flags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID")

	listsCreateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")
	listsUpdateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")

	listsAddAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsAddAccountsSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
	listsRemoveAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsRemoveAccountsSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
}

var listsSubcommands = []*cobra.Command{
	listsGetSubcommand,
	listsCreateSubcommand,
	listsUpdateSubcommand,
	listsDeleteSubcommand,
	listsGetAccountsSubcommand,
	listsAddAccountsSubcommand,
	listsRemoveAccountsSubcommand,
}

var listsGetSubcommand = &cobra.Command{
	Use:   "show",
	Short: "Display one or several lists",
	// TODO Long: ``,
	Aliases: []string{"get", "display", "ls"},
	RunE:    listsGetRunE,
}

var listsGetAccountsSubcommand = &cobra.Command{
	Use:   "accounts --list-id N",
	Short: "Display a list's accounts",
	RunE:  listsGetAccountsRunE,
}

var listsCreateSubcommand = &cobra.Command{
	Use:   "create --title TITLE",
	Short: "Create a list",
	RunE:  listsSetDeleteRunE,
}

var listsUpdateSubcommand = &cobra.Command{
	Use:   "update --list-id N --title TITLE",
	Short: "Update a list",
	RunE:  listsSetDeleteRunE,
}

var listsDeleteSubcommand = &cobra.Command{
	Use:     "delete --list-id N",
	Short:   "Delete a list",
	Aliases: []string{"rm", "del"},
	RunE:    listsSetDeleteRunE,
}

var listsAddAccountsSubcommand = &cobra.Command{
	Use:     "add-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Add one or several accounts to a list",
	Aliases: []string{"add-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

var listsRemoveAccountsSubcommand = &cobra.Command{
	Use:     "remove-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Remove one or several accounts from a list",
	Aliases: []string{"remove-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

func listsGetRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	if opt.listID > 0 {
		var list *madon.List
		list, err = gClient.GetList(opt.listID)
		obj = list
	} else {
		var lists []madon.List
		lists, err = gClient.GetLists(opt.accountID, limOpts)

		if opt.keep > 0 && len(lists) > int(opt.keep) {
			lists = lists[:opt.keep]
		}
		obj = lists
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsGetAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID <= 0 {
		return errors.New("missing list ID")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	var accounts []madon.Account
	accounts, err = gClient.GetListAccounts(opt.listID, limOpts)

	if opt.keep > 0 && len(accounts) > int(opt.keep) {
		accounts = accounts[:opt.keep]
	}
	obj = accounts

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsSetDeleteRunE(cmd *cobra.Command, args []string) error {
	const (
		actionUnknown = iota
		actionCreate
		actionUpdate
		actionDelete
	)

	var action int
	opt := listsOpts

	switch cmd.Name() {
	case "create":
		if opt.listID > 0 {
			return errors.New("list ID should not be provided with create")
		}
		action = actionCreate
	case "update":
		if opt.listID <= 0 {
			return errors.New("list ID is required")
		}
		action = actionUpdate
	case "delete", "rm", "del":
		action = actionDelete
	}

	// Additionnal checks
	if action == actionUnknown {
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsSetDeleteRunE: internal error")
	}

	if action != actionDelete && opt.title == "" {
		return errors.New("the list title is required")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	var obj interface{}
	var err error
	var list *madon.List

	switch action {
	case actionCreate:
		list, err = gClient.CreateList(opt.title)
		obj = list
	case actionUpdate:
		list, err = gClient.UpdateList(opt.listID, opt.title)
		obj = list
	case actionDelete:
		err = gClient.DeleteList(opt.listID)
		obj = nil
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsAddRemoveAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID <= 0 {
		return errors.New("missing list ID")
	}

	var ids []int64
	var err error
	ids, err = splitIDs(opt.accountIDs)
	if err != nil {
		return errors.New("cannot parse account IDs")
	}

	if opt.accountID > 0 { // Allow --account-id
		ids = []int64{opt.accountID}
	}
	if len(ids) < 1 {
		return errors.New("missing account IDs")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	switch cmd.Name() {
	case "add-account", "add-accounts":
		err = gClient.AddListAccounts(opt.listID, ids)
	case "remove-account", "remove-accounts":
		err = gClient.RemoveListAccounts(opt.listID, ids)
	default:
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsAddRemoveAccountsRunE: internal error")
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}

	return nil
}
<commit_msg>Add a "madonctl lists show" example<commit_after>// Copyright © 2018 Mikael Berthe <mikael@lilotux.net>
//
// Licensed under the MIT license.
// Please see the LICENSE file is this directory.

package cmd

import (
	"os"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/McKael/madon"
)

var listsOpts struct {
	listID     int64
	accountID  int64
	accountIDs string
	title      string

	// Used for several subcommands to limit the number of results
	limit, keep uint
	all         bool
}

//listsCmd represents the lists command
var listsCmd = &cobra.Command{
	Use:     "lists",
	Aliases: []string{"list"},
	Short:   "Manage lists",
	Example: `  madonctl lists create --title "Friends"
  madonctl lists show
  madonctl lists show --list-id 3
  madonctl lists update --list-id 3 --title "Family"
  madonctl lists delete --list-id 3
  madonctl lists accounts --list-id 2
  madonctl lists add-accounts --list-id 2 --account-ids 123,456
  madonctl lists remove-accounts --list-id 2 --account-ids 456
  madonctl lists show --account-id 123`,
}

func init() {
	RootCmd.AddCommand(listsCmd)

	// Subcommands
	listsCmd.AddCommand(listsSubcommands...)

	listsCmd.PersistentFlags().UintVarP(&listsOpts.limit, "limit", "l", 0, "Limit number of API results")
	listsCmd.PersistentFlags().UintVarP(&listsOpts.keep, "keep", "k", 0, "Limit number of results")
	listsCmd.PersistentFlags().BoolVar(&listsOpts.all, "all", false, "Fetch all results")

	listsCmd.PersistentFlags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID")

	listsGetSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
	// XXX accountUID?

	listsGetAccountsSubcommand.Flags().Int64VarP(&listsOpts.listID, "list-id", "G", 0, "List ID")

	listsCreateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")
	listsUpdateSubcommand.Flags().StringVar(&listsOpts.title, "title", "", "List title")

	listsAddAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsAddAccountsSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
	listsRemoveAccountsSubcommand.Flags().StringVar(&listsOpts.accountIDs, "account-ids", "", "Comma-separated list of account IDs")
	listsRemoveAccountsSubcommand.Flags().Int64VarP(&listsOpts.accountID, "account-id", "a", 0, "Account ID number")
}

var listsSubcommands = []*cobra.Command{
	listsGetSubcommand,
	listsCreateSubcommand,
	listsUpdateSubcommand,
	listsDeleteSubcommand,
	listsGetAccountsSubcommand,
	listsAddAccountsSubcommand,
	listsRemoveAccountsSubcommand,
}

var listsGetSubcommand = &cobra.Command{
	Use:   "show",
	Short: "Display one or several lists",
	// TODO Long: ``,
	Aliases: []string{"get", "display", "ls"},
	RunE:    listsGetRunE,
}

var listsGetAccountsSubcommand = &cobra.Command{
	Use:   "accounts --list-id N",
	Short: "Display a list's accounts",
	RunE:  listsGetAccountsRunE,
}

var listsCreateSubcommand = &cobra.Command{
	Use:   "create --title TITLE",
	Short: "Create a list",
	RunE:  listsSetDeleteRunE,
}

var listsUpdateSubcommand = &cobra.Command{
	Use:   "update --list-id N --title TITLE",
	Short: "Update a list",
	RunE:  listsSetDeleteRunE,
}

var listsDeleteSubcommand = &cobra.Command{
	Use:     "delete --list-id N",
	Short:   "Delete a list",
	Aliases: []string{"rm", "del"},
	RunE:    listsSetDeleteRunE,
}

var listsAddAccountsSubcommand = &cobra.Command{
	Use:     "add-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Add one or several accounts to a list",
	Aliases: []string{"add-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

var listsRemoveAccountsSubcommand = &cobra.Command{
	Use:     "remove-accounts --list-id N --account-ids ACC1,ACC2...",
	Short:   "Remove one or several accounts from a list",
	Aliases: []string{"remove-account"},
	RunE:    listsAddRemoveAccountsRunE,
}

func listsGetRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	if opt.listID > 0 {
		var list *madon.List
		list, err = gClient.GetList(opt.listID)
		obj = list
	} else {
		var lists []madon.List
		lists, err = gClient.GetLists(opt.accountID, limOpts)

		if opt.keep > 0 && len(lists) > int(opt.keep) {
			lists = lists[:opt.keep]
		}
		obj = lists
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsGetAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID <= 0 {
		return errors.New("missing list ID")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	// Set up LimitParams
	var limOpts *madon.LimitParams
	if opt.all || opt.limit > 0 {
		limOpts = new(madon.LimitParams)
		limOpts.All = opt.all
	}
	if opt.limit > 0 {
		limOpts.Limit = int(opt.limit)
	}

	var obj interface{}
	var err error

	var accounts []madon.Account
	accounts, err = gClient.GetListAccounts(opt.listID, limOpts)

	if opt.keep > 0 && len(accounts) > int(opt.keep) {
		accounts = accounts[:opt.keep]
	}
	obj = accounts

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsSetDeleteRunE(cmd *cobra.Command, args []string) error {
	const (
		actionUnknown = iota
		actionCreate
		actionUpdate
		actionDelete
	)

	var action int
	opt := listsOpts

	switch cmd.Name() {
	case "create":
		if opt.listID > 0 {
			return errors.New("list ID should not be provided with create")
		}
		action = actionCreate
	case "update":
		if opt.listID <= 0 {
			return errors.New("list ID is required")
		}
		action = actionUpdate
	case "delete", "rm", "del":
		action = actionDelete
	}

	// Additionnal checks
	if action == actionUnknown {
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsSetDeleteRunE: internal error")
	}

	if action != actionDelete && opt.title == "" {
		return errors.New("the list title is required")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	var obj interface{}
	var err error
	var list *madon.List

	switch action {
	case actionCreate:
		list, err = gClient.CreateList(opt.title)
		obj = list
	case actionUpdate:
		list, err = gClient.UpdateList(opt.listID, opt.title)
		obj = list
	case actionDelete:
		err = gClient.DeleteList(opt.listID)
		obj = nil
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}
	if obj == nil {
		return nil
	}

	p, err := getPrinter()
	if err != nil {
		errPrint("Error: %v", err)
		os.Exit(1)
	}
	return p.printObj(obj)
}

func listsAddRemoveAccountsRunE(cmd *cobra.Command, args []string) error {
	opt := listsOpts

	if opt.listID <= 0 {
		return errors.New("missing list ID")
	}

	var ids []int64
	var err error
	ids, err = splitIDs(opt.accountIDs)
	if err != nil {
		return errors.New("cannot parse account IDs")
	}

	if opt.accountID > 0 { // Allow --account-id
		ids = []int64{opt.accountID}
	}
	if len(ids) < 1 {
		return errors.New("missing account IDs")
	}

	// Log in
	if err := madonInit(true); err != nil {
		return err
	}

	switch cmd.Name() {
	case "add-account", "add-accounts":
		err = gClient.AddListAccounts(opt.listID, ids)
	case "remove-account", "remove-accounts":
		err = gClient.RemoveListAccounts(opt.listID, ids)
	default:
		// Shouldn't happen.  If it does, might be an unrecognized alias.
		return errors.New("listsAddRemoveAccountsRunE: internal error")
	}

	if err != nil {
		errPrint("Error: %s", err.Error())
		os.Exit(1)
	}

	return nil
}
<|endoftext|>