<commit_before>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package handler

import (
	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	"github.com/TheThingsNetwork/ttn/api/trace"
	"github.com/TheThingsNetwork/ttn/core/handler/device"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/pointer"
	"github.com/brocaar/lorawan"
)

func (h *handler) ConvertFromLoRaWAN(ctx ttnlog.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage, dev *device.Device) error {
	// Check for LoRaWAN
	if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil {
		return errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata")
	}

	// LoRaWAN: Unmarshal Uplink
	var phyPayload lorawan.PHYPayload
	err := phyPayload.UnmarshalBinary(ttnUp.Payload)
	if err != nil {
		return err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload")
	}

	// LoRaWAN: Validate MIC
	macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt
	ttnUp.Trace = ttnUp.Trace.WithEvent(trace.CheckMICEvent)
	ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey))
	if err != nil {
		return err
	}
	if !ok {
		return errors.NewErrNotFound("device that validates MIC")
	}

	appUp.HardwareSerial = dev.DevEUI.String()
	appUp.FCnt = macPayload.FHDR.FCnt
	ctx = ctx.WithField("FCnt", appUp.FCnt)
	if dev.FCntUp == appUp.FCnt {
		appUp.IsRetry = true
	}
	dev.FCntUp = appUp.FCnt

	// LoRaWAN: Decrypt
	if macPayload.FPort != nil {
		appUp.FPort = *macPayload.FPort
		if *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 {
			ctx = ctx.WithField("FCnt", appUp.FPort)
			if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil {
				return errors.NewErrInternal("Could not decrypt payload")
			}
			payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload)
			if !ok {
				return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload")
			}
			appUp.PayloadRaw = payload.Bytes
		}
	}

	if dev.CurrentDownlink != nil {
		// We have a downlink pending
		if dev.CurrentDownlink.Confirmed {
			// If it's confirmed, we can only unset it if we receive an ack.
			if macPayload.FHDR.FCtrl.ACK {
				dev.CurrentDownlink = nil

				// Send event over MQTT
				h.mqttEvent <- &types.DeviceEvent{
					AppID: appUp.AppID,
					DevID: appUp.DevID,
					Event: types.DownlinkAckEvent,
				}
			}
		} else if !appUp.IsRetry {
			// If it's unconfirmed (and the uplink is not a retry), we can unset it.
			dev.CurrentDownlink = nil
		}
	}

	return nil
}

func (h *handler) ConvertToLoRaWAN(ctx ttnlog.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage, dev *device.Device) error {
	// LoRaWAN: Unmarshal Downlink
	var phyPayload lorawan.PHYPayload
	err := phyPayload.UnmarshalBinary(ttnDown.Payload)
	if err != nil {
		return err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload")
	}
	if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil {
		macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt
	}

	// Abort when downlink not needed
	if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 {
		return ErrNotNeeded
	}

	// Set FPort
	if appDown.FPort != 0 {
		macPayload.FPort = &appDown.FPort
	}

	if appDown.Confirmed {
		phyPayload.MHDR.MType = lorawan.ConfirmedDataDown
	}

	// Set Payload
	if len(appDown.PayloadRaw) > 0 {
		ttnDown.Trace = ttnDown.Trace.WithEvent("set payload")
		macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}}
		if macPayload.FPort == nil || *macPayload.FPort == 0 {
			macPayload.FPort = pointer.Uint8(1)
		}
	} else {
		ttnDown.Trace = ttnDown.Trace.WithEvent("set empty payload")
		macPayload.FRMPayload = []lorawan.Payload{}
	}

	// Encrypt
	err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey))
	if err != nil {
		return err
	}

	// Set MIC
	err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))
	if err != nil {
		return err
	}

	// Marshal
	phyPayloadBytes, err := phyPayload.MarshalBinary()
	if err != nil {
		return err
	}

	ttnDown.Payload = phyPayloadBytes

	return nil
}
<commit_msg>Don't unset downlink on retries with ACK<commit_after>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package handler

import (
	ttnlog "github.com/TheThingsNetwork/go-utils/log"
	pb_broker "github.com/TheThingsNetwork/ttn/api/broker"
	"github.com/TheThingsNetwork/ttn/api/trace"
	"github.com/TheThingsNetwork/ttn/core/handler/device"
	"github.com/TheThingsNetwork/ttn/core/types"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/pointer"
	"github.com/brocaar/lorawan"
)

func (h *handler) ConvertFromLoRaWAN(ctx ttnlog.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage, dev *device.Device) error {
	// Check for LoRaWAN
	if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil {
		return errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata")
	}

	// LoRaWAN: Unmarshal Uplink
	var phyPayload lorawan.PHYPayload
	err := phyPayload.UnmarshalBinary(ttnUp.Payload)
	if err != nil {
		return err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload")
	}

	// LoRaWAN: Validate MIC
	macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt
	ttnUp.Trace = ttnUp.Trace.WithEvent(trace.CheckMICEvent)
	ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey))
	if err != nil {
		return err
	}
	if !ok {
		return errors.NewErrNotFound("device that validates MIC")
	}

	appUp.HardwareSerial = dev.DevEUI.String()
	appUp.FCnt = macPayload.FHDR.FCnt
	ctx = ctx.WithField("FCnt", appUp.FCnt)
	if dev.FCntUp == appUp.FCnt {
		appUp.IsRetry = true
	}
	dev.FCntUp = appUp.FCnt

	// LoRaWAN: Decrypt
	if macPayload.FPort != nil {
		appUp.FPort = *macPayload.FPort
		if *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 {
			ctx = ctx.WithField("FCnt", appUp.FPort)
			if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil {
				return errors.NewErrInternal("Could not decrypt payload")
			}
			payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload)
			if !ok {
				return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload")
			}
			appUp.PayloadRaw = payload.Bytes
		}
	}

	if dev.CurrentDownlink != nil && !appUp.IsRetry {
		// We have a downlink pending
		if dev.CurrentDownlink.Confirmed {
			// If it's confirmed, we can only unset it if we receive an ack.
			if macPayload.FHDR.FCtrl.ACK {
				dev.CurrentDownlink = nil

				// Send event over MQTT
				h.mqttEvent <- &types.DeviceEvent{
					AppID: appUp.AppID,
					DevID: appUp.DevID,
					Event: types.DownlinkAckEvent,
				}
			}
		} else {
			// If it's unconfirmed, we can unset it.
			dev.CurrentDownlink = nil
		}
	}

	return nil
}

func (h *handler) ConvertToLoRaWAN(ctx ttnlog.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage, dev *device.Device) error {
	// LoRaWAN: Unmarshal Downlink
	var phyPayload lorawan.PHYPayload
	err := phyPayload.UnmarshalBinary(ttnDown.Payload)
	if err != nil {
		return err
	}
	macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
	if !ok {
		return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload")
	}
	if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil {
		macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt
	}

	// Abort when downlink not needed
	if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 {
		return ErrNotNeeded
	}

	// Set FPort
	if appDown.FPort != 0 {
		macPayload.FPort = &appDown.FPort
	}

	if appDown.Confirmed {
		phyPayload.MHDR.MType = lorawan.ConfirmedDataDown
	}

	// Set Payload
	if len(appDown.PayloadRaw) > 0 {
		ttnDown.Trace = ttnDown.Trace.WithEvent("set payload")
		macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}}
		if macPayload.FPort == nil || *macPayload.FPort == 0 {
			macPayload.FPort = pointer.Uint8(1)
		}
	} else {
		ttnDown.Trace = ttnDown.Trace.WithEvent("set empty payload")
		macPayload.FRMPayload = []lorawan.Payload{}
	}

	// Encrypt
	err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey))
	if err != nil {
		return err
	}

	// Set MIC
	err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))
	if err != nil {
		return err
	}

	// Marshal
	phyPayloadBytes, err := phyPayload.MarshalBinary()
	if err != nil {
		return err
	}

	ttnDown.Payload = phyPayloadBytes

	return nil
}
<|endoftext|>


<commit_before>// Package histogram provides a Go implementation of BigML's histogram package
// for Clojure/Java. It is currently experiemental.
package histogram

import (
	"container/heap"
	"math"
	"sort"
)

type Bin struct {
	Count int
	Sum   float64
}

func (b *Bin) Update(x *Bin) {
	b.Count += x.Count
	b.Sum += x.Sum
}

func (b *Bin) Mean() float64 {
	return b.Sum / float64(b.Count)
}

type Bins []*Bin

func (bs Bins) Len() int           { return len(bs) }
func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() }
func (bs Bins) Swap(i, j int)      { bs[i], bs[j] = bs[j], bs[i] }

func (bs *Bins) Push(x interface{}) {
	*bs = append(*bs, x.(*Bin))
}

func (bs *Bins) Pop() interface{} {
	return bs.remove(len(*bs) - 1)
}

func (bs *Bins) remove(n int) *Bin {
	a := *bs
	if n < 0 || len(a) < n {
		return nil
	}
	x := a[n]
	*bs = append(a[:n], a[n+1:]...)
	return x
}

type Histogram struct {
	res *reservoir
}

func New(maxBins int) *Histogram {
	return &Histogram{res: newReservoir(maxBins)}
}

func (h *Histogram) Insert(f float64) {
	h.res.insert(&Bin{1, f})
	h.res.compress()
}

func (h *Histogram) Bins() Bins {
	return h.res.bins
}

type reservoir struct {
	n       int
	maxBins int
	bins    Bins
}

func newReservoir(maxBins int) *reservoir {
	return &reservoir{maxBins: maxBins}
}

func (r *reservoir) insert(bin *Bin) {
	r.n += bin.Count
	i := sort.Search(len(r.bins), func(i int) bool {
		return r.bins[i].Mean() >= bin.Mean()
	})
	if i < 0 || i == r.bins.Len() {
		// TODO(blake): Maybe use an .insert(i, bin) instead of
		// performing the extra work of a heap.Push.
		heap.Push(&r.bins, bin)
		return
	}
	r.bins[i].Update(bin)
}

func (r *reservoir) compress() {
	for r.bins.Len() > r.maxBins {
		minGapIndex := -1
		minGap := math.MaxFloat64
		for i := 0; i < r.bins.Len()-1; i++ {
			gap := gapWeight(r.bins[i], r.bins[i+1])
			if minGap > gap {
				minGap = gap
				minGapIndex = i
			}
		}
		prev := r.bins[minGapIndex]
		next := r.bins.remove(minGapIndex + 1)
		prev.Update(next)
	}
}

func gapWeight(prev, next *Bin) float64 {
	return next.Mean() - prev.Mean()
}
<commit_msg>remove garbage var<commit_after>// Package histogram provides a Go implementation of BigML's histogram package
// for Clojure/Java. It is currently experiemental.
package histogram

