<commit_before>// Package tdigest provides a highly accurate mergeable data-structure
// for quantile estimation.
package tdigest

import (
	"fmt"
	"math"
)

// TDigest is a quantile approximation data structure.
// Typical T-Digest use cases involve accumulating metrics on several
// distinct nodes of a cluster and then merging them together to get
// a system-wide quantile overview. Things such as: sensory data from
// IoT devices, quantiles over enormous document datasets (think
// ElasticSearch), performance metrics for distributed systems, etc.
type TDigest struct {
	summary     *summary
	compression float64
	count       uint64
	rng         TDigestRNG
}

// New creates a new digest.
//
// By default the digest is constructed with a configuration that
// should be useful for most use-cases.
func New(options ...tdigestOption) (*TDigest, error) {
	tdigest := &TDigest{
		compression: 100,
		count:       0,
		rng:         &globalRNG{},
	}

	for _, option := range options {
		err := option(tdigest)
		if err != nil {
			return nil, err
		}
	}

	tdigest.summary = newSummary(estimateCapacity(tdigest.compression))
	return tdigest, nil
}

func _quantile(index float64, previousIndex float64, nextIndex float64, previousMean float64, nextMean float64) float64 {
	delta := nextIndex - previousIndex
	previousWeight := (nextIndex - index) / delta
	nextWeight := (index - previousIndex) / delta
	return previousMean*previousWeight + nextMean*nextWeight
}

// Quantile returns the desired percentile estimation.
// Values of p must be between 0 and 1 (inclusive), will panic otherwise.
func (t *TDigest) Quantile(q float64) float64 {
	if q < 0 || q > 1 {
		panic("q must be between 0 and 1 (inclusive)")
	}

	if t.Len() == 0 {
		return math.NaN()
	} else if t.Len() == 1 {
		return t.summary.Mean(0)
	}

	index := q * float64(t.count-1)
	previousMean := math.NaN()
	previousIndex := float64(0)
	next, total := t.summary.FloorSum(index)

	if next > 0 {
		previousMean = t.summary.Mean(next - 1)
		previousIndex = total - float64(t.summary.Count(next-1)+1)/2
	}

	for {
		nextIndex := total + float64(t.summary.Count(next)-1)/2
		if nextIndex >= index {
			if math.IsNaN(previousMean) {
				// the index is before the 1st centroid
				if nextIndex == previousIndex {
					return t.summary.Mean(next)
				}
				// assume linear growth
				nextIndex2 := total + float64(t.summary.Count(next)) + float64(t.summary.Count(next+1)-1)/2
				previousMean = (nextIndex2*t.summary.Mean(next) - nextIndex*t.summary.Mean(next+1)) / (nextIndex2 - nextIndex)
			}
			// common case: two centroids found, the result in inbetween
			return _quantile(index, previousIndex, nextIndex, previousMean, t.summary.Mean(next))
		} else if next+1 == t.Len() {
			// the index is after the last centroid
			nextIndex2 := float64(t.count - 1)
			nextMean2 := (t.summary.Mean(next)*(nextIndex2-previousIndex) - previousMean*(nextIndex2-nextIndex)) / (nextIndex - previousIndex)
			return _quantile(index, nextIndex, nextIndex2, t.summary.Mean(next), nextMean2)
		}
		total += float64(t.summary.Count(next))
		previousMean = t.summary.Mean(next)
		previousIndex = nextIndex
		next++
	}
	// unreachable
}

func weightedAverage(x1 float64, w1 float64, x2 float64, w2 float64) float64 {
	if x1 > x2 {
		x1, x2, w1, w2 = x2, x1, w2, w1
	}
	return x1*w1/(w1+w2) + x2*w2/(w1+w2)
}

// AddWeighted registers a new sample in the digest.
//
// It's the main entry point for the digest and very likely the only
// method to be used for collecting samples. The count parameter is for
// when you are registering a sample that occurred multiple times - the
// most common value for this is 1.
//
// This will emit an error if `value` is NaN of if `count` is zero.
func (t *TDigest) AddWeighted(value float64, count uint32) (err error) {

	if count == 0 {
		return fmt.Errorf("Illegal datapoint <value: %.4f, count: %d>", value, count)
	}

	if t.Len() == 0 {
		err = t.summary.Add(value, count)
		t.count = uint64(count)
		return err
	}

	start := t.summary.Floor(value)
	if start == -1 {
		start = 0
	}

	minDistance := math.MaxFloat64
	lastNeighbor := t.Len()
	for neighbor := start; neighbor < t.Len(); neighbor++ {
		z := math.Abs(t.summary.Mean(neighbor) - value)
		if z < minDistance {
			start = neighbor
			minDistance = z
		} else if z > minDistance {
			lastNeighbor = neighbor
			break
		}
	}

	closest := t.Len()
	sum := t.summary.HeadSum(start)
	var n float32

	for neighbor := start; neighbor != lastNeighbor; neighbor++ {
		c := float64(t.summary.Count(neighbor))
		var q float64
		if t.count == 1 {
			q = 0.5
		} else {
			q = (sum + (c-1)/2) / float64(t.count-1)
		}
		k := 4 * float64(t.count) * q * (1 - q) / t.compression

		if c+float64(count) <= k {
			n++
			if t.rng.Float32() < 1/n {
				closest = neighbor
			}
		}
		sum += c
	}

	if closest == t.Len() {
		t.summary.Add(value, count)
	} else {
		c := float64(t.summary.Count(closest))
		newMean := weightedAverage(t.summary.Mean(closest), c, value, float64(count))
		t.summary.setAt(closest, newMean, uint32(c)+count)
	}
	t.count += uint64(count)

	if float64(t.Len()) > 20*t.compression {
		err = t.Compress()
	}

	return err
}

// Count returns the total number of samples this digest represents
// (i.e.: how many times Add() was called on it plus all the counts of
// other digests the current has merged with).
//
// This is useful mainly for two scenarios:
//
// 1. Knowing if there is enough data so you can trust the quantiles
// 2. Knowing if you've registered too many samples already and
//    deciding what to do about it.
//
// For the second case one approach would be to create a side empty
// digest and start registering samples on it as well as on the old
// (big) one and then discard the bigger one after a certain criterion
// is reached (say, minimum number of samples or a small relative
// error between new and old digests).
func (t TDigest) Count() uint64 {
	return t.count
}

// Add(x) is an alias for AddWeighted(x,1)
// Read the documentation for AddWeighted for more details.
func (t *TDigest) Add(value float64) error {
	return t.AddWeighted(value, 1)
}

// Compress tries to reduce the number of individual centroids stored
// in the digest.
// Compression trades off accuracy for performance and happens
// automatically after a certain amount of distinct samples have been
// stored.
func (t *TDigest) Compress() (err error) {
	if t.Len() <= 1 {
		return nil
	}

	oldTree := t.summary
	t.summary = newSummary(uint(t.Len()))
	t.count = 0

	shuffle(oldTree.means, oldTree.counts, t.rng)
	oldTree.ForEach(func(mean float64, count uint32) bool {
		err = t.AddWeighted(mean, count)
		return err == nil
	})

	return err
}

// Merge joins a given digest into itself.
// Merging is useful when you have multiple TDigest instances running
// in separate threads and you want to compute quantiles over all the
// samples. This is particularly important on a scatter-gather/map-reduce
// scenario.
func (t *TDigest) Merge(other *TDigest) (err error) {
	if other.Len() == 0 {
		return nil
	}

	// We must keep the other digest intact
	data := other.summary.Clone()
	shuffle(data.means, data.counts, t.rng)

	data.ForEach(func(mean float64, count uint32) bool {
		err = t.AddWeighted(mean, count)
		return err == nil
	})
	return err
}

// Len returns the number of centroids in the TDigest.
func (t *TDigest) Len() int { return t.summary.Len() }

// ForEachCentroid calls the specified function for each centroid.
// Iteration stops when the supplied function returns false, or when all
// centroids have been iterated.
func (t *TDigest) ForEachCentroid(f func(mean float64, count uint32) bool) {
	t.summary.ForEach(f)
}

func shuffle(means []float64, counts []uint32, rng TDigestRNG) {
	for i := len(means) - 1; i > 1; i-- {
		j := rng.Intn(i + 1)
		means[i], means[j], counts[i], counts[j] = means[j], means[i], counts[j], counts[i]
	}
}

func estimateCapacity(compression float64) uint {
	return uint(compression) * 10
}
<commit_msg>Tweak the docs a bit<commit_after>// Package tdigest provides a highly accurate mergeable data-structure
// for quantile estimation.
//
// Typical T-Digest use cases involve accumulating metrics on several
// distinct nodes of a cluster and then merging them together to get
// a system-wide quantile overview. Things such as: sensory data from
// IoT devices, quantiles over enormous document datasets (think
// ElasticSearch), performance metrics for distributed systems, etc.
//
// After you create (and configure, if desired) the digest:
//     digest := tdigest.New(tdigest.Compression(100))
//
// You can then use it for registering measurements:
//     digest.Add(number)
//
// Estimating quantiles:
//     digest.Quantile(0.99)
//
// And merging with another digest:
//     digest.Merge(otherDigest)
package tdigest

import (
	"fmt"
	"math"
)

// TDigest is a quantile approximation data structure.
type TDigest struct {
	summary     *summary
	compression float64
	count       uint64
	rng         TDigestRNG
}

// New creates a new digest.
//
// By default the digest is constructed with a configuration that
// should be useful for most use-cases. It comes with compression
// set to 100 and uses the global random number generator (same
// as using math/rand top-level functions).
func New(options ...tdigestOption) (*TDigest, error) {
	tdigest := &TDigest{
		compression: 100,
		count:       0,
		rng:         &globalRNG{},
	}

	for _, option := range options {
		err := option(tdigest)
		if err != nil {
			return nil, err
		}
	}

	tdigest.summary = newSummary(estimateCapacity(tdigest.compression))
	return tdigest, nil
}

func _quantile(index float64, previousIndex float64, nextIndex float64, previousMean float64, nextMean float64) float64 {
	delta := nextIndex - previousIndex
	previousWeight := (nextIndex - index) / delta
	nextWeight := (index - previousIndex) / delta
	return previousMean*previousWeight + nextMean*nextWeight
}

// Quantile returns the desired percentile estimation.
//
// Values of p must be between 0 and 1 (inclusive), will panic otherwise.
func (t *TDigest) Quantile(q float64) float64 {
	if q < 0 || q > 1 {
		panic("q must be between 0 and 1 (inclusive)")
	}

	if t.Len() == 0 {
		return math.NaN()
	} else if t.Len() == 1 {
		return t.summary.Mean(0)
	}

	index := q * float64(t.count-1)
	previousMean := math.NaN()
	previousIndex := float64(0)
	next, total := t.summary.FloorSum(index)

	if next > 0 {
		previousMean = t.summary.Mean(next - 1)
		previousIndex = total - float64(t.summary.Count(next-1)+1)/2
	}

	for {
		nextIndex := total + float64(t.summary.Count(next)-1)/2
		if nextIndex >= index {
			if math.IsNaN(previousMean) {
				// the index is before the 1st centroid
				if nextIndex == previousIndex {
					return t.summary.Mean(next)
				}
				// assume linear growth
				nextIndex2 := total + float64(t.summary.Count(next)) + float64(t.summary.Count(next+1)-1)/2
				previousMean = (nextIndex2*t.summary.Mean(next) - nextIndex*t.summary.Mean(next+1)) / (nextIndex2 - nextIndex)
			}
			// common case: two centroids found, the result in inbetween
			return _quantile(index, previousIndex, nextIndex, previousMean, t.summary.Mean(next))
		} else if next+1 == t.Len() {
			// the index is after the last centroid
			nextIndex2 := float64(t.count - 1)
			nextMean2 := (t.summary.Mean(next)*(nextIndex2-previousIndex) - previousMean*(nextIndex2-nextIndex)) / (nextIndex - previousIndex)
			return _quantile(index, nextIndex, nextIndex2, t.summary.Mean(next), nextMean2)
		}
		total += float64(t.summary.Count(next))
		previousMean = t.summary.Mean(next)
		previousIndex = nextIndex
		next++
	}
	// unreachable
}

