<commit_before>// GoUpdate written by Glen Baker glen@ehab.it
// GoUpdate is protected under MIT License for Open Source Software.

package main

import (
	"fmt"
	"github.com/codeskyblue/go-sh"
	"io/ioutil"
	"os"
	"sync"
)

var GOPATH = os.Getenv("GOPATH")

var GITHUBPATH = GOPATH + "/src/github.com"
var BITBUCKETPATH = GOPATH + "/src/bitbucket.org"
var GOOGLECODEPATH = GOPATH + "/src/code.google.com"
var GOPKGPATH = GOPATH + "/src/gopkg.in"

var HOSTS = []string{
	GITHUBPATH,
	BITBUCKETPATH,
	GOOGLECODEPATH,
	GOPKGPATH,
}

var packagesUpdated int
var problemPackages []string

func IsDir(path string) bool {
	f, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Println(err)
		}
		return false
	}

	if f.IsDir() {
		return true
	}
	return false
}

func ParseFilenames(path string) []string {
	var filenames []string
	contents, _ := ioutil.ReadDir(path)
	for _, f := range contents {
		filenames = append(filenames, f.Name())
	}
	return filenames
}

func IsGoFile(filename string) bool {
	if filename[len(filename)-3:] == ".go" {
		return true
	}
	return false
}

func CheckDirForGo(path string) bool {
	if IsDir(path) {
		for _, f := range ParseFilenames(path) {
			if IsGoFile(f) {
				return true
			}
		}
	}
	return false
}

func UpdatePackage(path string) bool {
	if IsDir(path) {
		err := sh.Command("go", "get", "-u", sh.Dir(path)).Run()
		if err != nil {
			problemPackages = append(problemPackages, path+" "+err.Error())
			fmt.Println("Package not updated:", path, err)
			return false
		}

		packagesUpdated += 1
		fmt.Println("Updated package:", path)
		return true
	}
	return false
}

func UpdatePackages(hostPath string) {
	paths := make(chan string, 64)

	// spawn four workers to update packages
	var wg sync.WaitGroup
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			for path := range paths {
				UpdatePackage(path)
			}
			wg.Done()
		}()
	}

	userPaths := ParseFilenames(hostPath)
	for _, user := range userPaths {
		// one directory deep
		packagePaths := ParseFilenames(hostPath + "/" + user)
		if hostPath == GOPKGPATH {
			path := hostPath + "/" + user
			if CheckDirForGo(path) {
				paths <- path
			}
		}
		for _, pack := range packagePaths {
			// two directories deep
			if hostPath == GOOGLECODEPATH {
				subpackPaths := ParseFilenames(hostPath + "/" + user + "/" + pack)
				for _, subpack := range subpackPaths {
					// three directories deep
					path := hostPath + "/" + user + "/" + pack + "/" + subpack
					if CheckDirForGo(path) {
						paths <- path
					} else {
						// four directories deep
						supSubPaths := ParseFilenames(path)
						for _, supSubPath := range supSubPaths {
							if CheckDirForGo(path + "/" + supSubPath) {
								paths <- supSubPath
							}
						}
					}
				}
			} else if hostPath == GITHUBPATH || hostPath == BITBUCKETPATH {
				path := hostPath + "/" + user + "/" + pack
				if CheckDirForGo(path) {
					paths <- path
				}
			}
		}
	}

	close(paths)
	wg.Wait()
}

func UpdatePackagesOnHosts(hosts []string) {
	fmt.Println("Updating Go packages hosted on github.com, bitbucket.org, code.google.com and gopkg.in")

	for _, host := range hosts {
		UpdatePackages(host)
	}
}

func reportUpdateStats() {
	fmt.Println("Total packages updated:", packagesUpdated)
	fmt.Println("Total package update errors:", len(problemPackages))
	for i, pack := range problemPackages {
		fmt.Println(i+1, pack)
	}
}

func main() {
	UpdatePackagesOnHosts(HOSTS)

	fmt.Println("\n  We have updated every G-bomb package we could find.\n  Good luck out there you sexy gopher!\n")

	reportUpdateStats()
}
<commit_msg>Updated reportUpdateStats copy<commit_after>// GoUpdate written by Glen Baker glen@ehab.it
// GoUpdate is protected under MIT License for Open Source Software.

package main

import (
	"fmt"
	"github.com/codeskyblue/go-sh"
	"io/ioutil"
	"os"
	"sync"
)

var GOPATH = os.Getenv("GOPATH")

var GITHUBPATH = GOPATH + "/src/github.com"
var BITBUCKETPATH = GOPATH + "/src/bitbucket.org"
var GOOGLECODEPATH = GOPATH + "/src/code.google.com"
var GOPKGPATH = GOPATH + "/src/gopkg.in"

var HOSTS = []string{
	GITHUBPATH,
	BITBUCKETPATH,
	GOOGLECODEPATH,
	GOPKGPATH,
}

var packagesUpdated int
var problemPackages []string

func IsDir(path string) bool {
	f, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Println(err)
		}
		return false
	}

	if f.IsDir() {
		return true
	}
	return false
}

func ParseFilenames(path string) []string {
	var filenames []string
	contents, _ := ioutil.ReadDir(path)
	for _, f := range contents {
		filenames = append(filenames, f.Name())
	}
	return filenames
}

func IsGoFile(filename string) bool {
	if filename[len(filename)-3:] == ".go" {
		return true
	}
	return false
}

func CheckDirForGo(path string) bool {
	if IsDir(path) {
		for _, f := range ParseFilenames(path) {
			if IsGoFile(f) {
				return true
			}
		}
	}
	return false
}

func UpdatePackage(path string) bool {
	if IsDir(path) {
		err := sh.Command("go", "get", "-u", sh.Dir(path)).Run()
		if err != nil {
			problemPackages = append(problemPackages, path+" "+err.Error())
			fmt.Println("Package not updated:", path, err)
			return false
		}

		packagesUpdated += 1
		fmt.Println("Updated package:", path)
		return true
	}
	return false
}

func UpdatePackages(hostPath string) {
	paths := make(chan string, 64)

	// spawn four workers to update packages
	var wg sync.WaitGroup
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			for path := range paths {
				UpdatePackage(path)
			}
			wg.Done()
		}()
	}

	userPaths := ParseFilenames(hostPath)
	for _, user := range userPaths {
		// one directory deep
		packagePaths := ParseFilenames(hostPath + "/" + user)
		if hostPath == GOPKGPATH {
			path := hostPath + "/" + user
			if CheckDirForGo(path) {
				paths <- path
			}
		}
		for _, pack := range packagePaths {
			// two directories deep
			if hostPath == GOOGLECODEPATH {
				subpackPaths := ParseFilenames(hostPath + "/" + user + "/" + pack)
				for _, subpack := range subpackPaths {
					// three directories deep
					path := hostPath + "/" + user + "/" + pack + "/" + subpack
					if CheckDirForGo(path) {
						paths <- path
					} else {
						// four directories deep
						supSubPaths := ParseFilenames(path)
						for _, supSubPath := range supSubPaths {
							if CheckDirForGo(path + "/" + supSubPath) {
								paths <- supSubPath
							}
						}
					}
				}
			} else if hostPath == GITHUBPATH || hostPath == BITBUCKETPATH {
				path := hostPath + "/" + user + "/" + pack
				if CheckDirForGo(path) {
					paths <- path
				}
			}
		}
	}

	close(paths)
	wg.Wait()
}

func UpdatePackagesOnHosts(hosts []string) {
	fmt.Println("Updating Go packages hosted on github.com, bitbucket.org, code.google.com and gopkg.in")

	for _, host := range hosts {
		UpdatePackages(host)
	}
}

func reportUpdateStats() {
	fmt.Println("'go get -u' ran successfully on", packagesUpdated, "packages.")
	fmt.Println("'go get -u' raised errors trying to update", len(problemPackages), "packages.")
	for i, pack := range problemPackages {
		fmt.Println(i+1, pack)
	}
}

func main() {
	UpdatePackagesOnHosts(HOSTS)

	fmt.Println("\n  We have updated every G-bomb package we could find.\n  Good luck out there you sexy gopher!\n")

	reportUpdateStats()
}
<|endoftext|>


<commit_before>package options

// AddDefaultOptions adds internal options that can be set by the user through
// the command-line interface.
func (o *Options) AddDefaultOptions() {
	o.Add(NewBoolOption("center"))
	o.Add(NewStringOption("columns"))
	o.Add(NewStringOption("sort"))
	o.Add(NewStringOption("topbar"))
}

// Defaults is the default, internal configuration file.
const Defaults string = `
# Global options
set nocenter
set columns=artist,track,title,album,year,time
set sort=file,track,disc,album,year,albumartistsort
set topbar="|$shortname $version||;${tag|artist} - ${tag|title}||${tag|album}, ${tag|year};$volume $mode $elapsed ${state} $time;|[${list|index}/${list|total}] ${list|title}||;;"

# Song tag styles
style album teal
style artist yellow
style date green
style time darkmagenta
style title white bold
style track green
style year green

# Tracklist styles
style allTagsMissing red
style currentSong black yellow
style cursor black white
style header green bold
style mostTagsMissing red
style selection white blue

# Topbar styles
style elapsed green
style listIndex darkblue
style listTitle blue bold
style listTotal darkblue
style mute red
style shortName bold
style state default
style switches teal
style tagMissing red
style topbar darkgray
style version gray
style volume green

# Other styles
style commandText default
style errorText white red bold
style readout default
style searchText white bold
style sequenceText teal
style statusbar default
style visualText teal

# Keyboard bindings: cursor movement
bind <Up> cursor up
bind k cursor up
bind <Down> cursor down
bind j cursor down
bind <PgUp> cursor pgup
bind <PgDn> cursor pgdn
bind <Home> cursor home
bind gg cursor home
bind <End> cursor end
bind G cursor end
bind gc cursor current
bind R cursor random
bind b cursor prevOf album
bind e cursor nextOf album

# Keyboard bindings: input mode
bind : inputmode input
bind / inputmode search
bind <F3> inputmode search
bind v select visual
bind V select visual

# Keyboard bindings: player and mixer
bind <Enter> play selection
bind <Space> pause
bind s stop
bind h previous
bind l next
bind + volume +2
bind - volume -2
bind <left> seek -5
bind <right> seek +5
bind <Alt-M> volume mute
bind S single

# Keyboard bindings: other
bind <C-c> quit
bind <C-l> redraw
bind <C-s> sort
bind i print file
bind t list next
bind T list previous
bind <C-d> list duplicate
bind <C-g> list remove
bind <C-j> isolate artist
bind <C-t> isolate albumartist album
bind & select nearby albumartist album
bind m select toggle
bind a add
bind <Delete> cut
bind x cut
bind y yank
bind p paste after
bind P paste before
`
<commit_msg>Add default bindings H, M, L for cursor high, middle, low<commit_after>package options

// AddDefaultOptions adds internal options that can be set by the user through
// the command-line interface.
func (o *Options) AddDefaultOptions() {
	o.Add(NewBoolOption("center"))
	o.Add(NewStringOption("columns"))
	o.Add(NewStringOption("sort"))
	o.Add(NewStringOption("topbar"))
}

