<commit_before>// Copyright 2017 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 text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"
	"sync"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
)

var (
	monotonicClock int64
)

func now() int64 {
	return monotonicClock
}

func init() {
	hooks.AppendHookOnBeforeUpdate(func() error {
		monotonicClock++
		return nil
	})
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
}

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, dx, dy fixed.Int26_6, op *ebiten.DrawImageOptions) {
	if img == nil {
		return
	}

	b := getGlyphBounds(face, r)
	op2 := &ebiten.DrawImageOptions{}
	if op != nil {
		*op2 = *op
	}
	op2.GeoM.Reset()
	op2.GeoM.Translate(float64((dx+b.Min.X)>>6), float64((dy+b.Min.Y)>>6))
	if op != nil {
		op2.GeoM.Concat(op.GeoM)
	}
	dst.DrawImage(img, op2)
}

var (
	glyphBoundsCache = map[font.Face]map[rune]fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = b
	return b
}

type glyphImageCacheEntry struct {
	image *ebiten.Image
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
)

func getGlyphImage(face font.Face, r rune) *ebiten.Image {
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	if e, ok := glyphImageCache[face][r]; ok {
		e.atime = now()
		return e.image
	}

	b := getGlyphBounds(face, r)
	w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
	if w == 0 || h == 0 {
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: nil,
			atime: now(),
		}
		return nil
	}

	if b.Min.X&((1<<6)-1) != 0 {
		w++
	}
	if b.Min.Y&((1<<6)-1) != 0 {
		h++
	}
	rgba := image.NewRGBA(image.Rect(0, 0, w, h))

	d := font.Drawer{
		Dst:  rgba,
		Src:  image.White,
		Face: face,
	}
	x, y := -b.Min.X, -b.Min.Y
	x, y = fixed.I(x.Ceil()), fixed.I(y.Ceil())
	d.Dot = fixed.Point26_6{X: x, Y: y}
	d.DrawString(string(r))

	img := ebiten.NewImageFromImage(rgba)
	if _, ok := glyphImageCache[face][r]; !ok {
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: img,
			atime: now(),
		}
	}

	return img
}

var textM sync.Mutex

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// This means that if the given text consisted of a single character ".",
// it would be positioned at the given position (x, y).
// Be careful that this doesn't represent left-upper corner position.
//
// clr is the color for text rendering.
//
// If you want to adjust the position of the text, these functions are useful:
//
//     * text.BoundString:                     the rendered bounds of the given text.
//     * golang.org/x/image/font.Face.Metrics: the metrics of the face.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at Draw are cached.
// The cache is shared with CacheGlyphs.
//
// It is OK to call Draw with a same text and a same face at every frame in terms of performance.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// Draw is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	cr, cg, cb, ca := clr.RGBA()
	if ca == 0 {
		return
	}

	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(float64(x), float64(y))
	op.ColorM.Scale(float64(cr)/float64(ca), float64(cg)/float64(ca), float64(cb)/float64(ca), float64(ca)/0xffff)
	DrawWithOptions(dst, text, face, op)
}

// DrawWithOptions draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// op is the options to draw glyph images.
// The origin point is a 'dot' (period) position.
// Be careful that the origin point is not left-upper corner position of dst.
// The default glyph color is while. op's ColorM adjusts the color.
//
// If you want to adjust the position of the text, these functions are useful:
//
//     * text.BoundString:                     the rendered bounds of the given text.
//     * golang.org/x/image/font.Face.Metrics: the metrics of the face.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at DrawWithOptions are cached.
// The cache is shared with CacheGlyphs.
//
// It is OK to call DrawWithOptions with a same text and a same face at every frame in terms of performance.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// DrawWithOptions is concurrent-safe.
func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions) {
	textM.Lock()
	defer textM.Unlock()

	var dx, dy fixed.Int26_6
	prevR := rune(-1)

	faceHeight := face.Metrics().Height

	for _, r := range text {
		if prevR >= 0 {
			dx += face.Kern(prevR, r)
		}
		if r == '\n' {
			dx = 0
			dy += faceHeight
			prevR = rune(-1)
			continue
		}

		img := getGlyphImage(face, r)
		drawGlyph(dst, face, r, img, dx, dy, options)
		dx += glyphAdvance(face, r)

		prevR = r
	}

	// cacheSoftLimit indicates the soft limit of the number of glyphs in the cache.
	// If the number of glyphs exceeds this soft limits, old glyphs are removed.
	// Even after clearning up the cache, the number of glyphs might still exceeds the soft limit, but
	// this is fine.
	const cacheSoftLimit = 512

	// Clean up the cache.
	if len(glyphImageCache[face]) > cacheSoftLimit {
		for r, e := range glyphImageCache[face] {
			// 60 is an arbitrary number.
			if e.atime < now()-60 {
				delete(glyphImageCache[face], r)
			}
		}
	}
}

// BoundString returns the measured size of a given string using a given font.
// This method will return the exact size in pixels that a string drawn by Draw will be.
// The bound's origin point indicates the dot (period) position.
// This means that if the text consists of one character '.', this dot is rendered at (0, 0).
//
// This is very similar to golang.org/x/image/font's BoundString,
// but this BoundString calculates the actual rendered area considering multiple lines and space characters.
//
// face is the font for text rendering.
// text is the string that's being measured.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// BoundString is concurrent-safe.
func BoundString(face font.Face, text string) image.Rectangle {
	textM.Lock()
	defer textM.Unlock()

	m := face.Metrics()
	faceHeight := m.Height

	fx, fy := fixed.I(0), fixed.I(0)
	prevR := rune(-1)

	var bounds fixed.Rectangle26_6
	for _, r := range text {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(0)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		b := getGlyphBounds(face, r)
		b.Min.X += fx
		b.Max.X += fx
		b.Min.Y += fy
		b.Max.Y += fy
		bounds = bounds.Union(b)

		fx += glyphAdvance(face, r)
		prevR = r
	}

	return image.Rect(
		int(math.Floor(fixed26_6ToFloat64(bounds.Min.X))),
		int(math.Floor(fixed26_6ToFloat64(bounds.Min.Y))),
		int(math.Ceil(fixed26_6ToFloat64(bounds.Max.X))),
		int(math.Ceil(fixed26_6ToFloat64(bounds.Max.Y))),
	)
}

// CacheGlyphs precaches the glyphs for the given text and the given font face into the cache.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
// The cache is shared with Draw.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Draw automatically creates and caches necessary glyphs, so usually you don't have to call CacheGlyphs
// explicitly. However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph
// cache and render it for each rune. This is very inefficient because creating a glyph image and rendering it are
// different operations (`(*ebiten.Image).ReplacePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as
// one draw call. CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely
// merged into one draw call regardless of the size of the text.
//
// If a rune's glyph is already cached, CacheGlyphs does nothing for the rune.
func CacheGlyphs(face font.Face, text string) {
	textM.Lock()
	defer textM.Unlock()

	for _, r := range text {
		getGlyphImage(face, r)
	}
}

// FaceWithLineHeight returns a font.Face with the given lineHeight in pixels.
// The returned face will otherwise have the same glyphs and metrics as face.
func FaceWithLineHeight(face font.Face, lineHeight float64) font.Face {
	return faceWithLineHeight{
		face:       face,
		lineHeight: fixed.Int26_6(lineHeight * (1 << 6)),
	}
}

type faceWithLineHeight struct {
	face       font.Face
	lineHeight fixed.Int26_6
}

func (f faceWithLineHeight) Close() error {
	return f.face.Close()
}

func (f faceWithLineHeight) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
	return f.face.Glyph(dot, r)
}

func (f faceWithLineHeight) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
	return f.face.GlyphBounds(r)
}

func (f faceWithLineHeight) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
	return f.face.GlyphAdvance(r)
}

func (f faceWithLineHeight) Kern(r0, r1 rune) fixed.Int26_6 {
	return f.face.Kern(r0, r1)
}

func (f faceWithLineHeight) Metrics() font.Metrics {
	m := f.face.Metrics()
	m.Height = f.lineHeight
	return m
}
<commit_msg>text: Refactoring<commit_after>// Copyright 2017 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 text offers functions to draw texts on an Ebiten's image.
//
// For the example using a TTF font, see font package in the examples.
package text

import (
	"image"
	"image/color"
	"math"
	"sync"

	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
)

var (
	monotonicClock int64
)

func now() int64 {
	return monotonicClock
}

func init() {
	hooks.AppendHookOnBeforeUpdate(func() error {
		monotonicClock++
		return nil
	})
}

func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
	return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
}

func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, dx, dy fixed.Int26_6, op *ebiten.DrawImageOptions) {
	if img == nil {
		return
	}

	b := getGlyphBounds(face, r)
	op2 := &ebiten.DrawImageOptions{}
	if op != nil {
		*op2 = *op
		op2.GeoM.Reset()
	}
	op2.GeoM.Translate(math.Floor(fixed26_6ToFloat64(dx+b.Min.X)), math.Floor(fixed26_6ToFloat64(dy+b.Min.Y)))
	if op != nil {
		op2.GeoM.Concat(op.GeoM)
	}
	dst.DrawImage(img, op2)
}

var (
	glyphBoundsCache = map[font.Face]map[rune]fixed.Rectangle26_6{}
)

func getGlyphBounds(face font.Face, r rune) fixed.Rectangle26_6 {
	if _, ok := glyphBoundsCache[face]; !ok {
		glyphBoundsCache[face] = map[rune]fixed.Rectangle26_6{}
	}
	if b, ok := glyphBoundsCache[face][r]; ok {
		return b
	}
	b, _, _ := face.GlyphBounds(r)
	glyphBoundsCache[face][r] = b
	return b
}

type glyphImageCacheEntry struct {
	image *ebiten.Image
	atime int64
}

var (
	glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
)

func getGlyphImage(face font.Face, r rune) *ebiten.Image {
	if _, ok := glyphImageCache[face]; !ok {
		glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
	}

	if e, ok := glyphImageCache[face][r]; ok {
		e.atime = now()
		return e.image
	}

	b := getGlyphBounds(face, r)
	w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
	if w == 0 || h == 0 {
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: nil,
			atime: now(),
		}
		return nil
	}

	if b.Min.X&((1<<6)-1) != 0 {
		w++
	}
	if b.Min.Y&((1<<6)-1) != 0 {
		h++
	}
	rgba := image.NewRGBA(image.Rect(0, 0, w, h))

	d := font.Drawer{
		Dst:  rgba,
		Src:  image.White,
		Face: face,
	}
	x, y := -b.Min.X, -b.Min.Y
	x, y = fixed.I(x.Ceil()), fixed.I(y.Ceil())
	d.Dot = fixed.Point26_6{X: x, Y: y}
	d.DrawString(string(r))

	img := ebiten.NewImageFromImage(rgba)
	if _, ok := glyphImageCache[face][r]; !ok {
		glyphImageCache[face][r] = &glyphImageCacheEntry{
			image: img,
			atime: now(),
		}
	}

	return img
}

var textM sync.Mutex

