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

package testing

import (
	"fmt";
	"flag";
)

var chatty bool;
func init() {
	flag.Bool("chatty", false, &chatty, "chatty");
}

export type T struct {
	errors	string;
	failed	bool;
	ch	*chan *T;
}

func (t *T) Fail() {
	t.failed = true
}

func (t *T) FailNow() {
	t.Fail();
	t.ch <- t;
	sys.goexit();
}

func (t *T) Log(args ...) {
	t.errors += "\t" + fmt.sprintln(args);
}

func (t *T) Logf(format string, args ...) {
	t.errors += fmt.sprintf("\t" + format, args);
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

func (t *T) Error(args ...) {
	t.Log(args);
	t.Fail();
}

func (t *T) Errorf(format string, args ...) {
	t.Logf(format, args);
	t.Fail();
}

func (t *T) Fatal(args ...) {
	t.Log(args);
	t.FailNow();
}

func (t *T) Fatalf(format string, args ...) {
	t.Logf(format, args);
	t.FailNow();
}

export type Test struct {
	name string;
	f *(*T);
}

func TRunner(t *T, test *Test) {
	test.f(t);
	t.ch <- t;
}

export func Main(tests *[]Test) {
	ok := true;
	if len(tests) == 0 {
		println("gotest: warning: no tests to run");
	}
	for i := 0; i < len(tests); i++ {
		if chatty {
			println("=== RUN ", tests[i].name);
		}
		t := new(T);
		t.ch = new(chan *T);
		go TRunner(t, &tests[i]);
		<-t.ch;
		if t.failed {
			println("--- FAIL:", tests[i].name);
			print(t.errors);
			ok = false;
		} else if chatty {
			println("--- PASS:", tests[i].name);
			print(t.errors);
		}
	}
	if !ok {
		println("FAIL");
		sys.exit(1);
	}
	println("PASS");
}
<commit_msg>automatically add tabs after newlines<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing

import (
	"fmt";
	"flag";
)

var chatty bool;
func init() {
	flag.Bool("chatty", false, &chatty, "chatty");
}

// Insert tabs after newlines - but not the last one
func Tabify(s string) string {
	for i := 0; i < len(s) - 1; i++ {	// -1 because if last char is newline, don't bother
		if s[i] == '\n' {
			return s[0:i+1] + "\t" + Tabify(s[i+1:len(s)]);
		}
	}
	return s
}

export type T struct {
	errors	string;
	failed	bool;
	ch	*chan *T;
}

func (t *T) Fail() {
	t.failed = true
}

func (t *T) FailNow() {
	t.Fail();
	t.ch <- t;
	sys.goexit();
}

func (t *T) Log(args ...) {
	t.errors += "\t" + Tabify(fmt.sprintln(args));
}

func (t *T) Logf(format string, args ...) {
	t.errors += Tabify(fmt.sprintf("\t" + format, args));
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

func (t *T) Error(args ...) {
	t.Log(args);
	t.Fail();
}

func (t *T) Errorf(format string, args ...) {
	t.Logf(format, args);
	t.Fail();
}

func (t *T) Fatal(args ...) {
	t.Log(args);
	t.FailNow();
}

func (t *T) Fatalf(format string, args ...) {
	t.Logf(format, args);
	t.FailNow();
}

export type Test struct {
	name string;
	f *(*T);
}

func TRunner(t *T, test *Test) {
	test.f(t);
	t.ch <- t;
}

export func Main(tests *[]Test) {
	ok := true;
	if len(tests) == 0 {
		println("gotest: warning: no tests to run");
	}
	for i := 0; i < len(tests); i++ {
		if chatty {
			println("=== RUN ", tests[i].name);
		}
		t := new(T);
		t.ch = new(chan *T);
		go TRunner(t, &tests[i]);
		<-t.ch;
		if t.failed {
			println("--- FAIL:", tests[i].name);
			print(t.errors);
			ok = false;
		} else if chatty {
			println("--- PASS:", tests[i].name);
			print(t.errors);
		}
	}
	if !ok {
		println("FAIL");
		sys.exit(1);
	}
	println("PASS");
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"runtime/pprof"
	"syscall"

	I "github.com/brentp/irelate"
)

func init() {
	// so that output stops when piping to head.
	done := make(chan os.Signal, 1)

	signal.Notify(done, os.Interrupt, syscall.SIGIO, syscall.SIGPIPE)
	go func() {
		for _ = range done {
			os.Exit(0)
		}
	}()
}

func main() {

	cpuProfile := flag.Bool("cpuProfile", false, "perform CPU profiling")
	flag.Parse()
	files := flag.Args()

	streams := make([]I.RelatableChannel, 0)
	for _, f := range files {
		// Streamer automatically returns a Relatalbe Channel for bam/gff/bed(.gz)
		streams = append(streams, I.Streamer(f))
	}

	if *cpuProfile {
		f, err := os.Create("irelate.cpu.pprof")
		if err != nil {
			panic(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	buf := bufio.NewWriter(os.Stdout)

	merged := I.Merge(streams...)
	for interval := range I.IRelate(merged, I.CheckRelatedByOverlap, false, 0) {
		// for bam output:
		bam := (interval).(*I.Bam)
		fmt.Fprintf(buf, "%s\t%d\t%d\t%s\t%v\n", bam.Chrom(), bam.Start(), bam.End(), bam.Cigar(), bam.Name(), bam.Seq())

	}
	buf.Flush()

}
<commit_msg>check intervals<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"runtime/pprof"
	"syscall"

	I "github.com/brentp/irelate"
)

func init() {
	// so that output stops when piping to head.
	done := make(chan os.Signal, 1)

	signal.Notify(done, os.Interrupt, syscall.SIGIO, syscall.SIGPIPE)
	go func() {
		for _ = range done {
			os.Exit(0)
		}
	}()
}

func main() {

	cpuProfile := flag.Bool("cpuProfile", false, "perform CPU profiling")
	flag.Parse()
	files := flag.Args()

	streams := make([]I.RelatableChannel, 0)
	for _, f := range files {
		// Streamer automatically returns a Relatalbe Channel for bam/gff/bed(.gz)
		streams = append(streams, I.Streamer(f))
	}

	if *cpuProfile {
		f, err := os.Create("irelate.cpu.pprof")
		if err != nil {
			panic(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	buf := bufio.NewWriter(os.Stdout)

	merged := I.Merge(streams...)
	//for interval := range I.IRelate(merged, I.CheckRelatedByOverlap) {
	for interval := range I.IRelate(merged, I.CheckRelatedByOverlap, false, 0) {
		// for bam output:
		// bam := *(interval).(*I.Bam)
		fmt.Fprintf(buf, "%s\t%d\t%d\t%d\n", interval.Chrom(), interval.Start(), interval.End(), len(interval.Related()))

	}
	buf.Flush()

}
<|endoftext|>


<commit_before>package main_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
)

func containsString(list []string, expected string) bool {
	for _, current := range list {
		if current == expected {
			return true
		}
	}
	return false
}

var _ = Describe("Main", func() {
	It("Makes a http get request to each url in a file", func() {
		port := 8000
		list := `http://127.0.0.1:8000/A
http://127.0.0.1:8000/B
http://127.0.0.1:8000/C
		`

		urlsVisited := []string{}

		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			urlsVisited = append(urlsVisited, r.URL.Path)
		})

		server := httptest.NewUnstartedServer(handler)
		server.Listener, _ = net.Listen("tcp", ":"+strconv.Itoa(port))
		server.Start()
		defer server.Close()
		file, err := ioutil.TempFile(os.TempDir(), "prefix")
		if err != nil {
			panic(err)
		}
		file.WriteString(list)
		defer os.Remove(file.Name())

		exePath, err := filepath.Abs("./code-named-something")
		if err != nil {
			panic(err)
		}
		cmd := exec.Command(exePath, "-f", file.Name())
		output, err := cmd.CombinedOutput()

		client := &http.Client{}
		req, _ := http.NewRequest("GET", "http://127.0.0.1:8000/A", nil)
		_, err = client.Do(req)
		if err != nil {
			fmt.Printf("%v", err)
			panic(err)
		}

		Expect(err).To(BeNil())
		Expect(containsString(urlsVisited, "/A")).To(Equal(true))
		Expect(containsString(urlsVisited, "/B")).To(Equal(true))
		Expect(containsString(urlsVisited, "/C")).To(Equal(true))
	})
})
<commit_msg>Removing the log and the variables which are not used<commit_after>package main_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
)

func containsString(list []string, expected string) bool {
	for _, current := range list {
		if current == expected {
			return true
		}
	}
	return false
}

var _ = Describe("Main", func() {
	It("Makes a http get request to each url in a file", func() {
		port := 8000
		list := `http://127.0.0.1:8000/A
http://127.0.0.1:8000/B
http://127.0.0.1:8000/C
		`

		urlsVisited := []string{}

		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			urlsVisited = append(urlsVisited, r.URL.Path)
		})

		server := httptest.NewUnstartedServer(handler)
		server.Listener, _ = net.Listen("tcp", ":"+strconv.Itoa(port))
		server.Start()
		defer server.Close()
		file, err := ioutil.TempFile(os.TempDir(), "prefix")
		if err != nil {
			panic(err)
		}
		file.WriteString(list)
		defer os.Remove(file.Name())

		exePath, err := filepath.Abs("./code-named-something")
		if err != nil {
			panic(err)
		}
		cmd := exec.Command(exePath, "-f", file.Name())
		_, _ = cmd.CombinedOutput()

		client := &http.Client{}
		req, _ := http.NewRequest("GET", "http://127.0.0.1:8000/A", nil)
		_, err = client.Do(req)
		if err != nil {
			fmt.Printf("%v", err)
			panic(err)
		}

		Expect(err).To(BeNil())
		Expect(containsString(urlsVisited, "/A")).To(Equal(true))
		Expect(containsString(urlsVisited, "/B")).To(Equal(true))
		Expect(containsString(urlsVisited, "/C")).To(Equal(true))
	})
})
<|endoftext|>


<commit_before>package mdfmt

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

const (
	testCasesPath string = "./test_cases/"
)

func TestFmt(t *testing.T) {
	files, err := ioutil.ReadDir(testCasesPath)
	if err != nil {
		t.Fatal(err)
	}

	ins := make([]os.FileInfo, 0, len(files)/2)
	outs := make([]os.FileInfo, 0, len(files)/2)

	for _, file := range files {
		if strings.Contains(file.Name(), ".in.") {
			ins = append(ins, file)
		} else if strings.Contains(file.Name(), ".out.") {
			outs = append(outs, file)
		}
	}

	if len(ins) != len(outs) {
		t.Fatal("There is a different number of ins and out")
	}

	if len(ins) == 0 {
		t.Fatal("There are no input files")
	}

	if len(outs) == 0 {
		t.Fatal("There are no output files")
	}

	for i, in := range ins {
		inFile, err := os.Open(filepath.Join(testCasesPath, in.Name()))
		if err != nil {
			t.Fatal(err)
		}
		defer inFile.Close()

		outFile, err := os.Open(filepath.Join(testCasesPath, outs[i].Name()))
		if err != nil {
			t.Fatal(err)
		}
		defer outFile.Close()

		actualOutput, err := ioutil.ReadAll(Fmt(inFile))
		if err != nil {
			t.Fatal(err)
		}

		expectedOutput, err := ioutil.ReadAll(outFile)
		if err != nil {
			t.Fatal(err)
		}

		if string(actualOutput) != string(expectedOutput) {
			t.Logf("\nEXPECTED:\n%s\nGOT:\n%s\n", expectedOutput, actualOutput)
			t.Fail()
		}

	}

}

func Fmt(in io.Reader) (out io.Reader) {
	return in
}
<commit_msg>[@ghthor, @vise890] add blank line after header<commit_after>package mdfmt

import (
	"bufio"
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

const (
	testCasesPath string = "./test_cases/"
)

func TestFmt(t *testing.T) {
	files, err := ioutil.ReadDir(testCasesPath)
	if err != nil {
		t.Fatal(err)
	}

	ins := make([]os.FileInfo, 0, len(files)/2)
	outs := make([]os.FileInfo, 0, len(files)/2)

	for _, file := range files {
		if strings.Contains(file.Name(), ".in.") {
			ins = append(ins, file)
		} else if strings.Contains(file.Name(), ".out.") {
			outs = append(outs, file)
		}
	}

	if len(ins) != len(outs) {
		t.Fatal("There is a different number of ins and out")
	}

	if len(ins) == 0 {
		t.Fatal("There are no input files")
	}

	if len(outs) == 0 {
		t.Fatal("There are no output files")
	}

	for i, in := range ins {
		inFile, err := os.Open(filepath.Join(testCasesPath, in.Name()))
		if err != nil {
			t.Fatal(err)
		}
		defer inFile.Close()

		outFile, err := os.Open(filepath.Join(testCasesPath, outs[i].Name()))
		if err != nil {
			t.Fatal(err)
		}
		defer outFile.Close()

		actualOutput, err := ioutil.ReadAll(Fmt(inFile))
		if err != nil {
			t.Fatal(err)
		}

		expectedOutput, err := ioutil.ReadAll(outFile)
		if err != nil {
			t.Fatal(err)
		}

		if string(actualOutput) != string(expectedOutput) {
			t.Logf("\nEXPECTED:\n%s\nGOT:\n%s\n", expectedOutput, actualOutput)
			t.Fail()
		}

	}

}

func Fmt(input io.Reader) (output io.Reader) {
	scanner := bufio.NewScanner(input)
	scanner.Split(bufio.ScanLines)

	out := bytes.NewBuffer(make([]byte, 0, 1024))

	for scanner.Scan() {
		line := scanner.Text()

		if line[0] == '#' {
			out.WriteString(line + "\n")
			mustBeFollowedByBlankLine(scanner, out)
		}
	}

	return out
}

func mustBeFollowedByBlankLine(scanner *bufio.Scanner, out io.Writer) {
	scanner.Split(bufio.ScanLines)

	if scanner.Scan() {
		if scanner.Text() == "" {
			out.Write([]byte("\n"))
		} else {
			out.Write([]byte("\n"))
			out.Write([]byte(scanner.Text() + "\n"))
		}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/nanopack/pulse/plexer"
	"github.com/nanopack/pulse/relay"
	"github.com/nanopack/pulse/server"
)

var address = "127.0.0.1:1234"
var wait = sync.WaitGroup{}

var messages = []plexer.MessageSet{}

func TestMain(m *testing.M) {
	err := server.Listen(address, func(msgSet plexer.MessageSet) error {
		messages = append(messages, msgSet)
		wait.Add(-len(msgSet.Messages))
		return nil
	})

	if err != nil {
		panic(fmt.Sprintf("unable to listen %v", err))
		return
	}
	rtn := m.Run()
	os.Exit(rtn)
}

func TestEndToEnd(test *testing.T) {
	relay, err := relay.NewRelay(address, "relay.station.1")
	if err != nil {
		test.Errorf("unable to connect to server %v", err)
		return
	}
	defer relay.Close()

	relay.AddCollector("cpu", []string{"hi", "how", "are:you"}, randCollector())

	relay.AddCollector("ram", nil, randCollector())

	relay.AddCollector("disk", nil, randCollector())
	time.Sleep(time.Millisecond * 100)
	wait.Add(1)
	server.Poll([]string{"disk"})
	wait.Wait()

	wait.Add(2)
	server.Poll([]string{"ram", "cpu"})
	wait.Wait()

	wait.Add(3)
	server.Poll([]string{"ram", "cpu", "disk"})
	wait.Wait()

	if len(messages) != 3 {
		test.Errorf("Expected to recieve 3 messages but instead got %d", len(messages))
	}
	messages = []plexer.MessageSet{}
}

func randCollector() relay.Collector {
	return relay.NewPointCollector(rand.Float64)
}
<commit_msg>remove unnecessary return<commit_after>package main

import (
	"fmt"
	"math/rand"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/nanopack/pulse/plexer"
	"github.com/nanopack/pulse/relay"
	"github.com/nanopack/pulse/server"
)

var address = "127.0.0.1:1234"
var wait = sync.WaitGroup{}

var messages = []plexer.MessageSet{}

func TestMain(m *testing.M) {
	err := server.Listen(address, func(msgSet plexer.MessageSet) error {
		messages = append(messages, msgSet)
		wait.Add(-len(msgSet.Messages))
		return nil
	})

	if err != nil {
		panic(fmt.Sprintf("unable to listen %v", err))
	}
	rtn := m.Run()
	os.Exit(rtn)
}

func TestEndToEnd(test *testing.T) {
	relay, err := relay.NewRelay(address, "relay.station.1")
	if err != nil {
		test.Errorf("unable to connect to server %v", err)
		return
	}
	defer relay.Close()

	relay.AddCollector("cpu", []string{"hi", "how", "are:you"}, randCollector())

	relay.AddCollector("ram", nil, randCollector())

	relay.AddCollector("disk", nil, randCollector())
	time.Sleep(time.Millisecond * 100)
	wait.Add(1)
	server.Poll([]string{"disk"})
	wait.Wait()

	wait.Add(2)
	server.Poll([]string{"ram", "cpu"})
	wait.Wait()

	wait.Add(3)
	server.Poll([]string{"ram", "cpu", "disk"})
	wait.Wait()

	if len(messages) != 3 {
		test.Errorf("Expected to recieve 3 messages but instead got %d", len(messages))
	}
	messages = []plexer.MessageSet{}
}

func randCollector() relay.Collector {
	return relay.NewPointCollector(rand.Float64)
}
<|endoftext|>


<commit_before>package main_test

import (
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/verdverm/frisby"
)

func TestPing(t *testing.T) {
	errs := frisby.Create("Test ping").
		Get("http://localhost:5025/ping").
		Send().
		ExpectStatus(http.StatusOK).
		ExpectContent("pong").
		AfterText(func(F *frisby.Frisby, text string, err error) {
		assert.Nil(t, err)
		assert.Equal(t, "pong", text)
	}).
		Errors()

	for _, err := range errs {
		assert.Nil(t, err)
	}
}
<commit_msg>update<commit_after>package main_test

import (
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/verdverm/frisby"
)

func TestPing(t *testing.T) {
	errs := frisby.Create("Test ping").
		Get("http://localhost:5025/ping").
		Send().
		ExpectStatus(http.StatusOK).
		ExpectContent("pong").
		AfterText(
		func(F *frisby.Frisby, text string, err error) {
			assert.Nil(t, err)
			assert.Equal(t, "pong", text)
		}).
		Errors()

	for _, err := range errs {
		assert.Nil(t, err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"testing"

	. "gopkg.in/go-playground/assert.v1"
)

// NOTES:
// - Run "go test" to run tests
// - Run "gocov test | gocov report" to report on test converage by file
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
//
// or
//
// -- may be a good idea to change to output path to somewherelike /tmp
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
//

func TestMain(m *testing.M) {

	// setup

	os.Exit(m.Run())

	// teardown
}

func TestNonExistantStaticDir(t *testing.T) {

	i := "static/test-files/garbagedir"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "stat static/test-files/garbagedir: no such file or directory")
}

func TestBadPackage(t *testing.T) {

	i := "static/test-files/test-inner"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := ""
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Package Name")
}

func TestBadOutputDir(t *testing.T) {

	i := "static/test-files/test-inner"
	flagStaticDir = &i

	o := ""
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Output Directory")
}

func TestBadStaticDir(t *testing.T) {

	i := ""
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Static File Directoy '.'")
}

func TestGenerateInitFile(t *testing.T) {

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := true
	flagInit = &init

	main()

	b, err := ioutil.ReadFile("static/test-files/test.go")
	Equal(t, err, nil)

	expected := `//go:generate statics -i=static/test-files/teststart -o=static/test-files/test.go -pkg=test -group=Assets

package test

import "github.com/joeybloggs/statics/static"

// newStaticAssets initializes a new *static.Files instance for use
func newStaticAssets(config *static.Config) (*static.Files, error) {

	return static.New(config, &static.DirFile{})
}
`

	Equal(t, string(b), expected)
}

func TestIgnore(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ".*.txt"
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}

func TestBadIgnore(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := "([12.gitignore"
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**Error Compiling Regex:error parsing regexp: missing closing ]: `[12.gitignore`")
}

func TestGenerateFilePrefix(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := "static/"
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}

func TestGenerateFile(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}
<commit_msg>Update main_test.go<commit_after>package main

import (
	"io/ioutil"
	"os"
	"testing"

	. "gopkg.in/go-playground/assert.v1"
)

// NOTES:
// - Run "go test" to run tests
// - Run "gocov test | gocov report" to report on test converage by file
// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called
//
// or
//
// -- may be a good idea to change to output path to somewherelike /tmp
// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html
//

func TestMain(m *testing.M) {

	// setup

	os.Exit(m.Run())

	// teardown
}

func TestNonExistantStaticDir(t *testing.T) {

	i := "static/test-files/garbagedir"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "stat static/test-files/garbagedir: no such file or directory")
}

func TestBadPackage(t *testing.T) {

	i := "static/test-files/test-inner"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := ""
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Package Name")
}

func TestBadOutputDir(t *testing.T) {

	i := "static/test-files/test-inner"
	flagStaticDir = &i

	o := ""
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Output Directory")
}

func TestBadStaticDir(t *testing.T) {

	i := ""
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**invalid Static File Directoy '.'")
}

func TestGenerateInitFile(t *testing.T) {

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	init := true
	flagInit = &init

	main()

	b, err := ioutil.ReadFile("static/test-files/test.go")
	Equal(t, err, nil)

	expected := `//go:generate statics -i=static/test-files/teststart -o=static/test-files/test.go -pkg=test -group=Assets

package test

import "gopkg.in/go-playground/statics.v1/static"

// newStaticAssets initializes a new *static.Files instance for use
func newStaticAssets(config *static.Config) (*static.Files, error) {

	return static.New(config, &static.DirFile{})
}
`

	Equal(t, string(b), expected)
}

func TestIgnore(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ".*.txt"
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}

func TestBadIgnore(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := "([12.gitignore"
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	PanicMatches(t, func() { main() }, "**Error Compiling Regex:error parsing regexp: missing closing ]: `[12.gitignore`")
}

func TestGenerateFilePrefix(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := "static/"
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}

func TestGenerateFile(t *testing.T) {

	Equal(t, true, true)

	i := "static/test-files/teststart"
	flagStaticDir = &i

	o := "static/test-files/test.go"
	flagOuputFile = &o

	p := "test"
	flagPkg = &p

	g := "Assets"
	flagGroup = &g

	ignore := ""
	flagIgnore = &ignore

	prefix := ""
	flagPrefix = &prefix

	init := false
	flagInit = &init

	main()
}
<|endoftext|>


<commit_before>package watcher

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var (
	// ErrNothingAdded is an error that occurs when a Watcher's Start() method is
	// called and no files or folders have been added to the Watcher's watchlist.
	ErrNothingAdded = errors.New("error: no files added to the watchlist")

	// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
	// being watched has been deleted.
	ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")

	// ErrWatcherClosed is sent through w.Errors when a Watcher is closed.
	ErrWatcherClosed = errors.New("error: watcher was closed")
)

// An Op is a type that is used to describe what type
// of event has occurred during the watching process.
type Op uint32

// Ops
const (
	Create Op = iota
	Write
	Remove
	Rename
	Chmod
)

var ops = map[Op]string{
	Create: "CREATE",
	Write:  "WRITE",
	Remove: "REMOVE",
	Rename: "RENAME",
	Chmod:  "CHMOD",
}

// String prints the string version of the Op consts
func (e Op) String() string {
	if op, found := ops[e]; found {
		return op
	}
	return "UNRECOGNIZED OP"
}

// An Option is a type that is used to set options for a Watcher.
type Option int

const (
	// NonRecursive sets the watcher to not watch directories recursively.
	NonRecursive Option = 1 << iota

	// IgnoreDotFiles sets the watcher to ignore dot files.
	IgnoreDotFiles
)

// An Event desribes an event that is received when files or directory
// changes occur. It includes the os.FileInfo of the changed file or
// directory and the type of event that's occurred and the full path of the file.
type Event struct {
	Op
	Path string
	os.FileInfo
}

// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
	if e.FileInfo != nil {
		pathType := "FILE"
		if e.IsDir() {
			pathType = "DIRECTORY"
		}
		return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
	}
	return "INVALID EVENT"
}

// A Watcher describes a file watcher.
type Watcher struct {
	Event chan Event
	Error chan error

	options []Option

	mu        *sync.Mutex
	running   bool
	files     map[string]os.FileInfo
	ignored   map[string]struct{}
	names     []string
	maxEvents int
}

// New returns a new initialized *Watcher.
func New(options ...Option) *Watcher {
	return &Watcher{
		Event:   make(chan Event),
		Error:   make(chan error),
		options: options,
		mu:      new(sync.Mutex),
		files:   make(map[string]os.FileInfo),
		ignored: make(map[string]struct{}),
		names:   []string{},
	}
}

// Ignore adds paths that should be ignored by the watcher.
func (w *Watcher) Ignore(paths ...string) error {
	for _, path := range paths {
		if err := w.Remove(path); err != nil {
			return err
		}
		w.mu.Lock()
		w.ignored[path] = struct{}{}
		w.mu.Unlock()
	}
	return nil
}

// WatchedFiles returns a map of all the files being watched.
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
	w.mu.Lock()
	defer w.mu.Unlock()

	return w.files
}

