<commit_before>//go:generate go get github.com/jteeuwen/go-bindata
//go:generate go install github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -debug -pkg hugo -prefix "assets" -o binary.go assets/...

// Package hugo makes the bridge between the static website generator Hugo
// and the webserver Caddy, also providing an administrative user interface.
package hugo

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"github.com/hacdias/caddy-filemanager"
	"github.com/hacdias/caddy-filemanager/assets"
	"github.com/hacdias/caddy-filemanager/directory"
	"github.com/hacdias/caddy-filemanager/frontmatter"
	"github.com/hacdias/caddy-filemanager/utils/variables"
	"github.com/hacdias/caddy-hugo/utils/commands"
	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/robfig/cron"
	"github.com/spf13/hugo/parser"
)

// Hugo is hugo
type Hugo struct {
	Next        httpserver.Handler
	Config      *Config
	FileManager *filemanager.FileManager
}

// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (h Hugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// If the site matches the baseURL
	if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL) {
		// Serve the hugo assets
		if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + AssetsURL) {
			return serveAssets(w, r, h.Config)
		}

		// Serve the filemanager assets
		if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + assets.BaseURL) {
			return h.FileManager.ServeHTTP(w, r)
		}

		// If the url matches exactly with /{admin}/settings/ serve that page
		// page variable isn't used here to avoid people using URLs like
		// "/{admin}/settings/something".
		if r.URL.Path == h.Config.BaseURL+"/settings/" || r.URL.Path == h.Config.BaseURL+"/settings" {
			var frontmatter string
			var err error

			if _, err = os.Stat(h.Config.Root + "config.yaml"); err == nil {
				frontmatter = "yaml"
			}

			if _, err = os.Stat(h.Config.Root + "config.json"); err == nil {
				frontmatter = "json"
			}

			if _, err = os.Stat(h.Config.Root + "config.toml"); err == nil {
				frontmatter = "toml"
			}

			http.Redirect(w, r, h.Config.BaseURL+"/config."+frontmatter, http.StatusTemporaryRedirect)
			return 0, nil
		}

		if r.Method == http.MethodPost && r.Header.Get("archetype") != "" {
			filename := r.Header.Get("Filename")
			archetype := r.Header.Get("archetype")

			if !strings.HasSuffix(filename, ".md") && !strings.HasSuffix(filename, ".markdown") {
				return h.FileManager.ServeHTTP(w, r)
			}

			filename = strings.Replace(r.URL.Path, h.Config.BaseURL+"/content/", "", 1) + filename
			filename = filepath.Clean(filename)

			args := []string{"new", filename, "--kind", archetype}

			if err := commands.Run(h.Config.Hugo, args, h.Config.Root); err != nil {
				return http.StatusInternalServerError, err
			}

			return http.StatusOK, nil
		}

		if directory.CanBeEdited(r.URL.Path) && r.Method == http.MethodPut {
			code, err := h.FileManager.ServeHTTP(w, r)

			if err != nil {
				return code, err
			}

			if r.Header.Get("Regenerate") == "true" {
				RunHugo(h.Config, false)
			}

			if r.Header.Get("Schedule") != "" {
				code, err = h.Schedule(w, r)
			}

			return code, err
		}

		return h.FileManager.ServeHTTP(w, r)
	}

	return h.Next.ServeHTTP(w, r)
}

// RunHugo is used to run the static website generator
func RunHugo(c *Config, force bool) {
	os.RemoveAll(c.Root + "public")

	// Prevent running if watching is enabled
	if b, pos := variables.StringInSlice("--watch", c.Args); b && !force {
		if len(c.Args) > pos && c.Args[pos+1] != "false" {
			return
		}

		if len(c.Args) == pos+1 {
			return
		}
	}

	if err := commands.Run(c.Hugo, c.Args, c.Root); err != nil {
		log.Panic(err)
	}
}

// Schedule schedules a post to be published later
func (h Hugo) Schedule(w http.ResponseWriter, r *http.Request) (int, error) {
	t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))

	if err != nil {
		return http.StatusInternalServerError, err
	}

	scheduler := cron.New()
	scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
		filename := r.URL.Path
		filename = strings.Replace(filename, h.Config.BaseURL, h.Config.Root, 1)
		filename = filepath.Clean(filename)

		raw, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Println(err)
			return
		}

		buffer := bytes.NewBuffer(raw)
		page, err := parser.ReadFrom(buffer)

		if err != nil {
			log.Println(err)
			return
		}

		content := strings.TrimSpace(string(page.Content()))
		front, err := frontmatter.Unmarshal(page.FrontMatter())

		if err != nil {
			log.Println(err)
			return
		}

		kind := reflect.TypeOf(front)

		if kind == reflect.TypeOf(map[interface{}]interface{}{}) {
			delete(front.(map[interface{}]interface{}), "draft")
			delete(front.(map[interface{}]interface{}), "Draft")
		} else {
			delete(front.(map[string]interface{}), "draft")
			delete(front.(map[string]interface{}), "Draft")
		}

		fm, _, err := directory.ParseFrontMatter(front, h.FileManager.Configs[0].FrontMatter)

		if err != nil {
			log.Println(err)
			return
		}

		f := new(bytes.Buffer)
		f.Write(fm)
		f.Write([]byte(content))
		file := f.Bytes()

		if err = ioutil.WriteFile(filename, file, 0666); err != nil {
			return
		}

		RunHugo(h.Config, false)
	})
	scheduler.Start()

	return http.StatusOK, nil
}

// serveAssets provides the needed assets for the front-end
func serveAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
	// gets the filename to be used with Assets function
	filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "public", 1)
	file, err := Asset(filename)
	if err != nil {
		return http.StatusNotFound, nil
	}

	// Get the file extension and its mimetype
	extension := filepath.Ext(filename)
	mediatype := mime.TypeByExtension(extension)

	// Write the header with the Content-Type and write the file
	// content to the buffer
	w.Header().Set("Content-Type", mediatype)
	w.Write(file)
	return 200, nil
}
<commit_msg>Remove debug option from assets<commit_after>//go:generate go get github.com/jteeuwen/go-bindata
//go:generate go install github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg hugo -prefix "assets" -o binary.go assets/...

// Package hugo makes the bridge between the static website generator Hugo
// and the webserver Caddy, also providing an administrative user interface.
package hugo

import (
	"bytes"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"github.com/hacdias/caddy-filemanager"
	"github.com/hacdias/caddy-filemanager/assets"
	"github.com/hacdias/caddy-filemanager/directory"
	"github.com/hacdias/caddy-filemanager/frontmatter"
	"github.com/hacdias/caddy-filemanager/utils/variables"
	"github.com/hacdias/caddy-hugo/utils/commands"
	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/robfig/cron"
	"github.com/spf13/hugo/parser"
)

// Hugo is hugo
type Hugo struct {
	Next        httpserver.Handler
	Config      *Config
	FileManager *filemanager.FileManager
}

// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (h Hugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// If the site matches the baseURL
	if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL) {
		// Serve the hugo assets
		if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + AssetsURL) {
			return serveAssets(w, r, h.Config)
		}

		// Serve the filemanager assets
		if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + assets.BaseURL) {
			return h.FileManager.ServeHTTP(w, r)
		}

		// If the url matches exactly with /{admin}/settings/ serve that page
		// page variable isn't used here to avoid people using URLs like
		// "/{admin}/settings/something".
		if r.URL.Path == h.Config.BaseURL+"/settings/" || r.URL.Path == h.Config.BaseURL+"/settings" {
			var frontmatter string
			var err error

			if _, err = os.Stat(h.Config.Root + "config.yaml"); err == nil {
				frontmatter = "yaml"
			}

			if _, err = os.Stat(h.Config.Root + "config.json"); err == nil {
				frontmatter = "json"
			}

			if _, err = os.Stat(h.Config.Root + "config.toml"); err == nil {
				frontmatter = "toml"
			}

			http.Redirect(w, r, h.Config.BaseURL+"/config."+frontmatter, http.StatusTemporaryRedirect)
			return 0, nil
		}

		if r.Method == http.MethodPost && r.Header.Get("archetype") != "" {
			filename := r.Header.Get("Filename")
			archetype := r.Header.Get("archetype")

			if !strings.HasSuffix(filename, ".md") && !strings.HasSuffix(filename, ".markdown") {
				return h.FileManager.ServeHTTP(w, r)
			}

			filename = strings.Replace(r.URL.Path, h.Config.BaseURL+"/content/", "", 1) + filename
			filename = filepath.Clean(filename)

			args := []string{"new", filename, "--kind", archetype}

			if err := commands.Run(h.Config.Hugo, args, h.Config.Root); err != nil {
				return http.StatusInternalServerError, err
			}

			return http.StatusOK, nil
		}

		if directory.CanBeEdited(r.URL.Path) && r.Method == http.MethodPut {
			code, err := h.FileManager.ServeHTTP(w, r)

			if err != nil {
				return code, err
			}

			if r.Header.Get("Regenerate") == "true" {
				RunHugo(h.Config, false)
			}

			if r.Header.Get("Schedule") != "" {
				code, err = h.Schedule(w, r)
			}

			return code, err
		}

		return h.FileManager.ServeHTTP(w, r)
	}

	return h.Next.ServeHTTP(w, r)
}

// RunHugo is used to run the static website generator
func RunHugo(c *Config, force bool) {
	os.RemoveAll(c.Root + "public")

	// Prevent running if watching is enabled
	if b, pos := variables.StringInSlice("--watch", c.Args); b && !force {
		if len(c.Args) > pos && c.Args[pos+1] != "false" {
			return
		}

		if len(c.Args) == pos+1 {
			return
		}
	}

	if err := commands.Run(c.Hugo, c.Args, c.Root); err != nil {
		log.Panic(err)
	}
}

// Schedule schedules a post to be published later
func (h Hugo) Schedule(w http.ResponseWriter, r *http.Request) (int, error) {
	t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))

	if err != nil {
		return http.StatusInternalServerError, err
	}

	scheduler := cron.New()
	scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
		filename := r.URL.Path
		filename = strings.Replace(filename, h.Config.BaseURL, h.Config.Root, 1)
		filename = filepath.Clean(filename)

		raw, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Println(err)
			return
		}

		buffer := bytes.NewBuffer(raw)
		page, err := parser.ReadFrom(buffer)

		if err != nil {
			log.Println(err)
			return
		}

		content := strings.TrimSpace(string(page.Content()))
		front, err := frontmatter.Unmarshal(page.FrontMatter())

		if err != nil {
			log.Println(err)
			return
		}

		kind := reflect.TypeOf(front)

		if kind == reflect.TypeOf(map[interface{}]interface{}{}) {
			delete(front.(map[interface{}]interface{}), "draft")
			delete(front.(map[interface{}]interface{}), "Draft")
		} else {
			delete(front.(map[string]interface{}), "draft")
			delete(front.(map[string]interface{}), "Draft")
		}

		fm, _, err := directory.ParseFrontMatter(front, h.FileManager.Configs[0].FrontMatter)

		if err != nil {
			log.Println(err)
			return
		}

		f := new(bytes.Buffer)
		f.Write(fm)
		f.Write([]byte(content))
		file := f.Bytes()

		if err = ioutil.WriteFile(filename, file, 0666); err != nil {
			return
		}

		RunHugo(h.Config, false)
	})
	scheduler.Start()

	return http.StatusOK, nil
}

// serveAssets provides the needed assets for the front-end
func serveAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
	// gets the filename to be used with Assets function
	filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "public", 1)
	file, err := Asset(filename)
	if err != nil {
		return http.StatusNotFound, nil
	}

	// Get the file extension and its mimetype
	extension := filepath.Ext(filename)
	mediatype := mime.TypeByExtension(extension)

	// Write the header with the Content-Type and write the file
	// content to the buffer
	w.Header().Set("Content-Type", mediatype)
	w.Write(file)
	return 200, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"

	"os"
	"os/signal"
	"syscall"

	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/rpi"

	"github.com/gicmo/PiCo/hue"
)

