<commit_before>package astar

import (
	"fmt"
	"testing"

	"github.com/wkhere/astar/graphs/geo"
)

// TODO:
// need more dense graph to test & benchmark non-trivial paths

func ExampleGeo() {
	g := Geo{}
	fmt.Println(Astar(g, "Wałcz", "Wałcz"))
	fmt.Println(Astar(g, "Wałcz", "Warszawa"))
	fmt.Println(Astar(g, "Warszawa", "Wałcz"))
	fmt.Println(Astar(g, "Wałcz", "Poznań"))
	// Output:
	// []
	// [Warszawa]
	// [Wałcz]
	// [Poznań]
}

func BenchmarkGeo(b *testing.B) {
	g := Geo{}
	for n := 0; n < b.N; n++ {
		Astar(g, "Wałcz", "Wałcz")
		Astar(g, "Wałcz", "Warszawa")
		Astar(g, "Wałcz", "Poznań")
	}
}

type Geo struct{}

func (g Geo) Nbs(node Node) []Node {
	return nbs[node]
}

func (g Geo) Dist(n1, n2 Node) (v Cost) {
	v, ok := distLookup(n1, n2)
	if !ok {
		panic(fmt.Sprintf("no dist for %v,%v", n1, n2))
	}
	return
}

func (g Geo) H(n1, n2 Node) Cost {
	return Cost(geo.H(coords[n1], coords[n2]))
}

var coords = map[Node]geo.Pt{
	"Wałcz":    geo.Pt{53.283853, 16.470173},
	"Poznań":   geo.Pt{52.408031, 16.920613},
	"Warszawa": geo.Pt{52.230069, 21.018513},
}

type nodePair struct{ n1, n2 Node }

var distances = map[nodePair]Cost{
	// these are arbitrary distances taken from real maps
	nodePair{"Wałcz", "Poznań"}:    119,
	nodePair{"Wałcz", "Warszawa"}:  421,
	nodePair{"Poznań", "Warszawa"}: 310,
}

var nbs = map[Node][]Node{}

func init() {
	for k := range distances {
		nbs[k.n1] = append(nbs[k.n1], k.n2)
		nbs[k.n2] = append(nbs[k.n2], k.n1)
	}
}

func distLookup(n1, n2 Node) (v Cost, ok bool) {
	v, ok = distances[nodePair{n1, n2}]
	if ok {
		return v, ok
	}
	v, ok = distances[nodePair{n2, n1}]
	return
}
<commit_msg>geo example: bit less trivial graph<commit_after>package astar

import (
	"fmt"
	"testing"

	"github.com/wkhere/astar/graphs/geo"
)

// TODO:
// need more dense graph to test & benchmark non-trivial paths

func ExampleGeo() {
	g := Geo{}
	fmt.Println(Astar(g, "Wałcz", "Wałcz"))
	fmt.Println(Astar(g, "Wałcz", "Warszawa"))
	fmt.Println(Astar(g, "Warszawa", "Wałcz"))
	fmt.Println(Astar(g, "Wałcz", "Poznań"))
	// Output:
	// []
	// [Warszawa]
	// [Wałcz]
	// [Trzcianka Poznań]
}

func BenchmarkGeo(b *testing.B) {
	g := Geo{}
	for n := 0; n < b.N; n++ {
		Astar(g, "Wałcz", "Wałcz")
		Astar(g, "Wałcz", "Warszawa")
		Astar(g, "Wałcz", "Poznań")
	}
}

type Geo struct{}

func (g Geo) Nbs(node Node) []Node {
	return nbs[node]
}

func (g Geo) Dist(n1, n2 Node) (v Cost) {
	v, ok := distLookup(n1, n2)
	if !ok {
		panic(fmt.Sprintf("no dist for %v,%v", n1, n2))
	}
	return
}

func (g Geo) H(n1, n2 Node) Cost {
	return Cost(geo.H(coords[n1], coords[n2]))
}

var coords = map[Node]geo.Pt{
	"Wałcz":     geo.Pt{53.283853, 16.470173},
	"Trzcianka": geo.Pt{53.0427712, 16.3763841},
	"Piła":      geo.Pt{53.1347933, 16.6195561},
	"Poznań":    geo.Pt{52.408031, 16.920613},
	"Warszawa":  geo.Pt{52.230069, 21.018513},
}

type nodePair struct{ n1, n2 Node }

var distances = map[nodePair]Cost{
	// these are arbitrary distances taken from real maps
	nodePair{"Wałcz", "Trzcianka"}:  31,
	nodePair{"Trzcianka", "Poznań"}: 88,
	nodePair{"Wałcz", "Piła"}:       28,
	nodePair{"Piła", "Poznań"}:      96,
	nodePair{"Wałcz", "Warszawa"}:   421,
	nodePair{"Poznań", "Warszawa"}:  310,
}

var nbs = map[Node][]Node{}

func init() {
	for k := range distances {
		nbs[k.n1] = append(nbs[k.n1], k.n2)
		nbs[k.n2] = append(nbs[k.n2], k.n1)
	}
}

func distLookup(n1, n2 Node) (v Cost, ok bool) {
	v, ok = distances[nodePair{n1, n2}]
	if ok {
		return v, ok
	}
	v, ok = distances[nodePair{n2, n1}]
	return
}
<|endoftext|>


<commit_before>package pixel

import (
	"fmt"
	"math"
)

// Vec is a 2D vector type with X and Y coordinates.
//
// Create vectors with the V constructor:
//
//   u := pixel.V(1, 2)
//   v := pixel.V(8, -3)
//
// Use various methods to manipulate them:
//
//   w := u.Add(v)
//   fmt.Println(w)        // Vec(9, -1)
//   fmt.Println(u.Sub(v)) // Vec(-7, 5)
//   u = pixel.V(2, 3)
//   v = pixel.V(8, 1)
//   if u.X < 0 {
//	     fmt.Println("this won't happen")
//   }
//   x := u.Unit().Dot(v.Unit())
type Vec struct {
	X, Y float64
}

// ZV is a zero vector.
var ZV = Vec{0, 0}

// V returns a new 2D vector with the given coordinates.
func V(x, y float64) Vec {
	return Vec{x, y}
}

// String returns the string representation of the vector u.
//
//   u := pixel.V(4.5, -1.3)
//   u.String()     // returns "Vec(4.5, -1.3)"
//   fmt.Println(u) // Vec(4.5, -1.3)
func (u Vec) String() string {
	return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
}

// XY returns the components of the vector in two return values.
func (u Vec) XY() (x, y float64) {
	return u.X, u.Y
}

// Add returns the sum of vectors u and v.
func (u Vec) Add(v Vec) Vec {
	return Vec{
		u.X + v.X,
		u.Y + v.Y,
	}
}

// Sub returns the difference betweeen vectors u and v.
func (u Vec) Sub(v Vec) Vec {
	return Vec{
		u.X - v.X,
		u.Y - v.Y,
	}
}

// To returns the vector from u to v. Equivalent to v.Sub(u).
func (u Vec) To(v Vec) Vec {
	return Vec{
		v.X - u.X,
		v.Y - u.Y,
	}
}

// Scaled returns the vector u multiplied by c.
func (u Vec) Scaled(c float64) Vec {
	return Vec{u.X * c, u.Y * c}
}

// ScaledXY returns the vector u multiplied by the vector v component-wise.
func (u Vec) ScaledXY(v Vec) Vec {
	return Vec{u.X * v.X, u.Y * v.Y}
}

// Len returns the length of the vector u.
func (u Vec) Len() float64 {
	return math.Hypot(u.X, u.Y)
}

// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
func (u Vec) Angle() float64 {
	return math.Atan2(u.Y, u.X)
}

// Unit returns a vector of length 1 facing the direction of u (has the same angle).
func (u Vec) Unit() Vec {
	if u.X == 0 && u.Y == 0 {
		return Vec{1, 0}
	}
	return u.Scaled(1 / u.Len())
}

// Rotated returns the vector u rotated by the given angle in radians.
func (u Vec) Rotated(angle float64) Vec {
	sin, cos := math.Sincos(angle)
	return Vec{
		u.X*cos - u.Y*sin,
		u.X*sin + u.Y*cos,
	}
}

// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster.
func (u Vec) Normal() Vec {
	return Vec{u.Y, -u.X}
}

// Dot returns the dot product of vectors u and v.
func (u Vec) Dot(v Vec) float64 {
	return u.X*v.X + u.Y*v.Y
}

// Cross return the cross product of vectors u and v.
func (u Vec) Cross(v Vec) float64 {
	return u.X*v.Y - v.X*u.Y
}

// Map applies the function f to both x and y components of the vector u and returns the modified
// vector.
//
//   u := pixel.V(10.5, -1.5)
//   v := u.Map(math.Floor)   // v is Vec(10, -2), both components of u floored
func (u Vec) Map(f func(float64) float64) Vec {
	return Vec{
		f(u.X),
		f(u.Y),
	}
}

// Lerp returns a linear interpolation between vectors a and b.
//
// This function basically returns a point along the line between a and b and t chooses which one.
// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
// return the appropriate point between a and b and so on.
func Lerp(a, b Vec, t float64) Vec {
	return a.Scaled(1 - t).Add(b.Scaled(t))
}

// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
// points, Min and Max.
//
// The invariant should hold, that Max's components are greater or equal than Min's components
// respectively.
type Rect struct {
	Min, Max Vec
}

// R returns a new Rect with given the Min and Max coordinates.
//
// Note that the returned rectangle is not automatically normalized.
func R(minX, minY, maxX, maxY float64) Rect {
	return Rect{
		Min: V(minX, minY),
		Max: V(maxX, maxY),
	}
}

// String returns the string representation of the Rect.
//
//   r := pixel.R(100, 50, 200, 300)
//   r.String()     // returns "Rect(100, 50, 200, 300)"
//   fmt.Println(r) // Rect(100, 50, 200, 300)
func (r Rect) String() string {
	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
}

// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
func (r Rect) Norm() Rect {
	return Rect{
		Min: Vec{
			math.Min(r.Min.X, r.Max.X),
			math.Min(r.Min.Y, r.Max.Y),
		},
		Max: Vec{
			math.Max(r.Min.X, r.Max.X),
			math.Max(r.Min.Y, r.Max.Y),
		},
	}
}

// W returns the width of the Rect.
func (r Rect) W() float64 {
	return r.Max.X - r.Min.X
}

// H returns the height of the Rect.
func (r Rect) H() float64 {
	return r.Max.Y - r.Min.Y
}

// Size returns the vector of width and height of the Rect.
func (r Rect) Size() Vec {
	return V(r.W(), r.H())
}

// Area returns the area of r. If r is not normalized, area may be negative.
func (r Rect) Area() float64 {
	return r.W() * r.H()
}

// Center returns the position of the center of the Rect.
func (r Rect) Center() Vec {
	return Lerp(r.Min, r.Max, 0.5)
}

// Moved returns the Rect moved (both Min and Max) by the given vector delta.
func (r Rect) Moved(delta Vec) Rect {
	return Rect{
		Min: r.Min.Add(delta),
		Max: r.Max.Add(delta),
	}
}

// Resized returns the Rect resized to the given size while keeping the position of the given
// anchor.
//
//   r.Resized(r.Min, size)      // resizes while keeping the position of the lower-left corner
//   r.Resized(r.Max, size)      // same with the top-right corner
//   r.Resized(r.Center(), size) // resizes around the center
//
// This function does not make sense for resizing a rectangle of zero area and will panic. Use
// ResizedMin in the case of zero area.
func (r Rect) Resized(anchor, size Vec) Rect {
	if r.W()*r.H() == 0 {
		panic(fmt.Errorf("(%T).Resize: zero area", r))
	}
	fraction := Vec{size.X / r.W(), size.Y / r.H()}
	return Rect{
		Min: anchor.Add(r.Min.Sub(anchor)).ScaledXY(fraction),
		Max: anchor.Add(r.Max.Sub(anchor)).ScaledXY(fraction),
	}
}

// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
// Min.
//
// Sizes of zero area are safe here.
func (r Rect) ResizedMin(size Vec) Rect {
	return Rect{
		Min: r.Min,
		Max: r.Min.Add(size),
	}
}

// Contains checks whether a vector u is contained within this Rect (including it's borders).
func (r Rect) Contains(u Vec) bool {
	return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
}

// Union returns a minimal Rect which covers both r and s. Rects r and s should be normalized.
func (r Rect) Union(s Rect) Rect {
	return R(
		math.Min(r.Min.X, s.Min.X),
		math.Min(r.Min.Y, s.Min.Y),
		math.Max(r.Max.X, s.Max.X),
		math.Max(r.Max.Y, s.Max.Y),
	)
}

// Matrix is a 3x2 affine matrix that can be used for all kinds of spatial transforms, such
// as movement, scaling and rotations.
//
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
// example:
//
//   pixel.IM.Moved(pixel.V(100, 200)).Rotated(pixel.ZV, math.Pi/2)
//
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
// vertically and then rotates everything by 90 degrees around the origin.
//
// Layout is:
// [0] [2] [4]
// [1] [3] [5]
//  0   0   1  (implicit row)
type Matrix [6]float64