// SetMaxEvents controls the maximum amount of events that are sent on
// the Event channel per watching cycle. If max events is less than 1, there is
// no limit, which is the default.
func (w *Watcher) SetMaxEvents(amount int) {
	w.mu.Lock()
	w.maxEvents = amount
	w.mu.Unlock()
}

// fileInfo is an implementation of os.FileInfo that can be used
// as a mocked os.FileInfo when triggering an event when the specified
// os.FileInfo is nil.
type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (fs *fileInfo) IsDir() bool {
	return false
}
func (fs *fileInfo) ModTime() time.Time {
	return fs.modTime
}
func (fs *fileInfo) Mode() os.FileMode {
	return fs.mode
}
func (fs *fileInfo) Name() string {
	return fs.name
}
func (fs *fileInfo) Size() int64 {
	return fs.size
}
func (fs *fileInfo) Sys() interface{} {
	return fs.sys
}

// Add adds either a single file or recursed directory to
// the Watcher's file list.
func (w *Watcher) Add(name string) (err error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if name == "." || name == ".." {
		name, err = filepath.Abs(name)
		if err != nil {
			return err
		}
	}

	name = filepath.Clean(name)

	// Add the name from w's names list.
	w.names = append(w.names, name)

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If watching a single file that isn't in the ignored list,
	// add it and return.
	_, ignored := w.ignored[name]
	if !fInfo.IsDir() && !ignored {
		w.files[fInfo.Name()] = fInfo
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to add to w.files.
	fInfoList, err := ListFiles(name, w.ignored, w.options...)
	if err != nil {
		return err
	}
	for k, v := range fInfoList {
		w.files[k] = v
	}
	return nil
}

// Remove removes either a single file or recursed directory from
// the Watcher's file list.
func (w *Watcher) Remove(name string) (err error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if name == "." || name == ".." {
		name, err = filepath.Abs(name)
		if err != nil {
			return err
		}
	}

	name = filepath.Clean(name)

	// Remove the name from w's names list.
	for i := range w.names {
		if w.names[i] == name {
			w.names = append(w.names[:i], w.names[i+1:]...)
		}
	}

	// If name is a single file, remove it and return.
	info, found := w.files[name]
	if !found {
		return nil // Doesn't exist, just return
	}
	if !info.IsDir() {
		delete(w.files, name)
		return nil
	}

	// If it's a directory, delete all of it's contents from w.files.
	for path := range w.files {
		if strings.HasPrefix(path, name) {
			delete(w.files, path)
		}
	}
	return nil
}

// TriggerEvent is a method that can be used to trigger an event, separate to
// the file watching process.
func (w *Watcher) TriggerEvent(eventType Op, file os.FileInfo) {
	if file == nil {
		file = &fileInfo{name: "triggered event", modTime: time.Now()}
	}
	w.Event <- Event{Op: eventType, Path: "-", FileInfo: file}
}

type renamedFrom struct {
	path string
	os.FileInfo
}

// Close removes all watched files from the watcher and causes Start to
// stop watching and return after the current watching cycle finishes if
// one is currently running.
func (w *Watcher) Close() error {
	w.mu.Lock()
	if !w.running {
		w.mu.Unlock()
		return nil
	}
	w.running = false
	w.Error <- ErrWatcherClosed
	for k, _ := range w.files {
		delete(w.files, k)
	}
	w.names = []string{}
	w.mu.Unlock()
	return nil
}

// Start starts the watching process and checks for changes every `pollInterval` duration.
// If pollInterval is 0, the default is 100ms.
func (w *Watcher) Start(pollInterval time.Duration) error {
	if pollInterval <= 0 {
		pollInterval = time.Millisecond * 100
	}

	if len(w.names) < 1 {
		return ErrNothingAdded
	}

	w.mu.Lock()
	w.running = true
	w.mu.Unlock()

	for {
		w.mu.Lock()

		if !w.running {
			w.mu.Unlock()
			return nil
		}

		fileList := make(map[string]os.FileInfo)
		for _, name := range w.names {
			// Retrieve the list of os.FileInfo's from w.Name.
			list, err := ListFiles(name, w.ignored, w.options...)
			if err != nil {
				if os.IsNotExist(err) {
					w.Error <- ErrWatchedFileDeleted
					if err := w.Remove(name); err != nil {
						w.mu.Unlock()
						return err
					}
					continue
				} else {
					w.Error <- err
				}
			}
			for k, v := range list {
				fileList[k] = v
			}
		}

		numEvents := 0

		events := map[Op]map[string]os.FileInfo{
			Create: make(map[string]os.FileInfo),
			Remove: make(map[string]os.FileInfo),
		}

		renamed := make(map[string]renamedFrom)

		// Check for added files.
		for path, file := range fileList {
			if _, found := w.files[path]; !found {
				events[Create][path] = file
			}
		}

		// Check for removed files.
		for path, file := range w.files {
			if _, found := fileList[path]; !found {
				events[Remove][path] = file
			}
		}

		// Check for renamed files.
		for path1, file1 := range events[Create] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			for path2, file2 := range events[Remove] {
				if file1.Size() == file2.Size() && path1 != path2 &&
					filepath.Dir(path1) == filepath.Dir(path2) &&
					file1.IsDir() == file2.IsDir() &&
					file1.ModTime() == file2.ModTime() { // TODO: Check this <--
					renamed[path2] = renamedFrom{path1, file1}
					w.Event <- Event{
						Op:       Rename,
						Path:     path2,
						FileInfo: file2,
					}
					numEvents++

					// Delete path1 from the added files map.
					delete(events[Create], path1)

					// Delete path2 from the deleted files map.
					delete(events[Remove], path2)
				}
			}
		}

		for path, file := range events[Create] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			w.Event <- Event{
				Op:       Create,
				Path:     path,
				FileInfo: file,
			}
			numEvents++
		}

		for path, file := range events[Remove] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			w.Event <- Event{
				Op:       Remove,
				Path:     path,
				FileInfo: file,
			}
			numEvents++
		}

		// Check for modified and chmoded files.
		for path, file := range w.files {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			_, addFound := events[Create][path]
			_, removeFound := events[Remove][path]
			renamedFrom, renameFound := renamed[path]
			if !addFound && !removeFound && !renameFound {
				if !file.IsDir() && fileList[path].ModTime() != file.ModTime() {
					w.Event <- Event{
						Op:       Write,
						Path:     path,
						FileInfo: file,
					}
					numEvents++
				}

				if w.maxEvents > 0 && numEvents >= w.maxEvents {
					goto SLEEP
				}
				if fileList[path].Mode() != file.Mode() {
					w.Event <- Event{
						Op:       Chmod,
						Path:     path,
						FileInfo: file,
					}
					numEvents++
				}
			}
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			if renameFound && renamedFrom.Mode() != file.Mode() {
				w.Event <- Event{
					Op:       Chmod,
					Path:     renamedFrom.path,
					FileInfo: renamedFrom.FileInfo,
				}
				numEvents++
			}
		}

	SLEEP:
		// Update w.files and then sleep for a little bit.
		w.files = fileList
		w.mu.Unlock()
		time.Sleep(pollInterval)
	}
}