func weightedAverage(x1 float64, w1 float64, x2 float64, w2 float64) float64 {
	if x1 > x2 {
		x1, x2, w1, w2 = x2, x1, w2, w1
	}
	return x1*w1/(w1+w2) + x2*w2/(w1+w2)
}

// AddWeighted registers a new sample in the digest.
//
// It's the main entry point for the digest and very likely the only
// method to be used for collecting samples. The count parameter is for
// when you are registering a sample that occurred multiple times - the
// most common value for this is 1.
//
// This will emit an error if `value` is NaN of if `count` is zero.
func (t *TDigest) AddWeighted(value float64, count uint32) (err error) {

	if count == 0 {
		return fmt.Errorf("Illegal datapoint <value: %.4f, count: %d>", value, count)
	}

	if t.Len() == 0 {
		err = t.summary.Add(value, count)
		t.count = uint64(count)
		return err
	}

	start := t.summary.Floor(value)
	if start == -1 {
		start = 0
	}

	minDistance := math.MaxFloat64
	lastNeighbor := t.Len()
	for neighbor := start; neighbor < t.Len(); neighbor++ {
		z := math.Abs(t.summary.Mean(neighbor) - value)
		if z < minDistance {
			start = neighbor
			minDistance = z
		} else if z > minDistance {
			lastNeighbor = neighbor
			break
		}
	}

	closest := t.Len()
	sum := t.summary.HeadSum(start)
	var n float32

	for neighbor := start; neighbor != lastNeighbor; neighbor++ {
		c := float64(t.summary.Count(neighbor))
		var q float64
		if t.count == 1 {
			q = 0.5
		} else {
			q = (sum + (c-1)/2) / float64(t.count-1)
		}
		k := 4 * float64(t.count) * q * (1 - q) / t.compression

		if c+float64(count) <= k {
			n++
			if t.rng.Float32() < 1/n {
				closest = neighbor
			}
		}
		sum += c
	}

	if closest == t.Len() {
		t.summary.Add(value, count)
	} else {
		c := float64(t.summary.Count(closest))
		newMean := weightedAverage(t.summary.Mean(closest), c, value, float64(count))
		t.summary.setAt(closest, newMean, uint32(c)+count)
	}
	t.count += uint64(count)

	if float64(t.Len()) > 20*t.compression {
		err = t.Compress()
	}

	return err
}

// Count returns the total number of samples this digest represents
//
// The result represents how many times Add() was called on a digest
// plus how many samples the digests it has been merged with had.
// This is useful mainly for two scenarios:
//
// - Knowing if there is enough data so you can trust the quantiles
//
// - Knowing if you've registered too many samples already and
// deciding what to do about it.
//
// For the second case one approach would be to create a side empty
// digest and start registering samples on it as well as on the old
// (big) one and then discard the bigger one after a certain criterion
// is reached (say, minimum number of samples or a small relative
// error between new and old digests).
func (t TDigest) Count() uint64 {
	return t.count
}

// Add(x) is an alias for AddWeighted(x,1)
// Read the documentation for AddWeighted for more details.
func (t *TDigest) Add(value float64) error {
	return t.AddWeighted(value, 1)
}

// Compress tries to reduce the number of individual centroids stored
// in the digest.
//
// Compression trades off accuracy for performance and happens
// automatically after a certain amount of distinct samples have been
// stored.
//
// At any point in time you may call Compress on a digest, but you
// may completely ignore this and it will compress itself automatically
// after it grows too much. If you are minimizing network traffic
// it might be a good idea to compress before serializing.
func (t *TDigest) Compress() (err error) {
	if t.Len() <= 1 {
		return nil
	}

	oldTree := t.summary
	t.summary = newSummary(uint(t.Len()))
	t.count = 0

	shuffle(oldTree.means, oldTree.counts, t.rng)
	oldTree.ForEach(func(mean float64, count uint32) bool {
		err = t.AddWeighted(mean, count)
		return err == nil
	})

	return err
}

// Merge joins a given digest into itself.
//
// Merging is useful when you have multiple TDigest instances running
// in separate threads and you want to compute quantiles over all the
// samples. This is particularly important on a scatter-gather/map-reduce
// scenario.
func (t *TDigest) Merge(other *TDigest) (err error) {
	if other.Len() == 0 {
		return nil
	}

	// We must keep the other digest intact
	data := other.summary.Clone()
	shuffle(data.means, data.counts, t.rng)

	data.ForEach(func(mean float64, count uint32) bool {
		err = t.AddWeighted(mean, count)
		return err == nil
	})
	return err
}

// Len returns the number of centroids in the TDigest.
func (t *TDigest) Len() int { return t.summary.Len() }

// ForEachCentroid calls the specified function for each centroid.
//
// Iteration stops when the supplied function returns false, or when all
// centroids have been iterated.
func (t *TDigest) ForEachCentroid(f func(mean float64, count uint32) bool) {
	t.summary.ForEach(f)
}

func shuffle(means []float64, counts []uint32, rng TDigestRNG) {
	for i := len(means) - 1; i > 1; i-- {
		j := rng.Intn(i + 1)
		means[i], means[j], counts[i], counts[j] = means[j], means[i], counts[j], counts[i]
	}
}

func estimateCapacity(compression float64) uint {
	return uint(compression) * 10
}
<|endoftext|>


<commit_before>package typhon

import (
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	log "github.com/mondough/slog"
	"github.com/mondough/terrors"
	"github.com/mondough/terrors/proto"
)

var (
	mapTerr2Status = map[string]int{
		terrors.ErrBadRequest:      http.StatusBadRequest,
		terrors.ErrUnauthorized:    http.StatusUnauthorized,
		terrors.ErrForbidden:       http.StatusForbidden,
		terrors.ErrNotFound:        http.StatusNotFound,
		terrors.ErrBadResponse:     http.StatusNotAcceptable,
		terrors.ErrTimeout:         http.StatusGatewayTimeout,
		terrors.ErrInternalService: http.StatusInternalServerError}
	mapStatus2Terr map[int]string
)

func init() {
	mapStatus2Terr = make(map[int]string, len(mapTerr2Status))
	for k, v := range mapTerr2Status {
		mapStatus2Terr[v] = k
	}
}

// ErrorStatusCode returns an HTTP status code for the error
func ErrorStatusCode(err error) int {
	code := terrors.Wrap(err, nil).(*terrors.Error).Code
	if c, ok := mapTerr2Status[strings.SplitN(code, ".", 2)[0]]; ok {
		return c
	}
	return http.StatusInternalServerError
}

// terr2StatusCode converts HTTP status codes to a roughly equivalent terrors' code
func status2TerrCode(code int) string {
	if c, ok := mapStatus2Terr[code]; ok {
		return c
	}
	return terrors.ErrInternalService
}

// ErrorFilter serialises and de-serialises response errors
func ErrorFilter(req Request, svc Service) Response {
	// If the request contains an error, short-circuit and return that directly
	if req.err != nil {
		return Response{
			Error: req.err}
	}
	rsp := svc(req)

	if rsp.Response == nil {
		rsp.Response = newHttpResponse(req)
	}
	if rsp.ctx == nil {
		rsp.ctx = req
	}

	if rsp.Error != nil && (rsp.Response == nil ||
		(rsp.StatusCode >= 200 && rsp.StatusCode <= 299)) {
		switch body := rsp.Body.(type) {
		case *bufCloser:
			body.Reset()
		default:
			rsp.Body = &bufCloser{}
		}

		// Serialise the error into the response
		terr := terrors.Wrap(rsp.Error, nil).(*terrors.Error)
		rsp.Encode(terrors.Marshal(terr))
		rsp.StatusCode = ErrorStatusCode(terr)
		rsp.Header.Set("Terror", "1")
	} else if rsp.StatusCode >= 400 && rsp.StatusCode <= 599 {
		b, _ := rsp.BodyBytes(false)
		switch rsp.Header.Get("Terror") {
		case "1":
			tp := &terrorsproto.Error{}
			if err := json.Unmarshal(b, tp); err != nil {
				log.Warn(rsp.ctx, "Failed to unmarshal terror: %v", err)
				rsp.Error = errors.New(string(b))
			} else {
				rsp.Error = terrors.Unmarshal(tp)
			}

		default:
			rsp.Error = errors.New(string(b))
		}
	}
	if rsp.Error != nil && rsp.Error.Error() == "" {
		rsp.Error = errors.New("Response error (empty body)")
	}

	return rsp
}
<commit_msg>Nil pointer<commit_after>package typhon

import (
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	log "github.com/mondough/slog"
	"github.com/mondough/terrors"
	"github.com/mondough/terrors/proto"
)

var (
	mapTerr2Status = map[string]int{
		terrors.ErrBadRequest:      http.StatusBadRequest,
		terrors.ErrUnauthorized:    http.StatusUnauthorized,
		terrors.ErrForbidden:       http.StatusForbidden,
		terrors.ErrNotFound:        http.StatusNotFound,
		terrors.ErrBadResponse:     http.StatusNotAcceptable,
		terrors.ErrTimeout:         http.StatusGatewayTimeout,
		terrors.ErrInternalService: http.StatusInternalServerError}
	mapStatus2Terr map[int]string
)

func init() {
	mapStatus2Terr = make(map[int]string, len(mapTerr2Status))
	for k, v := range mapTerr2Status {
		mapStatus2Terr[v] = k
	}
}

// ErrorStatusCode returns an HTTP status code for the error
func ErrorStatusCode(err error) int {
	code := terrors.Wrap(err, nil).(*terrors.Error).Code
	if c, ok := mapTerr2Status[strings.SplitN(code, ".", 2)[0]]; ok {
		return c
	}
	return http.StatusInternalServerError
}

// terr2StatusCode converts HTTP status codes to a roughly equivalent terrors' code
func status2TerrCode(code int) string {
	if c, ok := mapStatus2Terr[code]; ok {
		return c
	}
	return terrors.ErrInternalService
}

// ErrorFilter serialises and de-serialises response errors
func ErrorFilter(req Request, svc Service) Response {
	// If the request contains an error, short-circuit and return that directly
	if req.err != nil {
		return Response{
			Error: req.err}
	}
	rsp := svc(req)

	if rsp.Response == nil {
		rsp.Response = newHttpResponse(req)
	}
	if rsp.ctx == nil {
		rsp.ctx = req
	}

	if rsp.Error != nil && (rsp.Response == nil ||
		(rsp.StatusCode >= 200 && rsp.StatusCode <= 299)) {
		switch body := rsp.Body.(type) {
		case *bufCloser:
			body.Reset()
		default:
			rsp.Body = &bufCloser{}
		}

		// Serialise the error into the response
		terr := terrors.Wrap(rsp.Error, nil).(*terrors.Error)
		rsp.Encode(terrors.Marshal(terr))
		rsp.StatusCode = ErrorStatusCode(terr)
		rsp.Header.Set("Terror", "1")
	} else if rsp.Response != nil && rsp.StatusCode >= 400 && rsp.StatusCode <= 599 {
		b, _ := rsp.BodyBytes(false)
		switch rsp.Header.Get("Terror") {
		case "1":
			tp := &terrorsproto.Error{}
			if err := json.Unmarshal(b, tp); err != nil {
				log.Warn(rsp.ctx, "Failed to unmarshal terror: %v", err)
				rsp.Error = errors.New(string(b))
			} else {
				rsp.Error = terrors.Unmarshal(tp)
			}

		default:
			rsp.Error = errors.New(string(b))
		}
	}
	if rsp.Error != nil && rsp.Error.Error() == "" {
		rsp.Error = errors.New("Response error (empty body)")
	}

	return rsp
}
<|endoftext|>