// Defaults is the default, internal configuration file.
const Defaults string = `
# Global options
set nocenter
set columns=artist,track,title,album,year,time
set sort=file,track,disc,album,year,albumartistsort
set topbar="|$shortname $version||;${tag|artist} - ${tag|title}||${tag|album}, ${tag|year};$volume $mode $elapsed ${state} $time;|[${list|index}/${list|total}] ${list|title}||;;"

# Song tag styles
style album teal
style artist yellow
style date green
style time darkmagenta
style title white bold
style track green
style year green

# Tracklist styles
style allTagsMissing red
style currentSong black yellow
style cursor black white
style header green bold
style mostTagsMissing red
style selection white blue

# Topbar styles
style elapsed green
style listIndex darkblue
style listTitle blue bold
style listTotal darkblue
style mute red
style shortName bold
style state default
style switches teal
style tagMissing red
style topbar darkgray
style version gray
style volume green

# Other styles
style commandText default
style errorText white red bold
style readout default
style searchText white bold
style sequenceText teal
style statusbar default
style visualText teal

# Keyboard bindings: cursor movement
bind <Up> cursor up
bind k cursor up
bind <Down> cursor down
bind j cursor down
bind <PgUp> cursor pgup
bind <PgDn> cursor pgdn
bind <Home> cursor home
bind gg cursor home
bind <End> cursor end
bind G cursor end
bind gc cursor current
bind R cursor random
bind b cursor prevOf album
bind e cursor nextOf album
bind H cursor high
bind M cursor middle
bind L cursor low

# Keyboard bindings: input mode
bind : inputmode input
bind / inputmode search
bind <F3> inputmode search
bind v select visual
bind V select visual

# Keyboard bindings: player and mixer
bind <Enter> play selection
bind <Space> pause
bind s stop
bind h previous
bind l next
bind + volume +2
bind - volume -2
bind <left> seek -5
bind <right> seek +5
bind <Alt-M> volume mute
bind S single

# Keyboard bindings: other
bind <C-c> quit
bind <C-l> redraw
bind <C-s> sort
bind i print file
bind t list next
bind T list previous
bind <C-d> list duplicate
bind <C-g> list remove
bind <C-j> isolate artist
bind <C-t> isolate albumartist album
bind & select nearby albumartist album
bind m select toggle
bind a add
bind <Delete> cut
bind x cut
bind y yank
bind p paste after
bind P paste before
`
<|endoftext|>


<commit_before>package hammer

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sort"
	"sync"
	"time"

	"github.com/arodland/go-descriptivestats/descriptivestats"
	"github.com/bmizerany/perks/quantile"
)

type Hammer struct {
	RunFor           float64
	Threads          int
	Backlog          int
	QPS              float64
	LogErrors        bool
	GenerateFunction RequestGenerator
	exit             chan int
	exited           bool
	requests         chan Request
	throttled        chan Request
	results          chan Result
	stats            chan StatsSummary
	requestWorkers   sync.WaitGroup
	finishedResults  sync.WaitGroup
}

func (hammer *Hammer) warn(msg string) {
	log.Println(msg)
}

func (hammer *Hammer) warnf(fmt string, args ...interface{}) {
	log.Printf(fmt, args...)
}

type BodyBehavior int

const (
	IgnoreBody BodyBehavior = iota
	DiscardBody
	CopyBody
)

type Request struct {
	HTTPRequest  *http.Request
	Name         string
	BodyBehavior BodyBehavior
	Callback     RequestCallback
}

type RequestCallback func(Request, *http.Response, Result)

type RequestGenerator func(*Hammer)

type RandomURLGeneratorOptions struct {
	URLs         []string
	Headers      map[string][]string
	Name         string
	BodyBehavior BodyBehavior
	Callback     RequestCallback
}

func RandomURLGenerator(opts RandomURLGeneratorOptions) RequestGenerator {
	readiedRequests := make([]Request, len(opts.URLs))
	for i, url := range opts.URLs {
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			panic(err)
		}
		req.Header = opts.Headers
		readiedRequests[i] = Request{
			BodyBehavior: opts.BodyBehavior,
			HTTPRequest:  req,
			Name:         opts.Name,
			Callback:     opts.Callback,
		}
	}
	num := len(readiedRequests)

	return func(hammer *Hammer) {
		defer func() { close(hammer.requests) }()

		for {
			select {
			case <-hammer.exit:
				return
			default:
				var idx int
				if num == 1 {
					idx = 0
				} else {
					idx = rand.Intn(len(readiedRequests))
				}
				hammer.requests <- readiedRequests[idx]
			}
		}
	}
}

func (hammer *Hammer) SendRequest(req Request) {
	if !hammer.exited {
		hammer.requests <- req
	}
}

func (hammer *Hammer) SendRequestImmediately(req Request) {
	if !hammer.exited {
		hammer.throttled <- req
	}
}

func (hammer *Hammer) throttle() {
	ticker := time.NewTicker(time.Duration(float64(time.Second) / hammer.QPS))
	defer ticker.Stop()
	defer close(hammer.throttled)

	for {
		select {
		case <-hammer.exit:
			return
		case <-ticker.C:
			req := <-hammer.requests
			hammer.throttled <- req
		}
	}
}

type Result struct {
	Name       string
	Status     int
	Start      time.Time
	GotHeaders time.Time
	GotBody    time.Time
}

func copyResponseBody(res *http.Response) (gotBody time.Time) {
	var buf bytes.Buffer
	io.Copy(&buf, res.Body)
	gotBody = time.Now()
	res.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
	return
}

func (hammer *Hammer) sendRequests() {
	defer hammer.requestWorkers.Done()

	client := &http.Client{}

	for req := range hammer.throttled {
		var result Result
		result.Name = req.Name
		result.Start = time.Now()
		res, err := client.Do(req.HTTPRequest)
		result.GotHeaders = time.Now()
		if err != nil {
			result.Status = 499
			hammer.warn(err.Error())
		} else {
			result.Status = res.StatusCode
			if result.Status >= 400 {
				if req.BodyBehavior == CopyBody {
					result.GotBody = copyResponseBody(res)
				}
				if hammer.LogErrors {
					// TODO: refactor this into a method
					logOut, err := ioutil.TempFile(".", "error.log.")
					if err == nil {
						res.Write(logOut)
						result.GotBody = time.Now()
					} else {
						hammer.warnf("%s writing error log\n", err.Error())
					}
				} else if req.BodyBehavior == DiscardBody {
					io.Copy(ioutil.Discard, res.Body)
					result.GotBody = time.Now()
				}
				hammer.warnf("Got status %s for %s\n", res.Status, req.HTTPRequest.URL.String())
			} else if req.BodyBehavior == CopyBody {
				result.GotBody = copyResponseBody(res)
			} else if req.BodyBehavior == DiscardBody {
				io.Copy(ioutil.Discard, res.Body)
				result.GotBody = time.Now()
			} else {
				res.Body.Close()
			}
		}
		if req.Callback != nil {
			go req.Callback(req, res, result)
		}
		hammer.results <- result
	}
}

type Stats struct {
	Name           string
	Quantiles      []float64
	Begin          time.Time
	End            time.Time
	Statuses       map[int]int
	HeaderStats    descriptivestats.Stats
	HeaderQuantile quantile.Stream
	BodyStats      descriptivestats.Stats
	BodyQuantile   quantile.Stream
}

type SingleStatSummary struct {
	descriptivestats.Stats
	Quantiles map[float64]float64
}

type StatsSummary struct {
	Name     string
	Begin    time.Time
	End      time.Time
	Statuses map[int]int
	Headers  SingleStatSummary
	Body     SingleStatSummary
}

func newStats(name string, quantiles ...float64) *Stats {
	return &Stats{
		Name:           name,
		Quantiles:      quantiles,
		Statuses:       make(map[int]int),
		HeaderStats:    descriptivestats.Stats{},
		HeaderQuantile: *(quantile.NewTargeted(quantiles...)),
		BodyStats:      descriptivestats.Stats{},
		BodyQuantile:   *(quantile.NewTargeted(quantiles...)),
	}
}

func (stats *Stats) Summarize() (summary StatsSummary) {
	summary.Name = stats.Name
	summary.Begin = stats.Begin
	summary.End = stats.End
	summary.Statuses = stats.Statuses
	summary.Headers.Stats = stats.HeaderStats
	summary.Headers.Quantiles = make(map[float64]float64, len(stats.Quantiles))
	for _, quantile := range stats.Quantiles {
		summary.Headers.Quantiles[quantile] = stats.HeaderQuantile.Query(quantile)
	}
	summary.Body.Stats = stats.BodyStats
	if stats.BodyStats.Count > 0 {
		summary.Body.Quantiles = make(map[float64]float64, len(stats.Quantiles))
		for _, quantile := range stats.Quantiles {
			summary.Body.Quantiles[quantile] = stats.BodyQuantile.Query(quantile)
		}
	}
	return
}

func (hammer *Hammer) collectResults() {
	defer hammer.finishedResults.Done()

	statsMap := map[string]*Stats{}

	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	defer func() {
		for _, stats := range statsMap {
			hammer.stats <- stats.Summarize()
		}
		close(hammer.stats)
	}()

	for {
		select {
		case res, ok := <-hammer.results:
			if !ok {
				return
			}

			stats, statsExisted := statsMap[res.Name]
			if !statsExisted {
				stats = newStats(res.Name, 0.05, 0.95)
				statsMap[res.Name] = stats
			}

			stats.Statuses[res.Status]++

			start := res.Start
			end := res.GotHeaders
			dur := end.Sub(start).Seconds()
			stats.HeaderStats.Add(dur)
			stats.HeaderQuantile.Insert(dur)
			if res.GotBody != (time.Time{}) {
				end = res.GotBody
				dur := end.Sub(start).Seconds()
				stats.BodyStats.Add(dur)
				stats.BodyQuantile.Insert(dur)
			}
			if !statsExisted {
				stats.Begin = start
				stats.End = end
			} else {
				if start.Before(stats.Begin) {
					stats.Begin = start
				}
				if start.After(stats.End) {
					stats.End = start
				}
			}
		case <-ticker.C:
			for _, stats := range statsMap {
				hammer.stats <- stats.Summarize()
			}
		}
	}
}

func (stats *StatsSummary) PrintReport(w io.Writer) {
	runTime := stats.End.Sub(stats.Begin).Seconds()
	count := stats.Headers.Count
	fmt.Fprintf(
		w,
		`Run time: %.3f
Total hits: %.0f
Hits/sec: %.3f

Status totals:
`,
		runTime,
		count,
		count/runTime,
	)
	statusCodes := []int{}
	for code, _ := range stats.Statuses {
		statusCodes = append(statusCodes, code)
	}
	sort.Ints(statusCodes)
	for _, code := range statusCodes {
		fmt.Fprintf(w, "%d\t%d\t%.3f\n", code, stats.Statuses[code], 100*float64(stats.Statuses[code])/count)
	}
	if count > 0 {
		fmt.Fprintf(
			w,
			"\nFirst byte mean +/- SD: %.2f +/- %.2f ms\n",
			1000*stats.Headers.Mean(),
			1000*stats.Headers.StdDev(),
		)
		fmt.Fprintf(
			w,
			"First byte 5-95 pct: (%.2f, %.2f) ms\n",
			1000*stats.Headers.Quantiles[0.05],
			1000*stats.Headers.Quantiles[0.95],
		)
		if stats.Body.Count > 0 {
			fmt.Fprintf(
				w,
				"\nFull response mean +/- SD: %.2f +/- %.2f ms\n",
				1000*stats.Body.Mean(),
				1000*stats.Body.StdDev(),
			)
			fmt.Fprintf(
				w,
				"First byte 5-95 pct: (%.2f, %.2f) ms\n",
				1000*stats.Body.Quantiles[0.05],
				1000*stats.Body.Quantiles[0.95],
			)
		}
	}
}

func (hammer *Hammer) ReportPrinter(format string) func(StatsSummary) {
	return func(stats StatsSummary) {
		w, err := os.Create(fmt.Sprintf(format, stats.Name))
		if err != nil {
			hammer.warn(err.Error())
			return
		}
		fmt.Fprintf(w, "HAMMER REPORT FOR %s\n\n", stats.Name)
		stats.PrintReport(w)
		w.Close()
	}
}

func (hammer *Hammer) StatsPrinter(filename string) func(StatsSummary) {
	return func(stats StatsSummary) {
		statsFile, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			hammer.warn(err.Error())
			return
		}
		runTime := stats.End.Sub(stats.Begin).Seconds()
		count := stats.Headers.Count
		fmt.Fprintf(
			statsFile,
			"%s\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f",
			stats.Name,
			hammer.Threads,
			hammer.QPS,
			runTime,
			count,
			count/runTime,
			1000*stats.Headers.Mean(),
			1000*stats.Headers.StdDev(),
			1000*stats.Headers.Quantiles[0.05],
			1000*stats.Headers.Quantiles[0.95],
		)
		if stats.Body.Count > 0 {
			fmt.Fprintf(
				statsFile,
				"%f\t%f\t%f\t%f\n",
				1000*stats.Body.Mean(),
				1000*stats.Body.StdDev(),
				1000*stats.Body.Quantiles[0.05],
				1000*stats.Body.Quantiles[0.95],
			)
		} else {
			fmt.Fprintf(statsFile, "\n")
		}
		statsFile.Close()
	}
}