// Draw draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// (x, y) represents a 'dot' (period) position.
// This means that if the given text consisted of a single character ".",
// it would be positioned at the given position (x, y).
// Be careful that this doesn't represent left-upper corner position.
//
// clr is the color for text rendering.
//
// If you want to adjust the position of the text, these functions are useful:
//
//     * text.BoundString:                     the rendered bounds of the given text.
//     * golang.org/x/image/font.Face.Metrics: the metrics of the face.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at Draw are cached.
// The cache is shared with CacheGlyphs.
//
// It is OK to call Draw with a same text and a same face at every frame in terms of performance.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// Draw is concurrent-safe.
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
	cr, cg, cb, ca := clr.RGBA()
	if ca == 0 {
		return
	}

	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(float64(x), float64(y))
	op.ColorM.Scale(float64(cr)/float64(ca), float64(cg)/float64(ca), float64(cb)/float64(ca), float64(ca)/0xffff)
	DrawWithOptions(dst, text, face, op)
}

// DrawWithOptions draws a given text on a given destination image dst.
//
// face is the font for text rendering.
// op is the options to draw glyph images.
// The origin point is a 'dot' (period) position.
// Be careful that the origin point is not left-upper corner position of dst.
// The default glyph color is while. op's ColorM adjusts the color.
//
// If you want to adjust the position of the text, these functions are useful:
//
//     * text.BoundString:                     the rendered bounds of the given text.
//     * golang.org/x/image/font.Face.Metrics: the metrics of the face.
//
// The '\n' newline character puts the following text on the next line.
// Line height is based on Metrics().Height of the font.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at DrawWithOptions are cached.
// The cache is shared with CacheGlyphs.
//
// It is OK to call DrawWithOptions with a same text and a same face at every frame in terms of performance.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// DrawWithOptions is concurrent-safe.
func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions) {
	textM.Lock()
	defer textM.Unlock()

	var dx, dy fixed.Int26_6
	prevR := rune(-1)

	faceHeight := face.Metrics().Height

	for _, r := range text {
		if prevR >= 0 {
			dx += face.Kern(prevR, r)
		}
		if r == '\n' {
			dx = 0
			dy += faceHeight
			prevR = rune(-1)
			continue
		}

		img := getGlyphImage(face, r)
		drawGlyph(dst, face, r, img, dx, dy, options)
		dx += glyphAdvance(face, r)

		prevR = r
	}

	// cacheSoftLimit indicates the soft limit of the number of glyphs in the cache.
	// If the number of glyphs exceeds this soft limits, old glyphs are removed.
	// Even after clearning up the cache, the number of glyphs might still exceeds the soft limit, but
	// this is fine.
	const cacheSoftLimit = 512

	// Clean up the cache.
	if len(glyphImageCache[face]) > cacheSoftLimit {
		for r, e := range glyphImageCache[face] {
			// 60 is an arbitrary number.
			if e.atime < now()-60 {
				delete(glyphImageCache[face], r)
			}
		}
	}
}

// BoundString returns the measured size of a given string using a given font.
// This method will return the exact size in pixels that a string drawn by Draw will be.
// The bound's origin point indicates the dot (period) position.
// This means that if the text consists of one character '.', this dot is rendered at (0, 0).
//
// This is very similar to golang.org/x/image/font's BoundString,
// but this BoundString calculates the actual rendered area considering multiple lines and space characters.
//
// face is the font for text rendering.
// text is the string that's being measured.
//
// Be careful that the passed font face is held by this package and is never released.
// This is a known issue (#498).
//
// BoundString is concurrent-safe.
func BoundString(face font.Face, text string) image.Rectangle {
	textM.Lock()
	defer textM.Unlock()

	m := face.Metrics()
	faceHeight := m.Height

	fx, fy := fixed.I(0), fixed.I(0)
	prevR := rune(-1)

	var bounds fixed.Rectangle26_6
	for _, r := range text {
		if prevR >= 0 {
			fx += face.Kern(prevR, r)
		}
		if r == '\n' {
			fx = fixed.I(0)
			fy += faceHeight
			prevR = rune(-1)
			continue
		}

		b := getGlyphBounds(face, r)
		b.Min.X += fx
		b.Max.X += fx
		b.Min.Y += fy
		b.Max.Y += fy
		bounds = bounds.Union(b)

		fx += glyphAdvance(face, r)
		prevR = r
	}

	return image.Rect(
		int(math.Floor(fixed26_6ToFloat64(bounds.Min.X))),
		int(math.Floor(fixed26_6ToFloat64(bounds.Min.Y))),
		int(math.Ceil(fixed26_6ToFloat64(bounds.Max.X))),
		int(math.Ceil(fixed26_6ToFloat64(bounds.Max.Y))),
	)
}

// CacheGlyphs precaches the glyphs for the given text and the given font face into the cache.
//
// Glyphs used for rendering are cached in least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
// The cache is shared with Draw.
//
// Draw/DrawWithOptions and CacheGlyphs are implemented like this:
//
//     Draw        = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//                 + Draw them onto the destination by `(*ebiten.Image).DrawImage`
//     CacheGlyphs = Create glyphs by `(*ebiten.Image).ReplacePixels` and put them into the cache if necessary
//
// Draw automatically creates and caches necessary glyphs, so usually you don't have to call CacheGlyphs
// explicitly. However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph
// cache and render it for each rune. This is very inefficient because creating a glyph image and rendering it are
// different operations (`(*ebiten.Image).ReplacePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as
// one draw call. CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely
// merged into one draw call regardless of the size of the text.
//
// If a rune's glyph is already cached, CacheGlyphs does nothing for the rune.
func CacheGlyphs(face font.Face, text string) {
	textM.Lock()
	defer textM.Unlock()

	for _, r := range text {
		getGlyphImage(face, r)
	}
}

// FaceWithLineHeight returns a font.Face with the given lineHeight in pixels.
// The returned face will otherwise have the same glyphs and metrics as face.
func FaceWithLineHeight(face font.Face, lineHeight float64) font.Face {
	return faceWithLineHeight{
		face:       face,
		lineHeight: fixed.Int26_6(lineHeight * (1 << 6)),
	}
}

type faceWithLineHeight struct {
	face       font.Face
	lineHeight fixed.Int26_6
}

func (f faceWithLineHeight) Close() error {
	return f.face.Close()
}

func (f faceWithLineHeight) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
	return f.face.Glyph(dot, r)
}

func (f faceWithLineHeight) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
	return f.face.GlyphBounds(r)
}

func (f faceWithLineHeight) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
	return f.face.GlyphAdvance(r)
}

func (f faceWithLineHeight) Kern(r0, r1 rune) fixed.Int26_6 {
	return f.face.Kern(r0, r1)
}

func (f faceWithLineHeight) Metrics() font.Metrics {
	m := f.face.Metrics()
	m.Height = f.lineHeight
	return m
}
<|endoftext|>


<commit_before>package textmagic

import (
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"github.com/MJKWoolnough/memio"
)

var apiURLPrefix = "https://www.textmagic.com/app/api?"

const (
	cmdAccount       = "account"
	cmdCheckNumber   = "check_number"
	cmdDeleteReply   = "delete_reply"
	cmdMessageStatus = "message_status"
	cmdReceive       = "receive"
	cmdSend          = "send"
)

type TextMagic struct {
	username, password string
}

func New(username, password string) TextMagic {
	return TextMagic{username, password}
}

func (t TextMagic) sendAPI(cmd string, params url.Values, data interface{}) error {
	params.Set("username", t.username)
	params.Set("password", t.password)
	params.Set("cmd", cmd)
	r, err := http.Get(apiURLPrefix + params.Encode())
	if err != nil {
		return RequestError{cmd, err}
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		return StatusError{cmd, r.StatusCode}
	}
	jsonData := make([]byte, r.ContentLength) // avoid allocation using io.Pipe?
	var apiError APIError
	err = json.NewDecoder(io.TeeReader(r.Body, memio.Create(&jsonData))).Decode(&apiError)
	if err != nil {
		return JSONError{cmd, err}
	}
	if apiError.Code != 0 {
		apiError.Cmd = cmd
		return apiError
	}
	json.Unmarshal(jsonData, data)
	return nil
}

type balance struct {
	Balance float32 `json:"balance"`
}

func (t TextMagic) Account() (float32, error) {
	var b balance
	if err := t.sendAPI(cmdAccount, url.Values{}, &b); err != nil {
		return 0, err
	}
	return b.Balance, nil
}

type DeliveryNotificationCode string

func (d DeliveryNotificationCode) Status() string {
	switch d {
	case "q", "r", "a", "b", "s":
		return "intermediate"
	case "d", "f", "e", "j", "u":
		return "final"
	}
	return "unknown"
}

func (d DeliveryNotificationCode) String() string {
	switch d {
	case "q":
		return "The message is queued on the TextMagic server."
	case "r":
		return "The message has been sent to the mobile operator."
	case "a":
		return "The mobile operator has acknowledged the message."
	case "b":
		return "The mobile operator has queued the message."
	case "d":
		return "The message has been successfully delivered to the handset."
	case "f":
		return "An error occurred while delivering message."
	case "e":
		return "An error occurred while sending message."
	case "j":
		return "The mobile operator has rejected the message."
	case "s":
		return "This message is scheduled to be sent later."
	default:
		return "The status is unknown."

	}
}

type Status struct {
	Text      string                   `json:"text"`
	Status    DeliveryNotificationCode `json:"status"`
	Created   int64                    `json:"created_time"`
	Reply     string                   `json:"reply_number"`
	Cost      float32                  `json:"credits_cost"`
	Completed int64                    `json:"completed_time"`
}

func (t TextMagic) MessageStatus(ids []uint) (map[uint]Status, error) {
	statuses := make(map[uint]Status)
	for _, tIds := range splitSlice(ids) {
		messageIds := joinUints(tIds)
		strStatuses := make(map[string]Status)
		err := t.sendAPI(cmdMessageStatus, url.Values{"ids": {messageIds}}, strStatuses)
		if err != nil {
			return statuses, err
		}
		for messageID, status := range strStatuses {
			id, err := strconv.Atoi(messageID)
			if err != nil {
				continue
			}
			statuses[uint(id)] = status
		}
	}
	return statuses, nil
}

type Number struct {
	Price   float32 `json:"price"`
	Country string  `json:"country"`
}

func (t TextMagic) CheckNumber(numbers []uint) (map[uint]Number, error) {
	ns := make(map[string]Number)
	if err := t.sendAPI(cmdCheckNumber, url.Values{"phone": {joinUints(numbers)}}, ns); err != nil {
		return nil, err
	}
	toRet := make(map[uint]Number)
	for n, data := range ns {
		number, err := strconv.Atoi(n)
		if err != nil {
			continue
		}
		toRet[uint(number)] = data
	}
	return toRet, nil
}

type deleted struct {
	Deleted []uint `json:"deleted"`
}

func (t TextMagic) DeleteReply(ids []uint) ([]uint, error) {
	toRet := make([]uint, 0, len(ids))
	for _, tIds := range splitSlice(ids) {
		var d deleted
		if err := t.sendAPI(cmdDeleteReply, url.Values{"deleted": {joinUints(tIds)}}, &d); err != nil {
			return toRet, err
		}
		toRet = append(toRet, d.Deleted...)
	}
	return toRet, nil
}

type Message struct {
	ID        uint   `json:"message_id"`
	From      uint   `json:"from"`
	Timestamp int64  `json:"timestamp"`
	Text      string `json:"text"`
}

type received struct {
	Messages []Message `json:"messages"`
	Unread   uint      `json:"unread"`
}