// IM stands for identity matrix. Does nothing, no transformation.
var IM = Matrix{1, 0, 0, 1, 0, 0}

// String returns a string representation of the Matrix.
//
//   m := pixel.IM
//   fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
func (m Matrix) String() string {
	return fmt.Sprintf(
		"Matrix(%v %v %v | %v %v %v)",
		m[0], m[2], m[4],
		m[1], m[3], m[5],
	)
}

// Moved moves everything by the delta vector.
func (m Matrix) Moved(delta Vec) Matrix {
	m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
	return m
}

// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
	m[4], m[5] = m[4]-around.X, m[5]-around.Y
	m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
	m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
	m[4], m[5] = m[4]+around.X, m[5]+around.Y
	return m
}

// Scaled scales everything around a given point by the scale factor.
func (m Matrix) Scaled(around Vec, scale float64) Matrix {
	return m.ScaledXY(around, V(scale, scale))
}

// Rotated rotates everything around a given point by the given angle in radians.
func (m Matrix) Rotated(around Vec, angle float64) Matrix {
	sint, cost := math.Sincos(angle)
	m[4], m[5] = m[4]-around.X, m[5]-around.Y
	m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
	m[4], m[5] = m[4]+around.X, m[5]+around.Y
	return m
}

// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
// after the transformations of this Matrix.
func (m Matrix) Chained(next Matrix) Matrix {
	return Matrix{
		m[0]*next[0] + m[2]*next[1],
		m[1]*next[0] + m[3]*next[1],
		m[0]*next[2] + m[2]*next[3],
		m[1]*next[2] + m[3]*next[3],
		m[0]*next[4] + m[2]*next[5] + m[4],
		m[1]*next[4] + m[3]*next[5] + m[5],
	}
}

// Project applies all transformations added to the Matrix to a vector u and returns the result.
//
// Time complexity is O(1).
func (m Matrix) Project(u Vec) Vec {
	return Vec{m[0]*u.X + m[2]*u.Y + m[4], m[1]*u.X + m[3]*u.Y + m[5]}
}

// Unproject does the inverse operation to Project.
//
// It turns out that multiplying a vector by the inverse matrix of m can be nearly-accomplished by
// subtracting the translate part of the matrix and multplying by the inverse of the top-left 2x2
// matrix, and the inverse of a 2x2 matrix is simple enough to just be inlined in the computation.
//
// Time complexity is O(1).
func (m Matrix) Unproject(u Vec) Vec {
	d := (m[0] * m[3]) - (m[1] * m[2])
	u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d
	return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]}
}
<commit_msg>add Rect.Intersect<commit_after>package pixel

import (
	"fmt"
	"math"
)

// Vec is a 2D vector type with X and Y coordinates.
//
// Create vectors with the V constructor:
//
//   u := pixel.V(1, 2)
//   v := pixel.V(8, -3)
//
// Use various methods to manipulate them:
//
//   w := u.Add(v)
//   fmt.Println(w)        // Vec(9, -1)
//   fmt.Println(u.Sub(v)) // Vec(-7, 5)
//   u = pixel.V(2, 3)
//   v = pixel.V(8, 1)
//   if u.X < 0 {
//	     fmt.Println("this won't happen")
//   }
//   x := u.Unit().Dot(v.Unit())
type Vec struct {
	X, Y float64
}

// ZV is a zero vector.
var ZV = Vec{0, 0}

// V returns a new 2D vector with the given coordinates.
func V(x, y float64) Vec {
	return Vec{x, y}
}

// String returns the string representation of the vector u.
//
//   u := pixel.V(4.5, -1.3)
//   u.String()     // returns "Vec(4.5, -1.3)"
//   fmt.Println(u) // Vec(4.5, -1.3)
func (u Vec) String() string {
	return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
}

// XY returns the components of the vector in two return values.
func (u Vec) XY() (x, y float64) {
	return u.X, u.Y
}

// Add returns the sum of vectors u and v.
func (u Vec) Add(v Vec) Vec {
	return Vec{
		u.X + v.X,
		u.Y + v.Y,
	}
}

// Sub returns the difference betweeen vectors u and v.
func (u Vec) Sub(v Vec) Vec {
	return Vec{
		u.X - v.X,
		u.Y - v.Y,
	}
}

// To returns the vector from u to v. Equivalent to v.Sub(u).
func (u Vec) To(v Vec) Vec {
	return Vec{
		v.X - u.X,
		v.Y - u.Y,
	}
}

// Scaled returns the vector u multiplied by c.
func (u Vec) Scaled(c float64) Vec {
	return Vec{u.X * c, u.Y * c}
}

// ScaledXY returns the vector u multiplied by the vector v component-wise.
func (u Vec) ScaledXY(v Vec) Vec {
	return Vec{u.X * v.X, u.Y * v.Y}
}

// Len returns the length of the vector u.
func (u Vec) Len() float64 {
	return math.Hypot(u.X, u.Y)
}

// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
func (u Vec) Angle() float64 {
	return math.Atan2(u.Y, u.X)
}

// Unit returns a vector of length 1 facing the direction of u (has the same angle).
func (u Vec) Unit() Vec {
	if u.X == 0 && u.Y == 0 {
		return Vec{1, 0}
	}
	return u.Scaled(1 / u.Len())
}

// Rotated returns the vector u rotated by the given angle in radians.
func (u Vec) Rotated(angle float64) Vec {
	sin, cos := math.Sincos(angle)
	return Vec{
		u.X*cos - u.Y*sin,
		u.X*sin + u.Y*cos,
	}
}

// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster.
func (u Vec) Normal() Vec {
	return Vec{u.Y, -u.X}
}

// Dot returns the dot product of vectors u and v.
func (u Vec) Dot(v Vec) float64 {
	return u.X*v.X + u.Y*v.Y
}

// Cross return the cross product of vectors u and v.
func (u Vec) Cross(v Vec) float64 {
	return u.X*v.Y - v.X*u.Y
}

// Map applies the function f to both x and y components of the vector u and returns the modified
// vector.
//
//   u := pixel.V(10.5, -1.5)
//   v := u.Map(math.Floor)   // v is Vec(10, -2), both components of u floored
func (u Vec) Map(f func(float64) float64) Vec {
	return Vec{
		f(u.X),
		f(u.Y),
	}
}

// Lerp returns a linear interpolation between vectors a and b.
//
// This function basically returns a point along the line between a and b and t chooses which one.
// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
// return the appropriate point between a and b and so on.
func Lerp(a, b Vec, t float64) Vec {
	return a.Scaled(1 - t).Add(b.Scaled(t))
}

// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
// points, Min and Max.
//
// The invariant should hold, that Max's components are greater or equal than Min's components
// respectively.
type Rect struct {
	Min, Max Vec
}

// R returns a new Rect with given the Min and Max coordinates.
//
// Note that the returned rectangle is not automatically normalized.
func R(minX, minY, maxX, maxY float64) Rect {
	return Rect{
		Min: Vec{minX, minY},
		Max: Vec{maxX, maxY},
	}
}

// String returns the string representation of the Rect.
//
//   r := pixel.R(100, 50, 200, 300)
//   r.String()     // returns "Rect(100, 50, 200, 300)"
//   fmt.Println(r) // Rect(100, 50, 200, 300)
func (r Rect) String() string {
	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
}

// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
func (r Rect) Norm() Rect {
	return Rect{
		Min: Vec{
			math.Min(r.Min.X, r.Max.X),
			math.Min(r.Min.Y, r.Max.Y),
		},
		Max: Vec{
			math.Max(r.Min.X, r.Max.X),
			math.Max(r.Min.Y, r.Max.Y),
		},
	}
}

// W returns the width of the Rect.
func (r Rect) W() float64 {
	return r.Max.X - r.Min.X
}

// H returns the height of the Rect.
func (r Rect) H() float64 {
	return r.Max.Y - r.Min.Y
}

// Size returns the vector of width and height of the Rect.
func (r Rect) Size() Vec {
	return V(r.W(), r.H())
}

// Area returns the area of r. If r is not normalized, area may be negative.
func (r Rect) Area() float64 {
	return r.W() * r.H()
}

// Center returns the position of the center of the Rect.
func (r Rect) Center() Vec {
	return Lerp(r.Min, r.Max, 0.5)
}

// Moved returns the Rect moved (both Min and Max) by the given vector delta.
func (r Rect) Moved(delta Vec) Rect {
	return Rect{
		Min: r.Min.Add(delta),
		Max: r.Max.Add(delta),
	}
}

// Resized returns the Rect resized to the given size while keeping the position of the given
// anchor.
//
//   r.Resized(r.Min, size)      // resizes while keeping the position of the lower-left corner
//   r.Resized(r.Max, size)      // same with the top-right corner
//   r.Resized(r.Center(), size) // resizes around the center
//
// This function does not make sense for resizing a rectangle of zero area and will panic. Use
// ResizedMin in the case of zero area.
func (r Rect) Resized(anchor, size Vec) Rect {
	if r.W()*r.H() == 0 {
		panic(fmt.Errorf("(%T).Resize: zero area", r))
	}
	fraction := Vec{size.X / r.W(), size.Y / r.H()}
	return Rect{
		Min: anchor.Add(r.Min.Sub(anchor)).ScaledXY(fraction),
		Max: anchor.Add(r.Max.Sub(anchor)).ScaledXY(fraction),
	}
}

// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
// Min.
//
// Sizes of zero area are safe here.
func (r Rect) ResizedMin(size Vec) Rect {
	return Rect{
		Min: r.Min,
		Max: r.Min.Add(size),
	}
}

// Contains checks whether a vector u is contained within this Rect (including it's borders).
func (r Rect) Contains(u Vec) bool {
	return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
}

// Union returns the minimal Rect which covers both r and s. Rects r and s must be normalized.
func (r Rect) Union(s Rect) Rect {
	return R(
		math.Min(r.Min.X, s.Min.X),
		math.Min(r.Min.Y, s.Min.Y),
		math.Max(r.Max.X, s.Max.X),
		math.Max(r.Max.Y, s.Max.Y),
	)
}

// Intersect returns the maximal Rect which is covered by both r and s. Rects r and s must be normalized.
func (r Rect) Intersect(s Rect) Rect {
	t := R(
		math.Min(r.Max.X, s.Max.X),
		math.Min(r.Max.Y, s.Max.Y),
		math.Max(r.Min.X, s.Min.X),
		math.Max(r.Min.Y, s.Min.Y),
	)
	if t.Min.X >= t.Max.X || t.Min.Y >= t.Max.Y {
		return Rect{}
	}
	return t
}

// Matrix is a 3x2 affine matrix that can be used for all kinds of spatial transforms, such
// as movement, scaling and rotations.
//
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
// example:
//
//   pixel.IM.Moved(pixel.V(100, 200)).Rotated(pixel.ZV, math.Pi/2)
//
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
// vertically and then rotates everything by 90 degrees around the origin.
//
// Layout is:
// [0] [2] [4]
// [1] [3] [5]
//  0   0   1  (implicit row)
type Matrix [6]float64

// IM stands for identity matrix. Does nothing, no transformation.
var IM = Matrix{1, 0, 0, 1, 0, 0}

// String returns a string representation of the Matrix.
//
//   m := pixel.IM
//   fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
func (m Matrix) String() string {
	return fmt.Sprintf(
		"Matrix(%v %v %v | %v %v %v)",
		m[0], m[2], m[4],
		m[1], m[3], m[5],
	)
}

// Moved moves everything by the delta vector.
func (m Matrix) Moved(delta Vec) Matrix {
	m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
	return m
}

// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
	m[4], m[5] = m[4]-around.X, m[5]-around.Y
	m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
	m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
	m[4], m[5] = m[4]+around.X, m[5]+around.Y
	return m
}

// Scaled scales everything around a given point by the scale factor.
func (m Matrix) Scaled(around Vec, scale float64) Matrix {
	return m.ScaledXY(around, V(scale, scale))
}

// Rotated rotates everything around a given point by the given angle in radians.
func (m Matrix) Rotated(around Vec, angle float64) Matrix {
	sint, cost := math.Sincos(angle)
	m[4], m[5] = m[4]-around.X, m[5]-around.Y
	m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
	m[4], m[5] = m[4]+around.X, m[5]+around.Y
	return m
}

// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
// after the transformations of this Matrix.
func (m Matrix) Chained(next Matrix) Matrix {
	return Matrix{
		m[0]*next[0] + m[2]*next[1],
		m[1]*next[0] + m[3]*next[1],
		m[0]*next[2] + m[2]*next[3],
		m[1]*next[2] + m[3]*next[3],
		m[0]*next[4] + m[2]*next[5] + m[4],
		m[1]*next[4] + m[3]*next[5] + m[5],
	}
}

// Project applies all transformations added to the Matrix to a vector u and returns the result.
//
// Time complexity is O(1).
func (m Matrix) Project(u Vec) Vec {
	return Vec{m[0]*u.X + m[2]*u.Y + m[4], m[1]*u.X + m[3]*u.Y + m[5]}
}