<commit_before>package utils

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"math"
	"math/big"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/cespare/xxhash"
	"github.com/kalafut/imohash"
	"github.com/schollz/mnemonicode"
)

// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// GetInput returns the input with a given prompt
func GetInput(prompt string) string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Fprintf(os.Stderr, "%s", prompt)
	text, _ := reader.ReadString('\n')
	return strings.TrimSpace(text)
}

// HashFile returns the hash of a file or, in case of a symlink, the
// SHA256 hash of its target. Takes an argument to specify the algorithm to use.
func HashFile(fname string, algorithm string) (hash256 []byte, err error) {
	var fstats os.FileInfo
	fstats, err = os.Lstat(fname)
	if err != nil {
		return nil, err
	}
	if fstats.Mode()&os.ModeSymlink != 0 {
		var target string
		target, err = os.Readlink(fname)
		return []byte(SHA256(target)), nil
	}
	if err != nil {
		return nil, err
	}
	switch algorithm {
	case "imohash":
		return IMOHashFile(fname)
	case "md5":
		return MD5HashFile(fname)
	case "xxhash":
		return XXHashFile(fname)
	}
	err = fmt.Errorf("unspecified algorithm")
	return
}

// MD5HashFile returns MD5 hash
func MD5HashFile(fname string) (hash256 []byte, err error) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	h := md5.New()
	if _, err = io.Copy(h, f); err != nil {
		return
	}

	hash256 = h.Sum(nil)
	return
}

// IMOHashFile returns imohash
func IMOHashFile(fname string) (hash []byte, err error) {
	b, err := imohash.SumFile(fname)
	hash = b[:]
	return
}

var imofull = imohash.NewCustom(0, 0)

// IMOHashFileFull returns imohash of full file
func IMOHashFileFull(fname string) (hash []byte, err error) {
	b, err := imofull.SumFile(fname)
	hash = b[:]
	return
}

// XXHashFile returns the xxhash of a file
func XXHashFile(fname string) (hash256 []byte, err error) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	h := xxhash.New()
	if _, err = io.Copy(h, f); err != nil {
		return
	}

	hash256 = h.Sum(nil)
	return
}

// SHA256 returns sha256 sum
func SHA256(s string) string {
	sha := sha256.New()
	sha.Write([]byte(s))
	return hex.EncodeToString(sha.Sum(nil))
}

// PublicIP returns public ip address
func PublicIP() (ip string, err error) {
	resp, err := http.Get("https://canhazip.com")
	if err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}
		ip = strings.TrimSpace(string(bodyBytes))
	}
	return
}

// LocalIP returns local ip address
func LocalIP() string {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)

	return localAddr.IP.String()
}

func GenerateRandomPin() string {
	s := ""
	max := new(big.Int)
	max.SetInt64(9)
	for i := 0; i < 4; i++ {
		v, err := rand.Int(rand.Reader, max)
		if err != nil {
			panic(err)
		}
		s += fmt.Sprintf("%d", v)
	}
	return s
}

// GetRandomName returns mnemonicoded random name
func GetRandomName() string {
	var result []string
	bs := make([]byte, 4)
	rand.Read(bs)
	result = mnemonicode.EncodeWordList(result, bs)
	return GenerateRandomPin() + "-" + strings.Join(result, "-")
}

// ByteCountDecimal converts bytes to human readable byte string
func ByteCountDecimal(b int64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%d B", b)
	}
	div, exp := int64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}

// MissingChunks returns the positions of missing chunks.
// If file doesn't exist, it returns an empty chunk list (all chunks).
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int64) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	fstat, err := os.Stat(fname)
	if err != nil || fstat.Size() != fsize {
		return
	}

	emptyBuffer := make([]byte, chunkSize)
	chunkNum := 0
	chunks := make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
	var currentLocation int64
	for {
		buffer := make([]byte, chunkSize)
		bytesread, err := f.Read(buffer)
		if err != nil {
			break
		}
		if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
			chunks[chunkNum] = currentLocation
			chunkNum++
		}
		currentLocation += int64(bytesread)
	}
	if chunkNum == 0 {
		chunkRanges = []int64{}
	} else {
		chunks = chunks[:chunkNum]
		chunkRanges = []int64{int64(chunkSize), chunks[0]}
		curCount := 0
		for i, chunk := range chunks {
			if i == 0 {
				continue
			}
			curCount++
			if chunk-chunks[i-1] > int64(chunkSize) {
				chunkRanges = append(chunkRanges, int64(curCount))
				chunkRanges = append(chunkRanges, chunk)
				curCount = 0
			}
		}
		chunkRanges = append(chunkRanges, int64(curCount+1))
		chunks = chunkRanges
	}
	return
}

// ChunkRangesToChunks converts chunk ranges to list
func ChunkRangesToChunks(chunkRanges []int64) (chunks []int64) {
	if len(chunkRanges) == 0 {
		return
	}
	chunkSize := chunkRanges[0]
	chunks = []int64{}
	for i := 1; i < len(chunkRanges); i += 2 {
		for j := int64(0); j < (chunkRanges[i+1]); j++ {
			chunks = append(chunks, chunkRanges[i]+j*chunkSize)
		}
	}
	return
}

// GetLocalIPs returns all local ips
func GetLocalIPs() (ips []string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}
	ips = []string{}
	for _, address := range addrs {
		// check the address type and if it is not a loopback the display it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				ips = append(ips, ipnet.IP.String())
			}
		}
	}
	return
}

func RandomFileName() (fname string, err error) {
	f, err := os.CreateTemp(".", "croc-stdin-")
	if err != nil {
		return
	}
	fname = f.Name()
	_ = f.Close()
	return
}

func FindOpenPorts(host string, portNumStart, numPorts int) (openPorts []int) {
	openPorts = []int{}
	for port := portNumStart; port-portNumStart < 200; port++ {
		timeout := 100 * time.Millisecond
		conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprint(port)), timeout)
		if conn != nil {
			conn.Close()
		} else if err != nil {
			openPorts = append(openPorts, port)
		}
		if len(openPorts) >= numPorts {
			return
		}
	}
	return
}

// local ip determination
// https://stackoverflow.com/questions/41240761/check-if-ip-address-is-in-private-network-space
var privateIPBlocks []*net.IPNet

func init() {
	for _, cidr := range []string{
		"127.0.0.0/8",    // IPv4 loopback
		"10.0.0.0/8",     // RFC1918
		"172.16.0.0/12",  // RFC1918
		"192.168.0.0/16", // RFC1918
		"169.254.0.0/16", // RFC3927 link-local
		"::1/128",        // IPv6 loopback
		"fe80::/10",      // IPv6 link-local
		"fc00::/7",       // IPv6 unique local addr
	} {
		_, block, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(fmt.Errorf("parse error on %q: %v", cidr, err))
		}
		privateIPBlocks = append(privateIPBlocks, block)
	}
}

func IsLocalIP(ipaddress string) bool {
	if strings.Contains(ipaddress, "localhost") {
		return true
	}
	host, _, _ := net.SplitHostPort(ipaddress)
	ip := net.ParseIP(host)
	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
		return true
	}
	for _, block := range privateIPBlocks {
		if block.Contains(ip) {
			return true
		}
	}
	return false
}
<commit_msg>fix hash file error<commit_after>package utils

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"math"
	"math/big"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/cespare/xxhash"
	"github.com/kalafut/imohash"
	"github.com/schollz/mnemonicode"
)

// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// GetInput returns the input with a given prompt
func GetInput(prompt string) string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Fprintf(os.Stderr, "%s", prompt)
	text, _ := reader.ReadString('\n')
	return strings.TrimSpace(text)
}

// HashFile returns the hash of a file or, in case of a symlink, the
// SHA256 hash of its target. Takes an argument to specify the algorithm to use.
func HashFile(fname string, algorithm string) (hash256 []byte, err error) {
	var fstats os.FileInfo
	fstats, err = os.Lstat(fname)
	if err != nil {
		return nil, err
	}
	if fstats.Mode()&os.ModeSymlink != 0 {
		var target string
		target, err = os.Readlink(fname)
		if err != nil {
			return nil, err
		}
		return []byte(SHA256(target)), nil
	}
	switch algorithm {
	case "imohash":
		return IMOHashFile(fname)
	case "md5":
		return MD5HashFile(fname)
	case "xxhash":
		return XXHashFile(fname)
	}
	err = fmt.Errorf("unspecified algorithm")
	return
}

// MD5HashFile returns MD5 hash
func MD5HashFile(fname string) (hash256 []byte, err error) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	h := md5.New()
	if _, err = io.Copy(h, f); err != nil {
		return
	}

	hash256 = h.Sum(nil)
	return
}

// IMOHashFile returns imohash
func IMOHashFile(fname string) (hash []byte, err error) {
	b, err := imohash.SumFile(fname)
	hash = b[:]
	return
}

var imofull = imohash.NewCustom(0, 0)

// IMOHashFileFull returns imohash of full file
func IMOHashFileFull(fname string) (hash []byte, err error) {
	b, err := imofull.SumFile(fname)
	hash = b[:]
	return
}

// XXHashFile returns the xxhash of a file
func XXHashFile(fname string) (hash256 []byte, err error) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	h := xxhash.New()
	if _, err = io.Copy(h, f); err != nil {
		return
	}

	hash256 = h.Sum(nil)
	return
}

// SHA256 returns sha256 sum
func SHA256(s string) string {
	sha := sha256.New()
	sha.Write([]byte(s))
	return hex.EncodeToString(sha.Sum(nil))
}

// PublicIP returns public ip address
func PublicIP() (ip string, err error) {
	resp, err := http.Get("https://canhazip.com")
	if err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}
		ip = strings.TrimSpace(string(bodyBytes))
	}
	return
}

// LocalIP returns local ip address
func LocalIP() string {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)

	return localAddr.IP.String()
}

func GenerateRandomPin() string {
	s := ""
	max := new(big.Int)
	max.SetInt64(9)
	for i := 0; i < 4; i++ {
		v, err := rand.Int(rand.Reader, max)
		if err != nil {
			panic(err)
		}
		s += fmt.Sprintf("%d", v)
	}
	return s
}

// GetRandomName returns mnemonicoded random name
func GetRandomName() string {
	var result []string
	bs := make([]byte, 4)
	rand.Read(bs)
	result = mnemonicode.EncodeWordList(result, bs)
	return GenerateRandomPin() + "-" + strings.Join(result, "-")
}

// ByteCountDecimal converts bytes to human readable byte string
func ByteCountDecimal(b int64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%d B", b)
	}
	div, exp := int64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}

// MissingChunks returns the positions of missing chunks.
// If file doesn't exist, it returns an empty chunk list (all chunks).
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int64) {
	f, err := os.Open(fname)
	if err != nil {
		return
	}
	defer f.Close()

	fstat, err := os.Stat(fname)
	if err != nil || fstat.Size() != fsize {
		return
	}

	emptyBuffer := make([]byte, chunkSize)
	chunkNum := 0
	chunks := make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
	var currentLocation int64
	for {
		buffer := make([]byte, chunkSize)
		bytesread, err := f.Read(buffer)
		if err != nil {
			break
		}
		if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
			chunks[chunkNum] = currentLocation
			chunkNum++
		}
		currentLocation += int64(bytesread)
	}
	if chunkNum == 0 {
		chunkRanges = []int64{}
	} else {
		chunks = chunks[:chunkNum]
		chunkRanges = []int64{int64(chunkSize), chunks[0]}
		curCount := 0
		for i, chunk := range chunks {
			if i == 0 {
				continue
			}
			curCount++
			if chunk-chunks[i-1] > int64(chunkSize) {
				chunkRanges = append(chunkRanges, int64(curCount))
				chunkRanges = append(chunkRanges, chunk)
				curCount = 0
			}
		}
		chunkRanges = append(chunkRanges, int64(curCount+1))
		chunks = chunkRanges
	}
	return
}