func (t TextMagic) Receive(lastRetrieved uint) (uint, []Message, error) {
	var r received
	err := t.sendAPI(cmdReceive, url.Values{"last_retrieved_id": {strconv.Itoa(int(lastRetrieved))}}, &r)
	return r.Unread, r.Messages, err
}

const joinSep = ','

func joinUints(u []uint) string {
	toStr := make([]byte, 0, 10*len(u))
	var digits [21]byte
	for n, num := range u {
		if n > 0 {
			toStr = append(toStr, joinSep)
		}
		if num == 0 {
			toStr = append(toStr, '0')
			continue
		}
		pos := 21
		for ; num > 0; num /= 10 {
			pos--
			digits[pos] = '0' + byte(num%10)
		}
		toStr = append(toStr, digits[pos:]...)
	}
	return string(toStr)
}

const maxInSlice = 100

func splitSlice(slice []uint) [][]uint {
	toRet := make([][]uint, 0, len(slice)/maxInSlice+1)
	for len(slice) > 100 {
		toRet = append(toRet, slice[:100])
		slice = slice[100:]
	}
	if len(slice) > 0 {
		toRet = append(toRet, slice)
	}
	return toRet
}

// Errors

type APIError struct {
	Cmd     string
	Code    int    `json:"error_code"`
	Message string `json:"error_message"`
}

func (a APIError) Error() string {
	return "command " + a.Cmd + " returned the following API error: " + a.Message
}

type RequestError struct {
	Cmd string
	Err error
}

func (r RequestError) Error() string {
	return "command " + r.Cmd + " returned the following error while makeing the API call: " + r.Err.Error()
}

type StatusError struct {
	Cmd        string
	StatusCode int
}

func (s StatusError) Error() string {
	return "command " + s.Cmd + " returned a non-200 OK response: " + http.StatusText(s.StatusCode)
}

type JSONError struct {
	Cmd string
	Err error
}

func (j JSONError) Error() string {
	return "command " + j.Cmd + " returned malformed JSON: " + j.Err.Error()
}
<commit_msg>Replaced uint with uint64<commit_after>package textmagic

import (
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"github.com/MJKWoolnough/memio"
)

var apiURLPrefix = "https://www.textmagic.com/app/api?"

const (
	cmdAccount       = "account"
	cmdCheckNumber   = "check_number"
	cmdDeleteReply   = "delete_reply"
	cmdMessageStatus = "message_status"
	cmdReceive       = "receive"
	cmdSend          = "send"
)

type TextMagic struct {
	username, password string
}

func New(username, password string) TextMagic {
	return TextMagic{username, password}
}

func (t TextMagic) sendAPI(cmd string, params url.Values, data interface{}) error {
	params.Set("username", t.username)
	params.Set("password", t.password)
	params.Set("cmd", cmd)
	r, err := http.Get(apiURLPrefix + params.Encode())
	if err != nil {
		return RequestError{cmd, err}
	}
	defer r.Body.Close()
	if r.StatusCode != http.StatusOK {
		return StatusError{cmd, r.StatusCode}
	}
	jsonData := make([]byte, r.ContentLength) // avoid allocation using io.Pipe?
	var apiError APIError
	err = json.NewDecoder(io.TeeReader(r.Body, memio.Create(&jsonData))).Decode(&apiError)
	if err != nil {
		return JSONError{cmd, err}
	}
	if apiError.Code != 0 {
		apiError.Cmd = cmd
		return apiError
	}
	json.Unmarshal(jsonData, data)
	return nil
}

type balance struct {
	Balance float32 `json:"balance"`
}

func (t TextMagic) Account() (float32, error) {
	var b balance
	if err := t.sendAPI(cmdAccount, url.Values{}, &b); err != nil {
		return 0, err
	}
	return b.Balance, nil
}

type DeliveryNotificationCode string

func (d DeliveryNotificationCode) Status() string {
	switch d {
	case "q", "r", "a", "b", "s":
		return "intermediate"
	case "d", "f", "e", "j", "u":
		return "final"
	}
	return "unknown"
}

func (d DeliveryNotificationCode) String() string {
	switch d {
	case "q":
		return "The message is queued on the TextMagic server."
	case "r":
		return "The message has been sent to the mobile operator."
	case "a":
		return "The mobile operator has acknowledged the message."
	case "b":
		return "The mobile operator has queued the message."
	case "d":
		return "The message has been successfully delivered to the handset."
	case "f":
		return "An error occurred while delivering message."
	case "e":
		return "An error occurred while sending message."
	case "j":
		return "The mobile operator has rejected the message."
	case "s":
		return "This message is scheduled to be sent later."
	default:
		return "The status is unknown."

	}
}

type Status struct {
	Text      string                   `json:"text"`
	Status    DeliveryNotificationCode `json:"status"`
	Created   int64                    `json:"created_time"`
	Reply     string                   `json:"reply_number"`
	Cost      float32                  `json:"credits_cost"`
	Completed int64                    `json:"completed_time"`
}

func (t TextMagic) MessageStatus(ids []uint64) (map[uint64]Status, error) {
	statuses := make(map[uint64]Status)
	for _, tIds := range splitSlice(ids) {
		messageIds := joinUints(tIds)
		strStatuses := make(map[string]Status)
		err := t.sendAPI(cmdMessageStatus, url.Values{"ids": {messageIds}}, strStatuses)
		if err != nil {
			return statuses, err
		}
		for messageID, status := range strStatuses {
			id, err := strconv.Atoi(messageID)
			if err != nil {
				continue
			}
			statuses[uint64(id)] = status
		}
	}
	return statuses, nil
}

type Number struct {
	Price   float32 `json:"price"`
	Country string  `json:"country"`
}

func (t TextMagic) CheckNumber(numbers []uint64) (map[uint64]Number, error) {
	ns := make(map[string]Number)
	if err := t.sendAPI(cmdCheckNumber, url.Values{"phone": {joinUints(numbers)}}, ns); err != nil {
		return nil, err
	}
	toRet := make(map[uint64]Number)
	for n, data := range ns {
		number, err := strconv.Atoi(n)
		if err != nil {
			continue
		}
		toRet[uint64(number)] = data
	}
	return toRet, nil
}

type deleted struct {
	Deleted []uint64 `json:"deleted"`
}

func (t TextMagic) DeleteReply(ids []uint64) ([]uint64, error) {
	toRet := make([]uint64, 0, len(ids))
	for _, tIds := range splitSlice(ids) {
		var d deleted
		if err := t.sendAPI(cmdDeleteReply, url.Values{"deleted": {joinUints(tIds)}}, &d); err != nil {
			return toRet, err
		}
		toRet = append(toRet, d.Deleted...)
	}
	return toRet, nil
}

type Message struct {
	ID        uint64 `json:"message_id"`
	From      uint64 `json:"from"`
	Timestamp int64  `json:"timestamp"`
	Text      string `json:"text"`
}

type received struct {
	Messages []Message `json:"messages"`
	Unread   uint64    `json:"unread"`
}

func (t TextMagic) Receive(lastRetrieved uint64) (uint64, []Message, error) {
	var r received
	err := t.sendAPI(cmdReceive, url.Values{"last_retrieved_id": {strconv.Itoa(int(lastRetrieved))}}, &r)
	return r.Unread, r.Messages, err
}

func (t TextMagic) Send() {

}

const joinSep = ','

func joinUints(u []uint64) string {
	toStr := make([]byte, 0, 10*len(u))
	var digits [21]byte
	for n, num := range u {
		if n > 0 {
			toStr = append(toStr, joinSep)
		}
		if num == 0 {
			toStr = append(toStr, '0')
			continue
		}
		pos := 21
		for ; num > 0; num /= 10 {
			pos--
			digits[pos] = '0' + byte(num%10)
		}
		toStr = append(toStr, digits[pos:]...)
	}
	return string(toStr)
}

const maxInSlice = 100

func splitSlice(slice []uint64) [][]uint64 {
	toRet := make([][]uint64, 0, len(slice)/maxInSlice+1)
	for len(slice) > 100 {
		toRet = append(toRet, slice[:100])
		slice = slice[100:]
	}
	if len(slice) > 0 {
		toRet = append(toRet, slice)
	}
	return toRet
}

// Errors

type APIError struct {
	Cmd     string
	Code    int    `json:"error_code"`
	Message string `json:"error_message"`
}

func (a APIError) Error() string {
	return "command " + a.Cmd + " returned the following API error: " + a.Message
}

type RequestError struct {
	Cmd string
	Err error
}

func (r RequestError) Error() string {
	return "command " + r.Cmd + " returned the following error while makeing the API call: " + r.Err.Error()
}

type StatusError struct {
	Cmd        string
	StatusCode int
}

func (s StatusError) Error() string {
	return "command " + s.Cmd + " returned a non-200 OK response: " + http.StatusText(s.StatusCode)
}

type JSONError struct {
	Cmd string
	Err error
}

func (j JSONError) Error() string {
	return "command " + j.Cmd + " returned malformed JSON: " + j.Err.Error()
}
<|endoftext|>


<commit_before>// +build linux

package cgroups

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/docker/go-units"
)

const (
	cgroupNamePrefix = "name="
	CgroupProcesses  = "cgroup.procs"
)

// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
func FindCgroupMountpoint(subsystem string) (string, error) {
	// We are not using mount.GetMounts() because it's super-inefficient,
	// parsing it directly sped up x10 times because of not using Sscanf.
	// It was one of two major performance drawbacks in container start.
	if !isSubsystemAvailable(subsystem) {
		return "", NewNotFoundError(subsystem)
	}
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		txt := scanner.Text()
		fields := strings.Split(txt, " ")
		for _, opt := range strings.Split(fields[len(fields)-1], ",") {
			if opt == subsystem {
				return fields[4], nil
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}

	return "", NewNotFoundError(subsystem)
}

func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
	if !isSubsystemAvailable(subsystem) {
		return "", "", NewNotFoundError(subsystem)
	}
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return "", "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		txt := scanner.Text()
		fields := strings.Split(txt, " ")
		for _, opt := range strings.Split(fields[len(fields)-1], ",") {
			if opt == subsystem {
				return fields[4], fields[3], nil
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return "", "", err
	}

	return "", "", NewNotFoundError(subsystem)
}

func isSubsystemAvailable(subsystem string) bool {
	cgroups, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return false
	}
	_, avail := cgroups[subsystem]
	return avail
}

func FindCgroupMountpointDir() (string, error) {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		text := scanner.Text()
		fields := strings.Split(text, " ")
		// Safe as mountinfo encodes mountpoints with spaces as \040.
		index := strings.Index(text, " - ")
		postSeparatorFields := strings.Fields(text[index+3:])
		numPostFields := len(postSeparatorFields)

		// This is an error as we can't detect if the mount is for "cgroup"
		if numPostFields == 0 {
			return "", fmt.Errorf("Found no fields post '-' in %q", text)
		}

		if postSeparatorFields[0] == "cgroup" {
			// Check that the mount is properly formated.
			if numPostFields < 3 {
				return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
			}

			return filepath.Dir(fields[4]), nil
		}
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}

	return "", NewNotFoundError("cgroup")
}

type Mount struct {
	Mountpoint string
	Root       string
	Subsystems []string
}

func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) {
	if len(m.Subsystems) == 0 {
		return "", fmt.Errorf("no subsystem for mount")
	}

	return getControllerPath(m.Subsystems[0], cgroups)
}