// hasOption returns true or false based on whether or not
// an Option exists in an Option slice.
func hasOption(options []Option, option Option) bool {
	for _, o := range options {
		if option&o != 0 {
			return true
		}
	}
	return false
}

// ListFiles returns a map of all os.FileInfo's recursively
// contained in a directory. If name is a single file, it returns
// an os.FileInfo map containing a single os.FileInfo.
func ListFiles(name string, ignoredPaths map[string]struct{}, options ...Option) (map[string]os.FileInfo, error) {
	fileList := make(map[string]os.FileInfo)

	if _, ignored := ignoredPaths[name]; ignored {
		return nil, nil
	}

	name = filepath.Clean(name)

	nonRecursive := hasOption(options, NonRecursive)
	ignoreDotFiles := hasOption(options, IgnoreDotFiles)

	if nonRecursive {
		f, err := os.Open(name)
		if err != nil {
			return nil, err
		}
		defer f.Close()
		info, err := os.Stat(name)
		if err != nil {
			return nil, err
		}
		// Add the name to fileList.
		if !info.IsDir() && ignoreDotFiles && strings.HasPrefix(name, ".") {
			return fileList, nil
		}
		fileList[name] = info
		if !info.IsDir() {
			return fileList, nil
		}
		// It's a directory, read it's contents.
		fInfoList, err := f.Readdir(-1)
		if err != nil {
			return nil, err
		}
		// Add all of the FileInfo's returned from f.ReadDir to fileList.
		for _, fInfo := range fInfoList {
			if ignoreDotFiles && strings.HasPrefix(fInfo.Name(), ".") {
				continue
			}
			fileList[filepath.Join(name, fInfo.Name())] = fInfo
		}
		return fileList, nil
	}

	if err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		_, ignored := ignoredPaths[path]
		if ignored || (ignoreDotFiles && strings.HasPrefix(info.Name(), ".")) {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		fileList[path] = info

		return nil
	}); err != nil {
		return nil, err
	}

	return fileList, nil
}
<commit_msg>use os.SameFile for rename event file check comparison<commit_after>package watcher

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

