<commit_before>package goblin

import (
	"flag"
	"fmt"
	"regexp"
	"runtime"
	"sync"
	"testing"
	"time"
)

type Done func(error ...interface{})

type Runnable interface {
	run(*G) bool
}

type Itable interface {
	run(*G) bool
	failed(string, []string)
}

func (g *G) Describe(name string, h func()) {
	d := &Describe{name: name, h: h, parent: g.parent}

	if d.parent != nil {
		d.parent.children = append(d.parent.children, Runnable(d))
	}

	g.parent = d

	h()

	g.parent = d.parent

	if g.parent == nil && d.hasTests {
		g.reporter.Begin()
		if d.run(g) {
			g.t.Fail()
		}
		g.reporter.End()
	}
}

func (g *G) Timeout(time time.Duration) {
	g.timeout = time
	g.timer.Reset(time)
}

type Describe struct {
	name           string
	h              func()
	children       []Runnable
	befores        []func()
	afters         []func()
	afterEach      []func()
	beforeEach     []func()
	justBeforeEach []func()
	hasTests       bool
	parent         *Describe
}

func (d *Describe) runBeforeEach() {
	if d.parent != nil {
		d.parent.runBeforeEach()
	}

	for _, b := range d.beforeEach {
		b()
	}
}

func (d *Describe) runJustBeforeEach() {
	if d.parent != nil {
		d.parent.runJustBeforeEach()
	}

	for _, b := range d.justBeforeEach {
		b()
	}
}

func (d *Describe) runAfterEach() {

	if d.parent != nil {
		d.parent.runAfterEach()
	}

	for _, a := range d.afterEach {
		a()
	}
}

func (d *Describe) run(g *G) bool {
	failed := false
	if d.hasTests {
		g.reporter.BeginDescribe(d.name)

		for _, b := range d.befores {
			b()
		}

		for _, r := range d.children {
			if r.run(g) {
				failed = true
			}
		}

		for _, a := range d.afters {
			a()
		}

		g.reporter.EndDescribe()
	}

	return failed
}

type Failure struct {
	Stack    []string
	TestName string
	Message  string
}

type It struct {
	h        interface{}
	name     string
	parent   *Describe
	failure  *Failure
	reporter Reporter
	isAsync  bool
}

func (it *It) run(g *G) bool {
	g.currentIt = it

	if it.h == nil {
		g.reporter.ItIsPending(it.name)
		return false
	}
	//TODO: should handle errors for beforeEach
	it.parent.runBeforeEach()

	it.parent.runJustBeforeEach()

	runIt(g, it.h)

	it.parent.runAfterEach()

	failed := false
	if it.failure != nil {
		failed = true
	}

	if failed {
		g.reporter.ItFailed(it.name)
		g.reporter.Failure(it.failure)
	} else {
		g.reporter.ItPassed(it.name)
	}
	return failed
}

func (it *It) failed(msg string, stack []string) {
	it.failure = &Failure{Stack: stack, Message: msg, TestName: it.parent.name + " " + it.name}
}

type Xit struct {
	h        interface{}
	name     string
	parent   *Describe
	failure  *Failure
	reporter Reporter
	isAsync  bool
}

func (xit *Xit) run(g *G) bool {
	g.currentIt = xit

	g.reporter.ItIsExcluded(xit.name)
	return false
}

func (xit *Xit) failed(msg string, stack []string) {
	xit.failure = nil
}

func parseFlags() {
	//Flag parsing
	flag.Parse()
	if *regexParam != "" {
		runRegex = regexp.MustCompile(*regexParam)
	} else {
		runRegex = nil
	}
}

var timeout = flag.Duration("goblin.timeout", 5*time.Second, "Sets default timeouts for all tests")
var isTty = flag.Bool("goblin.tty", true, "Sets the default output format (color / monochrome)")
var regexParam = flag.String("goblin.run", "", "Runs only tests which match the supplied regex")
var runRegex *regexp.Regexp

func Goblin(t *testing.T, arguments ...string) *G {
	parseFlags()
	g := &G{t: t, timeout: *timeout}
	var fancy TextFancier
	if *isTty {
		fancy = &TerminalFancier{}
	} else {
		fancy = &Monochrome{}
	}

	g.reporter = Reporter(&DetailedReporter{fancy: fancy})
	return g
}

func runIt(g *G, h interface{}) {
	defer timeTrack(time.Now(), g)
	g.mutex.Lock()
	g.timedOut = false
	g.mutex.Unlock()
	g.timer = time.NewTimer(g.timeout)
	g.shouldContinue = make(chan bool)
	if call, ok := h.(func()); ok {
		// the test is synchronous
		go func(c chan bool) { call(); c <- true }(g.shouldContinue)
	} else if call, ok := h.(func(Done)); ok {
		doneCalled := 0
		go func(c chan bool) {
			call(func(msg ...interface{}) {
				if len(msg) > 0 {
					g.Fail(msg)
				} else {
					doneCalled++
					if doneCalled > 1 {
						g.Fail("Done called multiple times")
					}
					c <- true
				}
			})
		}(g.shouldContinue)
	} else {
		panic("Not implemented.")
	}
	select {
	case <-g.shouldContinue:
	case <-g.timer.C:
		//Set to nil as it shouldn't continue
		g.shouldContinue = nil
		g.timedOut = true
		g.Fail("Test exceeded " + fmt.Sprintf("%s", g.timeout))
	}
	// Reset timeout value
	g.timeout = *timeout
}

type G struct {
	t              *testing.T
	parent         *Describe
	currentIt      Itable
	timeout        time.Duration
	reporter       Reporter
	timedOut       bool
	shouldContinue chan bool
	mutex          sync.Mutex
	timer          *time.Timer
}

func (g *G) SetReporter(r Reporter) {
	g.reporter = r
}

func (g *G) It(name string, h ...interface{}) {
	if matchesRegex(name) {
		it := &It{name: name, parent: g.parent, reporter: g.reporter}
		notifyParents(g.parent)
		if len(h) > 0 {
			it.h = h[0]
		}
		g.parent.children = append(g.parent.children, Runnable(it))
	}
}

func (g *G) Xit(name string, h ...interface{}) {
	if matchesRegex(name) {
		xit := &Xit{name: name, parent: g.parent, reporter: g.reporter}
		notifyParents(g.parent)
		if len(h) > 0 {
			xit.h = h[0]
		}
		g.parent.children = append(g.parent.children, Runnable(xit))
	}
}

func matchesRegex(value string) bool {
	if runRegex != nil {
		return runRegex.MatchString(value)
	}
	return true
}

func notifyParents(d *Describe) {
	d.hasTests = true
	if d.parent != nil {
		notifyParents(d.parent)
	}
}

func (g *G) Before(h func()) {
	g.parent.befores = append(g.parent.befores, h)
}

func (g *G) BeforeEach(h func()) {
	g.parent.beforeEach = append(g.parent.beforeEach, h)
}

func (g *G) JustBeforeEach(h func()) {
	g.parent.justBeforeEach = append(g.parent.justBeforeEach, h)
}

func (g *G) After(h func()) {
	g.parent.afters = append(g.parent.afters, h)
}

func (g *G) AfterEach(h func()) {
	g.parent.afterEach = append(g.parent.afterEach, h)
}

func (g *G) Assert(src interface{}) *Assertion {
	return &Assertion{src: src, fail: g.Fail}
}

func timeTrack(start time.Time, g *G) {
	g.reporter.ItTook(time.Since(start))
}

