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

package buffered

import (
	"fmt"
	"image"
	"image/color"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/mipmap"
	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

type Image struct {
	img    *mipmap.Mipmap
	width  int
	height int

	hasFill   bool
	fillColor color.RGBA

	pixels               []byte
	needsToResolvePixels bool
}

func BeginFrame() error {
	if err := mipmap.BeginFrame(); err != nil {
		return err
	}
	return flushDelayedCommands()
}

func EndFrame() error {
	return mipmap.EndFrame()
}

func NewImage(width, height int, volatile bool) *Image {
	i := &Image{}
	i.initialize(width, height, volatile)
	return i
}

func (i *Image) initialize(width, height int, volatile bool) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.initialize(width, height, volatile)
			return nil
		})
		return
	}
	i.img = mipmap.New(width, height, volatile)
	i.width = width
	i.height = height
}

func NewScreenFramebufferImage(width, height int) *Image {
	i := &Image{}
	i.initializeAsScreenFramebuffer(width, height)
	return i
}

func (i *Image) initializeAsScreenFramebuffer(width, height int) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.initializeAsScreenFramebuffer(width, height)
			return nil
		})
		return
	}

	i.img = mipmap.NewScreenFramebufferMipmap(width, height)
	i.width = width
	i.height = height
}

func (i *Image) invalidatePendingPixels() {
	i.pixels = nil
	i.needsToResolvePixels = false
	i.hasFill = false
}

func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
	if i.needsToResolvePixels && i.hasFill {
		panic("buffered: needsToResolvePixels and hasFill must not be true at the same time")
	}
	if i.needsToResolvePixels {
		i.img.ReplacePixels(i.pixels)
		if !keepPendingPixels {
			i.pixels = nil
		}
		i.needsToResolvePixels = false
	}
	i.resolvePendingFill()
}

func (i *Image) resolvePendingFill() {
	if !i.hasFill {
		return
	}
	i.img.Fill(i.fillColor)
	i.hasFill = false
}

func (i *Image) MarkDisposed() {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.MarkDisposed()
			return nil
		})
		return
	}
	i.invalidatePendingPixels()
	i.img.MarkDisposed()
}

func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
	if needsToDelayCommands() {
		panic("buffered: the command queue is not available yet at At")
	}

	if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) {
		return nil, fmt.Errorf("buffered: out of range")
	}

	pix = make([]byte, 4*width*height)

	// If there are pixels or pending fillling that needs to be resolved, use this rather than resolving.
	// Resolving them needs to access GPU and is expensive (#1137).
	if img.hasFill {
		for i := 0; i < len(pix)/4; i++ {
			pix[4*i] = img.fillColor.R
			pix[4*i+1] = img.fillColor.G
			pix[4*i+2] = img.fillColor.B
			pix[4*i+3] = img.fillColor.A
		}
		return pix, nil
	}

	if img.pixels == nil {
		pix, err := img.img.Pixels(0, 0, img.width, img.height)
		if err != nil {
			return nil, err
		}
		img.pixels = pix
	}

	for j := 0; j < height; j++ {
		copy(pix[4*j*width:4*(j+1)*width], img.pixels[4*((j+y)*img.width+x):])
	}
	return pix, nil
}

func (i *Image) Dump(name string, blackbg bool) error {
	if needsToDelayCommands() {
		panic("buffered: the command queue is not available yet at Dump")
	}
	return i.img.Dump(name, blackbg)
}

func (i *Image) Fill(clr color.RGBA) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.Fill(clr)
			return nil
		})
		return
	}

	// Defer filling the image so that successive fillings will be merged into one (#1134).
	i.invalidatePendingPixels()
	i.fillColor = clr
	i.hasFill = true
}

func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
	if l := 4 * width * height; len(pix) != l {
		panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
	}

	if needsToDelayCommands() {
		copied := make([]byte, len(pix))
		copy(copied, pix)
		delayedCommands = append(delayedCommands, func() error {
			i.ReplacePixels(copied, x, y, width, height)
			return nil
		})
		return nil
	}

	i.resolvePendingFill()

	if x == 0 && y == 0 && width == i.width && height == i.height {
		i.invalidatePendingPixels()
		i.img.ReplacePixels(pix)
		return nil
	}

	// TODO: Can we use (*restorable.Image).ReplacePixels?
	if i.pixels == nil {
		pix, err := i.img.Pixels(0, 0, i.width, i.height)
		if err != nil {
			return err
		}
		i.pixels = pix
	}
	i.replacePendingPixels(pix, x, y, width, height)
	return nil
}

func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
	for j := 0; j < height; j++ {
		copy(i.pixels[4*((j+y)*i.width+x):], pix[4*j*width:4*(j+1)*width])
	}
	i.needsToResolvePixels = true
}

func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty float32, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	if i == src {
		panic("buffered: Image.DrawImage: src must be different from the receiver")
	}

	g := mipmap.GeoM{
		A:  a,
		B:  b,
		C:  c,
		D:  d,
		Tx: tx,
		Ty: ty,
	}

	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.drawImage(src, bounds, g, colorm, mode, filter)
			return nil
		})
		return
	}

	i.drawImage(src, bounds, g, colorm, mode, filter)
}

func (i *Image) drawImage(src *Image, bounds image.Rectangle, g mipmap.GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	src.resolvePendingPixels(true)
	i.resolvePendingPixels(false)
	i.img.DrawImage(src.img, bounds, g, colorm, mode, filter)
}

// DrawTriangles draws the src image with the given vertices.
//
// Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, shader *Shader, uniforms []interface{}) {
	var srcs []*Image
	if src != nil {
		srcs = append(srcs, src)
	}
	for _, u := range uniforms {
		if src, ok := u.(*Image); ok {
			srcs = append(srcs, src)
		}
	}

	for _, src := range srcs {
		if i == src {
			panic("buffered: Image.DrawTriangles: src must be different from the receiver")
		}
	}

	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			// Arguments are not copied. Copying is the caller's responsibility.
			i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms)
			return nil
		})
		return
	}

	for _, src := range srcs {
		src.resolvePendingPixels(true)
	}
	i.resolvePendingPixels(false)

	var s *mipmap.Shader
	if shader != nil {
		s = shader.shader
	}
	us := make([]interface{}, len(uniforms))
	for k, v := range uniforms {
		switch v := v.(type) {
		case *Image:
			i.resolvePendingPixels(true)
			us[k] = v.img
		default:
			us[k] = v
		}
	}

	var srcImg *mipmap.Mipmap
	if src != nil {
		srcImg = src.img
	}
	i.img.DrawTriangles(srcImg, vertices, indices, colorm, mode, filter, address, s, us)
}

type Shader struct {
	shader *mipmap.Shader
}

func NewShader(program *shaderir.Program) *Shader {
	return &Shader{
		shader: mipmap.NewShader(program),
	}
}

func (s *Shader) MarkDisposed() {
	s.shader.MarkDisposed()
	s.shader = nil
}
<commit_msg>buffered: Skip filling operation if possible<commit_after>// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package buffered

import (
	"fmt"
	"image"
	"image/color"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/mipmap"
	"github.com/hajimehoshi/ebiten/internal/shaderir"
)

type Image struct {
	img    *mipmap.Mipmap
	width  int
	height int

	hasFill   bool
	fillColor color.RGBA

	pixels               []byte
	needsToResolvePixels bool
}

func BeginFrame() error {
	if err := mipmap.BeginFrame(); err != nil {
		return err
	}
	return flushDelayedCommands()
}

func EndFrame() error {
	return mipmap.EndFrame()
}

func NewImage(width, height int, volatile bool) *Image {
	i := &Image{}
	i.initialize(width, height, volatile)
	return i
}

func (i *Image) initialize(width, height int, volatile bool) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.initialize(width, height, volatile)
			return nil
		})
		return
	}
	i.img = mipmap.New(width, height, volatile)
	i.width = width
	i.height = height
}

func NewScreenFramebufferImage(width, height int) *Image {
	i := &Image{}
	i.initializeAsScreenFramebuffer(width, height)
	return i
}

func (i *Image) initializeAsScreenFramebuffer(width, height int) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.initializeAsScreenFramebuffer(width, height)
			return nil
		})
		return
	}

	i.img = mipmap.NewScreenFramebufferMipmap(width, height)
	i.width = width
	i.height = height
}

func (i *Image) invalidatePendingPixels() {
	i.pixels = nil
	i.needsToResolvePixels = false
	i.hasFill = false
}

func (i *Image) resolvePendingPixels(keepPendingPixels bool) {
	if i.needsToResolvePixels && i.hasFill {
		panic("buffered: needsToResolvePixels and hasFill must not be true at the same time")
	}
	if i.needsToResolvePixels {
		i.img.ReplacePixels(i.pixels)
		if !keepPendingPixels {
			i.pixels = nil
		}
		i.needsToResolvePixels = false
	}
	i.resolvePendingFill()
}