func (hammer *Hammer) Run(statschan chan StatsSummary) {
	hammer.exit = make(chan int)
	hammer.requests = make(chan Request)
	hammer.throttled = make(chan Request, hammer.Backlog)
	hammer.results = make(chan Result, hammer.Threads*2)
	hammer.stats = statschan

	for i := 0; i < hammer.Threads; i++ {
		hammer.requestWorkers.Add(1)
		go hammer.sendRequests()
	}
	hammer.finishedResults.Add(1)
	go hammer.collectResults()
	go hammer.throttle()
	go hammer.GenerateFunction(hammer)
	go func() {
		hammer.requestWorkers.Wait()
		close(hammer.results)
	}()

	// Give it time to run...
	time.Sleep(time.Duration(hammer.RunFor * float64(time.Second)))
	// And then signal GenerateRequests to stop.
	hammer.exited = true
	close(hammer.exit)
	hammer.finishedResults.Wait()
}
<commit_msg>Make Exit public so that external generator functions can work<commit_after>package hammer

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"sort"
	"sync"
	"time"

	"github.com/arodland/go-descriptivestats/descriptivestats"
	"github.com/bmizerany/perks/quantile"
)

type Hammer struct {
	RunFor           float64
	Threads          int
	Backlog          int
	QPS              float64
	LogErrors        bool
	GenerateFunction RequestGenerator
	Exit             chan int
	exited           bool
	requests         chan Request
	throttled        chan Request
	results          chan Result
	stats            chan StatsSummary
	requestWorkers   sync.WaitGroup
	finishedResults  sync.WaitGroup
}

func (hammer *Hammer) warn(msg string) {
	log.Println(msg)
}

func (hammer *Hammer) warnf(fmt string, args ...interface{}) {
	log.Printf(fmt, args...)
}

type BodyBehavior int

const (
	IgnoreBody BodyBehavior = iota
	DiscardBody
	CopyBody
)

type Request struct {
	HTTPRequest  *http.Request
	Name         string
	BodyBehavior BodyBehavior
	Callback     RequestCallback
}

type RequestCallback func(Request, *http.Response, Result)

type RequestGenerator func(*Hammer)

type RandomURLGeneratorOptions struct {
	URLs         []string
	Headers      map[string][]string
	Name         string
	BodyBehavior BodyBehavior
	Callback     RequestCallback
}

func RandomURLGenerator(opts RandomURLGeneratorOptions) RequestGenerator {
	readiedRequests := make([]Request, len(opts.URLs))
	for i, url := range opts.URLs {
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			panic(err)
		}
		req.Header = opts.Headers
		readiedRequests[i] = Request{
			BodyBehavior: opts.BodyBehavior,
			HTTPRequest:  req,
			Name:         opts.Name,
			Callback:     opts.Callback,
		}
	}
	num := len(readiedRequests)

	return func(hammer *Hammer) {
		defer func() { close(hammer.requests) }()

		for {
			select {
			case <-hammer.Exit:
				return
			default:
				var idx int
				if num == 1 {
					idx = 0
				} else {
					idx = rand.Intn(len(readiedRequests))
				}
				hammer.requests <- readiedRequests[idx]
			}
		}
	}
}

func (hammer *Hammer) SendRequest(req Request) {
	if !hammer.exited {
		hammer.requests <- req
	}
}

func (hammer *Hammer) SendRequestImmediately(req Request) {
	if !hammer.exited {
		hammer.throttled <- req
	}
}

func (hammer *Hammer) throttle() {
	ticker := time.NewTicker(time.Duration(float64(time.Second) / hammer.QPS))
	defer ticker.Stop()
	defer close(hammer.throttled)

	for {
		select {
		case <-hammer.Exit:
			return
		case <-ticker.C:
			req := <-hammer.requests
			hammer.throttled <- req
		}
	}
}

type Result struct {
	Name       string
	Status     int
	Start      time.Time
	GotHeaders time.Time
	GotBody    time.Time
}

func copyResponseBody(res *http.Response) (gotBody time.Time) {
	var buf bytes.Buffer
	io.Copy(&buf, res.Body)
	gotBody = time.Now()
	res.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
	return
}

func (hammer *Hammer) sendRequests() {
	defer hammer.requestWorkers.Done()

	client := &http.Client{}

	for req := range hammer.throttled {
		var result Result
		result.Name = req.Name
		result.Start = time.Now()
		res, err := client.Do(req.HTTPRequest)
		result.GotHeaders = time.Now()
		if err != nil {
			result.Status = 499
			hammer.warn(err.Error())
		} else {
			result.Status = res.StatusCode
			if result.Status >= 400 {
				if req.BodyBehavior == CopyBody {
					result.GotBody = copyResponseBody(res)
				}
				if hammer.LogErrors {
					// TODO: refactor this into a method
					logOut, err := ioutil.TempFile(".", "error.log.")
					if err == nil {
						res.Write(logOut)
						result.GotBody = time.Now()
					} else {
						hammer.warnf("%s writing error log\n", err.Error())
					}
				} else if req.BodyBehavior == DiscardBody {
					io.Copy(ioutil.Discard, res.Body)
					result.GotBody = time.Now()
				}
				hammer.warnf("Got status %s for %s\n", res.Status, req.HTTPRequest.URL.String())
			} else if req.BodyBehavior == CopyBody {
				result.GotBody = copyResponseBody(res)
			} else if req.BodyBehavior == DiscardBody {
				io.Copy(ioutil.Discard, res.Body)
				result.GotBody = time.Now()
			} else {
				res.Body.Close()
			}
		}
		if req.Callback != nil {
			go req.Callback(req, res, result)
		}
		hammer.results <- result
	}
}

type Stats struct {
	Name           string
	Quantiles      []float64
	Begin          time.Time
	End            time.Time
	Statuses       map[int]int
	HeaderStats    descriptivestats.Stats
	HeaderQuantile quantile.Stream
	BodyStats      descriptivestats.Stats
	BodyQuantile   quantile.Stream
}

type SingleStatSummary struct {
	descriptivestats.Stats
	Quantiles map[float64]float64
}

type StatsSummary struct {
	Name     string
	Begin    time.Time
	End      time.Time
	Statuses map[int]int
	Headers  SingleStatSummary
	Body     SingleStatSummary
}

func newStats(name string, quantiles ...float64) *Stats {
	return &Stats{
		Name:           name,
		Quantiles:      quantiles,
		Statuses:       make(map[int]int),
		HeaderStats:    descriptivestats.Stats{},
		HeaderQuantile: *(quantile.NewTargeted(quantiles...)),
		BodyStats:      descriptivestats.Stats{},
		BodyQuantile:   *(quantile.NewTargeted(quantiles...)),
	}
}

func (stats *Stats) Summarize() (summary StatsSummary) {
	summary.Name = stats.Name
	summary.Begin = stats.Begin
	summary.End = stats.End
	summary.Statuses = stats.Statuses
	summary.Headers.Stats = stats.HeaderStats
	summary.Headers.Quantiles = make(map[float64]float64, len(stats.Quantiles))
	for _, quantile := range stats.Quantiles {
		summary.Headers.Quantiles[quantile] = stats.HeaderQuantile.Query(quantile)
	}
	summary.Body.Stats = stats.BodyStats
	if stats.BodyStats.Count > 0 {
		summary.Body.Quantiles = make(map[float64]float64, len(stats.Quantiles))
		for _, quantile := range stats.Quantiles {
			summary.Body.Quantiles[quantile] = stats.BodyQuantile.Query(quantile)
		}
	}
	return
}

func (hammer *Hammer) collectResults() {
	defer hammer.finishedResults.Done()

	statsMap := map[string]*Stats{}

	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	defer func() {
		for _, stats := range statsMap {
			hammer.stats <- stats.Summarize()
		}
		close(hammer.stats)
	}()

	for {
		select {
		case res, ok := <-hammer.results:
			if !ok {
				return
			}

			stats, statsExisted := statsMap[res.Name]
			if !statsExisted {
				stats = newStats(res.Name, 0.05, 0.95)
				statsMap[res.Name] = stats
			}

			stats.Statuses[res.Status]++

			start := res.Start
			end := res.GotHeaders
			dur := end.Sub(start).Seconds()
			stats.HeaderStats.Add(dur)
			stats.HeaderQuantile.Insert(dur)
			if res.GotBody != (time.Time{}) {
				end = res.GotBody
				dur := end.Sub(start).Seconds()
				stats.BodyStats.Add(dur)
				stats.BodyQuantile.Insert(dur)
			}
			if !statsExisted {
				stats.Begin = start
				stats.End = end
			} else {
				if start.Before(stats.Begin) {
					stats.Begin = start
				}
				if start.After(stats.End) {
					stats.End = start
				}
			}
		case <-ticker.C:
			for _, stats := range statsMap {
				hammer.stats <- stats.Summarize()
			}
		}
	}
}

func (stats *StatsSummary) PrintReport(w io.Writer) {
	runTime := stats.End.Sub(stats.Begin).Seconds()
	count := stats.Headers.Count
	fmt.Fprintf(
		w,
		`Run time: %.3f
Total hits: %.0f
Hits/sec: %.3f

Status totals:
`,
		runTime,
		count,
		count/runTime,
	)
	statusCodes := []int{}
	for code, _ := range stats.Statuses {
		statusCodes = append(statusCodes, code)
	}
	sort.Ints(statusCodes)
	for _, code := range statusCodes {
		fmt.Fprintf(w, "%d\t%d\t%.3f\n", code, stats.Statuses[code], 100*float64(stats.Statuses[code])/count)
	}
	if count > 0 {
		fmt.Fprintf(
			w,
			"\nFirst byte mean +/- SD: %.2f +/- %.2f ms\n",
			1000*stats.Headers.Mean(),
			1000*stats.Headers.StdDev(),
		)
		fmt.Fprintf(
			w,
			"First byte 5-95 pct: (%.2f, %.2f) ms\n",
			1000*stats.Headers.Quantiles[0.05],
			1000*stats.Headers.Quantiles[0.95],
		)
		if stats.Body.Count > 0 {
			fmt.Fprintf(
				w,
				"\nFull response mean +/- SD: %.2f +/- %.2f ms\n",
				1000*stats.Body.Mean(),
				1000*stats.Body.StdDev(),
			)
			fmt.Fprintf(
				w,
				"First byte 5-95 pct: (%.2f, %.2f) ms\n",
				1000*stats.Body.Quantiles[0.05],
				1000*stats.Body.Quantiles[0.95],
			)
		}
	}
}

func (hammer *Hammer) ReportPrinter(format string) func(StatsSummary) {
	return func(stats StatsSummary) {
		w, err := os.Create(fmt.Sprintf(format, stats.Name))
		if err != nil {
			hammer.warn(err.Error())
			return
		}
		fmt.Fprintf(w, "HAMMER REPORT FOR %s\n\n", stats.Name)
		stats.PrintReport(w)
		w.Close()
	}
}

func (hammer *Hammer) StatsPrinter(filename string) func(StatsSummary) {
	return func(stats StatsSummary) {
		statsFile, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			hammer.warn(err.Error())
			return
		}
		runTime := stats.End.Sub(stats.Begin).Seconds()
		count := stats.Headers.Count
		fmt.Fprintf(
			statsFile,
			"%s\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f",
			stats.Name,
			hammer.Threads,
			hammer.QPS,
			runTime,
			count,
			count/runTime,
			1000*stats.Headers.Mean(),
			1000*stats.Headers.StdDev(),
			1000*stats.Headers.Quantiles[0.05],
			1000*stats.Headers.Quantiles[0.95],
		)
		if stats.Body.Count > 0 {
			fmt.Fprintf(
				statsFile,
				"%f\t%f\t%f\t%f\n",
				1000*stats.Body.Mean(),
				1000*stats.Body.StdDev(),
				1000*stats.Body.Quantiles[0.05],
				1000*stats.Body.Quantiles[0.95],
			)
		} else {
			fmt.Fprintf(statsFile, "\n")
		}
		statsFile.Close()
	}
}