func (g *G) Fail(error interface{}) {
	//Skips 7 stacks due to the functions between the stack and the test
	stack := ResolveStack(7)
	message := fmt.Sprintf("%v", error)
	g.currentIt.failed(message, stack)
	if g.shouldContinue != nil {
		g.shouldContinue <- true
	}
	g.mutex.Lock()
	defer g.mutex.Unlock()
	if !g.timedOut {
		//Stop test function execution
		runtime.Goexit()
	}
}
<commit_msg>improve error handling when library use is not correct (#76)<commit_after>package goblin

import (
	"flag"
	"fmt"
	"regexp"
	"runtime"
	"sync"
	"testing"
	"time"
)

type Done func(error ...interface{})

type Runnable interface {
	run(*G) bool
}

type Itable interface {
	run(*G) bool
	failed(string, []string)
}

func (g *G) Describe(name string, h func()) {
	d := &Describe{name: name, h: h, parent: g.parent}

	if d.parent != nil {
		d.parent.children = append(d.parent.children, Runnable(d))
	}

	g.parent = d

	h()

	g.parent = d.parent

	if g.parent == nil && d.hasTests {
		g.reporter.Begin()
		if d.run(g) {
			g.t.Fail()
		}
		g.reporter.End()
	}
}

func (g *G) Timeout(time time.Duration) {
	g.timeout = time
	g.timer.Reset(time)
}

type Describe struct {
	name           string
	h              func()
	children       []Runnable
	befores        []func()
	afters         []func()
	afterEach      []func()
	beforeEach     []func()
	justBeforeEach []func()
	hasTests       bool
	parent         *Describe
}

func (d *Describe) runBeforeEach() {
	if d.parent != nil {
		d.parent.runBeforeEach()
	}

	for _, b := range d.beforeEach {
		b()
	}
}

func (d *Describe) runJustBeforeEach() {
	if d.parent != nil {
		d.parent.runJustBeforeEach()
	}

	for _, b := range d.justBeforeEach {
		b()
	}
}

func (d *Describe) runAfterEach() {

	if d.parent != nil {
		d.parent.runAfterEach()
	}

	for _, a := range d.afterEach {
		a()
	}
}

func (d *Describe) run(g *G) bool {
	failed := false
	if d.hasTests {
		g.reporter.BeginDescribe(d.name)

		for _, b := range d.befores {
			b()
		}

		for _, r := range d.children {
			if r.run(g) {
				failed = true
			}
		}

		for _, a := range d.afters {
			a()
		}

		g.reporter.EndDescribe()
	}

	return failed
}

type Failure struct {
	Stack    []string
	TestName string
	Message  string
}

type It struct {
	h        interface{}
	name     string
	parent   *Describe
	failure  *Failure
	reporter Reporter
	isAsync  bool
}

func (it *It) run(g *G) bool {
	g.currentIt = it

	if it.h == nil {
		g.reporter.ItIsPending(it.name)
		return false
	}
	//TODO: should handle errors for beforeEach
	it.parent.runBeforeEach()

	it.parent.runJustBeforeEach()

	runIt(g, it.h)

	it.parent.runAfterEach()

	failed := false
	if it.failure != nil {
		failed = true
	}

	if failed {
		g.reporter.ItFailed(it.name)
		g.reporter.Failure(it.failure)
	} else {
		g.reporter.ItPassed(it.name)
	}
	return failed
}

func (it *It) failed(msg string, stack []string) {
	it.failure = &Failure{Stack: stack, Message: msg, TestName: it.parent.name + " " + it.name}
}

type Xit struct {
	h        interface{}
	name     string
	parent   *Describe
	failure  *Failure
	reporter Reporter
	isAsync  bool
}

func (xit *Xit) run(g *G) bool {
	g.currentIt = xit

	g.reporter.ItIsExcluded(xit.name)
	return false
}

func (xit *Xit) failed(msg string, stack []string) {
	xit.failure = nil
}

func parseFlags() {
	//Flag parsing
	flag.Parse()
	if *regexParam != "" {
		runRegex = regexp.MustCompile(*regexParam)
	} else {
		runRegex = nil
	}
}

var timeout = flag.Duration("goblin.timeout", 5*time.Second, "Sets default timeouts for all tests")
var isTty = flag.Bool("goblin.tty", true, "Sets the default output format (color / monochrome)")
var regexParam = flag.String("goblin.run", "", "Runs only tests which match the supplied regex")
var runRegex *regexp.Regexp

func Goblin(t *testing.T, arguments ...string) *G {
	parseFlags()
	g := &G{t: t, timeout: *timeout}
	var fancy TextFancier
	if *isTty {
		fancy = &TerminalFancier{}
	} else {
		fancy = &Monochrome{}
	}

	g.reporter = Reporter(&DetailedReporter{fancy: fancy})
	return g
}

func runIt(g *G, h interface{}) {
	defer timeTrack(time.Now(), g)
	g.mutex.Lock()
	g.timedOut = false
	g.mutex.Unlock()
	g.timer = time.NewTimer(g.timeout)
	g.shouldContinue = make(chan bool)
	if call, ok := h.(func()); ok {
		// the test is synchronous
		go func(c chan bool) { call(); c <- true }(g.shouldContinue)
	} else if call, ok := h.(func(Done)); ok {
		doneCalled := 0
		go func(c chan bool) {
			call(func(msg ...interface{}) {
				if len(msg) > 0 {
					g.Fail(msg)
				} else {
					doneCalled++
					if doneCalled > 1 {
						g.Fail("Done called multiple times")
					}
					c <- true
				}
			})
		}(g.shouldContinue)
	} else {
		panic("Not implemented.")
	}
	select {
	case <-g.shouldContinue:
	case <-g.timer.C:
		//Set to nil as it shouldn't continue
		g.shouldContinue = nil
		g.timedOut = true
		g.Fail("Test exceeded " + fmt.Sprintf("%s", g.timeout))
	}
	// Reset timeout value
	g.timeout = *timeout
}

type G struct {
	t              *testing.T
	parent         *Describe
	currentIt      Itable
	timeout        time.Duration
	reporter       Reporter
	timedOut       bool
	shouldContinue chan bool
	mutex          sync.Mutex
	timer          *time.Timer
}

func (g *G) SetReporter(r Reporter) {
	g.reporter = r
}

func (g *G) It(name string, h ...interface{}) {
	if matchesRegex(name) {
		it := &It{name: name, parent: g.parent, reporter: g.reporter}
		if g.parent == nil {
			panic(fmt.Sprintf("It(\"%s\") block should be written inside Describe() block.", name))
		}
		notifyParents(g.parent)
		if len(h) > 0 {
			it.h = h[0]
		}
		g.parent.children = append(g.parent.children, Runnable(it))
	}
}

func (g *G) Xit(name string, h ...interface{}) {
	if matchesRegex(name) {
		xit := &Xit{name: name, parent: g.parent, reporter: g.reporter}
		notifyParents(g.parent)
		if len(h) > 0 {
			xit.h = h[0]
		}
		g.parent.children = append(g.parent.children, Runnable(xit))
	}
}

func matchesRegex(value string) bool {
	if runRegex != nil {
		return runRegex.MatchString(value)
	}
	return true
}

func notifyParents(d *Describe) {
	d.hasTests = true
	if d.parent != nil {
		notifyParents(d.parent)
	}
}

func (g *G) Before(h func()) {
	g.parent.befores = append(g.parent.befores, h)
}

func (g *G) BeforeEach(h func()) {
	g.parent.beforeEach = append(g.parent.beforeEach, h)
}

func (g *G) JustBeforeEach(h func()) {
	g.parent.justBeforeEach = append(g.parent.justBeforeEach, h)
}

func (g *G) After(h func()) {
	g.parent.afters = append(g.parent.afters, h)
}

func (g *G) AfterEach(h func()) {
	g.parent.afterEach = append(g.parent.afterEach, h)
}

func (g *G) Assert(src interface{}) *Assertion {
	return &Assertion{src: src, fail: g.Fail}
}

func timeTrack(start time.Time, g *G) {
	g.reporter.ItTook(time.Since(start))
}

func (g *G) Fail(error interface{}) {
	//Skips 7 stacks due to the functions between the stack and the test
	stack := ResolveStack(7)
	message := fmt.Sprintf("%v", error)
	if g.currentIt == nil {
		panic("Asserts should be written inside an It() block.")
	}
	g.currentIt.failed(message, stack)
	if g.shouldContinue != nil {
		g.shouldContinue <- true
	}
	g.mutex.Lock()
	defer g.mutex.Unlock()
	if !g.timedOut {
		//Stop test function execution
		runtime.Goexit()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"strconv"
	"time"
	"unicode/utf8"
)

var (
	server = flag.Bool("s", false, "run a server instead of a client")
	format = flag.String("f", "nice", "output format (vim | emacs | nice | csv | json)")
	input  = flag.String("in", "", "use this file instead of stdin input")
	sock   = CreateSockFlag("sock", "socket type (unix | tcp)")
	addr   = flag.String("addr", "localhost:37373", "address for tcp socket")
)

// TODO: find a better place for this function
// returns truncated 'data' and amount of bytes skipped (for cursor pos adjustment)
func filterOutShebang(data []byte) ([]byte, int) {
	if len(data) > 2 && data[0] == '#' && data[1] == '!' {
		newline := bytes.Index(data, []byte("\n"))
		if newline != -1 && len(data) > newline+1 {
			return data[newline+1:], newline + 1
		}
	}
	return data, 0
}

//-------------------------------------------------------------------------
// Formatter interfaces
//-------------------------------------------------------------------------

type FormatterEmpty interface {
	WriteEmpty()
}

type FormatterCandidates interface {
	WriteCandidates(names, types, classes []string, num int)
}

//-------------------------------------------------------------------------
// NiceFormatter (just for testing, simple textual output)
//-------------------------------------------------------------------------

type NiceFormatter struct{}

func (*NiceFormatter) WriteEmpty() {
	fmt.Printf("Nothing to complete.\n")
}

func (*NiceFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf("Found %d candidates:\n", len(names))
	for i := 0; i < len(names); i++ {
		abbr := fmt.Sprintf("%s %s %s", classes[i], names[i], types[i])
		if classes[i] == "func" {
			abbr = fmt.Sprintf("%s %s%s", classes[i], names[i], types[i][len("func"):])
		}
		fmt.Printf("  %s\n", abbr)
	}
}

//-------------------------------------------------------------------------
// VimFormatter
//-------------------------------------------------------------------------

type VimFormatter struct{}

func (*VimFormatter) WriteEmpty() {
	fmt.Print("[0, []]")
}

func (*VimFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf("[%d, [", num)
	for i := 0; i < len(names); i++ {
		word := names[i]
		if classes[i] == "func" {
			word += "("
		}
		if len(types[i]) == 6 {
			word += ")"
		}

		abbr := fmt.Sprintf("%s %s %s", classes[i], names[i], types[i])
		if classes[i] == "func" {
			abbr = fmt.Sprintf("%s %s%s", classes[i], names[i], types[i][len("func"):])
		}
		fmt.Printf("{'word': '%s', 'abbr': '%s'}", word, abbr)
		if i != len(names)-1 {
			fmt.Printf(", ")
		}

	}
	fmt.Printf("]]")
}

//-------------------------------------------------------------------------
// EmacsFormatter
//-------------------------------------------------------------------------

type EmacsFormatter struct{}

func (*EmacsFormatter) WriteCandidates(names, types, classes []string, num int) {
	for i := 0; i < len(names); i++ {
		name := names[i]
		hint := classes[i] + " " + types[i]
		if classes[i] == "func" {
			hint = types[i]
		}
		fmt.Printf("%s,,%s\n", name, hint)
	}
}

//-------------------------------------------------------------------------
// CSVFormatter
//-------------------------------------------------------------------------

type CSVFormatter struct{}

func (*CSVFormatter) WriteCandidates(names, types, classes []string, num int) {
	for i := 0; i < len(names); i++ {
		fmt.Printf("%s,,%s,,%s\n", classes[i], names[i], types[i])
	}
}

//-------------------------------------------------------------------------
// JSONFormatter
//-------------------------------------------------------------------------

type JSONFormatter struct{}

func (*JSONFormatter) WriteEmpty() {
	fmt.Print("[]")
}

func (*JSONFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf(`[%d, [`, num)
	for i := 0; i < len(names); i++ {
		fmt.Printf(`{"class": "%s", "name": "%s", "type": "%s"}`,
			classes[i], names[i], types[i])
		if i != len(names)-1 {
			fmt.Printf(", ")
		}
	}
	fmt.Print("]]")
}

//-------------------------------------------------------------------------

func getFormatter() interface{} {
	switch *format {
	case "vim":
		return new(VimFormatter)
	case "emacs":
		return new(EmacsFormatter)
	case "nice":
		return new(NiceFormatter)
	case "csv":
		return new(CSVFormatter)
	case "json":
		return new(JSONFormatter)
	}
	return new(VimFormatter)
}

func getSocketFilename() string {
	user := os.Getenv("USER")
	if user == "" {
		user = "all"
	}
	return fmt.Sprintf("%s/acrserver.%s", os.TempDir(), user)
}

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

func serverFunc() int {
	readConfig(&Config)

	addr := *addr
	if *sock == "unix" {
		addr = getSocketFilename()
		if fileExists(addr) {
			fmt.Printf("unix socket: '%s' already exists\n", addr)
			return 1
		}
	}
	daemon = NewDaemon(*sock, addr)
	if *sock == "unix" {
		// cleanup unix socket file
		defer os.Remove(addr)
	}

	rpcremote := new(RPCRemote)
	rpc.Register(rpcremote)

	daemon.acr.Loop()
	return 0
}

func cmdStatus(c *rpc.Client) {
	fmt.Printf("%s\n", Client_Status(c, 0))
}

func cmdAutoComplete(c *rpc.Client) {
	var file []byte
	var err error

	if *input != "" {
		file, err = ioutil.ReadFile(*input)
	} else {
		file, err = ioutil.ReadAll(os.Stdin)
	}

	if err != nil {
		panic(err.Error())
	}

	var skipped int
	file, skipped = filterOutShebang(file)

	filename := *input
	cursor := -1

	offset := ""
	switch flag.NArg() {
	case 2:
		offset = flag.Arg(1)
	case 3:
		filename = flag.Arg(1) // Override default filename
		offset = flag.Arg(2)
	}

	if offset != "" {
		if offset[0] == 'c' || offset[0] == 'C' {
			cursor, _ = strconv.Atoi(offset[1:])
			cursor = charToByteOffset(file, cursor)
		} else {
			cursor, _ = strconv.Atoi(offset)
		}
	}

	cursor -= skipped
	if filename != "" && !filepath.IsAbs(filename) {
		cwd, _ := os.Getwd()
		filename = filepath.Join(cwd, filename)
	}

	formatter := getFormatter()
	names, types, classes, partial := Client_AutoComplete(c, file, filename, cursor)
	if names == nil {
		if f, ok := formatter.(FormatterEmpty); ok {
			f.WriteEmpty()
		}
		return
	}

	if f, ok := formatter.(FormatterCandidates); ok {
		f.WriteCandidates(names, types, classes, partial)
	}
}

func cmdClose(c *rpc.Client) {
	Client_Close(c, 0)
}

func cmdDropCache(c *rpc.Client) {
	Client_DropCache(c, 0)
}

func cmdSet(c *rpc.Client) {
	switch flag.NArg() {
	case 1:
		fmt.Print(Client_Set(c, "\x00", "\x00"))
	case 2:
		fmt.Print(Client_Set(c, flag.Arg(1), "\x00"))
	case 3:
		fmt.Print(Client_Set(c, flag.Arg(1), flag.Arg(2)))
	}
}

func tryRunServer() error {
	path := GetExecutableFileName()

	args := []string{os.Args[0], "-s", "-sock", *sock, "-addr", *addr}
	cwd, _ := os.Getwd()
	procattr := os.ProcAttr{Dir: cwd, Env: os.Environ(), Files: []*os.File{nil, nil, nil}}
	p, err := os.StartProcess(path, args, &procattr)

	if err != nil {
		return err
	}
	return p.Release()
}

func tryToConnect(network, address string) (client *rpc.Client, err error) {
	t := 0
	for {
		client, err = rpc.Dial(network, address)
		if err != nil && t < 1000 {
			time.Sleep(10e6) // wait 10 milliseconds
			t += 10
			continue
		}
		break
	}

	return
}

func clientFunc() int {
	addr := *addr
	if *sock == "unix" {
		addr = getSocketFilename()
	}

	// client
	client, err := rpc.Dial(*sock, addr)
	if err != nil {
		err = tryRunServer()
		if err != nil {
			fmt.Printf("%s\n", err.Error())
			return 1
		}
		client, err = tryToConnect(*sock, addr)
		if err != nil {
			fmt.Printf("%s\n", err.Error())
			return 1
		}
	}
	defer client.Close()

	if flag.NArg() > 0 {
		switch flag.Arg(0) {
		case "autocomplete":
			cmdAutoComplete(client)
		case "close":
			cmdClose(client)
		case "status":
			cmdStatus(client)
		case "drop-cache":
			cmdDropCache(client)
		case "set":
			cmdSet(client)
		}
	}
	return 0
}

func charToByteOffset(s []byte, offsetC int) (offsetB int) {
	for offsetB = 0; offsetC > 0 && offsetB < len(s); offsetB++ {
		if utf8.RuneStart(s[offsetB]) {
			offsetC--
		}
	}
	return offsetB
}

func main() {
	flag.Parse()

	var retval int
	if *server {
		retval = serverFunc()
	} else {
		retval = clientFunc()
	}
	os.Exit(retval)
}
<commit_msg>Add ")" only if the type is a function.<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"net/rpc"
	"os"
	"path/filepath"
	"strconv"
	"time"
	"unicode/utf8"
)

var (
	server = flag.Bool("s", false, "run a server instead of a client")
	format = flag.String("f", "nice", "output format (vim | emacs | nice | csv | json)")
	input  = flag.String("in", "", "use this file instead of stdin input")
	sock   = CreateSockFlag("sock", "socket type (unix | tcp)")
	addr   = flag.String("addr", "localhost:37373", "address for tcp socket")
)

// TODO: find a better place for this function
// returns truncated 'data' and amount of bytes skipped (for cursor pos adjustment)
func filterOutShebang(data []byte) ([]byte, int) {
	if len(data) > 2 && data[0] == '#' && data[1] == '!' {
		newline := bytes.Index(data, []byte("\n"))
		if newline != -1 && len(data) > newline+1 {
			return data[newline+1:], newline + 1
		}
	}
	return data, 0
}

//-------------------------------------------------------------------------
// Formatter interfaces
//-------------------------------------------------------------------------

type FormatterEmpty interface {
	WriteEmpty()
}

type FormatterCandidates interface {
	WriteCandidates(names, types, classes []string, num int)
}

//-------------------------------------------------------------------------
// NiceFormatter (just for testing, simple textual output)
//-------------------------------------------------------------------------

type NiceFormatter struct{}

func (*NiceFormatter) WriteEmpty() {
	fmt.Printf("Nothing to complete.\n")
}

func (*NiceFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf("Found %d candidates:\n", len(names))
	for i := 0; i < len(names); i++ {
		abbr := fmt.Sprintf("%s %s %s", classes[i], names[i], types[i])
		if classes[i] == "func" {
			abbr = fmt.Sprintf("%s %s%s", classes[i], names[i], types[i][len("func"):])
		}
		fmt.Printf("  %s\n", abbr)
	}
}

//-------------------------------------------------------------------------
// VimFormatter
//-------------------------------------------------------------------------

type VimFormatter struct{}

func (*VimFormatter) WriteEmpty() {
	fmt.Print("[0, []]")
}

func (*VimFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf("[%d, [", num)
	for i := 0; i < len(names); i++ {
		word := names[i]
		if classes[i] == "func" {
			word += "("
			if len(types[i]) == 6 {
				word += ")"
			}
		}

		abbr := fmt.Sprintf("%s %s %s", classes[i], names[i], types[i])
		if classes[i] == "func" {
			abbr = fmt.Sprintf("%s %s%s", classes[i], names[i], types[i][len("func"):])
		}
		fmt.Printf("{'word': '%s', 'abbr': '%s'}", word, abbr)
		if i != len(names)-1 {
			fmt.Printf(", ")
		}

	}
	fmt.Printf("]]")
}

//-------------------------------------------------------------------------
// EmacsFormatter
//-------------------------------------------------------------------------

type EmacsFormatter struct{}

func (*EmacsFormatter) WriteCandidates(names, types, classes []string, num int) {
	for i := 0; i < len(names); i++ {
		name := names[i]
		hint := classes[i] + " " + types[i]
		if classes[i] == "func" {
			hint = types[i]
		}
		fmt.Printf("%s,,%s\n", name, hint)
	}
}

//-------------------------------------------------------------------------
// CSVFormatter
//-------------------------------------------------------------------------

type CSVFormatter struct{}

func (*CSVFormatter) WriteCandidates(names, types, classes []string, num int) {
	for i := 0; i < len(names); i++ {
		fmt.Printf("%s,,%s,,%s\n", classes[i], names[i], types[i])
	}
}

//-------------------------------------------------------------------------
// JSONFormatter
//-------------------------------------------------------------------------

type JSONFormatter struct{}

func (*JSONFormatter) WriteEmpty() {
	fmt.Print("[]")
}

func (*JSONFormatter) WriteCandidates(names, types, classes []string, num int) {
	fmt.Printf(`[%d, [`, num)
	for i := 0; i < len(names); i++ {
		fmt.Printf(`{"class": "%s", "name": "%s", "type": "%s"}`,
			classes[i], names[i], types[i])
		if i != len(names)-1 {
			fmt.Printf(", ")
		}
	}
	fmt.Print("]]")
}

//-------------------------------------------------------------------------

func getFormatter() interface{} {
	switch *format {
	case "vim":
		return new(VimFormatter)
	case "emacs":
		return new(EmacsFormatter)
	case "nice":
		return new(NiceFormatter)
	case "csv":
		return new(CSVFormatter)
	case "json":
		return new(JSONFormatter)
	}
	return new(VimFormatter)
}

func getSocketFilename() string {
	user := os.Getenv("USER")
	if user == "" {
		user = "all"
	}
	return fmt.Sprintf("%s/acrserver.%s", os.TempDir(), user)
}

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

func serverFunc() int {
	readConfig(&Config)

	addr := *addr
	if *sock == "unix" {
		addr = getSocketFilename()
		if fileExists(addr) {
			fmt.Printf("unix socket: '%s' already exists\n", addr)
			return 1
		}
	}
	daemon = NewDaemon(*sock, addr)
	if *sock == "unix" {
		// cleanup unix socket file
		defer os.Remove(addr)
	}

	rpcremote := new(RPCRemote)
	rpc.Register(rpcremote)

	daemon.acr.Loop()
	return 0
}

func cmdStatus(c *rpc.Client) {
	fmt.Printf("%s\n", Client_Status(c, 0))
}

func cmdAutoComplete(c *rpc.Client) {
	var file []byte
	var err error

	if *input != "" {
		file, err = ioutil.ReadFile(*input)
	} else {
		file, err = ioutil.ReadAll(os.Stdin)
	}

	if err != nil {
		panic(err.Error())
	}

	var skipped int
	file, skipped = filterOutShebang(file)

	filename := *input
	cursor := -1

	offset := ""
	switch flag.NArg() {
	case 2:
		offset = flag.Arg(1)
	case 3:
		filename = flag.Arg(1) // Override default filename
		offset = flag.Arg(2)
	}

	if offset != "" {
		if offset[0] == 'c' || offset[0] == 'C' {
			cursor, _ = strconv.Atoi(offset[1:])
			cursor = charToByteOffset(file, cursor)
		} else {
			cursor, _ = strconv.Atoi(offset)
		}
	}

	cursor -= skipped
	if filename != "" && !filepath.IsAbs(filename) {
		cwd, _ := os.Getwd()
		filename = filepath.Join(cwd, filename)
	}

	formatter := getFormatter()
	names, types, classes, partial := Client_AutoComplete(c, file, filename, cursor)
	if names == nil {
		if f, ok := formatter.(FormatterEmpty); ok {
			f.WriteEmpty()
		}
		return
	}

	if f, ok := formatter.(FormatterCandidates); ok {
		f.WriteCandidates(names, types, classes, partial)
	}
}

func cmdClose(c *rpc.Client) {
	Client_Close(c, 0)
}

func cmdDropCache(c *rpc.Client) {
	Client_DropCache(c, 0)
}

func cmdSet(c *rpc.Client) {
	switch flag.NArg() {
	case 1:
		fmt.Print(Client_Set(c, "\x00", "\x00"))
	case 2:
		fmt.Print(Client_Set(c, flag.Arg(1), "\x00"))
	case 3:
		fmt.Print(Client_Set(c, flag.Arg(1), flag.Arg(2)))
	}
}

func tryRunServer() error {
	path := GetExecutableFileName()

	args := []string{os.Args[0], "-s", "-sock", *sock, "-addr", *addr}
	cwd, _ := os.Getwd()
	procattr := os.ProcAttr{Dir: cwd, Env: os.Environ(), Files: []*os.File{nil, nil, nil}}
	p, err := os.StartProcess(path, args, &procattr)

	if err != nil {
		return err
	}
	return p.Release()
}

func tryToConnect(network, address string) (client *rpc.Client, err error) {
	t := 0
	for {
		client, err = rpc.Dial(network, address)
		if err != nil && t < 1000 {
			time.Sleep(10e6) // wait 10 milliseconds
			t += 10
			continue
		}
		break
	}

	return
}

func clientFunc() int {
	addr := *addr
	if *sock == "unix" {
		addr = getSocketFilename()
	}

	// client
	client, err := rpc.Dial(*sock, addr)
	if err != nil {
		err = tryRunServer()
		if err != nil {
			fmt.Printf("%s\n", err.Error())
			return 1
		}
		client, err = tryToConnect(*sock, addr)
		if err != nil {
			fmt.Printf("%s\n", err.Error())
			return 1
		}
	}
	defer client.Close()

	if flag.NArg() > 0 {
		switch flag.Arg(0) {
		case "autocomplete":
			cmdAutoComplete(client)
		case "close":
			cmdClose(client)
		case "status":
			cmdStatus(client)
		case "drop-cache":
			cmdDropCache(client)
		case "set":
			cmdSet(client)
		}
	}
	return 0
}

func charToByteOffset(s []byte, offsetC int) (offsetB int) {
	for offsetB = 0; offsetC > 0 && offsetB < len(s); offsetB++ {
		if utf8.RuneStart(s[offsetB]) {
			offsetC--
		}
	}
	return offsetB
}

func main() {
	flag.Parse()

	var retval int
	if *server {
		retval = serverFunc()
	} else {
		retval = clientFunc()
	}
	os.Exit(retval)
}
<|endoftext|>


<commit_before>package godate

import "time"

type dateFormat string

const (
	ANSIC    dateFormat = "Jan _2 2006"
	RubyDate dateFormat = "Jan 02 2006"
	RFC822   dateFormat = "02 Jan 06"
	RFC3339  dateFormat = "2006-01-02"
)

// Date handles time.Time as date handler
type Date struct {
	t time.Time
}

// Today returns Date of today
func Today() Date {
	return Date{
		t: time.Now().Truncate(time.Hour * 24),
	}
}

// Parse returns Date from time.Parse value
func Parse(layout, value string) (Date, error) {
	p, err := time.Parse(layout, value)
	if err != nil {
		return Date{}, err
	}

	return Date{t: p}, nil
}

// String is wrapper of Format method of time.Time
func (d Date) Format(format dateFormat) string {
	return d.t.Format(string(format))
}

// Equal returns bool resulted from comparing two date object
func (d Date) Equal(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year == xyear && month == xmonth && day == xday
}

// After reports whether the time instant d is after date.
func (d Date) After(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year >= xyear && month >= xmonth && day > xday
}

// Before reports whether the time instant d is before date.
func (d Date) Before(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year <= xyear && month <= xmonth && day < xday
}

// YearDay is wrapper of YearDay method of time.Time
func (d Date) YearDay() int {
	return d.t.YearDay()
}
<commit_msg>Fix for code climate issue<commit_after>package godate

import "time"

type dateFormat string

// These are format for Date
const (
	ANSIC    dateFormat = "Jan _2 2006"
	RubyDate dateFormat = "Jan 02 2006"
	RFC822   dateFormat = "02 Jan 06"
	RFC3339  dateFormat = "2006-01-02"
)

// Date handles time.Time as date handler
type Date struct {
	t time.Time
}

// Today returns Date of today
func Today() Date {
	return Date{
		t: time.Now().Truncate(time.Hour * 24),
	}
}

// Parse returns Date from time.Parse value
func Parse(layout, value string) (Date, error) {
	p, err := time.Parse(layout, value)
	if err != nil {
		return Date{}, err
	}

	return Date{t: p}, nil
}

// Format is wrapper of Format method of time.Time
func (d Date) Format(format dateFormat) string {
	return d.t.Format(string(format))
}

// Equal returns bool resulted from comparing two date object
func (d Date) Equal(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year == xyear && month == xmonth && day == xday
}

// After reports whether the time instant d is after date.
func (d Date) After(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year >= xyear && month >= xmonth && day > xday
}

// Before reports whether the time instant d is before date.
func (d Date) Before(date Date) bool {
	year, month, day := d.t.Date()
	xyear, xmonth, xday := date.t.Date()

	return year <= xyear && month <= xmonth && day < xday
}

// YearDay is wrapper of YearDay method of time.Time
func (d Date) YearDay() int {
	return d.t.YearDay()
}
<|endoftext|>


<commit_before>package main

import (
  "net/http"
  "log"
  "io/ioutil"
  "encoding/json"
  "os/exec"
  "flag"
  "strconv"
  "crypto/md5"
  "text/template"
  "bytes"
  "gopkg.in/fsnotify.v1"
)

// A map of repos => (a map of branches => (a list of actions))
// Todo: Idiomatic way of doing this?
type Config map[string]map[string][]string

type HookMsg struct {
  CanonicalUrl string `json:"canon_url"`

  Commits []struct {
    Branch string
  }

  Repository struct {
    AbsoluteUrl string `json:"absolute_url"`
  }
}

type Commit struct {
  Repository string
  Branch string
}

type Job struct {
  Commit Commit
  Action string
}

var listenPort = flag.Int("port", 8080, "portnumber to listen on")
var configFile = flag.String("config", "golive.json", "the configfile to read")
var verbose = flag.Bool("v", false, "print more output")

var jobTemplates = make(map[[16]byte]template.Template)
var config Config

func main() {
	flag.Parse()

  parseConfig(*configFile)
  go watchConfig(*configFile)

  msgs := make(chan HookMsg, 100)
  commits := make(chan Commit, 100)
  jobs := make(chan Job, 100)
  actions := make(chan string, 100)

  go hookWrangler(msgs, commits)
  go commitWrangler(commits, jobs, config)
  go jobWrangler(jobs, actions)
  go actionRunner(actions)

  log.Print("Starting golive server")

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    log.Print("Received request")

    payload := r.FormValue("payload")

    var hookmsg HookMsg
    if err := json.Unmarshal([]byte(payload), &hookmsg); err != nil {
      http.Error(w, "Could not decode json", 500)
      log.Fatal(err)
    }

    if *verbose {
      log.Print("Received commit: ", hookmsg)
    }

    msgs <- hookmsg
  })

  log.Fatal(http.ListenAndServe(":" + strconv.Itoa(*listenPort), nil))
}