func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) {
	res := make([]Mount, 0, len(ss))
	scanner := bufio.NewScanner(mi)
	numFound := 0
	for scanner.Scan() && numFound < len(ss) {
		txt := scanner.Text()
		sepIdx := strings.Index(txt, " - ")
		if sepIdx == -1 {
			return nil, fmt.Errorf("invalid mountinfo format")
		}
		if txt[sepIdx+3:sepIdx+9] != "cgroup" {
			continue
		}
		fields := strings.Split(txt, " ")
		m := Mount{
			Mountpoint: fields[4],
			Root:       fields[3],
		}
		for _, opt := range strings.Split(fields[len(fields)-1], ",") {
			if !ss[opt] {
				continue
			}
			if strings.HasPrefix(opt, cgroupNamePrefix) {
				m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
			} else {
				m.Subsystems = append(m.Subsystems, opt)
			}
			if !all {
				numFound++
			}
		}
		res = append(res, m)
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return res, nil
}

// GetCgroupMounts returns the mounts for the cgroup subsystems.
// all indicates whether to return just the first instance or all the mounts.
func GetCgroupMounts(all bool) ([]Mount, error) {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return nil, err
	}

	allMap := make(map[string]bool)
	for s := range allSubsystems {
		allMap[s] = true
	}
	return getCgroupMountsHelper(allMap, f, all)
}

// GetAllSubsystems returns all the cgroup subsystems supported by the kernel
func GetAllSubsystems() ([]string, error) {
	f, err := os.Open("/proc/cgroups")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	subsystems := []string{}

	s := bufio.NewScanner(f)
	for s.Scan() {
		if err := s.Err(); err != nil {
			return nil, err
		}
		text := s.Text()
		if text[0] != '#' {
			parts := strings.Fields(text)
			if len(parts) >= 4 && parts[3] != "0" {
				subsystems = append(subsystems, parts[0])
			}
		}
	}
	return subsystems, nil
}

// GetThisCgroupDir returns the relative path to the cgroup docker is running in.
func GetThisCgroupDir(subsystem string) (string, error) {
	cgroups, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return "", err
	}

	return getControllerPath(subsystem, cgroups)
}

func GetInitCgroupDir(subsystem string) (string, error) {

	cgroups, err := ParseCgroupFile("/proc/1/cgroup")
	if err != nil {
		return "", err
	}

	return getControllerPath(subsystem, cgroups)
}

func readProcsFile(dir string) ([]int, error) {
	f, err := os.Open(filepath.Join(dir, CgroupProcesses))
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var (
		s   = bufio.NewScanner(f)
		out = []int{}
	)

	for s.Scan() {
		if t := s.Text(); t != "" {
			pid, err := strconv.Atoi(t)
			if err != nil {
				return nil, err
			}
			out = append(out, pid)
		}
	}
	return out, nil
}

// ParseCgroupFile parses the given cgroup file, typically from
// /proc/<pid>/cgroup, into a map of subgroups to cgroup names.
func ParseCgroupFile(path string) (map[string]string, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return parseCgroupFromReader(f)
}

// helper function for ParseCgroupFile to make testing easier
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
	s := bufio.NewScanner(r)
	cgroups := make(map[string]string)

	for s.Scan() {
		if err := s.Err(); err != nil {
			return nil, err
		}

		text := s.Text()
		// from cgroups(7):
		// /proc/[pid]/cgroup
		// ...
		// For each cgroup hierarchy ... there is one entry
		// containing three colon-separated fields of the form:
		//     hierarchy-ID:subsystem-list:cgroup-path
		parts := strings.SplitN(text, ":", 3)
		if len(parts) < 3 {
			return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text)
		}

		for _, subs := range strings.Split(parts[1], ",") {
			cgroups[subs] = parts[2]
		}
	}
	return cgroups, nil
}

func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {

	if p, ok := cgroups[subsystem]; ok {
		return p, nil
	}

	if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok {
		return p, nil
	}

	return "", NewNotFoundError(subsystem)
}

func PathExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		return false
	}
	return true
}

func EnterPid(cgroupPaths map[string]string, pid int) error {
	for _, path := range cgroupPaths {
		if PathExists(path) {
			if err := WriteCgroupProc(path, pid); err != nil {
				return err
			}
		}
	}
	return nil
}

// RemovePaths iterates over the provided paths removing them.
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) {
	delay := 10 * time.Millisecond
	for i := 0; i < 5; i++ {
		if i != 0 {
			time.Sleep(delay)
			delay *= 2
		}
		for s, p := range paths {
			os.RemoveAll(p)
			// TODO: here probably should be logging
			_, err := os.Stat(p)
			// We need this strange way of checking cgroups existence because
			// RemoveAll almost always returns error, even on already removed
			// cgroups
			if os.IsNotExist(err) {
				delete(paths, s)
			}
		}
		if len(paths) == 0 {
			return nil
		}
	}
	return fmt.Errorf("Failed to remove paths: %v", paths)
}

func GetHugePageSize() ([]string, error) {
	var pageSizes []string
	sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"}
	files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
	if err != nil {
		return pageSizes, err
	}
	for _, st := range files {
		nameArray := strings.Split(st.Name(), "-")
		pageSize, err := units.RAMInBytes(nameArray[1])
		if err != nil {
			return []string{}, err
		}
		sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)
		pageSizes = append(pageSizes, sizeString)
	}

	return pageSizes, nil
}

// GetPids returns all pids, that were added to cgroup at path.
func GetPids(path string) ([]int, error) {
	return readProcsFile(path)
}

// GetAllPids returns all pids, that were added to cgroup at path and to all its
// subcgroups.
func GetAllPids(path string) ([]int, error) {
	var pids []int
	// collect pids from all sub-cgroups
	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
		dir, file := filepath.Split(p)
		if file != CgroupProcesses {
			return nil
		}
		if iErr != nil {
			return iErr
		}
		cPids, err := readProcsFile(dir)
		if err != nil {
			return err
		}
		pids = append(pids, cPids...)
		return nil
	})
	return pids, err
}

// WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file
func WriteCgroupProc(dir string, pid int) error {
	// Normally dir should not be empty, one case is that cgroup subsystem
	// is not mounted, we will get empty dir, and we want it fail here.
	if dir == "" {
		return fmt.Errorf("no such directory for %s", CgroupProcesses)
	}

	// Dont attach any pid to the cgroup if -1 is specified as a pid
	if pid != -1 {
		if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil {
			return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err)
		}
	}
	return nil
}
<commit_msg>small refactor<commit_after>// +build linux

package cgroups

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/docker/go-units"
)

const (
	cgroupNamePrefix = "name="
	CgroupProcesses  = "cgroup.procs"
)

// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
func FindCgroupMountpoint(subsystem string) (string, error) {
	mnt, _, err := FindCgroupMountpointAndRoot(subsystem)
	return mnt, err
}

func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
	// We are not using mount.GetMounts() because it's super-inefficient,
	// parsing it directly sped up x10 times because of not using Sscanf.
	// It was one of two major performance drawbacks in container start.
	if !isSubsystemAvailable(subsystem) {
		return "", "", NewNotFoundError(subsystem)
	}
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return "", "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		txt := scanner.Text()
		fields := strings.Split(txt, " ")
		for _, opt := range strings.Split(fields[len(fields)-1], ",") {
			if opt == subsystem {
				return fields[4], fields[3], nil
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return "", "", err
	}

	return "", "", NewNotFoundError(subsystem)
}

func isSubsystemAvailable(subsystem string) bool {
	cgroups, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return false
	}
	_, avail := cgroups[subsystem]
	return avail
}

func FindCgroupMountpointDir() (string, error) {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return "", err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		text := scanner.Text()
		fields := strings.Split(text, " ")
		// Safe as mountinfo encodes mountpoints with spaces as \040.
		index := strings.Index(text, " - ")
		postSeparatorFields := strings.Fields(text[index+3:])
		numPostFields := len(postSeparatorFields)

		// This is an error as we can't detect if the mount is for "cgroup"
		if numPostFields == 0 {
			return "", fmt.Errorf("Found no fields post '-' in %q", text)
		}

		if postSeparatorFields[0] == "cgroup" {
			// Check that the mount is properly formated.
			if numPostFields < 3 {
				return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
			}

			return filepath.Dir(fields[4]), nil
		}
	}
	if err := scanner.Err(); err != nil {
		return "", err
	}

	return "", NewNotFoundError("cgroup")
}

type Mount struct {
	Mountpoint string
	Root       string
	Subsystems []string
}

func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) {
	if len(m.Subsystems) == 0 {
		return "", fmt.Errorf("no subsystem for mount")
	}

	return getControllerPath(m.Subsystems[0], cgroups)
}

func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) {
	res := make([]Mount, 0, len(ss))
	scanner := bufio.NewScanner(mi)
	numFound := 0
	for scanner.Scan() && numFound < len(ss) {
		txt := scanner.Text()
		sepIdx := strings.Index(txt, " - ")
		if sepIdx == -1 {
			return nil, fmt.Errorf("invalid mountinfo format")
		}
		if txt[sepIdx+3:sepIdx+9] != "cgroup" {
			continue
		}
		fields := strings.Split(txt, " ")
		m := Mount{
			Mountpoint: fields[4],
			Root:       fields[3],
		}
		for _, opt := range strings.Split(fields[len(fields)-1], ",") {
			if !ss[opt] {
				continue
			}
			if strings.HasPrefix(opt, cgroupNamePrefix) {
				m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
			} else {
				m.Subsystems = append(m.Subsystems, opt)
			}
			if !all {
				numFound++
			}
		}
		res = append(res, m)
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return res, nil
}

// GetCgroupMounts returns the mounts for the cgroup subsystems.
// all indicates whether to return just the first instance or all the mounts.
func GetCgroupMounts(all bool) ([]Mount, error) {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return nil, err
	}

	allMap := make(map[string]bool)
	for s := range allSubsystems {
		allMap[s] = true
	}
	return getCgroupMountsHelper(allMap, f, all)
}

// GetAllSubsystems returns all the cgroup subsystems supported by the kernel
func GetAllSubsystems() ([]string, error) {
	f, err := os.Open("/proc/cgroups")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	subsystems := []string{}

	s := bufio.NewScanner(f)
	for s.Scan() {
		if err := s.Err(); err != nil {
			return nil, err
		}
		text := s.Text()
		if text[0] != '#' {
			parts := strings.Fields(text)
			if len(parts) >= 4 && parts[3] != "0" {
				subsystems = append(subsystems, parts[0])
			}
		}
	}
	return subsystems, nil
}

// GetThisCgroupDir returns the relative path to the cgroup docker is running in.
func GetThisCgroupDir(subsystem string) (string, error) {
	cgroups, err := ParseCgroupFile("/proc/self/cgroup")
	if err != nil {
		return "", err
	}

	return getControllerPath(subsystem, cgroups)
}

func GetInitCgroupDir(subsystem string) (string, error) {

	cgroups, err := ParseCgroupFile("/proc/1/cgroup")
	if err != nil {
		return "", err
	}

	return getControllerPath(subsystem, cgroups)
}

func readProcsFile(dir string) ([]int, error) {
	f, err := os.Open(filepath.Join(dir, CgroupProcesses))
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var (
		s   = bufio.NewScanner(f)
		out = []int{}
	)

	for s.Scan() {
		if t := s.Text(); t != "" {
			pid, err := strconv.Atoi(t)
			if err != nil {
				return nil, err
			}
			out = append(out, pid)
		}
	}
	return out, nil
}