// Unproject does the inverse operation to Project.
//
// It turns out that multiplying a vector by the inverse matrix of m can be nearly-accomplished by
// subtracting the translate part of the matrix and multplying by the inverse of the top-left 2x2
// matrix, and the inverse of a 2x2 matrix is simple enough to just be inlined in the computation.
//
// Time complexity is O(1).
func (m Matrix) Unproject(u Vec) Vec {
	d := (m[0] * m[3]) - (m[1] * m[2])
	u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d
	return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]}
}
<|endoftext|>


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

package gg

import (
	"fmt"
	"image/color"
	"math"
	"reflect"

	"github.com/aclements/go-gg/generic"
	"github.com/aclements/go-gg/table"
	"github.com/aclements/go-moremath/scale"
)

// Continuous -> Interpolatable? Definitely.
//
// Continuous -> Discrete? Can always discretize the input either in
// value order or in index order. In this case the transform (linear,
// log, etc) doesn't matter as long as it's order-preserving.
//
// Discrete -> Interpolatable? Pick evenly spaced values on [0,1].
//
// Discrete -> Discrete? Definitely. Cycle the range if it's not long
// enough. If the input range is a VarNominal, concatenate the
// sequences and use index ordering.
//
// It's not really "continuous", it's more specifically cardinal.

// XXX
//
// A Scaler can be cardinal, discrete, or identity.
//
// A cardinal Scaler has a VarCardinal input domain. If its output
// range is continuous, it maps an interval over the input to an
// interval of the output (possibly through a transformation such as a
// logarithm). If its output range is discrete, the input is
// discretized in value order and it acts like a discrete scale.
//
// XXX The cardinal -> discrete rule means we need to keep all of the
// input data, rather than just its bounds, just in case the range is
// discrete. Maybe it should just be a bucketing rule?
//
// A discrete Scaler has a VarNominal input domain. If the input is
// VarOrdinal, its order is used; otherwise, index order is imposed.
// If the output range is continuous, a discrete Scaler maps its input
// to the centers of equal sub-intervals of [0, 1] and then applies
// the Ranger. If the output range is discrete, the Scaler maps the
// Nth input level to the N%len(range)th output value.
//
// An identity Scaler ignores its input domain and output range and
// uses an identity function for mapping input to output. This is
// useful for specifying aesthetics directly, such as color or size,
// and is especially useful for constant Vars.
//
// XXX Should identity Scalers map numeric types to float64? Maybe it
// should depend on the range type of the ranger?
//
// XXX Arrange documentation as X -> Y?
type Scaler interface {
	// XXX

	ExpandDomain(table.Slice)

	// Ranger sets this Scaler's output range if r is non-nil and
	// returns the previous continuous range. This makes the range
	// continuous and overrides any set discrete range.
	Ranger(r Ranger) Ranger

	// DiscreteRange sets this Scaler's output range if r is
	// non-nil and returns the previous discrete range. r must be
	// a sequence (slice, array, or pointer to array). This makes
	// the range discrete and overrides any set continuous range.
	//
	// XXX This interface makes it annoying to test if a Scaler
	// has a range because you have to check both Ranger and
	// DiscreteRange.
	DiscreteRange(r interface{}) interface{}

	// XXX Should RangeType be implied by the aesthetic?
	//
	// XXX Should this be a method of Ranger instead?
	RangeType() reflect.Type

	// XXX
	//
	// x must be of the same type as the values in the domain Var.
	//
	// XXX Or should this take a slice? Or even a Var? That would
	// also eliminate RangeType(), though then Map would need to
	// know how to make the right type of return slice. Unless we
	// pushed slice mapping all the way to Ranger.
	//
	// XXX We could eliminate ExpandDomain if the caller was
	// required to pass everything to this at once and this did
	// the scale training. That would also make it easy to
	// implement the cardinal -> discrete by value order rule.
	// This would probably also make Map much faster.
	Map(x interface{}) interface{}

	// XXX What should this return? moremath returns values in the
	// input space, but that obviously doesn't work for discrete
	// scales if I want the ticks between values. It could return
	// values in the intermediate space or the output space.
	// Intermediate space works for continuous and discrete
	// inputs, but not for discrete ranges (maybe that's okay).
	// Output space is bad because I change the plot location in
	// the course of layout. Currently it returns values in the
	// input space or nil if ticks don't make sense.
	Ticks(n int) (major, minor table.Slice, labels []string)

	CloneScaler() Scaler
}

type ContinuousScaler interface {
	Scaler

	// TODO: There are two variations on min/max. 1) We can force
	// the min/max, even if there's data beyond it. 2) We can say
	// min/max has to be at least something, but data can expand
	// beyond it. In the latter case, maybe min/max doesn't matter
	// and it's just "include this point".

	SetMin(v float64) ContinuousScaler
	SetMax(v float64) ContinuousScaler
}

var float64Type = reflect.TypeOf(float64(0))
var colorType = reflect.TypeOf((*color.Color)(nil)).Elem()

var canCardinal = map[reflect.Kind]bool{
	reflect.Float32: true,
	reflect.Float64: true,
	reflect.Int:     true,
	reflect.Int8:    true,
	reflect.Int16:   true,
	reflect.Int32:   true,
	reflect.Int64:   true,
	reflect.Uint:    true,
	reflect.Uintptr: true,
	reflect.Uint8:   true,
	reflect.Uint16:  true,
	reflect.Uint32:  true,
	reflect.Uint64:  true,
}

func isCardinal(k reflect.Kind) bool {
	// XXX Move this to generic.IsCardinalR and rename CanOrderR
	// to IsOrderedR. Does complex count? It supports most
	// arithmetic operators. Maybe cardinal is a plot concept and
	// not a generic concept? If sort.Interface influences this,
	// this may need to be a question about a Slice, not a
	// reflect.Kind.
	return canCardinal[k]
}

type defaultScale struct {
	scale Scaler
}

func (s *defaultScale) String() string {
	return fmt.Sprintf("default (%s)", s.scale)
}

func (s *defaultScale) ExpandDomain(v table.Slice) {
	if s.scale == nil {
		var err error
		s.scale, err = DefaultScale(v)
		if err != nil {
			panic(&generic.TypeError{reflect.TypeOf(v), nil, err.Error()})
		}
	}
	s.scale.ExpandDomain(v)
}

func (s *defaultScale) ensure() Scaler {
	if s.scale == nil {
		s.scale = NewLinearScaler()
	}
	return s.scale
}

func (s *defaultScale) Ranger(r Ranger) Ranger {
	return s.ensure().Ranger(r)
}

func (s *defaultScale) DiscreteRange(r interface{}) interface{} {
	return s.ensure().DiscreteRange(r)
}

func (s *defaultScale) RangeType() reflect.Type {
	return s.ensure().RangeType()
}

func (s *defaultScale) Map(x interface{}) interface{} {
	return s.ensure().Map(x)
}

func (s *defaultScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	return s.ensure().Ticks(n)
}

func (s *defaultScale) CloneScaler() Scaler {
	if s.scale == nil {
		return &defaultScale{}
	}
	return &defaultScale{s.scale.CloneScaler()}
}

func DefaultScale(seq table.Slice) (Scaler, error) {
	// Handle common case types.
	switch seq.(type) {
	case []float64, []int, []uint:
		return NewLinearScaler(), nil

	case []string:
		// TODO: Ordinal scale
	}

	rt := reflect.TypeOf(seq).Elem()
	rtk := rt.Kind()

	switch {
	case rt.Implements(colorType):
		// For things that are already visual values, use an
		// identity scale.
		return NewIdentityScale(), nil

		// TODO: GroupAuto needs to make similar
		// cardinal/ordinal/nominal decisions. Deduplicate
		// these better.
	case isCardinal(rtk):
		return NewLinearScaler(), nil

	case generic.CanOrderR(rtk):
		// TODO: Ordinal scale
		panic("not implemented")

	case rt.Comparable():
		// TODO: Nominal scale
		panic("not implemented")
	}

	return nil, fmt.Errorf("no default scale type for %T", seq)
}

func NewIdentityScale() Scaler {
	return &identityScale{}
}

type identityScale struct {
	rangeType reflect.Type
}

func (s *identityScale) ExpandDomain(v table.Slice) {
	s.rangeType = reflect.TypeOf(v).Elem()
}

func (s *identityScale) RangeType() reflect.Type {
	return s.rangeType
}

func (s *identityScale) Ranger(r Ranger) Ranger                  { return nil }
func (s *identityScale) DiscreteRange(r interface{}) interface{} { return nil }
func (s *identityScale) Map(x interface{}) interface{}           { return x }

func (s *identityScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	return nil, nil, nil
}

func (s *identityScale) CloneScaler() Scaler {
	s2 := *s
	return &s2
}

// NewLinearScaler returns a continuous linear scale. The domain must
// be a VarCardinal.
//
// XXX If I return a Scaler, I can't have methods for setting fixed
// bounds and such. I don't really want to expose the whole type.
// Maybe a sub-interface for continuous Scalers?
func NewLinearScaler() ContinuousScaler {
	return &linearScale{
		s:       scale.Linear{Min: math.NaN(), Max: math.NaN()},
		dataMin: math.NaN(),
		dataMax: math.NaN(),
	}
}

type linearScale struct {
	s scale.Linear
	r Ranger

	dataMin, dataMax float64
}

func (s *linearScale) String() string {
	return fmt.Sprintf("linear [%g,%g] => %s", s.s.Min, s.s.Max, s.r)
}

func (s *linearScale) ExpandDomain(v table.Slice) {
	var data []float64
	generic.ConvertSlice(&data, v)
	min, max := s.dataMin, s.dataMax
	for _, v := range data {
		if math.IsNaN(v) || math.IsInf(v, 0) {
			continue
		}
		if v < min || math.IsNaN(min) {
			min = v
		}
		if v > max || math.IsNaN(max) {
			max = v
		}
	}
	s.dataMin, s.dataMax = min, max
}

func (s *linearScale) SetMin(v float64) ContinuousScaler {
	s.s.Min = v
	return s
}

func (s *linearScale) SetMax(v float64) ContinuousScaler {
	s.s.Max = v
	return s
}

func (s *linearScale) get() scale.Linear {
	ls := s.s
	if ls.Min > ls.Max {
		ls.Min, ls.Max = ls.Max, ls.Min
	}
	if math.IsNaN(ls.Min) {
		ls.Min = s.dataMin
	}
	if math.IsNaN(ls.Max) {
		ls.Max = s.dataMax
	}
	if math.IsNaN(ls.Min) {
		// Only possible if both dataMin and dataMax are NaN.
		ls.Min, ls.Max = -1, 1
	}
	return ls
}

func (s *linearScale) Ranger(r Ranger) Ranger {
	old := s.r
	if r != nil {
		s.r = r
	}
	return old
}

func (s *linearScale) DiscreteRange(r interface{}) interface{} {
	panic("not implemented")
}

func (s *linearScale) RangeType() reflect.Type {
	// XXX Discrete ranges
	return s.r.RangeType()
}

func (s *linearScale) Map(x interface{}) interface{} {
	ls := s.get()
	f64 := reflect.TypeOf(float64(0))
	v := reflect.ValueOf(x).Convert(f64).Float()
	return s.r.Map(ls.Map(v))
}

func (s *linearScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	ls := s.get()
	majorx, minorx := ls.Ticks(n)

	// Compute labels.
	//
	// TODO: Custom label formats.
	//
	// TODO: If the input type is not a built-in type, it may have
	// a useful custom String method. If it's integer-typed, maybe
	// I don't let the tick level go below 0.
	labels = make([]string, len(majorx))
	for i, x := range majorx {
		labels[i] = fmt.Sprintf("%g", x)
	}

	return majorx, minorx, labels
}

func (s *linearScale) CloneScaler() Scaler {
	s2 := *s
	return &s2
}

type Ranger interface {
	Map(x float64) (y interface{})
	Unmap(y interface{}) (x float64)
	RangeType() reflect.Type
}

func NewFloatRanger(lo, hi float64) Ranger {
	return &floatRanger{lo, hi - lo}
}

type floatRanger struct {
	lo, w float64
}

func (r *floatRanger) String() string {
	return fmt.Sprintf("[%g,%g]", r.lo, r.lo+r.w)
}

func (r *floatRanger) Map(x float64) interface{} {
	return x*r.w + r.lo
}

func (r *floatRanger) Unmap(y interface{}) float64 {
	return (y.(float64) - r.lo) / r.w
}

func (r *floatRanger) RangeType() reflect.Type {
	return float64Type
}

// mapMany applies scaler.Map to all of the values in seq and returns
// a slice of the results.
//
// TODO: Maybe this should just be how Scaler.Map works.
func mapMany(scaler Scaler, seq table.Slice) table.Slice {
	sv := reflect.ValueOf(seq)
	rt := reflect.SliceOf(scaler.RangeType())
	res := reflect.MakeSlice(rt, sv.Len(), sv.Len())
	for i, len := 0, sv.Len(); i < len; i++ {
		val := scaler.Map(sv.Index(i).Interface())
		res.Index(i).Set(reflect.ValueOf(val))
	}
	return res.Interface()
}
<commit_msg>gg: initial support for discrete rangers<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gg