func (i *Image) resolvePendingFill() {
	if !i.hasFill {
		return
	}
	i.img.Fill(i.fillColor)
	i.hasFill = false
}

func (i *Image) MarkDisposed() {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.MarkDisposed()
			return nil
		})
		return
	}
	i.invalidatePendingPixels()
	i.img.MarkDisposed()
}

func (img *Image) Pixels(x, y, width, height int) (pix []byte, err error) {
	if needsToDelayCommands() {
		panic("buffered: the command queue is not available yet at At")
	}

	if !image.Rect(x, y, x+width, y+height).In(image.Rect(0, 0, img.width, img.height)) {
		return nil, fmt.Errorf("buffered: out of range")
	}

	pix = make([]byte, 4*width*height)

	// If there are pixels or pending fillling that needs to be resolved, use this rather than resolving.
	// Resolving them needs to access GPU and is expensive (#1137).
	if img.hasFill {
		for i := 0; i < len(pix)/4; i++ {
			pix[4*i] = img.fillColor.R
			pix[4*i+1] = img.fillColor.G
			pix[4*i+2] = img.fillColor.B
			pix[4*i+3] = img.fillColor.A
		}
		return pix, nil
	}

	if img.pixels == nil {
		pix, err := img.img.Pixels(0, 0, img.width, img.height)
		if err != nil {
			return nil, err
		}
		img.pixels = pix
	}

	for j := 0; j < height; j++ {
		copy(pix[4*j*width:4*(j+1)*width], img.pixels[4*((j+y)*img.width+x):])
	}
	return pix, nil
}

func (i *Image) Dump(name string, blackbg bool) error {
	if needsToDelayCommands() {
		panic("buffered: the command queue is not available yet at Dump")
	}
	return i.img.Dump(name, blackbg)
}

func (i *Image) Fill(clr color.RGBA) {
	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.Fill(clr)
			return nil
		})
		return
	}

	// Defer filling the image so that successive fillings will be merged into one (#1134).
	i.invalidatePendingPixels()
	i.fillColor = clr
	i.hasFill = true
}

func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
	if l := 4 * width * height; len(pix) != l {
		panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
	}

	if needsToDelayCommands() {
		copied := make([]byte, len(pix))
		copy(copied, pix)
		delayedCommands = append(delayedCommands, func() error {
			i.ReplacePixels(copied, x, y, width, height)
			return nil
		})
		return nil
	}

	if x == 0 && y == 0 && width == i.width && height == i.height {
		i.invalidatePendingPixels()
		i.img.ReplacePixels(pix)
		return nil
	}

	i.resolvePendingFill()

	// TODO: Can we use (*restorable.Image).ReplacePixels?
	if i.pixels == nil {
		pix, err := i.img.Pixels(0, 0, i.width, i.height)
		if err != nil {
			return err
		}
		i.pixels = pix
	}
	i.replacePendingPixels(pix, x, y, width, height)
	return nil
}

func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
	for j := 0; j < height; j++ {
		copy(i.pixels[4*((j+y)*i.width+x):], pix[4*j*width:4*(j+1)*width])
	}
	i.needsToResolvePixels = true
}

func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty float32, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	if i == src {
		panic("buffered: Image.DrawImage: src must be different from the receiver")
	}

	g := mipmap.GeoM{
		A:  a,
		B:  b,
		C:  c,
		D:  d,
		Tx: tx,
		Ty: ty,
	}

	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			i.drawImage(src, bounds, g, colorm, mode, filter)
			return nil
		})
		return
	}

	i.drawImage(src, bounds, g, colorm, mode, filter)
}

func (i *Image) drawImage(src *Image, bounds image.Rectangle, g mipmap.GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
	src.resolvePendingPixels(true)
	i.resolvePendingPixels(false)
	i.img.DrawImage(src.img, bounds, g, colorm, mode, filter)
}

// DrawTriangles draws the src image with the given vertices.
//
// Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(src *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, shader *Shader, uniforms []interface{}) {
	var srcs []*Image
	if src != nil {
		srcs = append(srcs, src)
	}
	for _, u := range uniforms {
		if src, ok := u.(*Image); ok {
			srcs = append(srcs, src)
		}
	}

	for _, src := range srcs {
		if i == src {
			panic("buffered: Image.DrawTriangles: src must be different from the receiver")
		}
	}

	if needsToDelayCommands() {
		delayedCommands = append(delayedCommands, func() error {
			// Arguments are not copied. Copying is the caller's responsibility.
			i.DrawTriangles(src, vertices, indices, colorm, mode, filter, address, shader, uniforms)
			return nil
		})
		return
	}

	for _, src := range srcs {
		src.resolvePendingPixels(true)
	}
	i.resolvePendingPixels(false)

	var s *mipmap.Shader
	if shader != nil {
		s = shader.shader
	}
	us := make([]interface{}, len(uniforms))
	for k, v := range uniforms {
		switch v := v.(type) {
		case *Image:
			i.resolvePendingPixels(true)
			us[k] = v.img
		default:
			us[k] = v
		}
	}

	var srcImg *mipmap.Mipmap
	if src != nil {
		srcImg = src.img
	}
	i.img.DrawTriangles(srcImg, vertices, indices, colorm, mode, filter, address, s, us)
}

type Shader struct {
	shader *mipmap.Shader
}

func NewShader(program *shaderir.Program) *Shader {
	return &Shader{
		shader: mipmap.NewShader(program),
	}
}

func (s *Shader) MarkDisposed() {
	s.shader.MarkDisposed()
	s.shader = nil
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package tree

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/issue9/assert"
	ts "github.com/issue9/mux/internal/tree/syntax"
)

func newSegments(a *assert.Assertion, pattern string) []*ts.Segment {
	ss, err := ts.Parse(pattern)
	a.NotError(err).NotNil(ss)

	return ss
}

func TestNode_add_remove(t *testing.T) {
	a := assert.New(t)
	node := &node{}

	a.NotError(node.add(newSegments(a, "/"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}/author"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/1/author"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}/{author:\\w+}/profile"), buildHandler(1), http.MethodGet))

	// / 和 /posts/ 以及 /posts/1/author
	a.Equal(len(node.children), 3)
	node.print(0)

	node.remove(newSegments(a, "/posts/1/author"), http.MethodGet)
	// / 和 /posts/
	a.Equal(len(node.children), 2)

	node.remove(newSegments(a, "/posts/{id}/author"), http.MethodGet)
	// / 和 /posts/
	a.Equal(len(node.children), 2)
}

func TestNode_match_1(t *testing.T) {
	a := assert.New(t)
	node := &node{}

	// 添加路由项
	a.NotError(node.add(newSegments(a, "/"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}"), buildHandler(2), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}/author"), buildHandler(3), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/1/author"), buildHandler(4), http.MethodGet))

	test := func(path, method string, code int) {
		n := node.match(path)
		a.NotNil(n)
		a.NotNil(n.handlers)
		h := n.handlers.handler(method)
		a.NotNil(h)

		w := httptest.NewRecorder()
		r := httptest.NewRequest(method, path, nil)
		h.ServeHTTP(w, r)
		a.Equal(w.Code, code)
	}

	test("/", http.MethodGet, 1)
	test("/posts/1", http.MethodGet, 2)
	test("/posts/2", http.MethodGet, 2)
	test("/posts/2/author", http.MethodGet, 3)
	test("/posts/1/author", http.MethodGet, 4)
}

func TestNode_getParents(t *testing.T) {
	a := assert.New(t)

	n1 := &node{
		children: make([]*node, 0, 1),
	}
	n2 := &node{
		children: make([]*node, 0, 1),
		parent:   n1,
	}
	n3 := &node{
		parent: n2,
	}

	a.Equal(n3.getParents(), []*node{n3, n2, n1})
	a.Equal(n2.getParents(), []*node{n2, n1})
}

func TestRemoveNoddes(t *testing.T) {
	a := assert.New(t)

	n1 := &node{pattern: "/1"}
	n2 := &node{pattern: "/2"}
	n3 := &node{pattern: "/3"}
	n4 := &node{pattern: "/4"}
	nodes := []*node{n1, n2, n3, n4}

	// 不存在的元素
	nodes = removeNodes(nodes, "")
	a.Equal(len(nodes), 4)

	// 删除尾元素
	nodes = removeNodes(nodes, "/4")
	a.Equal(len(nodes), 3)

	// 删除中间无素
	nodes = removeNodes(nodes, "/2")
	a.Equal(len(nodes), 2)

	// 已删除，不存在的元素
	nodes = removeNodes(nodes, "/2")
	a.Equal(len(nodes), 2)

	// 第一个元素
	nodes = removeNodes(nodes, "/1")
	a.Equal(len(nodes), 1)

	// 最后一个元素
	nodes = removeNodes(nodes, "/3")
	a.Equal(len(nodes), 0)
}
<commit_msg>[internal/tree] 更加简单的 node 测试工具<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package tree

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/issue9/assert"
	ts "github.com/issue9/mux/internal/tree/syntax"
)

// node 的测试工具
type nodeTest struct {
	n *node
	a *assert.Assertion
}

func newNodeTest(a *assert.Assertion) *nodeTest {
	return &nodeTest{
		n: &node{},
		a: a,
	}
}

// 添加一条路由项。code 表示该路由项返回的报头，
// 测试路由项的 code 需要唯一，之后也是通过此值来判断其命中的路由项。
func (n *nodeTest) add(pattern string, code int, method string) {
	segs := newSegments(n.a, pattern)
	n.a.NotError(n.n.add(segs, buildHandler(code), method))
}

// 验证指定的路径是否匹配正确的路由项，通过 code 来确定
func (n *nodeTest) matchTrue(path string, code int, method string) {
	nn := n.n.match(path)
	n.a.NotNil(nn)
	n.a.NotNil(nn.handlers)

	h := nn.handlers.handler(method)
	n.a.NotNil(h)

	w := httptest.NewRecorder()
	r := httptest.NewRequest(method, path, nil)
	h.ServeHTTP(w, r)
	n.a.Equal(w.Code, code)
}

func newSegments(a *assert.Assertion, pattern string) []*ts.Segment {
	ss, err := ts.Parse(pattern)
	a.NotError(err).NotNil(ss)

	return ss
}

func TestNode_add_remove(t *testing.T) {
	a := assert.New(t)
	node := &node{}

	a.NotError(node.add(newSegments(a, "/"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}/author"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/1/author"), buildHandler(1), http.MethodGet))
	a.NotError(node.add(newSegments(a, "/posts/{id}/{author:\\w+}/profile"), buildHandler(1), http.MethodGet))

	// / 和 /posts/ 以及 /posts/1/author
	a.Equal(len(node.children), 3)
	node.print(0)

	node.remove(newSegments(a, "/posts/1/author"), http.MethodGet)
	// / 和 /posts/
	a.Equal(len(node.children), 2)

	node.remove(newSegments(a, "/posts/{id}/author"), http.MethodGet)
	// / 和 /posts/
	a.Equal(len(node.children), 2)
}