func main() {
	fmt.Println("PiCo")

	client, err := hue.ReadConfig("/etc/pico")
	if err != nil {
		panic(err)
	}

	sigch := make(chan os.Signal, 2)
	signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)

	if err = embd.InitGPIO(); err != nil {
		panic(err)
	}

	defer embd.CloseGPIO()

	pin18, err := embd.NewDigitalPin(18)
	defer pin18.Close()

	if err != nil {
		panic(err)
	}

	pin18.SetDirection(embd.Out)

	btn, err := embd.NewDigitalPin(24)
	if err != nil {
		panic(err)
	}
	defer btn.Close()

	btn.SetDirection(embd.In)
	btn.ActiveLow(false)

	pressed := make(chan time.Time, 2)
	err = btn.Watch(embd.EdgeFalling, func(btn embd.DigitalPin) {
		pressed <- time.Now()
	})

	onoff := embd.High
	pin18.Write(onoff)

	last_button := time.Unix(0, 0)

	ctchan := make(chan error, 2)

loop:
	for {
		select {
		case pressed_time := <-pressed:
			duration := pressed_time.Sub(last_button)
			if duration > 500*time.Millisecond {
				last_button = pressed_time
				pin18.Write(embd.Low)

				go func(clt *hue.Client, ch chan error) {
					herr := client.Toggle()
					ch <- herr
				}(&client, ctchan)

			}

		case sig := <-sigch:
			fmt.Println("Got signal", sig)
			break loop

		case err = <-ctchan:
			if err != nil {

			}

			pin18.Write(embd.High)
		}
	}
}
<commit_msg>Add some very simple button debouncing<commit_after>package main

import (
	"fmt"
	"time"

	"os"
	"os/signal"
	"syscall"

	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/rpi"

	"github.com/gicmo/PiCo/hue"
)

func main() {
	fmt.Println("PiCo")

	client, err := hue.ReadConfig("/etc/pico")
	if err != nil {
		panic(err)
	}

	sigch := make(chan os.Signal, 2)
	signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)

	if err = embd.InitGPIO(); err != nil {
		panic(err)
	}

	defer embd.CloseGPIO()

	pin18, err := embd.NewDigitalPin(18)
	defer pin18.Close()

	if err != nil {
		panic(err)
	}

	pin18.SetDirection(embd.Out)

	btn, err := embd.NewDigitalPin(24)
	if err != nil {
		panic(err)
	}
	defer btn.Close()

	btn.SetDirection(embd.In)
	btn.ActiveLow(false)

	pressed := make(chan time.Time, 2)
	err = btn.Watch(embd.EdgeBoth, func(btn embd.DigitalPin) {
		pressed <- time.Now()
	})

	bs, _ := btn.Read()
	fmt.Printf("[D] Button state: %v\n", bs)

	onoff := embd.High
	pin18.Write(onoff)

	last_button := time.Unix(0, 0)
	ctchan := make(chan error, 2)
	bc := make(chan int, 1)
	busy := false

loop:
	for {
		select {
		case pressed_time := <-pressed:
			bs, _ = btn.Read()
			fmt.Printf("[D] B: %v\n", bs)
			duration := pressed_time.Sub(last_button)
			if !busy && duration > 500*time.Millisecond {
				fmt.Printf("[D] Key pressed [starting timer]\n")
				last_button = pressed_time
				busy = true
				pin18.Write(embd.Low)

				time.AfterFunc(200*time.Millisecond, func() {
					if state, berr := btn.Read(); berr != nil {
						fmt.Printf("[E] Button read error\n")
						bc <- 0
					} else {
						bc <- state
					}
				})
			}

		case bstate := <-bc:
			fmt.Printf("[D] Timer report: %v\n", bstate)
			if bstate != 0 {
				go func(clt *hue.Client, ch chan error) {
					herr := client.Toggle()
					ch <- herr
				}(&client, ctchan)
			}

		case sig := <-sigch:
			fmt.Println("[I] Got signal", sig)
			break loop

		case err = <-ctchan:
			if err != nil {
				fmt.Printf("[W] toggle: %v", err)
			}
			busy = false
			pin18.Write(embd.High)
		}
	}
}
<|endoftext|>


<commit_before>// Package imap partially implements the Internet Message Access Protocol as
// defined in RFC 3501.
//
// Untagged IMAP responses are parsed into a Mailbox struct, which tracks all
// currently known information concerning the state of the remote mailbox.
// Because no significant information is returned through tagged responses,
// interaction with Mailbox is necessary for all queries.

package main

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	tp "net/textproto"
	"strings"
	"sync"
)

// The IMAP client.
type Client struct {
	// The underlying textproto connection may be used to extend the
	// functionality of this package; however, using Client.Cmd instead is
	// recommended, as doing so better preserves thread-safety.
	Text *tp.Conn

	Box  *Mailbox

	conn net.Conn // underlying raw connection.
	tags map[string]chan string
	tMut *sync.Mutex

	lit  chan string // channel where the literal string to be dumped is stored
}

// Represents the current known state of the remote server.
type Mailbox struct {
	capabilities []string
	mut          *sync.RWMutex
}

func (m *Mailbox) Capable(c string) bool {
	m.mut.RLock()
	for _, ca := range m.capabilities {
		if c == ca {
			return true
		}
	}
	m.mut.RUnlock()
	return false
}