import (
	"container/heap"
	"math"
	"sort"
)

type Bin struct {
	Count int
	Sum   float64
}

func (b *Bin) Update(x *Bin) {
	b.Count += x.Count
	b.Sum += x.Sum
}

func (b *Bin) Mean() float64 {
	return b.Sum / float64(b.Count)
}

type Bins []*Bin

func (bs Bins) Len() int           { return len(bs) }
func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() }
func (bs Bins) Swap(i, j int)      { bs[i], bs[j] = bs[j], bs[i] }

func (bs *Bins) Push(x interface{}) {
	*bs = append(*bs, x.(*Bin))
}

func (bs *Bins) Pop() interface{} {
	return bs.remove(len(*bs) - 1)
}

func (bs *Bins) remove(n int) *Bin {
	if n < 0 || len(*bs) < n {
		return nil
	}
	x := (*bs)[n]
	*bs = append((*bs)[:n], (*bs)[n+1:]...)
	return x
}

type Histogram struct {
	res *reservoir
}

func New(maxBins int) *Histogram {
	return &Histogram{res: newReservoir(maxBins)}
}

func (h *Histogram) Insert(f float64) {
	h.res.insert(&Bin{1, f})
	h.res.compress()
}

func (h *Histogram) Bins() Bins {
	return h.res.bins
}

type reservoir struct {
	n       int
	maxBins int
	bins    Bins
}

func newReservoir(maxBins int) *reservoir {
	return &reservoir{maxBins: maxBins}
}

func (r *reservoir) insert(bin *Bin) {
	r.n += bin.Count
	i := sort.Search(len(r.bins), func(i int) bool {
		return r.bins[i].Mean() >= bin.Mean()
	})
	if i < 0 || i == r.bins.Len() {
		// TODO(blake): Maybe use an .insert(i, bin) instead of
		// performing the extra work of a heap.Push.
		heap.Push(&r.bins, bin)
		return
	}
	r.bins[i].Update(bin)
}

func (r *reservoir) compress() {
	for r.bins.Len() > r.maxBins {
		minGapIndex := -1
		minGap := math.MaxFloat64
		for i := 0; i < r.bins.Len()-1; i++ {
			gap := gapWeight(r.bins[i], r.bins[i+1])
			if minGap > gap {
				minGap = gap
				minGapIndex = i
			}
		}
		prev := r.bins[minGapIndex]
		next := r.bins.remove(minGapIndex + 1)
		prev.Update(next)
	}
}

func gapWeight(prev, next *Bin) float64 {
	return next.Mean() - prev.Mean()
}
<|endoftext|>


<commit_before>// Example of how to use CART trees for both Classification and Regression

package main

import (
	"fmt"

	"github.com/sjwhitworth/golearn/base"
)

func main() {
	/* Performance of CART Algorithm:

		Training Time for Titanic Dataset ≈ 611 µs
		Prediction Time for Titanic Datset ≈ 101 µs

		Complexity Analysis:
			1x Dataset --   x ms
			2x Dataset --   1.7x ms
			128x Dataset -- 74x ms

			Complexity is sub linear

		Sklearn:
		Training Time for Titanic Dataset ≈ 8.8 µs
		Prediction Time for Titanic Datset ≈ 7.87 µs

		This implementation and sci-kit learn produce the exact same tree for the exact same dataset.
		Predictions on the same test set also yield the exact same accuracy.

		This implementation is optimized to prevent redundant iterations over the dataset, but it is not completely optimized. Also, sklearn makes use of numpy to access column easily, whereas here a complete iteration is required.
	 	In terms of Hyperparameters, this implmentation gives you the ability to choose the impurity function and the maxDepth.
		Many of the other hyperparameters used in sklearn are not here, but pruning and impurity is included.
	*/

	// Load Titanic Data For classification
	classificationData, err := base.ParseCSVToInstances("../datasets/titanic.csv", false)
	if err != nil {
		panic(err)
	}
	trainData, testData := base.InstancesTrainTestSplit(classificationData, 0.5)

	// Create New Classification Tree
	// Hyperparameters - loss function, max Depth (-1 will split until pure), list of unique labels
	decTree := NewDecisionTreeClassifier("entropy", -1, []int64{0, 1})

	// Train Tree
	err = decTree.Fit(trainData)
	if err != nil {
		panic(err)
	}
	// Print out tree for visualization - shows splits and feature and predictions
	fmt.Println(decTree.String())

	// Access Predictions
	classificationPreds := decTree.Predict(testData)

	fmt.Println("Titanic Predictions")
	fmt.Println(classificationPreds)

	// Evaluate Accuracy on Test Data
	fmt.Println(decTree.Evaluate(testData))

	// Load House Price Data For Regression
	regressionData, err := base.ParseCSVToInstances("../datasets/boston_house_prices.csv", false)
	if err != nil {
		panic(err)
	}
	trainRegData, testRegData := base.InstancesTrainTestSplit(regressionData, 0.5)

	// Hyperparameters - Loss function, max Depth (-1 will split until pure)
	regTree := NewDecisionTreeRegressor("mse", -1)

	// Train Tree
	err = regTree.Fit(trainRegData)
	if err != nil {
		panic(err)
	}

	// Print out tree for visualization
	fmt.Println(regTree.String())

	// Access Predictions
	regressionPreds := regTree.Predict(testRegData)

	fmt.Println("Boston House Price Predictions")
	fmt.Println(regressionPreds)

}
<commit_msg>Fixing Bug<commit_after>// Example of how to use CART trees for both Classification and Regression

package main

import (
	"fmt"

	"github.com/sjwhitworth/golearn/base"
	"github.com/sjwhitworth/golearn/trees"

)

func main() {
	/* Performance of CART Algorithm:

		Training Time for Titanic Dataset ≈ 611 µs
		Prediction Time for Titanic Datset ≈ 101 µs

		Complexity Analysis:
			1x Dataset --   x ms
			2x Dataset --   1.7x ms
			128x Dataset -- 74x ms

			Complexity is sub linear

		Sklearn:
		Training Time for Titanic Dataset ≈ 8.8 µs
		Prediction Time for Titanic Datset ≈ 7.87 µs

		This implementation and sci-kit learn produce the exact same tree for the exact same dataset.
		Predictions on the same test set also yield the exact same accuracy.

		This implementation is optimized to prevent redundant iterations over the dataset, but it is not completely optimized. Also, sklearn makes use of numpy to access column easily, whereas here a complete iteration is required.
	 	In terms of Hyperparameters, this implmentation gives you the ability to choose the impurity function and the maxDepth.
		Many of the other hyperparameters used in sklearn are not here, but pruning and impurity is included.
	*/

	// Load Titanic Data For classification
	classificationData, err := base.ParseCSVToInstances("../datasets/titanic.csv", false)
	if err != nil {
		panic(err)
	}
	trainData, testData := base.InstancesTrainTestSplit(classificationData, 0.5)

	// Create New Classification Tree
	// Hyperparameters - loss function, max Depth (-1 will split until pure), list of unique labels
	decTree := NewDecisionTreeClassifier("entropy", -1, []int64{0, 1})

	// Train Tree
	err = decTree.Fit(trainData)
	if err != nil {
		panic(err)
	}
	// Print out tree for visualization - shows splits and feature and predictions
	fmt.Println(decTree.String())

	// Access Predictions
	classificationPreds := decTree.Predict(testData)

	fmt.Println("Titanic Predictions")
	fmt.Println(classificationPreds)

	// Evaluate Accuracy on Test Data
	fmt.Println(decTree.Evaluate(testData))

	// Load House Price Data For Regression
	regressionData, err := base.ParseCSVToInstances("../datasets/boston_house_prices.csv", false)
	if err != nil {
		panic(err)
	}
	trainRegData, testRegData := base.InstancesTrainTestSplit(regressionData, 0.5)

	// Hyperparameters - Loss function, max Depth (-1 will split until pure)
	regTree := NewDecisionTreeRegressor("mse", -1)

	// Train Tree
	err = regTree.Fit(trainRegData)
	if err != nil {
		panic(err)
	}

	// Print out tree for visualization
	fmt.Println(regTree.String())

	// Access Predictions
	regressionPreds := regTree.Predict(testRegData)

	fmt.Println("Boston House Price Predictions")
	fmt.Println(regressionPreds)

}
<|endoftext|>


<commit_before>package executor

import (
	"github.com/jinzhu/gorm"
	"strconv"
	"broker-gateway/entities"
)

type DB interface {
	Migrate()
	// Create a new object
	Create(value interface{})
	// Update completely
	Save(model interface{})
	// Update partially
	Update(model interface{}, attrs map[string]string) *gorm.DB

	Query() *gorm.DB

}

type DBConfig struct {
	Host string
	Port int
	User string
	Password string
	DBName string
}

type db struct {
	client *gorm.DB
}

func NewDB(config DBConfig) (DB, error)  {
	d, err := gorm.Open("mysql",config.User+":"+
		config.Password + "@tcp(" +
		config.Host + ":" +
		strconv.Itoa(config.Port) + ")/"+
		config.DBName+"?charset=utf8")

	if err != nil {
		return nil, err
	}
	return &db{
		client: d,
	},nil
}

func (d *db) Migrate()  {
	d.client.AutoMigrate(&entities.Future{}, &entities.Firm{}, &entities.Order{}, &entities.Consignation{})
}

func (d *db) Query() *gorm.DB {
	return d.client
}

func (d *db) Create(value interface{})  {
	d.client.Create(value)
}

func (d *db) Save(model interface{})  {
	d.client.Save(model)
}

func (d *db) Update(model interface{}, attrs map[string]string) *gorm.DB {
	return d.client.Model(model).Update(attrs)
}
<commit_msg>Fix error of []uint8->type *time.Time<commit_after>package executor

import (
	"github.com/jinzhu/gorm"
	"strconv"
	"broker-gateway/entities"
)

type DB interface {
	Migrate()
	// Create a new object
	Create(value interface{})
	// Update completely
	Save(model interface{})
	// Update partially
	Update(model interface{}, attrs map[string]string) *gorm.DB

	Query() *gorm.DB

}

type DBConfig struct {
	Host string
	Port int
	User string
	Password string
	DBName string
}

type db struct {
	client *gorm.DB
}

func NewDB(config DBConfig) (DB, error)  {
	d, err := gorm.Open("mysql",config.User+":"+
		config.Password + "@tcp(" +
		config.Host + ":" +
		strconv.Itoa(config.Port) + ")/"+
		config.DBName+"?charset=utf8&parseTime=true")

	if err != nil {
		return nil, err
	}
	return &db{
		client: d,
	},nil
}

func (d *db) Migrate()  {
	d.client.AutoMigrate(&entities.Future{}, &entities.Firm{}, &entities.Order{}, &entities.Consignation{})
}

func (d *db) Query() *gorm.DB {
	return d.client
}