func (hammer *Hammer) Run(statschan chan StatsSummary) {
	hammer.Exit = make(chan int)
	hammer.requests = make(chan Request)
	hammer.throttled = make(chan Request, hammer.Backlog)
	hammer.results = make(chan Result, hammer.Threads*2)
	hammer.stats = statschan

	for i := 0; i < hammer.Threads; i++ {
		hammer.requestWorkers.Add(1)
		go hammer.sendRequests()
	}
	hammer.finishedResults.Add(1)
	go hammer.collectResults()
	go hammer.throttle()
	go hammer.GenerateFunction(hammer)
	go func() {
		hammer.requestWorkers.Wait()
		close(hammer.results)
	}()

	// Give it time to run...
	time.Sleep(time.Duration(hammer.RunFor * float64(time.Second)))
	// And then signal GenerateRequests to stop.
	hammer.exited = true
	close(hammer.Exit)
	hammer.finishedResults.Wait()
}
<|endoftext|>


<commit_before>package pixel

import "fmt"

// TrianglesData specifies a list of Triangles vertices with three common properties: Position,
// Color and Texture.
type TrianglesData []struct {
	Position Vec
	Color    NRGBA
	Texture  Vec
}

// Len returns the number of vertices in TrianglesData.
func (td *TrianglesData) Len() int {
	return len(*td)
}

// Draw is unimplemented for TrianglesData and panics.
func (td *TrianglesData) Draw() {
	panic(fmt.Errorf("%T.Draw: invalid operation", td))
}

func (td *TrianglesData) resize(len int) {
	if len > td.Len() {
		needAppend := len - td.Len()
		for i := 0; i < needAppend; i++ {
			*td = append(*td, struct {
				Position Vec
				Color    NRGBA
				Texture  Vec
			}{V(0, 0), NRGBA{1, 1, 1, 1}, V(-1, -1)})
		}
	}
	if len < td.Len() {
		*td = (*td)[:len]
	}
}

func (td *TrianglesData) updateData(offset int, t Triangles) {
	// fast path optimization
	if t, ok := t.(*TrianglesData); ok {
		copy((*td)[offset:], *t)
		return
	}

	// slow path manual copy
	if t, ok := t.(TrianglesPosition); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Position = t.Position(i)
		}
	}
	if t, ok := t.(TrianglesColor); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Color = t.Color(i)
		}
	}
	if t, ok := t.(TrianglesTexture); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Texture = t.Texture(i)
		}
	}
}

// Update copies vertex properties from the supplied Triangles into this TrianglesData.
//
// TrianglesPosition, TrianglesColor and TrianglesTexture are supported.
func (td *TrianglesData) Update(t Triangles) {
	td.resize(t.Len())
	td.updateData(0, t)
}

// Append adds supplied Triangles to the end of the TrianglesData.
func (td *TrianglesData) Append(t Triangles) {
	td.resize(td.Len() + t.Len())
	td.updateData(td.Len()-t.Len(), t)
}

// Copy returns an exact independent copy of this TrianglesData.
func (td *TrianglesData) Copy() Triangles {
	copyTd := make(TrianglesData, td.Len())
	copyTd.Update(td)
	return &copyTd
}

// Position returns the position property of i-th vertex.
func (td *TrianglesData) Position(i int) Vec {
	return (*td)[i].Position
}

// Color returns the color property of i-th vertex.
func (td *TrianglesData) Color(i int) NRGBA {
	return (*td)[i].Color
}

// Texture returns the texture property of i-th vertex.
func (td *TrianglesData) Texture(i int) Vec {
	return (*td)[i].Texture
}

// TrianglesDrawer is a helper type that wraps Triangles and turns them into a Drawer.
//
// It does so by creating a separate Triangles instance for each Target. The instances are
// correctly updated alongside the wrapped Triangles.
type TrianglesDrawer struct {
	Triangles

	tris  map[Target]Triangles
	dirty bool
}

func (td *TrianglesDrawer) flush() {
	if !td.dirty {
		return
	}
	td.dirty = false

	for _, t := range td.tris {
		t.Update(td.Triangles)
	}
}

// Draw draws the wrapped Triangles onto the provided Target.
func (td *TrianglesDrawer) Draw(target Target) {
	if td.tris == nil {
		td.tris = make(map[Target]Triangles)
	}

	td.flush()

	tri := td.tris[target]
	if tri == nil {
		tri = target.MakeTriangles(td.Triangles)
		td.tris[target] = tri
	}
	tri.Draw()
}

// Update updates the wrapped Triangles with the supplied Triangles.
//
// Call only this method to update the wrapped Triangles, otherwise the TrianglesDrawer will not
// work correctly.
func (td *TrianglesDrawer) Update(t Triangles) {
	td.dirty = true
	td.Triangles.Update(t)
}

// Append appends the supplied Triangles to the wrapped Triangles.
//
// Call only this method to append to the wrapped Triangles, otherwise the TrianglesDrawer will not
// work correctly.
func (td *TrianglesDrawer) Append(t Triangles) {
	td.dirty = true
	td.Triangles.Append(t)
}

// Sprite is a picture that can be drawn onto a Target. To change the position/rotation/scale of
// the Sprite, use Target's SetTransform method.
type Sprite struct {
	td  TrianglesDrawer
	pic *Picture
}

// NewSprite creates a Sprite with the supplied Picture. The dimensions of the returned Sprite match
// the dimensions of the Picture.
func NewSprite(pic *Picture) *Sprite {
	s := &Sprite{
		td: TrianglesDrawer{Triangles: &TrianglesData{}},
	}
	s.SetPicture(pic)
	return s
}

// SetPicture changes the Picture of the Sprite and resizes it accordingly.
func (s *Sprite) SetPicture(pic *Picture) {
	w, h := pic.Bounds().Size.XY()
	s.td.Update(&TrianglesData{
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)},
		{Position: V(w, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 0)},
		{Position: V(w, h), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)},
		{Position: V(w, h), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)},
		{Position: V(0, h), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 1)},
	})
	s.pic = pic
}

// Picture returns the current Picture of the Sprite.
func (s *Sprite) Picture() *Picture {
	return s.pic
}

// Draw draws the Sprite onto the provided Target.
func (s *Sprite) Draw(target Target) {
	target.SetPicture(s.pic)
	s.td.Draw(target)
}
<commit_msg>optimize Sprite<commit_after>package pixel

import "fmt"

// TrianglesData specifies a list of Triangles vertices with three common properties: Position,
// Color and Texture.
type TrianglesData []struct {
	Position Vec
	Color    NRGBA
	Texture  Vec
}

// Len returns the number of vertices in TrianglesData.
func (td *TrianglesData) Len() int {
	return len(*td)
}

// Draw is unimplemented for TrianglesData and panics.
func (td *TrianglesData) Draw() {
	panic(fmt.Errorf("%T.Draw: invalid operation", td))
}

func (td *TrianglesData) resize(len int) {
	if len > td.Len() {
		needAppend := len - td.Len()
		for i := 0; i < needAppend; i++ {
			*td = append(*td, struct {
				Position Vec
				Color    NRGBA
				Texture  Vec
			}{V(0, 0), NRGBA{1, 1, 1, 1}, V(-1, -1)})
		}
	}
	if len < td.Len() {
		*td = (*td)[:len]
	}
}

func (td *TrianglesData) updateData(offset int, t Triangles) {
	// fast path optimization
	if t, ok := t.(*TrianglesData); ok {
		copy((*td)[offset:], *t)
		return
	}

	// slow path manual copy
	if t, ok := t.(TrianglesPosition); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Position = t.Position(i)
		}
	}
	if t, ok := t.(TrianglesColor); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Color = t.Color(i)
		}
	}
	if t, ok := t.(TrianglesTexture); ok {
		for i := offset; i < len(*td); i++ {
			(*td)[i].Texture = t.Texture(i)
		}
	}
}

// Update copies vertex properties from the supplied Triangles into this TrianglesData.
//
// TrianglesPosition, TrianglesColor and TrianglesTexture are supported.
func (td *TrianglesData) Update(t Triangles) {
	td.resize(t.Len())
	td.updateData(0, t)
}

// Append adds supplied Triangles to the end of the TrianglesData.
func (td *TrianglesData) Append(t Triangles) {
	td.resize(td.Len() + t.Len())
	td.updateData(td.Len()-t.Len(), t)
}

// Copy returns an exact independent copy of this TrianglesData.
func (td *TrianglesData) Copy() Triangles {
	copyTd := make(TrianglesData, td.Len())
	copyTd.Update(td)
	return &copyTd
}

// Position returns the position property of i-th vertex.
func (td *TrianglesData) Position(i int) Vec {
	return (*td)[i].Position
}

// Color returns the color property of i-th vertex.
func (td *TrianglesData) Color(i int) NRGBA {
	return (*td)[i].Color
}

// Texture returns the texture property of i-th vertex.
func (td *TrianglesData) Texture(i int) Vec {
	return (*td)[i].Texture
}

// TrianglesDrawer is a helper type that wraps Triangles and turns them into a Drawer.
//
// It does so by creating a separate Triangles instance for each Target. The instances are
// correctly updated alongside the wrapped Triangles.
type TrianglesDrawer struct {
	Triangles

	tris  map[Target]Triangles
	dirty bool
}

func (td *TrianglesDrawer) flush() {
	if !td.dirty {
		return
	}
	td.dirty = false

	for _, t := range td.tris {
		t.Update(td.Triangles)
	}
}

// Draw draws the wrapped Triangles onto the provided Target.
func (td *TrianglesDrawer) Draw(target Target) {
	if td.tris == nil {
		td.tris = make(map[Target]Triangles)
	}

	td.flush()

	tri := td.tris[target]
	if tri == nil {
		tri = target.MakeTriangles(td.Triangles)
		td.tris[target] = tri
	}
	tri.Draw()
}

// Update updates the wrapped Triangles with the supplied Triangles.
//
// Call only this method to update the wrapped Triangles, otherwise the TrianglesDrawer will not
// work correctly.
func (td *TrianglesDrawer) Update(t Triangles) {
	td.dirty = true
	td.Triangles.Update(t)
}

// Append appends the supplied Triangles to the wrapped Triangles.
//
// Call only this method to append to the wrapped Triangles, otherwise the TrianglesDrawer will not
// work correctly.
func (td *TrianglesDrawer) Append(t Triangles) {
	td.dirty = true
	td.Triangles.Append(t)
}

// Sprite is a picture that can be drawn onto a Target. To change the position/rotation/scale of
// the Sprite, use Target's SetTransform method.
type Sprite struct {
	td   TrianglesDrawer
	data *TrianglesData
	pic  *Picture
}

// NewSprite creates a Sprite with the supplied Picture. The dimensions of the returned Sprite match
// the dimensions of the Picture.
func NewSprite(pic *Picture) *Sprite {
	data := &TrianglesData{
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 0)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)},
		{Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 1)},
	}
	s := &Sprite{
		td:   TrianglesDrawer{Triangles: data},
		data: data,
	}
	s.SetPicture(pic)
	return s
}

// SetPicture changes the Picture of the Sprite and resizes it accordingly.
func (s *Sprite) SetPicture(pic *Picture) {
	w, h := pic.Bounds().Size.XY()
	(*s.data)[0].Position = V(0, 0)
	(*s.data)[2].Position = V(w, h)
	(*s.data)[1].Position = V(w, 0)
	(*s.data)[3].Position = V(0, 0)
	(*s.data)[4].Position = V(w, h)
	(*s.data)[5].Position = V(0, h)
	s.pic = pic
}

// Picture returns the current Picture of the Sprite.
func (s *Sprite) Picture() *Picture {
	return s.pic
}