func TestNode_match(t *testing.T) {
	a := assert.New(t)
	test := newNodeTest(a)

	// 添加路由项
	test.add("/", 1, http.MethodGet)
	test.add("/posts/{id}", 2, http.MethodGet)
	test.add("/posts/{id}/author", 3, http.MethodGet)
	test.add("/posts/1/author", 4, http.MethodGet)

	test.matchTrue("/", 1, http.MethodGet)
	test.matchTrue("/posts/1", 2, http.MethodGet)
	test.matchTrue("/posts/2", 2, http.MethodGet)
	test.matchTrue("/posts/2/author", 3, http.MethodGet)
	test.matchTrue("/posts/1/author", 4, http.MethodGet)
}

func TestNode_getParents(t *testing.T) {
	a := assert.New(t)

	n1 := &node{
		children: make([]*node, 0, 1),
	}
	n2 := &node{
		children: make([]*node, 0, 1),
		parent:   n1,
	}
	n3 := &node{
		parent: n2,
	}

	a.Equal(n3.getParents(), []*node{n3, n2, n1})
	a.Equal(n2.getParents(), []*node{n2, n1})
}

func TestRemoveNoddes(t *testing.T) {
	a := assert.New(t)

	n1 := &node{pattern: "/1"}
	n2 := &node{pattern: "/2"}
	n3 := &node{pattern: "/3"}
	n4 := &node{pattern: "/4"}
	nodes := []*node{n1, n2, n3, n4}

	// 不存在的元素
	nodes = removeNodes(nodes, "")
	a.Equal(len(nodes), 4)

	// 删除尾元素
	nodes = removeNodes(nodes, "/4")
	a.Equal(len(nodes), 3)

	// 删除中间无素
	nodes = removeNodes(nodes, "/2")
	a.Equal(len(nodes), 2)

	// 已删除，不存在的元素
	nodes = removeNodes(nodes, "/2")
	a.Equal(len(nodes), 2)

	// 第一个元素
	nodes = removeNodes(nodes, "/1")
	a.Equal(len(nodes), 1)

	// 最后一个元素
	nodes = removeNodes(nodes, "/3")
	a.Equal(len(nodes), 0)
}
<|endoftext|>


<commit_before>package version

import (
	"fmt"
	"runtime"
)

const Binary = "0.3.5"

func String(app string) string {
	return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version())
}
<commit_msg>bump v0.3.6-alpha<commit_after>package version

import (
	"fmt"
	"runtime"
)

const Binary = "0.3.6-alpha"

func String(app string) string {
	return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version())
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"encoding/json"
	"fmt"

	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

const (
	changeCauseAnnotation = "kubernetes.io/change-cause"
)

// Client represents the wrapper of Kubernetes API client
type Client struct {
	annotationPrefix string
	clientConfig     clientcmd.ClientConfig
	clientset        kubernetes.Interface
}

// NewClient creates Client object using local kubecfg
func NewClient(annotationPrefix string, kubeconfig, context string) (*Client, error) {
	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{CurrentContext: context})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		return nil, errors.Wrap(err, "falied to load local kubeconfig")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load clientset")
	}

	return &Client{
		annotationPrefix: annotationPrefix,
		clientConfig:     clientConfig,
		clientset:        clientset,
	}, nil
}

// NewClientInCluster creates Client object in Kubernetes cluster
func NewClientInCluster(annotationPrefix string) (*Client, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, errors.Wrap(err, "failed to load kubeconfig in cluster")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "falied to load clientset")
	}

	return &Client{
		clientset: clientset,
	}, nil
}

// DetectTargetContainer returns the matched or the first container
func (c *Client) DetectTargetContainer(deployment *Deployment, name string) (*Container, error) {
	if name == "" {
		if len(deployment.Containers()) > 1 {
			names := []string{}

			for _, c := range deployment.Containers() {
				names = append(names, c.Name())
			}

			return nil, errors.Errorf("multiple containers %q found in deployment %q", names, deployment.Name())
		}

		return deployment.Containers()[0], nil
	}

	for _, c := range deployment.Containers() {
		if c.Name() == name {
			return c, nil
		}
	}

	return nil, errors.Errorf("container %q does not exist", name)
}

// DetectTargetDeployment returns the matched or the first deployment
func (c *Client) DetectTargetDeployment(namespace, name string) (*Deployment, error) {
	var deployment *Deployment

	if name == "" {
		ds, err := c.ListDeployments(namespace)
		if err != nil {
			return nil, errors.Wrap(err, "failed to retrieve Deployments")
		}

		if len(ds) == 0 {
			return nil, errors.Errorf("no Deployment found in namespace %q", namespace)
		}

		if len(ds) > 1 {
			names := []string{}

			for _, d := range ds {
				names = append(names, d.Name())
			}

			return nil, errors.Errorf("multiple Deployments %q found in namespace %q", names, namespace)
		}

		deployment = ds[0]
	} else {
		d, err := c.GetDeployment(namespace, name)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to retrieve Deployment %q", name)
		}

		deployment = d
	}

	return deployment, nil
}

// GetDeployment returns a deployment
func (c *Client) GetDeployment(namespace, name string) (*Deployment, error) {
	deployment, err := c.clientset.ExtensionsV1beta1().Deployments(namespace).Get(name)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to retrieve Deployment %q", name)
	}

	return NewDeployment(c.annotationPrefix, deployment), nil
}

// ListDeployments returns the list of deployment
func (c *Client) ListDeployments(namespace string) ([]*Deployment, error) {
	deployments, err := c.clientset.ExtensionsV1beta1().Deployments(namespace).List(v1.ListOptions{})
	if err != nil {
		return []*Deployment{}, errors.Wrap(err, "failed to retrieve Deployments")
	}

	ds := []*Deployment{}

	// `for _, d := range deployments` uses the same pointer in `d`
	for i := range deployments.Items {
		ds = append(ds, NewDeployment(c.annotationPrefix, &deployments.Items[i]))
	}

	return ds, nil
}

// SetAnnotations sets the given annotations to the given deployment
func (c *Client) SetAnnotations(deployment *Deployment, annotations map[string]string) (*Deployment, error) {
	j, err := json.Marshal(annotations)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to generate JSON from annotations: %v", annotations)
	}

	patch := fmt.Sprintf(`{
  "spec": {
    "metadata": {
      "annotations": %s,
    }
  }
}`, string(j))

	newd, err := c.clientset.ExtensionsV1beta1().Deployments(deployment.Namespace()).Patch(deployment.Name(), api.StrategicMergePatchType, []byte(patch))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to update deployment %q", deployment.Name())
	}

	return NewDeployment(c.annotationPrefix, newd), nil
}