// ChunkRangesToChunks converts chunk ranges to list
func ChunkRangesToChunks(chunkRanges []int64) (chunks []int64) {
	if len(chunkRanges) == 0 {
		return
	}
	chunkSize := chunkRanges[0]
	chunks = []int64{}
	for i := 1; i < len(chunkRanges); i += 2 {
		for j := int64(0); j < (chunkRanges[i+1]); j++ {
			chunks = append(chunks, chunkRanges[i]+j*chunkSize)
		}
	}
	return
}

// GetLocalIPs returns all local ips
func GetLocalIPs() (ips []string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}
	ips = []string{}
	for _, address := range addrs {
		// check the address type and if it is not a loopback the display it
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				ips = append(ips, ipnet.IP.String())
			}
		}
	}
	return
}

func RandomFileName() (fname string, err error) {
	f, err := os.CreateTemp(".", "croc-stdin-")
	if err != nil {
		return
	}
	fname = f.Name()
	_ = f.Close()
	return
}

func FindOpenPorts(host string, portNumStart, numPorts int) (openPorts []int) {
	openPorts = []int{}
	for port := portNumStart; port-portNumStart < 200; port++ {
		timeout := 100 * time.Millisecond
		conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprint(port)), timeout)
		if conn != nil {
			conn.Close()
		} else if err != nil {
			openPorts = append(openPorts, port)
		}
		if len(openPorts) >= numPorts {
			return
		}
	}
	return
}

// local ip determination
// https://stackoverflow.com/questions/41240761/check-if-ip-address-is-in-private-network-space
var privateIPBlocks []*net.IPNet

func init() {
	for _, cidr := range []string{
		"127.0.0.0/8",    // IPv4 loopback
		"10.0.0.0/8",     // RFC1918
		"172.16.0.0/12",  // RFC1918
		"192.168.0.0/16", // RFC1918
		"169.254.0.0/16", // RFC3927 link-local
		"::1/128",        // IPv6 loopback
		"fe80::/10",      // IPv6 link-local
		"fc00::/7",       // IPv6 unique local addr
	} {
		_, block, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(fmt.Errorf("parse error on %q: %v", cidr, err))
		}
		privateIPBlocks = append(privateIPBlocks, block)
	}
}

func IsLocalIP(ipaddress string) bool {
	if strings.Contains(ipaddress, "localhost") {
		return true
	}
	host, _, _ := net.SplitHostPort(ipaddress)
	ip := net.ParseIP(host)
	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
		return true
	}
	for _, block := range privateIPBlocks {
		if block.Contains(ip) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"path/filepath"
	"strings"
)

func splitSpaces(s string) []string {
	var r []string
	tokStart := -1
	for i, ch := range s {
		if ch == ' ' || ch == '\t' {
			if tokStart >= 0 {
				r = append(r, s[tokStart:i])
				tokStart = -1
			}
		} else {
			if tokStart < 0 {
				tokStart = i
			}
		}
	}
	if tokStart >= 0 {
		r = append(r, s[tokStart:])
	}
	Log("splitSpace(%q)=%q", s, r)
	return r
}

func splitSpacesBytes(s []byte) (r [][]byte) {
	tokStart := -1
	for i, ch := range s {
		if ch == ' ' || ch == '\t' {
			if tokStart >= 0 {
				r = append(r, s[tokStart:i])
				tokStart = -1
			}
		} else {
			if tokStart < 0 {
				tokStart = i
			}
		}
	}
	if tokStart >= 0 {
		r = append(r, s[tokStart:])
	}
	Log("splitSpace(%q)=%q", s, r)
	return r
}

func matchPattern(pat, str string) bool {
	s := strings.SplitN(pat, "%", 2)
	if len(s) != 2 {
		return pat == str
	}
	return strings.HasPrefix(str, s[0]) && strings.HasSuffix(str, s[1])
}

func matchPatternBytes(pat, str []byte) bool {
	s := bytes.SplitN(pat, []byte{'%'}, 2)
	if len(s) != 2 {
		return bytes.Equal(pat, str)
	}
	return bytes.HasPrefix(str, s[0]) && bytes.HasSuffix(str, s[1])
}

func substPattern(pat, repl, str string) string {
	ps := strings.SplitN(pat, "%", 2)
	if len(ps) != 2 {
		if str == pat {
			return repl
		}
		return str
	}
	in := str
	trimed := str
	if ps[0] != "" {
		trimed = strings.TrimPrefix(in, ps[0])
		if trimed == in {
			return str
		}
	}
	in = trimed
	if ps[1] != "" {
		trimed = strings.TrimSuffix(in, ps[1])
		if trimed == in {
			return str
		}
	}

	rs := strings.SplitN(repl, "%", 2)
	if len(rs) != 2 {
		return repl
	}
	return rs[0] + trimed + rs[1]
}

func substPatternBytes(pat, repl, str []byte) []byte {
	ps := bytes.SplitN(pat, []byte{'%'}, 2)
	if len(ps) != 2 {
		if bytes.Equal(str, pat) {
			return repl
		}
		return str
	}
	in := str
	trimed := str
	if len(ps[0]) != 0 {
		trimed = bytes.TrimPrefix(in, ps[0])
		if bytes.Equal(trimed, in) {
			return str
		}
	}
	in = trimed
	if len(ps[1]) != 0 {
		trimed = bytes.TrimSuffix(in, ps[1])
		if bytes.Equal(trimed, in) {
			return str
		}
	}

	rs := bytes.SplitN(repl, []byte{'%'}, 2)
	if len(rs) != 2 {
		return repl
	}

	r := make([]byte, 0, len(rs[0])+len(trimed)+len(rs[1])+1)
	r = append(r, rs[0]...)
	r = append(r, trimed...)
	r = append(r, rs[1]...)
	return r
}

func substRef(pat, repl, str string) string {
	if strings.IndexByte(pat, '%') >= 0 && strings.IndexByte(repl, '%') >= 0 {
		return substPattern(pat, repl, str)
	}
	str = strings.TrimSuffix(str, pat)
	return str + repl
}

func stripExt(s string) string {
	suf := filepath.Ext(s)
	return s[:len(s)-len(suf)]
}

func trimLeftSpace(s string) string {
	for i, ch := range s {
		if ch != ' ' && ch != '\t' {
			return s[i:]
		}
	}
	return ""
}
<commit_msg>Make matchPattern* faster<commit_after>package main

import (
	"bytes"
	"path/filepath"
	"strings"
)

func splitSpaces(s string) []string {
	var r []string
	tokStart := -1
	for i, ch := range s {
		if ch == ' ' || ch == '\t' {
			if tokStart >= 0 {
				r = append(r, s[tokStart:i])
				tokStart = -1
			}
		} else {
			if tokStart < 0 {
				tokStart = i
			}
		}
	}
	if tokStart >= 0 {
		r = append(r, s[tokStart:])
	}
	Log("splitSpace(%q)=%q", s, r)
	return r
}

func splitSpacesBytes(s []byte) (r [][]byte) {
	tokStart := -1
	for i, ch := range s {
		if ch == ' ' || ch == '\t' {
			if tokStart >= 0 {
				r = append(r, s[tokStart:i])
				tokStart = -1
			}
		} else {
			if tokStart < 0 {
				tokStart = i
			}
		}
	}
	if tokStart >= 0 {
		r = append(r, s[tokStart:])
	}
	Log("splitSpace(%q)=%q", s, r)
	return r
}

func matchPattern(pat, str string) bool {
	i := strings.IndexByte(pat, '%')
	if i < 0 {
		return pat == str
	}
	return strings.HasPrefix(str, pat[:i]) && strings.HasSuffix(str, pat[i+1:])
}

func matchPatternBytes(pat, str []byte) bool {
	i := bytes.IndexByte(pat, '%')
	if i < 0 {
		return bytes.Equal(pat, str)
	}
	return bytes.HasPrefix(str, pat[:i]) && bytes.HasSuffix(str, pat[i+1:])
}

func substPattern(pat, repl, str string) string {
	ps := strings.SplitN(pat, "%", 2)
	if len(ps) != 2 {
		if str == pat {
			return repl
		}
		return str
	}
	in := str
	trimed := str
	if ps[0] != "" {
		trimed = strings.TrimPrefix(in, ps[0])
		if trimed == in {
			return str
		}
	}
	in = trimed
	if ps[1] != "" {
		trimed = strings.TrimSuffix(in, ps[1])
		if trimed == in {
			return str
		}
	}

	rs := strings.SplitN(repl, "%", 2)
	if len(rs) != 2 {
		return repl
	}
	return rs[0] + trimed + rs[1]
}

func substPatternBytes(pat, repl, str []byte) []byte {
	ps := bytes.SplitN(pat, []byte{'%'}, 2)
	if len(ps) != 2 {
		if bytes.Equal(str, pat) {
			return repl
		}
		return str
	}
	in := str
	trimed := str
	if len(ps[0]) != 0 {
		trimed = bytes.TrimPrefix(in, ps[0])
		if bytes.Equal(trimed, in) {
			return str
		}
	}
	in = trimed
	if len(ps[1]) != 0 {
		trimed = bytes.TrimSuffix(in, ps[1])
		if bytes.Equal(trimed, in) {
			return str
		}
	}

	rs := bytes.SplitN(repl, []byte{'%'}, 2)
	if len(rs) != 2 {
		return repl
	}

	r := make([]byte, 0, len(rs[0])+len(trimed)+len(rs[1])+1)
	r = append(r, rs[0]...)
	r = append(r, trimed...)
	r = append(r, rs[1]...)
	return r
}

func substRef(pat, repl, str string) string {
	if strings.IndexByte(pat, '%') >= 0 && strings.IndexByte(repl, '%') >= 0 {
		return substPattern(pat, repl, str)
	}
	str = strings.TrimSuffix(str, pat)
	return str + repl
}

func stripExt(s string) string {
	suf := filepath.Ext(s)
	return s[:len(s)-len(suf)]
}

func trimLeftSpace(s string) string {
	for i, ch := range s {
		if ch != ' ' && ch != '\t' {
			return s[i:]
		}
	}
	return ""
}
<|endoftext|>


<commit_before>package main

import (
	"regexp"
	"strconv"
	"strings"
)

var (
	branches = regexp.MustCompile(`(?:[^,\\]|\\.)*`)
)

func newMatcher(pat string) (m *regexp.Regexp, err error) {
	pat = strings.Replace(pat, `\,`, `\\,`, -1)
	pat = `"` + pat + `"`
	pat, err = strconv.Unquote(pat)
	if err != nil {
		return nil, err
	}
	sp := branches.FindAllString(pat, -1)
	for i := 0; i < len(sp); i++ {
		sp[i] = strings.Replace(sp[i], `\,`, `,`, -1)
		sp[i] = regexp.QuoteMeta(sp[i])
	}
	pat = "(" + strings.Join(sp, "|") + ")"
	return regexp.Compile(pat)
}
<commit_msg>Implement sequense matcher as rough<commit_after>package main

import (
	"regexp"
	"strconv"
	"strings"
)

var (
	branches  = regexp.MustCompile(`(?:[^,\\]|\\.)*`)
	sequenses = regexp.MustCompile(`(?:[^~\\]|\\.)*`)
)

func newMatcher(pat string) (m *regexp.Regexp, err error) {
	pat = strings.Replace(pat, `\,`, `\\,`, -1)
	pat = strings.Replace(pat, `\~`, `\\~`, -1)
	pat = `"` + pat + `"`
	pat, err = strconv.Unquote(pat)
	if err != nil {
		return nil, err
	}
	sls := sequenses.FindAllString(pat, -1)
	for si := 0; si < len(sls); si++ {
		bls := branches.FindAllString(sls[si], -1)
		for bi := 0; bi < len(bls); bi++ {
			bls[bi] = strings.Replace(bls[bi], `\,`, `,`, -1)
			bls[bi] = strings.Replace(bls[bi], `\~`, `~`, -1)
			bls[bi] = regexp.QuoteMeta(bls[bi])
		}
		sls[si] = "(" + strings.Join(bls, "|") + ")"
	}
	pat = strings.Join(sls, "")
	return regexp.Compile(pat)
}
<|endoftext|>