func watchConfig(configFile string) {
  watcher, err := fsnotify.NewWatcher()
  if err != nil {
    log.Fatal(err)
  }

  log.Print("Watching config file: ", configFile)

  defer watcher.Close()

	done := make(chan bool)

  go func() {
    for {
      select {
        case event := <-watcher.Events:
          if event.Name == "" {
            continue
          }

          if *verbose {
            log.Println(event.Name, event.Op, event)
          }

          // The watching doesn't seem to follow renames, which happen i.e.
          // during Vim editing, so set up a new one
          if event.Op & fsnotify.Rename == fsnotify.Rename {
            addWatcher(watcher, configFile)
          }

          log.Print("Relading config file ", configFile)

          parseConfig(configFile)

        case err := <-watcher.Errors:
          if err == nil {
            continue
          }

          done <- true
          log.Fatal("Error when watching config file: ", err)
      }
    }
  }()

  addWatcher(watcher, configFile)

  <- done
}

func addWatcher(watcher *fsnotify.Watcher, file string) {
  err := watcher.Add(file)
  if err != nil {
    log.Fatal(err)
  }
}

func parseConfig(configFile string) {
    config_raw, err := ioutil.ReadFile(configFile)
    if err != nil {
      log.Fatal(err)
    }

    var newConfig Config
    json.Unmarshal(config_raw, &newConfig)

    config = newConfig

    if *verbose {
      log.Print("Loaded config: ", config)
    }
}