// SetImage sets new image to the given deployments
func (c *Client) SetImage(deployment *Deployment, container, image, cause string) (*Deployment, error) {
	patch := fmt.Sprintf(`{
  "metadata": {
    "annotations": {
      "%s": %q
    }
  },
  "spec": {
    "template": {
      "spec": {
        "containers": [
          {
            "name": "%s",
            "image": "%s"
          }
        ]
      }
    }
  }
}`, changeCauseAnnotation, cause, container, image)

	newd, err := c.clientset.ExtensionsV1beta1().Deployments(deployment.Namespace()).Patch(deployment.Name(), api.StrategicMergePatchType, []byte(patch))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to update deployment %q", deployment.Name())
	}

	return NewDeployment(c.annotationPrefix, newd), nil
}
<commit_msg>Move metadata under spec.template<commit_after>package kubernetes

import (
	"encoding/json"
	"fmt"

	"github.com/pkg/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

const (
	changeCauseAnnotation = "kubernetes.io/change-cause"
)

// Client represents the wrapper of Kubernetes API client
type Client struct {
	annotationPrefix string
	clientConfig     clientcmd.ClientConfig
	clientset        kubernetes.Interface
}

// NewClient creates Client object using local kubecfg
func NewClient(annotationPrefix string, kubeconfig, context string) (*Client, error) {
	clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
		&clientcmd.ConfigOverrides{CurrentContext: context})

	config, err := clientConfig.ClientConfig()
	if err != nil {
		return nil, errors.Wrap(err, "falied to load local kubeconfig")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to load clientset")
	}

	return &Client{
		annotationPrefix: annotationPrefix,
		clientConfig:     clientConfig,
		clientset:        clientset,
	}, nil
}

// NewClientInCluster creates Client object in Kubernetes cluster
func NewClientInCluster(annotationPrefix string) (*Client, error) {
	config, err := rest.InClusterConfig()
	if err != nil {
		return nil, errors.Wrap(err, "failed to load kubeconfig in cluster")
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "falied to load clientset")
	}

	return &Client{
		clientset: clientset,
	}, nil
}

// DetectTargetContainer returns the matched or the first container
func (c *Client) DetectTargetContainer(deployment *Deployment, name string) (*Container, error) {
	if name == "" {
		if len(deployment.Containers()) > 1 {
			names := []string{}

			for _, c := range deployment.Containers() {
				names = append(names, c.Name())
			}

			return nil, errors.Errorf("multiple containers %q found in deployment %q", names, deployment.Name())
		}

		return deployment.Containers()[0], nil
	}

	for _, c := range deployment.Containers() {
		if c.Name() == name {
			return c, nil
		}
	}

	return nil, errors.Errorf("container %q does not exist", name)
}

// DetectTargetDeployment returns the matched or the first deployment
func (c *Client) DetectTargetDeployment(namespace, name string) (*Deployment, error) {
	var deployment *Deployment

	if name == "" {
		ds, err := c.ListDeployments(namespace)
		if err != nil {
			return nil, errors.Wrap(err, "failed to retrieve Deployments")
		}

		if len(ds) == 0 {
			return nil, errors.Errorf("no Deployment found in namespace %q", namespace)
		}

		if len(ds) > 1 {
			names := []string{}

			for _, d := range ds {
				names = append(names, d.Name())
			}

			return nil, errors.Errorf("multiple Deployments %q found in namespace %q", names, namespace)
		}

		deployment = ds[0]
	} else {
		d, err := c.GetDeployment(namespace, name)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to retrieve Deployment %q", name)
		}

		deployment = d
	}

	return deployment, nil
}

// GetDeployment returns a deployment
func (c *Client) GetDeployment(namespace, name string) (*Deployment, error) {
	deployment, err := c.clientset.ExtensionsV1beta1().Deployments(namespace).Get(name)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to retrieve Deployment %q", name)
	}

	return NewDeployment(c.annotationPrefix, deployment), nil
}

// ListDeployments returns the list of deployment
func (c *Client) ListDeployments(namespace string) ([]*Deployment, error) {
	deployments, err := c.clientset.ExtensionsV1beta1().Deployments(namespace).List(v1.ListOptions{})
	if err != nil {
		return []*Deployment{}, errors.Wrap(err, "failed to retrieve Deployments")
	}

	ds := []*Deployment{}

	// `for _, d := range deployments` uses the same pointer in `d`
	for i := range deployments.Items {
		ds = append(ds, NewDeployment(c.annotationPrefix, &deployments.Items[i]))
	}

	return ds, nil
}

// SetAnnotations sets the given annotations to the given deployment
func (c *Client) SetAnnotations(deployment *Deployment, annotations map[string]string) (*Deployment, error) {
	j, err := json.Marshal(annotations)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to generate JSON from annotations: %v", annotations)
	}

	patch := fmt.Sprintf(`{
  "spec": {
    "template": {
      "metadata": {
        "annotations": %s
      }
    }
  }
}`, string(j))

	newd, err := c.clientset.ExtensionsV1beta1().Deployments(deployment.Namespace()).Patch(deployment.Name(), api.StrategicMergePatchType, []byte(patch))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to update deployment %q", deployment.Name())
	}

	return NewDeployment(c.annotationPrefix, newd), nil
}

// SetImage sets new image to the given deployments
func (c *Client) SetImage(deployment *Deployment, container, image, cause string) (*Deployment, error) {
	patch := fmt.Sprintf(`{
  "metadata": {
    "annotations": {
      "%s": %q
    }
  },
  "spec": {
    "template": {
      "spec": {
        "containers": [
          {
            "name": "%s",
            "image": "%s"
          }
        ]
      }
    }
  }
}`, changeCauseAnnotation, cause, container, image)

	newd, err := c.clientset.ExtensionsV1beta1().Deployments(deployment.Namespace()).Patch(deployment.Name(), api.StrategicMergePatchType, []byte(patch))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to update deployment %q", deployment.Name())
	}

	return NewDeployment(c.annotationPrefix, newd), nil
}
<|endoftext|>


<commit_before>package startCmd

import (
	// Stdlib
	"bytes"
	"fmt"
	"os"
	"regexp"

	// Internal
	"github.com/salsita/SalsaFlow/git-trunk/app"
	"github.com/salsita/SalsaFlow/git-trunk/config"
	"github.com/salsita/SalsaFlow/git-trunk/git"
	"github.com/salsita/SalsaFlow/git-trunk/log"
	"github.com/salsita/SalsaFlow/git-trunk/modules"
	"github.com/salsita/SalsaFlow/git-trunk/modules/common"
	"github.com/salsita/SalsaFlow/git-trunk/prompt"

	// Other
	"github.com/extemporalgenome/slug"
	"gopkg.in/tchap/gocli.v1"
)

var Command = &gocli.Command{
	UsageLine: `
  start`,
	Short: "start a new story",
	Long: `
  Starts a new story, i.e., create a new branch off ` + config.TrunkBranch + `
  or check out an existing branch containing the story id if there's already one.
  Then updates the story state in PM tool and sets you as the story owner.
	`,
	Action: run,
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.MustInit()

	if err := runMain(); err != nil {
		log.Fatalln("\nError: " + err.Error())
	}
}

func handleError(err error, stderr *bytes.Buffer) {
	log.FailWithDetails(err.Error(), stderr)
}