// Dial creates an unsecured connection to the IMAP server at the given address
// and returns the corresponding Client.
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// DialTLS creates a TLS_secured connection to the IMAP server at the given
// address and returns the corresponding Client.
func DialTLS(addr string) (*Client, error) {
	conn, err := tls.Dial("tcp", addr, nil)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// NewClient returns a new Client using an existing connection.
func NewClient(conn net.Conn) (*Client, error) {
	text := tp.NewConn(conn)
	client := &Client{
		Text: text,
		Box:  &Mailbox{
			capabilities: []string{},
			mut: new(sync.RWMutex),
		},
		conn: conn,
		tags: map[string]chan string{},
		tMut: new(sync.Mutex),
		lit: make(chan string),
	}

	input := make(chan string)

	// Read all input from conn
	go func() {
		l, err := text.ReadLine()
		for err == nil {
			input <- l
			l, err = text.ReadLine()
		}
		if err == io.EOF {
			close(input)
		} else {
			panic(err)
		}
	}()

	// Start the serving goroutine
	go func() {
		for {
			select {
			case l := <-input:
				if l[0] == '+' {
					// server is ready for transmission of literal string
					client.Text.PrintfLine(<-client.lit)
					continue
				} else if isUntagged(l) {
					client.handleUntagged(l[2:])
					continue
				}
				// handle tagged response
				ps := strings.SplitN(l, " ", 2)
				tag := ps[0]
				l = ps[1]
				client.tMut.Lock()
				client.tags[tag] <- l
				client.tMut.Unlock()
			}
		}
	}()
	return client, nil
}

func (c *Client) handleUntagged(l string) {
	c.Box.mut.Lock()
	switch l[0:strings.Index(l, " ")] {
	case "CAPABILITY":
		c.Box.capabilities = strings.Split(l, " ")[1:]
	default:
		println(l)
	}
	c.Box.mut.Unlock()
}

// Sends a command and retreives the tagged response.
func (c *Client) Cmd(format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s", tag, fmt.Sprintf(format, args...))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

// Equivalent to Cmd, but the first argument (which will be rotated to be the
// last) is sent as a literal string.
func (c *Client) CmdLit(lit, format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s {%d}", tag, fmt.Sprintf(format, args...), len(lit))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	c.lit <- lit

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

func isUntagged(l string) bool {
	return l[0] != 'x' // all tags are x00
}

// Noop sends a NOOP command to the server, which may be abused to test that
// the connection is still working, or keep it active.
func (c *Client) Noop() error {
	return c.Cmd("NOOP")
}

// Capability determines the server's capabilities.
func (c *Client) Capability() error {
	return c.Cmd("CAPABILITY")
}

// Login authenticates a client using the provided username and password. This
// method is only secure if TLS is being used. AUTHENTICATE and STARTTLS are
// not supported.
func (c *Client) Login(username, password string) error {
	return c.Cmd("LOGIN %s %s", username, password)
}

// Logout closes the connection, after instructing the server to do the same.
func (c *Client) Logout() error {
	return c.Cmd("LOGOUT")
}

// Select selects the specified IMAP mailbox, updating its information in the
// Mailbox object.
func (c *Client) Select(mb string) error {
	return c.Cmd(`SELECT "%s"`, mb)
}

// Examine is identical to select, but marks the mailbox read-only.
func (c *Client) Examine(mb string) error {
	return c.Cmd(`EXAMINE "%s"`, mb)
}

// Create creates the named mailbox.
func (c *Client) Create(mb string) error {
	return c.Cmd(`CREATE "%s"`, mb)
}

// Delete deletes the named mailbox.
func (c *Client) Delete(mb string) error {
	return c.Cmd(`DELETE "%s"`, mb)
}

// Rename renames the named mailbox to the new name.
func (c *Client) Rename(mb, name string) error {
	return c.Cmd(`RENAME "%s" "%s"`, mb, name)
}

// Subscribe adds the named mailbox to the list of "active" or "subscribed"
// mailboxes, to be used with Lsub .
func (c *Client) Subscribe(mb string) error {
	return c.Cmd(`SUBSCRIBE "%s"`, mb)
}

// Unsubscribe removes the named mailbox from the server's list of "active"
// mailboxes.
func (c *Client) Unsubscribe(mb string) error {
	return c.Cmd(`UNSUBSCRIBE "%s"`, mb)
}

// List lists all folder within basename that match the wildcard expression mb.
// The result is put into the Client's Mailbox struct.
func (c *Client) List(basename, mb string) error {
	return c.Cmd(`LIST "%s" "%s"`, basename, mb)
}

// Lsub is like List, but only operates on "active" mailboxes, as set with
// Subscribe and Unsubscribe.
func (c *Client) Lsub(basename, mb string) error {
	return c.Cmd(`LSUB "%s" "%s"`, basename, mb)
}

// Status queries the specified statuses of the indicated mailbox. This command
// should not be used on the currently selected mailbox. The legal status items
// are:
//
//	MESSAGES	The number of messages in the mailbox.
//	RECENT		The number of messages with the \Recent flag set.
//	UIDNEXT		The next unique identifier value of the mailbox.
//	UIDVALIDITY	The unique identifier validity value of the mailbox.
//	UNSEEN		The number of messages which do not have the \Seen flag set.
//
func (c *Client) Status(mb string, ss ...string) error {
	st := sliceAsString(ss)
	return c.Cmd(`STATUS "%s" %s`, mb, st)
}

// Append appends a message to the specified mailbox, which must exist.
//
// TODO handle flags and the optional date/time string.
func (c *Client) Append(mb, message string) error {
	return c.CmdLit(message, "APPEND \"%s\"", mb)
}

// CHECK

// CLOSE

// EXPUNGE

// SEARCH

// FETCH

// STORE

// COPY

// UID

// Converts a slice of strings to a parenthesized list of space-separated
// strings.
func sliceAsString(ss []string) string {
	return "(" + strings.Join(ss, " ") + ")"
}
<commit_msg>CHECK<commit_after>// Package imap partially implements the Internet Message Access Protocol as
// defined in RFC 3501.
//
// Untagged IMAP responses are parsed into a Mailbox struct, which tracks all
// currently known information concerning the state of the remote mailbox.
// Because no significant information is returned through tagged responses,
// interaction with Mailbox is necessary for all queries.

package main

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	tp "net/textproto"
	"strings"
	"sync"
)

// The IMAP client.
type Client struct {
	// The underlying textproto connection may be used to extend the
	// functionality of this package; however, using Client.Cmd instead is
	// recommended, as doing so better preserves thread-safety.
	Text *tp.Conn

	Box  *Mailbox

	conn net.Conn // underlying raw connection.
	tags map[string]chan string
	tMut *sync.Mutex

	lit  chan string // channel where the literal string to be dumped is stored
}

// Represents the current known state of the remote server.
type Mailbox struct {
	capabilities []string
	mut          *sync.RWMutex
}

func (m *Mailbox) Capable(c string) bool {
	m.mut.RLock()
	for _, ca := range m.capabilities {
		if c == ca {
			return true
		}
	}
	m.mut.RUnlock()
	return false
}

// Dial creates an unsecured connection to the IMAP server at the given address
// and returns the corresponding Client.
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// DialTLS creates a TLS_secured connection to the IMAP server at the given
// address and returns the corresponding Client.
func DialTLS(addr string) (*Client, error) {
	conn, err := tls.Dial("tcp", addr, nil)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// NewClient returns a new Client using an existing connection.
func NewClient(conn net.Conn) (*Client, error) {
	text := tp.NewConn(conn)
	client := &Client{
		Text: text,
		Box:  &Mailbox{
			capabilities: []string{},
			mut: new(sync.RWMutex),
		},
		conn: conn,
		tags: map[string]chan string{},
		tMut: new(sync.Mutex),
		lit: make(chan string),
	}

	input := make(chan string)

	// Read all input from conn
	go func() {
		l, err := text.ReadLine()
		for err == nil {
			input <- l
			l, err = text.ReadLine()
		}
		if err == io.EOF {
			close(input)
		} else {
			panic(err)
		}
	}()

	// Start the serving goroutine
	go func() {
		for {
			select {
			case l := <-input:
				if l[0] == '+' {
					// server is ready for transmission of literal string
					client.Text.PrintfLine(<-client.lit)
					continue
				} else if isUntagged(l) {
					client.handleUntagged(l[2:])
					continue
				}
				// handle tagged response
				ps := strings.SplitN(l, " ", 2)
				tag := ps[0]
				l = ps[1]
				client.tMut.Lock()
				client.tags[tag] <- l
				client.tMut.Unlock()
			}
		}
	}()
	return client, nil
}

func (c *Client) handleUntagged(l string) {
	c.Box.mut.Lock()
	switch l[0:strings.Index(l, " ")] {
	case "CAPABILITY":
		c.Box.capabilities = strings.Split(l, " ")[1:]
	default:
		println(l)
	}
	c.Box.mut.Unlock()
}

// Sends a command and retreives the tagged response.
func (c *Client) Cmd(format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s", tag, fmt.Sprintf(format, args...))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

// Equivalent to Cmd, but the first argument (which will be rotated to be the
// last) is sent as a literal string.
func (c *Client) CmdLit(lit, format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s {%d}", tag, fmt.Sprintf(format, args...), len(lit))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	c.lit <- lit

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

func isUntagged(l string) bool {
	return l[0] != 'x' // all tags are x00
}

// Noop sends a NOOP command to the server, which may be abused to test that
// the connection is still working, or keep it active.
func (c *Client) Noop() error {
	return c.Cmd("NOOP")
}

// Capability determines the server's capabilities.
func (c *Client) Capability() error {
	return c.Cmd("CAPABILITY")
}

// Login authenticates a client using the provided username and password. This
// method is only secure if TLS is being used. AUTHENTICATE and STARTTLS are
// not supported.
func (c *Client) Login(username, password string) error {
	return c.Cmd("LOGIN %s %s", username, password)
}

// Logout closes the connection, after instructing the server to do the same.
func (c *Client) Logout() error {
	return c.Cmd("LOGOUT")
}

// Select selects the specified IMAP mailbox, updating its information in the
// Mailbox object.
func (c *Client) Select(mb string) error {
	return c.Cmd(`SELECT "%s"`, mb)
}

// Examine is identical to select, but marks the mailbox read-only.
func (c *Client) Examine(mb string) error {
	return c.Cmd(`EXAMINE "%s"`, mb)
}

// Create creates the named mailbox.
func (c *Client) Create(mb string) error {
	return c.Cmd(`CREATE "%s"`, mb)
}

// Delete deletes the named mailbox.
func (c *Client) Delete(mb string) error {
	return c.Cmd(`DELETE "%s"`, mb)
}

// Rename renames the named mailbox to the new name.
func (c *Client) Rename(mb, name string) error {
	return c.Cmd(`RENAME "%s" "%s"`, mb, name)
}

// Subscribe adds the named mailbox to the list of "active" or "subscribed"
// mailboxes, to be used with Lsub .
func (c *Client) Subscribe(mb string) error {
	return c.Cmd(`SUBSCRIBE "%s"`, mb)
}

// Unsubscribe removes the named mailbox from the server's list of "active"
// mailboxes.
func (c *Client) Unsubscribe(mb string) error {
	return c.Cmd(`UNSUBSCRIBE "%s"`, mb)
}

// List lists all folder within basename that match the wildcard expression mb.
// The result is put into the Client's Mailbox struct.
func (c *Client) List(basename, mb string) error {
	return c.Cmd(`LIST "%s" "%s"`, basename, mb)
}

// Lsub is like List, but only operates on "active" mailboxes, as set with
// Subscribe and Unsubscribe.
func (c *Client) Lsub(basename, mb string) error {
	return c.Cmd(`LSUB "%s" "%s"`, basename, mb)
}

// Status queries the specified statuses of the indicated mailbox. This command
// should not be used on the currently selected mailbox. The legal status items
// are:
//
//	MESSAGES	The number of messages in the mailbox.
//	RECENT		The number of messages with the \Recent flag set.
//	UIDNEXT		The next unique identifier value of the mailbox.
//	UIDVALIDITY	The unique identifier validity value of the mailbox.
//	UNSEEN		The number of messages which do not have the \Seen flag set.
//
func (c *Client) Status(mb string, ss ...string) error {
	st := sliceAsString(ss)
	return c.Cmd(`STATUS "%s" %s`, mb, st)
}

// Append appends a message to the specified mailbox, which must exist.
//
// TODO handle flags and the optional date/time string.
func (c *Client) Append(mb, message string) error {
	return c.CmdLit(message, "APPEND \"%s\"", mb)
}

// Check tells the server to perform any necessary housekeeping.
func (c *Client) Check() error {
	return c.Cmd(`CHECK`)
}

// CLOSE

// EXPUNGE

// SEARCH

// FETCH

// STORE

// COPY

// UID

// Converts a slice of strings to a parenthesized list of space-separated
// strings.
func sliceAsString(ss []string) string {
	return "(" + strings.Join(ss, " ") + ")"
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	text "github.com/tonnerre/golang-text"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func getPath(p string) string {
	path, _ := filepath.Abs(p)
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatal(err)
	}
	return path
}

func asKey(p string) string {
	basename := filepath.Base(p)
	return strings.TrimSuffix(basename, filepath.Ext(basename))
}

// LoadInfo loads an Info object from a file path
func LoadInfo(p string) (i Info, err error) {
	data, err := ioutil.ReadFile(p)
	if err != nil {
		log.Fatal("Reading file failed: ", p)
	}

	i = Info{ID: asKey(p)}
	yaml.Unmarshal(data, &i)
	return
}

// Info is the main storage for information. All yaml files map to this.
type Info struct {
	ID      string
	Type    string `yaml:"type"`
	Summary string `yaml:"summary"`
	Body    string `yaml:"body"`
	Host    string `yaml:"host"`
	Command string `yaml:"command"`
}

func (i Info) String() string {
	return fmt.Sprintf("I: %s", i.ID)
}

// Execute will figure out the type of the info and execute accordingly
func (i *Info) Execute() {
	if i.Type == "info" {
		i.PrintBody()
	} else if i.Type == "command" {
		i.ExecuteCommand()
	}
}

// PrintBody will pretty format the body of the item
func (i *Info) PrintBody() {
	out := text.Wrap(i.Body, 80)
	fmt.Println(out)
}

// ExecuteCommand will execute the command specified by the item
func (i *Info) ExecuteCommand() {
	sh, _ := exec.LookPath("sh")
	args := []string{sh, "-c", i.Command}

	cmd := exec.Cmd{
		Path:   sh,
		Args:   args,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}

	err := cmd.Run()
	if err != nil {
		log.Fatal("oh noes :(")
	}
}
<commit_msg>Add prompt and pretty colors to ExecuteCommand<commit_after>package main

import (
	"fmt"
	"github.com/fatih/color"
	text "github.com/tonnerre/golang-text"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func getPath(p string) string {
	path, _ := filepath.Abs(p)
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatal(err)
	}
	return path
}

func asKey(p string) string {
	basename := filepath.Base(p)
	return strings.TrimSuffix(basename, filepath.Ext(basename))
}

// LoadInfo loads an Info object from a file path
func LoadInfo(p string) (i Info, err error) {
	data, err := ioutil.ReadFile(p)
	if err != nil {
		log.Fatal("Reading file failed: ", p)
	}

	i = Info{ID: asKey(p)}
	yaml.Unmarshal(data, &i)
	return
}

// Info is the main storage for information. All yaml files map to this.
type Info struct {
	ID      string
	Type    string `yaml:"type"`
	Summary string `yaml:"summary"`
	Body    string `yaml:"body"`
	Host    string `yaml:"host"`
	Command string `yaml:"command"`
}

func (i Info) String() string {
	return fmt.Sprintf("I: %s", i.ID)
}

// Execute will figure out the type of the info and execute accordingly
func (i *Info) Execute() {
	if i.Type == "info" {
		i.PrintBody()
	} else if i.Type == "command" {
		i.ExecuteCommand()
	}
}

// PrintBody will pretty format the body of the item
func (i *Info) PrintBody() {
	out := text.Wrap(i.Body, 80)
	fmt.Println(out)
}

// ExecuteCommand will execute the command specified by the item
func (i *Info) ExecuteCommand() {
	sh, _ := exec.LookPath("sh")
	args := []string{sh, "-c", i.Command}

	cmd := exec.Cmd{
		Path:   sh,
		Args:   args,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}

	blue := color.New(color.FgBlue, color.Bold).SprintfFunc()
	magenta := color.New(color.FgMagenta, color.Bold).SprintfFunc()
	yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
	green := color.New(color.FgGreen, color.Bold).SprintfFunc()

	fmt.Println(
		fmt.Sprintf("%s: %s\nRuns %s on %s\n",
			blue(i.ID),
			magenta(i.Summary),
			yellow(i.Command),
			green(i.getHost()),
		),
	)

	if i.ask("Do you want to continue? [y/N] ") {
		err := cmd.Run()
		if err != nil {
			log.Fatal("oh noes :(")
		}
	} else {
		fmt.Println("Doing nothing.")
	}
}

func (i *Info) ask(prompt string) bool {
	var resp string

	fmt.Print(prompt)
	_, err := fmt.Scanln(&resp)
	if err != nil {
		if err.Error() != "unexpected newline" && err.Error() != "EOF" {
			log.Fatal(err)
		} else {
			return false
		}
	}

	if string(strings.ToLower(resp)[0]) == "y" {
		return true
	}
	return false
}

func (i *Info) getHost() string {
	if i.Host == "" {
		return "localhost"
	}
	return i.Host
}
<|endoftext|>


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

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/sdboyer/gps"
)

var initCmd = &command{
	fn:   runInit,
	name: "init",
	short: `
	Write Manifest file in the root of the project directory.
	`,
	long: `
	Populates Manifest file with current deps of this project.
	The specified version of each dependent repository is the version
	available in the user's workspaces (as specified by GOPATH).
	If the dependency is not present in any workspaces it is not be
	included in the Manifest.
	Writes Lock file(?)
	Creates vendor/ directory(?)

    Notes from DOC:
    Reads existing dependency information written by other tools.
    Noting any information that is lost (unsupported features, etc).
    This functionality will be removed after a transition period (1 year?).
    Write Manifest file in the root of the project directory.
    * Populates Manifest file with current deps of this project.
    The specified version of each dependent repository is the version available in the user's workspaces (including vendor/ directories, if present).
    If the dependency is not present in any workspaces it will not be included in the Manifest. A warning will be issued for these dependencies.
    Creates vendor/ directory (if it does not exist)
    Copies the project’s dependencies from the workspace to the vendor/ directory (if they’re not already there).
    Writes a Lockfile in the root of the project directory.
    Invoke “dep status”.
	`,
}

// determineProjectRoot takes an absolute path and compares it against declared
// GOPATH(s) to determine what portion of the input path should be treated as an
// import path - as a project root.
//
// The second returned string indicates which GOPATH value was used.
func determineProjectRoot(path string) (string, string, error) {
	gopath := os.Getenv("GOPATH")
	for _, gp := range filepath.SplitList(gopath) {
		srcprefix := filepath.Join(gp, "src") + string(filepath.Separator)
		if strings.HasPrefix(path, srcprefix) {
			// filepath.ToSlash because we're dealing with an import path now,
			// not an fs path
			return filepath.ToSlash(strings.TrimPrefix(path, srcprefix)), gp, nil
		}
	}
	return "", "", fmt.Errorf("%s not in any $GOPATH", path)
}

func runInit(args []string) error {
	if len(args) > 1 {
		return fmt.Errorf("Too many args: %d", len(args))
	}
	var p string
	var err error
	if len(args) == 0 {
		p, err = os.Getwd()
		if err != nil {
			return errors.Wrap(err, "os.Getwd")
		}
	} else {
		p = args[0]
	}

	mf := filepath.Join(p, manifestName)
	lf := filepath.Join(p, lockName)

	// TODO: Lstat ? Do we care?
	_, merr := os.Stat(mf)
	if merr == nil {
		return fmt.Errorf("Manifest file %q already exists", mf)
	}
	_, lerr := os.Stat(lf)

	if os.IsNotExist(merr) {
		if lerr == nil {
			return fmt.Errorf("Invalid state: manifest %q does not exist, but lock %q does.", mf, lf)
		} else if !os.IsNotExist(lerr) {
			return errors.Wrap(lerr, "stat lockfile")
		}

		cpr, gopath, err := determineProjectRoot(p)
		if err != nil {
			return errors.Wrap(err, "determineProjectRoot")
		}
		pkgT, err := gps.ListPackages(p, cpr)
		if err != nil {
			return errors.Wrap(err, "gps.ListPackages")
		}
		sm, err := getSourceManager()
		if err != nil {
			return errors.Wrap(err, "getSourceManager")
		}
		defer sm.Release()

		// TODO: This is just wrong, need to figure out manifest file structure
		m := manifest{
			Dependencies: make(gps.ProjectConstraints),
		}

		processed := make(map[gps.ProjectRoot][]string)
		packages := make(map[string]bool)
		notondisk := make(map[gps.ProjectRoot]bool)
		ondisk := make(map[gps.ProjectRoot]gps.Version)
		for _, v := range pkgT.Packages {
			// TODO: Some errors maybe should not be skipped ;-)
			if v.Err != nil {
				continue
			}

			for _, i := range v.P.Imports {
				if isStdLib(i) { // TODO: Replace with non stubbed version
					continue
				}
				pr, err := sm.DeduceProjectRoot(i)
				if err != nil {
					return errors.Wrap(err, "sm.DeduceProjectRoot") // TODO: Skip and report ?
				}

				packages[i] = true
				if _, ok := processed[pr]; ok {
					if !contains(processed[pr], i) {
						processed[pr] = append(processed[pr], i)
					}

					continue
				}
				processed[pr] = []string{i}

				v, err := versionInWorkspace(pr)
				if err != nil {
					notondisk[pr] = true
					fmt.Printf("Could not determine version for %q, omitting from generated manifest\n", pr)
					continue
				}

				ondisk[pr] = v
				pp := gps.ProjectProperties{}
				switch v.Type() {
				case "branch", "version", "rev":
					pp.Constraint = v
				case "semver":
					c, _ := gps.NewSemverConstraint("^" + v.String())
					pp.Constraint = c
				}

				m.Dependencies[pr] = pp
			}
		}

		// Explore the packages we've found for transitive deps, either
		// completing the lock or identifying (more) missing projects that we'll
		// need to ask gps to solve for us.
		colors := make(map[string]uint8)
		const (
			white uint8 = iota
			grey
			black
		)

		// cache of PackageTrees, so we don't parse projects more than once
		ptrees := make(map[gps.ProjectRoot]gps.PackageTree)

		// depth-first traverser
		var dft func(string) error
		dft = func(pkg string) error {
			switch colors[pkg] {
			case white:
				colors[pkg] = grey

				pr, err := sm.DeduceProjectRoot(pkg)
				if err != nil {
					return errors.Wrap(err, "could not deduce project root for "+pkg)
				}

				// We already visited this project root earlier via some other
				// pkg within it, and made the decision that it's not on disk.
				// Respect that decision, and pop the stack.
				if notondisk[pr] {
					colors[pkg] = black
					return nil
				}

				ptree, has := ptrees[pr]
				if !has {
					// It's fine if the root does not exist - it indicates that this
					// project is not present in the workspace, and so we need to
					// solve to deal with this dep.
					r := filepath.Join(gopath, "src", string(pr))
					_, err := os.Lstat(r)
					if os.IsNotExist(err) {
						colors[pkg] = black
						notondisk[pr] = true
						return nil
					}

					ptree, err = gps.ListPackages(r, string(pr))
					if err != nil {
						// Any error here other than an a nonexistent dir (which
						// can't happen because we covered that case above) is
						// probably critical, so bail out.
						return errors.Wrap(err, "gps.ListPackages")
					}
				}

				rm := ptree.ExternalReach(false, false, nil)
				reached, ok := rm[pkg]
				if !ok {
					colors[pkg] = black
					// not on disk...
					notondisk[pr] = true
					return nil
				}

				if _, ok := processed[pr]; ok {
					if !contains(processed[pr], pkg) {
						processed[pr] = append(processed[pr], pkg)
					}
				} else {
					processed[pr] = []string{pkg}
				}

				// project must be on disk at this point; question is
				// whether we're first seeing it here, in the transitive
				// exploration, or if it arose in the direct dep parts
				if _, in := ondisk[pr]; !in {
					v, err := versionInWorkspace(pr)
					if err != nil {
						colors[pkg] = black
						notondisk[pr] = true
						return nil
					}
					ondisk[pr] = v
				}

				for _, rpkg := range reached {
					if isStdLib(rpkg) {
						continue
					}

					err := dft(rpkg)
					if err != nil {
						return err
					}
				}

				colors[pkg] = black
			case grey:
				return fmt.Errorf("Import cycle detected on %s", pkg)
			}
			return nil
		}

		// run the depth-first traversal from the set of immediate external
		// package imports we found in the current project
		for pkg := range packages {
			err := dft(pkg)
			if err != nil {
				return err // already errors.Wrap()'d internally
			}
		}

		// Make an initial lock from just what we know about the immediate deps
		// of the current project
		l := lock{
			P: make([]gps.LockedProject, 0, len(ondisk)),
		}

		for pr, v := range ondisk {
			l.P = append(l.P, gps.NewLockedProject(
				// TODO processed holds "absolute" import paths, but the
				// standard laid out elsewhere is to have them be expressed
				// relative to their project root when written in a lock
				gps.ProjectIdentifier{ProjectRoot: pr}, v, processed[pr]),
			)
		}

		var l2 *lock
		if len(notondisk) > 0 {
			params := gps.SolveParameters{
				RootDir:         p,
				RootPackageTree: pkgT,
				Manifest:        &m,
				Lock:            &l,
			}
			s, err := gps.Prepare(params, sm)
			if err != nil {
				return errors.Wrap(err, "prepare solver")
			}

			soln, err := s.Solve()
			if err != nil {
				handleAllTheFailuresOfTheWorld(err)
				return err
			}
			l2 = lockFromInterface(soln)
		} else {
			l2 = &l
		}

		if err := writeFile(mf, &m); err != nil {
			return errors.Wrap(err, "writeFile for manifest")
		}
		if err := writeFile(lf, l2); err != nil {
			return errors.Wrap(err, "writeFile for lock")
		}

		return nil
	}

	return errors.Wrap(err, "runInit fall through")
}

// contains checks if a array of strings contains a value
func contains(a []string, b string) bool {
	for _, v := range a {
		if b == v {
			return true
		}
	}
	return false
}

// TODO this is a stub, make it not a stub when gps gets its act together
func isStdLib(i string) bool {
	switch i {
	case "bytes", "container/heap", "crypto/sha256", "encoding/hex", "encoding/xml", "errors", "sort", "encoding/json", "flag", "fmt", "go/build", "go/scanner", "io", "io/ioutil", "log", "math/rand", "net/http", "net/url", "os", "os/exec", "path", "path/filepath", "regexp", "runtime", "strconv", "strings", "sync", "sync/atomic", "text/scanner", "text/tabwriter", "time":
		return true
	}
	return false
}

// TODO stub; considerable effort required for the real impl
func versionInWorkspace(pr gps.ProjectRoot) (gps.Version, error) {
	switch pr {
	case "github.com/sdboyer/gps":
		return gps.NewVersion("v0.12.0").Is("9ca61cb4e9851c80bb537e7d8e1be56e18e03cc9"), nil
	case "github.com/Masterminds/semver":
		return gps.NewBranch("2.x").Is("b3ef6b1808e9889dfb8767ce7068db923a3d07de"), nil
	case "github.com/Masterminds/vcs":
		return gps.Revision("fbe9fb6ad5b5f35b3e82a7c21123cfc526cbf895"), nil
	case "github.com/pkg/errors":
		return gps.NewVersion("v0.8.0").Is("645ef00459ed84a119197bfb8d8205042c6df63d"), nil
	case "github.com/armon/go-radix":
		return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
	case "github.com/termie/go-shutil":
		return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
	}

	return nil, fmt.Errorf("unknown project")
}

// TODO solve failures can be really creative - we need to be similarly creative
// in handling them and informing the user appropriately
func handleAllTheFailuresOfTheWorld(err error) {
	fmt.Println("ouchie, solve error: %s", err)
}

func writeFile(path string, in json.Marshaler) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b, err := in.MarshalJSON()
	if err != nil {
		return err
	}

	_, err = f.Write(b)
	return err
}
<commit_msg>dep: trim project root from pkg lists in lock<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/sdboyer/gps"
)

var initCmd = &command{
	fn:   runInit,
	name: "init",
	short: `
	Write Manifest file in the root of the project directory.
	`,
	long: `
	Populates Manifest file with current deps of this project.
	The specified version of each dependent repository is the version
	available in the user's workspaces (as specified by GOPATH).
	If the dependency is not present in any workspaces it is not be
	included in the Manifest.
	Writes Lock file(?)
	Creates vendor/ directory(?)

    Notes from DOC:
    Reads existing dependency information written by other tools.
    Noting any information that is lost (unsupported features, etc).
    This functionality will be removed after a transition period (1 year?).
    Write Manifest file in the root of the project directory.
    * Populates Manifest file with current deps of this project.
    The specified version of each dependent repository is the version available in the user's workspaces (including vendor/ directories, if present).
    If the dependency is not present in any workspaces it will not be included in the Manifest. A warning will be issued for these dependencies.
    Creates vendor/ directory (if it does not exist)
    Copies the project’s dependencies from the workspace to the vendor/ directory (if they’re not already there).
    Writes a Lockfile in the root of the project directory.
    Invoke “dep status”.
	`,
}

// determineProjectRoot takes an absolute path and compares it against declared
// GOPATH(s) to determine what portion of the input path should be treated as an
// import path - as a project root.
//
// The second returned string indicates which GOPATH value was used.
func determineProjectRoot(path string) (string, string, error) {
	gopath := os.Getenv("GOPATH")
	for _, gp := range filepath.SplitList(gopath) {
		srcprefix := filepath.Join(gp, "src") + string(filepath.Separator)
		if strings.HasPrefix(path, srcprefix) {
			// filepath.ToSlash because we're dealing with an import path now,
			// not an fs path
			return filepath.ToSlash(strings.TrimPrefix(path, srcprefix)), gp, nil
		}
	}
	return "", "", fmt.Errorf("%s not in any $GOPATH", path)
}

func runInit(args []string) error {
	if len(args) > 1 {
		return fmt.Errorf("Too many args: %d", len(args))
	}
	var p string
	var err error
	if len(args) == 0 {
		p, err = os.Getwd()
		if err != nil {
			return errors.Wrap(err, "os.Getwd")
		}
	} else {
		p = args[0]
	}

	mf := filepath.Join(p, manifestName)
	lf := filepath.Join(p, lockName)

	// TODO: Lstat ? Do we care?
	_, merr := os.Stat(mf)
	if merr == nil {
		return fmt.Errorf("Manifest file %q already exists", mf)
	}
	_, lerr := os.Stat(lf)

	if os.IsNotExist(merr) {
		if lerr == nil {
			return fmt.Errorf("Invalid state: manifest %q does not exist, but lock %q does.", mf, lf)
		} else if !os.IsNotExist(lerr) {
			return errors.Wrap(lerr, "stat lockfile")
		}

		cpr, gopath, err := determineProjectRoot(p)
		if err != nil {
			return errors.Wrap(err, "determineProjectRoot")
		}
		pkgT, err := gps.ListPackages(p, cpr)
		if err != nil {
			return errors.Wrap(err, "gps.ListPackages")
		}
		sm, err := getSourceManager()
		if err != nil {
			return errors.Wrap(err, "getSourceManager")
		}
		defer sm.Release()

		// TODO: This is just wrong, need to figure out manifest file structure
		m := manifest{
			Dependencies: make(gps.ProjectConstraints),
		}

		processed := make(map[gps.ProjectRoot][]string)
		packages := make(map[string]bool)
		notondisk := make(map[gps.ProjectRoot]bool)
		ondisk := make(map[gps.ProjectRoot]gps.Version)
		for _, v := range pkgT.Packages {
			// TODO: Some errors maybe should not be skipped ;-)
			if v.Err != nil {
				continue
			}

			for _, i := range v.P.Imports {
				if isStdLib(i) { // TODO: Replace with non stubbed version
					continue
				}
				pr, err := sm.DeduceProjectRoot(i)
				if err != nil {
					return errors.Wrap(err, "sm.DeduceProjectRoot") // TODO: Skip and report ?
				}

				packages[i] = true
				if _, ok := processed[pr]; ok {
					if !contains(processed[pr], i) {
						processed[pr] = append(processed[pr], i)
					}

					continue
				}
				processed[pr] = []string{i}

				v, err := versionInWorkspace(pr)
				if err != nil {
					notondisk[pr] = true
					fmt.Printf("Could not determine version for %q, omitting from generated manifest\n", pr)
					continue
				}

				ondisk[pr] = v
				pp := gps.ProjectProperties{}
				switch v.Type() {
				case "branch", "version", "rev":
					pp.Constraint = v
				case "semver":
					c, _ := gps.NewSemverConstraint("^" + v.String())
					pp.Constraint = c
				}

				m.Dependencies[pr] = pp
			}
		}

		// Explore the packages we've found for transitive deps, either
		// completing the lock or identifying (more) missing projects that we'll
		// need to ask gps to solve for us.
		colors := make(map[string]uint8)
		const (
			white uint8 = iota
			grey
			black
		)

		// cache of PackageTrees, so we don't parse projects more than once
		ptrees := make(map[gps.ProjectRoot]gps.PackageTree)

		// depth-first traverser
		var dft func(string) error
		dft = func(pkg string) error {
			switch colors[pkg] {
			case white:
				colors[pkg] = grey

				pr, err := sm.DeduceProjectRoot(pkg)
				if err != nil {
					return errors.Wrap(err, "could not deduce project root for "+pkg)
				}

				// We already visited this project root earlier via some other
				// pkg within it, and made the decision that it's not on disk.
				// Respect that decision, and pop the stack.
				if notondisk[pr] {
					colors[pkg] = black
					return nil
				}

				ptree, has := ptrees[pr]
				if !has {
					// It's fine if the root does not exist - it indicates that this
					// project is not present in the workspace, and so we need to
					// solve to deal with this dep.
					r := filepath.Join(gopath, "src", string(pr))
					_, err := os.Lstat(r)
					if os.IsNotExist(err) {
						colors[pkg] = black
						notondisk[pr] = true
						return nil
					}

					ptree, err = gps.ListPackages(r, string(pr))
					if err != nil {
						// Any error here other than an a nonexistent dir (which
						// can't happen because we covered that case above) is
						// probably critical, so bail out.
						return errors.Wrap(err, "gps.ListPackages")
					}
				}

				rm := ptree.ExternalReach(false, false, nil)
				reached, ok := rm[pkg]
				if !ok {
					colors[pkg] = black
					// not on disk...
					notondisk[pr] = true
					return nil
				}

				if _, ok := processed[pr]; ok {
					if !contains(processed[pr], pkg) {
						processed[pr] = append(processed[pr], pkg)
					}
				} else {
					processed[pr] = []string{pkg}
				}

				// project must be on disk at this point; question is
				// whether we're first seeing it here, in the transitive
				// exploration, or if it arose in the direct dep parts
				if _, in := ondisk[pr]; !in {
					v, err := versionInWorkspace(pr)
					if err != nil {
						colors[pkg] = black
						notondisk[pr] = true
						return nil
					}
					ondisk[pr] = v
				}

				for _, rpkg := range reached {
					if isStdLib(rpkg) {
						continue
					}

					err := dft(rpkg)
					if err != nil {
						return err
					}
				}

				colors[pkg] = black
			case grey:
				return fmt.Errorf("Import cycle detected on %s", pkg)
			}
			return nil
		}

		// run the depth-first traversal from the set of immediate external
		// package imports we found in the current project
		for pkg := range packages {
			err := dft(pkg)
			if err != nil {
				return err // already errors.Wrap()'d internally
			}
		}

		// Make an initial lock from just what we know about the immediate deps
		// of the current project
		l := lock{
			P: make([]gps.LockedProject, 0, len(ondisk)),
		}

		for pr, v := range ondisk {
			// That we have to chop off these path prefixes is a symptom of
			// a problem in gps itself
			pkgs := make([]string, 0, len(processed[pr]))
			prslash := string(pr) + "/"
			for _, pkg := range processed[pr] {
				if pkg == string(pr) {
					pkgs = append(pkgs, ".")
				} else {
					pkgs = append(pkgs, strings.TrimPrefix(pkg, prslash))
				}
			}

			l.P = append(l.P, gps.NewLockedProject(
				gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs),
			)
		}

		var l2 *lock
		if len(notondisk) > 0 {
			params := gps.SolveParameters{
				RootDir:         p,
				RootPackageTree: pkgT,
				Manifest:        &m,
				Lock:            &l,
			}
			s, err := gps.Prepare(params, sm)
			if err != nil {
				return errors.Wrap(err, "prepare solver")
			}

			soln, err := s.Solve()
			if err != nil {
				handleAllTheFailuresOfTheWorld(err)
				return err
			}
			l2 = lockFromInterface(soln)
		} else {
			l2 = &l
		}

		if err := writeFile(mf, &m); err != nil {
			return errors.Wrap(err, "writeFile for manifest")
		}
		if err := writeFile(lf, l2); err != nil {
			return errors.Wrap(err, "writeFile for lock")
		}

		return nil
	}

	return errors.Wrap(err, "runInit fall through")
}

// contains checks if a array of strings contains a value
func contains(a []string, b string) bool {
	for _, v := range a {
		if b == v {
			return true
		}
	}
	return false
}

// TODO this is a stub, make it not a stub when gps gets its act together
func isStdLib(i string) bool {
	switch i {
	case "bytes", "container/heap", "crypto/sha256", "encoding/hex", "encoding/xml", "errors", "sort", "encoding/json", "flag", "fmt", "go/build", "go/scanner", "io", "io/ioutil", "log", "math/rand", "net/http", "net/url", "os", "os/exec", "path", "path/filepath", "regexp", "runtime", "strconv", "strings", "sync", "sync/atomic", "text/scanner", "text/tabwriter", "time":
		return true
	}
	return false
}

// TODO stub; considerable effort required for the real impl
func versionInWorkspace(pr gps.ProjectRoot) (gps.Version, error) {
	switch pr {
	case "github.com/sdboyer/gps":
		return gps.NewVersion("v0.12.0").Is("9ca61cb4e9851c80bb537e7d8e1be56e18e03cc9"), nil
	case "github.com/Masterminds/semver":
		return gps.NewBranch("2.x").Is("b3ef6b1808e9889dfb8767ce7068db923a3d07de"), nil
	case "github.com/Masterminds/vcs":
		return gps.Revision("fbe9fb6ad5b5f35b3e82a7c21123cfc526cbf895"), nil
	case "github.com/pkg/errors":
		return gps.NewVersion("v0.8.0").Is("645ef00459ed84a119197bfb8d8205042c6df63d"), nil
	case "github.com/armon/go-radix":
		return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
	case "github.com/termie/go-shutil":
		return gps.NewBranch("master").Is("4239b77079c7b5d1243b7b4736304ce8ddb6f0f2"), nil
	}

	return nil, fmt.Errorf("unknown project")
}

// TODO solve failures can be really creative - we need to be similarly creative
// in handling them and informing the user appropriately
func handleAllTheFailuresOfTheWorld(err error) {
	fmt.Println("ouchie, solve error: %s", err)
}

func writeFile(path string, in json.Marshaler) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b, err := in.MarshalJSON()
	if err != nil {
		return err
	}

	_, err = f.Write(b)
	return err
}
<|endoftext|>


<commit_before>/*
This package provides memcached support for session on Kwiscale Framework.

To use it, you need to import the package in your application to let runtime call the "init()" function.

	import _ "gopkg.in/kwiscale/session-memcached"

Kwiscale will now be able to use session-memcached it you set your App configuration to "memcached":

	app := kwiscale.NewApp(&kwiscale.Config{
		SessionEgnine : "memcached",
		SessionEngineOptions: SessionEngineOptions{
			"servers" : "192.168.1.5:11211"
		}
	})

The "servers" options could be a coma separated list in string or a slices of strings:

	app := kwiscale.NewApp(&kwiscale.Config{
		SessionEgnine : "memcached",
		SessionEngineOptions: SessionEngineOptions{
			"servers" : []string{
				"192.168.1.5:11211",
				"192.168.1.6:11211",
				"192.168.1.7:11211",
				"192.168.1.8:11211",
			}
		}
	})

*/
package kwiscalesessionmemcached

import (
	"errors"
	"strings"

	"github.com/bradfitz/gomemcache/memcache"
	gsm "github.com/bradleypeabody/gorilla-sessions-memcache"
	"gopkg.in/kwiscale/framework.v0" // github.com/kwiscale/framework
)

func init() {
	kwiscale.RegisterSessionEngine("memcached", &MemcachedSessionEngine{})
}

// Implement ISessionStore
type MemcachedSessionEngine struct {
	memcache *memcache.Client
	store    *gsm.MemcacheStore
	secret   []byte
	name     string
	prefix   string
}

// Register session-memcached in kwiscale.
func (mc *MemcachedSessionEngine) Init() {
	mc.store = gsm.NewMemcacheStore(mc.memcache, mc.prefix, mc.secret)
}

// Name set the name of session (ie. the session cookie name too).
func (mc *MemcachedSessionEngine) Name(name string) {
	mc.name = name
}

// SetOptions is used here to set servers addresses.
// Example:
//
//		app := kwiscale.NewApp(&kwiscale.Config{
//			SessionEngine: "memcached",
//			SessionengineOptions: SessionEngineOptions{
//				"servers" : "192.168.1.1:11211,192.168.1.2:11211", // required
//				"prefix"  : "prefix_string", // optionnal
//			}
//		})
//
// The "servers" list could be a []string or a coma separated list.
func (mc *MemcachedSessionEngine) SetOptions(options kwiscale.SessionEngineOptions) {
	if servers, ok := options["servers"]; ok {
		switch servers := servers.(type) {
		case string:
			mc.setServers(strings.Split(servers, ",")...)
		case []string:
			mc.setServers(servers...)
		}
	}

	if prefix, ok := options["prefix"]; ok {
		mc.prefix, _ = prefix.(string)
	}
}

// Set secret []byte to encode session.
func (mc *MemcachedSessionEngine) SetSecret(s []byte) {
	mc.secret = s
}

// Get a value from the session.
func (mc *MemcachedSessionEngine) Get(handler kwiscale.WebHandler, key interface{}) (interface{}, error) {
	session, err := mc.store.Get(handler.GetRequest(), mc.name)
	if err != nil {
		return nil, err
	}

	if val, ok := session.Values[key]; ok {
		return val, nil
	}

	return nil, errors.New("empty session")
}

// Set value to the session.
func (mc *MemcachedSessionEngine) Set(handler kwiscale.WebHandler, key, value interface{}) {
	session, _ := mc.store.Get(handler.GetRequest(), mc.name)
	session.Values[key] = value
	session.Save(handler.GetRequest(), handler.GetResponse())
}

// Clean removes the entire values in session.
func (mc *MemcachedSessionEngine) Clean(handler kwiscale.WebHandler) {
	session, _ := mc.store.Get(handler.GetRequest(), mc.name)
	session.Values = make(map[interface{}]interface{})
	session.Save(handler.GetRequest(), handler.GetResponse())
}

// SetServers is called by "Init" method to create a memcache client used by the store.
func (mc *MemcachedSessionEngine) setServers(servers ...string) {
	mc.memcache = memcache.New(servers...)
}
<commit_msg>Change kwiscale version, my bad...<commit_after>/*
This package provides memcached support for session on Kwiscale Framework.

To use it, you need to import the package in your application to let runtime call the "init()" function.

	import _ "gopkg.in/kwiscale/session-memcached"

Kwiscale will now be able to use session-memcached it you set your App configuration to "memcached":

	app := kwiscale.NewApp(&kwiscale.Config{
		SessionEgnine : "memcached",
		SessionEngineOptions: SessionEngineOptions{
			"servers" : "192.168.1.5:11211"
		}
	})

The "servers" options could be a coma separated list in string or a slices of strings:

	app := kwiscale.NewApp(&kwiscale.Config{
		SessionEgnine : "memcached",
		SessionEngineOptions: SessionEngineOptions{
			"servers" : []string{
				"192.168.1.5:11211",
				"192.168.1.6:11211",
				"192.168.1.7:11211",
				"192.168.1.8:11211",
			}
		}
	})

*/
package kwiscalesessionmemcached

import (
	"errors"
	"strings"

	"github.com/bradfitz/gomemcache/memcache"
	gsm "github.com/bradleypeabody/gorilla-sessions-memcache"
	"gopkg.in/kwiscale/framework.v1" // github.com/kwiscale/framework
)

func init() {
	kwiscale.RegisterSessionEngine("memcached", &MemcachedSessionEngine{})
}

// Implement ISessionStore
type MemcachedSessionEngine struct {
	memcache *memcache.Client
	store    *gsm.MemcacheStore
	secret   []byte
	name     string
	prefix   string
}

// Register session-memcached in kwiscale.
func (mc *MemcachedSessionEngine) Init() {
	mc.store = gsm.NewMemcacheStore(mc.memcache, mc.prefix, mc.secret)
}

// Name set the name of session (ie. the session cookie name too).
func (mc *MemcachedSessionEngine) Name(name string) {
	mc.name = name
}

// SetOptions is used here to set servers addresses.
// Example:
//
//		app := kwiscale.NewApp(&kwiscale.Config{
//			SessionEngine: "memcached",
//			SessionengineOptions: SessionEngineOptions{
//				"servers" : "192.168.1.1:11211,192.168.1.2:11211", // required
//				"prefix"  : "prefix_string", // optionnal
//			}
//		})
//
// The "servers" list could be a []string or a coma separated list.
func (mc *MemcachedSessionEngine) SetOptions(options kwiscale.SessionEngineOptions) {
	if servers, ok := options["servers"]; ok {
		switch servers := servers.(type) {
		case string:
			mc.setServers(strings.Split(servers, ",")...)
		case []string:
			mc.setServers(servers...)
		}
	}

	if prefix, ok := options["prefix"]; ok {
		mc.prefix, _ = prefix.(string)
	}
}

// Set secret []byte to encode session.
func (mc *MemcachedSessionEngine) SetSecret(s []byte) {
	mc.secret = s
}

// Get a value from the session.
func (mc *MemcachedSessionEngine) Get(handler kwiscale.WebHandler, key interface{}) (interface{}, error) {
	session, err := mc.store.Get(handler.GetRequest(), mc.name)
	if err != nil {
		return nil, err
	}

	if val, ok := session.Values[key]; ok {
		return val, nil
	}

	return nil, errors.New("empty session")
}

// Set value to the session.
func (mc *MemcachedSessionEngine) Set(handler kwiscale.WebHandler, key, value interface{}) {
	session, _ := mc.store.Get(handler.GetRequest(), mc.name)
	session.Values[key] = value
	session.Save(handler.GetRequest(), handler.GetResponse())
}

// Clean removes the entire values in session.
func (mc *MemcachedSessionEngine) Clean(handler kwiscale.WebHandler) {
	session, _ := mc.store.Get(handler.GetRequest(), mc.name)
	session.Values = make(map[interface{}]interface{})
	session.Save(handler.GetRequest(), handler.GetResponse())
}

// SetServers is called by "Init" method to create a memcache client used by the store.
func (mc *MemcachedSessionEngine) setServers(servers ...string) {
	mc.memcache = memcache.New(servers...)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/nu7hatch/gouuid"
	"io/ioutil"
	//"log"
    log "github.com/Sirupsen/logrus"
	"net/http"
	"runtime"
	"time"
	"flag"
	"io"
)

type TargetList map[string][]EventTarget

// why not just []string of urls? In case we need meta data for these later on.
type EventTarget struct {
	Url string
}

// Our object used to repeat events.
// todo: should I just pass the http.Request? Is that thread safe?
// 		 I feel like the body being an ioreader is kind of a problem -- so most likely not
type RequestMessage struct {
	UUID    string
	URL     string
	Method  string
	Source  string
	Headers http.Header
	Body    []byte
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
	//w.WriteHeader(http.StatusNotFound)
	w.Write([]byte("No such event endpoint"))
}

func handleIncomingEvent(w http.ResponseWriter, r *http.Request) {

	// get a UUID for this transaction
	u5, err := uuid.NewV4()
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	body, _ := ioutil.ReadAll(r.Body)

	requestObj := RequestMessage{
		UUID:    u5.String(),
		URL:     r.RequestURI,
		Method:  r.Method,
		Source:  r.RemoteAddr,
		Headers: r.Header,
		Body:    body,
	}

	queue := requestObj.URL[1:]

	fmt.Fprintf(w, "{ \"id\":\"%s\" }\n", u5) // to lazy to do a real json.Marshal, etc

	for _, eventTarget := range targets[queue] {
		qu := QueueUrl{queue, eventTarget.Url}
		addchan <- qu
		// this select/case/default is a non-blocking chan push
		// todo: maybe circular chans here? IE: throw away oldest items when the chan is full.
		select {
		case sendPool[qu].RequestChan <- requestObj:
		default:
			log.WithFields(log.Fields{
				"id": u5,
				"queue": queue,
			}).Info("queue-full")
		}
	}
}

func sendEvent(client *http.Client, qu QueueUrl, req RequestMessage) {
	start := time.Now()
	var sent bool
	sent = false
	attempts := 0
	sleepDuration := time.Millisecond * 100
	for {
		attempts++
		httpReq, _ := http.NewRequest(req.Method, qu.Url, bytes.NewBuffer(req.Body))
		for headerName, values := range req.Headers {
			for _, value := range values {
				httpReq.Header.Add(headerName, value)
			}
		}
		httpReq.Header.Set("X-Wsq-Id", req.UUID)
		resp, err := client.Do(httpReq)

		if err == nil {
			// get rid of the response, we don't care
			// but we do need to clean it out, so the client can reuse the same connection
			io.Copy(ioutil.Discard, resp.Body)
			resp.Body.Close()
		}

		if err == nil && resp.StatusCode == 200 {
			sent = true
			break
		}

		// max duration, ever
		if time.Since(start) > time.Second*10 {
			break
		}

		// oops, didn't work; have a pause and try again in a bit
		time.Sleep(sleepDuration)

		// slowly ramp up our sleep interval, shall we? But cap at a minute, thanks.
		if sleepDuration < time.Duration(time.Minute) {
			sleepDuration = time.Duration(float64(sleepDuration) * 1.5)
		} else {
			sleepDuration = time.Duration(time.Minute)
		}
	}
	elapsed := time.Since(start)

	if sent {
		deltchan <- qu
	} else {
		delfchan <- qu
	}

	log.WithFields(log.Fields{
		"id": req.UUID,
		"queue": qu.Queue,
		"url": qu.Url,
		"attempts": attempts,
		"sent": sent,
		"duration": elapsed.Seconds() * 1e3, /* ms hack */
	}).Info("relay-end")
}

type QueueUrl struct {
	Queue string
	Url   string
}

type CounterVals struct {
	Current uint64
	Total   uint64
	Success uint64
	Failure uint64
}

var counters = make(map[QueueUrl]CounterVals)
var addchan = make(chan QueueUrl, 100)
var deltchan = make(chan QueueUrl, 100)
var delfchan = make(chan QueueUrl, 100)

type Worker struct {
  QueueUrl    QueueUrl
  RequestChan chan RequestMessage
  QuitChan    chan bool
}

func (w Worker) Start() {
	go func() {
		client := &http.Client{
			// todo: reasonable default?
			Timeout: 10 * time.Second,
			// no cookies, please
			Jar: nil,
		}
		for {
			work := <-w.RequestChan
			sendEvent(client, w.QueueUrl, work)
		}
	}()
}

var sendPool = make(map[QueueUrl]Worker)

var targets TargetList

func main() {
	//log.SetFlags(log.LstdFlags | log.Lmicroseconds)
    log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
		TimestampFormat: time.RFC3339Nano,
	})

	configId := flag.String("config", "default", "Which stanza of the config to use")
	flag.Parse()

	var ok bool
	targets, ok = allTargets[*configId]
	if !ok {
		panic("Could not load expected configuration")
	}

	// initialize counters to zero
	// You don't _have_ to do this, but I like having all the counters
	// reporting 0 immediately for stat collection purposes.
	for queue, eventTargets := range targets {
		for _, eventTarget := range eventTargets {
			qu := QueueUrl{queue, eventTarget.Url}
			counters[qu] = CounterVals{0,0,0,0}
			sendPool[qu] = Worker{
				QueueUrl: qu,
				RequestChan: make(chan RequestMessage, 10000),
				QuitChan: make(chan bool),
			}
			sendPool[qu].Start()
		}
	}

	// goroutine to keep the counters up-to-date
	go func() {
		for {
			// watch each channel as items rolls in and modify the counters as needed
			select {
			// you can't do counters[control].Current++ in go, so this mess is what results
			case control := <-addchan:
				tmp := counters[control]
				tmp.Current++
				tmp.Total++
				counters[control] = tmp
			case control := <-deltchan:
				tmp := counters[control]
				tmp.Current--
				tmp.Success++
				counters[control] = tmp
			case control := <-delfchan:
				tmp := counters[control]
				tmp.Current--
				tmp.Failure++
				counters[control] = tmp
			}
		}
	}()

	// A dumb goroutine to watch memory usage and counter metrics
	go func() {
		var mem runtime.MemStats
		for {
			runtime.ReadMemStats(&mem)
			log.WithFields(log.Fields{
				"mem.Alloc": mem.Alloc,
				"mem.TotalAlloc": mem.TotalAlloc,
				"mem.HeapAlloc": mem.HeapAlloc,
				"mem.HeapSys": mem.HeapSys,
				"runtime.NumGoroutine": runtime.NumGoroutine(),
			}).Info("metrics-mem")
			for cKeys, cVals:= range counters {
				log.WithFields(log.Fields{
					"queue": cKeys.Queue,
					"url": cKeys.Url,
					"current": cVals.Current,
					"total": cVals.Total,
					"success": cVals.Success,
					"failure": cVals.Failure,
					"chanlen": len(sendPool[cKeys].RequestChan),
				}).Info("metrics-queue")
			}
			time.Sleep(time.Second * 5)
		}
	}()

	// Oh, hey, there's the webserver!
	log.Info("starting server")
	for queue, _ := range targets {
		log.Info("registering queue @ /" + queue)
		http.HandleFunc("/" + queue, handleIncomingEvent)
	}
	http.HandleFunc("/", defaultHandler)
	err := http.ListenAndServe(":8000", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

}
<commit_msg>If a given queue+url chan is full, instead of failing to add new messages, instead start dropping older messages. Inflight messages will continue to try<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/nu7hatch/gouuid"
	"io/ioutil"
	//"log"
    log "github.com/Sirupsen/logrus"
	"net/http"
	"runtime"
	"time"
	"flag"
	"io"
)