func hookWrangler(msgs <-chan HookMsg, results chan<- Commit) {
  for msg := range msgs {
    repository := msg.CanonicalUrl + msg.Repository.AbsoluteUrl

    for _, commit := range msg.Commits {
      results <- Commit{ repository, commit.Branch}
    }
  }
}

func commitWrangler(commits <-chan Commit, results chan<- Job, config Config) {
  for commit := range commits {
    if commit.Branch == "" || commit.Repository == "" {
      continue
    }

    if branches, ok := config[commit.Repository]; ok {
      if actions, ok := branches[commit.Branch]; ok {
        for _, action := range actions {
          results <- Job{commit, string(action)}
        }
      }
    }
  }
}

func jobWrangler(jobs <-chan Job, actions chan<- string) {
  for job := range jobs {
    hash := md5.Sum([]byte(job.Action))

    if _, ok := jobTemplates[hash]; !ok {
      t, err := template.New(string(hash[:])).Parse(job.Action)
      if err != nil {
        log.Fatal("Could not compile template: ", job.Action, " - ", err)
      }

      jobTemplates[hash] = *t
    }

    t := jobTemplates[hash]

    var buff bytes.Buffer
    (&t).Execute(&buff, job.Commit)
    s := buff.String()

    actions <- s
  }
}

func actionRunner(actions <-chan string) {
  for action := range actions {
    if *verbose {
      log.Print("Running action: ", action)
    }

    command := exec.Command("bash", "-c", action)

    command.Run()
  }
}
<commit_msg>Be very verbose<commit_after>package main

import (
  "net/http"
  "log"
  "io/ioutil"
  "encoding/json"
  "os/exec"
  "flag"
  "strconv"
  "crypto/md5"
  "text/template"
  "bytes"
  "gopkg.in/fsnotify.v1"
)

// A map of repos => (a map of branches => (a list of actions))
// Todo: Idiomatic way of doing this?
type Config map[string]map[string][]string

type HookMsg struct {
  CanonicalUrl string `json:"canon_url"`

  Commits []struct {
    Branch string
  }

  Repository struct {
    AbsoluteUrl string `json:"absolute_url"`
  }
}

type Commit struct {
  Repository string
  Branch string
}

type Job struct {
  Commit Commit
  Action string
}

var listenPort = flag.Int("port", 8080, "portnumber to listen on")
var configFile = flag.String("config", "golive.json", "the configfile to read")
var verbose = flag.Bool("v", false, "print more output")

var jobTemplates = make(map[[16]byte]template.Template)
var config Config

func main() {
	flag.Parse()

  parseConfig(*configFile)
  go watchConfig(*configFile)

  msgs := make(chan HookMsg, 100)
  commits := make(chan Commit, 100)
  jobs := make(chan Job, 100)
  actions := make(chan string, 100)

  go hookWrangler(msgs, commits)
  go commitWrangler(commits, jobs, config)
  go jobWrangler(jobs, actions)
  go actionRunner(actions)

  log.Print("Starting golive server")

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    log.Print("Received request")

    payload := r.FormValue("payload")

    var hookmsg HookMsg
    if err := json.Unmarshal([]byte(payload), &hookmsg); err != nil {
      http.Error(w, "Could not decode json", 500)
      log.Fatal(err)
    }

    if *verbose {
      log.Print("Received commit: ", hookmsg)
    }

    msgs <- hookmsg
  })

  log.Fatal(http.ListenAndServe(":" + strconv.Itoa(*listenPort), nil))
}

func watchConfig(configFile string) {
  watcher, err := fsnotify.NewWatcher()
  if err != nil {
    log.Fatal(err)
  }

  log.Print("Watching config file: ", configFile)

  defer watcher.Close()

	done := make(chan bool)

  go func() {
    for {
      select {
        case event := <-watcher.Events:
          if event.Name == "" {
            continue
          }

          if *verbose {
            log.Println(event.Name, event.Op, event)
          }

          // The watching doesn't seem to follow renames, which happen i.e.
          // during Vim editing, so set up a new one
          if event.Op & fsnotify.Rename == fsnotify.Rename {
            addWatcher(watcher, configFile)
          }

          log.Print("Relading config file ", configFile)

          parseConfig(configFile)

        case err := <-watcher.Errors:
          if err == nil {
            continue
          }

          done <- true
          log.Fatal("Error when watching config file: ", err)
      }
    }
  }()

  addWatcher(watcher, configFile)

  <- done
}

func addWatcher(watcher *fsnotify.Watcher, file string) {
  err := watcher.Add(file)
  if err != nil {
    log.Fatal(err)
  }
}

func parseConfig(configFile string) {
    config_raw, err := ioutil.ReadFile(configFile)
    if err != nil {
      log.Fatal(err)
    }

    var newConfig Config
    json.Unmarshal(config_raw, &newConfig)

    config = newConfig

    if *verbose {
      log.Print("Loaded config: ", config)
    }
}

func hookWrangler(msgs <-chan HookMsg, results chan<- Commit) {
  for msg := range msgs {
    repository := msg.CanonicalUrl + msg.Repository.AbsoluteUrl

    for _, commit := range msg.Commits {
      results <- Commit{ repository, commit.Branch}
    }
  }
}

func commitWrangler(commits <-chan Commit, results chan<- Job, config Config) {
  for commit := range commits {
    if *verbose {
      log.Print("Received commit struct: ", commit)
    }

    if commit.Branch == "" || commit.Repository == "" {
      continue
    }

    if branches, ok := config[commit.Repository]; ok {
      if *verbose {
        log.Print("Repository present in config: ", commit.Repository)
      }
      if actions, ok := branches[commit.Branch]; ok {
        if *verbose {
          log.Print("Branch present in config: ", commit.Branch)
        }

        for _, action := range actions {
          results <- Job{commit, string(action)}
        }
      }
    }
  }
}

func jobWrangler(jobs <-chan Job, actions chan<- string) {
  for job := range jobs {
    hash := md5.Sum([]byte(job.Action))

    if _, ok := jobTemplates[hash]; !ok {
      t, err := template.New(string(hash[:])).Parse(job.Action)
      if err != nil {
        log.Fatal("Could not compile template: ", job.Action, " - ", err)
      }

      jobTemplates[hash] = *t
    }

    t := jobTemplates[hash]

    var buff bytes.Buffer
    (&t).Execute(&buff, job.Commit)
    s := buff.String()

    actions <- s
  }
}

func actionRunner(actions <-chan string) {
  for action := range actions {
    if *verbose {
      log.Print("Running action: ", action)
    }

    command := exec.Command("bash", "-c", action)

    command.Run()
  }
}
<|endoftext|>


<commit_before>package main

import  (
        "fmt"
        "github.com/spf13/cobra"
        "github.com/rakyll/globalconf"
        "../go-notetxt"
        "flag"
        "os/user"
        "time"
        "strings"
        "strconv"
)

func main() {

        conf, _ := globalconf.New("gonote")

        var flagNotedir = flag.String("dir", "", "Location of the note.txt directory.")
        var dir string

        var today bool

        var cmdAdd = &cobra.Command{
            Use:   "add [title] [tag]",
            Short: "Add a note.",
            Long:  `Add a note and tag it.`,
            Run: func(cmd *cobra.Command, args []string) {
                if len(args) < 1 && !today {
                        fmt.Println("I need something to add.")
                        return
                }

                var text string
                t := time.Now().Local()

                if today {
                        text = fmt.Sprintf("Daily journal, date %s", t.Format("02. 01. 2006"))
                } else {
                        text = strings.Join(args, " ")
                }

                file, err := notetxt.CreateNote(text, t.Format("2006/01/"), dir)
                if err != nil {
                        panic(err);
                }

                notetxt.OpenFileInEditor(file)
            },
        }
        cmdAdd.Flags().BoolVarP(&today, "today", "T", false,
                                 "Add today's journal entry.")

        var cmdList = &cobra.Command{
            Use:   "list",
            Short: "List notes.",
            Long:  `List all valid note files in the directory.`,
            Run: func(cmd *cobra.Command, args []string) {
                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                needle := strings.Join(args, " ")

                for i, note := range notes {
                    if note.Matches(needle) {
                        fmt.Printf("%d %s - %v\n", i, note.Name, note.Tags)
                    }
                }
            },
        }

        var cmdEdit = &cobra.Command{
            Use:   "edit",
            Short: "Edit notes.",
            Long:  `Edit a note identified by either an ID or a selector.`,
            Run: func(cmd *cobra.Command, args []string) {
                noteid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Notes matching your query:\n")
                        cmdList.Run(cmd, args)
                }

                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                notetxt.OpenFileInEditor(notes[noteid].Filename)

            },
        }

        var cmdTag = &cobra.Command{
            Use:   "tag <noteid> <tag-name>",
            Short: "Attaches a tag to a note.",
            Long:  `Tags a note with a one or more tags.`,
            Run: func(cmd *cobra.Command, args []string) {
                if len(args) < 2 {
                        fmt.Printf("Too few arguments.")
                }

                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                noteid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                file := notes[noteid].Filename
                tag := args[1]
                err = notetxt.TagNote(file, tag, dir)
                if err != nil {
                        panic(err)
                }

            },
        }

        var GonoterCmd = &cobra.Command{
            Use:   "gonote",
            Short: "gonote is a go implementation of note.txt specification.",
            Long: `A small, fast and fun implementation of note.txt`,
            Run: func(cmd *cobra.Command, args []string) {
            },
        }

        GonoterCmd.PersistentFlags().StringVarP(&dir, "directory", "", "",
                                     "Location of the note.txt directory.")

        conf.ParseAll()
        if dir == "" {
                if *flagNotedir == "" {
                        usr, err := user.Current()
                        if err != nil {
                                panic(err)
                        }

                        dir = usr.HomeDir + "/notes"
                } else {
                        dir = *flagNotedir
                }
        }

        GonoterCmd.AddCommand(cmdAdd)
        GonoterCmd.AddCommand(cmdList)
        GonoterCmd.AddCommand(cmdTag)
        GonoterCmd.AddCommand(cmdEdit)
        GonoterCmd.Execute()
}
<commit_msg>fix: noteid boundaries<commit_after>package main