import (
	"fmt"
	"image/color"
	"math"
	"reflect"

	"github.com/aclements/go-gg/generic"
	"github.com/aclements/go-gg/table"
	"github.com/aclements/go-moremath/scale"
)

// Continuous -> Interpolatable? Definitely.
//
// Continuous -> Discrete? Can always discretize the input either in
// value order or in index order. In this case the transform (linear,
// log, etc) doesn't matter as long as it's order-preserving. OTOH, a
// continuous input scale can be asked to map *any* value of its input
// type, but if I do this I can only map values that were trained.
// That suggests that I have to just bin the range to do this mapping.
//
// Discrete -> Interpolatable? Pick evenly spaced values on [0,1].
//
// Discrete -> Discrete? Definitely. Cycle the range if it's not long
// enough. If the input range is a VarNominal, concatenate the
// sequences and use index ordering.
//
// It's not really "continuous", it's more specifically cardinal.

// XXX
//
// A Scaler can be cardinal, discrete, or identity.
//
// A cardinal Scaler has a VarCardinal input domain. If its output
// range is continuous, it maps an interval over the input to an
// interval of the output (possibly through a transformation such as a
// logarithm). If its output range is discrete, the input is
// discretized in value order and it acts like a discrete scale.
//
// XXX The cardinal -> discrete rule means we need to keep all of the
// input data, rather than just its bounds, just in case the range is
// discrete. Maybe it should just be a bucketing rule?
//
// A discrete Scaler has a VarNominal input domain. If the input is
// VarOrdinal, its order is used; otherwise, index order is imposed.
// If the output range is continuous, a discrete Scaler maps its input
// to the centers of equal sub-intervals of [0, 1] and then applies
// the Ranger. If the output range is discrete, the Scaler maps the
// Nth input level to the N%len(range)th output value.
//
// An identity Scaler ignores its input domain and output range and
// uses an identity function for mapping input to output. This is
// useful for specifying aesthetics directly, such as color or size,
// and is especially useful for constant Vars.
//
// XXX Should identity Scalers map numeric types to float64? Maybe it
// should depend on the range type of the ranger?
//
// XXX Arrange documentation as X -> Y?
type Scaler interface {
	// XXX

	ExpandDomain(table.Slice)

	// Ranger sets this Scaler's output range if r is non-nil and
	// returns the previous range.
	Ranger(r Ranger) Ranger

	// XXX Should RangeType be implied by the aesthetic?
	//
	// XXX Should this be a method of Ranger instead?
	RangeType() reflect.Type

	// XXX
	//
	// x must be of the same type as the values in the domain Var.
	//
	// XXX Or should this take a slice? Or even a Var? That would
	// also eliminate RangeType(), though then Map would need to
	// know how to make the right type of return slice. Unless we
	// pushed slice mapping all the way to Ranger.
	//
	// XXX We could eliminate ExpandDomain if the caller was
	// required to pass everything to this at once and this did
	// the scale training. That would also make it easy to
	// implement the cardinal -> discrete by value order rule.
	// This would probably also make Map much faster.
	Map(x interface{}) interface{}

	// XXX What should this return? moremath returns values in the
	// input space, but that obviously doesn't work for discrete
	// scales if I want the ticks between values. It could return
	// values in the intermediate space or the output space.
	// Intermediate space works for continuous and discrete
	// inputs, but not for discrete ranges (maybe that's okay).
	// Output space is bad because I change the plot location in
	// the course of layout. Currently it returns values in the
	// input space or nil if ticks don't make sense.
	Ticks(n int) (major, minor table.Slice, labels []string)

	CloneScaler() Scaler
}

type ContinuousScaler interface {
	Scaler

	// TODO: There are two variations on min/max. 1) We can force
	// the min/max, even if there's data beyond it. 2) We can say
	// min/max has to be at least something, but data can expand
	// beyond it. In the latter case, maybe min/max doesn't matter
	// and it's just "include this point".

	SetMin(v float64) ContinuousScaler
	SetMax(v float64) ContinuousScaler
}

var float64Type = reflect.TypeOf(float64(0))
var colorType = reflect.TypeOf((*color.Color)(nil)).Elem()

var canCardinal = map[reflect.Kind]bool{
	reflect.Float32: true,
	reflect.Float64: true,
	reflect.Int:     true,
	reflect.Int8:    true,
	reflect.Int16:   true,
	reflect.Int32:   true,
	reflect.Int64:   true,
	reflect.Uint:    true,
	reflect.Uintptr: true,
	reflect.Uint8:   true,
	reflect.Uint16:  true,
	reflect.Uint32:  true,
	reflect.Uint64:  true,
}

func isCardinal(k reflect.Kind) bool {
	// XXX Move this to generic.IsCardinalR and rename CanOrderR
	// to IsOrderedR. Does complex count? It supports most
	// arithmetic operators. Maybe cardinal is a plot concept and
	// not a generic concept? If sort.Interface influences this,
	// this may need to be a question about a Slice, not a
	// reflect.Kind.
	return canCardinal[k]
}

type defaultScale struct {
	scale Scaler
}

func (s *defaultScale) String() string {
	return fmt.Sprintf("default (%s)", s.scale)
}

func (s *defaultScale) ExpandDomain(v table.Slice) {
	if s.scale == nil {
		var err error
		s.scale, err = DefaultScale(v)
		if err != nil {
			panic(&generic.TypeError{reflect.TypeOf(v), nil, err.Error()})
		}
	}
	s.scale.ExpandDomain(v)
}

func (s *defaultScale) ensure() Scaler {
	if s.scale == nil {
		s.scale = NewLinearScaler()
	}
	return s.scale
}

func (s *defaultScale) Ranger(r Ranger) Ranger {
	return s.ensure().Ranger(r)
}

func (s *defaultScale) RangeType() reflect.Type {
	return s.ensure().RangeType()
}

func (s *defaultScale) Map(x interface{}) interface{} {
	return s.ensure().Map(x)
}

func (s *defaultScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	return s.ensure().Ticks(n)
}

func (s *defaultScale) CloneScaler() Scaler {
	if s.scale == nil {
		return &defaultScale{}
	}
	return &defaultScale{s.scale.CloneScaler()}
}

func DefaultScale(seq table.Slice) (Scaler, error) {
	// Handle common case types.
	switch seq.(type) {
	case []float64, []int, []uint:
		return NewLinearScaler(), nil

	case []string:
		// TODO: Ordinal scale
	}

	rt := reflect.TypeOf(seq).Elem()
	rtk := rt.Kind()

	switch {
	case rt.Implements(colorType):
		// For things that are already visual values, use an
		// identity scale.
		return NewIdentityScale(), nil

		// TODO: GroupAuto needs to make similar
		// cardinal/ordinal/nominal decisions. Deduplicate
		// these better.
	case isCardinal(rtk):
		return NewLinearScaler(), nil

	case generic.CanOrderR(rtk):
		// TODO: Ordinal scale
		panic("not implemented")

	case rt.Comparable():
		// TODO: Nominal scale
		panic("not implemented")
	}

	return nil, fmt.Errorf("no default scale type for %T", seq)
}

func NewIdentityScale() Scaler {
	return &identityScale{}
}

type identityScale struct {
	rangeType reflect.Type
}

func (s *identityScale) ExpandDomain(v table.Slice) {
	s.rangeType = reflect.TypeOf(v).Elem()
}

func (s *identityScale) RangeType() reflect.Type {
	return s.rangeType
}

func (s *identityScale) Ranger(r Ranger) Ranger        { return nil }
func (s *identityScale) Map(x interface{}) interface{} { return x }

func (s *identityScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	return nil, nil, nil
}

func (s *identityScale) CloneScaler() Scaler {
	s2 := *s
	return &s2
}

// NewLinearScaler returns a continuous linear scale. The domain must
// be a VarCardinal.
//
// XXX If I return a Scaler, I can't have methods for setting fixed
// bounds and such. I don't really want to expose the whole type.
// Maybe a sub-interface for continuous Scalers?
func NewLinearScaler() ContinuousScaler {
	return &linearScale{
		s:       scale.Linear{Min: math.NaN(), Max: math.NaN()},
		dataMin: math.NaN(),
		dataMax: math.NaN(),
	}
}

type linearScale struct {
	s scale.Linear
	r Ranger

	dataMin, dataMax float64
}

func (s *linearScale) String() string {
	return fmt.Sprintf("linear [%g,%g] => %s", s.s.Min, s.s.Max, s.r)
}

func (s *linearScale) ExpandDomain(v table.Slice) {
	var data []float64
	generic.ConvertSlice(&data, v)
	min, max := s.dataMin, s.dataMax
	for _, v := range data {
		if math.IsNaN(v) || math.IsInf(v, 0) {
			continue
		}
		if v < min || math.IsNaN(min) {
			min = v
		}
		if v > max || math.IsNaN(max) {
			max = v
		}
	}
	s.dataMin, s.dataMax = min, max
}

func (s *linearScale) SetMin(v float64) ContinuousScaler {
	s.s.Min = v
	return s
}

func (s *linearScale) SetMax(v float64) ContinuousScaler {
	s.s.Max = v
	return s
}

func (s *linearScale) get() scale.Linear {
	ls := s.s
	if ls.Min > ls.Max {
		ls.Min, ls.Max = ls.Max, ls.Min
	}
	if math.IsNaN(ls.Min) {
		ls.Min = s.dataMin
	}
	if math.IsNaN(ls.Max) {
		ls.Max = s.dataMax
	}
	if math.IsNaN(ls.Min) {
		// Only possible if both dataMin and dataMax are NaN.
		ls.Min, ls.Max = -1, 1
	}
	return ls
}

func (s *linearScale) Ranger(r Ranger) Ranger {
	old := s.r
	if r != nil {
		s.r = r
	}
	return old
}

func (s *linearScale) RangeType() reflect.Type {
	return s.r.RangeType()
}

func (s *linearScale) Map(x interface{}) interface{} {
	ls := s.get()
	f64 := reflect.TypeOf(float64(0))
	v := reflect.ValueOf(x).Convert(f64).Float()
	scaled := ls.Map(v)
	switch r := s.r.(type) {
	case ContinuousRanger:
		return r.Map(scaled)

	case DiscreteRanger:
		_, levels := r.Levels()
		// Bin the scaled value into 'levels' bins.
		level := int(scaled * float64(levels))
		if level < 0 {
			level = 0
		} else if level >= levels {
			level = levels - 1
		}
		return r.Map(level, levels)

	default:
		panic("Ranger must be a ContinuousRanger or DiscreteRanger")
	}
}

func (s *linearScale) Ticks(n int) (major, minor table.Slice, labels []string) {
	ls := s.get()
	majorx, minorx := ls.Ticks(n)

	// Compute labels.
	//
	// TODO: Custom label formats.
	//
	// TODO: If the input type is not a built-in type, it may have
	// a useful custom String method. If it's integer-typed, maybe
	// I don't let the tick level go below 0.
	labels = make([]string, len(majorx))
	for i, x := range majorx {
		labels[i] = fmt.Sprintf("%g", x)
	}

	return majorx, minorx, labels
}

func (s *linearScale) CloneScaler() Scaler {
	s2 := *s
	return &s2
}

// XXX
//
// A Ranger must be either a ContinuousRanger or a DiscreteRanger.
type Ranger interface {
	RangeType() reflect.Type
}

type ContinuousRanger interface {
	Ranger
	Map(x float64) (y interface{})
	Unmap(y interface{}) (x float64)
}

type DiscreteRanger interface {
	Ranger
	Levels() (min, max int)
	Map(i, j int) interface{}
}

func NewFloatRanger(lo, hi float64) ContinuousRanger {
	return &floatRanger{lo, hi - lo}
}

type floatRanger struct {
	lo, w float64
}

func (r *floatRanger) String() string {
	return fmt.Sprintf("[%g,%g]", r.lo, r.lo+r.w)
}

func (r *floatRanger) RangeType() reflect.Type {
	return float64Type
}

func (r *floatRanger) Map(x float64) interface{} {
	return x*r.w + r.lo
}

func (r *floatRanger) Unmap(y interface{}) float64 {
	return (y.(float64) - r.lo) / r.w
}

func NewColorRanger(palette []color.Color) DiscreteRanger {
	// TODO: Support continuous palettes.
	//
	// TODO: Support discrete palettes that vary depending on the
	// number of levels.
	return &colorRanger{palette}
}

type colorRanger struct {
	palette []color.Color
}

func (r *colorRanger) RangeType() reflect.Type {
	return colorType
}

func (r *colorRanger) Levels() (min, max int) {
	return len(r.palette), len(r.palette)
}

func (r *colorRanger) Map(i, j int) interface{} {
	if i < 0 {
		i = 0
	} else if i >= len(r.palette) {
		i = len(r.palette) - 1
	}
	return r.palette[i]
}