func (d *db) Create(value interface{})  {
	d.client.Create(value)
}

func (d *db) Save(model interface{})  {
	d.client.Save(model)
}

func (d *db) Update(model interface{}, attrs map[string]string) *gorm.DB {
	return d.client.Model(model).Update(attrs)
}
<|endoftext|>


<commit_before>// Copyright ©2011-2012 Dan Kortschak <dan.kortschak@adelaide.edu.au>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Package seq provides the base for storage and manipulation of biological sequence information.
//
// A variety of sequence types are provided by derived packages including linear and protein sequence
// with and without quality scores. Multiple sequence data is also supported as unaligned sets and aligned sequences.
//
// Quality scoring is based on Phred scores, although there is the capacity to interconvert between Phred and
// Solexa scores and a Solexa quality package is provide, though not integrated.
package seq

import (
	"code.google.com/p/biogo/exp/alphabet"
	"code.google.com/p/biogo/exp/feat"
	"math"
)

const (
	Start = 1 << iota
	End
)

var (
	// The default value for Qphred scores from non-quality sequences.
	DefaultQphred alphabet.Qphred = 40
	// The default encoding for Qphred scores from non-quality sequences.
	DefaultEncoding alphabet.Encoding = alphabet.Sanger
)

type Alphabeter interface {
	Alphabet() alphabet.Alphabet
}

type QFilter func(a alphabet.Alphabet, thresh alphabet.Qphred, ql alphabet.QLetter) alphabet.Letter

// A Position holds a sequence position for all sequence types.
type Position struct {
	Col int // The index of a letter within a sequence.
	Row int // The specific sequence within a multiple sequence.
}

// A Sequence is a feature that stores sequence information.
type Sequence interface {
	Feature
	At(Position) alphabet.QLetter   // Return the letter at a specific position.
	Set(Position, alphabet.QLetter) // Set the letter at a specific position.
	Alphabet() alphabet.Alphabet    // Return the Alphabet being used.
	RevComp()                       // Reverse complement the sequence.
	Reverse()                       // Reverse the order of elements in the sequence.
	New() Sequence                  // Return a pointer to the zero value of the concrete type.
	Copy() Sequence                 // Return a copy of the Sequence.
	Slicer
	Conformationer
	ConformationSetter
}

// A Feature describes the basis for sequence features.
type Feature interface {
	feat.Feature
	feat.Offsetter
}

// A Conformationer can give information regarding the sequence's conformation. For the
// purposes of sequtils, types that are not a Conformationer are treated as linear.
type Conformationer interface {
	Conformation() feat.Conformation
}

// A ConformationSetter can set its sequence conformation.
type ConformationSetter interface {
	SetConformation(feat.Conformation)
}

// A Slicer returns and sets a Slice.
type Slicer interface {
	Slice() alphabet.Slice
	SetSlice(alphabet.Slice)
}

// A Scorer is a sequence type that provides Phred-based scoring information.
type Scorer interface {
	EAt(Position) float64          // Return the p(Error) for a specific position.
	SetE(Position, float64)        // Set the p(Error) for a specific position.
	Encoding() alphabet.Encoding   // Return the score encoding scheme.
	SetEncoding(alphabet.Encoding) // Set the score encoding scheme.
	QEncode(pos Position) byte     // Encode the quality at pos according the the encoding scheme.
}

// A Quality is a feature whose elements are Phred scores.
type Quality interface {
	Scorer
	Copy() Quality // Return a copy of the Quality.
}

// Getter describes the interface for sets of sequences or aligned multiple sequences.
type Getter interface {
	Rows() int
	Get(i int) Sequence
}

// GetterAppender is a type for sets of sequences or aligned multiple sequences that can append letters to individual or grouped sequences.
type GetterAppender interface {
	Getter
	AppendEach(a [][]alphabet.QLetter) (err error)
}

// An Appender can append letters.
type Appender interface {
	AppendLetters(...alphabet.Letter) error
	AppendQLetters(...alphabet.QLetter) error
}

// Aligned describes the interface for aligned multiple sequences.
type Aligned interface {
	Start() int
	End() int
	Rows() int
	Column(pos int, fill bool) []alphabet.Letter
	ColumnQL(pos int, fill bool) []alphabet.QLetter
}

// An AlignedAppenderis a multiple sequence alignment that can append letters.
type AlignedAppender interface {
	Aligned
	AppendColumns(a ...[]alphabet.QLetter) (err error)
	AppendEach(a [][]alphabet.QLetter) (err error)
}

// ConsenseFunc is a function type that returns the consensus letter for a column of an alignment.
type ConsenseFunc func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter

// The default ConsenseFunc function.
var DefaultConsensus = func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter {
	w := make([]int, alpha.Len())
	c := a.Column(pos, fill)

	for _, l := range c {
		if alpha.IsValid(l) {
			w[alpha.IndexOf(l)]++
		}
	}

	var max, maxi int
	for i, v := range w {
		if v > max {
			max, maxi = v, i
		}
	}

	return alphabet.QLetter{
		L: alpha.Letter(maxi),
		Q: alphabet.Ephred(1 - (float64(max) / float64(len(c)))),
	}
}

// Tolerance on float comparison for DefaultQConsensus.
var FloatTolerance float64 = 1e-10

// A default ConsenseFunc function that takes letter quality into account.
// http://staden.sourceforge.net/manual/gap4_unix_120.html
var DefaultQConsensus = func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter {
	w := make([]float64, alpha.Len())
	for i := range w {
		w[i] = 1
	}

	others := float64(alpha.Len() - 1)
	c := a.ColumnQL(pos, fill)
	for _, l := range c {
		if alpha.IsValid(l.L) {
			i, alt := alpha.IndexOf(l.L), l.Q.ProbE()
			p := (1 - alt)
			alt /= others
			for b := range w {
				if i == b {
					w[b] *= p
				} else {
					w[b] *= alt
				}
			}
		}
	}

	var (
		max         = 0.
		sum         float64
		best, count int
	)
	for _, p := range w {
		sum += p
	}
	for i, v := range w {
		if v /= sum; v > max {
			max, best = v, i
			count = 0
		}
		if v == max || math.Abs(max-v) < FloatTolerance {
			count++
		}
	}

	if count > 1 {
		return alphabet.QLetter{
			L: alpha.Ambiguous(),
			Q: 0,
		}
	}

	return alphabet.QLetter{
		L: alpha.Letter(best),
		Q: alphabet.Ephred(1 - max),
	}
}
<commit_msg>Cleaner organisation of the consensus functions<commit_after>// Copyright ©2011-2012 Dan Kortschak <dan.kortschak@adelaide.edu.au>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Package seq provides the base for storage and manipulation of biological sequence information.
//
// A variety of sequence types are provided by derived packages including linear and protein sequence
// with and without quality scores. Multiple sequence data is also supported as unaligned sets and aligned sequences.
//
// Quality scoring is based on Phred scores, although there is the capacity to interconvert between Phred and
// Solexa scores and a Solexa quality package is provide, though not integrated.
package seq

import (
	"code.google.com/p/biogo/exp/alphabet"
	"code.google.com/p/biogo/exp/feat"
	"math"
)

const (
	Start = 1 << iota
	End
)

var (
	// The default value for Qphred scores from non-quality sequences.
	DefaultQphred alphabet.Qphred = 40
	// The default encoding for Qphred scores from non-quality sequences.
	DefaultEncoding alphabet.Encoding = alphabet.Sanger
)

type Alphabeter interface {
	Alphabet() alphabet.Alphabet
}

type QFilter func(a alphabet.Alphabet, thresh alphabet.Qphred, ql alphabet.QLetter) alphabet.Letter

// A Position holds a sequence position for all sequence types.
type Position struct {
	Col int // The index of a letter within a sequence.
	Row int // The specific sequence within a multiple sequence.
}

// A Sequence is a feature that stores sequence information.
type Sequence interface {
	Feature
	At(Position) alphabet.QLetter   // Return the letter at a specific position.
	Set(Position, alphabet.QLetter) // Set the letter at a specific position.
	Alphabet() alphabet.Alphabet    // Return the Alphabet being used.
	RevComp()                       // Reverse complement the sequence.
	Reverse()                       // Reverse the order of elements in the sequence.
	New() Sequence                  // Return a pointer to the zero value of the concrete type.
	Copy() Sequence                 // Return a copy of the Sequence.
	Slicer
	Conformationer
	ConformationSetter
}

// A Feature describes the basis for sequence features.
type Feature interface {
	feat.Feature
	feat.Offsetter
}

// A Conformationer can give information regarding the sequence's conformation. For the
// purposes of sequtils, types that are not a Conformationer are treated as linear.
type Conformationer interface {
	Conformation() feat.Conformation
}

// A ConformationSetter can set its sequence conformation.
type ConformationSetter interface {
	SetConformation(feat.Conformation)
}

// A Slicer returns and sets a Slice.
type Slicer interface {
	Slice() alphabet.Slice
	SetSlice(alphabet.Slice)
}

// A Scorer is a sequence type that provides Phred-based scoring information.
type Scorer interface {
	EAt(Position) float64          // Return the p(Error) for a specific position.
	SetE(Position, float64)        // Set the p(Error) for a specific position.
	Encoding() alphabet.Encoding   // Return the score encoding scheme.
	SetEncoding(alphabet.Encoding) // Set the score encoding scheme.
	QEncode(pos Position) byte     // Encode the quality at pos according the the encoding scheme.
}

// A Quality is a feature whose elements are Phred scores.
type Quality interface {
	Scorer
	Copy() Quality // Return a copy of the Quality.
}

// Getter describes the interface for sets of sequences or aligned multiple sequences.
type Getter interface {
	Rows() int
	Get(i int) Sequence
}

// GetterAppender is a type for sets of sequences or aligned multiple sequences that can append letters to individual or grouped sequences.
type GetterAppender interface {
	Getter
	AppendEach(a [][]alphabet.QLetter) (err error)
}

// An Appender can append letters.
type Appender interface {
	AppendLetters(...alphabet.Letter) error
	AppendQLetters(...alphabet.QLetter) error
}

// Aligned describes the interface for aligned multiple sequences.
type Aligned interface {
	Start() int
	End() int
	Rows() int
	Column(pos int, fill bool) []alphabet.Letter
	ColumnQL(pos int, fill bool) []alphabet.QLetter
}

// An AlignedAppenderis a multiple sequence alignment that can append letters.
type AlignedAppender interface {
	Aligned
	AppendColumns(a ...[]alphabet.QLetter) (err error)
	AppendEach(a [][]alphabet.QLetter) (err error)
}

// ConsenseFunc is a function type that returns the consensus letter for a column of an alignment.
type ConsenseFunc func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter

var (
	// The default ConsenseFunc function.
	DefaultConsensus = func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter {
		w := make([]int, alpha.Len())
		c := a.Column(pos, fill)

		for _, l := range c {
			if alpha.IsValid(l) {
				w[alpha.IndexOf(l)]++
			}
		}

		var max, maxi int
		for i, v := range w {
			if v > max {
				max, maxi = v, i
			}
		}

		return alphabet.QLetter{
			L: alpha.Letter(maxi),
			Q: alphabet.Ephred(1 - (float64(max) / float64(len(c)))),
		}
	}

	// A default ConsenseFunc function that takes letter quality into account.
	// http://staden.sourceforge.net/manual/gap4_unix_120.html
	DefaultQConsensus = func(a Aligned, alpha alphabet.Alphabet, pos int, fill bool) alphabet.QLetter {
		w := make([]float64, alpha.Len())
		for i := range w {
			w[i] = 1
		}

		others := float64(alpha.Len() - 1)
		c := a.ColumnQL(pos, fill)
		for _, l := range c {
			if alpha.IsValid(l.L) {
				i, alt := alpha.IndexOf(l.L), l.Q.ProbE()
				p := (1 - alt)
				alt /= others
				for b := range w {
					if i == b {
						w[b] *= p
					} else {
						w[b] *= alt
					}
				}
			}
		}

		var (
			max         = 0.
			sum         float64
			best, count int
		)
		for _, p := range w {
			sum += p
		}
		for i, v := range w {
			if v /= sum; v > max {
				max, best = v, i
				count = 0
			}
			if v == max || math.Abs(max-v) < FloatTolerance {
				count++
			}
		}

		if count > 1 {
			return alphabet.QLetter{
				L: alpha.Ambiguous(),
				Q: 0,
			}
		}

		return alphabet.QLetter{
			L: alpha.Letter(best),
			Q: alphabet.Ephred(1 - max),
		}
	}

	// Tolerance on float comparison for DefaultQConsensus.
	FloatTolerance float64 = 1e-10
)
<|endoftext|>


<commit_before>package logrus_sentry

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/getsentry/raven-go"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

func getAndDel(d logrus.Fields, key string) (string, bool) {
	var (
		ok  bool
		v   interface{}
		val string
	)
	if v, ok = d[key]; !ok {
		return "", false
	}

	if val, ok = v.(string); !ok {
		return "", false
	}
	delete(d, key)
	return val, true
}

func getAndDelRequest(d logrus.Fields, key string) (*http.Request, bool) {
	var (
		ok  bool
		v   interface{}
		req *http.Request
	)
	if v, ok = d[key]; !ok {
		return nil, false
	}
	if req, ok = v.(*http.Request); !ok || req == nil {
		return nil, false
	}
	delete(d, key)
	return req, true
}

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent
	Timeout time.Duration

	client *raven.Client
	levels []logrus.Level
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return &SentryHook{100 * time.Millisecond, client, levels}, nil
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return &SentryHook{100 * time.Millisecond, client, levels}, nil
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	return &SentryHook{100 * time.Millisecond, client, levels}, nil
}

// Called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: logger, server_name and http_request
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Interfaces = append(
		packet.Interfaces,
		raven.NewException(
			errors.New(entry.Message),
			raven.NewStacktrace(4, 10, nil),
		),
	)
	d := entry.Data

	if logger, ok := getAndDel(d, "logger"); ok {
		packet.Logger = logger
	}
	if serverName, ok := getAndDel(d, "server_name"); ok {
		packet.ServerName = serverName
	}
	if req, ok := getAndDelRequest(d, "http_request"); ok {
		packet.Interfaces = append(packet.Interfaces, raven.NewHttp(req))
	}
	packet.Extra = map[string]interface{}(d)

	_, errCh := hook.client.Capture(packet, nil)
	timeout := hook.Timeout
	if timeout != 0 {
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
	return nil
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}
<commit_msg>Add package prefix argument<commit_after>package logrus_sentry

import (
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/getsentry/raven-go"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

func getAndDel(d logrus.Fields, key string) (string, bool) {
	var (
		ok  bool
		v   interface{}
		val string
	)
	if v, ok = d[key]; !ok {
		return "", false
	}

	if val, ok = v.(string); !ok {
		return "", false
	}
	delete(d, key)
	return val, true
}

func getAndDelRequest(d logrus.Fields, key string) (*http.Request, bool) {
	var (
		ok  bool
		v   interface{}
		req *http.Request
	)
	if v, ok = d[key]; !ok {
		return nil, false
	}
	if req, ok = v.(*http.Request); !ok || req == nil {
		return nil, false
	}
	delete(d, key)
	return req, true
}

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent
	Timeout time.Duration

	client             *raven.Client
	levels             []logrus.Level
	appPackagePrefixes []string
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return &SentryHook{100 * time.Millisecond, client, levels}, nil
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return &SentryHook{100 * time.Millisecond, client, levels}, nil
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level, appPackagePrefixes []string) (*SentryHook, error) {
	return &SentryHook{
		Timeout:            100 * time.Millisecond,
		client:             client,
		levels:             levels,
		appPackagePrefixes: appPackagePrefixes,
	}, nil
}

// Called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: logger, server_name and http_request
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Interfaces = append(
		packet.Interfaces,
		raven.NewException(
			errors.New(entry.Message),
			raven.NewStacktrace(1, 10, hook.appPackagePrefixes),
		),
	)
	d := entry.Data

	if logger, ok := getAndDel(d, "logger"); ok {
		packet.Logger = logger
	}
	if serverName, ok := getAndDel(d, "server_name"); ok {
		packet.ServerName = serverName
	}
	if req, ok := getAndDelRequest(d, "http_request"); ok {
		packet.Interfaces = append(packet.Interfaces, raven.NewHttp(req))
	}
	packet.Extra = map[string]interface{}(d)

	_, errCh := hook.client.Capture(packet, nil)
	timeout := hook.Timeout
	if timeout != 0 {
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
	return nil
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}
<|endoftext|>


<commit_before>package config_test

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"

	"github.com/fly/config"
)

// In this example our organization is named "podhub", and our project
// namespace is "canary".
//
// In this example we have a file located at /Users/jchen/.config/podhub/canary/config.yaml,
// with the following contents:
//  example:
//    - "a"
//    - "b"
//    - "c"
func ExampleNamespace() {
	type Config struct {
		Example []string `yaml:"example"`
	}

	var err error
	var cfg Config
	var path string
	var cfgNS = config.Namespace{
		Organization: "podhub",
		System:       "canary",
	}

	path, _ = cfgNS.Path()
	fmt.Println("Path to config " + path)

	err = cfgNS.Load(&cfg)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println("Contents of cfg " + fmt.Sprint(cfg))
	// Output: Path to config: /Users/jchen/.config/podhub/canary/config.yaml
	// Output: Contents of cfg: {[a b c]}
}

func TestExpandUser(t *testing.T) {
	var homeDir = os.Getenv("HOME")
	var correctPath = homeDir + "/.config/fly/config/testconfig.yaml"
	var err error
	var path string
	path, err = config.ExpandUser("~/.config/fly/config/testconfig.yaml")

	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	// docs say not to trust /home/travis to be homedir. We'll need to
	// revisit this later.
	if path != correctPath {
		t.Error("Expected ", correctPath, ", got ", path)
	}
}