func runMain() (err error) {
	var (
		selectedStory common.Story
		currentBranch string
	)

	defer func() {
		// Checkout the original branch.
		if currentBranch == "" {
			return
		}
		msg := fmt.Sprintf("Checkout the original branch (%s)", currentBranch)
		log.Run(msg)
		stderr, err := git.Checkout(currentBranch)
		if err != nil {
			handleError(err, stderr)
			return
		}
	}()

	// Remember the current branch.
	log.Run("Remember the current branch")
	currentBranch, stderr, err := git.CurrentBranch()
	if err != nil {
		handleError(err, stderr)
		return err
	}

	log.Run("Loading stories from PM tool")
	stories, err := modules.GetIssueTracker().StartableStories()
	if err != nil {
		handleError(err, nil)
		return err
	}

	// List stories that can be started.
	for i, story := range stories {
		log.Printf("[%d]: %s -> %s\n", i, story.ReadableId(), story.Title())
	}

	// Prompt user to choose.
	index, err := prompt.PromptIndex("Choose story: ", 0, len(stories)-1)
	if err != nil {
		log.Fatalln(err.Error())
	}

	selectedStory = stories[index]

	// Get all branches.
	localRefs, remoteRefs, stderr, err := git.ListStoryRefs()
	if err != nil {
		handleError(err, nil)
		return err
	}

	// Get all story branches connected to `selectedStory` id.
	log.Run("Check existing branches")
	matchingBranches := make(map[string]bool)

	stripRemotePrefixRe := regexp.MustCompile(".*/(story/.+/.+)$")
	for _, ref := range append(localRefs, remoteRefs...) {
		storyId, err := git.RefToStoryId(ref)
		if err != nil {
			handleError(err, nil)
			return err
		}
		if storyId == selectedStory.Id() {
			// We found a matching story branch. Let's strip off the `remote/origin` etc
			// part so that we have just the branch name.
			branchName := stripRemotePrefixRe.ReplaceAllString(ref, "$1")
			matchingBranches[branchName] = true
		}
	}

	if len(matchingBranches) == 1 {
		// There is only one branch => checkout and work on that one.
		for branch := range matchingBranches {
			log.Log(fmt.Sprintf("Found one existing story branch: %s", branch))
			log.Run(fmt.Sprintf("Checking out %s", branch))
			if stderr, err = git.Checkout(branch); err != nil {
				handleError(err, stderr)
				return
			}
		}

	} else if len(matchingBranches) > 1 {
		// There are multiple branches. Let the user choose which one to work on.
		log.Log("Found multiple existing story branches:")
		for branch := range matchingBranches {
			log.Printf("\t - %s\n", branch)
		}
		log.Log("Please checkout one of them and work there.")

	} else {
		// There is no branch => create a new one.
		line, err := prompt.Prompt("Please insert branch slug: ")
		if err != nil {
			handleError(err, nil)
			return err
		}
		branchName := fmt.Sprintf("story/%s/%s", slug.Slug(line), selectedStory.Id())
		ok, err := prompt.Confirm(
			fmt.Sprintf("The branch will be called '%s', OK?", branchName))
		if err != nil {
			handleError(err, nil)
			return err
		}
		if !ok {
			log.Fatalln("I will exit now. If you want to try again, just run me again!")
		}

		msg := "Checkout " + branchName
		if _, stderr, err = git.Git("branch", branchName, config.TrunkBranch); err != nil {
			handleError(err, stderr)
			return err
		}

		if _, stderr, err = git.Git("checkout", branchName); err != nil {
			handleError(err, stderr)
			return err
		}

		// Delete created branch on rollback.
		defer func(msg string) {
			if err != nil {
				log.Rollback(msg)
				if stderr, err := git.Branch("-D", branchName); err != nil {
					handleError(err, stderr)
				}
			}
		}(msg)
	}

	log.Run(fmt.Sprintf("Starting story %s", selectedStory.Id()))
	if err := selectedStory.Start(); err != nil {
		handleError(err, err.Stderr)
		return err
	}

	log.Run("Setting you as the story owner")
	user, err := modules.GetIssueTracker().CurrentUser()
	if err != nil {
		handleError(err, nil)
		return err
	}
	// TODO: We should update, not overwrite the owners.
	if err := selectedStory.SetOwners([]common.User{user}); err != nil {
		handleError(err, nil)
		return err
	}

	return nil
}
<commit_msg>Use more idiomatic Go mapping.<commit_after>package startCmd

import (
	// Stdlib
	"bytes"
	"fmt"
	"os"
	"regexp"

	// Internal
	"github.com/salsita/SalsaFlow/git-trunk/app"
	"github.com/salsita/SalsaFlow/git-trunk/config"
	"github.com/salsita/SalsaFlow/git-trunk/git"
	"github.com/salsita/SalsaFlow/git-trunk/log"
	"github.com/salsita/SalsaFlow/git-trunk/modules"
	"github.com/salsita/SalsaFlow/git-trunk/modules/common"
	"github.com/salsita/SalsaFlow/git-trunk/prompt"

	// Other
	"github.com/extemporalgenome/slug"
	"gopkg.in/tchap/gocli.v1"
)

var Command = &gocli.Command{
	UsageLine: `
  start`,
	Short: "start a new story",
	Long: `
  Starts a new story, i.e., create a new branch off ` + config.TrunkBranch + `
  or check out an existing branch containing the story id if there's already one.
  Then updates the story state in PM tool and sets you as the story owner.
	`,
	Action: run,
}

func run(cmd *gocli.Command, args []string) {
	if len(args) != 0 {
		cmd.Usage()
		os.Exit(2)
	}

	app.MustInit()

	if err := runMain(); err != nil {
		log.Fatalln("\nError: " + err.Error())
	}
}

func handleError(err error, stderr *bytes.Buffer) {
	log.FailWithDetails(err.Error(), stderr)
}

func runMain() (err error) {
	var (
		selectedStory common.Story
		currentBranch string
	)

	defer func() {
		// Checkout the original branch.
		if currentBranch == "" {
			return
		}
		msg := fmt.Sprintf("Checkout the original branch (%s)", currentBranch)
		log.Run(msg)
		stderr, err := git.Checkout(currentBranch)
		if err != nil {
			handleError(err, stderr)
			return
		}
	}()

	// Remember the current branch.
	log.Run("Remember the current branch")
	currentBranch, stderr, err := git.CurrentBranch()
	if err != nil {
		handleError(err, stderr)
		return err
	}

	log.Run("Loading stories from PM tool")
	stories, err := modules.GetIssueTracker().StartableStories()
	if err != nil {
		handleError(err, nil)
		return err
	}

	// List stories that can be started.
	for i, story := range stories {
		log.Printf("[%d]: %s -> %s\n", i, story.ReadableId(), story.Title())
	}

	// Prompt user to choose.
	index, err := prompt.PromptIndex("Choose story: ", 0, len(stories)-1)
	if err != nil {
		log.Fatalln(err.Error())
	}

	selectedStory = stories[index]

	// Get all branches.
	localRefs, remoteRefs, stderr, err := git.ListStoryRefs()
	if err != nil {
		handleError(err, nil)
		return err
	}

	// Get all story branches connected to `selectedStory` id.
	log.Run("Check existing branches")
	matchingBranches := map[string]struct{}{}

	stripRemotePrefixRe := regexp.MustCompile(".*/(story/.+/.+)$")
	for _, ref := range append(localRefs, remoteRefs...) {
		storyId, err := git.RefToStoryId(ref)
		if err != nil {
			handleError(err, nil)
			return err
		}
		if storyId == selectedStory.Id() {
			// We found a matching story branch. Let's strip off the `remote/origin` etc
			// part so that we have just the branch name.
			branchName := stripRemotePrefixRe.ReplaceAllString(ref, "$1")
			matchingBranches[branchName] = struct{}{}
		}
	}

	if len(matchingBranches) == 1 {
		// There is only one branch => checkout and work on that one.
		for branch := range matchingBranches {
			log.Log(fmt.Sprintf("Found one existing story branch: %s", branch))
			log.Run(fmt.Sprintf("Checking out %s", branch))
			if stderr, err = git.Checkout(branch); err != nil {
				handleError(err, stderr)
				return
			}
		}

	} else if len(matchingBranches) > 1 {
		// There are multiple branches. Let the user choose which one to work on.
		log.Log("Found multiple existing story branches:")
		for branch := range matchingBranches {
			log.Printf("\t - %s\n", branch)
		}
		log.Log("Please checkout one of them and work there.")

	} else {
		// There is no branch => create a new one.
		line, err := prompt.Prompt("Please insert branch slug: ")
		if err != nil {
			handleError(err, nil)
			return err
		}
		branchName := fmt.Sprintf("story/%s/%s", slug.Slug(line), selectedStory.Id())
		ok, err := prompt.Confirm(
			fmt.Sprintf("The branch will be called '%s', OK?", branchName))
		if err != nil {
			handleError(err, nil)
			return err
		}
		if !ok {
			log.Fatalln("I will exit now. If you want to try again, just run me again!")
		}

		msg := "Checkout " + branchName
		if _, stderr, err = git.Git("branch", branchName, config.TrunkBranch); err != nil {
			handleError(err, stderr)
			return err
		}

		if _, stderr, err = git.Git("checkout", branchName); err != nil {
			handleError(err, stderr)
			return err
		}

		// Delete created branch on rollback.
		defer func(msg string) {
			if err != nil {
				log.Rollback(msg)
				if stderr, err := git.Branch("-D", branchName); err != nil {
					handleError(err, stderr)
				}
			}
		}(msg)
	}

	log.Run(fmt.Sprintf("Starting story %s", selectedStory.Id()))
	if err := selectedStory.Start(); err != nil {
		handleError(err, err.Stderr)
		return err
	}

	log.Run("Setting you as the story owner")
	user, err := modules.GetIssueTracker().CurrentUser()
	if err != nil {
		handleError(err, nil)
		return err
	}
	// TODO: We should update, not overwrite the owners.
	if err := selectedStory.SetOwners([]common.User{user}); err != nil {
		handleError(err, nil)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"socialapi/config"
	"time"

	"github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/koding/bongo"
)

var mentionRegex = verbalexpressions.New().
	Find("@").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id,string"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug"                               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// type of the message
	TypeConstant string `json:"typeConstant"               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creator of the channel message
	AccountId int64 `json:"accountId,string"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId,string" sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits int16 `json:"-"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Deletion date of the channel message
	DeletedAt time.Time `json:"deletedAt"`
}

func (c *ChannelMessage) BeforeCreate() {
	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c ChannelMessage) GetId() int64 {
	return c.Id
}

func (c ChannelMessage) TableName() string {
	return "api.channel_message"
}