// mapMany applies scaler.Map to all of the values in seq and returns
// a slice of the results.
//
// TODO: Maybe this should just be how Scaler.Map works.
func mapMany(scaler Scaler, seq table.Slice) table.Slice {
	sv := reflect.ValueOf(seq)
	rt := reflect.SliceOf(scaler.RangeType())
	res := reflect.MakeSlice(rt, sv.Len(), sv.Len())
	for i, len := 0, sv.Len(); i < len; i++ {
		val := scaler.Map(sv.Index(i).Interface())
		res.Index(i).Set(reflect.ValueOf(val))
	}
	return res.Interface()
}
<|endoftext|>


<commit_before>package cluster

import (
	"bufio"
	"errors"
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis"
	"github.com/mediocregopher/radix.v2/cluster"
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"github.com/mediocregopher/radix.v2/util"
)

// Server is the thread that listen for clients' connections
type Server struct {
	sync.Mutex
	config   lib.RelayerConfig
	mode     int
	done     chan bool
	listener net.Listener
	pool     util.Cmder
}

type reqData struct {
	cmd      string
	args     []*redis.Resp
	compress bool
	answerCh chan *redis.Resp
}

const (
	requestBufferSize = 64
	listenTimeout     = 15
	maxSenders        = 2 // Max number of write goutines
	selectCommand     = "SELECT"
)

// errors
var (
	errBadCmd = errors.New("ERR bad command")
	commands  map[string]*redis.Resp

	respOK         = redis.NewRespSimple("OK")
	respTrue       = redis.NewResp(1)
	respBadCommand = redis.NewResp(errBadCmd)
)

func init() {
	// These are the commands that can be sent in "background" when in smart mode
	// The values are the immediate responses to the clients
	commands = map[string]*redis.Resp{
		"SET":       respOK,
		"SETEX":     respOK,
		"PSETEX":    respOK,
		"MSET":      respOK,
		"HMSET":     respOK,
		"SELECT":    respOK,
		"HSET":      respTrue,
		"EXPIRE":    respTrue,
		"EXPIREAT":  respTrue,
		"PEXPIRE":   respTrue,
		"PEXPIREAT": respTrue,
	}
}

// New creates a new Redis cluster or pool client
func New(c lib.RelayerConfig, done chan bool) (*Server, error) {
	srv := &Server{
		done: done,
	}

	err := srv.Reload(&c)
	if err != nil {
		log.Println("no available redis cluster nodes", srv.config.URL)
		return nil, err
	}

	return srv, nil
}

// Reload the configuration
func (srv *Server) Reload(c *lib.RelayerConfig) error {
	srv.Lock()
	defer srv.Unlock()

	reset := false
	if srv.config.URL != c.URL {
		reset = true
	}
	srv.config = *c // Save a copy
	srv.mode = c.Type()

	if srv.config.Protocol == "redis-cluster" {
		return srv.reloadCluster(reset)
	}
	return srv.reloadPool(reset)
}

// Start listening in the specified local port
func (srv *Server) Start() (e error) {
	srv.Lock()
	defer srv.Unlock()

	if srv.pool == nil {
		return
	}

	srv.listener, e = lib.Listener(&srv.config)
	if e != nil {
		return e
	}

	// Serve clients
	go func() {
		for {
			netConn, e := srv.listener.Accept()
			if e != nil {
				log.Println("Exiting", srv.config.ListenHost())
				return
			}
			go srv.handleConnection(netConn)
		}
	}()

	return nil
}

func (srv *Server) handleConnection(netCon net.Conn) {
	defer netCon.Close()
	conn := bufio.NewReadWriter(bufio.NewReader(netCon), bufio.NewWriter(netCon))
	defer conn.Flush()

	reqCh := make(chan *reqData, requestBufferSize)
	defer close(reqCh)

	for i := 0; i < senders; i++ {
		go sender(srv.pool, reqCh)
	}

	respCh := make(chan *redis.Resp)
	reader := redis.NewRespReader(conn)
	for {
		conn.Flush()
		err := netCon.SetReadDeadline(time.Now().Add(listenTimeout * time.Second))
		if err != nil {
			log.Printf("error setting read deadline: %s", err)
			return
		}

		req := reader.Read()
		if redis.IsTimeout(req) {
			continue
		} else if req.IsType(redis.IOErr) {
			return
		}

		resp := srv.process(req, reqCh, respCh)
		if srv.config.Compress || srv.config.Uncompress {
			resp = compress.UResp(resp)
		}
		resp.WriteTo(conn)
	}
}

func (srv *Server) process(m *redis.Resp, reqCh chan *reqData, respCh chan *redis.Resp) *redis.Resp {
	ms, err := m.Array()
	if err != nil || len(ms) < 1 {
		return respBadCommand
	}

	cmd, err := ms[0].Str()
	if err != nil || strings.ToUpper(cmd) == selectCommand {
		return respBadCommand
	}

	data := reqData{
		cmd:      cmd,
		args:     ms[1:],
		compress: srv.config.Compress,
	}

	doAsync := false
	var fastResponse *redis.Resp
	if srv.mode == lib.ModeSmart {
		fastResponse, doAsync = commands[strings.ToUpper(cmd)]
	}

	if doAsync {
		reqCh <- &data
		return fastResponse
	}

	data.answerCh = respCh
	reqCh <- &data
	return <-respCh
}

func (srv *Server) reloadCluster(reset bool) error {
	if srv.pool != nil {
		p, ok := srv.pool.(*cluster.Cluster)
		if !ok {
			return errors.New("Relod cluster failed, bad type")
		}

		if !reset {
			log.Printf("Reload redis cluster server at port %s for target %s", srv.config.Listen, srv.config.Host())
			e := p.Reset()
			return e
		}
		log.Printf("Reset redis cluster server at port %s for target %s", srv.config.Listen, srv.config.Host())
		p.Close()
	}

	// Allows a list of URLs separated by spaces
	for _, url := range strings.Split(srv.config.URL, " ") {
		addr, err := lib.Host(url)
		if err != nil {
			continue
		}

		// Choose the highest value from config
		size := 0
		if srv.config.MaxIdleConnections > srv.config.MaxConnections {
			size = srv.config.MaxIdleConnections
		} else {
			size = srv.config.MaxConnections
		}
		if srv.pool, err = cluster.NewWithOpts(cluster.Opts{Addr: addr, PoolSize: size}); err != nil {
			log.Printf("Error in cluster %s: %s", addr, err)
			srv.pool = nil
			continue
		}
		lib.Debugf("Cluster linked to %s", addr)
		return nil
	}
	srv.pool = nil
	return errors.New("no available redis cluster nodes")
}

// The pool is only for testing, it doesn't ensure the use of the select'ed database
func (srv *Server) reloadPool(reset bool) error {
	if srv.pool != nil {
		p, ok := srv.pool.(*pool.Pool)
		if !ok {
			return errors.New("Reload pool failed, bad type")
		}
		if !reset {
			log.Printf("Reload redis pool server at port %s for target %s", srv.config.Listen, srv.config.Host())
			return nil
		}
		log.Printf("Reset redis pool server at port %s for target %s", srv.config.Listen, srv.config.Host())
		p.Empty()
	}

	var err error
	srv.pool, err = pool.New("tcp", srv.config.Host(), srv.config.MaxIdleConnections)
	if err != nil {
		srv.pool = nil
		return errors.New("connection error")
	}

	lib.Debugf("Pool linked to %s", srv.config.Host())
	return nil
}

// Exit closes the listener and send done to main
func (srv *Server) Exit() {
	if srv.listener != nil {
		srv.listener.Close()
	}
	srv.done <- true
}

func sender(cl util.Cmder, reqCh chan *reqData) {
	for m := range reqCh {
		args := make([]interface{}, len(m.args))
		for i, arg := range m.args {
			args[i] = arg
			if m.compress {
				b, e := arg.Bytes()
				if e == nil && len(b) > compress.MinCompressSize {
					args[i] = compress.Bytes(b)
				}
			}
		}

		resp := cl.Cmd(m.cmd, args...)
		if m.answerCh != nil {
			m.answerCh <- resp
		}
	}
}
<commit_msg>Fixed typo<commit_after>package cluster

import (
	"bufio"
	"errors"
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis"
	"github.com/mediocregopher/radix.v2/cluster"
	"github.com/mediocregopher/radix.v2/pool"
	"github.com/mediocregopher/radix.v2/redis"
	"github.com/mediocregopher/radix.v2/util"
)

// Server is the thread that listen for clients' connections
type Server struct {
	sync.Mutex
	config   lib.RelayerConfig
	mode     int
	done     chan bool
	listener net.Listener
	pool     util.Cmder
}

type reqData struct {
	cmd      string
	args     []*redis.Resp
	compress bool
	answerCh chan *redis.Resp
}

const (
	requestBufferSize = 64
	listenTimeout     = 15
	maxSenders        = 2 // Max number of write goutines
	selectCommand     = "SELECT"
)

// errors
var (
	errBadCmd = errors.New("ERR bad command")
	commands  map[string]*redis.Resp

	respOK         = redis.NewRespSimple("OK")
	respTrue       = redis.NewResp(1)
	respBadCommand = redis.NewResp(errBadCmd)
)

func init() {
	// These are the commands that can be sent in "background" when in smart mode
	// The values are the immediate responses to the clients
	commands = map[string]*redis.Resp{
		"SET":       respOK,
		"SETEX":     respOK,
		"PSETEX":    respOK,
		"MSET":      respOK,
		"HMSET":     respOK,
		"SELECT":    respOK,
		"HSET":      respTrue,
		"EXPIRE":    respTrue,
		"EXPIREAT":  respTrue,
		"PEXPIRE":   respTrue,
		"PEXPIREAT": respTrue,
	}
}

// New creates a new Redis cluster or pool client
func New(c lib.RelayerConfig, done chan bool) (*Server, error) {
	srv := &Server{
		done: done,
	}

	err := srv.Reload(&c)
	if err != nil {
		log.Println("no available redis cluster nodes", srv.config.URL)
		return nil, err
	}

	return srv, nil
}

// Reload the configuration
func (srv *Server) Reload(c *lib.RelayerConfig) error {
	srv.Lock()
	defer srv.Unlock()

	reset := false
	if srv.config.URL != c.URL {
		reset = true
	}
	srv.config = *c // Save a copy
	srv.mode = c.Type()

	if srv.config.Protocol == "redis-cluster" {
		return srv.reloadCluster(reset)
	}
	return srv.reloadPool(reset)
}

// Start listening in the specified local port
func (srv *Server) Start() (e error) {
	srv.Lock()
	defer srv.Unlock()

	if srv.pool == nil {
		return
	}

	srv.listener, e = lib.Listener(&srv.config)
	if e != nil {
		return e
	}

	// Serve clients
	go func() {
		for {
			netConn, e := srv.listener.Accept()
			if e != nil {
				log.Println("Exiting", srv.config.ListenHost())
				return
			}
			go srv.handleConnection(netConn)
		}
	}()

	return nil
}

func (srv *Server) handleConnection(netCon net.Conn) {
	defer netCon.Close()
	conn := bufio.NewReadWriter(bufio.NewReader(netCon), bufio.NewWriter(netCon))
	defer conn.Flush()

	reqCh := make(chan *reqData, requestBufferSize)
	defer close(reqCh)

	for i := 0; i < maxSenders; i++ {
		go sender(srv.pool, reqCh)
	}

	respCh := make(chan *redis.Resp)
	reader := redis.NewRespReader(conn)
	for {
		conn.Flush()
		err := netCon.SetReadDeadline(time.Now().Add(listenTimeout * time.Second))
		if err != nil {
			log.Printf("error setting read deadline: %s", err)
			return
		}

		req := reader.Read()
		if redis.IsTimeout(req) {
			continue
		} else if req.IsType(redis.IOErr) {
			return
		}

		resp := srv.process(req, reqCh, respCh)
		if srv.config.Compress || srv.config.Uncompress {
			resp = compress.UResp(resp)
		}
		resp.WriteTo(conn)
	}
}

func (srv *Server) process(m *redis.Resp, reqCh chan *reqData, respCh chan *redis.Resp) *redis.Resp {
	ms, err := m.Array()
	if err != nil || len(ms) < 1 {
		return respBadCommand
	}

	cmd, err := ms[0].Str()
	if err != nil || strings.ToUpper(cmd) == selectCommand {
		return respBadCommand
	}

	data := reqData{
		cmd:      cmd,
		args:     ms[1:],
		compress: srv.config.Compress,
	}

	doAsync := false
	var fastResponse *redis.Resp
	if srv.mode == lib.ModeSmart {
		fastResponse, doAsync = commands[strings.ToUpper(cmd)]
	}

	if doAsync {
		reqCh <- &data
		return fastResponse
	}

	data.answerCh = respCh
	reqCh <- &data
	return <-respCh
}