// Draw draws the Sprite onto the provided Target.
func (s *Sprite) Draw(target Target) {
	target.SetPicture(s.pic)
	s.td.Draw(target)
}
<|endoftext|>


<commit_before>package restserver

import (
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

func TestJoin(t *testing.T) {
	var tests = []struct {
		base   string
		names  []string
		result string
	}{
		{"/", []string{"foo", "bar"}, "/foo/bar"},
		{"/srv/server", []string{"foo", "bar"}, "/srv/server/foo/bar"},
		{"/srv/server", []string{"foo", "..", "bar"}, "/srv/server/foo/bar"},
		{"/srv/server", []string{"..", "bar"}, "/srv/server/bar"},
		{"/srv/server", []string{".."}, "/srv/server"},
		{"/srv/server", []string{"..", ".."}, "/srv/server"},
		{"/srv/server", []string{"repo", "data"}, "/srv/server/repo/data"},
		{"/srv/server", []string{"repo", "data", "..", ".."}, "/srv/server/repo/data"},
		{"/srv/server", []string{"repo", "data", "..", "data", "..", "..", ".."}, "/srv/server/repo/data/data"},
	}

	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			got, err := join(filepath.FromSlash(test.base), test.names...)
			if err != nil {
				t.Fatal(err)
			}

			want := filepath.FromSlash(test.result)
			if got != want {
				t.Fatalf("wrong result returned, want %v, got %v", want, got)
			}
		})
	}
}

// declare a few helper functions

// wantFunc tests the HTTP response in res and calls t.Error() if something is incorrect.
type wantFunc func(t testing.TB, res *httptest.ResponseRecorder)

// newRequest returns a new HTTP request with the given params. On error, t.Fatal is called.
func newRequest(t testing.TB, method, path string, body io.Reader) *http.Request {
	req, err := http.NewRequest(method, path, body)
	if err != nil {
		t.Fatal(err)
	}
	return req
}

// wantCode returns a function which checks that the response has the correct HTTP status code.
func wantCode(code int) wantFunc {
	return func(t testing.TB, res *httptest.ResponseRecorder) {
		if res.Code != code {
			t.Errorf("wrong response code, want %v, got %v", code, res.Code)
		}
	}
}

// wantBody returns a function which checks that the response has the data in the body.
func wantBody(body string) wantFunc {
	return func(t testing.TB, res *httptest.ResponseRecorder) {
		if res.Body == nil {
			t.Errorf("body is nil, want %q", body)
			return
		}

		if !bytes.Equal(res.Body.Bytes(), []byte(body)) {
			t.Errorf("wrong response body, want:\n  %q\ngot:\n  %q", body, res.Body.Bytes())
		}
	}
}

// checkRequest uses f to process the request and runs the checker functions on the result.
func checkRequest(t testing.TB, f http.HandlerFunc, req *http.Request, want []wantFunc) {
	rr := httptest.NewRecorder()
	f(rr, req)

	for _, fn := range want {
		fn(t, rr)
	}
}

// TestRequest is a sequence of HTTP requests with (optional) tests for the response.
type TestRequest struct {
	req  *http.Request
	want []wantFunc
}

// createOverwriteDeleteSeq returns a sequence which will create a new file at
// path, and then try to overwrite and delete it.
func createOverwriteDeleteSeq(t testing.TB, path string, data string) []TestRequest {
	// add a file, try to overwrite and delete it
	req := []TestRequest{
		{
			req:  newRequest(t, "GET", path, nil),
			want: []wantFunc{wantCode(http.StatusNotFound)},
		},
	}

	if !strings.HasSuffix(path, "/config") {
		req = append(req, TestRequest{
			// broken upload must fail
			req:  newRequest(t, "POST", path, strings.NewReader(data+"broken")),
			want: []wantFunc{wantCode(http.StatusBadRequest)},
		})
	}

	req = append(req,
		TestRequest{
			req:  newRequest(t, "POST", path, strings.NewReader(data)),
			want: []wantFunc{wantCode(http.StatusOK)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
		TestRequest{
			req:  newRequest(t, "POST", path, strings.NewReader(data+"other stuff")),
			want: []wantFunc{wantCode(http.StatusForbidden)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
		TestRequest{
			req:  newRequest(t, "DELETE", path, nil),
			want: []wantFunc{wantCode(http.StatusForbidden)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
	)
	return req
}

// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticHandler(t *testing.T) {
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		t.Fatal(err)
	}
	data := "random data file " + hex.EncodeToString(buf)
	dataHash := sha256.Sum256([]byte(data))
	fileID := hex.EncodeToString(dataHash[:])

	var tests = []struct {
		seq []TestRequest
	}{
		{createOverwriteDeleteSeq(t, "/config", data)},
		{createOverwriteDeleteSeq(t, "/data/"+fileID, data)},
		{
			// ensure we can add and remove lock files
			[]TestRequest{
				{
					req:  newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusNotFound)},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data+"broken")),
					want: []wantFunc{wantCode(http.StatusBadRequest)},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data)),
					want: []wantFunc{wantCode(http.StatusOK)},
				},
				{
					req: newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{
						wantCode(http.StatusOK),
						wantBody(data),
					},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data+"other data")),
					want: []wantFunc{wantCode(http.StatusForbidden)},
				},
				{
					req:  newRequest(t, "DELETE", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusOK)},
				},
				{
					req:  newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusNotFound)},
				},
			},
		},

		// Test subrepos
		{createOverwriteDeleteSeq(t, "/parent1/sub1/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent1/sub1/data/"+fileID, data)},
		{createOverwriteDeleteSeq(t, "/parent1/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent1/data/"+fileID, data)},
		{createOverwriteDeleteSeq(t, "/parent2/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent2/data/"+fileID, data)},
	}

	// setup rclone with a local backend in a temporary directory
	tempdir, err := ioutil.TempDir("", "rclone-restic-test-")
	if err != nil {
		t.Fatal(err)
	}

	// make sure the tempdir is properly removed
	defer func() {
		err := os.RemoveAll(tempdir)
		if err != nil {
			t.Fatal(err)
		}
	}()

	// set append-only mode and configure path
	mux, err := NewHandler(&Server{
		AppendOnly:   true,
		Path:         tempdir,
		NoAuth:       true,
		Debug:        true,
		PanicOnError: true,
	})
	if err != nil {
		t.Fatalf("error from NewHandler: %v", err)
	}

	// create the repos
	for _, path := range []string{"/", "/parent1/sub1/", "/parent1/", "/parent2/"} {
		checkRequest(t, mux.ServeHTTP,
			newRequest(t, "POST", path+"?create=true", nil),
			[]wantFunc{wantCode(http.StatusOK)})
	}

	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			for i, seq := range test.seq {
				t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
				checkRequest(t, mux.ServeHTTP, seq.req, seq.want)
			}
		})
	}
}

func TestSplitURLPath(t *testing.T) {
	var tests = []struct {
		// Params
		urlPath  string
		maxDepth int
		// Expected result
		folderPath []string
		remainder  string
	}{
		{"/", 0, nil, "/"},
		{"/", 2, nil, "/"},
		{"/foo/bar/locks/0123", 0, nil, "/foo/bar/locks/0123"},
		{"/foo/bar/locks/0123", 1, []string{"foo"}, "/bar/locks/0123"},
		{"/foo/bar/locks/0123", 2, []string{"foo", "bar"}, "/locks/0123"},
		{"/foo/bar/locks/0123", 3, []string{"foo", "bar"}, "/locks/0123"},
		{"/foo/bar/zzz/locks/0123", 2, []string{"foo", "bar"}, "/zzz/locks/0123"},
		{"/foo/bar/zzz/locks/0123", 3, []string{"foo", "bar", "zzz"}, "/locks/0123"},
		{"/foo/bar/locks/", 2, []string{"foo", "bar"}, "/locks/"},
		{"/foo/locks/", 2, []string{"foo"}, "/locks/"},
		{"/foo/data/", 2, []string{"foo"}, "/data/"},
		{"/foo/index/", 2, []string{"foo"}, "/index/"},
		{"/foo/keys/", 2, []string{"foo"}, "/keys/"},
		{"/foo/snapshots/", 2, []string{"foo"}, "/snapshots/"},
		{"/foo/config", 2, []string{"foo"}, "/config"},
		{"/foo/", 2, []string{"foo"}, "/"},
		{"/foo/bar/", 2, []string{"foo", "bar"}, "/"},
		{"/foo/bar", 2, []string{"foo"}, "/bar"},
		{"/locks/", 2, nil, "/locks/"},
		// This function only splits, it does not check the path components!
		{"/././locks/", 2, []string{".", "."}, "/locks/"},
		{"/../../locks/", 2, []string{"..", ".."}, "/locks/"},
		{"///locks/", 2, []string{"", ""}, "/locks/"},
		{"////locks/", 2, []string{"", ""}, "//locks/"},
		// Robustness against broken input
		{"/", -42, nil, "/"},
		{"foo", 2, nil, "foo"},
		{"foo/bar", 2, nil, "foo/bar"},
		{"", 2, nil, ""},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
			folderPath, remainder := splitURLPath(test.urlPath, test.maxDepth)

			var fpEqual bool
			if len(test.folderPath) == 0 && len(folderPath) == 0 {
				fpEqual = true // this check allows for nil vs empty slice
			} else {
				fpEqual = reflect.DeepEqual(test.folderPath, folderPath)
			}
			if !fpEqual {
				t.Errorf("wrong folderPath: want %v, got %v", test.folderPath, folderPath)
			}

			if test.remainder != remainder {
				t.Errorf("wrong remainder: want %v, got %v", test.remainder, remainder)
			}
		})
	}
}
<commit_msg>Mark helper functions<commit_after>package restserver

import (
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

func TestJoin(t *testing.T) {
	var tests = []struct {
		base   string
		names  []string
		result string
	}{
		{"/", []string{"foo", "bar"}, "/foo/bar"},
		{"/srv/server", []string{"foo", "bar"}, "/srv/server/foo/bar"},
		{"/srv/server", []string{"foo", "..", "bar"}, "/srv/server/foo/bar"},
		{"/srv/server", []string{"..", "bar"}, "/srv/server/bar"},
		{"/srv/server", []string{".."}, "/srv/server"},
		{"/srv/server", []string{"..", ".."}, "/srv/server"},
		{"/srv/server", []string{"repo", "data"}, "/srv/server/repo/data"},
		{"/srv/server", []string{"repo", "data", "..", ".."}, "/srv/server/repo/data"},
		{"/srv/server", []string{"repo", "data", "..", "data", "..", "..", ".."}, "/srv/server/repo/data/data"},
	}

	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			got, err := join(filepath.FromSlash(test.base), test.names...)
			if err != nil {
				t.Fatal(err)
			}

			want := filepath.FromSlash(test.result)
			if got != want {
				t.Fatalf("wrong result returned, want %v, got %v", want, got)
			}
		})
	}
}

// declare a few helper functions

// wantFunc tests the HTTP response in res and calls t.Error() if something is incorrect.
type wantFunc func(t testing.TB, res *httptest.ResponseRecorder)

// newRequest returns a new HTTP request with the given params. On error, t.Fatal is called.
func newRequest(t testing.TB, method, path string, body io.Reader) *http.Request {
	req, err := http.NewRequest(method, path, body)
	if err != nil {
		t.Fatal(err)
	}
	return req
}

// wantCode returns a function which checks that the response has the correct HTTP status code.
func wantCode(code int) wantFunc {
	return func(t testing.TB, res *httptest.ResponseRecorder) {
		t.Helper()
		if res.Code != code {
			t.Errorf("wrong response code, want %v, got %v", code, res.Code)
		}
	}
}

// wantBody returns a function which checks that the response has the data in the body.
func wantBody(body string) wantFunc {
	return func(t testing.TB, res *httptest.ResponseRecorder) {
		t.Helper()
		if res.Body == nil {
			t.Errorf("body is nil, want %q", body)
			return
		}

		if !bytes.Equal(res.Body.Bytes(), []byte(body)) {
			t.Errorf("wrong response body, want:\n  %q\ngot:\n  %q", body, res.Body.Bytes())
		}
	}
}