const (
	ChannelMessage_TYPE_POST            = "post"
	ChannelMessage_TYPE_REPLY           = "reply"
	ChannelMessage_TYPE_JOIN            = "join"
	ChannelMessage_TYPE_LEAVE           = "leave"
	ChannelMessage_TYPE_CHAT            = "chat"
	ChannelMessage_TYPE_PRIVATE_MESSAGE = "privatemessage"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *ChannelMessage) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *ChannelMessage) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func bodyLenCheck(body string) error {
	if len(body) < config.Get().Limits.MessageBodyMinLen {
		return fmt.Errorf("Message Body Length should be greater than %d, yours is %d ", config.Get().Limits.MessageBodyMinLen, len(body))
	}

	return nil
}

// todo create a new message while updating the channel_message and delete other
// cases, since deletion is a soft delete, old instances will still be there
func (c *ChannelMessage) Update() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}

	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}
	return messages, nil
}

func (c *ChannelMessage) BuildMessages(query *Query, messages []ChannelMessage) ([]*ChannelMessageContainer, error) {
	containers := make([]*ChannelMessageContainer, len(messages))
	if len(containers) == 0 {
		return containers, nil
	}

	for i, message := range messages {
		d := NewChannelMessage()
		*d = message
		data, err := d.BuildMessage(query)
		if err != nil {
			return containers, err
		}
		containers[i] = data
	}

	return containers, nil
}

func (c *ChannelMessage) BuildMessage(query *Query) (*ChannelMessageContainer, error) {
	cmc, err := c.FetchRelatives(query)
	if err != nil {
		return nil, err
	}

	mr := NewMessageReply()
	mr.MessageId = c.Id
	q := query
	q.Limit = 3
	replies, err := mr.List(query)
	if err != nil {
		return nil, err
	}

	repliesCount, err := mr.Count()
	if err != nil {
		return nil, err
	}
	cmc.RepliesCount = repliesCount

	populatedChannelMessagesReplies := make([]*ChannelMessageContainer, len(replies))
	for rl := 0; rl < len(replies); rl++ {
		cmrc, err := replies[rl].FetchRelatives(query)
		if err != nil {
			return nil, err
		}
		populatedChannelMessagesReplies[rl] = cmrc
	}

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

func (c *ChannelMessage) BuildEmptyMessageContainer() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	oldId, err := AccountOldIdById(c.AccountId)
	if err != nil {
		return nil, err
	}

	container.AccountOldId = oldId

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = make([]string, 0)
	interactionContainer.IsInteracted = false
	interactionContainer.ActorsCount = 0

	container.Interactions = make(map[string]*InteractionContainer)
	container.Interactions["like"] = interactionContainer

	return container, nil
}

func (c *ChannelMessage) FetchRelatives(query *Query) (*ChannelMessageContainer, error) {
	container, err := c.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	i := NewInteraction()
	i.MessageId = c.Id

	// get preview
	query.Type = "like"
	query.Limit = 3
	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	// check if the current user is interacted in this thread
	isInteracted, err := i.IsInteracted(query.AccountId)
	if err != nil {
		return nil, err
	}

	interactionContainer.IsInteracted = isInteracted

	// fetch interaction count
	count, err := i.Count(query.Type)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	container.Interactions["like"] = interactionContainer
	return container, nil
}

func generateMessageListQuery(channelId int64, q *Query) *bongo.Query {
	messageType := q.Type
	if messageType == "" {
		messageType = ChannelMessage_TYPE_POST
	}

	return &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":         q.AccountId,
			"initial_channel_id": channelId,
			"type_constant":      messageType,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}
}

func (c *ChannelMessage) FetchMessagesByChannelId(channelId int64, q *Query) ([]ChannelMessage, error) {
	query := generateMessageListQuery(channelId, q)

	var messages []ChannelMessage
	if err := c.Some(&messages, query); err != nil {
		return nil, err
	}

	if messages == nil {
		return make([]ChannelMessage, 0), nil
	}
	return messages, nil
}

func (c *ChannelMessage) GetMentionedUsernames() []string {
	flattened := make([]string, 0)

	res := mentionRegex.FindAllStringSubmatch(c.Body, -1)
	if len(res) == 0 {
		return flattened
	}

	participants := map[string]struct{}{}
	// remove duplicate mentions
	for _, ele := range res {
		participants[ele[1]] = struct{}{}
	}

	for participant := range participants {
		flattened = append(flattened, participant)
	}

	return flattened
}

func (c *ChannelMessage) FetchTotalMessageCount(q *Query) (int, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	return c.CountWithQuery(query)
}
<commit_msg>Social: add FetchMessageIds function<commit_after>package models

import (
	"errors"
	"fmt"
	"socialapi/config"
	"time"

	"github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/koding/bongo"
)

var mentionRegex = verbalexpressions.New().
	Find("@").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id,string"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug"                               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// type of the message
	TypeConstant string `json:"typeConstant"               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creator of the channel message
	AccountId int64 `json:"accountId,string"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId,string" sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits int16 `json:"-"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Deletion date of the channel message
	DeletedAt time.Time `json:"deletedAt"`
}

func (c *ChannelMessage) BeforeCreate() {
	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c ChannelMessage) GetId() int64 {
	return c.Id
}

func (c ChannelMessage) TableName() string {
	return "api.channel_message"
}

const (
	ChannelMessage_TYPE_POST            = "post"
	ChannelMessage_TYPE_REPLY           = "reply"
	ChannelMessage_TYPE_JOIN            = "join"
	ChannelMessage_TYPE_LEAVE           = "leave"
	ChannelMessage_TYPE_CHAT            = "chat"
	ChannelMessage_TYPE_PRIVATE_MESSAGE = "privatemessage"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *ChannelMessage) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *ChannelMessage) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func bodyLenCheck(body string) error {
	if len(body) < config.Get().Limits.MessageBodyMinLen {
		return fmt.Errorf("Message Body Length should be greater than %d, yours is %d ", config.Get().Limits.MessageBodyMinLen, len(body))
	}

	return nil
}

// todo create a new message while updating the channel_message and delete other
// cases, since deletion is a soft delete, old instances will still be there
func (c *ChannelMessage) Update() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}

	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}
	return messages, nil
}

func (c *ChannelMessage) BuildMessages(query *Query, messages []ChannelMessage) ([]*ChannelMessageContainer, error) {
	containers := make([]*ChannelMessageContainer, len(messages))
	if len(containers) == 0 {
		return containers, nil
	}

	for i, message := range messages {
		d := NewChannelMessage()
		*d = message
		data, err := d.BuildMessage(query)
		if err != nil {
			return containers, err
		}
		containers[i] = data
	}

	return containers, nil
}

func (c *ChannelMessage) BuildMessage(query *Query) (*ChannelMessageContainer, error) {
	cmc, err := c.FetchRelatives(query)
	if err != nil {
		return nil, err
	}

	mr := NewMessageReply()
	mr.MessageId = c.Id
	q := query
	q.Limit = 3
	replies, err := mr.List(query)
	if err != nil {
		return nil, err
	}

	repliesCount, err := mr.Count()
	if err != nil {
		return nil, err
	}
	cmc.RepliesCount = repliesCount

	populatedChannelMessagesReplies := make([]*ChannelMessageContainer, len(replies))
	for rl := 0; rl < len(replies); rl++ {
		cmrc, err := replies[rl].FetchRelatives(query)
		if err != nil {
			return nil, err
		}
		populatedChannelMessagesReplies[rl] = cmrc
	}

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

func (c *ChannelMessage) BuildEmptyMessageContainer() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	oldId, err := AccountOldIdById(c.AccountId)
	if err != nil {
		return nil, err
	}

	container.AccountOldId = oldId

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = make([]string, 0)
	interactionContainer.IsInteracted = false
	interactionContainer.ActorsCount = 0

	container.Interactions = make(map[string]*InteractionContainer)
	container.Interactions["like"] = interactionContainer

	return container, nil
}

func (c *ChannelMessage) FetchRelatives(query *Query) (*ChannelMessageContainer, error) {
	container, err := c.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	i := NewInteraction()
	i.MessageId = c.Id

	// get preview
	query.Type = "like"
	query.Limit = 3
	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	// check if the current user is interacted in this thread
	isInteracted, err := i.IsInteracted(query.AccountId)
	if err != nil {
		return nil, err
	}

	interactionContainer.IsInteracted = isInteracted

	// fetch interaction count
	count, err := i.Count(query.Type)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	container.Interactions["like"] = interactionContainer
	return container, nil
}

func generateMessageListQuery(channelId int64, q *Query) *bongo.Query {
	messageType := q.Type
	if messageType == "" {
		messageType = ChannelMessage_TYPE_POST
	}

	return &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":         q.AccountId,
			"initial_channel_id": channelId,
			"type_constant":      messageType,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}
}

func (c *ChannelMessage) FetchMessagesByChannelId(channelId int64, q *Query) ([]ChannelMessage, error) {
	query := generateMessageListQuery(channelId, q)

	var messages []ChannelMessage
	if err := c.Some(&messages, query); err != nil {
		return nil, err
	}

	if messages == nil {
		return make([]ChannelMessage, 0), nil
	}
	return messages, nil
}