// ParseCgroupFile parses the given cgroup file, typically from
// /proc/<pid>/cgroup, into a map of subgroups to cgroup names.
func ParseCgroupFile(path string) (map[string]string, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return parseCgroupFromReader(f)
}

// helper function for ParseCgroupFile to make testing easier
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
	s := bufio.NewScanner(r)
	cgroups := make(map[string]string)

	for s.Scan() {
		if err := s.Err(); err != nil {
			return nil, err
		}

		text := s.Text()
		// from cgroups(7):
		// /proc/[pid]/cgroup
		// ...
		// For each cgroup hierarchy ... there is one entry
		// containing three colon-separated fields of the form:
		//     hierarchy-ID:subsystem-list:cgroup-path
		parts := strings.SplitN(text, ":", 3)
		if len(parts) < 3 {
			return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text)
		}

		for _, subs := range strings.Split(parts[1], ",") {
			cgroups[subs] = parts[2]
		}
	}
	return cgroups, nil
}

func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {

	if p, ok := cgroups[subsystem]; ok {
		return p, nil
	}

	if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok {
		return p, nil
	}

	return "", NewNotFoundError(subsystem)
}

func PathExists(path string) bool {
	if _, err := os.Stat(path); err != nil {
		return false
	}
	return true
}

func EnterPid(cgroupPaths map[string]string, pid int) error {
	for _, path := range cgroupPaths {
		if PathExists(path) {
			if err := WriteCgroupProc(path, pid); err != nil {
				return err
			}
		}
	}
	return nil
}

// RemovePaths iterates over the provided paths removing them.
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) {
	delay := 10 * time.Millisecond
	for i := 0; i < 5; i++ {
		if i != 0 {
			time.Sleep(delay)
			delay *= 2
		}
		for s, p := range paths {
			os.RemoveAll(p)
			// TODO: here probably should be logging
			_, err := os.Stat(p)
			// We need this strange way of checking cgroups existence because
			// RemoveAll almost always returns error, even on already removed
			// cgroups
			if os.IsNotExist(err) {
				delete(paths, s)
			}
		}
		if len(paths) == 0 {
			return nil
		}
	}
	return fmt.Errorf("Failed to remove paths: %v", paths)
}

func GetHugePageSize() ([]string, error) {
	var pageSizes []string
	sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"}
	files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
	if err != nil {
		return pageSizes, err
	}
	for _, st := range files {
		nameArray := strings.Split(st.Name(), "-")
		pageSize, err := units.RAMInBytes(nameArray[1])
		if err != nil {
			return []string{}, err
		}
		sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)
		pageSizes = append(pageSizes, sizeString)
	}

	return pageSizes, nil
}

// GetPids returns all pids, that were added to cgroup at path.
func GetPids(path string) ([]int, error) {
	return readProcsFile(path)
}

// GetAllPids returns all pids, that were added to cgroup at path and to all its
// subcgroups.
func GetAllPids(path string) ([]int, error) {
	var pids []int
	// collect pids from all sub-cgroups
	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
		dir, file := filepath.Split(p)
		if file != CgroupProcesses {
			return nil
		}
		if iErr != nil {
			return iErr
		}
		cPids, err := readProcsFile(dir)
		if err != nil {
			return err
		}
		pids = append(pids, cPids...)
		return nil
	})
	return pids, err
}

// WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file
func WriteCgroupProc(dir string, pid int) error {
	// Normally dir should not be empty, one case is that cgroup subsystem
	// is not mounted, we will get empty dir, and we want it fail here.
	if dir == "" {
		return fmt.Errorf("no such directory for %s", CgroupProcesses)
	}

	// Dont attach any pid to the cgroup if -1 is specified as a pid
	if pid != -1 {
		if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil {
			return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright ©2014 The gonum 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 path_test

import (
	"math"
	"reflect"
	"testing"

	"github.com/gonum/graph"
	"github.com/gonum/graph/concrete"
	"github.com/gonum/graph/internal"
	"github.com/gonum/graph/path"
	"github.com/gonum/graph/topo"
)

func TestSimpleAStar(t *testing.T) {
	tg, err := internal.NewTileGraphFrom("" +
		"▀  ▀\n" +
		"▀▀ ▀\n" +
		"▀▀ ▀\n" +
		"▀▀ ▀",
	)
	if err != nil {
		t.Fatalf("Couldn't generate tilegraph: %v", err)
	}

	p, cost, _ := path.AStar(concrete.Node(1), concrete.Node(14), tg, nil)
	if math.Abs(cost-4) > 1e-5 {
		t.Errorf("A* reports incorrect cost for simple tilegraph search")
	}

	if p == nil {
		t.Fatalf("A* fails to find path for for simple tilegraph search")
	} else {
		correctPath := []int{1, 2, 6, 10, 14}
		if len(p) != len(correctPath) {
			t.Fatalf("Astar returns wrong length path for simple tilegraph search")
		}
		for i, node := range p {
			if node.ID() != correctPath[i] {
				t.Errorf("Astar returns wrong path at step", i, "got:", node, "actual:", correctPath[i])
			}
		}
	}
}

func TestBiggerAStar(t *testing.T) {
	tg := internal.NewTileGraph(3, 3, true)

	p, cost, _ := path.AStar(concrete.Node(0), concrete.Node(8), tg, nil)

	if math.Abs(cost-4) > 1e-5 || !topo.IsPathIn(tg, p) {
		t.Error("Non-optimal or impossible path found for 3x3 grid")
	}

	tg = internal.NewTileGraph(1000, 1000, true)
	p, cost, _ = path.AStar(concrete.Node(0), concrete.Node(999*1000+999), tg, nil)
	if !topo.IsPathIn(tg, p) || cost != 1998 {
		t.Error("Non-optimal or impossible path found for 100x100 grid; cost:", cost, "path:\n"+tg.PathString(p))
	}
}

func TestObstructedAStar(t *testing.T) {
	tg := internal.NewTileGraph(10, 10, true)

	// Creates a partial "wall" down the middle row with a gap down the left side
	tg.SetPassability(4, 1, false)
	tg.SetPassability(4, 2, false)
	tg.SetPassability(4, 3, false)
	tg.SetPassability(4, 4, false)
	tg.SetPassability(4, 5, false)
	tg.SetPassability(4, 6, false)
	tg.SetPassability(4, 7, false)
	tg.SetPassability(4, 8, false)
	tg.SetPassability(4, 9, false)

	rows, cols := tg.Dimensions()
	p, cost1, expanded := path.AStar(concrete.Node(5), tg.CoordsToNode(rows-1, cols-1), tg, nil)

	if !topo.IsPathIn(tg, p) {
		t.Error("Path doesn't exist in obstructed graph")
	}

	ManhattanHeuristic := func(n1, n2 graph.Node) float64 {
		id1, id2 := n1.ID(), n2.ID()
		r1, c1 := tg.IDToCoords(id1)
		r2, c2 := tg.IDToCoords(id2)

		return math.Abs(float64(r1)-float64(r2)) + math.Abs(float64(c1)-float64(c2))
	}

	p, cost2, expanded2 := path.AStar(concrete.Node(5), tg.CoordsToNode(rows-1, cols-1), tg, ManhattanHeuristic)
	if !topo.IsPathIn(tg, p) {
		t.Error("Path doesn't exist when using heuristic on obstructed graph")
	}

	if math.Abs(cost1-cost2) > 1e-5 {
		t.Error("Cost when using admissible heuristic isn't approximately equal to cost without it")
	}

	if expanded2 > expanded {
		t.Error("Using admissible, consistent heuristic expanded more nodes than null heuristic (possible, but unlikely -- suggests an error somewhere)")
	}

}

func TestNoPathAStar(t *testing.T) {
	tg := internal.NewTileGraph(5, 5, true)

	// Creates a "wall" down the middle row
	tg.SetPassability(2, 0, false)
	tg.SetPassability(2, 1, false)
	tg.SetPassability(2, 2, false)
	tg.SetPassability(2, 3, false)
	tg.SetPassability(2, 4, false)

	rows, _ := tg.Dimensions()
	path, _, _ := path.AStar(tg.CoordsToNode(0, 2), tg.CoordsToNode(rows-1, 2), tg, nil)

	if len(path) > 0 { // Note that a nil slice will return len of 0, this won't panic
		t.Error("A* finds path where none exists")
	}
}

func TestSmallAStar(t *testing.T) {
	g := newSmallGonumGraph()
	heur := newSmallHeuristic()
	if ok, edge, goal := monotonic(g, heur); !ok {
		t.Fatalf("non-monotonic heuristic.  edge: %v goal: %v", edge, goal)
	}

	ps := path.DijkstraAllPaths(g)
	for _, start := range g.Nodes() {
		for _, goal := range g.Nodes() {
			gotPath, gotWeight, _ := path.AStar(start, goal, g, heur)
			wantPath, wantWeight, _ := ps.Between(start, goal)
			if gotWeight != wantWeight {
				t.Errorf("unexpected A* path weight from %v to %v result: got:%s want:%s",
					start, goal, gotWeight, wantWeight)
			}
			if !reflect.DeepEqual(gotPath, wantPath) {
				t.Errorf("unexpected A* path from %v to %v result:\ngot: %v\nwant:%v",
					start, goal, gotPath, wantPath)
			}
		}
	}
}

func newSmallGonumGraph() *concrete.Graph {
	eds := []struct{ n1, n2, edgeCost int }{
		{1, 2, 7},
		{1, 3, 9},
		{1, 6, 14},
		{2, 3, 10},
		{2, 4, 15},
		{3, 4, 11},
		{3, 6, 2},
		{4, 5, 7},
		{5, 6, 9},
	}
	g := concrete.NewGraph()
	for n := concrete.Node(1); n <= 6; n++ {
		g.AddNode(n)
	}
	for _, ed := range eds {
		e := concrete.Edge{
			concrete.Node(ed.n1),
			concrete.Node(ed.n2),
		}
		g.SetEdge(e, float64(ed.edgeCost))
	}
	return g
}

func newSmallHeuristic() func(n1, n2 graph.Node) float64 {
	nds := []struct{ id, x, y int }{
		{1, 0, 6},
		{2, 1, 0},
		{3, 8, 7},
		{4, 16, 0},
		{5, 17, 6},
		{6, 9, 8},
	}
	return func(n1, n2 graph.Node) float64 {
		i1 := n1.ID() - 1
		i2 := n2.ID() - 1
		dx := nds[i2].x - nds[i1].x
		dy := nds[i2].y - nds[i1].y
		return math.Hypot(float64(dx), float64(dy))
	}
}

type costEdgeListGraph interface {
	graph.Weighter
	path.EdgeListerGraph
}

func monotonic(g costEdgeListGraph, heur func(n1, n2 graph.Node) float64) (bool, graph.Edge, graph.Node) {
	for _, goal := range g.Nodes() {
		for _, edge := range g.Edges() {
			from := edge.From()
			to := edge.To()
			if heur(from, goal) > g.Weight(edge)+heur(to, goal) {
				return false, edge, goal
			}
		}
	}
	return true, nil, nil
}
<commit_msg>path: clean up AStar tests<commit_after>// Copyright ©2014 The gonum 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 path_test

import (
	"math"
	"reflect"
	"testing"

	"github.com/gonum/graph"
	"github.com/gonum/graph/concrete"
	"github.com/gonum/graph/internal"
	"github.com/gonum/graph/path"
	"github.com/gonum/graph/topo"
)

var aStarTests = []struct {
	name string
	g    graph.Graph

	s, t      int
	heuristic path.Heuristic
	wantPath  []int
}{
	{
		name: "simple path",
		g: func() graph.Graph {
			tg, err := internal.NewTileGraphFrom("" +
				"▀  ▀\n" +
				"▀▀ ▀\n" +
				"▀▀ ▀\n" +
				"▀▀ ▀",
			)
			if err != nil {
				panic("bad test graph")
			}
			return tg
		}(),

		s: 1, t: 14,
		wantPath: []int{1, 2, 6, 10, 14},
	},
	{
		name: "small open graph",
		g:    internal.NewTileGraph(3, 3, true),

		s: 0, t: 8,
	},
	{
		name: "large open graph",
		g:    internal.NewTileGraph(1000, 1000, true),

		s: 0, t: 999*1000 + 999,
	},
	{
		name: "no path",
		g: func() graph.Graph {
			tg := internal.NewTileGraph(5, 5, true)

			// Create a complete "wall" across the middle row.
			tg.SetPassability(2, 0, false)
			tg.SetPassability(2, 1, false)
			tg.SetPassability(2, 2, false)
			tg.SetPassability(2, 3, false)
			tg.SetPassability(2, 4, false)

			return tg
		}(),

		s: 2, t: 22,
	},
	{
		name: "partially obstructed",
		g: func() graph.Graph {
			tg := internal.NewTileGraph(10, 10, true)

			// Create a partial "wall" accross the middle
			// row with a gap at the left-hand end.
			tg.SetPassability(4, 1, false)
			tg.SetPassability(4, 2, false)
			tg.SetPassability(4, 3, false)
			tg.SetPassability(4, 4, false)
			tg.SetPassability(4, 5, false)
			tg.SetPassability(4, 6, false)
			tg.SetPassability(4, 7, false)
			tg.SetPassability(4, 8, false)
			tg.SetPassability(4, 9, false)

			return tg
		}(),

		s: 5, t: 9*10 + 9,
	},
	{
		name: "partially obstructed with heuristic",
		g: func() graph.Graph {
			tg := internal.NewTileGraph(10, 10, true)

			// Create a partial "wall" accross the middle
			// row with a gap at the left-hand end.
			tg.SetPassability(4, 1, false)
			tg.SetPassability(4, 2, false)
			tg.SetPassability(4, 3, false)
			tg.SetPassability(4, 4, false)
			tg.SetPassability(4, 5, false)
			tg.SetPassability(4, 6, false)
			tg.SetPassability(4, 7, false)
			tg.SetPassability(4, 8, false)
			tg.SetPassability(4, 9, false)

			return tg
		}(),

		s: 5, t: 9*10 + 9,
		// Manhattan Heuristic
		heuristic: func(u, v graph.Node) float64 {
			uid := u.ID()
			cu := (uid % 10)
			ru := (uid - cu) / 10

			vid := v.ID()
			cv := (vid % 10)
			rv := (vid - cv) / 10

			return math.Abs(float64(ru-rv)) + math.Abs(float64(cu-cv))
		},
	},
}

func TestAStar(t *testing.T) {
	for _, test := range aStarTests {
		p, cost, _ := path.AStar(concrete.Node(test.s), concrete.Node(test.t), test.g, test.heuristic)

		if !topo.IsPathIn(test.g, p) {
			t.Error("got path that is not path in input graph for %q", test.name)
		}

		bfp, ok := path.BellmanFordFrom(concrete.Node(test.s), test.g)
		if !ok {
			t.Fatalf("unexpected negative cycle in %q", test.name)
		}
		if want := bfp.WeightTo(concrete.Node(test.t)); cost != want {
			t.Errorf("unexpected cost for %q: got:%v want:%v", test.name, cost, want)
		}

		var got = make([]int, 0, len(p))
		for _, n := range p {
			got = append(got, n.ID())
		}
		if test.wantPath != nil && !reflect.DeepEqual(got, test.wantPath) {
			t.Errorf("unexpected result for %q:\ngot: %v\nwant:%v", test.name, got, test.wantPath)
		}
	}
}

func TestExhaustiveAStar(t *testing.T) {
	g := concrete.NewGraph()
	nodes := []locatedNode{
		1: {id: 1, x: 0, y: 6},
		2: {id: 2, x: 1, y: 0},
		3: {id: 3, x: 8, y: 7},
		4: {id: 4, x: 16, y: 0},
		5: {id: 5, x: 17, y: 6},
		6: {id: 6, x: 9, y: 8},
	}
	for _, n := range nodes[1:] {
		g.AddNode(n)
	}

	edges := []weightedEdge{
		{from: nodes[1], to: nodes[2], cost: 7},
		{from: nodes[1], to: nodes[3], cost: 9},
		{from: nodes[1], to: nodes[6], cost: 14},
		{from: nodes[2], to: nodes[3], cost: 10},
		{from: nodes[2], to: nodes[4], cost: 15},
		{from: nodes[3], to: nodes[4], cost: 11},
		{from: nodes[3], to: nodes[6], cost: 2},
		{from: nodes[4], to: nodes[5], cost: 7},
		{from: nodes[5], to: nodes[6], cost: 9},
	}
	for _, e := range edges {
		g.SetEdge(e, e.cost)
	}

	heuristic := func(u, v graph.Node) float64 {
		lu := u.(locatedNode)
		lv := v.(locatedNode)
		return math.Hypot(lu.x-lv.x, lu.y-lv.y)
	}

	if ok, edge, goal := isMonotonic(g, heuristic); !ok {
		t.Fatalf("non-monotonic heuristic at edge:%v for goal:%v", edge, goal)
	}

	ps := path.DijkstraAllPaths(g)
	for _, start := range g.Nodes() {
		for _, goal := range g.Nodes() {
			gotPath, gotWeight, _ := path.AStar(start, goal, g, heuristic)
			wantPath, wantWeight, _ := ps.Between(start, goal)
			if gotWeight != wantWeight {
				t.Errorf("unexpected path weight from %v to %v result: got:%s want:%s",
					start, goal, gotWeight, wantWeight)
			}
			if !reflect.DeepEqual(gotPath, wantPath) {
				t.Errorf("unexpected path from %v to %v result:\ngot: %v\nwant:%v",
					start, goal, gotPath, wantPath)
			}
		}
	}
}

type locatedNode struct {
	id   int
	x, y float64
}

func (n locatedNode) ID() int { return n.id }

type weightedEdge struct {
	from, to locatedNode
	cost     float64
}

func (e weightedEdge) From() graph.Node { return e.from }
func (e weightedEdge) To() graph.Node   { return e.to }

type costEdgeListGraph interface {
	graph.Weighter
	path.EdgeListerGraph
}

func isMonotonic(g costEdgeListGraph, h path.Heuristic) (ok bool, at graph.Edge, goal graph.Node) {
	for _, goal := range g.Nodes() {
		for _, edge := range g.Edges() {
			from := edge.From()
			to := edge.To()
			if h(from, goal) > g.Weight(edge)+h(to, goal) {
				return false, edge, goal
			}
		}
	}
	return true, nil, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"math"
	"net/http"
	"net/http/httputil"
	"regexp"
	"time"

	"github.com/awslabs/aws-sdk-go/internal/endpoints"
)

type Service struct {
	Config            *Config
	Handlers          Handlers
	ManualSend        bool
	ServiceName       string
	APIVersion        string
	Endpoint          string
	SigningRegion     string
	JSONVersion       string
	TargetPrefix      string
	RetryRules        func(*Request) time.Duration
	ShouldRetry       func(*Request) bool
	DefaultMaxRetries uint
}

var schemeRE = regexp.MustCompile("^([^:]+)://")

func NewService(config *Config) *Service {
	svc := &Service{Config: config}
	svc.Initialize()
	return svc
}

func (s *Service) Initialize() {
	if s.Config == nil {
		s.Config = &Config{}
	}
	if s.Config.HTTPClient == nil {
		s.Config.HTTPClient = http.DefaultClient
	}

	if s.RetryRules == nil {
		s.RetryRules = retryRules
	}

	if s.ShouldRetry == nil {
		s.ShouldRetry = shouldRetry
	}

	s.DefaultMaxRetries = 3
	s.Handlers.Validate.PushBack(ValidateEndpointHandler)
	s.Handlers.Build.PushBack(UserAgentHandler)
	s.Handlers.Sign.PushBack(BuildContentLength)
	s.Handlers.Send.PushBack(SendHandler)
	s.Handlers.Retry.PushBack(RetryHandler)
	s.Handlers.AfterRetry.PushBack(AfterRetryHandler)
	s.Handlers.ValidateResponse.PushBack(ValidateResponseHandler)
	s.AddDebugHandlers()
	s.buildEndpoint()

	if !s.Config.DisableParamValidation {
		s.Handlers.Validate.PushBack(ValidateParameters)
	}
}

func (s *Service) buildEndpoint() {
	if s.Config.Endpoint != "" {
		s.Endpoint = s.Config.Endpoint
	} else {
		s.Endpoint, s.SigningRegion =
			endpoints.EndpointForRegion(s.ServiceName, s.Config.Region)
	}

	if s.Endpoint != "" && !schemeRE.MatchString(s.Endpoint) {
		scheme := "https"
		if s.Config.DisableSSL {
			scheme = "http"
		}
		s.Endpoint = scheme + "://" + s.Endpoint
	}
}

func (s *Service) AddDebugHandlers() {
	out := s.Config.Logger
	if s.Config.LogLevel == 0 {
		return
	}

	s.Handlers.Sign.PushBack(func(r *Request) {
		dumpedBody, _ := httputil.DumpRequest(r.HTTPRequest, true)

		fmt.Fprintf(out, "=> [%s] %s.%s(%+v)\n", r.Time,
			r.Service.ServiceName, r.Operation.Name, r.Params)
		fmt.Fprintf(out, "---[ REQUEST PRE-SIGN ]------------------------------\n")
		fmt.Fprintf(out, "%s\n", string(dumpedBody))
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
	s.Handlers.Send.PushFront(func(r *Request) {
		dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, true)

		fmt.Fprintf(out, "---[ REQUEST POST-SIGN ]-----------------------------\n")
		fmt.Fprintf(out, "%s\n", string(dumpedBody))
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
	s.Handlers.Send.PushBack(func(r *Request) {
		fmt.Fprintf(out, "---[ RESPONSE ]--------------------------------------\n")
		if r.HTTPResponse != nil {
			dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, true)
			fmt.Fprintf(out, "%s\n", string(dumpedBody))
		} else if r.Error != nil {
			fmt.Fprintf(out, "%s\n", r.Error)
		}
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
}

func (s *Service) MaxRetries() uint {
	if s.Config.MaxRetries < 0 {
		return s.DefaultMaxRetries
	} else {
		return uint(s.Config.MaxRetries)
	}
}

func retryRules(r *Request) time.Duration {
	delay := time.Duration(math.Pow(2, float64(r.RetryCount))) * 30
	return delay * time.Millisecond
}

func shouldRetry(r *Request) bool {
	if r.HTTPResponse.StatusCode >= 500 {
		return true
	} else if err := Error(r.Error); err != nil {
		switch err.Code {
		case "ExpiredTokenException":
		case "ProvisionedThroughputExceededException", "Throttling":
			return true
		}
	}
	return false
}
<commit_msg>aws: remove extranous else<commit_after>package aws

import (
	"fmt"
	"math"
	"net/http"
	"net/http/httputil"
	"regexp"
	"time"

	"github.com/awslabs/aws-sdk-go/internal/endpoints"
)

type Service struct {
	Config            *Config
	Handlers          Handlers
	ManualSend        bool
	ServiceName       string
	APIVersion        string
	Endpoint          string
	SigningRegion     string
	JSONVersion       string
	TargetPrefix      string
	RetryRules        func(*Request) time.Duration
	ShouldRetry       func(*Request) bool
	DefaultMaxRetries uint
}

var schemeRE = regexp.MustCompile("^([^:]+)://")

func NewService(config *Config) *Service {
	svc := &Service{Config: config}
	svc.Initialize()
	return svc
}

func (s *Service) Initialize() {
	if s.Config == nil {
		s.Config = &Config{}
	}
	if s.Config.HTTPClient == nil {
		s.Config.HTTPClient = http.DefaultClient
	}

	if s.RetryRules == nil {
		s.RetryRules = retryRules
	}

	if s.ShouldRetry == nil {
		s.ShouldRetry = shouldRetry
	}

	s.DefaultMaxRetries = 3
	s.Handlers.Validate.PushBack(ValidateEndpointHandler)
	s.Handlers.Build.PushBack(UserAgentHandler)
	s.Handlers.Sign.PushBack(BuildContentLength)
	s.Handlers.Send.PushBack(SendHandler)
	s.Handlers.Retry.PushBack(RetryHandler)
	s.Handlers.AfterRetry.PushBack(AfterRetryHandler)
	s.Handlers.ValidateResponse.PushBack(ValidateResponseHandler)
	s.AddDebugHandlers()
	s.buildEndpoint()

	if !s.Config.DisableParamValidation {
		s.Handlers.Validate.PushBack(ValidateParameters)
	}
}

func (s *Service) buildEndpoint() {
	if s.Config.Endpoint != "" {
		s.Endpoint = s.Config.Endpoint
	} else {
		s.Endpoint, s.SigningRegion =
			endpoints.EndpointForRegion(s.ServiceName, s.Config.Region)
	}

	if s.Endpoint != "" && !schemeRE.MatchString(s.Endpoint) {
		scheme := "https"
		if s.Config.DisableSSL {
			scheme = "http"
		}
		s.Endpoint = scheme + "://" + s.Endpoint
	}
}

func (s *Service) AddDebugHandlers() {
	out := s.Config.Logger
	if s.Config.LogLevel == 0 {
		return
	}

	s.Handlers.Sign.PushBack(func(r *Request) {
		dumpedBody, _ := httputil.DumpRequest(r.HTTPRequest, true)

		fmt.Fprintf(out, "=> [%s] %s.%s(%+v)\n", r.Time,
			r.Service.ServiceName, r.Operation.Name, r.Params)
		fmt.Fprintf(out, "---[ REQUEST PRE-SIGN ]------------------------------\n")
		fmt.Fprintf(out, "%s\n", string(dumpedBody))
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
	s.Handlers.Send.PushFront(func(r *Request) {
		dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, true)

		fmt.Fprintf(out, "---[ REQUEST POST-SIGN ]-----------------------------\n")
		fmt.Fprintf(out, "%s\n", string(dumpedBody))
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
	s.Handlers.Send.PushBack(func(r *Request) {
		fmt.Fprintf(out, "---[ RESPONSE ]--------------------------------------\n")
		if r.HTTPResponse != nil {
			dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, true)
			fmt.Fprintf(out, "%s\n", string(dumpedBody))
		} else if r.Error != nil {
			fmt.Fprintf(out, "%s\n", r.Error)
		}
		fmt.Fprintf(out, "-----------------------------------------------------\n")
	})
}

func (s *Service) MaxRetries() uint {
	if s.Config.MaxRetries < 0 {
		return s.DefaultMaxRetries
	} else {
		return uint(s.Config.MaxRetries)
	}
}

func retryRules(r *Request) time.Duration {
	delay := time.Duration(math.Pow(2, float64(r.RetryCount))) * 30
	return delay * time.Millisecond
}

func shouldRetry(r *Request) bool {
	if r.HTTPResponse.StatusCode >= 500 {
		return true
	}
	if err := Error(r.Error); err != nil {
		switch err.Code {
		case "ExpiredTokenException":
		case "ProvisionedThroughputExceededException", "Throttling":
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.5.0"
<commit_msg>Tag release v0.6.0<commit_after>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.6.0"
<|endoftext|>


<commit_before>package medtronic

import (
	"fmt"
	"testing"
	"time"
)

func TestTimeOfDay(t *testing.T) {
	cases := []struct {
		s   string
		t   TimeOfDay
		err error
	}{
		{"00:00", 0, nil},
		{"12:00", Duration(12 * time.Hour).TimeOfDay(), nil},
		{"23:59", Duration(24*time.Hour - 1*time.Minute).TimeOfDay(), nil},
		{"01:02:03", 0, fmt.Errorf("")},
		{"24:00", 0, fmt.Errorf("")},
		{"01:60", 0, fmt.Errorf("")},
	}
	for _, c := range cases {
		t.Run(c.s, func(t *testing.T) {
			if c.err == nil {
				s := c.t.String()
				if s != c.s {
					t.Errorf("%v.String() == %v, want %v", c.t, s, c.s)
				}
			}
			td, err := ParseTimeOfDay(c.s)
			if err == nil {
				if c.err == nil {
					if td == c.t {
						return
					}
					t.Errorf("ParseTimeOfDay(%s) == %v, want %v", c.s, td, c.t)
				} else {
					t.Errorf("ParseTimeOfDay(%s) == %v, want error", c.s, td)
				}
			} else {
				if c.err != nil {
					return
				}
				t.Errorf("ParseTimeOfDay(%s) == %v, want %v", c.s, err, c.t)
			}
		})
	}

}

func TestHalfHours(t *testing.T) {
	cases := []struct {
		h uint8
		d time.Duration
	}{
		{0, 0},
		{1, 30 * time.Minute},
		{3, 90 * time.Minute},
		{4, 2 * time.Hour},
	}
	for _, c := range cases {
		t.Run(fmt.Sprintf("from%d", c.h), func(t *testing.T) {
			d := halfHoursToDuration(c.h)
			if d != Duration(c.d) {
				t.Errorf("halfHoursToDuration(%d) == %v, want %v", c.h, d, c.d)
			}
		})
		t.Run(fmt.Sprintf("to%d", c.h), func(t *testing.T) {
			h := TimeOfDay(c.d).HalfHours()
			if h != c.h {
				t.Errorf("HalfHours(%v) == %d, want %d", c.d, h, c.h)
			}
		})
	}
}

func TestSinceMidnight(t *testing.T) {
	cases := []struct {
		t time.Time
		d TimeOfDay
	}{
		{parseTime("2015-01-01T09:00"), Duration(9 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-15T10:00:00.5"), Duration(10*time.Hour + 500*time.Millisecond).TimeOfDay()},
		{parseTime("2016-06-15T20:30"), Duration(20*time.Hour + 30*time.Minute).TimeOfDay()},
		{parseTime("2010-11-30T23:59:59.999"), Duration(24*time.Hour - time.Millisecond).TimeOfDay()},
		// DST changes
		{parseTime("2016-03-13T01:00"), Duration(1 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-13T03:00"), Duration(3 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-13T12:00"), Duration(12 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T01:00"), Duration(1 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T02:00"), Duration(2 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T03:00"), Duration(3 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T23:00"), Duration(23 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T23:30"), Duration(23*time.Hour + 30*time.Minute).TimeOfDay()},
	}
	for _, c := range cases {
		t.Run(c.t.Format(time.Kitchen), func(t *testing.T) {
			d := SinceMidnight(c.t)
			if d != c.d {
				// Print TimeOfDay as underlying time.Duration.
				t.Errorf("sinceMidnight(%v) == %v, want %v", c.t, time.Duration(d), time.Duration(c.d))
			}
		})
	}
}

func TestDecodeTime(t *testing.T) {
	cases := []struct {
		b []byte
		t time.Time
	}{
		{parseBytes("1F 40 00 01 05"), parseTime("2005-01-01T00:00:31")},
		{parseBytes("75 B7 13 04 10"), parseTime("2016-06-04T19:55:53")},
		{parseBytes("5D B3 0F 06 10"), parseTime("2016-06-06T15:51:29")},
		{parseBytes("40 94 12 0F 10"), parseTime("2016-06-15T18:20:00")},
	}
	for _, c := range cases {
		t.Run(c.t.Format(time.Kitchen), func(t *testing.T) {
			ts := time.Time(decodeTime(c.b))
			if !ts.Equal(c.t) {
				t.Errorf("decodeTime(% X) == %v, want %v", c.b, ts, c.t)
			}
		})
	}
}

func TestDecodeDate(t *testing.T) {
	cases := []struct {
		b []byte
		t time.Time
	}{
		{parseBytes("BF 0F"), parseTime("2015-10-31T00:00")},
		{parseBytes("78 10"), parseTime("2016-06-24T00:00")},
	}
	for _, c := range cases {
		t.Run(c.t.Format("2006-01-02"), func(t *testing.T) {
			ts := time.Time(decodeDate(c.b))
			if !ts.Equal(c.t) {
				t.Errorf("decodeDate(% X) == %v, want %v", c.b, ts, c.t)
			}
		})
	}
}
<commit_msg>Additional decodeTime tests<commit_after>package medtronic

import (
	"fmt"
	"testing"
	"time"
)

func TestTimeOfDay(t *testing.T) {
	cases := []struct {
		s   string
		t   TimeOfDay
		err error
	}{
		{"00:00", 0, nil},
		{"12:00", Duration(12 * time.Hour).TimeOfDay(), nil},
		{"23:59", Duration(24*time.Hour - 1*time.Minute).TimeOfDay(), nil},
		{"01:02:03", 0, fmt.Errorf("")},
		{"24:00", 0, fmt.Errorf("")},
		{"01:60", 0, fmt.Errorf("")},
	}
	for _, c := range cases {
		t.Run(c.s, func(t *testing.T) {
			if c.err == nil {
				s := c.t.String()
				if s != c.s {
					t.Errorf("%v.String() == %v, want %v", c.t, s, c.s)
				}
			}
			td, err := ParseTimeOfDay(c.s)
			if err == nil {
				if c.err == nil {
					if td == c.t {
						return
					}
					t.Errorf("ParseTimeOfDay(%s) == %v, want %v", c.s, td, c.t)
				} else {
					t.Errorf("ParseTimeOfDay(%s) == %v, want error", c.s, td)
				}
			} else {
				if c.err != nil {
					return
				}
				t.Errorf("ParseTimeOfDay(%s) == %v, want %v", c.s, err, c.t)
			}
		})
	}

}

func TestHalfHours(t *testing.T) {
	cases := []struct {
		h uint8
		d time.Duration
	}{
		{0, 0},
		{1, 30 * time.Minute},
		{3, 90 * time.Minute},
		{4, 2 * time.Hour},
	}
	for _, c := range cases {
		t.Run(fmt.Sprintf("from%d", c.h), func(t *testing.T) {
			d := halfHoursToDuration(c.h)
			if d != Duration(c.d) {
				t.Errorf("halfHoursToDuration(%d) == %v, want %v", c.h, d, c.d)
			}
		})
		t.Run(fmt.Sprintf("to%d", c.h), func(t *testing.T) {
			h := TimeOfDay(c.d).HalfHours()
			if h != c.h {
				t.Errorf("HalfHours(%v) == %d, want %d", c.d, h, c.h)
			}
		})
	}
}

func TestSinceMidnight(t *testing.T) {
	cases := []struct {
		t time.Time
		d TimeOfDay
	}{
		{parseTime("2015-01-01T09:00"), Duration(9 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-15T10:00:00.5"), Duration(10*time.Hour + 500*time.Millisecond).TimeOfDay()},
		{parseTime("2016-06-15T20:30"), Duration(20*time.Hour + 30*time.Minute).TimeOfDay()},
		{parseTime("2010-11-30T23:59:59.999"), Duration(24*time.Hour - time.Millisecond).TimeOfDay()},
		// DST changes
		{parseTime("2016-03-13T01:00"), Duration(1 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-13T03:00"), Duration(3 * time.Hour).TimeOfDay()},
		{parseTime("2016-03-13T12:00"), Duration(12 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T01:00"), Duration(1 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T02:00"), Duration(2 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T03:00"), Duration(3 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T23:00"), Duration(23 * time.Hour).TimeOfDay()},
		{parseTime("2016-11-06T23:30"), Duration(23*time.Hour + 30*time.Minute).TimeOfDay()},
	}
	for _, c := range cases {
		t.Run(c.t.Format(time.Kitchen), func(t *testing.T) {
			d := SinceMidnight(c.t)
			if d != c.d {
				// Print TimeOfDay as underlying time.Duration.
				t.Errorf("sinceMidnight(%v) == %v, want %v", c.t, time.Duration(d), time.Duration(c.d))
			}
		})
	}
}

func TestDecodeTime(t *testing.T) {
	cases := []struct {
		b []byte
		t time.Time
	}{
		{parseBytes("1F 40 00 01 05"), parseTime("2005-01-01T00:00:31")},
		{parseBytes("09 A2 0A 15 10"), parseTime("2016-02-21T10:34:09")},
		{parseBytes("42 22 54 65 10"), parseTime("2016-04-05T20:34:02")},
		{parseBytes("79 23 0C 12 10"), parseTime("2016-04-18T12:35:57")},
		{parseBytes("75 B7 13 04 10"), parseTime("2016-06-04T19:55:53")},
		{parseBytes("5D B3 0F 06 10"), parseTime("2016-06-06T15:51:29")},
		{parseBytes("40 94 12 0F 10"), parseTime("2016-06-15T18:20:00")},
		{parseBytes("B1 34 87 6B 12"), parseTime("2018-08-11T07:52:49")},
	}
	for _, c := range cases {
		t.Run(c.t.Format(time.Kitchen), func(t *testing.T) {
			ts := time.Time(decodeTime(c.b))
			if !ts.Equal(c.t) {
				t.Errorf("decodeTime(% X) == %v, want %v", c.b, ts, c.t)
			}
		})
	}
}

func TestDecodeDate(t *testing.T) {
	cases := []struct {
		b []byte
		t time.Time
	}{
		{parseBytes("BF 0F"), parseTime("2015-10-31T00:00")},
		{parseBytes("78 10"), parseTime("2016-06-24T00:00")},
	}
	for _, c := range cases {
		t.Run(c.t.Format("2006-01-02"), func(t *testing.T) {
			ts := time.Time(decodeDate(c.b))
			if !ts.Equal(c.t) {
				t.Errorf("decodeDate(% X) == %v, want %v", c.b, ts, c.t)
			}
		})
	}
}
<|endoftext|>


<commit_before>package gtimeentry

import (
	"encoding/json"
	"fmt"
	"gopkg.in/dougEfresh/toggl-http-client.v8"
	"gopkg.in/dougEfresh/toggl-workspace.v8"
	"gopkg.in/dougEfresh/toggl-project.v8"
	"time"
)

type TimeEntry struct {
	Id          uint64                `json:"id,omitempty"`
	Description string                `json:"description"`
	Project     *gproject.Project     `json:"project"`
	Start       time.Time             `json:"start"`
	Stop        time.Time             `json:"stop"`
	Duration    uint64                `json:"duration"`
	Billable    bool                  `json:"billable"`
	Workspace   *gworkspace.Workspace `json:"workspace"`
	Tags        []string              `json:"tags"`
	Pid         uint64                `json:"pid"`
	Wid         uint64                `json:"wid"`
	Tid         uint64                `json:"tid"`
	CreatedWith string                `json:"created_with,omitempty" `
}

type TimeEntries []TimeEntry

const Endpoint = "/time_entries"
const EndpointCurrent = Endpoint + "/current"
const EndpointStart = Endpoint + "/start"

//Return a UserClient. An error is also returned when some configuration option is invalid
//    thc,err := gtoggl.NewClient("token")
//    uc,err := guser.NewClient(thc)
func NewClient(thc *ghttp.TogglHttpClient) *TimeEntryClient {
	tc := &TimeEntryClient{
		thc: thc,
	}
	tc.endpoint = thc.Url + Endpoint
	tc.currentEndpoint = thc.Url + EndpointCurrent
	tc.startEndpoint = thc.Url + EndpointStart
	return tc
}

type TimeEntryClient struct {
	thc             *ghttp.TogglHttpClient
	endpoint        string
	startEndpoint   string
	currentEndpoint string
}

func (c *TimeEntryClient) Get(tid uint64) (*TimeEntry, error) {
	return timeEntryResponse(c.thc.GetRequest(fmt.Sprintf("%s/%d", c.endpoint, tid)))
}

func (tc *TimeEntryClient) Delete(id uint64) error {
	_, err := tc.thc.DeleteRequest(fmt.Sprintf("%s/%d", tc.endpoint, id), nil)
	return err
}

func (c *TimeEntryClient) List() (TimeEntries, error) {
	body, err := c.thc.GetRequest(c.endpoint)
	var te TimeEntries
	if err != nil {
		return te, err
	}
	if body == nil {
		return nil, nil
	}
	err = json.Unmarshal(*body, &te)
	return te, err
}

func (c *TimeEntryClient) Create(t *TimeEntry) (*TimeEntry, error) {
	if len(t.CreatedWith) < 0 {
		t.CreatedWith = "gtoggl"
	}
	up := createRequest{TimeEntry: t}
	body, err := json.Marshal(up)
	if err != nil {
		return nil, err
	}
	return timeEntryResponse(c.thc.PutRequest(c.endpoint, body))
}

func (c *TimeEntryClient) Update(t *TimeEntry) (*TimeEntry, error) {
	up := updateRequest{TimeEntry: t}
	body, err := json.Marshal(up)
	if err != nil {
		return nil, err
	}
	return timeEntryResponse(c.thc.PutRequest(fmt.Sprintf("%s/%d", c.endpoint, t.Id), body))
}

func timeEntryResponse(response *json.RawMessage, error error) (*TimeEntry, error) {
	if error != nil {
		return nil, error
	}
	if response == nil {
		return nil, nil
	}
	var tResp ghttp.TogglResponse
	err := json.Unmarshal(*response, &tResp)
	if err != nil {
		return nil, err
	}
	var t TimeEntry
	err = json.Unmarshal(*tResp.Data, &t)
	if err != nil {
		return nil, err
	}
	return &t, err
}

type updateRequest struct {
	TimeEntry *TimeEntry `json:"time_entry"`
}
type createRequest struct {
	TimeEntry *TimeEntry `json:"time_entry"`
}
<commit_msg>updating to use interface{}<commit_after>package gtimeentry

import (
	"encoding/json"
	"fmt"
	"gopkg.in/dougEfresh/toggl-http-client.v8"
	"gopkg.in/dougEfresh/toggl-workspace.v8"
	"gopkg.in/dougEfresh/toggl-project.v8"
	"time"
)

type TimeEntry struct {
	Id          uint64                `json:"id,omitempty"`
	Description string                `json:"description"`
	Project     *gproject.Project     `json:"project"`
	Start       time.Time             `json:"start"`
	Stop        time.Time             `json:"stop"`
	Duration    uint64                `json:"duration"`
	Billable    bool                  `json:"billable"`
	Workspace   *gworkspace.Workspace `json:"workspace"`
	Tags        []string              `json:"tags"`
	Pid         uint64                `json:"pid"`
	Wid         uint64                `json:"wid"`
	Tid         uint64                `json:"tid"`
	CreatedWith string                `json:"created_with,omitempty" `
}

type TimeEntries []TimeEntry

const Endpoint = "/time_entries"
const EndpointCurrent = Endpoint + "/current"
const EndpointStart = Endpoint + "/start"

//Return a UserClient. An error is also returned when some configuration option is invalid
//    thc,err := gtoggl.NewClient("token")
//    uc,err := guser.NewClient(thc)
func NewClient(thc *ghttp.TogglHttpClient) *TimeEntryClient {
	tc := &TimeEntryClient{
		thc: thc,
	}
	tc.endpoint = thc.Url + Endpoint
	tc.currentEndpoint = thc.Url + EndpointCurrent
	tc.startEndpoint = thc.Url + EndpointStart
	return tc
}

type TimeEntryClient struct {
	thc             *ghttp.TogglHttpClient
	endpoint        string
	startEndpoint   string
	currentEndpoint string
}

func (c *TimeEntryClient) Get(tid uint64) (*TimeEntry, error) {
	return timeEntryResponse(c.thc.GetRequest(fmt.Sprintf("%s/%d", c.endpoint, tid)))
}

func (tc *TimeEntryClient) Delete(id uint64) error {
	_, err := tc.thc.DeleteRequest(fmt.Sprintf("%s/%d", tc.endpoint, id), nil)
	return err
}

func (c *TimeEntryClient) List() (TimeEntries, error) {
	body, err := c.thc.GetRequest(c.endpoint)
	var te TimeEntries
	if err != nil {
		return te, err
	}
	if body == nil {
		return nil, nil
	}
	err = json.Unmarshal(*body, &te)
	return te, err
}

func (c *TimeEntryClient) Create(t *TimeEntry) (*TimeEntry, error) {
	if len(t.CreatedWith) < 0 {
		t.CreatedWith = "gtoggl"
	}
	up := map[string]interface{} {"time_entry" : t}
	return timeEntryResponse(c.thc.PutRequest(c.endpoint, up))
}

func (c *TimeEntryClient) Update(t *TimeEntry) (*TimeEntry, error) {
	up := map[string]interface{} {"time_entry" : t}
	return timeEntryResponse(c.thc.PutRequest(fmt.Sprintf("%s/%d", c.endpoint, t.Id), up))
}

func timeEntryResponse(response *json.RawMessage, error error) (*TimeEntry, error) {
	if error != nil {
		return nil, error
	}
	if response == nil {
		return nil, nil
	}
	var tResp ghttp.TogglResponse
	err := json.Unmarshal(*response, &tResp)
	if err != nil {
		return nil, err
	}
	var t TimeEntry
	err = json.Unmarshal(*tResp.Data, &t)
	if err != nil {
		return nil, err
	}
	return &t, err
}

<|endoftext|>