func TestLoad(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	type configExample struct {
		Location string `yaml:"location"`
		Burritos bool   `yaml:"burritos"`
	}

	var correctCfgText = `location: Señor Sisig
burritos: true`
	var correctCfg = configExample{
		Location: "Señor Sisig",
		Burritos: true,
	}
	var err error
	var cfg configExample
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755
	var fileMode os.FileMode = 0644

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	err = ioutil.WriteFile(correctPath, []byte(correctCfgText), fileMode)
	if err != nil {
		t.Error("Got an error writing ", correctPath, ", got an error: ", err)
	}

	// Test
	err = config.Load(correctPath, &cfg)
	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	if cfg != correctCfg {
		t.Error("Expecting ", correctCfg, ", got ", cfg)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}

func TestUserBase(t *testing.T) {
	const correctUserBase = "~/.config/"
	var userBase string
	userBase = config.UserBase

	if userBase != correctUserBase {
		t.Error("Expecting ", correctUserBase, ", got ", userBase)
	}
}

func TestSystemBase(t *testing.T) {
	const correctSystemBase = "~/.config/"
	var systemBase string
	systemBase = config.SystemBase

	if systemBase != correctSystemBase {
		t.Error("Expecting ", correctSystemBase, ", got ", systemBase)
	}
}

func TestNamespacePath(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	var cfgNS = config.Namespace{
		Organization: "fly",
		System:       "config",
	}
	var err error
	var path string
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	_, err = os.Create(correctPath)
	if err != nil {
		t.Error("Unable to create file ", correctPath, ", got an error: ", err)
	}

	// Test
	path, err = cfgNS.Path()
	if err != nil {
		t.Error("Got an error: ", err, " expecting nil")
	}
	if path != correctPath {
		t.Error("Expecting ", correctPath, ", got ", path)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}

func TestNamespaceLoad(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	type configExample struct {
		Location string
		Burritos bool
	}

	var correctCfgText = `location: Señor Sisig
burritos: true`
	var correctCfg = configExample{
		Location: "Señor Sisig",
		Burritos: true,
	}
	var cfgNS = config.Namespace{
		Organization: "fly",
		System:       "config",
	}
	var err error
	var cfg configExample
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755
	var fileMode os.FileMode = 0644

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	err = ioutil.WriteFile(correctPath, []byte(correctCfgText), fileMode)
	if err != nil {
		t.Error("Got an error writing ", correctPath, ", got an error: ", err)
	}

	// Test
	err = cfgNS.Load(&cfg)
	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	if cfg != correctCfg {
		t.Error("Expecting ", correctCfg, ", got ", cfg)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}
<commit_msg>fix TestSystemBase test<commit_after>package config_test

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"

	"github.com/fly/config"
)

// In this example our organization is named "podhub", and our project
// namespace is "canary".
//
// In this example we have a file located at /Users/jchen/.config/podhub/canary/config.yaml,
// with the following contents:
//  example:
//    - "a"
//    - "b"
//    - "c"
func ExampleNamespace() {
	type Config struct {
		Example []string `yaml:"example"`
	}

	var err error
	var cfg Config
	var path string
	var cfgNS = config.Namespace{
		Organization: "podhub",
		System:       "canary",
	}

	path, _ = cfgNS.Path()
	fmt.Println("Path to config " + path)

	err = cfgNS.Load(&cfg)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println("Contents of cfg " + fmt.Sprint(cfg))
	// Output: Path to config: /Users/jchen/.config/podhub/canary/config.yaml
	// Output: Contents of cfg: {[a b c]}
}

func TestExpandUser(t *testing.T) {
	var homeDir = os.Getenv("HOME")
	var correctPath = homeDir + "/.config/fly/config/testconfig.yaml"
	var err error
	var path string
	path, err = config.ExpandUser("~/.config/fly/config/testconfig.yaml")

	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	// docs say not to trust /home/travis to be homedir. We'll need to
	// revisit this later.
	if path != correctPath {
		t.Error("Expected ", correctPath, ", got ", path)
	}
}

func TestLoad(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	type configExample struct {
		Location string `yaml:"location"`
		Burritos bool   `yaml:"burritos"`
	}

	var correctCfgText = `location: Señor Sisig
burritos: true`
	var correctCfg = configExample{
		Location: "Señor Sisig",
		Burritos: true,
	}
	var err error
	var cfg configExample
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755
	var fileMode os.FileMode = 0644

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	err = ioutil.WriteFile(correctPath, []byte(correctCfgText), fileMode)
	if err != nil {
		t.Error("Got an error writing ", correctPath, ", got an error: ", err)
	}

	// Test
	err = config.Load(correctPath, &cfg)
	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	if cfg != correctCfg {
		t.Error("Expecting ", correctCfg, ", got ", cfg)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}

func TestUserBase(t *testing.T) {
	const correctUserBase = "~/.config/"
	var userBase string
	userBase = config.UserBase

	if userBase != correctUserBase {
		t.Error("Expecting ", correctUserBase, ", got ", userBase)
	}
}

func TestSystemBase(t *testing.T) {
	const correctSystemBase = "/etc/"
	var systemBase string
	systemBase = config.SystemBase

	if systemBase != correctSystemBase {
		t.Error("Expecting ", correctSystemBase, ", got ", systemBase)
	}
}

func TestNamespacePath(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	var cfgNS = config.Namespace{
		Organization: "fly",
		System:       "config",
	}
	var err error
	var path string
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	_, err = os.Create(correctPath)
	if err != nil {
		t.Error("Unable to create file ", correctPath, ", got an error: ", err)
	}

	// Test
	path, err = cfgNS.Path()
	if err != nil {
		t.Error("Got an error: ", err, " expecting nil")
	}
	if path != correctPath {
		t.Error("Expecting ", correctPath, ", got ", path)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}

func TestNamespaceLoad(t *testing.T) {
	const correctDir = "/etc/fly/config/"
	const correctPath = correctDir + "testconfig.yaml"
	type configExample struct {
		Location string
		Burritos bool
	}

	var correctCfgText = `location: Señor Sisig
burritos: true`
	var correctCfg = configExample{
		Location: "Señor Sisig",
		Burritos: true,
	}
	var cfgNS = config.Namespace{
		Organization: "fly",
		System:       "config",
	}
	var err error
	var cfg configExample
	var homeDir = os.Getenv("HOME")
	var dirMode os.FileMode = 0755
	var fileMode os.FileMode = 0644

	// Setup
	os.RemoveAll(homeDir + "/.config/fly/config/testconfig.yaml")
	os.MkdirAll(correctDir, dirMode)
	err = ioutil.WriteFile(correctPath, []byte(correctCfgText), fileMode)
	if err != nil {
		t.Error("Got an error writing ", correctPath, ", got an error: ", err)
	}

	// Test
	err = cfgNS.Load(&cfg)
	if err != nil {
		t.Error("Got an error: ", err, ", expecting nil")
	}

	if cfg != correctCfg {
		t.Error("Expecting ", correctCfg, ", got ", cfg)
	}

	// Teardown
	err = os.RemoveAll(correctPath)
	if err != nil {
		t.Error("Unable to remove file ", correctPath,
			" in teardown, got an error: ", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/rpc"
	"os"
	"testing"
	"time"
)

func TestConfig(t *testing.T) {
	service := &Config{
		MongoDB: "mongodb://localhost/testtits",
		Ublox: &Ublox{
			Token: os.Getenv("Ublox"),
			Servers: []string{
				"http://online-live1.services.u-blox.com/GetOnlineData.ashx",
				"http://online-live2.services.u-blox.com/GetOnlineData.ashx",
			},
			Timeout:     time.Minute * 2,
			CacheTime:   time.Minute * 30,
			MaxDistance: 10000.0,
			Pacc:        100000,
		},
		LBS: &LBS{
			Type:  "Google",
			Token: os.Getenv("Google"),
		},
		POI: &POI{},
	}

	data, err := json.MarshalIndent(service, "", "    ")
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println(string(data))

	defer service.Close()
	go func() {
		err := service.Run(":1234")
		if err != nil {
			t.Fatal(err)
		}
	}()
	time.Sleep(time.Second)

	client, err := rpc.Dial("tcp", ":1234")
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	// UBLOX

	ubloxRequest := UbloxRequest{
		Point: NewPoint(38.67451, 55.715084),
		Profile: UbloxProfile{
			Datatype:    []string{"pos", "eph", "aux"},
			Format:      "aid",
			GNSS:        []string{"gps"},
			FilterOnPos: true,
		},
	}
	var ubloxOut []byte
	err = client.Call("Ublox.Get", ubloxRequest, &ubloxOut)
	if err != nil {
		t.Error("UBLOX error:", err)
	} else {
		fmt.Println("UBLOX:", len(ubloxOut))
	}

	// LBS

	type CellTower struct {
		MobileCountryCode uint16 // The mobile country code.
		MobileNetworkCode uint16 // The mobile network code.
		LocationAreaCode  uint16 // The location area code for GSM and WCDMA networks. The tracking area code for LTE networks.
		CellId            uint32 // The cell id or cell identity.
		SignalStrength    int16  // The signal strength for this cell network, either the RSSI or RSCP.
		Age               uint32 // The number of milliseconds since this networks was last detected.
		TimingAdvance     uint8  // The timing advance value for this cell network.
	}

	type WifiAccessPoint struct {
		MacAddress         string // The BSSID of the WiFi network.
		SignalStrength     int16  // The received signal strength (RSSI) in dBm.
		Age                uint32 // The number of milliseconds since this network was last detected.
		Channel            uint8  // The WiFi channel, often 1 - 13 for networks in the 2.4GHz range.
		SignalToNoiseRatio uint16 // The current signal to noise ratio measured in dB.
	}

	type Fallbacks struct {
		LAC bool // If no exact cell match can be found, fall back from exact cell position estimates to more coarse grained cell location area estimates, rather than going directly to an even worse GeoIP based estimate.
		IP  bool // If no position can be estimated based on any of the provided data points, fall back to an estimate based on a GeoIP database based on the senders IP address at the time of the query.
	}

	type LBSRequest struct {
		HomeMobileCountryCode uint16            // The mobile country code stored on the SIM card (100-999).
		HomeMobileNetworkCode uint16            // The mobile network code stored on the SIM card (0-32767).
		RadioType             string            // The mobile radio type. Supported values are lte, gsm, cdma, and wcdma.
		Carrier               string            // The clear text name of the cell carrier / operator.
		ConsiderIp            bool              // Should the clients IP address be used to locate it, defaults to true.
		CellTowers            []CellTower       // Array of cell towers
		WifiAccessPoints      []WifiAccessPoint // Array of wifi access points
		IPAddress             string            // Client IP Address
		Fallbacks             *Fallbacks        // The fallback section is a custom addition to the GLS API.
	}

	lbsRequest := LBSRequest{
		RadioType:             "gsm",
		HomeMobileCountryCode: 250,
		HomeMobileNetworkCode: 1,
		ConsiderIp:            false,
		CellTowers: []CellTower{
			{250, 1, 6101, 4765, -62, 0, 0},
			{250, 1, 6101, 4762, -56, 0, 0},
			{250, 1, 6101, 4763, -60, 0, 0},
			{250, 1, 6101, 4766, -75, 0, 0},
			{250, 1, 818, 13000, -87, 0, 0},
			{250, 1, 818, 13049, -83, 0, 0},
			{250, 1, 6101, 4761, -76, 0, 0},
		},
	}

	var lbsResponse LBSResponse
	err = client.Call("LBS.Get", lbsRequest, &lbsResponse)
	if err != nil {
		t.Error("LBS error:", err)
	} else {
		fmt.Println("LBS:", lbsResponse)
	}

	// POI

	place := Place{
		Group:  "test_group",
		ID:     "test_id",
		Name:   "Test Place",
		Center: NewPoint(38.67451, 55.715084),
		Radius: 456.08,
	}
	var placeID string
	err = client.Call("POI.Save", place, &placeID)
	if err != nil {
		t.Error("Save POI error:", err)
	} else {
		fmt.Println("Save POI:", placeID)
	}

	var places = make([]Place, 0)
	err = client.Call("POI.Get", place.Group, &places)
	if err != nil {
		t.Error("Get POI error:", err)
	} else {
		fmt.Println("Get POI:", places)
	}

	placePoint := PlacePoint{
		Group: place.Group,
		Point: place.Center,
	}
	var placeIDs = make([]string, 0)
	err = client.Call("POI.In", placePoint, &placeIDs)
	if err != nil {
		t.Error("In POI error:", err)
	} else {
		fmt.Println("In POI:", placeIDs)
	}

	placeID2 := PlaceID{
		Group: "test_group",
		ID:    "test_id",
	}
	err = client.Call("POI.Delete", placeID2, &placeID)
	if err != nil {
		t.Error("Delete POI error:", err)
	} else {
		fmt.Println("Delete POI:", placeID)
	}
}
<commit_msg>Travis CI<commit_after>package main

import (
	"fmt"
	"net/rpc"
	"os"
	"testing"
	"time"
)

func TestConfig(t *testing.T) {
	service := &Config{
		MongoDB: "mongodb://localhost/testtits",
		Ublox: &Ublox{
			Token: os.Getenv("Ublox"),
			Servers: []string{
				"http://online-live1.services.u-blox.com/GetOnlineData.ashx",
				"http://online-live2.services.u-blox.com/GetOnlineData.ashx",
			},
			Timeout:     time.Minute * 2,
			CacheTime:   time.Minute * 30,
			MaxDistance: 10000.0,
			Pacc:        100000,
		},
		LBS: &LBS{
			Type:  "Google",
			Token: os.Getenv("Google"),
		},
		POI: &POI{},
	}

	// data, err := json.MarshalIndent(service, "", "    ")
	// if err != nil {
	// 	t.Fatal(err)
	// }
	// fmt.Println(string(data))

	// defer service.Close()
	go func() {
		err := service.Run(":1234")
		if err != nil {
			t.Fatal(err)
		}
	}()
	time.Sleep(time.Second)

	client, err := rpc.Dial("tcp", ":1234")
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	// UBLOX

	ubloxRequest := UbloxRequest{
		Point: NewPoint(38.67451, 55.715084),
		Profile: UbloxProfile{
			Datatype:    []string{"pos", "eph", "aux"},
			Format:      "aid",
			GNSS:        []string{"gps"},
			FilterOnPos: true,
		},
	}
	var ubloxOut []byte
	err = client.Call("Ublox.Get", ubloxRequest, &ubloxOut)
	if err != nil {
		t.Error("UBLOX error:", err)
	} else {
		fmt.Println("UBLOX:", len(ubloxOut))
	}

	// LBS

	type CellTower struct {
		MobileCountryCode uint16 // The mobile country code.
		MobileNetworkCode uint16 // The mobile network code.
		LocationAreaCode  uint16 // The location area code for GSM and WCDMA networks. The tracking area code for LTE networks.
		CellId            uint32 // The cell id or cell identity.
		SignalStrength    int16  // The signal strength for this cell network, either the RSSI or RSCP.
		Age               uint32 // The number of milliseconds since this networks was last detected.
		TimingAdvance     uint8  // The timing advance value for this cell network.
	}

	type WifiAccessPoint struct {
		MacAddress         string // The BSSID of the WiFi network.
		SignalStrength     int16  // The received signal strength (RSSI) in dBm.
		Age                uint32 // The number of milliseconds since this network was last detected.
		Channel            uint8  // The WiFi channel, often 1 - 13 for networks in the 2.4GHz range.
		SignalToNoiseRatio uint16 // The current signal to noise ratio measured in dB.
	}

	type Fallbacks struct {
		LAC bool // If no exact cell match can be found, fall back from exact cell position estimates to more coarse grained cell location area estimates, rather than going directly to an even worse GeoIP based estimate.
		IP  bool // If no position can be estimated based on any of the provided data points, fall back to an estimate based on a GeoIP database based on the senders IP address at the time of the query.
	}

	type LBSRequest struct {
		HomeMobileCountryCode uint16            // The mobile country code stored on the SIM card (100-999).
		HomeMobileNetworkCode uint16            // The mobile network code stored on the SIM card (0-32767).
		RadioType             string            // The mobile radio type. Supported values are lte, gsm, cdma, and wcdma.
		Carrier               string            // The clear text name of the cell carrier / operator.
		ConsiderIp            bool              // Should the clients IP address be used to locate it, defaults to true.
		CellTowers            []CellTower       // Array of cell towers
		WifiAccessPoints      []WifiAccessPoint // Array of wifi access points
		IPAddress             string            // Client IP Address
		Fallbacks             *Fallbacks        // The fallback section is a custom addition to the GLS API.
	}

	lbsRequest := LBSRequest{
		RadioType:             "gsm",
		HomeMobileCountryCode: 250,
		HomeMobileNetworkCode: 1,
		ConsiderIp:            false,
		CellTowers: []CellTower{
			{250, 1, 6101, 4765, -62, 0, 0},
			{250, 1, 6101, 4762, -56, 0, 0},
			{250, 1, 6101, 4763, -60, 0, 0},
			{250, 1, 6101, 4766, -75, 0, 0},
			{250, 1, 818, 13000, -87, 0, 0},
			{250, 1, 818, 13049, -83, 0, 0},
			{250, 1, 6101, 4761, -76, 0, 0},
		},
	}

	var lbsResponse LBSResponse
	err = client.Call("LBS.Get", lbsRequest, &lbsResponse)
	if err != nil {
		t.Error("LBS error:", err)
	} else {
		fmt.Println("LBS:", lbsResponse)
	}

	// POI

	place := Place{
		Group:  "test_group",
		ID:     "test_id",
		Name:   "Test Place",
		Center: NewPoint(38.67451, 55.715084),
		Radius: 456.08,
	}
	var placeID string
	err = client.Call("POI.Save", place, &placeID)
	if err != nil {
		t.Error("Save POI error:", err)
	} else {
		fmt.Println("Save POI:", placeID)
	}

	var places = make([]Place, 0)
	err = client.Call("POI.Get", place.Group, &places)
	if err != nil {
		t.Error("Get POI error:", err)
	} else {
		fmt.Println("Get POI:", places)
	}

	placePoint := PlacePoint{
		Group: place.Group,
		Point: place.Center,
	}
	var placeIDs = make([]string, 0)
	err = client.Call("POI.In", placePoint, &placeIDs)
	if err != nil {
		t.Error("In POI error:", err)
	} else {
		fmt.Println("In POI:", placeIDs)
	}

	placeID2 := PlaceID{
		Group: "test_group",
		ID:    "test_id",
	}
	err = client.Call("POI.Delete", placeID2, &placeID)
	if err != nil {
		t.Error("Delete POI error:", err)
	} else {
		fmt.Println("Delete POI:", placeID)
	}
}
<|endoftext|>


<commit_before>package extension

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/kayex/sirius"
	"github.com/kayex/sirius/text"
	"net/http"
)

type IPLookup struct{}

func (ipl *IPLookup) Run(m sirius.Message, cfg sirius.ExtensionConfig) (sirius.MessageAction, error) {
	cmd, match := m.Command("ip")

	if !match {
		return sirius.NoAction(), nil
	}

	ip := cmd.Arg(0)

	if ip == "" {
		return sirius.NoAction(), nil
	}

	var lookup map[string]interface{}

	err, lookup := ipLookup(ip)

	if err != nil {
		return nil, errors.New(fmt.Sprintf("IP Lookup error: %v", err))
	}

	// Output format:
	//
	// `IP`
	// City, Country (COUNTRY CODE)
	// ISP
	output := fmt.Sprintf("%v\n"+
		"%v, %v (%v)\n"+
		"%v",
		text.Code(ip),
		lookup["city"],
		lookup["country"],
		text.Code(string(lookup["countryCode"])),
		lookup["isp"])
	edit := m.EditText().Set(output)

	return edit, nil
}

func ipLookup(ip string) (error, map[string]interface{}) {
	url := "http://ip-api.com/json/" + ip

	c := &http.Client{}
	r, err := c.Get(url)

	if err != nil {
		return err, nil
	}
	defer r.Body.Close()

	var lookup map[string]interface{}
	json.NewDecoder(r.Body).Decode(&lookup)

	return nil, lookup
}
<commit_msg>Add iPInfo struct<commit_after>package extension

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/kayex/sirius"
	"github.com/kayex/sirius/text"
	"net/http"
)

type IPLookup struct{}

func (ipl *IPLookup) Run(m sirius.Message, cfg sirius.ExtensionConfig) (sirius.MessageAction, error) {
	cmd, match := m.Command("ip")

	if !match {
		return sirius.NoAction(), nil
	}

	ip := cmd.Arg(0)

	if ip == "" {
		return sirius.NoAction(), nil
	}

	err, info := ipLookup(ip)

	if err != nil {
		return nil, errors.New(fmt.Sprintf("IP Lookup error: %v", err))
	}

	ip = text.Code(ip)

	// Output format:
	//
	// `IP`
	// City, Country (`COUNTRY CODE`)
	// ISP
	output := fmt.Sprintf("%v\n"+
		"%v, %v (%v)\n"+
		"%v",
		text.Code(ip),
		info.City,
		info.Country,
		text.Code(info.CountryCode),
		info.ISP)
	edit := m.EditText().Set(output)

	return edit, nil
}

func ipLookup(ip string) (error, *iPInfo) {
	url := "http://ip-api.com/json/" + ip

	c := &http.Client{}
	r, err := c.Get(url)

	if err != nil {
		return err, nil
	}
	defer r.Body.Close()

	var info *iPInfo
	err = json.NewDecoder(r.Body).Decode(info)

	return nil, info
}

type iPInfo struct {
	City        string `json:"city"`
	Country     string `json:"country"`
	CountryCode string `json:"countryCode"`
	ISP         string `json:"isp"`
}
<|endoftext|>


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

// Package costs gets billing information from an ElasticSearch.
package costs

import (
	"fmt"
	"strings"
	"time"

	"github.com/olivere/elastic"
)

// aggregationBuilder is an alias for the function type that is used in the
// aggregationBuilder functions.
type aggregationBuilder func([]string) []paramAggrAndName

// paramNameToFuncPtr maps parameter names to functions building the aggregations.
// map of string keys and functions pointer as values. For each possible param
// after parsing (removing the ':<TAG_KEY>' in the case of the tag), there is a function associated to it
// that create the Aggregations needed by this param.
// If a new param, that is only creating aggregations, needs to be added,
// a functions with an aggregationBuilder prototype need to be added to the list below.
var paramNameToFuncPtr = map[string]aggregationBuilder{
	"product":          createAggregationPerProduct,
	"availabilityzone": createAggregationPerAvailabilityZone,
	"region":           createAggregationPerRegion,
	"account":          createAggregationPerAccount,
	"tag":              createAggregationPerTag,
	"cost":             createCostSumAggregation,
	"day":              createAggregationPerDay,
	"week":             createAggregationPerWeek,
	"month":            createAggregationPerMonth,
	"year":             createAggregationPerYear,
}

// paramAggrAndName is a structure containing the name of the parameter and
// corresponding aggregation. A parameter is a string that is passed to the
// GetElasticSearchParams and represents an aggregation. A list of those
// parameters can be found in the paramNameToFuncPtr map.
type paramAggrAndName struct {
	name string
	aggr elastic.Aggregation
}

// aggregationMaxSize is the maximum size of an Elastic Search Aggregation
const aggregationMaxSize = 0x7FFFFFFF

// createQueryAccountFilter creates and return a new *elastic.TermsQuery on the accountList array
func createQueryAccountFilter(accountList []string) *elastic.TermsQuery {
	accountListFormatted := make([]interface{}, len(accountList))
	for i, v := range accountList {
		accountListFormatted[i] = v
	}
	return elastic.NewTermsQuery("usageAccountId", accountListFormatted...)
}

// createQueryTimeRange creates and return a new *elastic.RangeQuery based on the duration
// defined by durationBegin and durationEnd
func createQueryTimeRange(durationBegin time.Time, durationEnd time.Time) *elastic.RangeQuery {
	return elastic.NewRangeQuery("usageStartDate").
		From(durationBegin).To(durationEnd)
}

// createAggregationPerProduct creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'product_name'
func createAggregationPerProduct(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-product",
			aggr: elastic.NewTermsAggregation().
				Field("productCode").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerAvailabilityZone creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'availability_zone'
func createAggregationPerAvailabilityZone(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-availabilityzone",
			aggr: elastic.NewTermsAggregation().
				Field("availabilityZone").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerRegion creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'region'
func createAggregationPerRegion(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-region",
			aggr: elastic.NewTermsAggregation().
				Field("region").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerAccount creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'linked_account_id'
func createAggregationPerAccount(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-account",
			aggr: elastic.NewTermsAggregation().
				Field("usageAccountId").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerDay creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a day
func createAggregationPerDay(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-day",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("day"),
		},
	}
}

// createAggregationPerWeek creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a week
func createAggregationPerWeek(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-week",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("week"),
		},
	}
}

// createAggregationPerMonth creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a month
func createAggregationPerMonth(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-month",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("month"),
		},
	}
}

// createAggregationPerYear creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a year
func createAggregationPerYear(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-year",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("year"),
		},
	}
}