func (srv *Server) reloadCluster(reset bool) error {
	if srv.pool != nil {
		p, ok := srv.pool.(*cluster.Cluster)
		if !ok {
			return errors.New("Relod cluster failed, bad type")
		}

		if !reset {
			log.Printf("Reload redis cluster server at port %s for target %s", srv.config.Listen, srv.config.Host())
			e := p.Reset()
			return e
		}
		log.Printf("Reset redis cluster server at port %s for target %s", srv.config.Listen, srv.config.Host())
		p.Close()
	}

	// Allows a list of URLs separated by spaces
	for _, url := range strings.Split(srv.config.URL, " ") {
		addr, err := lib.Host(url)
		if err != nil {
			continue
		}

		// Choose the highest value from config
		size := 0
		if srv.config.MaxIdleConnections > srv.config.MaxConnections {
			size = srv.config.MaxIdleConnections
		} else {
			size = srv.config.MaxConnections
		}
		if srv.pool, err = cluster.NewWithOpts(cluster.Opts{Addr: addr, PoolSize: size}); err != nil {
			log.Printf("Error in cluster %s: %s", addr, err)
			srv.pool = nil
			continue
		}
		lib.Debugf("Cluster linked to %s", addr)
		return nil
	}
	srv.pool = nil
	return errors.New("no available redis cluster nodes")
}

// The pool is only for testing, it doesn't ensure the use of the select'ed database
func (srv *Server) reloadPool(reset bool) error {
	if srv.pool != nil {
		p, ok := srv.pool.(*pool.Pool)
		if !ok {
			return errors.New("Reload pool failed, bad type")
		}
		if !reset {
			log.Printf("Reload redis pool server at port %s for target %s", srv.config.Listen, srv.config.Host())
			return nil
		}
		log.Printf("Reset redis pool server at port %s for target %s", srv.config.Listen, srv.config.Host())
		p.Empty()
	}

	var err error
	srv.pool, err = pool.New("tcp", srv.config.Host(), srv.config.MaxIdleConnections)
	if err != nil {
		srv.pool = nil
		return errors.New("connection error")
	}

	lib.Debugf("Pool linked to %s", srv.config.Host())
	return nil
}

// Exit closes the listener and send done to main
func (srv *Server) Exit() {
	if srv.listener != nil {
		srv.listener.Close()
	}
	srv.done <- true
}

func sender(cl util.Cmder, reqCh chan *reqData) {
	for m := range reqCh {
		args := make([]interface{}, len(m.args))
		for i, arg := range m.args {
			args[i] = arg
			if m.compress {
				b, e := arg.Bytes()
				if e == nil && len(b) > compress.MinCompressSize {
					args[i] = compress.Bytes(b)
				}
			}
		}

		resp := cl.Cmd(m.cmd, args...)
		if m.answerCh != nil {
			m.answerCh <- resp
		}
	}
}
<|endoftext|>


<commit_before>package latency

import (
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/cloudfoundry-incubator/candiedyaml"
	"github.com/pivotal-cf/cf-redis-broker/redis/client"
	"github.com/pivotal-golang/lager"
)

type Latency struct {
	redisClient       client.Client
	latencyFilePath   string
	interval          time.Duration
	pingStopChan      chan (bool)
	fileWriteStopChan chan (bool)
	logger            lager.Logger
}

func NewLatency(
	redisClient client.Client,
	latencyFilePath string,
	interval time.Duration,
	logger lager.Logger,
) *Latency {
	latency := &Latency{
		redisClient:     redisClient,
		latencyFilePath: latencyFilePath,
		interval:        interval,
		logger:          logger,
	}
	latency.pingStopChan = make(chan bool)
	latency.fileWriteStopChan = make(chan bool)
	return latency
}

type Config struct {
	Interval        string `yaml:"interval"`
	LatencyFilePath string `yaml:"latency_file_path"`
}

func LoadConfig(filePath string) (*Config, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}

	config := &Config{}
	if err := candiedyaml.NewDecoder(file).Decode(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (l *Latency) Start() error {
	var (
		totalDuration time.Duration
		count         int
		updateMutex   sync.Mutex
	)

	l.logger.Info("Start latency monitering")
	go func() {
		for {
			select {
			case <-time.After(time.Millisecond * 10):
				start := time.Now()
				l.redisClient.Ping()
				duration := time.Since(start)

				func() {
					updateMutex.Lock()
					defer updateMutex.Unlock()

					totalDuration = totalDuration + duration
					count++
				}()
			case <-l.pingStopChan:
				return
			}
		}
	}()

	go func() {
		for {
			select {
			case <-time.After(l.interval):
				func() {
					updateMutex.Lock()
					defer updateMutex.Unlock()

					microTime := float64(totalDuration.Nanoseconds()/int64(count)) / 1000000
					stringDuration := fmt.Sprintf("%.2f", microTime)

					l.logger.Info("Writing latency to file", lager.Data{"Latency": stringDuration})
					ioutil.WriteFile(l.latencyFilePath, []byte(stringDuration), 0644)

					totalDuration = 0.
					count = 0
				}()

			case <-l.fileWriteStopChan:
				return
			}
		}
	}()

	return nil
}

func (l *Latency) Stop() error {
	defer close(l.pingStopChan)
	defer close(l.fileWriteStopChan)

	l.pingStopChan <- true
	l.fileWriteStopChan <- true
	return nil
}
<commit_msg>Fix typo in latency logging<commit_after>package latency

import (
	"fmt"
	"io/ioutil"
	"os"
	"sync"
	"time"

	"github.com/cloudfoundry-incubator/candiedyaml"
	"github.com/pivotal-cf/cf-redis-broker/redis/client"
	"github.com/pivotal-golang/lager"
)

type Latency struct {
	redisClient       client.Client
	latencyFilePath   string
	interval          time.Duration
	pingStopChan      chan (bool)
	fileWriteStopChan chan (bool)
	logger            lager.Logger
}

func NewLatency(
	redisClient client.Client,
	latencyFilePath string,
	interval time.Duration,
	logger lager.Logger,
) *Latency {
	latency := &Latency{
		redisClient:     redisClient,
		latencyFilePath: latencyFilePath,
		interval:        interval,
		logger:          logger,
	}
	latency.pingStopChan = make(chan bool)
	latency.fileWriteStopChan = make(chan bool)
	return latency
}

type Config struct {
	Interval        string `yaml:"interval"`
	LatencyFilePath string `yaml:"latency_file_path"`
}

func LoadConfig(filePath string) (*Config, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}

	config := &Config{}
	if err := candiedyaml.NewDecoder(file).Decode(config); err != nil {
		return nil, err
	}

	return config, nil
}

func (l *Latency) Start() error {
	var (
		totalDuration time.Duration
		count         int
		updateMutex   sync.Mutex
	)

	l.logger.Info("Start latency monitoring")
	go func() {
		for {
			select {
			case <-time.After(time.Millisecond * 10):
				start := time.Now()
				l.redisClient.Ping()
				duration := time.Since(start)

				func() {
					updateMutex.Lock()
					defer updateMutex.Unlock()

					totalDuration = totalDuration + duration
					count++
				}()
			case <-l.pingStopChan:
				return
			}
		}
	}()

	go func() {
		for {
			select {
			case <-time.After(l.interval):
				func() {
					updateMutex.Lock()
					defer updateMutex.Unlock()

					microTime := float64(totalDuration.Nanoseconds()/int64(count)) / 1000000
					stringDuration := fmt.Sprintf("%.2f", microTime)

					l.logger.Info("Writing latency to file", lager.Data{"Latency": stringDuration})
					ioutil.WriteFile(l.latencyFilePath, []byte(stringDuration), 0644)

					totalDuration = 0.
					count = 0
				}()

			case <-l.fileWriteStopChan:
				return
			}
		}
	}()

	return nil
}

func (l *Latency) Stop() error {
	defer close(l.pingStopChan)
	defer close(l.fileWriteStopChan)

	l.pingStopChan <- true
	l.fileWriteStopChan <- true
	return nil
}
<|endoftext|>


<commit_before>package git

import (
	"testing"
)

func init() {
	runner = &fakeRunner{}
}

var (
	testingPath = "_testing/kamino-test"
	testingSha  = "97d2258b4a58d9bf07636d76c97a3eb09490cf70"
)