type TargetList map[string][]EventTarget

// why not just []string of urls? In case we need meta data for these later on.
type EventTarget struct {
	Url string
}

// Our object used to repeat events.
// todo: should I just pass the http.Request? Is that thread safe?
// 		 I feel like the body being an ioreader is kind of a problem -- so most likely not
type RequestMessage struct {
	UUID    string
	URL     string
	Method  string
	Source  string
	Headers http.Header
	Body    []byte
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
	//w.WriteHeader(http.StatusNotFound)
	w.Write([]byte("No such event endpoint"))
}

func handleIncomingEvent(w http.ResponseWriter, r *http.Request) {

	// get a UUID for this transaction
	u5, err := uuid.NewV4()
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	body, _ := ioutil.ReadAll(r.Body)

	requestObj := RequestMessage{
		UUID:    u5.String(),
		URL:     r.RequestURI,
		Method:  r.Method,
		Source:  r.RemoteAddr,
		Headers: r.Header,
		Body:    body,
	}

	queue := requestObj.URL[1:]

	fmt.Fprintf(w, "{ \"id\":\"%s\" }\n", u5) // to lazy to do a real json.Marshal, etc

	for _, eventTarget := range targets[queue] {
		qu := QueueUrl{queue, eventTarget.Url}
		addchan <- qu
		// this select/case/default is a non-blocking chan push
		select {
		case sendPool[qu].RequestChan <- requestObj:
		default:
			// metricize that we're dropping messages
			dellchan <- qu
			// kill off the oldests, not-in-flight message
			<-sendPool[qu].RequestChan
			// we attempt to send the current message one last time, but this it no guaranteed to work
			select {
			case sendPool[qu].RequestChan <- requestObj:
			default:
				log.WithFields(log.Fields{
					"id": u5,
					"queue": queue,
					"url": eventTarget.Url,
				}).Info("queue-full-message-lost")
			}
		}
	}
}