<commit_before>package stage

import (
	. "github.com/etheriqa/crescent"
)

type StagePrototype struct {
	prototype             *Unit
	prototypePhase        int
	prototypeCooldownTime InstanceTime

	iron            *Unit
	ironInitialized bool
	ironLeavingTime *InstanceTime

	silver            *Unit
	silverInitialized bool
	silverLeavingTime *InstanceTime
}

func NewStagePrototype() Stage {
	return &StagePrototype{}
}

func (s *StagePrototype) Initialize(g Game) error {
	id, err := g.Join(UnitGroupAI, "P-0", NewClassStagePrototype())
	if err != nil {
		return err
	}
	s.prototype = g.UnitQuery().Find(id)
	s.prototypeCooldownTime = g.Clock().Now()
	return nil
}

func (s *StagePrototype) OnTick(g Game) {
	s.syncMinion(g)
	s.actPrototype(g)
	s.actIron(g)
	s.actSilver(g)
}

func (s *StagePrototype) syncMinion(g Game) {
	g.UnitQuery().EachFriend(s.prototype, func(u *Unit) {
		switch u.ClassName() {
		case ClassName("Iron"):
			s.iron = u
		case ClassName("Silver"):
			s.silver = u
		default:

		}
	})

	if s.ironLeavingTime == nil && s.iron != nil && s.iron.IsDead() {
		s.ironLeavingTime = new(InstanceTime)
		*s.ironLeavingTime = g.Clock().Add(10 * Second)
	}

	if s.ironLeavingTime != nil && g.Clock().After(*s.ironLeavingTime) {
		g.Leave(s.iron.ID())
		s.iron = nil
		s.ironInitialized = false
		s.ironLeavingTime = nil
	}

	if s.silverLeavingTime == nil && s.silver != nil && s.silver.IsDead() {
		s.silverLeavingTime = new(InstanceTime)
		*s.silverLeavingTime = g.Clock().Add(10 * Second)
	}

	if s.silverLeavingTime != nil && g.Clock().After(*s.silverLeavingTime) {
		g.Leave(s.silver.ID())
		s.silver = nil
		s.silverInitialized = false
		s.silverLeavingTime = nil
	}
}

func (s *StagePrototype) actPrototype(g Game) {
	switch {
	case g.Clock().Before(s.prototypeCooldownTime):
		return
	case s.isActivating(g, s.prototype):
		return
	case s.prototype.Health() == s.prototype.HealthMax():
		return
	case s.prototype.Health() > s.prototype.HealthMax()*0.5:
		if s.prototypePhase != 1 {
			s.prototypePhase = 1
			g.Cooldown(s.prototype, s.prototype.Ability("Attack"))
			g.Cooldown(s.prototype, s.prototype.Ability("Falcon"))
			g.Cooldown(s.prototype, s.prototype.Ability("Shark"))
			g.Cooldown(s.prototype, s.prototype.Ability("Iron"))
		}
		s.actPrototypePhase1(g)
	default:
		if s.prototypePhase != 2 {
			s.prototypePhase = 2
			g.Cooldown(s.prototype, s.prototype.Ability("Attack"))
			g.Cooldown(s.prototype, s.prototype.Ability("Ray"))
			g.Cooldown(s.prototype, s.prototype.Ability("Bell"))
			g.Cooldown(s.prototype, s.prototype.Ability("Silver"))
		}
		s.actPrototypePhase2(g)
	}
}

func (s *StagePrototype) actPrototypePhase1(g Game) {
	o := s.maxThreatEnemy(g)
	if o == nil {
		return
	}
	if s.iron == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Iron"))
	}
	g.Activating(s.prototype, o, s.prototype.Ability("Shark"))
	g.Activating(s.prototype, o, s.prototype.Ability("Falcon"))
	g.Activating(s.prototype, o, s.prototype.Ability("Attack"))
	s.prototypeCooldownTime = g.Clock().Add(3 * Second)
}

func (s *StagePrototype) actPrototypePhase2(g Game) {
	o := s.maxThreatEnemy(g)
	if o == nil {
		return
	}
	if s.silver == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Silver"))
	}
	if s.iron == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Iron"))
	}
	g.Activating(s.prototype, nil, s.prototype.Ability("Bell"))
	g.Activating(s.prototype, o, s.prototype.Ability("Ray"))
	g.Activating(s.prototype, o, s.prototype.Ability("Shark"))
	g.Activating(s.prototype, o, s.prototype.Ability("Falcon"))
	g.Activating(s.prototype, o, s.prototype.Ability("Attack"))
	s.prototypeCooldownTime = g.Clock().Add(2 * Second)
}

func (s *StagePrototype) actIron(g Game) {
	if s.iron == nil {
		return
	}
	if !s.ironInitialized {
		g.Cooldown(s.iron, s.iron.Ability("Stun"))
		g.Cooldown(s.iron, s.iron.Ability("Iron"))
		s.ironInitialized = true
		return
	}
	var o *Unit
	g.UnitQuery().EachEnemy(s.iron, func(u *Unit) {
		if o == nil || u.ClassName() == "Healer" {
			o = u
		}
	})
	g.Activating(s.iron, o, s.iron.Ability("Stun"))
	g.Activating(s.iron, o, s.iron.Ability("Iron"))
}

func (s *StagePrototype) actSilver(g Game) {
	if s.silver == nil {
		return
	}
	if !s.silverInitialized {
		g.Cooldown(s.silver, s.silver.Ability("Silence"))
		g.Cooldown(s.silver, s.silver.Ability("Silver"))
		s.silverInitialized = true
		return
	}
	var o *Unit
	g.UnitQuery().EachEnemy(s.silver, func(u *Unit) {
		if o == nil || u.ClassName() == "Tank" {
			o = u
		}
	})
	g.Activating(s.silver, o, s.silver.Ability("Silence"))
	g.Activating(s.silver, o, s.silver.Ability("Silver"))
}

func (s *StagePrototype) isActivating(g Game, u *Unit) bool {
	return g.EffectQuery().BindSubject(u).Some(func(h Effect) bool {
		switch h.(type) {
		case *Activating:
			return true
		}
		return false
	})
}

func (s *StagePrototype) maxThreatEnemy(g Game) *Unit {
	var u *Unit
	var threat Statistic
	g.EffectQuery().BindObject(s.prototype).Each(func(h Effect) {
		switch h := h.(type) {
		case *Threat:
			if h.Subject().IsDead() {
				return
			}
			if u == nil || h.Threat() > threat {
				u = h.Subject()
				threat = h.Threat()
			}
		}
	})
	return u
}

func NewClassStagePrototype() (class *Class) {
	var attack, falcon, shark, iron, ray, bell, silver, dandelion, tidalBore, diamond, pastorale, vines, waveCrest, morningLull, jadeite Ability
	class = &Class{
		Name:                 "Prototype",
		Health:               40000,
		HealthRegeneration:   0,
		Mana:                 1000,
		ManaRegeneration:     0,
		Armor:                DefaultArmor,
		MagicResistance:      DefaultMagicResistance,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&attack, &falcon, &shark, &iron, &ray, &bell, &silver, &dandelion, &tidalBore, &diamond, &pastorale, &vines, &waveCrest, &morningLull, &jadeite},
	}
	attack = Ability{
		Name:               "Attack",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 0 * Second,
		CooldownDuration:   4 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := 100 + 100*(1-s.Subject().Health()/s.Subject().HealthMax())
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	falcon = Ability{
		Name:               "Falcon",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := Statistic(160)
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	shark = Ability{
		Name:               "Shark",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   12 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := Statistic(310)
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	iron = Ability{
		Name:               "Iron",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   30 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Join(s.Subject().Group(), "Iron-PX1", NewClassStageIron())
		},
	}
	ray = Ability{
		Name:               "Ray",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   20 * Second,
		DisableTypes: []DisableType{
			DisableTypeSilence,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 100).Perform(g)
			if o.IsDead() {
				return
			}
			g.DoT(NewMagicDamage(s, o, 35), ray.Name, 10*Second)
		},
	}
	bell = Ability{
		Name:               "Bell",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   30 * Second,
		DisableTypes: []DisableType{
			DisableTypeSilence,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			g.UnitQuery().EachEnemy(s.Subject(), func(enemy *Unit) {
				if enemy.IsDead() {
					return
				}
				NewMagicDamage(s, enemy, 180).Perform(g)
			})
		},
	}
	silver = Ability{
		Name:               "Silver",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   40 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Join(s.Subject().Group(), "Silver-PX2", NewClassStageSilver())
		},
	}
	return
}

func NewClassStageIron() (class *Class) {
	var silence, iron Ability
	class = &Class{
		Name:                 "Iron",
		Health:               500,
		HealthRegeneration:   50,
		Mana:                 100,
		ManaRegeneration:     0,
		Armor:                300,
		MagicResistance:      DefaultMagicResistance,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&silence, &iron},
	}
	silence = Ability{
		Name:               "Stun",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Disable(o, DisableTypeStun, 3*Second)
		},
	}
	iron = Ability{
		Name:               "Iron",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   5 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 120).Perform(g)
		},
	}
	return class
}

func NewClassStageSilver() (class *Class) {
	var stun, silver Ability
	class = &Class{
		Name:                 "Silver",
		Health:               1000,
		HealthRegeneration:   100,
		Mana:                 100,
		ManaRegeneration:     0,
		Armor:                DefaultArmor,
		MagicResistance:      300,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&stun, &silver},
	}
	stun = Ability{
		Name:               "Silence",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Disable(o, DisableTypeSilence, 3*Second)
		},
	}
	silver = Ability{
		Name:               "Silver",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   5 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 270).Perform(g)
		},
	}
	return class
}
<commit_msg>Fix damages<commit_after>package stage

import (
	. "github.com/etheriqa/crescent"
)

type StagePrototype struct {
	prototype             *Unit
	prototypePhase        int
	prototypeCooldownTime InstanceTime

	iron            *Unit
	ironInitialized bool
	ironLeavingTime *InstanceTime

	silver            *Unit
	silverInitialized bool
	silverLeavingTime *InstanceTime
}

func NewStagePrototype() Stage {
	return &StagePrototype{}
}

func (s *StagePrototype) Initialize(g Game) error {
	id, err := g.Join(UnitGroupAI, "P-0", NewClassStagePrototype())
	if err != nil {
		return err
	}
	s.prototype = g.UnitQuery().Find(id)
	s.prototypeCooldownTime = g.Clock().Now()
	return nil
}

func (s *StagePrototype) OnTick(g Game) {
	s.syncMinion(g)
	s.actPrototype(g)
	s.actIron(g)
	s.actSilver(g)
}

func (s *StagePrototype) syncMinion(g Game) {
	g.UnitQuery().EachFriend(s.prototype, func(u *Unit) {
		switch u.ClassName() {
		case ClassName("Iron"):
			s.iron = u
		case ClassName("Silver"):
			s.silver = u
		default:

		}
	})

	if s.ironLeavingTime == nil && s.iron != nil && s.iron.IsDead() {
		s.ironLeavingTime = new(InstanceTime)
		*s.ironLeavingTime = g.Clock().Add(10 * Second)
	}

	if s.ironLeavingTime != nil && g.Clock().After(*s.ironLeavingTime) {
		g.Leave(s.iron.ID())
		s.iron = nil
		s.ironInitialized = false
		s.ironLeavingTime = nil
	}

	if s.silverLeavingTime == nil && s.silver != nil && s.silver.IsDead() {
		s.silverLeavingTime = new(InstanceTime)
		*s.silverLeavingTime = g.Clock().Add(10 * Second)
	}

	if s.silverLeavingTime != nil && g.Clock().After(*s.silverLeavingTime) {
		g.Leave(s.silver.ID())
		s.silver = nil
		s.silverInitialized = false
		s.silverLeavingTime = nil
	}
}