func TestRemoteAccountHTTP(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  http://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountHTTPS(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  https://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountSSH(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git@github.com:rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountGit(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountWithoutSuffix(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git://github.com/rafecolton/docker-builder (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestSha(t *testing.T) {
	runner.(*fakeRunner).sha = "abc123\n"
	actual := Sha("")
	expected := "abc123"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestBranch(t *testing.T) {
	runner.(*fakeRunner).branch = "asdf\n"
	actual := Branch("")
	expected := "asdf"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestBranchAlt(t *testing.T) {
	runner.(*fakeRunner).branch = "HEAD\n"
	runner.(*fakeRunner).branch2 = `  master
  move-mithril-to-quay
* update-loadbalancer-role
  using-mighril-from-quay-instead-of-docker-hub`
	actual := Branch("")
	expected := "master"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestCleanClean(t *testing.T) {
	if !IsClean("") {
		t.Errorf("expected cleanliness")
	}
}

func TestCleanDirty(t *testing.T) {
	runner.(*fakeRunner).clean = " 2 files changed, 139 insertions(+), 105 deletions(-)\n"
	if IsClean("") {
		t.Errorf("repo is dirty")
	}
}

func TestUpToDateDiverged(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "abc"
	runner.(*fakeRunner).upToDateRemote = "123"
	runner.(*fakeRunner).upToDateBase = "def"
	if UpToDate("") != StatusDiverged {
		t.Errorf("status should be StatusDiverged")
	}
}

func TestUpToDateUpToDate(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateRemote = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusUpToDate {
		t.Errorf("status should be StatusUpToDate")
	}
}

func TestUpToDateNeedToPush(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "f4c103a85141c59749ef24320a538ae7ed238909"
	runner.(*fakeRunner).upToDateRemote = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateBase = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusNeedToPush {
		t.Errorf("status should be StatusNeedToPush")
	}
}

func TestUpToDateNeedToPull(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateRemote = "f4c103a85141c59749ef24320a538ae7ed238909"
	runner.(*fakeRunner).upToDateBase = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusNeedToPull {
		t.Errorf("status should be StatusNeedToPull")
	}
}

func TestTag(t *testing.T) {
	runner.(*fakeRunner).tag = "foo-tag"
	if Tag("") != "foo-tag" {
		t.Errorf("expected foo-tag, got %s", Tag(""))
	}
}
<commit_msg>Adding a test for status printing<commit_after>package git

import (
	"testing"
)

func init() {
	runner = &fakeRunner{}
}

var (
	testingPath = "_testing/kamino-test"
	testingSha  = "97d2258b4a58d9bf07636d76c97a3eb09490cf70"
)

func TestRemoteAccountHTTP(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  http://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountHTTPS(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  https://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountSSH(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git@github.com:rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountGit(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git://github.com/rafecolton/docker-builder.git (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestRemoteAccountWithoutSuffix(t *testing.T) {
	runner.(*fakeRunner).remoteV = `origin  git://github.com/rafecolton/docker-builder (fetch)`
	expected := "rafecolton"
	actual := RemoteAccount("")
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestSha(t *testing.T) {
	runner.(*fakeRunner).sha = "abc123\n"
	actual := Sha("")
	expected := "abc123"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestBranch(t *testing.T) {
	runner.(*fakeRunner).branch = "asdf\n"
	actual := Branch("")
	expected := "asdf"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestBranchAlt(t *testing.T) {
	runner.(*fakeRunner).branch = "HEAD\n"
	runner.(*fakeRunner).branch2 = `  master
  move-mithril-to-quay
* update-loadbalancer-role
  using-mighril-from-quay-instead-of-docker-hub`
	actual := Branch("")
	expected := "master"
	if actual != expected {
		t.Errorf("expected %q, got %q", expected, actual)
	}
}

func TestCleanClean(t *testing.T) {
	if !IsClean("") {
		t.Errorf("expected cleanliness")
	}
}

func TestCleanDirty(t *testing.T) {
	runner.(*fakeRunner).clean = " 2 files changed, 139 insertions(+), 105 deletions(-)\n"
	if IsClean("") {
		t.Errorf("repo is dirty")
	}
}

func TestUpToDateDiverged(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "abc"
	runner.(*fakeRunner).upToDateRemote = "123"
	runner.(*fakeRunner).upToDateBase = "def"
	if UpToDate("") != StatusDiverged {
		t.Errorf("status should be StatusDiverged")
	}
}

func TestUpToDateUpToDate(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateRemote = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusUpToDate {
		t.Errorf("status should be StatusUpToDate")
	}
}

func TestUpToDateNeedToPush(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "f4c103a85141c59749ef24320a538ae7ed238909"
	runner.(*fakeRunner).upToDateRemote = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateBase = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusNeedToPush {
		t.Errorf("status should be StatusNeedToPush")
	}
}

func TestUpToDateNeedToPull(t *testing.T) {
	runner.(*fakeRunner).upToDateLocal = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	runner.(*fakeRunner).upToDateRemote = "f4c103a85141c59749ef24320a538ae7ed238909"
	runner.(*fakeRunner).upToDateBase = "50d1ab234ffa3df05162c8eae4dddef1d907faa8"
	if UpToDate("") != StatusNeedToPull {
		t.Errorf("status should be StatusNeedToPull")
	}
}

func TestTag(t *testing.T) {
	runner.(*fakeRunner).tag = "foo-tag"
	if Tag("") != "foo-tag" {
		t.Errorf("expected foo-tag, got %s", Tag(""))
	}
}

func TestStatusString(t *testing.T) {
	assertStatusString(StatusUpToDate, "StatusUpToDate", t)
	assertStatusString(StatusNeedToPull, "StatusNeedToPull", t)
	assertStatusString(StatusNeedToPush, "StatusNeedToPush", t)
	assertStatusString(StatusDiverged, "StatusDiverged", t)
}

func assertStatusString(status Status, str string, t *testing.T) {
	if status.String() != str {
		t.Errorf("expected " + str + ", got " + status.String())
	}
}
<|endoftext|>


<commit_before>package glhelpers

import (
	"log"
	"runtime"
	"testing"

	"github.com/go-gl/gl"
	"github.com/go-gl/glfw"
)

var main_thread_work = make(chan func())
var main_thread_work_done = make(chan bool)

func OnTheMainThread(setup, after func()) {
	main_thread_work <- setup
	main_thread_work <- after
	<-main_thread_work_done
}

func init() {
	go func() {
		runtime.LockOSThread()

		if err := glfw.Init(); err != nil {
			log.Panic("glfw Error:", err)
		}

		w, h := 400, 400
		err := glfw.OpenWindow(w, h, 0, 0, 0, 0, 0, 0, glfw.Windowed)
		if err != nil {
			log.Panic("Error:", err)
		}

		if gl.Init() != 0 {
			log.Panic("gl error")
		}

		glfw.SetWindowSizeCallback(Reshape)
		glfw.SwapBuffers()

		for {
			(<-main_thread_work)()
			glfw.SwapBuffers()
			(<-main_thread_work)()
			main_thread_work_done <- true
		}

	}()
}

func Reshape(width, height int) {
	gl.Viewport(0, 0, width, height)

	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	gl.Ortho(-1, 1, -1, 1, -1, 1)
	//gl.Ortho(-2.1, 6.1, -2.25*2, 2.1*2, -1, 1) // Y debug

	gl.MatrixMode(gl.MODELVIEW)
	gl.LoadIdentity()

	gl.ClearColor(0, 0, 0, 1)
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
}

// Draw a test pattern
func TestWindowCoords(t *testing.T) {
	OnTheMainThread(func() {
		w, h := GetViewportWH()
		With(WindowCoords{}, func() {
			// So that we draw in the middle of the pixel
			gl.Translated(0.5, 0.5, 0)

			stride := 1
			internal_n := 4
			for b := 0; b < w/2-internal_n*stride; b += stride {
				if b/stride%2 == 0 {
					gl.Color4f(1, 1, 1, 1)
				} else {
					gl.Color4f(1, 0, 0, 1)
				}
				With(Primitive{gl.LINE_LOOP}, func() {
					gl.Vertex2i(b, b)
					gl.Vertex2i(w-b, b)
					gl.Vertex2i(w-b, h-b)
					gl.Vertex2i(b, h-b)
				})
			}

			// Central white, green, blue checked pattern
			gl.PointSize(2)
			With(Primitive{gl.POINTS}, func() {
				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2-2, h/2-2)
				gl.Vertex2i(w/2+2, h/2+2)

				gl.Color4f(0, 1, 0, 1)
				gl.Vertex2i(w/2+2, h/2-2)
				gl.Vertex2i(w/2-2, h/2+2)

				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2, h/2)
			})

			// Blue horizontal line to show
			With(Primitive{gl.LINE_LOOP}, func() {
				gl.Color4f(0, 0, 1, 1)
				gl.Vertex2i(0, h/2-4)
				gl.Vertex2i(w, h/2-4)

				gl.Vertex2i(w/2-4, 0)
				gl.Vertex2i(w/2-4, h)
			})

			// Remove top left and top right pixel
			gl.PointSize(1)
			gl.Color4f(0, 0, 0, 1)
			With(Primitive{gl.POINTS}, func() {
				gl.Vertex2i(0, 0)
				gl.Vertex2i(w, 0)
			})
		})
	}, func() {
		CaptureToPng("TestWindowCoords.png")
	})
}
<commit_msg>test: Shrink window to 100x100<commit_after>package glhelpers

import (
	"log"
	"runtime"
	"testing"

	"github.com/go-gl/gl"
	"github.com/go-gl/glfw"
)

var main_thread_work = make(chan func())
var main_thread_work_done = make(chan bool)

func OnTheMainThread(setup, after func()) {
	main_thread_work <- setup
	main_thread_work <- after
	<-main_thread_work_done
}

func init() {
	go func() {
		runtime.LockOSThread()

		if err := glfw.Init(); err != nil {
			log.Panic("glfw Error:", err)
		}

		w, h := 100, 100
		err := glfw.OpenWindow(w, h, 0, 0, 0, 0, 0, 0, glfw.Windowed)
		if err != nil {
			log.Panic("Error:", err)
		}

		if gl.Init() != 0 {
			log.Panic("gl error")
		}

		glfw.SetWindowSizeCallback(Reshape)
		glfw.SwapBuffers()

		for {
			(<-main_thread_work)()
			glfw.SwapBuffers()
			(<-main_thread_work)()
			main_thread_work_done <- true
		}

	}()
}

func Reshape(width, height int) {
	gl.Viewport(0, 0, width, height)

	gl.MatrixMode(gl.PROJECTION)
	gl.LoadIdentity()
	gl.Ortho(-1, 1, -1, 1, -1, 1)
	//gl.Ortho(-2.1, 6.1, -2.25*2, 2.1*2, -1, 1) // Y debug

	gl.MatrixMode(gl.MODELVIEW)
	gl.LoadIdentity()

	gl.ClearColor(0, 0, 0, 1)
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
}

// Draw a test pattern
func TestWindowCoords(t *testing.T) {
	OnTheMainThread(func() {
		w, h := GetViewportWH()
		With(WindowCoords{}, func() {
			// So that we draw in the middle of the pixel
			gl.Translated(0.5, 0.5, 0)

			stride := 1
			internal_n := 4
			for b := 0; b < w/2-internal_n*stride; b += stride {
				if b/stride%2 == 0 {
					gl.Color4f(1, 1, 1, 1)
				} else {
					gl.Color4f(1, 0, 0, 1)
				}
				With(Primitive{gl.LINE_LOOP}, func() {
					gl.Vertex2i(b, b)
					gl.Vertex2i(w-b, b)
					gl.Vertex2i(w-b, h-b)
					gl.Vertex2i(b, h-b)
				})
			}

			// Central white, green, blue checked pattern
			gl.PointSize(2)
			With(Primitive{gl.POINTS}, func() {
				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2-2, h/2-2)
				gl.Vertex2i(w/2+2, h/2+2)

				gl.Color4f(0, 1, 0, 1)
				gl.Vertex2i(w/2+2, h/2-2)
				gl.Vertex2i(w/2-2, h/2+2)

				gl.Color4f(1, 1, 1, 1)
				gl.Vertex2i(w/2, h/2)
			})

			// Blue horizontal line to show
			With(Primitive{gl.LINE_LOOP}, func() {
				gl.Color4f(0, 0, 1, 1)
				gl.Vertex2i(0, h/2-4)
				gl.Vertex2i(w, h/2-4)

				gl.Vertex2i(w/2-4, 0)
				gl.Vertex2i(w/2-4, h)
			})

			// Remove top left and top right pixel
			gl.PointSize(1)
			gl.Color4f(0, 0, 0, 1)
			With(Primitive{gl.POINTS}, func() {
				gl.Vertex2i(0, 0)
				gl.Vertex2i(w, 0)
			})
		})
	}, func() {
		CaptureToPng("TestWindowCoords.png")
	})
}
<|endoftext|>


<commit_before>package main

import (
	_ "Gin_API_Framework/models"
	_ "Gin_API_Framework/web-routers"
	"github.com/astaxie/beego"
	"fmt"
    "path"
    "runtime"
)

func callerSourcePath() string {
    _, callerPath, _, _ := runtime.Caller(1)
    return path.Dir(callerPath)
}

func main() {

    curpath := callerSourcePath()
    static_path := path.Join(curpath, "/", "static")
    template_path := path.Join(curpath, "/web-controllers/templates")

	beego.SetStaticPath("/static",static_path)
	beego.SetViewsPath(template_path)

    fmt.Println(beego.AppConfig.String("HttpPort"))
	fmt.Println("[static path]" , static_path)
	fmt.Println("[template path]" , template_path)

	beego.Run(":8000")
}<commit_msg>fix beego config<commit_after>package main

import (
	_ "Gin_API_Framework/models"
	_ "Gin_API_Framework/web-routers"
	"github.com/astaxie/beego"
	"fmt"
    "path"
    "runtime"
)

func callerSourcePath() string {
    _, callerPath, _, _ := runtime.Caller(1)
    return path.Dir(callerPath)
}

func main() {

    curpath := callerSourcePath()
    static_path := path.Join(curpath, "/", "static")
    template_path := path.Join(curpath, "/web-controllers/templates")

    beego.LoadAppConfig("ini", path.Join(curpath, "/conf/app.conf"))
	beego.SetStaticPath("/static",static_path)
	beego.SetViewsPath(template_path)

    fmt.Println(beego.AppConfig.Int("HttpPort"))
	fmt.Println("[static path]" , static_path)
	fmt.Println("[template path]" , template_path)

	beego.Run()
}<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"

	"github.com/mvdan/gibot/site/gitlab"

	api "github.com/xanzy/go-gitlab"
)

const (
	listenAddr = ":9990"
	listenPath = "/webhooks/gitlab"
)

func webhookListen() {
	http.HandleFunc(listenPath, gitlabHandler)
	log.Printf("Receiving webhooks on %s", listenPath)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

func gitlabHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	event := strings.TrimSpace(r.Header.Get("X-Gitlab-Event"))
	var err error
	switch event {
	case "Push Hook":
		err = onPush(r.Body)
	case "Issue Hook":
		err = onIssue(r.Body)
	case "Merge Request Hook":
		err = onMergeRequest(r.Body)
	default:
		log.Printf("Webhook event we don't handle: %s", event)
	}
	if err != nil {
		log.Print(err)
	}
}

var headBranch = regexp.MustCompile(`^refs/heads/(.*)$`)

func getBranch(ref string) string {
	if s := headBranch.FindStringSubmatch(ref); s != nil {
		return s[1]
	}
	log.Printf("Unknown branch ref format: %s", ref)
	return ""
}

func getRepo(apiRepo *api.Repository) (*gitlab.Repo, error) {
	repo := repos[apiRepo.Homepage]
	if repo == nil {
		return nil, fmt.Errorf("unknown repo: %s", apiRepo.Homepage)
	}
	return repo, nil
}

var mergeMessage = regexp.MustCompile(`^[Mm]erge `)

func onPush(body io.Reader) error {
	var pe api.PushEvent
	if err := json.NewDecoder(body).Decode(&pe); err != nil {
		return fmt.Errorf("invalid push event body: %v", err)
	}
	repo, err := getRepo(pe.Repository)
	if err != nil {
		return err
	}
	user, err := repo.GetUser(pe.UserID)
	if err != nil {
		return fmt.Errorf("unknown user: %v", err)
	}
	branch := getBranch(pe.Ref)
	if branch == "" {
		return fmt.Errorf("no branch")
	}
	commits := make([]*api.Commit, 0, len(pe.Commits))
	for _, c := range pe.Commits {
		if mergeMessage.MatchString(c.Message) {
			continue
		}
		commits = append(commits, c)
	}
	var message string
	switch len(commits) {
	case 0:
		return fmt.Errorf("empty commits")
	case 1:
		// Message here means Title, how useful.
		title := gitlab.ShortTitle(commits[0].Message)
		short := gitlab.ShortCommit(commits[0].ID)
		message = fmt.Sprintf("%s pushed to %s: %s - %s",
			user.Username, branch, title, repo.CommitURL(short))
	default:
		url := repo.CompareURL(pe.Before, pe.After)
		message = fmt.Sprintf("%s pushed %d commits to %s - %s",
			user.Username, len(commits), branch, url)
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}

func onIssue(body io.Reader) error {
	var ie api.IssueEvent
	if err := json.NewDecoder(body).Decode(&ie); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := ie.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened #%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "close", "reopen", "update":
		return nil
	default:
		return fmt.Errorf("issue action we don't handle: %s", attrs.Action)
	}
	repo, err := getRepo(ie.Repository)
	if err != nil {
		return err
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}

func onMergeRequest(body io.Reader) error {
	var me api.MergeEvent
	if err := json.NewDecoder(body).Decode(&me); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := me.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened !%d: %s - %s",
			me.User.Username, attrs.Iid, title, attrs.URL)
	case "merge":
		message = fmt.Sprintf("%s merged !%d: %s - %s",
			me.User.Username, attrs.Iid, title, attrs.URL)
	case "close", "reopen", "update":
		return nil
	default:
		return fmt.Errorf("merge action we don't handle: %s", attrs.Action)
	}
	repo, err := getRepo(me.Repository)
	if err != nil {
		return err
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}
<commit_msg>Don't alert on MR merge events<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"

	"github.com/mvdan/gibot/site/gitlab"

	api "github.com/xanzy/go-gitlab"
)

const (
	listenAddr = ":9990"
	listenPath = "/webhooks/gitlab"
)

func webhookListen() {
	http.HandleFunc(listenPath, gitlabHandler)
	log.Printf("Receiving webhooks on %s", listenPath)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

func gitlabHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	event := strings.TrimSpace(r.Header.Get("X-Gitlab-Event"))
	var err error
	switch event {
	case "Push Hook":
		err = onPush(r.Body)
	case "Issue Hook":
		err = onIssue(r.Body)
	case "Merge Request Hook":
		err = onMergeRequest(r.Body)
	default:
		log.Printf("Webhook event we don't handle: %s", event)
	}
	if err != nil {
		log.Print(err)
	}
}

var headBranch = regexp.MustCompile(`^refs/heads/(.*)$`)

func getBranch(ref string) string {
	if s := headBranch.FindStringSubmatch(ref); s != nil {
		return s[1]
	}
	log.Printf("Unknown branch ref format: %s", ref)
	return ""
}

func getRepo(apiRepo *api.Repository) (*gitlab.Repo, error) {
	repo := repos[apiRepo.Homepage]
	if repo == nil {
		return nil, fmt.Errorf("unknown repo: %s", apiRepo.Homepage)
	}
	return repo, nil
}

var mergeMessage = regexp.MustCompile(`^[Mm]erge `)

func onPush(body io.Reader) error {
	var pe api.PushEvent
	if err := json.NewDecoder(body).Decode(&pe); err != nil {
		return fmt.Errorf("invalid push event body: %v", err)
	}
	repo, err := getRepo(pe.Repository)
	if err != nil {
		return err
	}
	user, err := repo.GetUser(pe.UserID)
	if err != nil {
		return fmt.Errorf("unknown user: %v", err)
	}
	branch := getBranch(pe.Ref)
	if branch == "" {
		return fmt.Errorf("no branch")
	}
	commits := make([]*api.Commit, 0, len(pe.Commits))
	for _, c := range pe.Commits {
		if mergeMessage.MatchString(c.Message) {
			continue
		}
		commits = append(commits, c)
	}
	var message string
	switch len(commits) {
	case 0:
		return fmt.Errorf("empty commits")
	case 1:
		// Message here means Title, how useful.
		title := gitlab.ShortTitle(commits[0].Message)
		short := gitlab.ShortCommit(commits[0].ID)
		message = fmt.Sprintf("%s pushed to %s: %s - %s",
			user.Username, branch, title, repo.CommitURL(short))
	default:
		url := repo.CompareURL(pe.Before, pe.After)
		message = fmt.Sprintf("%s pushed %d commits to %s - %s",
			user.Username, len(commits), branch, url)
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}

func onIssue(body io.Reader) error {
	var ie api.IssueEvent
	if err := json.NewDecoder(body).Decode(&ie); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := ie.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened #%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "close", "reopen", "update":
		return nil
	default:
		return fmt.Errorf("issue action we don't handle: %s", attrs.Action)
	}
	repo, err := getRepo(ie.Repository)
	if err != nil {
		return err
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}

func onMergeRequest(body io.Reader) error {
	var me api.MergeEvent
	if err := json.NewDecoder(body).Decode(&me); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := me.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened !%d: %s - %s",
			me.User.Username, attrs.Iid, title, attrs.URL)
	case "close", "reopen", "update", "merge":
		return nil
	default:
		return fmt.Errorf("merge action we don't handle: %s", attrs.Action)
	}
	repo, err := getRepo(me.Repository)
	if err != nil {
		return err
	}
	sendNotices(config.Feeds, repo.Name, message)
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"regexp"

	"github.com/mvdan/gibot/site/gitlab"
)