// checkRequest uses f to process the request and runs the checker functions on the result.
func checkRequest(t testing.TB, f http.HandlerFunc, req *http.Request, want []wantFunc) {
	t.Helper()
	rr := httptest.NewRecorder()
	f(rr, req)

	for _, fn := range want {
		fn(t, rr)
	}
}

// TestRequest is a sequence of HTTP requests with (optional) tests for the response.
type TestRequest struct {
	req  *http.Request
	want []wantFunc
}

// createOverwriteDeleteSeq returns a sequence which will create a new file at
// path, and then try to overwrite and delete it.
func createOverwriteDeleteSeq(t testing.TB, path string, data string) []TestRequest {
	// add a file, try to overwrite and delete it
	req := []TestRequest{
		{
			req:  newRequest(t, "GET", path, nil),
			want: []wantFunc{wantCode(http.StatusNotFound)},
		},
	}

	if !strings.HasSuffix(path, "/config") {
		req = append(req, TestRequest{
			// broken upload must fail
			req:  newRequest(t, "POST", path, strings.NewReader(data+"broken")),
			want: []wantFunc{wantCode(http.StatusBadRequest)},
		})
	}

	req = append(req,
		TestRequest{
			req:  newRequest(t, "POST", path, strings.NewReader(data)),
			want: []wantFunc{wantCode(http.StatusOK)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
		TestRequest{
			req:  newRequest(t, "POST", path, strings.NewReader(data+"other stuff")),
			want: []wantFunc{wantCode(http.StatusForbidden)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
		TestRequest{
			req:  newRequest(t, "DELETE", path, nil),
			want: []wantFunc{wantCode(http.StatusForbidden)},
		},
		TestRequest{
			req: newRequest(t, "GET", path, nil),
			want: []wantFunc{
				wantCode(http.StatusOK),
				wantBody(data),
			},
		},
	)
	return req
}

// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticHandler(t *testing.T) {
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		t.Fatal(err)
	}
	data := "random data file " + hex.EncodeToString(buf)
	dataHash := sha256.Sum256([]byte(data))
	fileID := hex.EncodeToString(dataHash[:])

	var tests = []struct {
		seq []TestRequest
	}{
		{createOverwriteDeleteSeq(t, "/config", data)},
		{createOverwriteDeleteSeq(t, "/data/"+fileID, data)},
		{
			// ensure we can add and remove lock files
			[]TestRequest{
				{
					req:  newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusNotFound)},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data+"broken")),
					want: []wantFunc{wantCode(http.StatusBadRequest)},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data)),
					want: []wantFunc{wantCode(http.StatusOK)},
				},
				{
					req: newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{
						wantCode(http.StatusOK),
						wantBody(data),
					},
				},
				{
					req:  newRequest(t, "POST", "/locks/"+fileID, strings.NewReader(data+"other data")),
					want: []wantFunc{wantCode(http.StatusForbidden)},
				},
				{
					req:  newRequest(t, "DELETE", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusOK)},
				},
				{
					req:  newRequest(t, "GET", "/locks/"+fileID, nil),
					want: []wantFunc{wantCode(http.StatusNotFound)},
				},
			},
		},

		// Test subrepos
		{createOverwriteDeleteSeq(t, "/parent1/sub1/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent1/sub1/data/"+fileID, data)},
		{createOverwriteDeleteSeq(t, "/parent1/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent1/data/"+fileID, data)},
		{createOverwriteDeleteSeq(t, "/parent2/config", "foobar")},
		{createOverwriteDeleteSeq(t, "/parent2/data/"+fileID, data)},
	}

	// setup rclone with a local backend in a temporary directory
	tempdir, err := ioutil.TempDir("", "rclone-restic-test-")
	if err != nil {
		t.Fatal(err)
	}

	// make sure the tempdir is properly removed
	defer func() {
		err := os.RemoveAll(tempdir)
		if err != nil {
			t.Fatal(err)
		}
	}()

	// set append-only mode and configure path
	mux, err := NewHandler(&Server{
		AppendOnly:   true,
		Path:         tempdir,
		NoAuth:       true,
		Debug:        true,
		PanicOnError: true,
	})
	if err != nil {
		t.Fatalf("error from NewHandler: %v", err)
	}

	// create the repos
	for _, path := range []string{"/", "/parent1/sub1/", "/parent1/", "/parent2/"} {
		checkRequest(t, mux.ServeHTTP,
			newRequest(t, "POST", path+"?create=true", nil),
			[]wantFunc{wantCode(http.StatusOK)})
	}

	for _, test := range tests {
		t.Run("", func(t *testing.T) {
			for i, seq := range test.seq {
				t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
				checkRequest(t, mux.ServeHTTP, seq.req, seq.want)
			}
		})
	}
}

func TestSplitURLPath(t *testing.T) {
	var tests = []struct {
		// Params
		urlPath  string
		maxDepth int
		// Expected result
		folderPath []string
		remainder  string
	}{
		{"/", 0, nil, "/"},
		{"/", 2, nil, "/"},
		{"/foo/bar/locks/0123", 0, nil, "/foo/bar/locks/0123"},
		{"/foo/bar/locks/0123", 1, []string{"foo"}, "/bar/locks/0123"},
		{"/foo/bar/locks/0123", 2, []string{"foo", "bar"}, "/locks/0123"},
		{"/foo/bar/locks/0123", 3, []string{"foo", "bar"}, "/locks/0123"},
		{"/foo/bar/zzz/locks/0123", 2, []string{"foo", "bar"}, "/zzz/locks/0123"},
		{"/foo/bar/zzz/locks/0123", 3, []string{"foo", "bar", "zzz"}, "/locks/0123"},
		{"/foo/bar/locks/", 2, []string{"foo", "bar"}, "/locks/"},
		{"/foo/locks/", 2, []string{"foo"}, "/locks/"},
		{"/foo/data/", 2, []string{"foo"}, "/data/"},
		{"/foo/index/", 2, []string{"foo"}, "/index/"},
		{"/foo/keys/", 2, []string{"foo"}, "/keys/"},
		{"/foo/snapshots/", 2, []string{"foo"}, "/snapshots/"},
		{"/foo/config", 2, []string{"foo"}, "/config"},
		{"/foo/", 2, []string{"foo"}, "/"},
		{"/foo/bar/", 2, []string{"foo", "bar"}, "/"},
		{"/foo/bar", 2, []string{"foo"}, "/bar"},
		{"/locks/", 2, nil, "/locks/"},
		// This function only splits, it does not check the path components!
		{"/././locks/", 2, []string{".", "."}, "/locks/"},
		{"/../../locks/", 2, []string{"..", ".."}, "/locks/"},
		{"///locks/", 2, []string{"", ""}, "/locks/"},
		{"////locks/", 2, []string{"", ""}, "//locks/"},
		// Robustness against broken input
		{"/", -42, nil, "/"},
		{"foo", 2, nil, "foo"},
		{"foo/bar", 2, nil, "foo/bar"},
		{"", 2, nil, ""},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
			folderPath, remainder := splitURLPath(test.urlPath, test.maxDepth)

			var fpEqual bool
			if len(test.folderPath) == 0 && len(folderPath) == 0 {
				fpEqual = true // this check allows for nil vs empty slice
			} else {
				fpEqual = reflect.DeepEqual(test.folderPath, folderPath)
			}
			if !fpEqual {
				t.Errorf("wrong folderPath: want %v, got %v", test.folderPath, folderPath)
			}

			if test.remainder != remainder {
				t.Errorf("wrong remainder: want %v, got %v", test.remainder, remainder)
			}
		})
	}
}
<|endoftext|>


<commit_before>package grx

import (
	"testing"
	"time"
	"net/http"

	"github.com/stretchr/testify/assert"

	"github.com/jochasinga/grx/event"
	"github.com/jochasinga/grx/observable"
	"github.com/jochasinga/grx/observer"
)

// TestCreateObservableWithCOnstructor tests if the constructor method returns an Observable
func TestCreateObservableWithConstructor(t *testing.T) {
	testStream := observable.New()
	assert.IsType(t, &observable.Observable{}, testStream)
}

// TestCreateObservableWithEmpty tests if Empty creates an empty Observable that terminates right away.
func TestCreateObservableWithEmpty(t *testing.T) {
	msg := "Sumpin's"
	testStream := observable.Empty()
	testObserver := &observer.Observer{
		OnCompleted: func(e *event.Event) {
			msg += " brewin'"
		},
	}
	testStream.Subscribe(testObserver)
	assert.Equal(t, "Sumpin's brewin'", msg)
}

// TestCreateObservableWithJust tests if Just method returns an <*Observable>
func TestCreateObservableWithJust(t *testing.T) {

	// Provided a URL string
	url := "http://api.com/api/v1.0/user"

	// and an Observable created with Just method
	testStream := observable.Just(url)

	// Observable must have the type <Observable>
	assert.IsType(t, &observable.Observable{}, testStream)
}

// TestCreateObservableWithFrom tests if From methods returns an <*Observable>
func TestCreateObservableWithFrom(t *testing.T) {
	// Provided a slice of URL strings
	urls := []interface{}{
		"http://api.com/api/v1.0/user",
		"https://dropbox.com/api/v2.1/get/storage",
		"http://googleapi.com/map",
	}

	// and an Observable created with From method
	testStream := observable.From(urls)

	// Observable must have the type <*Observable>
	assert.IsType(t, &observable.Observable{}, testStream)
}

func TestCreateObservableWithStart(t *testing.T) {

	// Provided a "directive" function which return an event that emits
	// a slice of integer numbers from 0 to 20.
	directive := func() *event.Event {
		ev := &event.Event{ Value: []int{} }
		for i:=0; i<=20; i++ {
			ev.Value = append(ev.Value.([]int), i)
		}
		return ev
	}

	testStream := observable.Start(directive)

	// Make sure it's the right type
	assert.IsType(t, &observable.Observable{}, testStream)
	nums := []int{}
	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			for _, num := range ev.Value.([]int) {
				nums = append(nums, num * 2)
			}
		},
		OnCompleted: func(ev *event.Event) {
			nums = append(nums, 666)
		},
	}

	testStream.Subscribe(testObserver)
	expected := []int{}
	for i:=0; i<=20; i++ {
		expected = append(expected, i * 2)
	}
	expected = append(expected, 666)
	assert.Exactly(t, expected, nums)
}

func TestSubscribeToJustObservable1(t *testing.T) {
	urlWithUserID := ""

	// Provided an Observable created with Just method
	url := "http://api.com/api/v1.0/user"
	expected := url + "?id=999"
	testStream := observable.Just(url)

	testObserver := &observer.Observer{
		OnNext: func(e *event.Event) { urlWithUserID = e.Value.(string) },
		OnCompleted: func(e *event.Event) { urlWithUserID = urlWithUserID + "?id=999" },
	}
	testStream.Subscribe(testObserver)
	assert.Exactly(t, expected, urlWithUserID)
}

func TestSubscribeToJustObservable2(t *testing.T) {
	nums := []int{}

	// Create an Observable with an integer.
	testStream := observable.Just(1)

	// Create an Observer object.
	testObserver := &observer.Observer{
		// While there is more events down the stream (in this case, just one), add 1 to the value emitted.
		OnNext: func(e *event.Event) {
			num := e.Value.(int) + 1
			nums = append(nums, num)
		},

		// If an error is encountered at any time, panic.
		OnError: func(e *event.Event) { panic(e.Error) },

		// When the stream comes to an end, append 0 to the slice.
		OnCompleted: func(e *event.Event) { nums = append(nums, 0) },
	}

	// Start listening to stream
	testStream.Subscribe(testObserver)
	assert.Exactly(t, []int{2, 0}, nums)
}