func (s *StagePrototype) actPrototype(g Game) {
	switch {
	case g.Clock().Before(s.prototypeCooldownTime):
		return
	case s.isActivating(g, s.prototype):
		return
	case s.prototype.Health() == s.prototype.HealthMax():
		return
	case s.prototype.Health() > s.prototype.HealthMax()*0.5:
		if s.prototypePhase != 1 {
			s.prototypePhase = 1
			g.Cooldown(s.prototype, s.prototype.Ability("Attack"))
			g.Cooldown(s.prototype, s.prototype.Ability("Falcon"))
			g.Cooldown(s.prototype, s.prototype.Ability("Shark"))
			g.Cooldown(s.prototype, s.prototype.Ability("Iron"))
		}
		s.actPrototypePhase1(g)
	default:
		if s.prototypePhase != 2 {
			s.prototypePhase = 2
			g.Cooldown(s.prototype, s.prototype.Ability("Attack"))
			g.Cooldown(s.prototype, s.prototype.Ability("Ray"))
			g.Cooldown(s.prototype, s.prototype.Ability("Bell"))
			g.Cooldown(s.prototype, s.prototype.Ability("Silver"))
		}
		s.actPrototypePhase2(g)
	}
}

func (s *StagePrototype) actPrototypePhase1(g Game) {
	o := s.maxThreatEnemy(g)
	if o == nil {
		return
	}
	if s.iron == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Iron"))
	}
	g.Activating(s.prototype, o, s.prototype.Ability("Shark"))
	g.Activating(s.prototype, o, s.prototype.Ability("Falcon"))
	g.Activating(s.prototype, o, s.prototype.Ability("Attack"))
	s.prototypeCooldownTime = g.Clock().Add(3 * Second)
}

func (s *StagePrototype) actPrototypePhase2(g Game) {
	o := s.maxThreatEnemy(g)
	if o == nil {
		return
	}
	if s.silver == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Silver"))
	}
	if s.iron == nil {
		g.Activating(s.prototype, nil, s.prototype.Ability("Iron"))
	}
	g.Activating(s.prototype, nil, s.prototype.Ability("Bell"))
	g.Activating(s.prototype, o, s.prototype.Ability("Ray"))
	g.Activating(s.prototype, o, s.prototype.Ability("Shark"))
	g.Activating(s.prototype, o, s.prototype.Ability("Falcon"))
	g.Activating(s.prototype, o, s.prototype.Ability("Attack"))
	s.prototypeCooldownTime = g.Clock().Add(2 * Second)
}

func (s *StagePrototype) actIron(g Game) {
	if s.iron == nil {
		return
	}
	if !s.ironInitialized {
		g.Cooldown(s.iron, s.iron.Ability("Stun"))
		g.Cooldown(s.iron, s.iron.Ability("Iron"))
		s.ironInitialized = true
		return
	}
	var o *Unit
	g.UnitQuery().EachEnemy(s.iron, func(u *Unit) {
		if o == nil || u.ClassName() == "Healer" {
			o = u
		}
	})
	g.Activating(s.iron, o, s.iron.Ability("Stun"))
	g.Activating(s.iron, o, s.iron.Ability("Iron"))
}

func (s *StagePrototype) actSilver(g Game) {
	if s.silver == nil {
		return
	}
	if !s.silverInitialized {
		g.Cooldown(s.silver, s.silver.Ability("Silence"))
		g.Cooldown(s.silver, s.silver.Ability("Silver"))
		s.silverInitialized = true
		return
	}
	var o *Unit
	g.UnitQuery().EachEnemy(s.silver, func(u *Unit) {
		if o == nil || u.ClassName() == "Tank" {
			o = u
		}
	})
	g.Activating(s.silver, o, s.silver.Ability("Silence"))
	g.Activating(s.silver, o, s.silver.Ability("Silver"))
}

func (s *StagePrototype) isActivating(g Game, u *Unit) bool {
	return g.EffectQuery().BindSubject(u).Some(func(h Effect) bool {
		switch h.(type) {
		case *Activating:
			return true
		}
		return false
	})
}

func (s *StagePrototype) maxThreatEnemy(g Game) *Unit {
	var u *Unit
	var threat Statistic
	g.EffectQuery().BindObject(s.prototype).Each(func(h Effect) {
		switch h := h.(type) {
		case *Threat:
			if h.Subject().IsDead() {
				return
			}
			if u == nil || h.Threat() > threat {
				u = h.Subject()
				threat = h.Threat()
			}
		}
	})
	return u
}

func NewClassStagePrototype() (class *Class) {
	var attack, falcon, shark, iron, ray, bell, silver, dandelion, tidalBore, diamond, pastorale, vines, waveCrest, morningLull, jadeite Ability
	class = &Class{
		Name:                 "Prototype",
		Health:               40000,
		HealthRegeneration:   0,
		Mana:                 1000,
		ManaRegeneration:     0,
		Armor:                DefaultArmor,
		MagicResistance:      DefaultMagicResistance,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&attack, &falcon, &shark, &iron, &ray, &bell, &silver, &dandelion, &tidalBore, &diamond, &pastorale, &vines, &waveCrest, &morningLull, &jadeite},
	}
	attack = Ability{
		Name:               "Attack",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 0 * Second,
		CooldownDuration:   4 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := 100 + 100*(1-s.Subject().Health()/s.Subject().HealthMax())
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	falcon = Ability{
		Name:               "Falcon",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := Statistic(160)
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	shark = Ability{
		Name:               "Shark",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   12 * Second,
		DisableTypes: []DisableType{
			DisableTypeStun,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			baseDamage := Statistic(310)
			NewPhysicalDamage(s, o, baseDamage).Perform(g)
		},
	}
	iron = Ability{
		Name:               "Iron",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   30 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Join(s.Subject().Group(), "Iron-PX1", NewClassStageIron())
		},
	}
	ray = Ability{
		Name:               "Ray",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   20 * Second,
		DisableTypes: []DisableType{
			DisableTypeSilence,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 100).Perform(g)
			if o.IsDead() {
				return
			}
			g.DoT(NewMagicDamage(s, o, 45), ray.Name, 10*Second)
		},
	}
	bell = Ability{
		Name:               "Bell",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   30 * Second,
		DisableTypes: []DisableType{
			DisableTypeSilence,
		},
		Perform: func(g Game, s Subject, o *Unit) {
			g.UnitQuery().EachEnemy(s.Subject(), func(enemy *Unit) {
				if enemy.IsDead() {
					return
				}
				NewMagicDamage(s, enemy, 280).Perform(g)
			})
		},
	}
	silver = Ability{
		Name:               "Silver",
		TargetType:         TargetTypeNone,
		ActivationDuration: 1 * Second,
		CooldownDuration:   40 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Join(s.Subject().Group(), "Silver-PX2", NewClassStageSilver())
		},
	}
	return
}

func NewClassStageIron() (class *Class) {
	var silence, iron Ability
	class = &Class{
		Name:                 "Iron",
		Health:               500,
		HealthRegeneration:   50,
		Mana:                 100,
		ManaRegeneration:     0,
		Armor:                300,
		MagicResistance:      DefaultMagicResistance,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&silence, &iron},
	}
	silence = Ability{
		Name:               "Stun",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Disable(o, DisableTypeStun, 3*Second)
		},
	}
	iron = Ability{
		Name:               "Iron",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   5 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 120).Perform(g)
		},
	}
	return class
}

func NewClassStageSilver() (class *Class) {
	var stun, silver Ability
	class = &Class{
		Name:                 "Silver",
		Health:               1000,
		HealthRegeneration:   100,
		Mana:                 100,
		ManaRegeneration:     0,
		Armor:                DefaultArmor,
		MagicResistance:      300,
		CriticalStrikeChance: DefaultCriticalStrikeChance,
		CriticalStrikeFactor: DefaultCriticalStrikeFactor,
		DamageThreatFactor:   DefaultDamageThreatFactor,
		HealingThreatFactor:  DefaultHealingThreatFactor,
		Abilities:            []*Ability{&stun, &silver},
	}
	stun = Ability{
		Name:               "Silence",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   8 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			g.Disable(o, DisableTypeSilence, 3*Second)
		},
	}
	silver = Ability{
		Name:               "Silver",
		TargetType:         TargetTypeEnemy,
		ActivationDuration: 1 * Second,
		CooldownDuration:   5 * Second,
		DisableTypes:       []DisableType{},
		Perform: func(g Game, s Subject, o *Unit) {
			NewMagicDamage(s, o, 270).Perform(g)
		},
	}
	return class
}
<|endoftext|>


<commit_before>package toml

import (
	"fmt"
	"reflect"
	"strings"
)

type target interface {
	// Dereferences the target.
	get() reflect.Value

	// Store a string at the target.
	setString(v string) error

	// Store a boolean at the target
	setBool(v bool) error

	// Store an int64 at the target
	setInt64(v int64) error

	// Store a float64 at the target
	setFloat64(v float64) error

	// Stores any  value at the target
	set(v reflect.Value) error
}

// valueTarget just contains a reflect.Value that can be set.
// It is used for struct fields.
type valueTarget reflect.Value

func (t valueTarget) get() reflect.Value {
	return reflect.Value(t)
}

func (t valueTarget) set(v reflect.Value) error {
	reflect.Value(t).Set(v)
	return nil
}

func (t valueTarget) setString(v string) error {
	t.get().SetString(v)
	return nil
}

func (t valueTarget) setBool(v bool) error {
	t.get().SetBool(v)
	return nil
}

func (t valueTarget) setInt64(v int64) error {
	t.get().SetInt(v)
	return nil
}

func (t valueTarget) setFloat64(v float64) error {
	t.get().SetFloat(v)
	return nil
}

// interfaceTarget wraps an other target to dereference on get.
type interfaceTarget struct {
	x target
}

func (t interfaceTarget) get() reflect.Value {
	return t.x.get().Elem()
}

func (t interfaceTarget) set(v reflect.Value) error {
	return t.x.set(v)
}

func (t interfaceTarget) setString(v string) error {
	return t.x.setString(v)
}

func (t interfaceTarget) setBool(v bool) error {
	return t.x.setBool(v)
}

func (t interfaceTarget) setInt64(v int64) error {
	return t.x.setInt64(v)
}

func (t interfaceTarget) setFloat64(v float64) error {
	return t.x.setFloat64(v)
}

// mapTarget targets a specific key of a map.
type mapTarget struct {
	v reflect.Value
	k reflect.Value
}

func (t mapTarget) get() reflect.Value {
	return t.v.MapIndex(t.k)
}

func (t mapTarget) set(v reflect.Value) error {
	t.v.SetMapIndex(t.k, v)
	return nil
}

func (t mapTarget) setString(v string) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setBool(v bool) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setInt64(v int64) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setFloat64(v float64) error {
	return t.set(reflect.ValueOf(v))
}

func ensureSlice(t target) error {
	f := t.get()

	switch f.Type().Kind() {
	case reflect.Slice:
		if f.IsNil() {
			return t.set(reflect.MakeSlice(f.Type(), 0, 0))
		}
	case reflect.Interface:
		if f.IsNil() {
			return t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0))
		}
		if f.Type().Elem().Kind() != reflect.Slice {
			return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind())
		}
	case reflect.Ptr:
		if f.IsNil() {
			ptr := reflect.New(f.Type().Elem())
			err := t.set(ptr)
			if err != nil {
				return err
			}
			f = t.get()
		}
		return ensureSlice(valueTarget(f.Elem()))
	default:
		return fmt.Errorf("cannot initialize a slice in %s", f.Kind())
	}
	return nil
}

var sliceInterfaceType = reflect.TypeOf([]interface{}{})