func (c *ChannelMessage) GetMentionedUsernames() []string {
	flattened := make([]string, 0)

	res := mentionRegex.FindAllStringSubmatch(c.Body, -1)
	if len(res) == 0 {
		return flattened
	}

	participants := map[string]struct{}{}
	// remove duplicate mentions
	for _, ele := range res {
		participants[ele[1]] = struct{}{}
	}

	for participant := range participants {
		flattened = append(flattened, participant)
	}

	return flattened
}

func (c *ChannelMessage) FetchTotalMessageCount(q *Query) (int, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	return c.CountWithQuery(query)
}

func (c *ChannelMessage) FetchMessageIds(q *Query) ([]int64, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pluck:      "id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}

	var messageIds []int64
	if err := c.Some(&messageIds, query); err != nil {
		return nil, err
	}

	if messageIds == nil {
		return make([]int64, 0), nil
	}

	return messageIds, nil
}
<|endoftext|>


<commit_before>package drawing

import (
	"errors"
	"image"
	"image/color"
	"math"

	"github.com/golang/freetype/raster"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/draw"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

// NewRasterGraphicContext creates a new Graphic context from an image.
func NewRasterGraphicContext(img draw.Image) (*RasterGraphicContext, error) {
	var painter Painter
	switch selectImage := img.(type) {
	case *image.RGBA:
		painter = raster.NewRGBAPainter(selectImage)
	default:
		return nil, errors.New("NewRasterGraphicContext() :: invalid image type")
	}
	return NewRasterGraphicContextWithPainter(img, painter), nil
}

// NewRasterGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewRasterGraphicContextWithPainter(img draw.Image, painter Painter) *RasterGraphicContext {
	width, height := img.Bounds().Dx(), img.Bounds().Dy()
	return &RasterGraphicContext{
		NewStackGraphicContext(),
		img,
		painter,
		raster.NewRasterizer(width, height),
		raster.NewRasterizer(width, height),
		&truetype.GlyphBuf{},
		DefaultDPI,
	}
}

// RasterGraphicContext is the implementation of GraphicContext for a raster image
type RasterGraphicContext struct {
	*StackGraphicContext
	img              draw.Image
	painter          Painter
	fillRasterizer   *raster.Rasterizer
	strokeRasterizer *raster.Rasterizer
	glyphBuf         *truetype.GlyphBuf
	DPI              float64
}

// SetDPI sets the screen resolution in dots per inch.
func (rgc *RasterGraphicContext) SetDPI(dpi float64) {
	rgc.DPI = dpi
	rgc.recalc()
}

// GetDPI returns the resolution of the Image GraphicContext
func (rgc *RasterGraphicContext) GetDPI() float64 {
	return rgc.DPI
}

// Clear fills the current canvas with a default transparent color
func (rgc *RasterGraphicContext) Clear() {
	width, height := rgc.img.Bounds().Dx(), rgc.img.Bounds().Dy()
	rgc.ClearRect(0, 0, width, height)
}

// ClearRect fills the current canvas with a default transparent color at the specified rectangle
func (rgc *RasterGraphicContext) ClearRect(x1, y1, x2, y2 int) {
	imageColor := image.NewUniform(rgc.current.FillColor)
	draw.Draw(rgc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}

// DrawImage draws the raster image in the current canvas
func (rgc *RasterGraphicContext) DrawImage(img image.Image) {
	DrawImage(img, rgc.img, rgc.current.Tr, draw.Over, BilinearFilter)
}

// FillString draws the text at point (0, 0)
func (rgc *RasterGraphicContext) FillString(text string) (cursor float64, err error) {
	cursor, err = rgc.FillStringAt(text, 0, 0)
	return
}

// FillStringAt draws the text at the specified point (x, y)
func (rgc *RasterGraphicContext) FillStringAt(text string, x, y float64) (cursor float64, err error) {
	cursor, err = rgc.CreateStringPath(text, x, y)
	rgc.Fill()
	return
}

// StrokeString draws the contour of the text at point (0, 0)
func (rgc *RasterGraphicContext) StrokeString(text string) (cursor float64, err error) {
	cursor, err = rgc.StrokeStringAt(text, 0, 0)
	return
}

// StrokeStringAt draws the contour of the text at point (x, y)
func (rgc *RasterGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64, err error) {
	cursor, err = rgc.CreateStringPath(text, x, y)
	rgc.Stroke()
	return
}

func (rgc *RasterGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
	if err := rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), glyph, font.HintingNone); err != nil {
		return err
	}
	e0 := 0
	for _, e1 := range rgc.glyphBuf.Ends {
		DrawContour(rgc, rgc.glyphBuf.Points[e0:e1], dx, dy)
		e0 = e1
	}
	return nil
}

// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (rgc *RasterGraphicContext) CreateStringPath(s string, x, y float64) (cursor float64, err error) {
	f := rgc.GetFont()
	if f == nil {
		err = errors.New("No font loaded, cannot continue")
		return
	}
	rgc.recalc()

	startx := x
	prev, hasPrev := truetype.Index(0), false
	for _, rc := range s {
		index := f.Index(rc)
		if hasPrev {
			x += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
		}
		err = rgc.drawGlyph(index, x, y)
		if err != nil {
			cursor = x - startx
			return
		}
		x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
		prev, hasPrev = index, true
	}
	cursor = x - startx
	return
}

// GetStringBounds returns the approximate points bounds of a string.
// It will be required to convert points to pixels for measurement.
func (rgc *RasterGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64, err error) {
	f := rgc.GetFont()
	if f == nil {
		err = errors.New("No font loaded, cannot continue")
		return
	}
	rgc.recalc()

	cursor := 0.0
	prev, hasPrev := truetype.Index(0), false
	for _, rc := range s {
		index := f.Index(rc)
		if hasPrev {
			cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
		}

		if err = rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), index, font.HintingNone); err != nil {
			return
		}
		e0 := 0
		for _, e1 := range rgc.glyphBuf.Ends {
			ps := rgc.glyphBuf.Points[e0:e1]
			for _, p := range ps {
				x, y := pointToF64Point(p)
				top = math.Min(top, y)
				bottom = math.Max(bottom, y)
				left = math.Min(left, x+cursor)
				right = math.Max(right, x+cursor)
			}
			e0 = e1
		}
		cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
		prev, hasPrev = index, true
	}
	return
}

// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (rgc *RasterGraphicContext) recalc() {
	rgc.current.Scale = rgc.current.FontSizePoints * float64(rgc.DPI) * 64.0 / 72.0
}

// SetFont sets the font used to draw text.
func (rgc *RasterGraphicContext) SetFont(font *truetype.Font) {
	rgc.current.Font = font
}

// GetFont returns the font used to draw text.
func (rgc *RasterGraphicContext) GetFont() *truetype.Font {
	return rgc.current.Font
}

// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) {
	rgc.current.FontSizePoints = fontSizePoints
	rgc.recalc()
}

func (rgc *RasterGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
	rgc.painter.SetColor(color)
	rasterizer.Rasterize(rgc.painter)
	rasterizer.Clear()
	rgc.current.Path.Clear()
}

// Stroke strokes the paths with the color specified by SetStrokeColor
func (rgc *RasterGraphicContext) Stroke(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.strokeRasterizer.UseNonZeroWinding = true

	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
	stroker.HalfLineWidth = rgc.current.LineWidth / 2

	var liner Flattener
	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
	} else {
		liner = stroker
	}
	for _, p := range paths {
		Flatten(p, liner, rgc.current.Tr.GetScale())
	}

	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
}

// Fill fills the paths with the color specified by SetFillColor
func (rgc *RasterGraphicContext) Fill(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding

	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}
	for _, p := range paths {
		Flatten(p, flattener, rgc.current.Tr.GetScale())
	}

	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
}

// FillStroke first fills the paths and than strokes them
func (rgc *RasterGraphicContext) FillStroke(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding
	rgc.strokeRasterizer.UseNonZeroWinding = true

	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}

	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
	stroker.HalfLineWidth = rgc.current.LineWidth / 2

	var liner Flattener
	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
	} else {
		liner = stroker
	}

	demux := DemuxFlattener{Flatteners: []Flattener{flattener, liner}}
	for _, p := range paths {
		Flatten(p, demux, rgc.current.Tr.GetScale())
	}

	// Fill
	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
	// Stroke
	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
}
<commit_msg>tweaks.<commit_after>package drawing

import (
	"errors"
	"image"
	"image/color"
	"math"

	"github.com/golang/freetype/raster"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/draw"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

// NewRasterGraphicContext creates a new Graphic context from an image.
func NewRasterGraphicContext(img draw.Image) (*RasterGraphicContext, error) {
	var painter Painter
	switch selectImage := img.(type) {
	case *image.RGBA:
		painter = raster.NewRGBAPainter(selectImage)
	default:
		return nil, errors.New("NewRasterGraphicContext() :: invalid image type")
	}
	return NewRasterGraphicContextWithPainter(img, painter), nil
}

// NewRasterGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewRasterGraphicContextWithPainter(img draw.Image, painter Painter) *RasterGraphicContext {
	width, height := img.Bounds().Dx(), img.Bounds().Dy()
	return &RasterGraphicContext{
		NewStackGraphicContext(),
		img,
		painter,
		raster.NewRasterizer(width, height),
		raster.NewRasterizer(width, height),
		&truetype.GlyphBuf{},
		DefaultDPI,
	}
}