var (
	// ErrNothingAdded is an error that occurs when a Watcher's Start() method is
	// called and no files or folders have been added to the Watcher's watchlist.
	ErrNothingAdded = errors.New("error: no files added to the watchlist")

	// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
	// being watched has been deleted.
	ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")

	// ErrWatcherClosed is sent through w.Errors when a Watcher is closed.
	ErrWatcherClosed = errors.New("error: watcher was closed")
)

// An Op is a type that is used to describe what type
// of event has occurred during the watching process.
type Op uint32

// Ops
const (
	Create Op = iota
	Write
	Remove
	Rename
	Chmod
)

var ops = map[Op]string{
	Create: "CREATE",
	Write:  "WRITE",
	Remove: "REMOVE",
	Rename: "RENAME",
	Chmod:  "CHMOD",
}

// String prints the string version of the Op consts
func (e Op) String() string {
	if op, found := ops[e]; found {
		return op
	}
	return "UNRECOGNIZED OP"
}

// An Option is a type that is used to set options for a Watcher.
type Option int

const (
	// NonRecursive sets the watcher to not watch directories recursively.
	NonRecursive Option = 1 << iota

	// IgnoreDotFiles sets the watcher to ignore dot files.
	IgnoreDotFiles
)

// An Event desribes an event that is received when files or directory
// changes occur. It includes the os.FileInfo of the changed file or
// directory and the type of event that's occurred and the full path of the file.
type Event struct {
	Op
	Path string
	os.FileInfo
}

// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
	if e.FileInfo != nil {
		pathType := "FILE"
		if e.IsDir() {
			pathType = "DIRECTORY"
		}
		return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
	}
	return "INVALID EVENT"
}

// A Watcher describes a file watcher.
type Watcher struct {
	Event chan Event
	Error chan error

	options []Option

	mu        *sync.Mutex
	running   bool
	files     map[string]os.FileInfo
	ignored   map[string]struct{}
	names     []string
	maxEvents int
}

// New returns a new initialized *Watcher.
func New(options ...Option) *Watcher {
	return &Watcher{
		Event:   make(chan Event),
		Error:   make(chan error),
		options: options,
		mu:      new(sync.Mutex),
		files:   make(map[string]os.FileInfo),
		ignored: make(map[string]struct{}),
		names:   []string{},
	}
}

// Ignore adds paths that should be ignored by the watcher.
func (w *Watcher) Ignore(paths ...string) error {
	for _, path := range paths {
		if err := w.Remove(path); err != nil {
			return err
		}
		w.mu.Lock()
		w.ignored[path] = struct{}{}
		w.mu.Unlock()
	}
	return nil
}

// WatchedFiles returns a map of all the files being watched.
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
	w.mu.Lock()
	defer w.mu.Unlock()

	return w.files
}

// SetMaxEvents controls the maximum amount of events that are sent on
// the Event channel per watching cycle. If max events is less than 1, there is
// no limit, which is the default.
func (w *Watcher) SetMaxEvents(amount int) {
	w.mu.Lock()
	w.maxEvents = amount
	w.mu.Unlock()
}

// fileInfo is an implementation of os.FileInfo that can be used
// as a mocked os.FileInfo when triggering an event when the specified
// os.FileInfo is nil.
type fileInfo struct {
	name    string
	size    int64
	mode    os.FileMode
	modTime time.Time
	sys     interface{}
}

func (fs *fileInfo) IsDir() bool {
	return false
}
func (fs *fileInfo) ModTime() time.Time {
	return fs.modTime
}
func (fs *fileInfo) Mode() os.FileMode {
	return fs.mode
}
func (fs *fileInfo) Name() string {
	return fs.name
}
func (fs *fileInfo) Size() int64 {
	return fs.size
}
func (fs *fileInfo) Sys() interface{} {
	return fs.sys
}

// Add adds either a single file or recursed directory to
// the Watcher's file list.
func (w *Watcher) Add(name string) (err error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if name == "." || name == ".." {
		name, err = filepath.Abs(name)
		if err != nil {
			return err
		}
	}

	name = filepath.Clean(name)

	// Add the name from w's names list.
	w.names = append(w.names, name)

	// Make sure name exists.
	fInfo, err := os.Stat(name)
	if err != nil {
		return err
	}

	// If watching a single file that isn't in the ignored list,
	// add it and return.
	_, ignored := w.ignored[name]
	if !fInfo.IsDir() && !ignored {
		w.files[fInfo.Name()] = fInfo
		return nil
	}

	// Retrieve a list of all of the os.FileInfo's to add to w.files.
	fInfoList, err := ListFiles(name, w.ignored, w.options...)
	if err != nil {
		return err
	}
	for k, v := range fInfoList {
		w.files[k] = v
	}
	return nil
}

// Remove removes either a single file or recursed directory from
// the Watcher's file list.
func (w *Watcher) Remove(name string) (err error) {
	w.mu.Lock()
	defer w.mu.Unlock()

	if name == "." || name == ".." {
		name, err = filepath.Abs(name)
		if err != nil {
			return err
		}
	}

	name = filepath.Clean(name)

	// Remove the name from w's names list.
	for i := range w.names {
		if w.names[i] == name {
			w.names = append(w.names[:i], w.names[i+1:]...)
		}
	}

	// If name is a single file, remove it and return.
	info, found := w.files[name]
	if !found {
		return nil // Doesn't exist, just return
	}
	if !info.IsDir() {
		delete(w.files, name)
		return nil
	}

	// If it's a directory, delete all of it's contents from w.files.
	for path := range w.files {
		if strings.HasPrefix(path, name) {
			delete(w.files, path)
		}
	}
	return nil
}

// TriggerEvent is a method that can be used to trigger an event, separate to
// the file watching process.
func (w *Watcher) TriggerEvent(eventType Op, file os.FileInfo) {
	if file == nil {
		file = &fileInfo{name: "triggered event", modTime: time.Now()}
	}
	w.Event <- Event{Op: eventType, Path: "-", FileInfo: file}
}

type renamedFrom struct {
	path string
	os.FileInfo
}

// Close removes all watched files from the watcher and causes Start to
// stop watching and return after the current watching cycle finishes if
// one is currently running.
func (w *Watcher) Close() error {
	w.mu.Lock()
	if !w.running {
		w.mu.Unlock()
		return nil
	}
	w.running = false
	w.Error <- ErrWatcherClosed
	for k, _ := range w.files {
		delete(w.files, k)
	}
	w.names = []string{}
	w.mu.Unlock()
	return nil
}

// Start starts the watching process and checks for changes every `pollInterval` duration.
// If pollInterval is 0, the default is 100ms.
func (w *Watcher) Start(pollInterval time.Duration) error {
	if pollInterval <= 0 {
		pollInterval = time.Millisecond * 100
	}

	if len(w.names) < 1 {
		return ErrNothingAdded
	}

	w.mu.Lock()
	w.running = true
	w.mu.Unlock()

	for {
		w.mu.Lock()

		if !w.running {
			w.mu.Unlock()
			return nil
		}

		fileList := make(map[string]os.FileInfo)
		for _, name := range w.names {
			// Retrieve the list of os.FileInfo's from w.Name.
			list, err := ListFiles(name, w.ignored, w.options...)
			if err != nil {
				if os.IsNotExist(err) {
					w.Error <- ErrWatchedFileDeleted
					if err := w.Remove(name); err != nil {
						w.mu.Unlock()
						return err
					}
					continue
				} else {
					w.Error <- err
				}
			}
			for k, v := range list {
				fileList[k] = v
			}
		}

		numEvents := 0

		events := map[Op]map[string]os.FileInfo{
			Create: make(map[string]os.FileInfo),
			Remove: make(map[string]os.FileInfo),
		}

		renamed := make(map[string]renamedFrom)

		// Check for added files.
		for path, file := range fileList {
			if _, found := w.files[path]; !found {
				events[Create][path] = file
			}
		}

		// Check for removed files.
		for path, file := range w.files {
			if _, found := fileList[path]; !found {
				events[Remove][path] = file
			}
		}

		// Check for renamed files.
		for path1, file1 := range events[Create] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			for path2, file2 := range events[Remove] {
				renamed[path2] = renamedFrom{path1, file1}
				if os.SameFile(file1, file2) {
					w.Event <- Event{
						Op:       Rename,
						Path:     path2,
						FileInfo: file2,
					}
					numEvents++

					// Delete path1 from the added files map.
					delete(events[Create], path1)

					// Delete path2 from the deleted files map.
					delete(events[Remove], path2)
				}
			}
		}

		for path, file := range events[Create] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			w.Event <- Event{
				Op:       Create,
				Path:     path,
				FileInfo: file,
			}
			numEvents++
		}

		for path, file := range events[Remove] {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			w.Event <- Event{
				Op:       Remove,
				Path:     path,
				FileInfo: file,
			}
			numEvents++
		}

		// Check for modified and chmoded files.
		for path, file := range w.files {
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			_, addFound := events[Create][path]
			_, removeFound := events[Remove][path]
			renamedFrom, renameFound := renamed[path]
			if !addFound && !removeFound && !renameFound {
				if !file.IsDir() && fileList[path].ModTime() != file.ModTime() {
					w.Event <- Event{
						Op:       Write,
						Path:     path,
						FileInfo: file,
					}
					numEvents++
				}

				if w.maxEvents > 0 && numEvents >= w.maxEvents {
					goto SLEEP
				}
				if fileList[path].Mode() != file.Mode() {
					w.Event <- Event{
						Op:       Chmod,
						Path:     path,
						FileInfo: file,
					}
					numEvents++
				}
			}
			if w.maxEvents > 0 && numEvents >= w.maxEvents {
				goto SLEEP
			}
			if renameFound && renamedFrom.Mode() != file.Mode() {
				w.Event <- Event{
					Op:       Chmod,
					Path:     renamedFrom.path,
					FileInfo: renamedFrom.FileInfo,
				}
				numEvents++
			}
		}

	SLEEP:
		// Update w.files and then sleep for a little bit.
		w.files = fileList
		w.mu.Unlock()
		time.Sleep(pollInterval)
	}
}