func setString(t target, v string) error {
	f := t.get()

	switch f.Kind() {
	case reflect.String:
		return t.setString(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setString(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign string to a %s", f.Kind())
	}
}

func setBool(t target, v bool) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Bool:
		return t.setBool(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setBool(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign bool to a %s", f.String())
	}
}

func setInt64(t target, v int64) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		// TODO: overflow checks
		return t.setInt64(v)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		// TODO: overflow checks
		converted := reflect.ValueOf(v).Convert(f.Type())
		return t.set(converted)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setInt64(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign int64 to a %s", f.String())
	}
}

func setFloat64(t target, v float64) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Float32, reflect.Float64:
		// TODO: overflow checks
		return t.setFloat64(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setFloat64(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign float64 to a %s", f.String())
	}
}

func pushNew(t target) (target, error) {
	f := t.get()

	switch f.Kind() {
	case reflect.Slice:
		idx := f.Len()
		err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem()))
		if err != nil {
			return nil, err
		}
		return valueTarget(t.get().Index(idx)), nil
	case reflect.Interface:
		if f.IsNil() {
			panic("interface should have been initialized")
		}
		ifaceElem := f.Elem()
		if ifaceElem.Kind() != reflect.Slice {
			return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
		}
		idx := ifaceElem.Len()
		newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
		newSlice := reflect.Append(ifaceElem, newElem)
		err := t.set(newSlice)
		if err != nil {
			return nil, err
		}
		return valueTarget(t.get().Elem().Index(idx)), nil
	case reflect.Ptr:
		return pushNew(valueTarget(f.Elem()))
	default:
		return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
	}
}

func scopeTableTarget(append bool, t target, name string) (target, bool, error) {
	x := t.get()

	switch x.Kind() {
	// Kinds that need to recurse

	case reflect.Interface:
		t, err := scopeInterface(append, t)
		if err != nil {
			return t, false, err
		}
		return scopeTableTarget(append, t, name)
	case reflect.Ptr:
		t, err := scopePtr(t)
		if err != nil {
			return t, false, err
		}
		return scopeTableTarget(append, t, name)
	case reflect.Slice:
		t, err := scopeSlice(append, t)
		if err != nil {
			return t, false, err
		}
		append = false
		return scopeTableTarget(append, t, name)

	// Terminal kinds

	case reflect.Struct:
		return scopeStruct(x, name)
	case reflect.Map:
		return scopeMap(x, name)
	default:
		panic(fmt.Errorf("can't scope on a %s", x.Kind()))
	}
}

func scopeInterface(append bool, t target) (target, error) {
	err := initInterface(append, t)
	if err != nil {
		return t, err
	}
	return interfaceTarget{t}, nil
}

func scopePtr(t target) (target, error) {
	err := initPtr(t)
	if err != nil {
		return t, err
	}
	return valueTarget(t.get().Elem()), nil
}

func initPtr(t target) error {
	x := t.get()
	if !x.IsNil() {
		return nil
	}
	return t.set(reflect.New(x.Type().Elem()))
}

// initInterface makes sure that the interface pointed at by the target is not
// nil.
// Returns the target to the initialized value of the target.
func initInterface(append bool, t target) error {
	x := t.get()

	if x.Kind() != reflect.Interface {
		panic("this should only be called on interfaces")
	}

	if !x.IsNil() {
		return nil
	}

	var newElement reflect.Value
	if append {
		newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)
	} else {
		newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{}))
	}
	err := t.set(newElement)
	if err != nil {
		return err
	}

	return nil
}

func scopeSlice(append bool, t target) (target, error) {
	v := t.get()

	if append {
		newElem := reflect.New(v.Type().Elem())
		newSlice := reflect.Append(v, newElem.Elem())
		err := t.set(newSlice)
		if err != nil {
			return t, err
		}
		v = t.get()
	}
	return valueTarget(v.Index(v.Len() - 1)), nil
}

func scopeMap(v reflect.Value, name string) (target, bool, error) {
	if v.IsNil() {
		v.Set(reflect.MakeMap(v.Type()))
	}

	k := reflect.ValueOf(name)
	if !v.MapIndex(k).IsValid() {
		newElem := reflect.New(v.Type().Elem())
		v.SetMapIndex(k, newElem.Elem())
	}

	return mapTarget{
		v: v,
		k: k,
	}, true, nil
}

func scopeStruct(v reflect.Value, name string) (target, bool, error) {
	// TODO: cache this, and reduce allocations

	fieldPaths := map[string][]int{}

	path := make([]int, 0, 16)
	var walk func(reflect.Value)
	walk = func(v reflect.Value) {
		t := v.Type()
		for i := 0; i < t.NumField(); i++ {
			l := len(path)
			path = append(path, i)
			f := t.Field(i)
			if f.PkgPath != "" {
				// only consider exported fields
				continue
			}
			if f.Anonymous {
				walk(v.Field(i))
			} else {
				fieldName, ok := f.Tag.Lookup("toml")
				if !ok {
					fieldName = f.Name
				}

				pathCopy := make([]int, len(path))
				copy(pathCopy, path)

				fieldPaths[fieldName] = pathCopy
				// extra copy for the case-insensitive match
				fieldPaths[strings.ToLower(fieldName)] = pathCopy
			}
			path = path[:l]
		}
	}

	walk(v)

	path, ok := fieldPaths[name]
	if !ok {
		path, ok = fieldPaths[strings.ToLower(name)]
	}
	if !ok {
		return nil, false, nil
	}

	return valueTarget(v.FieldByIndex(path)), true, nil
}
<commit_msg>Cast map key type<commit_after>package toml

import (
	"fmt"
	"reflect"
	"strings"
)

type target interface {
	// Dereferences the target.
	get() reflect.Value

	// Store a string at the target.
	setString(v string) error

	// Store a boolean at the target
	setBool(v bool) error

	// Store an int64 at the target
	setInt64(v int64) error

	// Store a float64 at the target
	setFloat64(v float64) error

	// Stores any  value at the target
	set(v reflect.Value) error
}

// valueTarget just contains a reflect.Value that can be set.
// It is used for struct fields.
type valueTarget reflect.Value

func (t valueTarget) get() reflect.Value {
	return reflect.Value(t)
}

func (t valueTarget) set(v reflect.Value) error {
	reflect.Value(t).Set(v)
	return nil
}

func (t valueTarget) setString(v string) error {
	t.get().SetString(v)
	return nil
}

func (t valueTarget) setBool(v bool) error {
	t.get().SetBool(v)
	return nil
}

func (t valueTarget) setInt64(v int64) error {
	t.get().SetInt(v)
	return nil
}

func (t valueTarget) setFloat64(v float64) error {
	t.get().SetFloat(v)
	return nil
}

// interfaceTarget wraps an other target to dereference on get.
type interfaceTarget struct {
	x target
}

func (t interfaceTarget) get() reflect.Value {
	return t.x.get().Elem()
}

func (t interfaceTarget) set(v reflect.Value) error {
	return t.x.set(v)
}

func (t interfaceTarget) setString(v string) error {
	return t.x.setString(v)
}

func (t interfaceTarget) setBool(v bool) error {
	return t.x.setBool(v)
}

func (t interfaceTarget) setInt64(v int64) error {
	return t.x.setInt64(v)
}

func (t interfaceTarget) setFloat64(v float64) error {
	return t.x.setFloat64(v)
}

// mapTarget targets a specific key of a map.
type mapTarget struct {
	v reflect.Value
	k reflect.Value
}

func (t mapTarget) get() reflect.Value {
	return t.v.MapIndex(t.k)
}

func (t mapTarget) set(v reflect.Value) error {
	t.v.SetMapIndex(t.k, v)
	return nil
}

func (t mapTarget) setString(v string) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setBool(v bool) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setInt64(v int64) error {
	return t.set(reflect.ValueOf(v))
}

func (t mapTarget) setFloat64(v float64) error {
	return t.set(reflect.ValueOf(v))
}

func ensureSlice(t target) error {
	f := t.get()

	switch f.Type().Kind() {
	case reflect.Slice:
		if f.IsNil() {
			return t.set(reflect.MakeSlice(f.Type(), 0, 0))
		}
	case reflect.Interface:
		if f.IsNil() {
			return t.set(reflect.MakeSlice(sliceInterfaceType, 0, 0))
		}
		if f.Type().Elem().Kind() != reflect.Slice {
			return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind())
		}
	case reflect.Ptr:
		if f.IsNil() {
			ptr := reflect.New(f.Type().Elem())
			err := t.set(ptr)
			if err != nil {
				return err
			}
			f = t.get()
		}
		return ensureSlice(valueTarget(f.Elem()))
	default:
		return fmt.Errorf("cannot initialize a slice in %s", f.Kind())
	}
	return nil
}

var sliceInterfaceType = reflect.TypeOf([]interface{}{})

func setString(t target, v string) error {
	f := t.get()

	switch f.Kind() {
	case reflect.String:
		return t.setString(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setString(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign string to a %s", f.Kind())
	}
}

func setBool(t target, v bool) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Bool:
		return t.setBool(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setBool(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign bool to a %s", f.String())
	}
}

func setInt64(t target, v int64) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		// TODO: overflow checks
		converted := reflect.ValueOf(v).Convert(f.Type())
		return t.set(converted)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		// TODO: overflow checks
		converted := reflect.ValueOf(v).Convert(f.Type())
		return t.set(converted)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setInt64(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign int64 to a %s", f.String())
	}
}

func setFloat64(t target, v float64) error {
	f := t.get()

	switch f.Kind() {
	case reflect.Float32, reflect.Float64:
		// TODO: overflow checks
		return t.setFloat64(v)
	case reflect.Interface:
		return t.set(reflect.ValueOf(v))
	case reflect.Ptr:
		if !f.Elem().IsValid() {
			err := t.set(reflect.New(f.Type().Elem()))
			if err != nil {
				return err
			}
			f = t.get()
		}
		return setFloat64(valueTarget(f.Elem()), v)
	default:
		return fmt.Errorf("cannot assign float64 to a %s", f.String())
	}
}

func pushNew(t target) (target, error) {
	f := t.get()

	switch f.Kind() {
	case reflect.Slice:
		idx := f.Len()
		err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem()))
		if err != nil {
			return nil, err
		}
		return valueTarget(t.get().Index(idx)), nil
	case reflect.Interface:
		if f.IsNil() {
			panic("interface should have been initialized")
		}
		ifaceElem := f.Elem()
		if ifaceElem.Kind() != reflect.Slice {
			return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
		}
		idx := ifaceElem.Len()
		newElem := reflect.New(ifaceElem.Type().Elem()).Elem()
		newSlice := reflect.Append(ifaceElem, newElem)
		err := t.set(newSlice)
		if err != nil {
			return nil, err
		}
		return valueTarget(t.get().Elem().Index(idx)), nil
	case reflect.Ptr:
		return pushNew(valueTarget(f.Elem()))
	default:
		return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind())
	}
}

func scopeTableTarget(append bool, t target, name string) (target, bool, error) {
	x := t.get()

	switch x.Kind() {
	// Kinds that need to recurse

	case reflect.Interface:
		t, err := scopeInterface(append, t)
		if err != nil {
			return t, false, err
		}
		return scopeTableTarget(append, t, name)
	case reflect.Ptr:
		t, err := scopePtr(t)
		if err != nil {
			return t, false, err
		}
		return scopeTableTarget(append, t, name)
	case reflect.Slice:
		t, err := scopeSlice(append, t)
		if err != nil {
			return t, false, err
		}
		append = false
		return scopeTableTarget(append, t, name)

	// Terminal kinds

	case reflect.Struct:
		return scopeStruct(x, name)
	case reflect.Map:
		return scopeMap(x, name)
	default:
		panic(fmt.Errorf("can't scope on a %s", x.Kind()))
	}
}

func scopeInterface(append bool, t target) (target, error) {
	err := initInterface(append, t)
	if err != nil {
		return t, err
	}
	return interfaceTarget{t}, nil
}

func scopePtr(t target) (target, error) {
	err := initPtr(t)
	if err != nil {
		return t, err
	}
	return valueTarget(t.get().Elem()), nil
}