func sendEvent(client *http.Client, qu QueueUrl, req RequestMessage) {
	start := time.Now()
	var sent bool
	sent = false
	attempts := 0
	sleepDuration := time.Millisecond * 100
	for {
		attempts++
		httpReq, _ := http.NewRequest(req.Method, qu.Url, bytes.NewBuffer(req.Body))
		for headerName, values := range req.Headers {
			for _, value := range values {
				httpReq.Header.Add(headerName, value)
			}
		}
		httpReq.Header.Set("X-Wsq-Id", req.UUID)
		resp, err := client.Do(httpReq)

		if err == nil {
			// get rid of the response, we don't care
			// but we do need to clean it out, so the client can reuse the same connection
			io.Copy(ioutil.Discard, resp.Body)
			resp.Body.Close()
		}

		if err == nil && resp.StatusCode == 200 {
			sent = true
			break
		}

		// max duration, ever
		if time.Since(start) > time.Second*10 {
			break
		}

		// oops, didn't work; have a pause and try again in a bit
		time.Sleep(sleepDuration)

		// slowly ramp up our sleep interval, shall we? But cap at a minute, thanks.
		if sleepDuration < time.Duration(time.Minute) {
			sleepDuration = time.Duration(float64(sleepDuration) * 1.5)
		} else {
			sleepDuration = time.Duration(time.Minute)
		}
	}
	elapsed := time.Since(start)

	if sent {
		deltchan <- qu
	} else {
		delfchan <- qu
	}

	log.WithFields(log.Fields{
		"id": req.UUID,
		"queue": qu.Queue,
		"url": qu.Url,
		"attempts": attempts,
		"sent": sent,
		"duration": elapsed.Seconds() * 1e3, /* ms hack */
	}).Info("relay-end")
}