// hasOption returns true or false based on whether or not
// an Option exists in an Option slice.
func hasOption(options []Option, option Option) bool {
	for _, o := range options {
		if option&o != 0 {
			return true
		}
	}
	return false
}

// ListFiles returns a map of all os.FileInfo's recursively
// contained in a directory. If name is a single file, it returns
// an os.FileInfo map containing a single os.FileInfo.
func ListFiles(name string, ignoredPaths map[string]struct{}, options ...Option) (map[string]os.FileInfo, error) {
	fileList := make(map[string]os.FileInfo)

	if _, ignored := ignoredPaths[name]; ignored {
		return nil, nil
	}

	name = filepath.Clean(name)

	nonRecursive := hasOption(options, NonRecursive)
	ignoreDotFiles := hasOption(options, IgnoreDotFiles)

	if nonRecursive {
		f, err := os.Open(name)
		if err != nil {
			return nil, err
		}
		defer f.Close()
		info, err := os.Stat(name)
		if err != nil {
			return nil, err
		}
		// Add the name to fileList.
		if !info.IsDir() && ignoreDotFiles && strings.HasPrefix(name, ".") {
			return fileList, nil
		}
		fileList[name] = info
		if !info.IsDir() {
			return fileList, nil
		}
		// It's a directory, read it's contents.
		fInfoList, err := f.Readdir(-1)
		if err != nil {
			return nil, err
		}
		// Add all of the FileInfo's returned from f.ReadDir to fileList.
		for _, fInfo := range fInfoList {
			if ignoreDotFiles && strings.HasPrefix(fInfo.Name(), ".") {
				continue
			}
			fileList[filepath.Join(name, fInfo.Name())] = fInfo
		}
		return fileList, nil
	}

	if err := filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		_, ignored := ignoredPaths[path]
		if ignored || (ignoreDotFiles && strings.HasPrefix(info.Name(), ".")) {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		fileList[path] = info

		return nil
	}); err != nil {
		return nil, err
	}

	return fileList, nil
}
<|endoftext|>


<commit_before>package behaviors

import (
	"testing"

	"github.com/workfit/tester/assert"
)

func TestRoundRobin(t *testing.T) {
	var b interface{}
	b = &RoundRobin{}
	_, ok := b.(Interface)
	assert.For(t).ThatActual(ok).IsTrue()
}

func TestCurrentPlayer(t *testing.T) {
	var b interface{}
	b = &CurrentPlayer{}
	_, ok := b.(Interface)
	assert.For(t).ThatActual(ok).IsTrue()
}
<commit_msg>Add tests that verify that behaviors.RoundRobin and CurrentPlayer implement the moves/interfaces they intend to implement, which will help detect drift if those interfaces change.<commit_after>package behaviors

import (
	"testing"

	"github.com/jkomoros/boardgame/moves/interfaces"

	"github.com/workfit/tester/assert"
)

func TestRoundRobin(t *testing.T) {
	var b interface{}
	b = &RoundRobin{}
	_, ok := b.(Interface)
	assert.For(t).ThatActual(ok).IsTrue()
	_, ok = b.(interfaces.RoundRobinProperties)
	assert.For(t).ThatActual(ok).IsTrue()
}

func TestCurrentPlayer(t *testing.T) {
	var b interface{}
	b = &CurrentPlayer{}
	_, ok := b.(Interface)
	assert.For(t).ThatActual(ok).IsTrue()
	_, ok = b.(interfaces.CurrentPlayerSetter)
	assert.For(t).ThatActual(ok).IsTrue()
}
<|endoftext|>


<commit_before>package action

import (
	"bytes"
	"io"
	"net"
	"net/url"

	"fmt"

	"errors"

	"github.com/catatsuy/isucon6-final/bench/fails"
	"github.com/catatsuy/isucon6-final/bench/http"
	"github.com/catatsuy/isucon6-final/bench/score"
	"github.com/catatsuy/isucon6-final/bench/session"
	"github.com/catatsuy/isucon6-final/bench/sse"
)

const (
	GetScore  = 1
	PostScore = 20
)

type Checker interface {
	Check(body io.Reader, l *fails.Logger) bool
	CheckStatus(status int, l *fails.Logger) bool
}

type CheckFunc func(body io.Reader, l *fails.Logger) bool

type StatusChecker struct {
	ExpectedStatus int
	CheckFunc      CheckFunc
}

func (sc StatusChecker) Check(body io.Reader, l *fails.Logger) bool {
	return sc.CheckFunc(body, l)
}

func (sc StatusChecker) CheckStatus(status int, l *fails.Logger) bool {
	if status != sc.ExpectedStatus {
		l.Add(fmt.Sprintf("ステータスが%dではありません: %d", sc.ExpectedStatus, status), nil)
		return false
	}
	return true
}

func OK(f CheckFunc) StatusChecker {
	return StatusChecker{
		ExpectedStatus: 200,
		CheckFunc:      f,
	}
}

func BadRequest(f CheckFunc) StatusChecker {
	return StatusChecker{
		ExpectedStatus: 400,
		CheckFunc:      f,
	}
}

func request(s *session.Session, method, path string, body io.Reader, headers map[string]string, c Checker) bool {
	l := &fails.Logger{Prefix: "[" + method + " " + path + "] "}

	u, err := url.Parse(path)
	if err != nil { //TODO: メッセージ
		return false
	}
	u.Scheme = s.Scheme
	u.Host = s.Host

	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		l.Critical("予期せぬ失敗です (主催者に連絡してください)", err)
		return false
	}

	req.Header.Set("User-Agent", s.UserAgent)
	if headers != nil {
		for key, val := range headers {
			req.Header.Set(key, val)
		}
	}

	res, err := s.Client.Do(req)

	if err != nil {
		if err, ok := err.(net.Error); ok && err.Timeout() {
			l.Add("リクエストがタイムアウトしました", err)
			return false
		}
		l.Add("リクエストが失敗しました", err)
		return false
	}
	defer res.Body.Close()

	ok := c.CheckStatus(res.StatusCode, l)
	if !ok {
		return false
	}

	return c.Check(res.Body, l)
}

func Get(s *session.Session, path string, c Checker) bool {
	ok := request(s, "GET", path, nil, nil, c)
	if ok {
		score.Increment(GetScore)
	}
	return ok
}

func Post(s *session.Session, path string, body []byte, headers map[string]string, c Checker) bool {
	ok := request(s, "POST", path, bytes.NewBuffer(body), headers, c)
	if ok {
		score.Increment(PostScore)
	}
	return ok
}

func SSE(s *session.Session, path string) (*sse.EventSource, bool) {
	u, err := url.Parse(path)
	if err != nil {
		fails.Add("予期せぬエラー（主催者に連絡してください）",
			errors.New("URLのパースに失敗しました: "+path+", error: "+err.Error()))
		return nil, false
	}
	u.Scheme = s.Scheme
	u.Host = s.Host

	es := sse.NewEventSource(s.Client, u.String())
	es.AddHeader("User-Agent", s.UserAgent)
	return es, true
}
<commit_msg>Fail with critical when URL parsing failed<commit_after>package action

import (
	"bytes"
	"io"
	"net"
	"net/url"

	"fmt"

	"errors"

	"github.com/catatsuy/isucon6-final/bench/fails"
	"github.com/catatsuy/isucon6-final/bench/http"
	"github.com/catatsuy/isucon6-final/bench/score"
	"github.com/catatsuy/isucon6-final/bench/session"
	"github.com/catatsuy/isucon6-final/bench/sse"
)

const (
	GetScore  = 1
	PostScore = 20
)

type Checker interface {
	Check(body io.Reader, l *fails.Logger) bool
	CheckStatus(status int, l *fails.Logger) bool
}

type CheckFunc func(body io.Reader, l *fails.Logger) bool

type StatusChecker struct {
	ExpectedStatus int
	CheckFunc      CheckFunc
}

func (sc StatusChecker) Check(body io.Reader, l *fails.Logger) bool {
	return sc.CheckFunc(body, l)
}

func (sc StatusChecker) CheckStatus(status int, l *fails.Logger) bool {
	if status != sc.ExpectedStatus {
		l.Add(fmt.Sprintf("ステータスが%dではありません: %d", sc.ExpectedStatus, status), nil)
		return false
	}
	return true
}

func OK(f CheckFunc) StatusChecker {
	return StatusChecker{
		ExpectedStatus: 200,
		CheckFunc:      f,
	}
}

func BadRequest(f CheckFunc) StatusChecker {
	return StatusChecker{
		ExpectedStatus: 400,
		CheckFunc:      f,
	}
}

func request(s *session.Session, method, path string, body io.Reader, headers map[string]string, c Checker) bool {
	l := &fails.Logger{Prefix: "[" + method + " " + path + "] "}

	u, err := url.Parse(path)
	if err != nil {
		l.Critical("予期せぬエラー（主催者に連絡してください）",
			errors.New("URLのパースに失敗しました: "+path+", error: "+err.Error()))
		return false
	}
	u.Scheme = s.Scheme
	u.Host = s.Host

	req, err := http.NewRequest(method, u.String(), body)
	if err != nil {
		l.Critical("予期せぬ失敗です (主催者に連絡してください)", err)
		return false
	}

	req.Header.Set("User-Agent", s.UserAgent)
	if headers != nil {
		for key, val := range headers {
			req.Header.Set(key, val)
		}
	}

	res, err := s.Client.Do(req)

	if err != nil {
		if err, ok := err.(net.Error); ok && err.Timeout() {
			l.Add("リクエストがタイムアウトしました", err)
			return false
		}
		l.Add("リクエストが失敗しました", err)
		return false
	}
	defer res.Body.Close()

	ok := c.CheckStatus(res.StatusCode, l)
	if !ok {
		return false
	}

	return c.Check(res.Body, l)
}