// createAggregationPerTag creates and returns a new []paramAggrAndName of size 2 which consits
// of two aggregations that are required for the tag param.
// The first aggregation is a FilterAggregation on the field 'tag.key', and with a value of
// the tag key passed in the parameter 'paramSplit' in the form "user:<TAG_KEY_VALUE>".
// The second aggregation is a TermsAggregation that creates bucket aggregation on the field
// 'tag.value'.
// No SubAggregation is created in this function, as it needs to be created in the nestAggregation function
func createAggregationPerTag(paramSplit []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-tag_key",
			aggr: elastic.NewFilterAggregation().
				Filter(elastic.NewTermQuery("tag.key", fmt.Sprintf("user:%v", paramSplit[1])))},
		paramAggrAndName{
			name: "tag_value",
			aggr: elastic.NewTermsAggregation().
				Field("tag.value").Size(aggregationMaxSize)},
	}
}

// createCostSumAggregation : Creates and return a new []paramAggrAndName of size 1, which creates a
// SumAggregation on the field 'cost'
func createCostSumAggregation(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "value",
			aggr: elastic.NewSumAggregation().Field("unblendedCost"),
		},
	}
}

// reverseAggregationArray : reverse the paramAggrAndName slice that is passed to it
func reverseAggregationArray(aggregationArray []paramAggrAndName) []paramAggrAndName {
	for i := len(aggregationArray)/2 - 1; i >= 0; i-- {
		opp := len(aggregationArray) - 1 - i
		aggregationArray[i], aggregationArray[opp] = aggregationArray[opp], aggregationArray[i]
	}
	return aggregationArray
}