type QueueUrl struct {
	Queue string
	Url   string
}

type CounterVals struct {
	Current uint64
	Total   uint64
	Success uint64
	Failure uint64
	Lost    uint64
}

var counters = make(map[QueueUrl]CounterVals)
var addchan = make(chan QueueUrl, 100)
var deltchan = make(chan QueueUrl, 100)
var delfchan = make(chan QueueUrl, 100)
var dellchan = make(chan QueueUrl, 100)

type Worker struct {
  QueueUrl    QueueUrl
  RequestChan chan RequestMessage
  QuitChan    chan bool
}

func (w Worker) Start() {
	go func() {
		client := &http.Client{
			// todo: reasonable default?
			Timeout: 10 * time.Second,
			// no cookies, please
			Jar: nil,
		}
		for {
			work := <-w.RequestChan
			sendEvent(client, w.QueueUrl, work)
		}
	}()
}

var sendPool = make(map[QueueUrl]Worker)

var targets TargetList

func main() {
	//log.SetFlags(log.LstdFlags | log.Lmicroseconds)
    log.SetFormatter(&log.TextFormatter{
		FullTimestamp: true,
		TimestampFormat: time.RFC3339Nano,
	})

	configId := flag.String("config", "default", "Which stanza of the config to use")
	flag.Parse()

	var ok bool
	targets, ok = allTargets[*configId]
	if !ok {
		panic("Could not load expected configuration")
	}

	// initialize counters to zero
	// You don't _have_ to do this, but I like having all the counters
	// reporting 0 immediately for stat collection purposes.
	for queue, eventTargets := range targets {
		for _, eventTarget := range eventTargets {
			qu := QueueUrl{queue, eventTarget.Url}
			counters[qu] = CounterVals{0,0,0,0,0}
			sendPool[qu] = Worker{
				QueueUrl: qu,
				RequestChan: make(chan RequestMessage, 15),
				QuitChan: make(chan bool),
			}
			sendPool[qu].Start()
		}
	}

	// goroutine to keep the counters up-to-date
	go func() {
		for {
			// watch each channel as items rolls in and modify the counters as needed
			select {
			// you can't do counters[control].Current++ in go, so this mess is what results
			case control := <-addchan:
				tmp := counters[control]
				tmp.Current++
				tmp.Total++
				counters[control] = tmp
			case control := <-deltchan:
				tmp := counters[control]
				tmp.Current--
				tmp.Success++
				counters[control] = tmp
			case control := <-delfchan:
				tmp := counters[control]
				tmp.Current--
				tmp.Failure++
				counters[control] = tmp
			case control := <-dellchan:
				tmp := counters[control]
				tmp.Current--
				tmp.Lost++
				counters[control] = tmp
			}
		}
	}()

	// A dumb goroutine to watch memory usage and counter metrics
	go func() {
		var mem runtime.MemStats
		for {
			runtime.ReadMemStats(&mem)
			log.WithFields(log.Fields{
				"mem.Alloc": mem.Alloc,
				"mem.TotalAlloc": mem.TotalAlloc,
				"mem.HeapAlloc": mem.HeapAlloc,
				"mem.HeapSys": mem.HeapSys,
				"runtime.NumGoroutine": runtime.NumGoroutine(),
			}).Info("metrics-mem")
			for cKeys, cVals:= range counters {
				log.WithFields(log.Fields{
					"queue": cKeys.Queue,
					"url": cKeys.Url,
					"current": cVals.Current,
					"total": cVals.Total,
					"success": cVals.Success,
					"failure": cVals.Failure,
					"lost": cVals.Lost,
					"chanlen": len(sendPool[cKeys].RequestChan),
				}).Info("metrics-queue")
			}
			time.Sleep(time.Second * 5)
		}
	}()

	// Oh, hey, there's the webserver!
	log.Info("starting server")
	for queue, _ := range targets {
		log.Info("registering queue @ /" + queue)
		http.HandleFunc("/" + queue, handleIncomingEvent)
	}
	http.HandleFunc("/", defaultHandler)
	err := http.ListenAndServe(":8000", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/gorilla/handlers"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"gopkg.in/mgo.v2"
)

type ErrorJson struct {
	Error string
}

var AddDeviceMissingDeviceIdError = ErrorJson{"POST to /device must be a JSON with a DeviceId property."}

func hello(context web.C, w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Herro, %s!", context.URLParams["name"])
}

func main() {
	err := SetupRoutes("SidewinderMain")
	if err == nil {
		goji.Serve()
	} else {
		fmt.Errorf("Error on launch:\n%v", err.Error())
		os.Exit(1)
	}
}

func SetupRoutes(mongoDB string) error {
	sidewinderDirector, err := NewSidewinderDirector(mongoDB)
	if err != nil {
		return err
	}

	goji.Get("/hello/:name", hello)
	goji.Get("/store/info", sidewinderDirector.DatastoreInfo)
	goji.Handle("/devices", handlers.MethodHandler{
		"POST": catchErr(sidewinderDirector.postDevice),
	})
	goji.Options("/devices/:id", func(context web.C, writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Allow", "DELETE")
		writer.WriteHeader(200)
	})
	goji.Delete("/devices/:id", func(context web.C, writer http.ResponseWriter, request *http.Request) {
		deviceCollection := sidewinderDirector.Store().DB().C("devices")
		deviceId := context.URLParams["id"]

		var result DeviceDocument

		if err := deviceCollection.FindId(deviceId).One(&result); err != nil {
			writer.WriteHeader(500)
			fmt.Fprintln(writer, err.Error())
			return
		}

		if err := deviceCollection.RemoveId(deviceId); err != nil {
			writer.WriteHeader(500)
			fmt.Fprintln(writer, err.Error())
			return
		}

		writeJson(200, result, writer)
	})
	return nil
}

func NewSidewinderDirector(mongoDB string) (*SidewinderDirector, error) {
	session, err := mgo.Dial("mongo,localhost")
	if err != nil {
		return nil, err
	}

	return &SidewinderDirector{mongoDB, session}, nil
}

type Handler func(writer http.ResponseWriter, request *http.Request) error

func catchErr(handler Handler) http.Handler {
	return web.HandlerFunc(func(context web.C, writer http.ResponseWriter, request *http.Request) {
		if err := handler(writer, request); err != nil {
			writeJson(500, ErrorJson{err.Error()}, writer)
		}
	})
}

func (self *SidewinderDirector) postDevice(writer http.ResponseWriter, request *http.Request) error {
	var sentJSON DeviceDocument
	if err := json.NewDecoder(request.Body).Decode(&sentJSON); err == nil && sentJSON.DeviceId != "" {
		if recordWasCreated, err := self.Store().AddDevice(sentJSON.DeviceId); err == nil {
			code := 200
			if recordWasCreated {
				code = 201
			}
			return writeJson(code, sentJSON, writer)
		} else {
			return err
		}
	}

	return writeJson(400, AddDeviceMissingDeviceIdError, writer)
}

func writeJson(code int, value interface{}, writer http.ResponseWriter) error {
	writer.WriteHeader(code)
	writer.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(writer).Encode(value)
}
<commit_msg>Minor, minor refactors.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/gorilla/handlers"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"gopkg.in/mgo.v2"
)