func Get(s *session.Session, path string, c Checker) bool {
	ok := request(s, "GET", path, nil, nil, c)
	if ok {
		score.Increment(GetScore)
	}
	return ok
}

func Post(s *session.Session, path string, body []byte, headers map[string]string, c Checker) bool {
	ok := request(s, "POST", path, bytes.NewBuffer(body), headers, c)
	if ok {
		score.Increment(PostScore)
	}
	return ok
}

func SSE(s *session.Session, path string) (*sse.EventSource, bool) {
	u, err := url.Parse(path)
	if err != nil {
		fails.Critical("予期せぬエラー（主催者に連絡してください）",
			errors.New("URLのパースに失敗しました: "+path+", error: "+err.Error()))
		return nil, false
	}
	u.Scheme = s.Scheme
	u.Host = s.Host

	es := sse.NewEventSource(s.Client, u.String())
	es.AddHeader("User-Agent", s.UserAgent)
	return es, true
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"text/template"

	"github.com/PuerkitoBio/goquery"
	"github.com/avelino/awesome-go/pkg/slug"
)

type Link struct {
	Title       string
	Url         string
	Description string
}

type Object struct {
	Title       string
	Slug        string
	Description string
	Items       []Link
}

func main() {
	err := GenerateHTML()
	if err != nil {
		panic(err)
	}
	input, err := os.ReadFile("./tmpl/index.html")
	if err != nil {
		panic(err)
	}
	buf := bytes.NewBuffer(input)
	query, err := goquery.NewDocumentFromReader(buf)
	if err != nil {
		panic(err)
	}

	objs := make(map[string]*Object)
	query.Find("body #content ul ul").First().Each(func(_ int, s *goquery.Selection) {
		s.Find("li a").Each(func(_ int, s *goquery.Selection) {
			selector, exists := s.Attr("href")
			if !exists {
				return
			}
			obj := makeObjById(selector, query.Find("body"))
			if obj == nil {
				return
			}
			objs[selector] = obj
		})
	})

	makeCategoryPages(objs)
	linkCategoryPagesInIndex(string(input), query, objs)

	makeSitemap(objs)
}

func makeCategoryPages(objs map[string]*Object) {
	for _, obj := range objs {
		folder := fmt.Sprintf("tmpl/%s", obj.Slug)
		err := os.Mkdir(folder, 0755)
		if err != nil {
			log.Println(err)
		}

		t := template.Must(template.ParseFiles("tmpl/cat-tmpl.html"))
		f, _ := os.Create(fmt.Sprintf("%s/index.html", folder))
		t.Execute(f, obj)
	}
}

func makeSitemap(objs map[string]*Object) {
	t := template.Must(template.ParseFiles("tmpl/sitemap-tmpl.xml"))
	f, _ := os.Create("tmpl/sitemap.xml")
	t.Execute(f, objs)
}

func makeObjById(selector string, s *goquery.Selection) (obj *Object) {
	s.Find(selector).Each(func(_ int, s *goquery.Selection) {
		desc := s.NextFiltered("p")
		ul := s.NextFilteredUntil("ul", "h2")

		links := []Link{}
		ul.Find("li").Each(func(_ int, s *goquery.Selection) {
			url, _ := s.Find("a").Attr("href")
			link := Link{
				Title:       s.Find("a").Text(),
				Description: s.Text(),
				Url:         url,
			}
			links = append(links, link)
		})
		obj = &Object{
			Slug:        slug.Generate(s.Text()),
			Title:       s.Text(),
			Description: desc.Text(),
			Items:       links,
		}
	})
	return
}

func linkCategoryPagesInIndex(html string, query *goquery.Document, objs map[string]*Object) {
	query.Find("body #content ul li ul li a").Each(func(_ int, s *goquery.Selection) {
		href, hrefExists := s.Attr("href")
		if !hrefExists {
			return
		}

		// do not replace links if no page has been created for it
		_, objExists := objs[href]
		if !objExists {
			return
		}

		uri := strings.SplitAfter(href, "#")
		if len(uri) >= 2 && uri[1] != "contents" {
			html = strings.ReplaceAll(
				html, fmt.Sprintf(`href="%s"`, href), fmt.Sprintf(`href="%s"`, uri[1]))
		}
	})

	os.WriteFile("./tmpl/index.html", []byte(html), 0644)
}
<commit_msg>- include "Resources" in category page rendering<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strings"
	"text/template"

	"github.com/PuerkitoBio/goquery"
	"github.com/avelino/awesome-go/pkg/slug"
)

type Link struct {
	Title       string
	Url         string
	Description string
}

type Object struct {
	Title       string
	Slug        string
	Description string
	Items       []Link
}

func main() {
	err := GenerateHTML()
	if err != nil {
		panic(err)
	}
	input, err := os.ReadFile("./tmpl/index.html")
	if err != nil {
		panic(err)
	}
	buf := bytes.NewBuffer(input)
	query, err := goquery.NewDocumentFromReader(buf)
	if err != nil {
		panic(err)
	}

	objs := make(map[string]*Object)
	query.Find("body #contents").NextFiltered("ul").Find("ul").Each(func(_ int, s *goquery.Selection) {
		s.Find("li a").Each(func(_ int, s *goquery.Selection) {
			selector, exists := s.Attr("href")
			if !exists {
				return
			}
			obj := makeObjById(selector, query.Find("body"))
			if obj == nil {
				return
			}
			objs[selector] = obj
		})
	})

	makeCategoryPages(objs)
	linkCategoryPagesInIndex(string(input), query, objs)

	makeSitemap(objs)
}

func makeCategoryPages(objs map[string]*Object) {
	for _, obj := range objs {
		folder := fmt.Sprintf("tmpl/%s", obj.Slug)
		err := os.Mkdir(folder, 0755)
		if err != nil {
			log.Println(err)
		}

		t := template.Must(template.ParseFiles("tmpl/cat-tmpl.html"))
		f, _ := os.Create(fmt.Sprintf("%s/index.html", folder))
		t.Execute(f, obj)
	}
}

func makeSitemap(objs map[string]*Object) {
	t := template.Must(template.ParseFiles("tmpl/sitemap-tmpl.xml"))
	f, _ := os.Create("tmpl/sitemap.xml")
	t.Execute(f, objs)
}

func makeObjById(selector string, s *goquery.Selection) (obj *Object) {
	s.Find(selector).Each(func(_ int, s *goquery.Selection) {
		desc := s.NextFiltered("p")
		ul := s.NextFilteredUntil("ul", "h2")

		links := []Link{}
		ul.Find("li").Each(func(_ int, s *goquery.Selection) {
			url, _ := s.Find("a").Attr("href")
			link := Link{
				Title:       s.Find("a").Text(),
				Description: s.Text(),
				Url:         url,
			}
			links = append(links, link)
		})
		obj = &Object{
			Slug:        slug.Generate(s.Text()),
			Title:       s.Text(),
			Description: desc.Text(),
			Items:       links,
		}
	})
	return
}

func linkCategoryPagesInIndex(html string, query *goquery.Document, objs map[string]*Object) {
	query.Find("body #content ul li ul li a").Each(func(_ int, s *goquery.Selection) {
		href, hrefExists := s.Attr("href")
		if !hrefExists {
			return
		}

		// do not replace links if no page has been created for it
		_, objExists := objs[href]
		if !objExists {
			return
		}

		uri := strings.SplitAfter(href, "#")
		if len(uri) >= 2 && uri[1] != "contents" {
			html = strings.ReplaceAll(
				html, fmt.Sprintf(`href="%s"`, href), fmt.Sprintf(`href="%s"`, uri[1]))
		}
	})

	os.WriteFile("./tmpl/index.html", []byte(html), 0644)
}
<|endoftext|>


<commit_before>package web

import (
	"strconv"
	"time"

	"github.com/go-iris2/iris2"
	"github.com/go-iris2/iris2/adaptors/sessions"
	"github.com/go-iris2/iris2/adaptors/sessions/sessiondb/file"
	"github.com/go-iris2/iris2/adaptors/view"
	"github.com/jinzhu/gorm"
	"github.com/rikvdh/ci/lib/auth"
	"github.com/rikvdh/ci/lib/builder"
	"github.com/rikvdh/ci/lib/config"
	"github.com/rikvdh/ci/models"
	"net/url"
	"strings"
)

func cleanReponame(remote string) string {
	u, _ := url.Parse(remote)
	return u.Hostname() + strings.Replace(u.Path, ".git", "", 1)
}

func getBranchAction(ctx *iris2.Context) {
	item := models.Branch{}
	id, err := ctx.ParamInt("id")
	if err != nil {
		ctx.Redirect(ctx.Referer())
		return
	}
	models.Handle().Preload("Jobs", func(db *gorm.DB) *gorm.DB {
		return db.Order("jobs.id DESC")
	}).Preload("Build").Where("id = ?", id).First(&item)

	item.Build.Uri = cleanReponame(item.Build.Uri)
	for k := range item.Jobs {
		item.Jobs[k].Reference = item.Jobs[k].Reference[:7]
		item.Jobs[k].SetStatusTime()
	}

	ctx.MustRender("branch.html", iris2.Map{"Page": "Branch " + item.Name + "(" + item.Build.Uri + ")", "Branch": item})
}

func getJobAction(ctx *iris2.Context) {
	item := models.Job{}
	id, err := ctx.ParamInt("id")
	if err != nil {
		ctx.Session().SetFlash("msg", "Invalid ID")
		ctx.Redirect(ctx.Referer())
		return
	}
	models.Handle().Preload("Branch").Preload("Build").Where("id = ?", id).First(&item)

	item.SetStatusTime()

	log := builder.GetLog(&item)
	item.Build.Uri = cleanReponame(item.Build.Uri)
	item.Reference = item.Reference[:7]
	ctx.MustRender("job.html", iris2.Map{
		"Page": "Job #" + strconv.Itoa(int(item.ID)) + "(" + item.Build.Uri + ")",
		"Job":  item,
		"Log":  log})
}

func homeAction(ctx *iris2.Context) {
	var builds []models.Build

	models.Handle().Find(&builds)
	for k := range builds {
		builds[k].Uri = cleanReponame(builds[k].Uri)
		builds[k].FetchLatestStatus()
	}
	ctx.MustRender("home.html", iris2.Map{"Page": "Home", "Builds": builds})
}