func initPtr(t target) error {
	x := t.get()
	if !x.IsNil() {
		return nil
	}
	return t.set(reflect.New(x.Type().Elem()))
}

// initInterface makes sure that the interface pointed at by the target is not
// nil.
// Returns the target to the initialized value of the target.
func initInterface(append bool, t target) error {
	x := t.get()

	if x.Kind() != reflect.Interface {
		panic("this should only be called on interfaces")
	}

	if !x.IsNil() {
		return nil
	}

	var newElement reflect.Value
	if append {
		newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)
	} else {
		newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{}))
	}
	err := t.set(newElement)
	if err != nil {
		return err
	}

	return nil
}

func scopeSlice(append bool, t target) (target, error) {
	v := t.get()

	if append {
		newElem := reflect.New(v.Type().Elem())
		newSlice := reflect.Append(v, newElem.Elem())
		err := t.set(newSlice)
		if err != nil {
			return t, err
		}
		v = t.get()
	}
	return valueTarget(v.Index(v.Len() - 1)), nil
}

func scopeMap(v reflect.Value, name string) (target, bool, error) {
	if v.IsNil() {
		v.Set(reflect.MakeMap(v.Type()))
	}

	k := reflect.ValueOf(name)

	keyType := v.Type().Key()
	if !k.Type().AssignableTo(keyType) {
		if !k.Type().ConvertibleTo(keyType) {
			return nil, false, fmt.Errorf("cannot convert string into map key type %s", keyType)
		}
		k = k.Convert(keyType)
	}

	if !v.MapIndex(k).IsValid() {
		newElem := reflect.New(v.Type().Elem())
		v.SetMapIndex(k, newElem.Elem())
	}

	return mapTarget{
		v: v,
		k: k,
	}, true, nil
}

func scopeStruct(v reflect.Value, name string) (target, bool, error) {
	// TODO: cache this, and reduce allocations

	fieldPaths := map[string][]int{}

	path := make([]int, 0, 16)
	var walk func(reflect.Value)
	walk = func(v reflect.Value) {
		t := v.Type()
		for i := 0; i < t.NumField(); i++ {
			l := len(path)
			path = append(path, i)
			f := t.Field(i)
			if f.PkgPath != "" {
				// only consider exported fields
				continue
			}
			if f.Anonymous {
				walk(v.Field(i))
			} else {
				fieldName, ok := f.Tag.Lookup("toml")
				if !ok {
					fieldName = f.Name
				}

				pathCopy := make([]int, len(path))
				copy(pathCopy, path)

				fieldPaths[fieldName] = pathCopy
				// extra copy for the case-insensitive match
				fieldPaths[strings.ToLower(fieldName)] = pathCopy
			}
			path = path[:l]
		}
	}

	walk(v)

	path, ok := fieldPaths[name]
	if !ok {
		path, ok = fieldPaths[strings.ToLower(name)]
	}
	if !ok {
		return nil, false, nil
	}

	return valueTarget(v.FieldByIndex(path)), true, nil
}
<|endoftext|>


<commit_before>package wrap

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"time"

	"github.com/Songmu/retry"
	"github.com/Songmu/wrapcommander"
	mackerel "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"golang.org/x/sync/errgroup"
	cli "gopkg.in/urfave/cli.v1"
)

type wrap struct {
	name                  string
	detail                bool
	memo                  string
	warning               bool
	preventAlertAutoClose bool
	hostID                string
	apibase               string
	apikey                string
	cmd                   []string

	outStream, errStream io.Writer
}

func (wr *wrap) run() error {
	re := wr.runCmd()
	if err := wr.report(re); err != nil {
		logger.Logf("error", "failed to post following report to Mackerel: %s\n%s",
			err, re.buildMsg(wr.detail))
	}
	if !re.Success {
		return cli.NewExitError(re.Msg, re.ExitCode)
	}
	return nil
}

func (wr *wrap) runCmd() *result {
	cmd := exec.Command(wr.cmd[0], wr.cmd[1:]...)
	re := &result{
		Cmd:  wr.cmd,
		Name: wr.name,
		Memo: wr.memo,
	}

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	defer stdoutPipe.Close()

	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	defer stderrPipe.Close()

	var (
		bufStdout = &bytes.Buffer{}
		bufStderr = &bytes.Buffer{}
		bufMerged = &bytes.Buffer{}
	)
	stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(bufStdout, bufMerged))
	stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(bufStderr, bufMerged))

	re.StartAt = time.Now()
	err = cmd.Start()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	re.Pid = cmd.Process.Pid
	eg := &errgroup.Group{}

	eg.Go(func() error {
		defer stdoutPipe.Close()
		_, err := io.Copy(wr.outStream, stdoutPipe2)
		return err
	})
	eg.Go(func() error {
		defer stderrPipe.Close()
		_, err := io.Copy(wr.errStream, stderrPipe2)
		return err
	})
	eg.Wait()

	cmdErr := cmd.Wait()
	re.EndAt = time.Now()
	re.ExitCode = wrapcommander.ResolveExitCode(cmdErr)
	if re.ExitCode > 128 {
		w, ok := wrapcommander.ErrorToWaitStatus(cmdErr)
		if ok {
			re.Signaled = w.Signaled()
		}
	}
	if !re.Signaled {
		re.Msg = fmt.Sprintf("command exited with code: %d", re.ExitCode)
	} else {
		re.Msg = fmt.Sprintf("command died with signal: %d", re.ExitCode&127)
	}
	re.Stdout = bufStdout.String()
	re.Stderr = bufStderr.String()
	re.Output = bufMerged.String()

	re.Success = re.ExitCode == 0
	return re
}

func (wr *wrap) report(re *result) error {
	defer func() {
		err := re.saveResult()
		if err != nil {
			logger.Logf("error", "failed to save result: %s", err)
		}
	}()

	if wr.apikey == "" || wr.hostID == "" {
		return fmt.Errorf("Both of apikey and hostID are needed to report result to Mackerel")
	}
	lastRe, err := re.loadLastResult()
	if err != nil {
		// resultFile something went wrong.
		// It may be no permission, broken json, not a normal file, and so on.
		// Though it rough, try to delete as workaround
		err := os.RemoveAll(re.resultFile())
		if err != nil {
			return err
		}
	}
	if !re.Success || !wr.preventAlertAutoClose && (lastRe == nil || !lastRe.Success) {
		return wr.doReport(re)
	}
	return nil
}

func (wr *wrap) doReport(re *result) error {
	checkSt := mackerel.CheckStatusOK
	if !re.Success {
		if wr.warning {
			checkSt = mackerel.CheckStatusWarning
		} else {
			checkSt = mackerel.CheckStatusCritical
		}
	}
	payload := &mackerel.CheckReports{
		Reports: []*mackerel.CheckReport{
			{
				Source:     mackerel.NewCheckSourceHost(wr.hostID),
				Name:       re.checkName(),
				Status:     checkSt,
				OccurredAt: time.Now().Unix(),
				Message:    re.buildMsg(wr.detail),
			},
		},
	}
	mcli, err := mackerel.NewClientWithOptions(wr.apikey, wr.apibase, false)
	if err != nil {
		return err
	}
	return retry.Retry(3, time.Second*3, func() error {
		return mcli.PostCheckReports(payload)
	})
}
<commit_msg>[wrap] do not save or load result file when preventAlertAutoClose is ture<commit_after>package wrap

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"time"

	"github.com/Songmu/retry"
	"github.com/Songmu/wrapcommander"
	mackerel "github.com/mackerelio/mackerel-client-go"
	"github.com/mackerelio/mkr/logger"
	"golang.org/x/sync/errgroup"
	cli "gopkg.in/urfave/cli.v1"
)

type wrap struct {
	name                  string
	detail                bool
	memo                  string
	warning               bool
	preventAlertAutoClose bool
	hostID                string
	apibase               string
	apikey                string
	cmd                   []string

	outStream, errStream io.Writer
}

func (wr *wrap) run() error {
	re := wr.runCmd()
	if err := wr.report(re); err != nil {
		logger.Logf("error", "failed to post following report to Mackerel: %s\n%s",
			err, re.buildMsg(wr.detail))
	}
	if !re.Success {
		return cli.NewExitError(re.Msg, re.ExitCode)
	}
	return nil
}

func (wr *wrap) runCmd() *result {
	cmd := exec.Command(wr.cmd[0], wr.cmd[1:]...)
	re := &result{
		Cmd:  wr.cmd,
		Name: wr.name,
		Memo: wr.memo,
	}

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	defer stdoutPipe.Close()

	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	defer stderrPipe.Close()

	var (
		bufStdout = &bytes.Buffer{}
		bufStderr = &bytes.Buffer{}
		bufMerged = &bytes.Buffer{}
	)
	stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(bufStdout, bufMerged))
	stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(bufStderr, bufMerged))

	re.StartAt = time.Now()
	err = cmd.Start()
	if err != nil {
		return re.errorEnd("command invocation failed with follwing error: %s", err)
	}
	re.Pid = cmd.Process.Pid
	eg := &errgroup.Group{}

	eg.Go(func() error {
		defer stdoutPipe.Close()
		_, err := io.Copy(wr.outStream, stdoutPipe2)
		return err
	})
	eg.Go(func() error {
		defer stderrPipe.Close()
		_, err := io.Copy(wr.errStream, stderrPipe2)
		return err
	})
	eg.Wait()

	cmdErr := cmd.Wait()
	re.EndAt = time.Now()
	re.ExitCode = wrapcommander.ResolveExitCode(cmdErr)
	if re.ExitCode > 128 {
		w, ok := wrapcommander.ErrorToWaitStatus(cmdErr)
		if ok {
			re.Signaled = w.Signaled()
		}
	}
	if !re.Signaled {
		re.Msg = fmt.Sprintf("command exited with code: %d", re.ExitCode)
	} else {
		re.Msg = fmt.Sprintf("command died with signal: %d", re.ExitCode&127)
	}
	re.Stdout = bufStdout.String()
	re.Stderr = bufStderr.String()
	re.Output = bufMerged.String()

	re.Success = re.ExitCode == 0
	return re
}

func (wr *wrap) report(re *result) error {
	defer func() {
		if wr.preventAlertAutoClose {
			return
		}
		err := re.saveResult()
		if err != nil {
			logger.Logf("error", "failed to save result: %s", err)
		}
	}()

	if wr.apikey == "" || wr.hostID == "" {
		return fmt.Errorf("Both of apikey and hostID are needed to report result to Mackerel")
	}
	var lastRe *result
	if !wr.preventAlertAutoClose {
		var err error
		lastRe, err = re.loadLastResult()
		if err != nil {
			// resultFile something went wrong.
			// It may be no permission, broken json, not a normal file, and so on.
			// Though it rough, try to delete as workaround
			err := os.RemoveAll(re.resultFile())
			if err != nil {
				return err
			}
		}
	}
	if !re.Success || !wr.preventAlertAutoClose && (lastRe == nil || !lastRe.Success) {
		return wr.doReport(re)
	}
	return nil
}

func (wr *wrap) doReport(re *result) error {
	checkSt := mackerel.CheckStatusOK
	if !re.Success {
		if wr.warning {
			checkSt = mackerel.CheckStatusWarning
		} else {
			checkSt = mackerel.CheckStatusCritical
		}
	}
	payload := &mackerel.CheckReports{
		Reports: []*mackerel.CheckReport{
			{
				Source:     mackerel.NewCheckSourceHost(wr.hostID),
				Name:       re.checkName(),
				Status:     checkSt,
				OccurredAt: time.Now().Unix(),
				Message:    re.buildMsg(wr.detail),
			},
		},
	}
	mcli, err := mackerel.NewClientWithOptions(wr.apikey, wr.apibase, false)
	if err != nil {
		return err
	}
	return retry.Retry(3, time.Second*3, func() error {
		return mcli.PostCheckReports(payload)
	})
}
<|endoftext|>