import  (
        "fmt"
        "github.com/spf13/cobra"
        "github.com/rakyll/globalconf"
        "../go-notetxt"
        "flag"
        "os/user"
        "time"
        "strings"
        "strconv"
)

func main() {

        conf, _ := globalconf.New("gonote")

        var flagNotedir = flag.String("dir", "", "Location of the note.txt directory.")
        var dir string

        var today bool

        var cmdAdd = &cobra.Command{
            Use:   "add [title] [tag]",
            Short: "Add a note.",
            Long:  `Add a note and tag it.`,
            Run: func(cmd *cobra.Command, args []string) {
                if len(args) < 1 && !today {
                        fmt.Println("I need something to add.")
                        return
                }

                var text string
                t := time.Now().Local()

                if today {
                        text = fmt.Sprintf("Daily journal, date %s", t.Format("02. 01. 2006"))
                } else {
                        text = strings.Join(args, " ")
                }

                file, err := notetxt.CreateNote(text, t.Format("2006/01/"), dir)
                if err != nil {
                        panic(err);
                }

                notetxt.OpenFileInEditor(file)
            },
        }
        cmdAdd.Flags().BoolVarP(&today, "today", "T", false,
                                 "Add today's journal entry.")

        var cmdList = &cobra.Command{
            Use:   "list",
            Short: "List notes.",
            Long:  `List all valid note files in the directory.`,
            Run: func(cmd *cobra.Command, args []string) {
                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                needle := strings.Join(args, " ")

                for i, note := range notes {
                    if note.Matches(needle) {
                        fmt.Printf("%d %s - %v\n", i, note.Name, note.Tags)
                    }
                }
            },
        }

        var cmdEdit = &cobra.Command{
            Use:   "edit",
            Short: "Edit notes.",
            Long:  `Edit a note identified by either an ID or a selector.`,
            Run: func(cmd *cobra.Command, args []string) {
                noteid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Notes matching your query:\n")
                        cmdList.Run(cmd, args)
                }

                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                if noteid > len(notes) || noteid < 0 {
                        fmt.Printf("Invalid note ID (%v)\n", noteid)
                        return
                }

                notetxt.OpenFileInEditor(notes[noteid].Filename)

            },
        }

        var cmdTag = &cobra.Command{
            Use:   "tag <noteid> <tag-name>",
            Short: "Attaches a tag to a note.",
            Long:  `Tags a note with a one or more tags.`,
            Run: func(cmd *cobra.Command, args []string) {
                if len(args) < 2 {
                        fmt.Printf("Too few arguments.")
                }

                notes, err := notetxt.ParseDir(dir)
                if err != nil {
                    panic(err)
                }

                noteid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if noteid > len(notes) || noteid < 0 {
                        fmt.Printf("Invalid note ID (%v)\n", noteid)
                        return
                }

                file := notes[noteid].Filename
                tag := args[1]
                err = notetxt.TagNote(file, tag, dir)
                if err != nil {
                        panic(err)
                }

            },
        }

        var GonoterCmd = &cobra.Command{
            Use:   "gonote",
            Short: "gonote is a go implementation of note.txt specification.",
            Long: `A small, fast and fun implementation of note.txt`,
            Run: func(cmd *cobra.Command, args []string) {
            },
        }

        GonoterCmd.PersistentFlags().StringVarP(&dir, "directory", "", "",
                                     "Location of the note.txt directory.")

        conf.ParseAll()
        if dir == "" {
                if *flagNotedir == "" {
                        usr, err := user.Current()
                        if err != nil {
                                panic(err)
                        }

                        dir = usr.HomeDir + "/notes"
                } else {
                        dir = *flagNotedir
                }
        }

        GonoterCmd.AddCommand(cmdAdd)
        GonoterCmd.AddCommand(cmdList)
        GonoterCmd.AddCommand(cmdTag)
        GonoterCmd.AddCommand(cmdEdit)
        GonoterCmd.Execute()
}
<|endoftext|>


<commit_before>package gen

import (
	"bufio"
	"bytes"
	"io"
	"os"
	"text/template"

	"golang.org/x/tools/imports"
)

func writeToFile(file, template string, structs structs) error {
	f, err := os.Create(file)
	if err != nil {
		return err
	}

	w := bufio.NewWriter(f)
	defer w.Flush()

	b, err := writeWithFormat(file, template, structs)
	if err != nil {
		return err
	}

	w.Write(b)
	return nil
}

func writeWithFormat(file, template string, structs structs) ([]byte, error) {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	write(w, template, structs)
	w.Flush()

	formatted, err := imports.Process(file, b.Bytes(), nil)
	if err != nil {
		return nil, err
	}
	return formatted, nil
}

func write(w io.Writer, tplText string, structs structs) error {
	t := template.New("t")
	t.Funcs(template.FuncMap{})
	tpl := template.Must(t.Parse(tplText))
	if err := tpl.Execute(w, structs); err != nil {
		return err
	}

	return nil
}
<commit_msg>Add calling 'File.Close'<commit_after>package gen

import (
	"bufio"
	"bytes"
	"io"
	"os"
	"text/template"

	"golang.org/x/tools/imports"
)

func writeToFile(file, template string, structs structs) error {
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	defer f.Close()

	w := bufio.NewWriter(f)
	defer w.Flush()

	b, err := writeWithFormat(file, template, structs)
	if err != nil {
		return err
	}

	w.Write(b)
	return nil
}

func writeWithFormat(file, template string, structs structs) ([]byte, error) {
	var b bytes.Buffer
	w := bufio.NewWriter(&b)

	write(w, template, structs)
	w.Flush()

	formatted, err := imports.Process(file, b.Bytes(), nil)
	if err != nil {
		return nil, err
	}
	return formatted, nil
}

func write(w io.Writer, tplText string, structs structs) error {
	t := template.New("t")
	t.Funcs(template.FuncMap{})
	tpl := template.Must(t.Parse(tplText))
	if err := tpl.Execute(w, structs); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package get

import (
	"compress/gzip"
	"crypto"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net/url"
	"path"

	"github.com/moio/minima/util"
)

// common

// XMLLocation maps a <location> tag in repodata/repomd.xml or repodata/<ID>-primary.xml.gz
type XMLLocation struct {
	Href string `xml:"href,attr"`
}

// repodata/repomd.xml

// XMLRepomd maps a <repomd> tag in repodata/repomd.xml
type XMLRepomd struct {
	Data []XMLData `xml:"data"`
}

// XMLData maps a <data> tag in repodata/repomd.xml
type XMLData struct {
	Type     string      `xml:"type,attr"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// repodata/<ID>-primary.xml.gz

// XMLMetaData maps a <metadata> tag in repodata/<ID>-primary.xml.gz
type XMLMetaData struct {
	Packages []XMLPackage `xml:"package"`
}

// XMLPackage maps a <package> tag in repodata/<ID>-primary.xml.gz
type XMLPackage struct {
	Arch     string      `xml:"arch"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// XMLChecksum maps a <checksum> tag in repodata/<ID>-primary.xml.gz
type XMLChecksum struct {
	Type     string `xml:"type,attr"`
	Checksum string `xml:",cdata"`
}

var hashMap = map[string]crypto.Hash{
	"sha":    crypto.SHA1,
	"sha1":   crypto.SHA1,
	"sha256": crypto.SHA256,
}

const repomdPath = "repodata/repomd.xml"

// Syncer syncs repos from an HTTP source to a Storage
type Syncer struct {
	// URL of the repo this syncer syncs
	URL     url.URL
	archs   map[string]bool
	storage Storage
}

// NewSyncer creates a new Syncer
func NewSyncer(url url.URL, archs map[string]bool, storage Storage) *Syncer {
	return &Syncer{url, archs, storage}
}

// StoreRepo stores an HTTP repo in a Storage, automatically retrying in case of recoverable errors
func (r *Syncer) StoreRepo() (err error) {
	checksumMap := r.readChecksumMap()
	for i := 0; i < 20; i++ {
		err = r.storeRepo(checksumMap)
		if err == nil {
			return
		}

		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode {
			if uerr.StatusCode == 404 {
				log.Printf("Got 404, presumably temporarily, retrying...\n")
			} else {
				return err
			}
		}

		_, checksumError := err.(*util.ChecksumError)
		if checksumError {
			log.Printf(err.Error())
			log.Printf("Checksum did not match, presumably the repo was published while syncing, retrying...\n")
		} else {
			return err
		}
	}

	log.Printf("Too many temporary errors, aborting...\n")
	return err
}

// StoreRepo stores an HTTP repo in a Storage
func (r *Syncer) storeRepo(checksumMap map[string]XMLChecksum) (err error) {
	packagesToDownload, packagesToRecycle, err := r.processMetadata(checksumMap)
	if err != nil {
		return
	}

	downloadCount := len(packagesToDownload)
	log.Printf("Downloading %v packages...\n", downloadCount)
	for i, pack := range packagesToDownload {
		description := fmt.Sprintf("(%v/%v) %v", i+1, downloadCount, path.Base(pack.Location.Href))
		err = r.downloadStoreApply(pack.Location.Href, pack.Checksum.Checksum, description, hashMap[pack.Checksum.Type], util.Nop)
		if err != nil {
			return err
		}
	}

	recycleCount := len(packagesToRecycle)
	log.Printf("Recycling %v packages...\n", recycleCount)
	for _, pack := range packagesToRecycle {
		err = r.storage.Recycle(pack.Location.Href)
		if err != nil {
			return
		}
	}

	log.Printf("Committing changes...\n")
	err = r.storage.Commit()
	if err != nil {
		return
	}
	return
}

// downloadStore downloads a repo-relative path into a file
func (r *Syncer) downloadStore(path string, description string) error {
	return r.downloadStoreApply(path, "", description, 0, util.Nop)
}

// downloadStoreApply downloads a repo-relative path into a file, while applying a ReaderConsumer
func (r *Syncer) downloadStoreApply(relativePath string, checksum string, description string, hash crypto.Hash, f util.ReaderConsumer) error {
	log.Printf("Downloading %v...", description)
	url := r.URL
	url.Path = path.Join(r.URL.Path, relativePath)
	body, err := ReadURL(url.String())
	if err != nil {
		return err
	}

	return util.Compose(r.storage.StoringMapper(relativePath, checksum, hash), f)(body)
}

// processMetadata stores the repo metadata and returns a list of package file
// paths to download
func (r *Syncer) processMetadata(checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	err = r.downloadStoreApply(repomdPath, "", path.Base(repomdPath), 0, func(reader io.ReadCloser) (err error) {
		decoder := xml.NewDecoder(reader)
		var repomd XMLRepomd
		err = decoder.Decode(&repomd)
		if err != nil {
			return
		}

		data := repomd.Data
		for i := 0; i < len(data); i++ {
			log.Printf(data[i].Location.Href)
			metadataLocation := data[i].Location.Href
			metadataChecksum := data[i].Checksum
			decision := r.decide(metadataLocation, metadataChecksum, checksumMap)
			switch decision {
			case Download:
				log.Printf("...downloading")
				err = r.downloadStoreApply(metadataLocation, metadataChecksum.Checksum, path.Base(metadataLocation), hashMap[metadataChecksum.Type], util.Nop)
				if err != nil {
					return
				}
			case Recycle:
				log.Printf("...recycling")
				r.storage.Recycle(metadataLocation)
			}

			if data[i].Type == "primary" {
				packagesToDownload, packagesToRecycle, err = r.processPrimary(metadataLocation, checksumMap)
			}
		}
		return
	})
	if err != nil {
		return
	}

	ascPath := repomdPath + ".asc"
	err = r.downloadStore(ascPath, path.Base(ascPath))
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
		} else {
			return
		}
	}

	keyPath := repomdPath + ".key"
	err = r.downloadStore(keyPath, path.Base(keyPath))
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
			err = nil
		} else {
			return
		}
	}

	return
}