// nestAggregation takes a slice of paramAggrAndName type, and will nest the different aggregations.
// Aggregations are nested by creating a chain of SubAggregation
// A type switch is required to simulate downcasting from the interface elastic.Aggregation.
// Current types on the type switch are TermsAggregation, FilterAggregation, SumAggregation and
// DateHistogramAggregation.
// If a new function creating a type that is not listed here is added to the paramNameToFuncPtr map
// it should be added to the type switch, or the function will create bugged SubAggregations
func nestAggregation(allAggrSlice []paramAggrAndName) elastic.Aggregation {
	allAggrSlice = reverseAggregationArray(allAggrSlice)
	aggrToNest := allAggrSlice[0]
	for _, baseAggr := range allAggrSlice[1:] {
		switch assertedBaseAggr := baseAggr.aggr.(type) {
		case *elastic.TermsAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		case *elastic.FilterAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		case *elastic.DateHistogramAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		}
	}
	return aggrToNest.aggr
}

// GetElasticSearchParams is used to construct an ElasticSearch *elastic.SearchService used to perform a request on ES
// It takes as paramters :
// 	- accountList []string : A slice of strings representing aws account number, in the format of the field
//	'awsdetailedlineitem.linked_account_id'
//	- durationBeing time.Time : A time.Time struct representing the begining of the time range in the query
//	- durationEnd time.Time : A time.Time struct representing the end of the time range in the query
//	- param []string : A slice of strings representing the different parameters, in the nesting order,
//	that will create aggregations.
//	Those can be :
//		- "product" : It will create a TermsAggregation on the field 'product_name'
//		- "availabilityzone" : It will create a TermsAggregation on the field 'availability_zone'
//		- "region" : It will create a TermsAggregation on the field 'region'
//		- "account" : It will create a TermsAggregation on the field 'linked_account_id'
//		- "tag:<TAG_KEY>" : It will create a FilterAggregation on the field 'tag.key',
//		filtering on the value 'user:<TAG_KEY>'.
//		It will then create a TermsAggregation on the field 'tag.value'
//		- "[day|week|month|year]": It will create a DateHistogramAggregation on the specified duration on
//		the field 'usage_start_date'
//	- client *elastic.Client : an instance of *elastic.Client that represent an Elastic Search client.
//	It needs to be fully configured and ready to execute a client.Search()
//	- index string : The Elastic Search index on wich to execute the query. In this context the default value
//	should be "awsdetailedlineitems"
// This function excepts arguments passed to it to be sanitize. If they are not, the following cases will make
// it crash :
//	- For the 'tag:<TAG_KEY>' param, if the separator is not present, or if there is no key that is passed to it,
//	the program will crash
//	- If a param in the slice is not present in the detailedLineItemsFieldsName, the program will crash.
//	- If the client is nil or malconfigured, it will crash
//	- If the index is not an index present in the ES, it will crash
func GetElasticSearchParams(accountList []string, durationBegin time.Time,
	durationEnd time.Time, params []string, client *elastic.Client, index string) *elastic.SearchService {
	query := elastic.NewBoolQuery()
	if len(accountList) > 0 {
		query = query.Filter(createQueryAccountFilter(accountList))
	}
	// We are excluding AWSDataTransfer products because it's value is always zero.
	// Data transfer costs are included in other products' costs.
	query = query.Filter(createQueryTimeRange(durationBegin, durationEnd),
		elastic.NewBoolQuery().MustNot(elastic.NewTermQuery("productCode", "AWSDataTransfer")))
	search := client.Search().Index(index).Size(0).Query(query)
	params = append(params, "cost")
	var allAggregationSlice []paramAggrAndName
	for _, paramName := range params {
		paramNameSplit := strings.Split(paramName, ":")
		paramAggr := paramNameToFuncPtr[paramNameSplit[0]](paramNameSplit)
		allAggregationSlice = append(allAggregationSlice, paramAggr...)
	}
	aggregationParamName := allAggregationSlice[0].name
	nestedAggregation := nestAggregation(allAggregationSlice)
	search.Aggregation(aggregationParamName, nestedAggregation)
	return search
}
<commit_msg>Moved wrongly placed comment<commit_after>//   Copyright 2017 MSolution.IO
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

// Package costs gets billing information from an ElasticSearch.
package costs

import (
	"fmt"
	"strings"
	"time"

	"github.com/olivere/elastic"
)

// aggregationBuilder is an alias for the function type that is used in the
// aggregationBuilder functions.
type aggregationBuilder func([]string) []paramAggrAndName

// paramNameToFuncPtr maps parameter names to functions building the aggregations.
// map of string keys and functions pointer as values. For each possible param
// after parsing (removing the ':<TAG_KEY>' in the case of the tag), there is a function associated to it
// that create the Aggregations needed by this param.
// If a new param, that is only creating aggregations, needs to be added,
// a functions with an aggregationBuilder prototype need to be added to the list below.
var paramNameToFuncPtr = map[string]aggregationBuilder{
	"product":          createAggregationPerProduct,
	"availabilityzone": createAggregationPerAvailabilityZone,
	"region":           createAggregationPerRegion,
	"account":          createAggregationPerAccount,
	"tag":              createAggregationPerTag,
	"cost":             createCostSumAggregation,
	"day":              createAggregationPerDay,
	"week":             createAggregationPerWeek,
	"month":            createAggregationPerMonth,
	"year":             createAggregationPerYear,
}

// paramAggrAndName is a structure containing the name of the parameter and
// corresponding aggregation. A parameter is a string that is passed to the
// GetElasticSearchParams and represents an aggregation. A list of those
// parameters can be found in the paramNameToFuncPtr map.
type paramAggrAndName struct {
	name string
	aggr elastic.Aggregation
}

// aggregationMaxSize is the maximum size of an Elastic Search Aggregation
const aggregationMaxSize = 0x7FFFFFFF

// createQueryAccountFilter creates and return a new *elastic.TermsQuery on the accountList array
func createQueryAccountFilter(accountList []string) *elastic.TermsQuery {
	accountListFormatted := make([]interface{}, len(accountList))
	for i, v := range accountList {
		accountListFormatted[i] = v
	}
	return elastic.NewTermsQuery("usageAccountId", accountListFormatted...)
}

// createQueryTimeRange creates and return a new *elastic.RangeQuery based on the duration
// defined by durationBegin and durationEnd
func createQueryTimeRange(durationBegin time.Time, durationEnd time.Time) *elastic.RangeQuery {
	return elastic.NewRangeQuery("usageStartDate").
		From(durationBegin).To(durationEnd)
}

// createAggregationPerProduct creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'product_name'
func createAggregationPerProduct(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-product",
			aggr: elastic.NewTermsAggregation().
				Field("productCode").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerAvailabilityZone creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'availability_zone'
func createAggregationPerAvailabilityZone(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-availabilityzone",
			aggr: elastic.NewTermsAggregation().
				Field("availabilityZone").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerRegion creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'region'
func createAggregationPerRegion(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-region",
			aggr: elastic.NewTermsAggregation().
				Field("region").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerAccount creates and returns a new []paramAggrAndName of size 1 which creates a
// bucket aggregation on the field 'linked_account_id'
func createAggregationPerAccount(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-account",
			aggr: elastic.NewTermsAggregation().
				Field("usageAccountId").Size(aggregationMaxSize),
		},
	}
}

// createAggregationPerDay creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a day
func createAggregationPerDay(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-day",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("day"),
		},
	}
}

// createAggregationPerWeek creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a week
func createAggregationPerWeek(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-week",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("week"),
		},
	}
}

// createAggregationPerMonth creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a month
func createAggregationPerMonth(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-month",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("month"),
		},
	}
}

// createAggregationPerYear creates and returns a new []paramAggrAndName of size 1 which creates a
// date histogram aggregation on the field 'usage_start_date' with a time range of a year
func createAggregationPerYear(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-year",
			aggr: elastic.NewDateHistogramAggregation().
				Field("usageStartDate").MinDocCount(0).Interval("year"),
		},
	}
}

// createAggregationPerTag creates and returns a new []paramAggrAndName of size 2 which consits
// of two aggregations that are required for the tag param.
// The first aggregation is a FilterAggregation on the field 'tag.key', and with a value of
// the tag key passed in the parameter 'paramSplit' in the form "user:<TAG_KEY_VALUE>".
// The second aggregation is a TermsAggregation that creates bucket aggregation on the field
// 'tag.value'.
// No SubAggregation is created in this function, as it needs to be created in the nestAggregation function
func createAggregationPerTag(paramSplit []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "by-tag_key",
			aggr: elastic.NewFilterAggregation().
				Filter(elastic.NewTermQuery("tag.key", fmt.Sprintf("user:%v", paramSplit[1])))},
		paramAggrAndName{
			name: "tag_value",
			aggr: elastic.NewTermsAggregation().
				Field("tag.value").Size(aggregationMaxSize)},
	}
}