type ErrorJson struct {
	Error string
}

var AddDeviceMissingDeviceIdError = ErrorJson{"POST to /device must be a JSON with a DeviceId property."}

func hello(context web.C, w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Herro, %s!", context.URLParams["name"])
}

func main() {
	err := SetupRoutes("SidewinderMain")
	if err == nil {
		goji.Serve()
	} else {
		fmt.Errorf("Error on launch:\n%v", err.Error())
		os.Exit(1)
	}
}

func SetupRoutes(mongoDB string) error {
	sidewinderDirector, err := NewSidewinderDirector(mongoDB)
	if err != nil {
		return err
	}

	goji.Get("/hello/:name", hello)
	goji.Get("/store/info", sidewinderDirector.DatastoreInfo)
	goji.Handle("/devices", handlers.MethodHandler{
		"POST": catchErr(sidewinderDirector.postDevice),
	})
	goji.Options("/devices/:id", func(context web.C, writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Allow", "DELETE")
		writer.WriteHeader(200)
	})
	goji.Delete("/devices/:id", func(context web.C, writer http.ResponseWriter, request *http.Request) {
		deviceCollection := sidewinderDirector.Store().DB().C("devices")
		deviceId := context.URLParams["id"]

		var result DeviceDocument

		if err := deviceCollection.FindId(deviceId).One(&result); err != nil {
			writer.WriteHeader(500)
			fmt.Fprintln(writer, err.Error())
			return
		}

		if err := deviceCollection.RemoveId(deviceId); err != nil {
			writer.WriteHeader(500)
			fmt.Fprintln(writer, err.Error())
			return
		}

		writeJson(200, result, writer)
	})
	return nil
}