func TestSubscribeToFromObservable(t *testing.T) {
	nums := []interface{}{1, 2, 3, 4, 5, 6}
	numCopy := []int{}

	// Create an Observable with an integer.
	testObservable := observable.From(nums)
	testObserver := &observer.Observer{
		// While there is more events down the stream (in this case, just one), add 1 to the value emitted.
		OnNext: func(e *event.Event) {
			numCopy = append(numCopy, e.Value.(int) + 1)
		},

		// When the stream comes to an end, append 0 to the slice.
		OnCompleted: func(e *event.Event) {
			numCopy = append(numCopy, 0)
		},

	}

	// Start listening to stream
	testObservable.Subscribe(testObserver)
	assert.Exactly(t, []int{2, 3, 4, 5, 6, 7, 0}, numCopy)
}

func TestStartMethodWithFakeExternalCalls(t *testing.T) {

	fakeResponses := []*http.Response{}

	// Fake directives that returns an Event containing an HTTP response.
	directive1 := func() *event.Event {
		res := &http.Response{
			Status: "404 NOT FOUND",
			StatusCode: 404,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(time.Millisecond * 20)
		return &event.Event{ Value: res }
	}

	directive2 := func() *event.Event {
		res := &http.Response{
			Status: "200 OK",
			StatusCode: 200,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(10 * time.Millisecond)
		return &event.Event{ Value: res }
	}

	directive3 := func() *event.Event {
		res := &http.Response{
			Status: "500 SERVER ERROR",
			StatusCode: 500,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(30 * time.Millisecond)
		return &event.Event{ Value: res }
	}

	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			fakeResponses = append(fakeResponses, ev.Value.(*http.Response))
		},
		OnCompleted: func(ev *event.Event) {
			fakeResponses = append(fakeResponses, &http.Response{
					Status: "999 End",
					StatusCode: 999,
			})
		},
	}

	testObservable := observable.Start(directive1, directive2, directive3).Subscribe(testObserver)

	assert := assert.New(t)

	assert.IsType(&observable.Observable{}, testObservable)
	assert.Equal(4, len(fakeResponses))
	assert.Equal(200, fakeResponses[0].StatusCode)
	assert.Equal(404, fakeResponses[1].StatusCode)
	assert.Equal(500, fakeResponses[2].StatusCode)
	assert.Equal(999, fakeResponses[3].StatusCode)
}

func TestCreateObservableWithInterval(t *testing.T) {
	numch := make(chan int, 1)
	go func() {
		_ = observable.Interval(time.Millisecond).Subscribe(&observer.Observer{
			OnNext: func(e *event.Event) {
				numch <- e.Value.(int)
			},
		})
	}()

	for i := 0; i <= 10; i++ {
		<-time.After(1 * time.Millisecond)
		assert.Equal(t, i, <-numch)
	}
}

func TestSubscriptionDoesNotBlock(t *testing.T) {
	testStream := observable.Just("Hello")

	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			time.Sleep(1 * time.Second)
			return
		},
		OnCompleted: func(ev *event.Event) {
			t.Log("DONE")
		},
	}

	first := time.Now()
	testStream.Subscribe(testObserver)
	elapsed := time.Since(first)

	comp := assert.Comparison(func() bool {
		return elapsed < 1 * time.Second
	})

	assert.Condition(t, comp)
}

<commit_msg>Add 100ms delay after every Subscribe call<commit_after>package grx

import (
	"testing"
	"time"
	"net/http"

	"github.com/stretchr/testify/assert"

	"github.com/jochasinga/grx/event"
	"github.com/jochasinga/grx/observable"
	"github.com/jochasinga/grx/observer"
)

// TestCreateObservableWithCOnstructor tests if the constructor method returns an Observable
func TestCreateObservableWithConstructor(t *testing.T) {
	testStream := observable.New()
	assert.IsType(t, &observable.Observable{}, testStream)
}

// TestCreateObservableWithEmpty tests if Empty creates an empty Observable that terminates right away.
func TestCreateObservableWithEmpty(t *testing.T) {
	msg := "Sumpin's"
	testStream := observable.Empty()
	testObserver := &observer.Observer{
		OnCompleted: func(e *event.Event) {
			msg += " brewin'"
		},
	}
	testStream.Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	assert.Equal(t, "Sumpin's brewin'", msg)
}

// TestCreateObservableWithJust tests if Just method returns an <*Observable>
func TestCreateObservableWithJust(t *testing.T) {

	// Provided a URL string
	url := "http://api.com/api/v1.0/user"

	// and an Observable created with Just method
	testStream := observable.Just(url)

	// Observable must have the type <Observable>
	assert.IsType(t, &observable.Observable{}, testStream)
}

// TestCreateObservableWithFrom tests if From methods returns an <*Observable>
func TestCreateObservableWithFrom(t *testing.T) {
	// Provided a slice of URL strings
	urls := []interface{}{
		"http://api.com/api/v1.0/user",
		"https://dropbox.com/api/v2.1/get/storage",
		"http://googleapi.com/map",
	}

	// and an Observable created with From method
	testStream := observable.From(urls)

	// Observable must have the type <*Observable>
	assert.IsType(t, &observable.Observable{}, testStream)
}

func TestCreateObservableWithStart(t *testing.T) {

	// Provided a "directive" function which return an event that emits
	// a slice of integer numbers from 0 to 20.
	directive := func() *event.Event {
		ev := &event.Event{ Value: []int{} }
		for i:=0; i<=20; i++ {
			ev.Value = append(ev.Value.([]int), i)
		}
		return ev
	}

	testStream := observable.Start(directive)

	// Make sure it's the right type
	assert.IsType(t, &observable.Observable{}, testStream)

	nums := []int{}
	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			for _, num := range ev.Value.([]int) {
				nums = append(nums, num * 2)
			}
		},
		OnCompleted: func(ev *event.Event) {
			nums = append(nums, 666)
		},
	}

	testStream.Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	expected := []int{}
	for i:=0; i<=20; i++ {
		expected = append(expected, i * 2)
	}
	expected = append(expected, 666)
	assert.Exactly(t, expected, nums)
}

func TestSubscribeToJustObservable1(t *testing.T) {
	urlWithUserID := ""

	// Provided an Observable created with Just method
	url := "http://api.com/api/v1.0/user"
	expected := url + "?id=999"
	testStream := observable.Just(url)

	testObserver := &observer.Observer{
		OnNext: func(e *event.Event) { urlWithUserID = e.Value.(string) },
		OnCompleted: func(e *event.Event) { urlWithUserID = urlWithUserID + "?id=999" },
	}
	testStream.Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	assert.Exactly(t, expected, urlWithUserID)
}

func TestSubscribeToJustObservable2(t *testing.T) {
	nums := []int{}

	// Create an Observable with an integer.
	testStream := observable.Just(1)

	// Create an Observer object.
	testObserver := &observer.Observer{
		// While there is more events down the stream (in this case, just one), add 1 to the value emitted.
		OnNext: func(e *event.Event) {
			num := e.Value.(int) + 1
			nums = append(nums, num)
		},

		// If an error is encountered at any time, panic.
		OnError: func(e *event.Event) { panic(e.Error) },

		// When the stream comes to an end, append 0 to the slice.
		OnCompleted: func(e *event.Event) { nums = append(nums, 0) },
	}

	// Start listening to stream
	testStream.Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	assert.Exactly(t, []int{2, 0}, nums)
}

func TestSubscribeToFromObservable(t *testing.T) {
	nums := []interface{}{1, 2, 3, 4, 5, 6}
	numCopy := []int{}

	// Create an Observable with an integer.
	testObservable := observable.From(nums)
	testObserver := &observer.Observer{
		// While there is more events down the stream (in this case, just one), add 1 to the value emitted.
		OnNext: func(e *event.Event) {
			numCopy = append(numCopy, e.Value.(int) + 1)
		},

		// When the stream comes to an end, append 0 to the slice.
		OnCompleted: func(e *event.Event) {
			numCopy = append(numCopy, 0)
		},

	}

	// Start listening to stream
	testObservable.Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	assert.Exactly(t, []int{2, 3, 4, 5, 6, 7, 0}, numCopy)
}

func TestStartMethodWithFakeExternalCalls(t *testing.T) {

	fakeResponses := []*http.Response{}

	// Fake directives that returns an Event containing an HTTP response.
	directive1 := func() *event.Event {
		res := &http.Response{
			Status: "404 NOT FOUND",
			StatusCode: 404,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(time.Millisecond * 20)
		return &event.Event{ Value: res }
	}

	directive2 := func() *event.Event {
		res := &http.Response{
			Status: "200 OK",
			StatusCode: 200,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(10 * time.Millisecond)
		return &event.Event{ Value: res }
	}

	directive3 := func() *event.Event {
		res := &http.Response{
			Status: "500 SERVER ERROR",
			StatusCode: 500,
			Proto: "HTTP/1.0",
			ProtoMajor: 1,
		}
		time.Sleep(30 * time.Millisecond)
		return &event.Event{ Value: res }
	}

	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			fakeResponses = append(fakeResponses, ev.Value.(*http.Response))
		},
		OnCompleted: func(ev *event.Event) {
			fakeResponses = append(fakeResponses, &http.Response{
					Status: "999 End",
					StatusCode: 999,
			})
		},
	}

	testObservable := observable.Start(directive1, directive2, directive3).Subscribe(testObserver)

	// Block until side-effect is made
	<-time.After(100 * time.Millisecond)

	assert := assert.New(t)

	assert.IsType(&observable.Observable{}, testObservable)
	assert.Equal(4, len(fakeResponses))
	assert.Equal(200, fakeResponses[0].StatusCode)
	assert.Equal(404, fakeResponses[1].StatusCode)
	assert.Equal(500, fakeResponses[2].StatusCode)
	assert.Equal(999, fakeResponses[3].StatusCode)
}

func TestCreateObservableWithInterval(t *testing.T) {
	numch := make(chan int, 1)
	go func() {
		_ = observable.Interval(time.Millisecond).Subscribe(&observer.Observer{
			OnNext: func(e *event.Event) {
				numch <- e.Value.(int)
			},
		})
	}()

	for i := 0; i <= 10; i++ {
		<-time.After(1 * time.Millisecond)
		assert.Equal(t, i, <-numch)
	}
}

func TestSubscriptionDoesNotBlock(t *testing.T) {
	testStream := observable.Just("Hello")

	testObserver := &observer.Observer{
		OnNext: func(ev *event.Event) {
			time.Sleep(1 * time.Second)
			return
		},
		OnCompleted: func(ev *event.Event) {
			t.Log("DONE")
		},
	}

	first := time.Now()
	testStream.Subscribe(testObserver)
	elapsed := time.Since(first)

	comp := assert.Comparison(func() bool {
		return elapsed < 1 * time.Second
	})

	assert.Condition(t, comp)
}

<|endoftext|>


<commit_before>package gui

import (
	"log"
	"runtime"
	"unicode"

	"github.com/felixangell/phi/cfg"
	"github.com/felixangell/strife"
	"github.com/veandco/go-sdl2/sdl"
)

// View is an array of buffers basically.
type View struct {
	BaseComponent

	conf           *cfg.TomlConfig
	buffers        []*BufferPane
	focusedBuff    int
	commandPalette *CommandPalette
}

func NewView(width, height int, conf *cfg.TomlConfig) *View {
	view := &View{
		conf:    conf,
		buffers: []*BufferPane{},
	}

	view.Translate(width, height)
	view.Resize(width, height)

	view.commandPalette = NewCommandPalette(*conf, view)
	view.UnfocusBuffers()

	return view
}

func (n *View) hidePalette() {
	p := n.commandPalette
	p.clearInput()
	p.SetFocus(false)

	// set focus to the buffer
	// that invoked the cmd palette
	if p.parentBuff != nil {
		p.parentBuff.SetFocus(true)
		n.focusedBuff = p.parentBuff.index
	}

	// remove focus from palette
	p.buff.SetFocus(false)
}

func (n *View) focusPalette(buff *Buffer) {
	p := n.commandPalette
	p.SetFocus(true)

	// focus the command palette
	p.buff.SetFocus(true)

	// remove focus from the buffer
	// that invoked the command palette
	p.parentBuff = buff
}

func (n *View) UnfocusBuffers() {
	// clear focus from buffers
	for _, buffPane := range n.buffers {
		buffPane.Buff.SetFocus(false)
	}
}

func sign(dir int) int {
	if dir > 0 {
		return 1
	} else if dir < 0 {
		return -1
	}
	return 0
}