func (r *Syncer) readMetaData(reader io.Reader) (primary XMLMetaData, err error) {
	gzReader, err := gzip.NewReader(reader)
	if err != nil {
		return
	}
	defer gzReader.Close()

	decoder := xml.NewDecoder(gzReader)
	err = decoder.Decode(&primary)

	return
}

func (r *Syncer) readChecksumMap() (checksumMap map[string]XMLChecksum) {
	checksumMap = make(map[string]XMLChecksum)
	repomdReader, err := r.storage.NewReader(repomdPath, Permanent)
	if err != nil {
		if err == ErrFileNotFound {
			log.Println("First-time sync started")
		} else {
			log.Println(err.Error())
			log.Println("Error while reading previously-downloaded metadata. Starting sync from scratch")
		}
		return
	}
	defer repomdReader.Close()

	decoder := xml.NewDecoder(repomdReader)
	var repomd XMLRepomd
	err = decoder.Decode(&repomd)
	if err != nil {
		log.Println(err.Error())
		log.Println("Error while parsing previously-downloaded metadata. Starting sync from scratch")
		return
	}

	data := repomd.Data
	for i := 0; i < len(data); i++ {
		dataHref := data[i].Location.Href
		dataChecksum := data[i].Checksum
		checksumMap[dataHref] = dataChecksum
		if data[i].Type == "primary" {
			primaryReader, err := r.storage.NewReader(dataHref, Permanent)
			if err != nil {
				return
			}
			primary, err := r.readMetaData(primaryReader)
			if err != nil {
				return
			}
			for _, pack := range primary.Packages {
				checksumMap[pack.Location.Href] = pack.Checksum
			}
		}
	}
	return
}

// processPrimary stores the primary XML metadata file and returns a list of
// package file paths to download
func (r *Syncer) processPrimary(path string, checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	reader, err := r.storage.NewReader(path, Temporary)
	if err != nil {
		return
	}
	primary, err := r.readMetaData(reader)
	if err != nil {
		return
	}

	allArchs := len(r.archs) == 0
	for _, pack := range primary.Packages {
		if allArchs || pack.Arch == "noarch" || r.archs[pack.Arch] {
			decision := r.decide(pack.Location.Href, pack.Checksum, checksumMap)
			switch decision {
			case Download:
				packagesToDownload = append(packagesToDownload, pack)
			case Recycle:
				packagesToRecycle = append(packagesToRecycle, pack)
			}
		}
	}
	return
}

// Decision encodes what to do with a file
type Decision int

const (
	// Download means the Syncer will download a file
	Download Decision = iota
	// Recycle means the Syncer will copy an existing file without downloading
	Recycle
	// Skip means the Syncer detected an already-existing file and has nothing to do
	Skip
)

func (r *Syncer) decide(location string, checksum XMLChecksum, checksumMap map[string]XMLChecksum) Decision {
	previousChecksum, foundInPermanentLocation := checksumMap[location]
	if !foundInPermanentLocation || previousChecksum.Type != checksum.Type || previousChecksum.Checksum != checksum.Checksum {
		reader, err := r.storage.NewReader(location, Temporary)
		if err != nil {
			return Download
		}
		defer reader.Close()
		readChecksum, err := util.Checksum(reader, hashMap[checksum.Type])
		if err != nil || readChecksum != checksum.Checksum {
			return Download
		}
		return Skip
	}
	return Recycle
}
<commit_msg>Check repomd.xml signatures properly<commit_after>package get

import (
	"bytes"
	"compress/gzip"
	"crypto"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"path"

	"github.com/moio/minima/util"
	"golang.org/x/crypto/openpgp"
)

// common

// XMLLocation maps a <location> tag in repodata/repomd.xml or repodata/<ID>-primary.xml.gz
type XMLLocation struct {
	Href string `xml:"href,attr"`
}

// repodata/repomd.xml

// XMLRepomd maps a <repomd> tag in repodata/repomd.xml
type XMLRepomd struct {
	Data []XMLData `xml:"data"`
}

// XMLData maps a <data> tag in repodata/repomd.xml
type XMLData struct {
	Type     string      `xml:"type,attr"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// repodata/<ID>-primary.xml.gz

// XMLMetaData maps a <metadata> tag in repodata/<ID>-primary.xml.gz
type XMLMetaData struct {
	Packages []XMLPackage `xml:"package"`
}

// XMLPackage maps a <package> tag in repodata/<ID>-primary.xml.gz
type XMLPackage struct {
	Arch     string      `xml:"arch"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// XMLChecksum maps a <checksum> tag in repodata/<ID>-primary.xml.gz
type XMLChecksum struct {
	Type     string `xml:"type,attr"`
	Checksum string `xml:",cdata"`
}

var hashMap = map[string]crypto.Hash{
	"sha":    crypto.SHA1,
	"sha1":   crypto.SHA1,
	"sha256": crypto.SHA256,
}

const repomdPath = "repodata/repomd.xml"

// Syncer syncs repos from an HTTP source to a Storage
type Syncer struct {
	// URL of the repo this syncer syncs
	URL     url.URL
	archs   map[string]bool
	storage Storage
}

// NewSyncer creates a new Syncer
func NewSyncer(url url.URL, archs map[string]bool, storage Storage) *Syncer {
	return &Syncer{url, archs, storage}
}

// StoreRepo stores an HTTP repo in a Storage, automatically retrying in case of recoverable errors
func (r *Syncer) StoreRepo() (err error) {
	checksumMap := r.readChecksumMap()
	for i := 0; i < 20; i++ {
		err = r.storeRepo(checksumMap)
		if err == nil {
			return
		}

		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode {
			if uerr.StatusCode == 404 {
				log.Printf("Got 404, presumably temporarily, retrying...\n")
			} else {
				return err
			}
		}

		_, checksumError := err.(*util.ChecksumError)
		if checksumError {
			log.Printf(err.Error())
			log.Printf("Checksum did not match, presumably the repo was published while syncing, retrying...\n")
		}

		_, signatureError := err.(*SignatureError)
		if signatureError {
			log.Printf(err.Error())
			log.Printf("Signature not valid, presumably the repo was published while syncing, retrying...\n")
		} else {
			return err
		}
	}

	log.Printf("Too many temporary errors, aborting...\n")
	return err
}

// StoreRepo stores an HTTP repo in a Storage
func (r *Syncer) storeRepo(checksumMap map[string]XMLChecksum) (err error) {
	packagesToDownload, packagesToRecycle, err := r.processMetadata(checksumMap)
	if err != nil {
		return
	}

	downloadCount := len(packagesToDownload)
	log.Printf("Downloading %v packages...\n", downloadCount)
	for i, pack := range packagesToDownload {
		description := fmt.Sprintf("(%v/%v) %v", i+1, downloadCount, path.Base(pack.Location.Href))
		err = r.downloadStoreApply(pack.Location.Href, pack.Checksum.Checksum, description, hashMap[pack.Checksum.Type], util.Nop)
		if err != nil {
			return err
		}
	}

	recycleCount := len(packagesToRecycle)
	log.Printf("Recycling %v packages...\n", recycleCount)
	for _, pack := range packagesToRecycle {
		err = r.storage.Recycle(pack.Location.Href)
		if err != nil {
			return
		}
	}

	log.Printf("Committing changes...\n")
	err = r.storage.Commit()
	if err != nil {
		return
	}
	return
}

// downloadStore downloads a repo-relative path into a file
func (r *Syncer) downloadStore(path string, description string) error {
	return r.downloadStoreApply(path, "", description, 0, util.Nop)
}

// downloadStoreApply downloads a repo-relative path into a file, while applying a ReaderConsumer
func (r *Syncer) downloadStoreApply(relativePath string, checksum string, description string, hash crypto.Hash, f util.ReaderConsumer) error {
	log.Printf("Downloading %v...", description)
	url := r.URL
	url.Path = path.Join(r.URL.Path, relativePath)
	body, err := ReadURL(url.String())
	if err != nil {
		return err
	}

	return util.Compose(r.storage.StoringMapper(relativePath, checksum, hash), f)(body)
}

// processMetadata stores the repo metadata and returns a list of package file
// paths to download
func (r *Syncer) processMetadata(checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	err = r.downloadStoreApply(repomdPath, "", path.Base(repomdPath), 0, func(reader io.ReadCloser) (err error) {
		b, err := ioutil.ReadAll(reader)
		if err != nil {
			return
		}

		err = r.checkRepomdSignature(bytes.NewReader(b))
		if err != nil {
			return
		}

		decoder := xml.NewDecoder(bytes.NewReader(b))
		var repomd XMLRepomd
		err = decoder.Decode(&repomd)
		if err != nil {
			return
		}

		data := repomd.Data
		for i := 0; i < len(data); i++ {
			log.Printf(data[i].Location.Href)
			metadataLocation := data[i].Location.Href
			metadataChecksum := data[i].Checksum
			decision := r.decide(metadataLocation, metadataChecksum, checksumMap)
			switch decision {
			case Download:
				log.Printf("...downloading")
				err = r.downloadStoreApply(metadataLocation, metadataChecksum.Checksum, path.Base(metadataLocation), hashMap[metadataChecksum.Type], util.Nop)
				if err != nil {
					return
				}
			case Recycle:
				log.Printf("...recycling")
				r.storage.Recycle(metadataLocation)
			}

			if data[i].Type == "primary" {
				packagesToDownload, packagesToRecycle, err = r.processPrimary(metadataLocation, checksumMap)
			}
		}
		return
	})
	if err != nil {
		return
	}

	return
}

func (r *Syncer) checkRepomdSignature(repomdReader io.Reader) (err error) {
	ascPath := repomdPath + ".asc"
	keyPath := repomdPath + ".key"

	err = r.downloadStoreApply(ascPath, "", path.Base(ascPath), 0, func(signatureReader io.ReadCloser) (err error) {
		err = r.downloadStoreApply(keyPath, "", path.Base(keyPath), 0, func(keyReader io.ReadCloser) (err error) {
			keyring, err := openpgp.ReadArmoredKeyRing(keyReader)
			if err != nil {
				return &SignatureError{"repomd.key file does not contain a valid signature"}
			}
			_, err = openpgp.CheckArmoredDetachedSignature(keyring, repomdReader, signatureReader)
			if err != nil {
				return &SignatureError{"repomd.asc signature check failed, signature is not valid"}
			}
			return
		})
		if err != nil {
			uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
			if unexpectedStatusCode && uerr.StatusCode == 404 {
				log.Printf("Got 404, ignoring...")
				err = nil
			}
		}
		return
	})
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
			err = nil
		}
	}
	return
}

// SignatureError is returned if a signature was found but it's invalid
type SignatureError struct {
	reason string
}

func (e *SignatureError) Error() string {
	return fmt.Sprintf("Signature error: %s", e.reason)
}

func (r *Syncer) readMetaData(reader io.Reader) (primary XMLMetaData, err error) {
	gzReader, err := gzip.NewReader(reader)
	if err != nil {
		return
	}
	defer gzReader.Close()

	decoder := xml.NewDecoder(gzReader)
	err = decoder.Decode(&primary)

	return
}

func (r *Syncer) readChecksumMap() (checksumMap map[string]XMLChecksum) {
	checksumMap = make(map[string]XMLChecksum)
	repomdReader, err := r.storage.NewReader(repomdPath, Permanent)
	if err != nil {
		if err == ErrFileNotFound {
			log.Println("First-time sync started")
		} else {
			log.Println("Error while reading previously-downloaded metadata. Starting sync from scratch")
		}
		return
	}
	defer repomdReader.Close()

	decoder := xml.NewDecoder(repomdReader)
	var repomd XMLRepomd
	err = decoder.Decode(&repomd)
	if err != nil {
		log.Println("Error while parsing previously-downloaded metadata. Starting sync from scratch")
		return
	}

	data := repomd.Data
	for i := 0; i < len(data); i++ {
		dataHref := data[i].Location.Href
		dataChecksum := data[i].Checksum
		checksumMap[dataHref] = dataChecksum
		if data[i].Type == "primary" {
			primaryReader, err := r.storage.NewReader(dataHref, Permanent)
			if err != nil {
				return
			}
			primary, err := r.readMetaData(primaryReader)
			if err != nil {
				return
			}
			for _, pack := range primary.Packages {
				checksumMap[pack.Location.Href] = pack.Checksum
			}
		}
	}
	return
}