func Start() {
	http := iris2.New(iris2.Configuration{Gzip: false})
	http.Adapt(sessions.New(sessions.Config{
		Cookie:         "ci-session-id",
		Expires:        2 * time.Hour,
		SessionStorage: file.New("./tmp"),
	}))
	http.Adapt(view.HTML("./templates", ".html"))
	http.Layout("layout.html")
	http.StaticWeb("/public", "./public")

	http.Get("/login", loginAction)
	http.Post("/login", loginAction)

	http.Get("/register", registerAction)
	http.Post("/register", registerAction)

	http.Get("/logout", logoutAction)

	startWs(http)

	party := http.Party("", auth.New())
	{
		party.Get("/", homeAction)
		party.Get("/deletebuild/:id", deleteBuildAction)
		party.Get("/addbuild", addBuildAction)
		party.Post("/addbuild", addBuildAction)
		party.Get("/build/:id", getBuildAction)
		party.Get("/branch/:id", getBranchAction)
		party.Get("/job/:id", getJobAction)
	}

	http.Listen(config.Get().ListeningURI)
}
<commit_msg>Sort build by last updated first<commit_after>package web

import (
	"strconv"
	"time"

	"github.com/go-iris2/iris2"
	"github.com/go-iris2/iris2/adaptors/sessions"
	"github.com/go-iris2/iris2/adaptors/sessions/sessiondb/file"
	"github.com/go-iris2/iris2/adaptors/view"
	"github.com/jinzhu/gorm"
	"github.com/rikvdh/ci/lib/auth"
	"github.com/rikvdh/ci/lib/builder"
	"github.com/rikvdh/ci/lib/config"
	"github.com/rikvdh/ci/models"
	"net/url"
	"strings"
)

func cleanReponame(remote string) string {
	u, _ := url.Parse(remote)
	return u.Hostname() + strings.Replace(u.Path, ".git", "", 1)
}

func getBranchAction(ctx *iris2.Context) {
	item := models.Branch{}
	id, err := ctx.ParamInt("id")
	if err != nil {
		ctx.Redirect(ctx.Referer())
		return
	}
	models.Handle().Preload("Jobs", func(db *gorm.DB) *gorm.DB {
		return db.Order("jobs.id DESC")
	}).Preload("Build").Where("id = ?", id).First(&item)

	item.Build.Uri = cleanReponame(item.Build.Uri)
	for k := range item.Jobs {
		item.Jobs[k].Reference = item.Jobs[k].Reference[:7]
		item.Jobs[k].SetStatusTime()
	}

	ctx.MustRender("branch.html", iris2.Map{"Page": "Branch " + item.Name + "(" + item.Build.Uri + ")", "Branch": item})
}

func getJobAction(ctx *iris2.Context) {
	item := models.Job{}
	id, err := ctx.ParamInt("id")
	if err != nil {
		ctx.Session().SetFlash("msg", "Invalid ID")
		ctx.Redirect(ctx.Referer())
		return
	}
	models.Handle().Preload("Branch").Preload("Build").Where("id = ?", id).First(&item)

	item.SetStatusTime()

	log := builder.GetLog(&item)
	item.Build.Uri = cleanReponame(item.Build.Uri)
	item.Reference = item.Reference[:7]
	ctx.MustRender("job.html", iris2.Map{
		"Page": "Job #" + strconv.Itoa(int(item.ID)) + "(" + item.Build.Uri + ")",
		"Job":  item,
		"Log":  log})
}

func homeAction(ctx *iris2.Context) {
	var builds []models.Build

	models.Handle().Order("updated_at DESC").Find(&builds)
	for k := range builds {
		builds[k].Uri = cleanReponame(builds[k].Uri)
		builds[k].FetchLatestStatus()
	}
	ctx.MustRender("home.html", iris2.Map{"Page": "Home", "Builds": builds})
}

func Start() {
	http := iris2.New(iris2.Configuration{Gzip: false})
	http.Adapt(sessions.New(sessions.Config{
		Cookie:         "ci-session-id",
		Expires:        2 * time.Hour,
		SessionStorage: file.New("./tmp"),
	}))
	http.Adapt(view.HTML("./templates", ".html"))
	http.Layout("layout.html")
	http.StaticWeb("/public", "./public")

	http.Get("/login", loginAction)
	http.Post("/login", loginAction)

	http.Get("/register", registerAction)
	http.Post("/register", registerAction)

	http.Get("/logout", logoutAction)

	startWs(http)

	party := http.Party("", auth.New())
	{
		party.Get("/", homeAction)
		party.Get("/deletebuild/:id", deleteBuildAction)
		party.Get("/addbuild", addBuildAction)
		party.Post("/addbuild", addBuildAction)
		party.Get("/build/:id", getBuildAction)
		party.Get("/branch/:id", getBranchAction)
		party.Get("/job/:id", getJobAction)
	}

	http.Listen(config.Get().ListeningURI)
}
<|endoftext|>


<commit_before>// Copyright (c) 2020, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the
//    names of its contributors may be used to endorse or promote products
//    derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package web

import (
	"fmt"
	"os/exec"
	"runtime"

	"github.com/openthread/ot-ns/progctx"

	"github.com/simonlingoogle/go-simplelogger"
)

var (
	grpcWebProxyProc *exec.Cmd

	grpcWebProxyParams struct {
		serverBindAddress   string
		serverHttpDebugPort int
		grpcServicePort     int
		webSitePort         int
	}
)

func ConfigWeb(serverBindAddress string, serverHttpDebugPort int, grpcServicePort int, webSitePort int) {
	grpcWebProxyParams.serverBindAddress = serverBindAddress
	grpcWebProxyParams.serverHttpDebugPort = serverHttpDebugPort
	grpcWebProxyParams.grpcServicePort = grpcServicePort
	grpcWebProxyParams.webSitePort = webSitePort
	simplelogger.Debugf("ConfigWeb: %+v", grpcWebProxyParams)
}

func OpenWeb(ctx *progctx.ProgCtx) error {
	if err := assureGrpcWebProxyRunning(ctx); err != nil {
		simplelogger.Errorf("start grpcwebproxy failed: %v", err)
		simplelogger.Errorf("Web visualization is unusable. Please make sure grpcwebproxy is installed.")
		return err
	}

	return openWebBrowser(fmt.Sprintf("http://localhost:%d/visualize?addr=localhost:%d", grpcWebProxyParams.webSitePort, grpcWebProxyParams.serverHttpDebugPort))
}

// open opens the specified URL in the default browser of the user.
func openWebBrowser(url string) error {
	var cmd string
	var args []string

	switch runtime.GOOS {
	case "windows":
		cmd = "cmd"
		args = []string{"/c", "start"}
	case "darwin":
		cmd = "open"
	default: // "linux", "freebsd", "openbsd", "netbsd"
		cmd = "xdg-open"
	}

	args = append(args, url)
	return exec.Command(cmd, args...).Start()
}

func assureGrpcWebProxyRunning(ctx *progctx.ProgCtx) error {
	if grpcWebProxyProc == nil {
		proc := exec.CommandContext(ctx, "grpcwebproxy", []string{
			fmt.Sprintf("--backend_addr=localhost:%d", grpcWebProxyParams.grpcServicePort),
			"--run_tls_server=false",
			"--allow_all_origins",
			"--server_http_max_read_timeout=1h",
			"--server_http_max_write_timeout=1h",
			fmt.Sprintf("--server_bind_address=%s", grpcWebProxyParams.serverBindAddress),
			fmt.Sprintf("--server_http_debug_port=%d", grpcWebProxyParams.serverHttpDebugPort),
		}...)

		if err := proc.Start(); err != nil {
			return err
		}

		grpcWebProxyProc = proc
		ctx.WaitAdd("grpcwebproxy", 1)
		go func() {
			defer ctx.WaitDone("grpcwebproxy")

			err := grpcWebProxyProc.Wait()
			if err != nil && ctx.Err() == nil {
				simplelogger.Errorf("grpcwebproxy exit unexpectedly: %v", err)
			}
		}()
		simplelogger.Infof("grpcwebproxy started.")
	}

	return nil
}
<commit_msg>[grpcwebproxy] kills existing process (#73)<commit_after>// Copyright (c) 2020, The OTNS Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the
//    names of its contributors may be used to endorse or promote products
//    derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package web

import (
	"fmt"
	"os/exec"
	"runtime"

	"github.com/openthread/ot-ns/progctx"

	"github.com/simonlingoogle/go-simplelogger"
)

var (
	grpcWebProxyProc *exec.Cmd

	grpcWebProxyParams struct {
		serverBindAddress   string
		serverHttpDebugPort int
		grpcServicePort     int
		webSitePort         int
	}
)

func ConfigWeb(serverBindAddress string, serverHttpDebugPort int, grpcServicePort int, webSitePort int) {
	grpcWebProxyParams.serverBindAddress = serverBindAddress
	grpcWebProxyParams.serverHttpDebugPort = serverHttpDebugPort
	grpcWebProxyParams.grpcServicePort = grpcServicePort
	grpcWebProxyParams.webSitePort = webSitePort
	simplelogger.Debugf("ConfigWeb: %+v", grpcWebProxyParams)
}

func OpenWeb(ctx *progctx.ProgCtx) error {
	if err := assureGrpcWebProxyRunning(ctx); err != nil {
		simplelogger.Errorf("start grpcwebproxy failed: %v", err)
		simplelogger.Errorf("Web visualization is unusable. Please make sure grpcwebproxy is installed.")
		return err
	}

	return openWebBrowser(fmt.Sprintf("http://localhost:%d/visualize?addr=localhost:%d", grpcWebProxyParams.webSitePort, grpcWebProxyParams.serverHttpDebugPort))
}

// open opens the specified URL in the default browser of the user.
func openWebBrowser(url string) error {
	var cmd string
	var args []string

	switch runtime.GOOS {
	case "windows":
		cmd = "cmd"
		args = []string{"/c", "start"}
	case "darwin":
		cmd = "open"
	default: // "linux", "freebsd", "openbsd", "netbsd"
		cmd = "xdg-open"
	}

	args = append(args, url)
	return exec.Command(cmd, args...).Start()
}

func startGrpcWebProxy(ctx *progctx.ProgCtx) error {
	grpcWebProxyProc = exec.CommandContext(ctx, "grpcwebproxy", []string{
		fmt.Sprintf("--backend_addr=localhost:%d", grpcWebProxyParams.grpcServicePort),
		"--run_tls_server=false",
		"--allow_all_origins",
		"--server_http_max_read_timeout=1h",
		"--server_http_max_write_timeout=1h",
		fmt.Sprintf("--server_bind_address=%s", grpcWebProxyParams.serverBindAddress),
		fmt.Sprintf("--server_http_debug_port=%d", grpcWebProxyParams.serverHttpDebugPort),
	}...)

	return grpcWebProxyProc.Start()
}

func assureGrpcWebProxyRunning(ctx *progctx.ProgCtx) error {
	if grpcWebProxyProc == nil {
		if err := startGrpcWebProxy(ctx); err != nil {
			return err
		}

		simplelogger.Infof("grpcwebproxy started.")

		ctx.WaitAdd("grpcwebproxy", 1)
		go func() {
			defer ctx.WaitDone("grpcwebproxy")

			err := grpcWebProxyProc.Wait()
			if err != nil && ctx.Err() == nil {
				simplelogger.Warnf("grpcwebproxy exit unexpectedly: %v, try restarting ...", err)

				tryKillExistingGrpcWebProxyProcess()

				if err = startGrpcWebProxy(ctx); err != nil {
					simplelogger.Errorf("grpcwebproxy restart failed: %v", err)
					return
				}

				simplelogger.Infof("grpcwebproxy restarted.")
				err = grpcWebProxyProc.Wait()

				if err != nil && ctx.Err() == nil {
					simplelogger.Errorf("grpcwebproxy exit unexpectedly: %v", err)
					simplelogger.Errorf("Web visualization might not be working properly!")
				}
			}
		}()
	}

	return nil
}

func tryKillExistingGrpcWebProxyProcess() {
	var err error

	defer func() {
		if err != nil {
			simplelogger.Warnf("Kill existing grpcwebproxy process failed: %v", err)
		}
	}()

	pattern := fmt.Sprintf("grpcwebproxy.*--server_http_debug_port=%d", grpcWebProxyParams.serverHttpDebugPort)
	cmd := exec.Command("pkill", "-f", pattern)
	if err = cmd.Start(); err != nil {
		simplelogger.Errorf("pkill grpcwebproxy failed: %v", err)
		return
	}

	if err = cmd.Wait(); err != nil || !cmd.ProcessState.Success() {
		simplelogger.Errorf("pkill grpcwebproxy failed: %s", cmd.ProcessState)
	}
}
<|endoftext|>


<commit_before>package web

import (
	"code.google.com/p/go.net/websocket"
	"fmt"
	"github.com/coreos/go-raft"
	"html/template"
	"net/http"
	//"time"
)

var s *raft.Server
var mainTempl *template.Template

type MainPage struct {
	Leader  string
	Address string
}

func mainHandler(c http.ResponseWriter, req *http.Request) {

	p := &MainPage{Leader: s.Leader(),
		Address: s.Name()}

	mainTempl.Execute(c, p)
}

func Start(server *raft.Server, port int) {
	mainTempl = template.Must(template.ParseFiles("home.html"))
	s = server

	go h.run()
	http.HandleFunc("/", mainHandler)
	http.Handle("/ws", websocket.Handler(wsHandler))

	//http.HandleFunc("/", handler)
	fmt.Println("web listening at port ", port)
	http.ListenAndServe(fmt.Sprintf(":%v", port), nil)
}
<commit_msg>clean web<commit_after>package web

import (
	"code.google.com/p/go.net/websocket"
	"fmt"
	"github.com/coreos/go-raft"
	"html/template"
	"net/http"
)

var s *raft.Server
var mainTempl *template.Template

type MainPage struct {
	Leader  string
	Address string
}

func mainHandler(c http.ResponseWriter, req *http.Request) {

	p := &MainPage{Leader: s.Leader(),
		Address: s.Name()}

	mainTempl.Execute(c, p)
}

func Start(server *raft.Server, port int) {
	mainTempl = template.Must(template.ParseFiles("web/index.html"))
	s = server

	go h.run()
	http.HandleFunc("/", mainHandler)
	http.Handle("/ws", websocket.Handler(wsHandler))

	fmt.Println("web listening at port ", port)
	http.ListenAndServe(fmt.Sprintf(":%v", port), nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/gorilla/schema"
	"net/http"
)

type Hasher struct {
	Query  string //query
	Format string //format of hash
}

var hasher = new(Hasher)
var decoder = schema.NewDecoder()

func MyHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		panic(err)
	}
	err = decoder.Decode(hasher, r.Form)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(w, ""+"Query: %s"+"
"+"Format: %s"+"", hasher.Query, hasher.Format)
}

func InputHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, InputForm)
}