// createCostSumAggregation : Creates and return a new []paramAggrAndName of size 1, which creates a
// SumAggregation on the field 'cost'
func createCostSumAggregation(_ []string) []paramAggrAndName {
	return []paramAggrAndName{
		paramAggrAndName{
			name: "value",
			aggr: elastic.NewSumAggregation().Field("unblendedCost"),
		},
	}
}

// reverseAggregationArray : reverse the paramAggrAndName slice that is passed to it
func reverseAggregationArray(aggregationArray []paramAggrAndName) []paramAggrAndName {
	for i := len(aggregationArray)/2 - 1; i >= 0; i-- {
		opp := len(aggregationArray) - 1 - i
		aggregationArray[i], aggregationArray[opp] = aggregationArray[opp], aggregationArray[i]
	}
	return aggregationArray
}

// nestAggregation takes a slice of paramAggrAndName type, and will nest the different aggregations.
// Aggregations are nested by creating a chain of SubAggregation
// A type switch is required to simulate downcasting from the interface elastic.Aggregation.
// Current types on the type switch are TermsAggregation, FilterAggregation, SumAggregation and
// DateHistogramAggregation.
// If a new function creating a type that is not listed here is added to the paramNameToFuncPtr map
// it should be added to the type switch, or the function will create bugged SubAggregations
func nestAggregation(allAggrSlice []paramAggrAndName) elastic.Aggregation {
	allAggrSlice = reverseAggregationArray(allAggrSlice)
	aggrToNest := allAggrSlice[0]
	for _, baseAggr := range allAggrSlice[1:] {
		switch assertedBaseAggr := baseAggr.aggr.(type) {
		case *elastic.TermsAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		case *elastic.FilterAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		case *elastic.DateHistogramAggregation:
			aggrBuff := assertedBaseAggr.SubAggregation(aggrToNest.name, aggrToNest.aggr)
			aggrToNest = paramAggrAndName{name: baseAggr.name, aggr: aggrBuff}
		}
	}
	return aggrToNest.aggr
}

// GetElasticSearchParams is used to construct an ElasticSearch *elastic.SearchService used to perform a request on ES
// It takes as paramters :
// 	- accountList []string : A slice of strings representing aws account number, in the format of the field
//	'awsdetailedlineitem.linked_account_id'
//	- durationBeing time.Time : A time.Time struct representing the begining of the time range in the query
//	- durationEnd time.Time : A time.Time struct representing the end of the time range in the query
//	- param []string : A slice of strings representing the different parameters, in the nesting order,
//	that will create aggregations.
//	Those can be :
//		- "product" : It will create a TermsAggregation on the field 'product_name'
//		- "availabilityzone" : It will create a TermsAggregation on the field 'availability_zone'
//		- "region" : It will create a TermsAggregation on the field 'region'
//		- "account" : It will create a TermsAggregation on the field 'linked_account_id'
//		- "tag:<TAG_KEY>" : It will create a FilterAggregation on the field 'tag.key',
//		filtering on the value 'user:<TAG_KEY>'.
//		It will then create a TermsAggregation on the field 'tag.value'
//		- "[day|week|month|year]": It will create a DateHistogramAggregation on the specified duration on
//		the field 'usage_start_date'
//	- client *elastic.Client : an instance of *elastic.Client that represent an Elastic Search client.
//	It needs to be fully configured and ready to execute a client.Search()
//	- index string : The Elastic Search index on wich to execute the query. In this context the default value
//	should be "awsdetailedlineitems"
// This function excepts arguments passed to it to be sanitize. If they are not, the following cases will make
// it crash :
//	- For the 'tag:<TAG_KEY>' param, if the separator is not present, or if there is no key that is passed to it,
//	the program will crash
//	- If a param in the slice is not present in the detailedLineItemsFieldsName, the program will crash.
//	- If the client is nil or malconfigured, it will crash
//	- If the index is not an index present in the ES, it will crash
// We are excluding AWSDataTransfer products because it's value is always zero.
// Data transfer costs are included in other products' costs.
func GetElasticSearchParams(accountList []string, durationBegin time.Time,
	durationEnd time.Time, params []string, client *elastic.Client, index string) *elastic.SearchService {
	query := elastic.NewBoolQuery()
	if len(accountList) > 0 {
		query = query.Filter(createQueryAccountFilter(accountList))
	}
	query = query.Filter(createQueryTimeRange(durationBegin, durationEnd),
		elastic.NewBoolQuery().MustNot(elastic.NewTermQuery("productCode", "AWSDataTransfer")))
	search := client.Search().Index(index).Size(0).Query(query)
	params = append(params, "cost")
	var allAggregationSlice []paramAggrAndName
	for _, paramName := range params {
		paramNameSplit := strings.Split(paramName, ":")
		paramAggr := paramNameToFuncPtr[paramNameSplit[0]](paramNameSplit)
		allAggregationSlice = append(allAggregationSlice, paramAggr...)
	}
	aggregationParamName := allAggregationSlice[0].name
	nestedAggregation := nestAggregation(allAggregationSlice)
	search.Aggregation(aggregationParamName, nestedAggregation)
	return search
}
<|endoftext|>


<commit_before>package spec

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

	"github.com/stretchr/testify/assert"
)

func getTempFile(content string) *os.File {
	tmpfile, _ := ioutil.TempFile("", "")
	tmpfile.WriteString(content)
	tmpfile.Seek(0, 0)
	return tmpfile
}

func TestRead(t *testing.T) {
	bads := []string{
		"",
		"version:3",
		`
version: 3
services:
`,
		`
version: 3
services:
  name1:
    instances: 42
`,
	}

	for _, bad := range bads {
		f := getTempFile(bad)
		defer os.Remove(f.Name())
		s := Spec{}
		assert.Error(t, s.Read(f))
	}

	goods := map[int]string{
		1: `
version: 3
services:
  name1:
    instances: 1
    image: image1
`,
		2: `
version: 3
services:
  name1:
    instances: 1
    image: image1
  name2:
    image: image2
    instances: 2
`,
	}

	for size, good := range goods {
		f := getTempFile(good)
		defer os.Remove(f.Name())
		s := Spec{}
		assert.NoError(t, s.Read(f))
		assert.Equal(t, size, len(s.JobSpecs()))
		for i, jobSpec := range s.JobSpecs() {
			assert.Equal(t, fmt.Sprintf("name%d", i+1), jobSpec.Meta.Name)
			assert.EqualValues(t, i+1, jobSpec.GetService().Instances)
			assert.Equal(t, fmt.Sprintf("image%d", i+1), jobSpec.Template.GetContainer().Image.Reference)
		}
	}
}

func TestSpecDiff(t *testing.T) {
	spec := &Spec{
		Version:   3,
		Namespace: "namespace",
		Services: map[string]*ServiceConfig{
			"name1": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img1"}},
			"name2": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img2"}},
		},
	}

	diff, err := spec.Diff(0, "remote", "local",
		&Spec{
			Version:   3,
			Namespace: "namespace",
			Services: map[string]*ServiceConfig{
				"name1": {Instances: 2, ContainerConfig: ContainerConfig{Image: "img1"}},
				"name2": {Instances: 3, ContainerConfig: ContainerConfig{Image: "img2"}},
			},
		},
	)
	assert.NoError(t, err)
	assert.Equal(t, "--- remote\n+++ local\n@@ -6 +6 @@\n-    instances: 2\n+    instances: 1\n@@ -9 +9 @@\n-    instances: 3\n+    instances: 1\n", diff)

	diff, err = spec.Diff(0, "old", "new",
		&Spec{
			Version:   3,
			Namespace: "namespace",
			Services: map[string]*ServiceConfig{
				"name1": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img3"}},
				"name2": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img2"}},
			},
		},
	)

	assert.NoError(t, err)
	assert.Equal(t, "--- old\n+++ new\n@@ -5 +5 @@\n-    image: img3\n+    image: img1\n", diff)
}
<commit_msg>fix spec test<commit_after>package spec

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

	"github.com/stretchr/testify/assert"
)

func getTempFile(content string) *os.File {
	tmpfile, _ := ioutil.TempFile("", "")
	tmpfile.WriteString(content)
	tmpfile.Seek(0, 0)
	return tmpfile
}

func TestRead(t *testing.T) {
	bads := []string{
		"",
		"version:3",
		`
version: 3
services:
`,
		`
version: 3
services:
  name1:
    instances: 42
`,
	}

	for _, bad := range bads {
		f := getTempFile(bad)
		defer os.Remove(f.Name())
		s := Spec{}
		assert.Error(t, s.Read(f))
	}

	goods := map[int]string{
		1: `
version: 3
services:
  name1:
    instances: 1
    image: image1
`,
		2: `
version: 3
services:
  name1:
    instances: 1
    image: image1
  name2:
    image: image2
    instances: 2
`,
	}

	for size, good := range goods {
		f := getTempFile(good)
		defer os.Remove(f.Name())
		s := Spec{}
		assert.NoError(t, s.Read(f))
		assert.Equal(t, size, len(s.JobSpecs()))
		for _, jobSpec := range s.JobSpecs() {
			assert.Equal(t, fmt.Sprintf("name%d", jobSpec.GetService().Instances), jobSpec.Meta.Name)
			assert.Equal(t, fmt.Sprintf("image%d", jobSpec.GetService().Instances), jobSpec.Template.GetContainer().Image.Reference)
		}
	}
}

func TestSpecDiff(t *testing.T) {
	spec := &Spec{
		Version:   3,
		Namespace: "namespace",
		Services: map[string]*ServiceConfig{
			"name1": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img1"}},
			"name2": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img2"}},
		},
	}

	diff, err := spec.Diff(0, "remote", "local",
		&Spec{
			Version:   3,
			Namespace: "namespace",
			Services: map[string]*ServiceConfig{
				"name1": {Instances: 2, ContainerConfig: ContainerConfig{Image: "img1"}},
				"name2": {Instances: 3, ContainerConfig: ContainerConfig{Image: "img2"}},
			},
		},
	)
	assert.NoError(t, err)
	assert.Equal(t, "--- remote\n+++ local\n@@ -6 +6 @@\n-    instances: 2\n+    instances: 1\n@@ -9 +9 @@\n-    instances: 3\n+    instances: 1\n", diff)

	diff, err = spec.Diff(0, "old", "new",
		&Spec{
			Version:   3,
			Namespace: "namespace",
			Services: map[string]*ServiceConfig{
				"name1": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img3"}},
				"name2": {Instances: 1, ContainerConfig: ContainerConfig{Image: "img2"}},
			},
		},
	)

	assert.NoError(t, err)
	assert.Equal(t, "--- old\n+++ new\n@@ -5 +5 @@\n-    image: img3\n+    image: img1\n", diff)
}
<|endoftext|>