// processPrimary stores the primary XML metadata file and returns a list of
// package file paths to download
func (r *Syncer) processPrimary(path string, checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	reader, err := r.storage.NewReader(path, Temporary)
	if err != nil {
		return
	}
	primary, err := r.readMetaData(reader)
	if err != nil {
		return
	}

	allArchs := len(r.archs) == 0
	for _, pack := range primary.Packages {
		if allArchs || pack.Arch == "noarch" || r.archs[pack.Arch] {
			decision := r.decide(pack.Location.Href, pack.Checksum, checksumMap)
			switch decision {
			case Download:
				packagesToDownload = append(packagesToDownload, pack)
			case Recycle:
				packagesToRecycle = append(packagesToRecycle, pack)
			}
		}
	}
	return
}

// Decision encodes what to do with a file
type Decision int

const (
	// Download means the Syncer will download a file
	Download Decision = iota
	// Recycle means the Syncer will copy an existing file without downloading
	Recycle
	// Skip means the Syncer detected an already-existing file and has nothing to do
	Skip
)

func (r *Syncer) decide(location string, checksum XMLChecksum, checksumMap map[string]XMLChecksum) Decision {
	previousChecksum, foundInPermanentLocation := checksumMap[location]
	if !foundInPermanentLocation || previousChecksum.Type != checksum.Type || previousChecksum.Checksum != checksum.Checksum {
		reader, err := r.storage.NewReader(location, Temporary)
		if err != nil {
			return Download
		}
		defer reader.Close()
		readChecksum, err := util.Checksum(reader, hashMap[checksum.Type])
		if err != nil || readChecksum != checksum.Checksum {
			return Download
		}
		return Skip
	}
	return Recycle
}
<|endoftext|>


<commit_before>package get

import (
	"compress/gzip"
	"crypto"
	"encoding/xml"
	"io"
	"log"

	"github.com/moio/minima/util"
)

// common

// XMLLocation maps a <location> tag in repodata/repomd.xml or repodata/<ID>-primary.xml.gz
type XMLLocation struct {
	Href string `xml:"href,attr"`
}

// repodata/repomd.xml

// XMLRepomd maps a <repomd> tag in repodata/repomd.xml
type XMLRepomd struct {
	Data []XMLData `xml:"data"`
}

// XMLData maps a <data> tag in repodata/repomd.xml
type XMLData struct {
	Type     string      `xml:"type,attr"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// repodata/<ID>-primary.xml.gz

// XMLMetaData maps a <metadata> tag in repodata/<ID>-primary.xml.gz
type XMLMetaData struct {
	Packages []XMLPackage `xml:"package"`
}

// XMLPackage maps a <package> tag in repodata/<ID>-primary.xml.gz
type XMLPackage struct {
	Arch     string      `xml:"arch"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// XMLChecksum maps a <checksum> tag in repodata/<ID>-primary.xml.gz
type XMLChecksum struct {
	Type     string `xml:"type,attr"`
	Checksum string `xml:",cdata"`
}

var hashMap = map[string]crypto.Hash{
	"sha":    crypto.SHA1,
	"sha1":   crypto.SHA1,
	"sha256": crypto.SHA256,
}

const repomdPath = "repodata/repomd.xml"

// Syncer syncs repos from an HTTP source to a Storage
type Syncer struct {
	// URL of the repo this syncer syncs
	Url     string
	archs   map[string]bool
	storage Storage
}

// NewSyncer creates a new Syncer
func NewSyncer(url string, archs map[string]bool, storage Storage) *Syncer {
	return &Syncer{url, archs, storage}
}

// StoreRepo stores an HTTP repo in a Storage, automatically retrying in case of recoverable errors
func (r *Syncer) StoreRepo() (err error) {
	checksumMap := r.readChecksumMap()
	for i := 0; i < 10; i++ {
		err = r.storeRepo(checksumMap)
		if err == nil {
			return
		}

		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode {
			if uerr.StatusCode == 404 {
				log.Printf("Got 404, presumably temporarily, retrying...\n")
			} else {
				return err
			}
		}

		_, checksumError := err.(*util.ChecksumError)
		if checksumError {
			log.Printf("Checksum did not match, presumably the repo was published while syncing, retrying...\n")
		} else {
			return err
		}
	}

	log.Printf("Too many temporary errors, aborting...\n")
	return err
}

// StoreRepo stores an HTTP repo in a Storage
func (r *Syncer) storeRepo(checksumMap map[string]XMLChecksum) (err error) {
	packagesToDownload, packagesToRecycle, err := r.processMetadata(checksumMap)
	if err != nil {
		return
	}

	downloadCount := len(packagesToDownload)
	log.Printf("Downloading %v packages...\n", downloadCount)
	for _, pack := range packagesToDownload {
		err = r.downloadStoreApply(pack.Location.Href, pack.Checksum.Checksum, hashMap[pack.Checksum.Type], util.Nop)
		if err != nil {
			return err
		}
	}

	recycleCount := len(packagesToRecycle)
	log.Printf("Recycling %v packages...\n", recycleCount)
	for _, pack := range packagesToRecycle {
		err = r.storage.Recycle(pack.Location.Href)
		if err != nil {
			return
		}
	}

	log.Printf("Committing changes...\n")
	err = r.storage.Commit()
	if err != nil {
		return
	}
	return
}

// downloadStore downloads a repo-relative path into a file
func (r *Syncer) downloadStore(path string) error {
	return r.downloadStoreApply(path, "", 0, util.Nop)
}

// downloadStoreApply downloads a repo-relative path into a file, while applying a ReaderConsumer
func (r *Syncer) downloadStoreApply(path string, checksum string, hash crypto.Hash, f util.ReaderConsumer) error {
	log.Printf("Downloading %v...", path)
	body, err := ReadURL(r.Url + "/" + path)
	if err != nil {
		return err
	}

	return util.Compose(r.storage.StoringMapper(path, checksum, hash), f)(body)
}

// processMetadata stores the repo metadata and returns a list of package file
// paths to download
func (r *Syncer) processMetadata(checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	err = r.downloadStoreApply(repomdPath, "", 0, func(reader io.ReadCloser) (err error) {
		decoder := xml.NewDecoder(reader)
		var repomd XMLRepomd
		err = decoder.Decode(&repomd)
		if err != nil {
			return
		}

		data := repomd.Data
		for i := 0; i < len(data); i++ {
			metadataPath := data[i].Location.Href
			if data[i].Type == "primary" {
				packagesToDownload, packagesToRecycle, err = r.processPrimary(metadataPath, checksumMap)
			} else {
				err = r.downloadStore(metadataPath)
			}
			if err != nil {
				return
			}
		}
		return
	})
	if err != nil {
		return
	}

	err = r.downloadStore(repomdPath + ".asc")
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
		} else {
			return
		}
	}

	err = r.downloadStore(repomdPath + ".key")
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
			err = nil
		} else {
			return
		}
	}

	return
}

func (r *Syncer) readMetaData(reader io.Reader) (primary XMLMetaData, err error) {
	gzReader, err := gzip.NewReader(reader)
	if err != nil {
		return
	}
	defer gzReader.Close()

	decoder := xml.NewDecoder(gzReader)
	err = decoder.Decode(&primary)

	return
}

func (r *Syncer) readChecksumMap() (checksumMap map[string]XMLChecksum) {
	checksumMap = make(map[string]XMLChecksum)
	repomdReader, err := r.storage.NewReader(repomdPath, Permanent)
	if err != nil {
		if err == ErrFileNotFound {
			log.Println("First-time sync started")
		} else {
			log.Println(err.Error())
			log.Println("Error while reading previously-downloaded metadata. Starting sync from scratch")
		}
		return
	}
	defer repomdReader.Close()

	decoder := xml.NewDecoder(repomdReader)
	var repomd XMLRepomd
	err = decoder.Decode(&repomd)
	if err != nil {
		log.Println(err.Error())
		log.Println("Error while parsing previously-downloaded metadata. Starting sync from scratch")
		return
	}

	data := repomd.Data
	for i := 0; i < len(data); i++ {
		dataHref := data[i].Location.Href
		dataChecksum := data[i].Checksum
		checksumMap[dataHref] = dataChecksum
		if data[i].Type == "primary" {
			primaryReader, err := r.storage.NewReader(dataHref, Permanent)
			if err != nil {
				return
			}
			primary, err := r.readMetaData(primaryReader)
			if err != nil {
				return
			}
			for _, pack := range primary.Packages {
				checksumMap[pack.Location.Href] = pack.Checksum
			}
		}
	}
	return
}

// processPrimary stores the primary XML metadata file and returns a list of
// package file paths to download
func (r *Syncer) processPrimary(path string, checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	err = r.downloadStoreApply(path, "", 0, func(reader io.ReadCloser) (err error) {
		primary, err := r.readMetaData(reader)
		if err != nil {
			return
		}

		allArchs := len(r.archs) == 0
		for _, pack := range primary.Packages {
			if allArchs || pack.Arch == "noarch" || r.archs[pack.Arch] {
				previousChecksum, foundInPermanentLocation := checksumMap[pack.Location.Href]
				if !foundInPermanentLocation || previousChecksum.Type != pack.Checksum.Type || previousChecksum.Checksum != pack.Checksum.Checksum {
					reader, err := r.storage.NewReader(pack.Location.Href, Temporary)
					if err != nil {
						log.Printf("...package '%v' not found or not recyclable, will be downloaded\n", pack.Location.Href)
						packagesToDownload = append(packagesToDownload, pack)
					} else {
						checksum, err := util.Checksum(reader, hashMap[pack.Checksum.Type])
						if err != nil || checksum != pack.Checksum.Checksum {
							log.Printf("...package '%v' found in partially-downloaded repo, not recyclable, will be re-downloaded\n", pack.Location.Href)
							packagesToDownload = append(packagesToDownload, pack)
						} else {
							log.Printf("...package '%v' found in partially-downloaded repo, recyclable, will be skipped\n", pack.Location.Href)
						}
					}
				} else {
					log.Printf("...package '%v' found in already-downloaded repo, recyclable, will be recycled\n", pack.Location.Href)
					packagesToRecycle = append(packagesToRecycle, pack)
				}
			}
		}
		return
	})
	return
}
<commit_msg>Extend logic to metadata files<commit_after>package get

import (
	"compress/gzip"
	"crypto"
	"encoding/xml"
	"io"
	"log"

	"github.com/moio/minima/util"
)

// common

// XMLLocation maps a <location> tag in repodata/repomd.xml or repodata/<ID>-primary.xml.gz
type XMLLocation struct {
	Href string `xml:"href,attr"`
}

// repodata/repomd.xml

// XMLRepomd maps a <repomd> tag in repodata/repomd.xml
type XMLRepomd struct {
	Data []XMLData `xml:"data"`
}