// RasterGraphicContext is the implementation of GraphicContext for a raster image
type RasterGraphicContext struct {
	*StackGraphicContext
	img              draw.Image
	painter          Painter
	fillRasterizer   *raster.Rasterizer
	strokeRasterizer *raster.Rasterizer
	glyphBuf         *truetype.GlyphBuf
	DPI              float64
}

// SetDPI sets the screen resolution in dots per inch.
func (rgc *RasterGraphicContext) SetDPI(dpi float64) {
	rgc.DPI = dpi
	rgc.recalc()
}

// GetDPI returns the resolution of the Image GraphicContext
func (rgc *RasterGraphicContext) GetDPI() float64 {
	return rgc.DPI
}

// Clear fills the current canvas with a default transparent color
func (rgc *RasterGraphicContext) Clear() {
	width, height := rgc.img.Bounds().Dx(), rgc.img.Bounds().Dy()
	rgc.ClearRect(0, 0, width, height)
}

// ClearRect fills the current canvas with a default transparent color at the specified rectangle
func (rgc *RasterGraphicContext) ClearRect(x1, y1, x2, y2 int) {
	imageColor := image.NewUniform(rgc.current.FillColor)
	draw.Draw(rgc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}

// DrawImage draws the raster image in the current canvas
func (rgc *RasterGraphicContext) DrawImage(img image.Image) {
	DrawImage(img, rgc.img, rgc.current.Tr, draw.Over, BilinearFilter)
}

// FillString draws the text at point (0, 0)
func (rgc *RasterGraphicContext) FillString(text string) (cursor float64, err error) {
	cursor, err = rgc.FillStringAt(text, 0, 0)
	return
}

// FillStringAt draws the text at the specified point (x, y)
func (rgc *RasterGraphicContext) FillStringAt(text string, x, y float64) (cursor float64, err error) {
	cursor, err = rgc.CreateStringPath(text, x, y)
	rgc.Fill()
	return
}

// StrokeString draws the contour of the text at point (0, 0)
func (rgc *RasterGraphicContext) StrokeString(text string) (cursor float64, err error) {
	cursor, err = rgc.StrokeStringAt(text, 0, 0)
	return
}

// StrokeStringAt draws the contour of the text at point (x, y)
func (rgc *RasterGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64, err error) {
	cursor, err = rgc.CreateStringPath(text, x, y)
	rgc.Stroke()
	return
}

func (rgc *RasterGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
	if err := rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), glyph, font.HintingNone); err != nil {
		return err
	}
	e0 := 0
	for _, e1 := range rgc.glyphBuf.Ends {
		DrawContour(rgc, rgc.glyphBuf.Points[e0:e1], dx, dy)
		e0 = e1
	}
	return nil
}

// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (rgc *RasterGraphicContext) CreateStringPath(s string, x, y float64) (cursor float64, err error) {
	f := rgc.GetFont()
	if f == nil {
		err = errors.New("No font loaded, cannot continue")
		return
	}
	rgc.recalc()

	startx := x
	prev, hasPrev := truetype.Index(0), false
	for _, rc := range s {
		index := f.Index(rc)
		if hasPrev {
			x += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
		}
		err = rgc.drawGlyph(index, x, y)
		if err != nil {
			cursor = x - startx
			return
		}
		x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
		prev, hasPrev = index, true
	}
	cursor = x - startx
	return
}

// GetStringBounds returns the approximate points bounds of a string.
// It will be required to convert points to pixels for measurement.
func (rgc *RasterGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64, err error) {
	f := rgc.GetFont()
	if f == nil {
		err = errors.New("No font loaded, cannot continue")
		return
	}
	rgc.recalc()

	left = math.MaxFloat64
	top = math.MaxFloat64

	cursor := 0.0
	prev, hasPrev := truetype.Index(0), false
	for _, rc := range s {
		index := f.Index(rc)
		if hasPrev {
			cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(rgc.current.Scale), prev, index))
		}

		if err = rgc.glyphBuf.Load(rgc.current.Font, fixed.Int26_6(rgc.current.Scale), index, font.HintingNone); err != nil {
			return
		}
		e0 := 0
		for _, e1 := range rgc.glyphBuf.Ends {
			ps := rgc.glyphBuf.Points[e0:e1]
			for _, p := range ps {
				x, y := pointToF64Point(p)
				top = math.Min(top, y)
				bottom = math.Max(bottom, y)
				left = math.Min(left, x+cursor)
				right = math.Max(right, x+cursor)
			}
			e0 = e1
		}
		cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(rgc.current.Scale), index).AdvanceWidth)
		prev, hasPrev = index, true
	}
	return
}

// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (rgc *RasterGraphicContext) recalc() {
	rgc.current.Scale = rgc.current.FontSizePoints * float64(rgc.DPI)
}

// SetFont sets the font used to draw text.
func (rgc *RasterGraphicContext) SetFont(font *truetype.Font) {
	rgc.current.Font = font
}

// GetFont returns the font used to draw text.
func (rgc *RasterGraphicContext) GetFont() *truetype.Font {
	return rgc.current.Font
}

// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (rgc *RasterGraphicContext) SetFontSize(fontSizePoints float64) {
	rgc.current.FontSizePoints = fontSizePoints
	rgc.recalc()
}

func (rgc *RasterGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
	rgc.painter.SetColor(color)
	rasterizer.Rasterize(rgc.painter)
	rasterizer.Clear()
	rgc.current.Path.Clear()
}

// Stroke strokes the paths with the color specified by SetStrokeColor
func (rgc *RasterGraphicContext) Stroke(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.strokeRasterizer.UseNonZeroWinding = true

	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
	stroker.HalfLineWidth = rgc.current.LineWidth / 2

	var liner Flattener
	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
	} else {
		liner = stroker
	}
	for _, p := range paths {
		Flatten(p, liner, rgc.current.Tr.GetScale())
	}

	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
}

// Fill fills the paths with the color specified by SetFillColor
func (rgc *RasterGraphicContext) Fill(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding

	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}
	for _, p := range paths {
		Flatten(p, flattener, rgc.current.Tr.GetScale())
	}

	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
}

// FillStroke first fills the paths and than strokes them
func (rgc *RasterGraphicContext) FillStroke(paths ...*Path) {
	paths = append(paths, rgc.current.Path)
	rgc.fillRasterizer.UseNonZeroWinding = rgc.current.FillRule == FillRuleWinding
	rgc.strokeRasterizer.UseNonZeroWinding = true

	flattener := Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.fillRasterizer}}

	stroker := NewLineStroker(rgc.current.Cap, rgc.current.Join, Transformer{Tr: rgc.current.Tr, Flattener: FtLineBuilder{Adder: rgc.strokeRasterizer}})
	stroker.HalfLineWidth = rgc.current.LineWidth / 2

	var liner Flattener
	if rgc.current.Dash != nil && len(rgc.current.Dash) > 0 {
		liner = NewDashVertexConverter(rgc.current.Dash, rgc.current.DashOffset, stroker)
	} else {
		liner = stroker
	}

	demux := DemuxFlattener{Flatteners: []Flattener{flattener, liner}}
	for _, p := range paths {
		Flatten(p, demux, rgc.current.Tr.GetScale())
	}

	// Fill
	rgc.paint(rgc.fillRasterizer, rgc.current.FillColor)
	// Stroke
	rgc.paint(rgc.strokeRasterizer, rgc.current.StrokeColor)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/coreos/rocket/app-container/discovery"
)

var (
	cmdFetch = &Command{
		Name:        "fetch",
		Description: "Discover and download an app container image",
		Summary:     "Discover, download and store on disk the app container image for one or more apps",
		Run:         runFetch,
	}
)

func runFetch(args []string) (exit int) {
	if len(args) < 1 {
		fmt.Fprintf(os.Stderr, "discover: at least one name required")
	}
	q := globalFlags.Quiet

	for _, name := range args {
		app, err := discovery.NewAppFromString(name)
		if err != nil {
			stderr(q, "%s: %s", name, err)
			return 1
		}
		eps, err := discovery.DiscoverEndpoints(*app, transportFlags.Insecure)
		if err != nil {
			stderr(q, "error fetching %s: %s", name, err)
			return 1
		}
		// TODO(philips): store the images..
		fmt.Println(strings.Join(eps.Sig, ","))
		fmt.Println(strings.Join(eps.ACI, ","))
		fmt.Println(strings.Join(eps.Keys, ","))
	}

	return
}
<commit_msg>actool: remove the fetch subcommand<commit_after><|endoftext|>