const listenAddr = ":9990"

func webhookListen() {
	for _, repo := range repos {
		listenRepo(repo)
	}

	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

func listenRepo(repo *gitlab.Repo) {
	path := fmt.Sprintf("/webhooks/gitlab/%s", repo.Name)
	http.HandleFunc(path, gitlabHandler(repo.Name))
	log.Printf("Receiving webhooks for %s on %s%s", repo.Name, listenAddr, path)
}

func toInt(v interface{}) int {
	i, ok := v.(float64)
	if !ok {
		return 0
	}
	return int(i)
}

func toStr(v interface{}) string {
	s, ok := v.(string)
	if !ok {
		return ""
	}
	return s
}

func toSlice(v interface{}) []interface{} {
	l, ok := v.([]interface{})
	if !ok {
		return []interface{}{}
	}
	return l
}

func toMap(v interface{}) map[string]interface{} {
	m, ok := v.(map[string]interface{})
	if !ok {
		return map[string]interface{}{}
	}
	return m
}

func gitlabHandler(reponame string) func(http.ResponseWriter, *http.Request) {
	repo, e := repos[reponame]
	if !e {
		panic("unknown repo")
	}
	return func(w http.ResponseWriter, r *http.Request) {
		decoder := json.NewDecoder(r.Body)
		m := make(map[string]interface{})
		if err := decoder.Decode(&m); err != nil {
			log.Printf("Error decoding webhook data: %v", err)
			return
		}
		kind := toStr(m["object_kind"])
		switch kind {
		case "push":
			onPush(repo, m)
		case "issue":
			onIssue(repo, m)
		case "merge_request":
			onMergeRequest(repo, m)
		case "tag_push":
		case "note":
		default:
			log.Printf("Webhook event we don't handle: %s", kind)
			return
		}
	}
}

var headBranch = regexp.MustCompile(`^refs/heads/(.*)$`)

func getBranch(ref string) string {
	if s := headBranch.FindStringSubmatch(ref); s != nil {
		return s[1]
	}
	log.Printf("Unknown branch ref format: %s", ref)
	return ""
}

func onPush(r *gitlab.Repo, m map[string]interface{}) {
	userID := toInt(m["user_id"])
	user, err := r.GetUser(userID)
	if err != nil {
		log.Printf("Unknown user: %v", err)
		return
	}
	username := user.Username
	branch := getBranch(toStr(m["ref"]))
	if branch == "" {
		return
	}
	count := toInt(m["total_commits_count"])
	var message string
	if count > 1 {
		before := toStr(m["before"])
		after := toStr(m["after"])
		url := r.CompareURL(before, after)
		message = fmt.Sprintf("%s pushed %d commits to %s - %s", username, count, branch, url)
	} else {
		commits := toSlice(m["commits"])
		if len(commits) == 0 {
			log.Printf("Empty commits")
			return
		}
		commit := toMap(commits[0])
		title := gitlab.ShortTitle(toStr(commit["message"]))
		sha := toStr(commit["id"])
		short := gitlab.ShortCommit(sha)
		url := r.CommitURL(short)
		message = fmt.Sprintf("%s pushed to %s: %s - %s", username, branch, title, url)
	}
	sendNotices(config.Feeds, r.Name, message)
}

func onIssue(r *gitlab.Repo, m map[string]interface{}) {
	user := toMap(m["user"])
	username := toStr(user["username"])
	attrs := toMap(m["object_attributes"])
	iid := toInt(attrs["iid"])
	title := gitlab.ShortTitle(toStr(attrs["title"]))
	url := toStr(attrs["url"])
	action := toStr(attrs["action"])
	var message string
	switch action {
	case "open":
		message = fmt.Sprintf("%s opened #%d: %s - %s", username, iid, title, url)
	case "close":
		message = fmt.Sprintf("%s closed #%d: %s - %s", username, iid, title, url)
	case "reopen":
		message = fmt.Sprintf("%s reopened #%d: %s - %s", username, iid, title, url)
	case "update":
		return
	default:
		log.Printf("Issue action we don't handle: %s", action)
		return
	}
	sendNotices(config.Feeds, r.Name, message)
}

func onMergeRequest(r *gitlab.Repo, m map[string]interface{}) {
	user := toMap(m["user"])
	username := toStr(user["username"])
	attrs := toMap(m["object_attributes"])
	iid := toInt(attrs["iid"])
	title := gitlab.ShortTitle(toStr(attrs["title"]))
	url := toStr(attrs["url"])
	action := toStr(attrs["action"])
	var message string
	switch action {
	case "open":
		message = fmt.Sprintf("%s opened !%d: %s - %s", username, iid, title, url)
	case "merge":
		message = fmt.Sprintf("%s merged !%d: %s - %s", username, iid, title, url)
	case "close", "reopen", "update":
		return
	default:
		log.Printf("Merge Request action we don't handle: %s", action)
		return
	}
	sendNotices(config.Feeds, r.Name, message)
}
<commit_msg>Switch to safer, saner struct apis<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"

	"github.com/mvdan/gibot/site/gitlab"

	api "github.com/xanzy/go-gitlab"
)

const listenAddr = ":9990"

func webhookListen() {
	for _, repo := range repos {
		listenRepo(repo)
	}

	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

func listenRepo(repo *gitlab.Repo) {
	path := fmt.Sprintf("/webhooks/gitlab/%s", repo.Name)
	http.HandleFunc(path, gitlabHandler(repo.Name))
	log.Printf("Receiving webhooks for %s on %s%s", repo.Name, listenAddr, path)
}

func gitlabHandler(reponame string) func(http.ResponseWriter, *http.Request) {
	repo, e := repos[reponame]
	if !e {
		panic("unknown repo")
	}
	return func(w http.ResponseWriter, r *http.Request) {
		event := strings.TrimSpace(r.Header.Get("X-Gitlab-Event"))
		var err error
		switch event {
		case "Push Hook":
			err = onPush(repo, r.Body)
		case "Issue Hook":
			err = onIssue(repo, r.Body)
		case "Merge Request Hook":
			err = onMergeRequest(repo, r.Body)
		default:
			log.Printf("Webhook event we don't handle: %s", event)
		}
		if err != nil {
			log.Print(err)
		}
	}
}

var headBranch = regexp.MustCompile(`^refs/heads/(.*)$`)

func getBranch(ref string) string {
	if s := headBranch.FindStringSubmatch(ref); s != nil {
		return s[1]
	}
	log.Printf("Unknown branch ref format: %s", ref)
	return ""
}

func onPush(r *gitlab.Repo, rc io.ReadCloser) error {
	var pe api.PushEvent
	if err := json.NewDecoder(rc).Decode(&pe); err != nil {
		return fmt.Errorf("invalid push event body: %v", err)
	}
	user, err := r.GetUser(pe.UserID)
	if err != nil {
		return fmt.Errorf("unknown user: %v", err)
	}
	branch := getBranch(pe.Ref)
	if branch == "" {
		return fmt.Errorf("no branch")
	}
	var message string
	if pe.TotalCommitsCount > 1 {
		url := r.CompareURL(pe.Before, pe.After)
		message = fmt.Sprintf("%s pushed %d commits to %s - %s",
			user.Username, pe.TotalCommitsCount, branch, url)
	} else {
		if len(pe.Commits) == 0 {
			return fmt.Errorf("empty commits")
		}
		commit := pe.Commits[0]
		title := gitlab.ShortTitle(commit.Title)
		short := gitlab.ShortCommit(commit.ID)
		message = fmt.Sprintf("%s pushed to %s: %s - %s",
			user.Username, branch, title, r.CommitURL(short))
	}
	sendNotices(config.Feeds, r.Name, message)
	return nil
}

func onIssue(r *gitlab.Repo, rc io.ReadCloser) error {
	var ie api.IssueEvent
	if err := json.NewDecoder(rc).Decode(&ie); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := ie.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened #%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "close":
		message = fmt.Sprintf("%s closed #%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "reopen":
		message = fmt.Sprintf("%s reopened #%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "update":
		return nil
	default:
		return fmt.Errorf("issue action we don't handle: %s", attrs.Action)
	}
	sendNotices(config.Feeds, r.Name, message)
	return nil
}

func onMergeRequest(r *gitlab.Repo, rc io.ReadCloser) error {
	var ie api.MergeEvent
	if err := json.NewDecoder(rc).Decode(&ie); err != nil {
		return fmt.Errorf("invalid issue event body: %v", err)
	}
	attrs := ie.ObjectAttributes
	title := gitlab.ShortTitle(attrs.Title)
	var message string
	switch attrs.Action {
	case "open":
		message = fmt.Sprintf("%s opened !%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "merge":
		message = fmt.Sprintf("%s merged !%d: %s - %s",
			ie.User.Username, attrs.Iid, title, attrs.URL)
	case "close", "reopen", "update":
		return nil
	default:
		return fmt.Errorf("merge action we don't handle: %s", attrs.Action)
	}
	sendNotices(config.Feeds, r.Name, message)
	return nil
}
<|endoftext|>