const InputForm = `

<form method="GET" action="/hash">

Type the text you want to convert:
<input type="text" name="Query" />

<select name="Format">
<option value="md5">MD5</option>
<option value="sha1">SHA1</option>
<option value="sha256">SHA256</option>
</select>
<button type="submit">Go</button>
</form>

`

func main() {
	http.HandleFunc("/", InputHandler)
	http.HandleFunc("/hash", MyHandler)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>Adds custom names for struct fields populated with url values<commit_after>package main

import (
	"fmt"
	"github.com/gorilla/schema"
	"net/http"
)

type Hasher struct {
	Query  string `schema:"q"`      //query
	Format string `schema:"format"` //format of hash
}

var hasher = new(Hasher)
var decoder = schema.NewDecoder()

func MyHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		panic(err)
	}
	err = decoder.Decode(hasher, r.Form)
	if err != nil {
		panic(err)
	}
	fmt.Fprintf(w, ""+"Query: %s"+"
"+"Format: %s"+"", hasher.Query, hasher.Format)
}

func InputHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, InputForm)
}

const InputForm = `

<form method="GET" action="/hash">

Type the text you want to convert:
<input type="text" name="q" />

<select name="format">
<option value="md5">MD5</option>
<option value="sha1">SHA1</option>
<option value="sha256">SHA256</option>
</select>
<button type="submit">Go</button>
</form>

`

func main() {
	http.HandleFunc("/", InputHandler)
	http.HandleFunc("/hash", MyHandler)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package mailgun

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"
)

const MAX_FORM_SIZE = 2 * 1024 * 1024

type Event struct {
	name   string
	time   time.Time
	rcpt   string
	msgid  string
	reason string
}

func (e *Event) Name() string    { return e.name }
func (e *Event) Time() time.Time { return e.time }
func (e *Event) Rcpt() string    { return e.rcpt }
func (e *Event) MsgId() string   { return e.msgid }
func (e *Event) Reason() string  { return e.reason }

var (
	ErrMethodNotAllowed = fmt.Errorf("method not allowed")
	ErrBadSignature     = fmt.Errorf("bad signature")
	ErrInvalidTimestamp = fmt.Errorf("invalid timestamp")
	ErrInvalidForm      = fmt.Errorf("invalid form data")
)

type Webhook struct {
	key string
}

func NewWebhook(key string) *Webhook {
	return &Webhook{key}
}

func (wh *Webhook) Handle(w http.ResponseWriter, req *http.Request) (evt *Event, err error) {
	if req.Method != "POST" {
		err = ErrMethodNotAllowed
		w.Header().Set("Allow", "POST")
		http.Error(w, "only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	if err = req.ParseMultipartForm(MAX_FORM_SIZE); err != nil {
		err = ErrInvalidForm
		http.Error(w, "invalid form", http.StatusBadRequest)
		return
	}

	ts := req.Form.Get("timestamp")
	if !wh.Verify(ts, req.Form.Get("token"), req.Form.Get("signature")) {
		err = ErrBadSignature
		http.Error(w, "bad signature", http.StatusForbidden)
		return
	}

	unix, err := strconv.ParseInt(ts, 10, 64) // second since Unix epoch
	if err != nil {
		err = ErrInvalidTimestamp
		http.Error(w, "invalid timestamp", http.StatusBadRequest)
		return
	}
	evt = &Event{
		time:   time.Unix(unix, 0),
		name:   req.Form.Get("event"),
		rcpt:   req.Form.Get("recipient"),
		msgid:  req.Form.Get("Message-Id"),
		reason: req.Form.Get("reason"),
	}
	switch evt.name {
	case "bounced":
		evt.reason = fmt.Sprintf("%s %s %s", req.Form.Get("code"), req.Form.Get("error"), req.Form.Get("notification"))
	case "dropped":
		evt.reason = fmt.Sprintf("%s %s", req.Form.Get("reason"), req.Form.Get("description"))
	}
	return
}

func (wh *Webhook) Verify(timestamp, token, signature string) bool {
	h := hmac.New(sha256.New, []byte(wh.key))
	io.WriteString(h, timestamp)
	io.WriteString(h, token)
	return signature == hex.EncodeToString(h.Sum(nil))
}
<commit_msg>Increased security of webhook verification<commit_after>package mailgun

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"
)

const MAX_FORM_SIZE = 2 * 1024 * 1024

type Event struct {
	name   string
	time   time.Time
	rcpt   string
	msgid  string
	reason string
}

func (e *Event) Name() string    { return e.name }
func (e *Event) Time() time.Time { return e.time }
func (e *Event) Rcpt() string    { return e.rcpt }
func (e *Event) MsgId() string   { return e.msgid }
func (e *Event) Reason() string  { return e.reason }

var (
	ErrMethodNotAllowed = fmt.Errorf("method not allowed")
	ErrBadSignature     = fmt.Errorf("bad signature")
	ErrInvalidTimestamp = fmt.Errorf("invalid timestamp")
	ErrInvalidForm      = fmt.Errorf("invalid form data")
)

type Webhook struct {
	key string
}

func NewWebhook(key string) *Webhook {
	return &Webhook{key}
}

func (wh *Webhook) Handle(w http.ResponseWriter, req *http.Request) (evt *Event, err error) {
	if req.Method != "POST" {
		err = ErrMethodNotAllowed
		w.Header().Set("Allow", "POST")
		http.Error(w, "only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	if err = req.ParseMultipartForm(MAX_FORM_SIZE); err != nil {
		err = ErrInvalidForm
		http.Error(w, "invalid form", http.StatusBadRequest)
		return
	}

	ts := req.Form.Get("timestamp")
	if !wh.Verify(ts, req.Form.Get("token"), req.Form.Get("signature")) {
		err = ErrBadSignature
		http.Error(w, "bad signature", http.StatusForbidden)
		return
	}

	unix, err := strconv.ParseInt(ts, 10, 64) // second since Unix epoch
	if err != nil {
		err = ErrInvalidTimestamp
		http.Error(w, "invalid timestamp", http.StatusBadRequest)
		return
	}
	evt = &Event{
		time:   time.Unix(unix, 0),
		name:   req.Form.Get("event"),
		rcpt:   req.Form.Get("recipient"),
		msgid:  req.Form.Get("Message-Id"),
		reason: req.Form.Get("reason"),
	}
	switch evt.name {
	case "bounced":
		evt.reason = fmt.Sprintf("%s %s %s", req.Form.Get("code"), req.Form.Get("error"), req.Form.Get("notification"))
	case "dropped":
		evt.reason = fmt.Sprintf("%s %s", req.Form.Get("reason"), req.Form.Get("description"))
	}
	return
}

func (wh *Webhook) Verify(timestamp, token, signature string) bool {
	h := hmac.New(sha256.New, []byte(wh.key))
	io.WriteString(h, timestamp)
	io.WriteString(h, token)
	calcSig := h.Sum(nil)
	sig, err := hex.DecodeString(signature)
	if err != nil {
		return false
	}
	if len(sig) != len(calcSig) {
		return false
	}
	return subtle.ConstantTimeCompare(sig, calcSig) == 1
}
<|endoftext|>