func (n *View) removeBuffer(index int) {
	log.Println("Removing buffer index:", index)
	log.Println("num buffs before delete: ", len(n.buffers))

	n.buffers = append(n.buffers[:index], n.buffers[index+1:]...)

	// only resize the buffers if we have
	// some remaining in the window
	if len(n.buffers) > 0 {
		bufferWidth := n.w / len(n.buffers)

		// translate all the components accordingly.
		for idx, buffPane := range n.buffers {
			// re-write the index.
			buffPane.Buff.index = idx

			buffPane.Resize(bufferWidth, n.h)
			buffPane.SetPosition(bufferWidth*idx, 0)
		}
	}

	dir := -1
	if n.focusedBuff == 0 {
		dir = 1
	}

	n.ChangeFocus(dir)
}

// FIXME
func (n *View) ChangeFocus(dir int) {
	// we cant change focus if there are no
	// buffers to focus to
	if len(n.buffers) == 0 {
		return
	}

	prevBuff := n.buffers[n.focusedBuff]

	if dir == -1 {
		n.focusedBuff--
	} else if dir == 1 {
		n.focusedBuff++
	}

	if n.focusedBuff < 0 {
		n.focusedBuff = len(n.buffers) - 1
	} else if n.focusedBuff >= len(n.buffers) {
		n.focusedBuff = 0
	}

	if prevBuff != nil {
		prevBuff.Buff.SetFocus(false)
	}

	if buffPane := n.getCurrentBuffPane(); buffPane != nil {
		buffPane.Buff.SetFocus(true)
	}
}

func (n *View) getCurrentBuffPane() *BufferPane {
	if len(n.buffers) == 0 {
		return nil
	}

	if buffPane := n.buffers[n.focusedBuff]; buffPane != nil {
		return buffPane
	}
	return nil
}

func (n *View) getCurrentBuff() *Buffer {
	buff := n.getCurrentBuffPane()
	if buff != nil {
		return buff.Buff
	}
	return nil
}

func (n *View) OnInit() {
}

func (n *View) OnUpdate() bool {
	dirty := false

	CONTROL_DOWN = strife.KeyPressed(sdl.K_LCTRL) || strife.KeyPressed(sdl.K_RCTRL)
	SUPER_DOWN = strife.KeyPressed(sdl.K_LGUI) || strife.KeyPressed(sdl.K_RGUI)

	shortcutName := "ctrl"
	source := cfg.Shortcuts.Controls

	if strife.PollKeys() && (SUPER_DOWN || CONTROL_DOWN) {
		if runtime.GOOS == "darwin" {
			if SUPER_DOWN {
				source = cfg.Shortcuts.Supers
				shortcutName = "super"
			} else if CONTROL_DOWN {
				source = cfg.Shortcuts.Controls
				shortcutName = "control"
			}
		} else {
			source = cfg.Shortcuts.Supers
		}

		r := rune(strife.PopKey())

		if r == sdl.K_F12 {
			DEBUG_MODE = !DEBUG_MODE
		}

		left := sdl.K_LEFT
		right := sdl.K_RIGHT
		up := sdl.K_UP
		down := sdl.K_DOWN

		// map to left/right/etc.
		// FIXME
		var key string
		switch int(r) {
		case left:
			key = "left"
		case right:
			key = "right"
		case up:
			key = "up"
		case down:
			key = "down"
		default:
			key = string(unicode.ToLower(r))
		}

		actionName, actionExists := source[key]
		if actionExists {
			if action, ok := actions[actionName]; ok {
				log.Println("Executing action '" + actionName + "'")
				return action.proc(n, []string{})
			}
		} else {
			log.Println("view: unimplemented shortcut", shortcutName, "+", string(unicode.ToLower(r)), "#", int(r), actionName, key)
		}
	}

	buff := n.getCurrentBuff()
	if buff != nil {
		buff.OnUpdate()
	}

	n.commandPalette.OnUpdate()

	return dirty
}

func (n *View) OnRender(ctx *strife.Renderer) {
	for _, buffPane := range n.buffers {
		buffPane.OnRender(ctx)
	}

	n.commandPalette.OnRender(ctx)
}

func (n *View) OnDispose() {}

func (n *View) AddBuffer() *Buffer {
	n.UnfocusBuffers()

	cfg := n.conf
	c := NewBuffer(cfg, BufferConfig{
		cfg.Theme.Background,
		cfg.Theme.Foreground,
		cfg.Theme.Cursor,
		cfg.Theme.Cursor_Invert,
		cfg.Theme.Gutter_Background,
		cfg.Theme.Gutter_Foreground,
		cfg.Editor.Loaded_Font,
	}, n, len(n.buffers))

	c.SetFocus(true)

	// work out the size of the buffer and set it
	// note that we +1 the components because
	// we haven't yet added the panel
	var bufferWidth int
	bufferWidth = n.w / (c.index + 1)

	n.focusedBuff = c.index

	n.buffers = append(n.buffers, NewBufferPane(c))

	// translate all the buffers accordingly.
	idx := 0
	for _, buffPane := range n.buffers {
		buffPane.Resize(bufferWidth, n.h)
		buffPane.SetPosition(bufferWidth*idx, 0)
		idx++
	}

	return c
}
<commit_msg>typo/bug fix<commit_after>package gui

import (
	"log"
	"runtime"
	"unicode"

	"github.com/felixangell/phi/cfg"
	"github.com/felixangell/strife"
	"github.com/veandco/go-sdl2/sdl"
)

// View is an array of buffers basically.
type View struct {
	BaseComponent

	conf           *cfg.TomlConfig
	buffers        []*BufferPane
	focusedBuff    int
	commandPalette *CommandPalette
}

func NewView(width, height int, conf *cfg.TomlConfig) *View {
	view := &View{
		conf:    conf,
		buffers: []*BufferPane{},
	}

	view.Translate(width, height)
	view.Resize(width, height)

	view.commandPalette = NewCommandPalette(*conf, view)
	view.UnfocusBuffers()

	return view
}

func (n *View) hidePalette() {
	p := n.commandPalette
	p.clearInput()
	p.SetFocus(false)

	// set focus to the buffer
	// that invoked the cmd palette
	if p.parentBuff != nil {
		p.parentBuff.SetFocus(true)
		n.focusedBuff = p.parentBuff.index
	}

	// remove focus from palette
	p.buff.SetFocus(false)
}

func (n *View) focusPalette(buff *Buffer) {
	p := n.commandPalette
	p.SetFocus(true)

	// focus the command palette
	p.buff.SetFocus(true)

	// remove focus from the buffer
	// that invoked the command palette
	p.parentBuff = buff
}

func (n *View) UnfocusBuffers() {
	// clear focus from buffers
	for _, buffPane := range n.buffers {
		buffPane.Buff.SetFocus(false)
	}
}

func sign(dir int) int {
	if dir > 0 {
		return 1
	} else if dir < 0 {
		return -1
	}
	return 0
}

func (n *View) removeBuffer(index int) {
	log.Println("Removing buffer index:", index)
	log.Println("num buffs before delete: ", len(n.buffers))

	n.buffers = append(n.buffers[:index], n.buffers[index+1:]...)

	// only resize the buffers if we have
	// some remaining in the window
	if len(n.buffers) > 0 {
		bufferWidth := n.w / len(n.buffers)

		// translate all the components accordingly.
		for idx, buffPane := range n.buffers {
			// re-write the index.
			buffPane.Buff.index = idx

			buffPane.Resize(bufferWidth, n.h)
			buffPane.SetPosition(bufferWidth*idx, 0)
		}
	}

	dir := -1
	if n.focusedBuff == 0 {
		dir = 1
	}

	n.ChangeFocus(dir)
}

// FIXME
func (n *View) ChangeFocus(dir int) {
	// we cant change focus if there are no
	// buffers to focus to
	if len(n.buffers) == 0 {
		return
	}

	prevBuff := n.buffers[n.focusedBuff]

	if dir == -1 {
		n.focusedBuff--
	} else if dir == 1 {
		n.focusedBuff++
	}

	if n.focusedBuff < 0 {
		n.focusedBuff = len(n.buffers) - 1
	} else if n.focusedBuff >= len(n.buffers) {
		n.focusedBuff = 0
	}

	if prevBuff != nil {
		prevBuff.Buff.SetFocus(false)
	}

	if buffPane := n.getCurrentBuffPane(); buffPane != nil {
		buffPane.Buff.SetFocus(true)
	}
}

func (n *View) getCurrentBuffPane() *BufferPane {
	if len(n.buffers) == 0 {
		return nil
	}

	if buffPane := n.buffers[n.focusedBuff]; buffPane != nil {
		return buffPane
	}
	return nil
}

func (n *View) getCurrentBuff() *Buffer {
	buff := n.getCurrentBuffPane()
	if buff != nil {
		return buff.Buff
	}
	return nil
}

func (n *View) OnInit() {
}

func (n *View) OnUpdate() bool {
	dirty := false

	CONTROL_DOWN = strife.KeyPressed(sdl.K_LCTRL) || strife.KeyPressed(sdl.K_RCTRL)
	SUPER_DOWN = strife.KeyPressed(sdl.K_LGUI) || strife.KeyPressed(sdl.K_RGUI)

	shortcutName := "ctrl"
	source := cfg.Shortcuts.Controls

	if strife.PollKeys() && (SUPER_DOWN || CONTROL_DOWN) {
		if runtime.GOOS == "darwin" {
			if SUPER_DOWN {
				source = cfg.Shortcuts.Supers
				shortcutName = "super"
			} else if CONTROL_DOWN {
				source = cfg.Shortcuts.Controls
				shortcutName = "control"
			}
		} else {
			source = cfg.Shortcuts.Supers
		}

		r := rune(strife.PopKey())

		if r == sdl.K_F12 {
			DEBUG_MODE = !DEBUG_MODE
		}

		left := sdl.K_LEFT
		right := sdl.K_RIGHT
		up := sdl.K_UP
		down := sdl.K_DOWN

		// map to left/right/etc.
		// FIXME
		var key string
		switch int(r) {
		case left:
			key = "left"
		case right:
			key = "right"
		case up:
			key = "up"
		case down:
			key = "down"
		default:
			key = string(unicode.ToLower(r))
		}

		actionName, actionExists := source[key]
		if actionExists {
			if action, ok := actions[actionName]; ok {
				log.Println("Executing action '" + actionName + "'")
				return action.proc(n, []string{})
			}
		} else {
			log.Println("view: unimplemented shortcut", shortcutName, "+", string(unicode.ToLower(r)), "#", int(r), actionName, key)
		}
	}

	buff := n.getCurrentBuffPane()
	if buff != nil {
		buff.OnUpdate()
	}

	n.commandPalette.OnUpdate()

	return dirty
}

func (n *View) OnRender(ctx *strife.Renderer) {
	for _, buffPane := range n.buffers {
		buffPane.OnRender(ctx)
	}

	n.commandPalette.OnRender(ctx)
}

func (n *View) OnDispose() {}

func (n *View) AddBuffer() *Buffer {
	n.UnfocusBuffers()

	cfg := n.conf
	c := NewBuffer(cfg, BufferConfig{
		cfg.Theme.Background,
		cfg.Theme.Foreground,
		cfg.Theme.Cursor,
		cfg.Theme.Cursor_Invert,
		cfg.Theme.Gutter_Background,
		cfg.Theme.Gutter_Foreground,
		cfg.Editor.Loaded_Font,
	}, n, len(n.buffers))

	c.SetFocus(true)

	// work out the size of the buffer and set it
	// note that we +1 the components because
	// we haven't yet added the panel
	var bufferWidth int
	bufferWidth = n.w / (c.index + 1)

	n.focusedBuff = c.index

	n.buffers = append(n.buffers, NewBufferPane(c))

	// translate all the buffers accordingly.
	idx := 0
	for _, buffPane := range n.buffers {
		buffPane.Resize(bufferWidth, n.h)
		buffPane.SetPosition(bufferWidth*idx, 0)
		idx++
	}

	return c
}
<|endoftext|>