// XMLData maps a <data> tag in repodata/repomd.xml
type XMLData struct {
	Type     string      `xml:"type,attr"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// repodata/<ID>-primary.xml.gz

// XMLMetaData maps a <metadata> tag in repodata/<ID>-primary.xml.gz
type XMLMetaData struct {
	Packages []XMLPackage `xml:"package"`
}

// XMLPackage maps a <package> tag in repodata/<ID>-primary.xml.gz
type XMLPackage struct {
	Arch     string      `xml:"arch"`
	Location XMLLocation `xml:"location"`
	Checksum XMLChecksum `xml:"checksum"`
}

// XMLChecksum maps a <checksum> tag in repodata/<ID>-primary.xml.gz
type XMLChecksum struct {
	Type     string `xml:"type,attr"`
	Checksum string `xml:",cdata"`
}

var hashMap = map[string]crypto.Hash{
	"sha":    crypto.SHA1,
	"sha1":   crypto.SHA1,
	"sha256": crypto.SHA256,
}

const repomdPath = "repodata/repomd.xml"

// Syncer syncs repos from an HTTP source to a Storage
type Syncer struct {
	// URL of the repo this syncer syncs
	Url     string
	archs   map[string]bool
	storage Storage
}

// NewSyncer creates a new Syncer
func NewSyncer(url string, archs map[string]bool, storage Storage) *Syncer {
	return &Syncer{url, archs, storage}
}

// StoreRepo stores an HTTP repo in a Storage, automatically retrying in case of recoverable errors
func (r *Syncer) StoreRepo() (err error) {
	checksumMap := r.readChecksumMap()
	for i := 0; i < 10; i++ {
		err = r.storeRepo(checksumMap)
		if err == nil {
			return
		}

		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode {
			if uerr.StatusCode == 404 {
				log.Printf("Got 404, presumably temporarily, retrying...\n")
			} else {
				return err
			}
		}

		_, checksumError := err.(*util.ChecksumError)
		if checksumError {
			log.Printf("Checksum did not match, presumably the repo was published while syncing, retrying...\n")
		} else {
			return err
		}
	}

	log.Printf("Too many temporary errors, aborting...\n")
	return err
}

// StoreRepo stores an HTTP repo in a Storage
func (r *Syncer) storeRepo(checksumMap map[string]XMLChecksum) (err error) {
	packagesToDownload, packagesToRecycle, err := r.processMetadata(checksumMap)
	if err != nil {
		return
	}

	downloadCount := len(packagesToDownload)
	log.Printf("Downloading %v packages...\n", downloadCount)
	for _, pack := range packagesToDownload {
		err = r.downloadStoreApply(pack.Location.Href, pack.Checksum.Checksum, hashMap[pack.Checksum.Type], util.Nop)
		if err != nil {
			return err
		}
	}

	recycleCount := len(packagesToRecycle)
	log.Printf("Recycling %v packages...\n", recycleCount)
	for _, pack := range packagesToRecycle {
		err = r.storage.Recycle(pack.Location.Href)
		if err != nil {
			return
		}
	}

	log.Printf("Committing changes...\n")
	err = r.storage.Commit()
	if err != nil {
		return
	}
	return
}

// downloadStore downloads a repo-relative path into a file
func (r *Syncer) downloadStore(path string) error {
	return r.downloadStoreApply(path, "", 0, util.Nop)
}

// downloadStoreApply downloads a repo-relative path into a file, while applying a ReaderConsumer
func (r *Syncer) downloadStoreApply(path string, checksum string, hash crypto.Hash, f util.ReaderConsumer) error {
	log.Printf("Downloading %v...", path)
	body, err := ReadURL(r.Url + "/" + path)
	if err != nil {
		return err
	}

	return util.Compose(r.storage.StoringMapper(path, checksum, hash), f)(body)
}

// processMetadata stores the repo metadata and returns a list of package file
// paths to download
func (r *Syncer) processMetadata(checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	err = r.downloadStoreApply(repomdPath, "", 0, func(reader io.ReadCloser) (err error) {
		decoder := xml.NewDecoder(reader)
		var repomd XMLRepomd
		err = decoder.Decode(&repomd)
		if err != nil {
			return
		}

		data := repomd.Data
		for i := 0; i < len(data); i++ {
			metadataLocation := data[i].Location.Href
			metadataChecksum := data[i].Checksum
			decision := r.decide(metadataLocation, metadataChecksum, checksumMap)
			switch decision {
			case Download:
				err = r.downloadStore(metadataLocation)
				if err != nil {
					return
				}
			case Recycle:
				r.storage.Recycle(metadataLocation)
			}

			if data[i].Type == "primary" {
				packagesToDownload, packagesToRecycle, err = r.processPrimary(metadataLocation, checksumMap)
			}
		}
		return
	})
	if err != nil {
		return
	}

	err = r.downloadStore(repomdPath + ".asc")
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
		} else {
			return
		}
	}

	err = r.downloadStore(repomdPath + ".key")
	if err != nil {
		uerr, unexpectedStatusCode := err.(*UnexpectedStatusCodeError)
		if unexpectedStatusCode && uerr.StatusCode == 404 {
			log.Printf("Got 404, ignoring...")
			err = nil
		} else {
			return
		}
	}

	return
}

func (r *Syncer) readMetaData(reader io.Reader) (primary XMLMetaData, err error) {
	gzReader, err := gzip.NewReader(reader)
	if err != nil {
		return
	}
	defer gzReader.Close()

	decoder := xml.NewDecoder(gzReader)
	err = decoder.Decode(&primary)

	return
}

func (r *Syncer) readChecksumMap() (checksumMap map[string]XMLChecksum) {
	checksumMap = make(map[string]XMLChecksum)
	repomdReader, err := r.storage.NewReader(repomdPath, Permanent)
	if err != nil {
		if err == ErrFileNotFound {
			log.Println("First-time sync started")
		} else {
			log.Println(err.Error())
			log.Println("Error while reading previously-downloaded metadata. Starting sync from scratch")
		}
		return
	}
	defer repomdReader.Close()

	decoder := xml.NewDecoder(repomdReader)
	var repomd XMLRepomd
	err = decoder.Decode(&repomd)
	if err != nil {
		log.Println(err.Error())
		log.Println("Error while parsing previously-downloaded metadata. Starting sync from scratch")
		return
	}

	data := repomd.Data
	for i := 0; i < len(data); i++ {
		dataHref := data[i].Location.Href
		dataChecksum := data[i].Checksum
		checksumMap[dataHref] = dataChecksum
		if data[i].Type == "primary" {
			primaryReader, err := r.storage.NewReader(dataHref, Permanent)
			if err != nil {
				return
			}
			primary, err := r.readMetaData(primaryReader)
			if err != nil {
				return
			}
			for _, pack := range primary.Packages {
				checksumMap[pack.Location.Href] = pack.Checksum
			}
		}
	}
	return
}

// processPrimary stores the primary XML metadata file and returns a list of
// package file paths to download
func (r *Syncer) processPrimary(path string, checksumMap map[string]XMLChecksum) (packagesToDownload []XMLPackage, packagesToRecycle []XMLPackage, err error) {
	reader, err := r.storage.NewReader(path, Temporary)
	if err != nil {
		return
	}
	primary, err := r.readMetaData(reader)
	if err != nil {
		return
	}

	allArchs := len(r.archs) == 0
	for _, pack := range primary.Packages {
		if allArchs || pack.Arch == "noarch" || r.archs[pack.Arch] {
			decision := r.decide(pack.Location.Href, pack.Checksum, checksumMap)
			switch decision {
			case Download:
				packagesToDownload = append(packagesToDownload, pack)
			case Recycle:
				packagesToRecycle = append(packagesToRecycle, pack)
			}
		}
	}
	return
}

// Decision encodes what to do with a file
type Decision int

const (
	// Download means the Syncer will download a file
	Download Decision = iota
	// Recycle means the Syncer will copy an existing file without downloading
	Recycle
	// Skip means the Syncer detected an already-existing file and has nothing to do
	Skip
)

func (r *Syncer) decide(location string, checksum XMLChecksum, checksumMap map[string]XMLChecksum) Decision {
	previousChecksum, foundInPermanentLocation := checksumMap[location]
	if !foundInPermanentLocation || previousChecksum.Type != checksum.Type || previousChecksum.Checksum != checksum.Checksum {
		reader, err := r.storage.NewReader(location, Temporary)
		if err != nil {
			log.Printf("...'%v' not found or not recyclable, will be downloaded\n", location)
			return Download
		}
		defer reader.Close()
		readChecksum, err := util.Checksum(reader, hashMap[checksum.Type])
		if err != nil || readChecksum != checksum.Checksum {
			log.Printf("...'%v' found in partially-downloaded repo, not recyclable, will be re-downloaded\n", location)
			return Download
		}
		log.Printf("...'%v' found in partially-downloaded repo, recyclable, will be skipped\n", location)
		return Skip
	}
	log.Printf("...'%v' found in already-downloaded repo, recyclable, will be recycled\n", location)
	return Recycle
}
<|endoftext|>


<commit_before>// Copyright 2013 Xing Xing <mikespook@gmail.com>.
// All rights reserved.
// Use of this source code is governed by a commercial
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/mikespook/ghoko"
	"github.com/mikespook/golib/pid"
	"github.com/mikespook/golib/signal"
)

var (
	addr       string
	scriptPath string
	secret     string
	tlsCert    string
	tlsKey     string
	pidFile    string
	rootUrl    string
)

func init() {
	flag.StringVar(&addr, "addr", ":3080", "Address of HTTP service")
	flag.StringVar(&scriptPath, "script", path.Dir(os.Args[0]), "Path of lua files")
	flag.StringVar(&secret, "secret", "", "Secret token")
	flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file")
	flag.StringVar(&tlsKey, "tls-key", "", "TLS key file")
	flag.StringVar(&pidFile, "pid", "", "PID file")
	flag.StringVar(&rootUrl, "root", "/", "Root path of URL")
	flag.Parse()
}

func main() {
	log.Printf("Starting: webhook=%q script=%q\n", ghoko.CallbackUrl(tlsCert, tlsKey, addr, rootUrl), scriptPath)
	if pidFile != "" {
		if p, err := pid.New(pidFile); err != nil {
			log.Fatalln(err)
		} else {
			defer func() {
				if err := p.Close(); err != nil {
					log.Fatalln(err)
				}
			}()
			log.Printf("PID: %d file=%q\n", p.Pid, pidFile)
		}
	}
	defer func() {
		log.Println("Exited!")
	}()

	// Begin
	p := path.Clean(scriptPath)
	ghk := ghoko.New(p, secret, rootUrl)
	go func() {
		defer func() {
			if err := signal.Send(os.Getpid(), os.Interrupt); err != nil {
				panic(err)
			}
		}()
		var err error
		if tlsCert == "" || tlsKey == "" {
			err = http.ListenAndServe(addr, ghk)
		} else {
			err = http.ListenAndServeTLS(addr, tlsCert, tlsKey, ghk)
		}
		if err != nil {
			log.Fatalln(err)
		}
	}()
	// End
	signal.Bind(os.Interrupt, func() uint {
		return signal.BreakExit
	})
	signal.Wait()
}
<commit_msg>added version<commit_after>// Copyright 2013 Xing Xing <mikespook@gmail.com>.
// All rights reserved.
// Use of this source code is governed by a commercial
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/mikespook/ghoko"
	"github.com/mikespook/golib/pid"
	"github.com/mikespook/golib/signal"
)

var (
	addr       string
	scriptPath string
	secret     string
	tlsCert    string
	tlsKey     string
	pidFile    string
	rootUrl    string
	version    string
	platform   string
	built      string
	ver        bool
)

func init() {
	flag.StringVar(&addr, "addr", ":3080", "Address of HTTP service")
	flag.StringVar(&scriptPath, "script", path.Dir(os.Args[0]), "Path of lua files")
	flag.StringVar(&secret, "secret", "", "Secret token")
	flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file")
	flag.StringVar(&tlsKey, "tls-key", "", "TLS key file")
	flag.StringVar(&pidFile, "pid", "", "PID file")
	flag.StringVar(&rootUrl, "root", "/", "Root path of URL")
	flag.BoolVar(&ver, "version", false, "Display version of ghoko")
	flag.Parse()
}

func main() {
	if ver {
		log.Printf("Version: %s (%s) [%s]", version, platform, built)
		return
	}

	log.Printf("Starting: webhook=%q script=%q\n", ghoko.CallbackUrl(tlsCert, tlsKey, addr, rootUrl), scriptPath)
	if pidFile != "" {
		if p, err := pid.New(pidFile); err != nil {
			log.Fatalln(err)
		} else {
			defer func() {
				if err := p.Close(); err != nil {
					log.Fatalln(err)
				}
			}()
			log.Printf("PID: %d file=%q\n", p.Pid, pidFile)
		}
	}
	defer func() {
		log.Println("Exited!")
	}()

	// Begin
	p := path.Clean(scriptPath)
	ghk := ghoko.New(p, secret, rootUrl)
	go func() {
		defer func() {
			if err := signal.Send(os.Getpid(), os.Interrupt); err != nil {
				panic(err)
			}
		}()
		var err error
		if tlsCert == "" || tlsKey == "" {
			err = http.ListenAndServe(addr, ghk)
		} else {
			err = http.ListenAndServeTLS(addr, tlsCert, tlsKey, ghk)
		}
		if err != nil {
			log.Fatalln(err)
		}
	}()
	// End
	signal.Bind(os.Interrupt, func() uint {
		return signal.BreakExit
	})
	signal.Wait()
}
<|endoftext|>