func NewSidewinderDirector(mongoDB string) (*SidewinderDirector, error) {
	session, err := mgo.Dial("mongo,localhost")
	if err != nil {
		return nil, err
	}

	return &SidewinderDirector{mongoDB, session}, nil
}

type Handler func(writer http.ResponseWriter, request *http.Request) error

func catchErr(handler Handler) http.Handler {
	return web.HandlerFunc(func(context web.C, writer http.ResponseWriter, request *http.Request) {
		if err := handler(writer, request); err != nil {
			writeJson(500, ErrorJson{err.Error()}, writer)
		}
	})
}

func (self *SidewinderDirector) postDevice(writer http.ResponseWriter, request *http.Request) error {
	var sentJSON DeviceDocument
	if decodeErr := json.NewDecoder(request.Body).Decode(&sentJSON); decodeErr == nil && sentJSON.DeviceId != "" {
		if recordWasCreated, err := self.Store().AddDevice(sentJSON.DeviceId); err != nil {
			return err
		} else if recordWasCreated {
			return writeJson(201, sentJSON, writer)
		} else {
			return writeJson(200, sentJSON, writer)
		}
	}
	return writeJson(400, AddDeviceMissingDeviceIdError, writer)
}

func writeJson(code int, value interface{}, writer http.ResponseWriter) error {
	writer.WriteHeader(code)
	writer.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(writer).Encode(value)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"time"
)

func main() {
	logger := log.New(os.Stderr, "", 0)
	if len(os.Args) == 1 || len(os.Args) == 0 {
		logger.Println("Example")
		logger.Println("$SilentKiller sleep 16 #will kill before the sleep ends")
		logger.Fatal("$SilentKiller -t 10 sleep 16 #Will change the default timeout from 15s to 10s")
	}

	var waittime int = 15
	var cmd *exec.Cmd
	if os.Args[1] == "-t" {
		cmd = exec.Command(os.Args[3], os.Args[4:]...)
		i, e := strconv.ParseInt(os.Args[2], 10, 64)
		if e != nil {
			logger.Fatalf("Cannot turn %s into a number", os.Args[2])
		}
		waittime = int(i)
	} else {
		cmd = exec.Command(os.Args[1], os.Args[2:]...)
	}

	stdout, err := cmd.StdoutPipe()
	stdin, _ := cmd.StdinPipe()

	if err != nil {
		logger.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		logger.Fatal(err)
	}
	buf := make([]byte, 1024)
	pkc := make(chan bool)
	go monitor(pkc, cmd, waittime)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			logger.Printf("Caught %s exiting", sig)
			cmd.Process.Kill()
			os.Exit(0)
		}
	}()
	stdinbuf := make([]byte, 1024)
	go func() {
		for {
			n, e := os.Stdin.Read(stdinbuf)
			if e != nil {
				os.Exit(0)
			}
			stdin.Write(stdinbuf[:n])
		}
	}()

	for {
		n, e := stdout.Read(buf)
		if e != nil {
			os.Exit(0)
		}
		os.Stdout.Write(buf[:n])
		pkc <- true
	}
}

func monitor(poke chan bool, cmd *exec.Cmd, waittime int) {
	logger := log.New(os.Stderr, "", 0)

	for {
		select {
		case <-poke:
			// a read from ch has occurred
		case <-time.After(time.Second * time.Duration(waittime)):
			// the read from ch has timed out
			logger.Fatal("Timed out")
			cmd.Process.Kill()
			os.Exit(0)
		}
	}
}
<commit_msg>Changed if a SIG happens then the exit code is 1<commit_after>package main

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"time"
)

func main() {
	logger := log.New(os.Stderr, "", 0)
	if len(os.Args) == 1 || len(os.Args) == 0 {
		logger.Println("Example")
		logger.Println("$SilentKiller sleep 16 #will kill before the sleep ends")
		logger.Fatal("$SilentKiller -t 10 sleep 16 #Will change the default timeout from 15s to 10s")
	}

	var waittime int = 15
	var cmd *exec.Cmd
	if os.Args[1] == "-t" {
		cmd = exec.Command(os.Args[3], os.Args[4:]...)
		i, e := strconv.ParseInt(os.Args[2], 10, 64)
		if e != nil {
			logger.Fatalf("Cannot turn %s into a number", os.Args[2])
		}
		waittime = int(i)
	} else {
		cmd = exec.Command(os.Args[1], os.Args[2:]...)
	}

	stdout, err := cmd.StdoutPipe()
	stdin, _ := cmd.StdinPipe()

	if err != nil {
		logger.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		logger.Fatal(err)
	}
	buf := make([]byte, 1024)
	pkc := make(chan bool)
	go monitor(pkc, cmd, waittime)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			logger.Printf("Caught %s exiting", sig)
			cmd.Process.Kill()
			os.Exit(1)
		}
	}()
	stdinbuf := make([]byte, 1024)
	go func() {
		for {
			n, e := os.Stdin.Read(stdinbuf)
			if e != nil {
				os.Exit(0)
			}
			stdin.Write(stdinbuf[:n])
		}
	}()

	for {
		n, e := stdout.Read(buf)
		if e != nil {
			os.Exit(0)
		}
		os.Stdout.Write(buf[:n])
		pkc <- true
	}
}

func monitor(poke chan bool, cmd *exec.Cmd, waittime int) {
	logger := log.New(os.Stderr, "", 0)

	for {
		select {
		case <-poke:
			// a read from ch has occurred
		case <-time.After(time.Second * time.Duration(waittime)):
			// the read from ch has timed out
			logger.Fatal("Timed out")
			cmd.Process.Kill()
			os.Exit(0)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	_ "github.com/joho/godotenv/autoload"
	"github.com/urfave/cli"
)

var version string // build number set at compile-time

func main() {
	app := cli.NewApp()
	app.Name = "Drone downstream plugin"
	app.Usage = "drone downstream plugin"
	app.Action = run
	app.Version = version
	app.Flags = []cli.Flag{

		cli.StringSliceFlag{
			Name:   "repositories",
			Usage:  "list of repositories to trigger",
			EnvVar: "PLUGIN_REPOSITORIES",
		},
		cli.StringFlag{
			Name:   "server",
			Usage:  "trigger a drone build on a custom server",
			EnvVar: "PLUGIN_SERVER",
		},
		cli.StringFlag{
			Name:   "token",
			Usage:  "drone API token from your user setttings",
			EnvVar: "DOWNSTREAM_TOKEN",
		},
		cli.BoolFlag{
			Name:   "fork",
			Usage:  "trigger a new build for a repository",
			EnvVar: "PLUGIN_FORK",
		},
	}

	app.Run(os.Args)
}

func run(c *cli.Context) error {

	plugin := Plugin{
		Repos:  c.StringSlice("repositories"),
		Server: c.String("server"),
		Token:  c.String("token"),
		Fork:   c.Bool("fork"),
	}

	if err := plugin.Exec(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	return nil
}
<commit_msg>Add PLUGIN_TOKEN to recognized environment variables<commit_after>package main

import (
	"fmt"
	"os"

	_ "github.com/joho/godotenv/autoload"
	"github.com/urfave/cli"
)

var version string // build number set at compile-time

func main() {
	app := cli.NewApp()
	app.Name = "Drone downstream plugin"
	app.Usage = "drone downstream plugin"
	app.Action = run
	app.Version = version
	app.Flags = []cli.Flag{

		cli.StringSliceFlag{
			Name:   "repositories",
			Usage:  "List of repositories to trigger",
			EnvVar: "PLUGIN_REPOSITORIES",
		},
		cli.StringFlag{
			Name:   "server",
			Usage:  "Trigger a drone build on a custom server",
			EnvVar: "PLUGIN_SERVER",
		},
		cli.StringFlag{
			Name:   "token",
			Usage:  "Drone API token from your user settings",
			EnvVar: "DOWNSTREAM_TOKEN,PLUGIN_TOKEN",
		},
		cli.BoolFlag{
			Name:   "fork",
			Usage:  "Trigger a new build for a repository",
			EnvVar: "PLUGIN_FORK",
		},
	}

	app.Run(os.Args)
}

func run(c *cli.Context) error {

	plugin := Plugin{
		Repos:  c.StringSlice("repositories"),
		Server: c.String("server"),
		Token:  c.String("token"),
		Fork:   c.Bool("fork"),
	}

	if err := plugin.Exec(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	return nil
}
<|endoftext|>