<commit_before>// Copyright 2014 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/maruel/wi/wi-plugin"
	"log"
	"time"
)

type CommandHandler func(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string)

// command is the boilerplate wi.Command implementation.
type command struct {
	name      string
	handler   CommandHandler
	category  wi.CommandCategory
	shortDesc langMap
	longDesc  langMap
}

func (c *command) Name() string {
	return c.name
}

func (c *command) Handle(cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	c.handler(c, cd, w, args...)
}

func (c *command) Category(cd wi.CommandDispatcherFull, w wi.Window) wi.CommandCategory {
	return c.category
}

func (c *command) ShortDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return getStr(cd.CurrentLanguage(), c.shortDesc)
}

func (c *command) LongDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return getStr(cd.CurrentLanguage(), c.longDesc)
}

// commandAlias references another command by its name. It's important to not
// bind directly to the wi.Command reference, so that if a command is replaced
// by a plugin, that the replacement command is properly called by the alias.
type commandAlias struct {
	name    string
	command string
}

func (c *commandAlias) Name() string {
	return c.name
}

func (c *commandAlias) Handle(cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// The alias is executed inline. This is important for command queue
	// ordering.
	cmd := wi.GetCommand(cd, w, c.command)
	if cmd != nil {
		cmd.Handle(cd, w, args...)
	} else {
		cmd = wi.GetCommand(cd, w, "alert")
		txt := fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasNotFound), c.name, c.command)
		cmd.Handle(cd, w, txt)
	}
}

func (c *commandAlias) Category(cd wi.CommandDispatcherFull, w wi.Window) wi.CommandCategory {
	cmd := wi.GetCommand(cd, w, c.command)
	if cmd != nil {
		return c.Category(cd, w)
	}
	return wi.UnknownCategory
}

func (c *commandAlias) ShortDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasFor), c.command)
}

func (c *commandAlias) LongDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasFor), c.command)
}

type commands struct {
	commands map[string]wi.Command
}

func (c *commands) Register(cmd wi.Command) bool {
	name := cmd.Name()
	_, ok := c.commands[name]
	c.commands[name] = cmd
	return !ok
}

func (c *commands) Get(cmd string) wi.Command {
	return c.commands[cmd]
}

func makeCommands() wi.Commands {
	return &commands{make(map[string]wi.Command)}
}

// Default commands

func cmdAlert(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Create an infobar that automatically dismiss itself after 5s.
	if len(args) != 1 {
		cd.ExecuteCommand(w, "alert", c.LongDesc(cd, w))
		return
	}
	wi.RootWindow(w).NewChildWindow(makeAlertView(args[0]), wi.DockingFloating)
	log.Printf("Tree:\n%s", wi.RootWindow(w).Tree())
	//w2.Activate()
	go func() {
		<-time.After(5 * time.Second)
		// TODO(maruel): Dismiss.
	}()
}

func cmdAddStatusBar(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// Create a tree of views that is used for alignment.
	if len(args) != 0 {
		cd.ExecuteCommand(w, "alert", c.LongDesc(cd, w))
		return
	}
	statusWindowRoot := w.NewChildWindow(makeStatusViewRoot(), wi.DockingBottom)
	statusWindowRoot.NewChildWindow(makeStatusViewName(), wi.DockingLeft)
	statusWindowRoot.NewChildWindow(makeStatusViewPosition(), wi.DockingRight)
}

func cmdOpen(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// The Window and View are created synchronously. The View is populated
	// asynchronously.
	log.Printf("Faking opening a file: %s", args)
}

func cmdNew(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
	} else {
		w.NewChildWindow(makeView("New doc", -1, -1), wi.DockingFill)
	}
}

func cmdShell(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	log.Printf("Faking opening a new shell: %s", args)
}

func cmdDoc(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Grab the current word under selection if no args is
	// provided. Pass this token to shell.
	docArgs := make([]string, len(args)+1)
	docArgs[0] = "doc"
	copy(docArgs[1:], args)
	//dispatcher.Execute(w, "shell", docArgs...)
}

func isDirtyRecurse(cd wi.CommandDispatcherFull, w wi.Window) bool {
	for _, child := range w.ChildrenWindows() {
		if isDirtyRecurse(cd, child) {
			return true
		}
		v := child.View()
		if v.IsDirty() {
			cd.ExecuteCommand(w, "alert", fmt.Sprintf(getStr(cd.CurrentLanguage(), viewDirty), v.Title()))
			return true
		}
	}
	return false
}

func cmdQuit(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): For all the View, question if fine to quit via
	// view.IsDirty(). If not fine, "prompt" y/n to force quit. If n, stop there.
	// - Send a signal to each plugin.
	// - Send a signal back to the main loop.
	root := wi.RootWindow(w)
	if !isDirtyRecurse(cd, root) {
		quitFlag = true
		// PostDraw wakes up the command event loop so it detects it's time to
		// quit.
		cd.PostDraw()
	}
}

func cmdHelp(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Creates a new Window with a ViewHelp.
	log.Printf("Faking help: %s", args)
}

func cmdAlias(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 3 {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	if args[0] == "window" {
	} else if args[0] == "global" {
		w = wi.RootWindow(w)
	} else {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	alias := &commandAlias{args[1], args[2]}
	w.View().Commands().Register(alias)
}

func cmdKeyBind(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 4 {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	location := args[0]
	modeName := args[1]
	keyName := args[2]
	cmdName := args[3]

	if location == "global" {
		w = wi.RootWindow(w)
	} else if location != "window" {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}

	var mode wi.KeyboardMode
	if modeName == "command" {
		mode = wi.CommandMode
	} else if modeName == "edit" {
		mode = wi.CommandMode
	} else if modeName == "all" {
		mode = wi.AllMode
	} else {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	w.View().KeyBindings().Set(mode, keyName, cmdName)
}

func cmdShowCommandWindow(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "show_command_window")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}

	// Create the Window with the command view and attach it to the currently
	// focused Window.
	cmdWindow := makeCommandView()
	w.NewChildWindow(cmdWindow, wi.DockingFloating)
}

func cmdLogWindowTree(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "log_window_tree")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	root := wi.RootWindow(w)
	log.Printf("Window tree:\n%s", root.Tree())
}

// Native commands.
var defaultCommands = []wi.Command{

	// WindowCategory

	// TODO(maruel): Use a 5 seconds infobar.
	&command{
		"alert",
		cmdAlert,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Shows a modal message",
		},
		langMap{
			wi.LangEn: "Prints a message in a modal dialog box.",
		},
	},
	&command{
		"add_status_bar",
		cmdAddStatusBar,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Adds the standard status bar",
		},
		langMap{
			wi.LangEn: "Adds the standard status bar to the active window. This command exists so it can be overriden by a plugin, so it can create its own status bar.",
		},
	},
	&command{
		"open",
		cmdOpen,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Opens a file in a new buffer",
		},
		langMap{
			wi.LangEn: "Opens a file in a new buffer.",
		},
	},
	&command{
		"new",
		cmdNew,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Create a new buffer",
		},
		langMap{
			wi.LangEn: "Create a new buffer.",
		},
	},

	// Editor process lifetime management.
	&command{
		"quit",
		cmdQuit,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Quits",
		},
		langMap{
			wi.LangEn: "Quits the editor. Optionally bypasses writing the files to disk.",
		},
	},

	// High level commands.
	&command{
		"shell",
		cmdShell,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Opens a shell process",
		},
		langMap{
			wi.LangEn: "Opens a shell process in a new buffer.",
		},
	},
	&command{
		"doc",
		cmdDoc,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Search godoc documentation",
		},
		langMap{
			wi.LangEn: "Uses the 'doc' tool to get documentation about the text under the cursor.",
		},
	},
	&command{
		"help",
		cmdHelp,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Prints help",
		},
		langMap{
			wi.LangEn: "Prints general help or help for a particular command.",
		},
	},
	&commandAlias{"q", "quit"},

	// CommandsCategory

	&command{
		"alias",
		cmdAlias,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Binds an alias to another command",
		},
		langMap{
			// TODO(maruel): For complex aliasing, use macro?
			wi.LangEn: "Usage: alias [window|global] <alias> <name>\nBinds an alias to another command. The alias can either be local to the window or global",
		},
	},
	&command{
		"keybind",
		cmdKeyBind,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Binds a keyboard mapping to a command",
		},
		langMap{
			wi.LangEn: "Usage: keybind [window|global] [command|edit|all] <key> <command>\nBinds a keyboard mapping to a command. The binding can be to the active view for view-specific key binding or to the root view for global key bindings.",
		},
	},
	&command{
		"show_command_window",
		cmdShowCommandWindow,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Shows the interactive command window",
		},
		langMap{
			wi.LangEn: "This commands exists so it can be bound to a key to pop up the interactive command window.",
		},
	},

	// Debugging.
	&command{
		"log_window_tree",
		cmdLogWindowTree,
		wi.DebugCategory,
		langMap{
			wi.LangEn: "Logs the tree in the log file",
		},
		langMap{
			wi.LangEn: "Logs the tree in the log file, this is only relevant if -verbose is used.",
		},
	},

	// DIRECTION = up/down/left/right
	// window_DIRECTION
	// window_close
	// cursor_move_DIRECTION
	// add_text/insert/delete
	// undo/redo
	// verb/movement/multiplier
	// Modes, select (both column and normal), command.
	// 'screenshot', mainly for unit test; open a new buffer with the screenshot, so it can be saved with 'w'.
	// ...
}

// RegisterDefaultCommands registers the top-level native commands. This
// includes the window management commands, opening a new file buffer (it's a
// text editor after all) and help, quitting, etc. It doesn't includes handling
// a file buffer itself, it's up to the relevant view to add the corresponding
// commands. For example, "open" is implemented but "write" is not!
func RegisterDefaultCommands(dispatcher wi.Commands) {
	for _, cmd := range defaultCommands {
		dispatcher.Register(cmd)
	}
}
<commit_msg>Sort commands<commit_after>// Copyright 2014 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/maruel/wi/wi-plugin"
	"log"
	"time"
)

type CommandHandler func(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string)

// command is the boilerplate wi.Command implementation.
type command struct {
	name      string
	handler   CommandHandler
	category  wi.CommandCategory
	shortDesc langMap
	longDesc  langMap
}

func (c *command) Name() string {
	return c.name
}

func (c *command) Handle(cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	c.handler(c, cd, w, args...)
}

func (c *command) Category(cd wi.CommandDispatcherFull, w wi.Window) wi.CommandCategory {
	return c.category
}

func (c *command) ShortDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return getStr(cd.CurrentLanguage(), c.shortDesc)
}

func (c *command) LongDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return getStr(cd.CurrentLanguage(), c.longDesc)
}

// commandAlias references another command by its name. It's important to not
// bind directly to the wi.Command reference, so that if a command is replaced
// by a plugin, that the replacement command is properly called by the alias.
type commandAlias struct {
	name    string
	command string
}

func (c *commandAlias) Name() string {
	return c.name
}

func (c *commandAlias) Handle(cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// The alias is executed inline. This is important for command queue
	// ordering.
	cmd := wi.GetCommand(cd, w, c.command)
	if cmd != nil {
		cmd.Handle(cd, w, args...)
	} else {
		cmd = wi.GetCommand(cd, w, "alert")
		txt := fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasNotFound), c.name, c.command)
		cmd.Handle(cd, w, txt)
	}
}

func (c *commandAlias) Category(cd wi.CommandDispatcherFull, w wi.Window) wi.CommandCategory {
	cmd := wi.GetCommand(cd, w, c.command)
	if cmd != nil {
		return c.Category(cd, w)
	}
	return wi.UnknownCategory
}

func (c *commandAlias) ShortDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasFor), c.command)
}

func (c *commandAlias) LongDesc(cd wi.CommandDispatcherFull, w wi.Window) string {
	return fmt.Sprintf(getStr(cd.CurrentLanguage(), aliasFor), c.command)
}

type commands struct {
	commands map[string]wi.Command
}

func (c *commands) Register(cmd wi.Command) bool {
	name := cmd.Name()
	_, ok := c.commands[name]
	c.commands[name] = cmd
	return !ok
}

func (c *commands) Get(cmd string) wi.Command {
	return c.commands[cmd]
}

func makeCommands() wi.Commands {
	return &commands{make(map[string]wi.Command)}
}

// Default commands

func cmdAlert(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Create an infobar that automatically dismiss itself after 5s.
	if len(args) != 1 {
		cd.ExecuteCommand(w, "alert", c.LongDesc(cd, w))
		return
	}
	// TODO(maruel): Use a 5 seconds infobar.
	wi.RootWindow(w).NewChildWindow(makeAlertView(args[0]), wi.DockingFloating)
	log.Printf("Tree:\n%s", wi.RootWindow(w).Tree())
	//w2.Activate()
	go func() {
		<-time.After(5 * time.Second)
		// TODO(maruel): Dismiss.
	}()
}

func cmdAddStatusBar(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// Create a tree of views that is used for alignment.
	if len(args) != 0 {
		cd.ExecuteCommand(w, "alert", c.LongDesc(cd, w))
		return
	}
	statusWindowRoot := w.NewChildWindow(makeStatusViewRoot(), wi.DockingBottom)
	statusWindowRoot.NewChildWindow(makeStatusViewName(), wi.DockingLeft)
	statusWindowRoot.NewChildWindow(makeStatusViewPosition(), wi.DockingRight)
}

func cmdOpen(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// The Window and View are created synchronously. The View is populated
	// asynchronously.
	log.Printf("Faking opening a file: %s", args)
}

func cmdNew(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
	} else {
		w.NewChildWindow(makeView("New doc", -1, -1), wi.DockingFill)
	}
}

func cmdShell(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	log.Printf("Faking opening a new shell: %s", args)
}

func cmdDoc(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Grab the current word under selection if no args is
	// provided. Pass this token to shell.
	docArgs := make([]string, len(args)+1)
	docArgs[0] = "doc"
	copy(docArgs[1:], args)
	//dispatcher.Execute(w, "shell", docArgs...)
}

func isDirtyRecurse(cd wi.CommandDispatcherFull, w wi.Window) bool {
	for _, child := range w.ChildrenWindows() {
		if isDirtyRecurse(cd, child) {
			return true
		}
		v := child.View()
		if v.IsDirty() {
			cd.ExecuteCommand(w, "alert", fmt.Sprintf(getStr(cd.CurrentLanguage(), viewDirty), v.Title()))
			return true
		}
	}
	return false
}

func cmdQuit(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): For all the View, question if fine to quit via
	// view.IsDirty(). If not fine, "prompt" y/n to force quit. If n, stop there.
	// - Send a signal to each plugin.
	// - Send a signal back to the main loop.
	root := wi.RootWindow(w)
	if !isDirtyRecurse(cd, root) {
		quitFlag = true
		// PostDraw wakes up the command event loop so it detects it's time to
		// quit.
		cd.PostDraw()
	}
}

func cmdHelp(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	// TODO(maruel): Creates a new Window with a ViewHelp.
	log.Printf("Faking help: %s", args)
}

func cmdAlias(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 3 {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	if args[0] == "window" {
	} else if args[0] == "global" {
		w = wi.RootWindow(w)
	} else {
		cmd := wi.GetCommand(cd, w, "alias")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	alias := &commandAlias{args[1], args[2]}
	w.View().Commands().Register(alias)
}

func cmdKeyBind(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 4 {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	location := args[0]
	modeName := args[1]
	keyName := args[2]
	cmdName := args[3]

	if location == "global" {
		w = wi.RootWindow(w)
	} else if location != "window" {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}

	var mode wi.KeyboardMode
	if modeName == "command" {
		mode = wi.CommandMode
	} else if modeName == "edit" {
		mode = wi.CommandMode
	} else if modeName == "all" {
		mode = wi.AllMode
	} else {
		cmd := wi.GetCommand(cd, w, "keybind")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	w.View().KeyBindings().Set(mode, keyName, cmdName)
}

func cmdShowCommandWindow(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "show_command_window")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}

	// Create the Window with the command view and attach it to the currently
	// focused Window.
	cmdWindow := makeCommandView()
	w.NewChildWindow(cmdWindow, wi.DockingFloating)
}

func cmdLogWindowTree(c *command, cd wi.CommandDispatcherFull, w wi.Window, args ...string) {
	if len(args) != 0 {
		cmd := wi.GetCommand(cd, w, "log_window_tree")
		cd.ExecuteCommand(w, "alert", cmd.LongDesc(cd, w))
		return
	}
	root := wi.RootWindow(w)
	log.Printf("Window tree:\n%s", root.Tree())
}

// Native commands.
var defaultCommands = []wi.Command{
	&command{
		"alert",
		cmdAlert,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Shows a modal message",
		},
		langMap{
			wi.LangEn: "Prints a message in a modal dialog box.",
		},
	},
	&command{
		"add_status_bar",
		cmdAddStatusBar,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Adds the standard status bar",
		},
		langMap{
			wi.LangEn: "Adds the standard status bar to the active window. This command exists so it can be overriden by a plugin, so it can create its own status bar.",
		},
	},
	&command{
		"help",
		cmdHelp,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Prints help",
		},
		langMap{
			wi.LangEn: "Prints general help or help for a particular command.",
		},
	},
	&command{
		"new",
		cmdNew,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Create a new buffer",
		},
		langMap{
			wi.LangEn: "Create a new buffer.",
		},
	},
	&command{
		"open",
		cmdOpen,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Opens a file in a new buffer",
		},
		langMap{
			wi.LangEn: "Opens a file in a new buffer.",
		},
	},
	&command{
		"quit",
		cmdQuit,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Quits",
		},
		langMap{
			wi.LangEn: "Quits the editor. Optionally bypasses writing the files to disk.",
		},
	},
	&command{
		"shell",
		cmdShell,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Opens a shell process",
		},
		langMap{
			wi.LangEn: "Opens a shell process in a new buffer.",
		},
	},
	&command{
		"doc",
		cmdDoc,
		wi.WindowCategory,
		langMap{
			wi.LangEn: "Search godoc documentation",
		},
		langMap{
			wi.LangEn: "Uses the 'doc' tool to get documentation about the text under the cursor.",
		},
	},

	&command{
		"alias",
		cmdAlias,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Binds an alias to another command",
		},
		langMap{
			// TODO(maruel): For complex aliasing, use macro?
			wi.LangEn: "Usage: alias [window|global] <alias> <name>\nBinds an alias to another command. The alias can either be local to the window or global",
		},
	},
	&command{
		"keybind",
		cmdKeyBind,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Binds a keyboard mapping to a command",
		},
		langMap{
			wi.LangEn: "Usage: keybind [window|global] [command|edit|all] <key> <command>\nBinds a keyboard mapping to a command. The binding can be to the active view for view-specific key binding or to the root view for global key bindings.",
		},
	},
	&command{
		"show_command_window",
		cmdShowCommandWindow,
		wi.CommandsCategory,
		langMap{
			wi.LangEn: "Shows the interactive command window",
		},
		langMap{
			wi.LangEn: "This commands exists so it can be bound to a key to pop up the interactive command window.",
		},
	},

	&command{
		"log_window_tree",
		cmdLogWindowTree,
		wi.DebugCategory,
		langMap{
			wi.LangEn: "Logs the tree in the log file",
		},
		langMap{
			wi.LangEn: "Logs the tree in the log file, this is only relevant if -verbose is used.",
		},
	},

	&commandAlias{"q", "quit"},

	// DIRECTION = up/down/left/right
	// window_DIRECTION
	// window_close
	// cursor_move_DIRECTION
	// add_text/insert/delete
	// undo/redo
	// verb/movement/multiplier
	// Modes, select (both column and normal), command.
	// 'screenshot', mainly for unit test; open a new buffer with the screenshot, so it can be saved with 'w'.
	// ...
}

// RegisterDefaultCommands registers the top-level native commands. This
// includes the window management commands, opening a new file buffer (it's a
// text editor after all) and help, quitting, etc. It doesn't includes handling
// a file buffer itself, it's up to the relevant view to add the corresponding
// commands. For example, "open" is implemented but "write" is not!
func RegisterDefaultCommands(dispatcher wi.Commands) {
	for _, cmd := range defaultCommands {
		dispatcher.Register(cmd)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"errors"
	"fmt"
)

// Commands holds a large list of useful methods to interact with the server,
// and wrappers for common events.
type Commands struct {
	c *Client
}

// Nick changes the client nickname.
func (cmd *Commands) Nick(name string) error {
	if !IsValidNick(name) {
		return &ErrInvalidTarget{Target: name}
	}

	cmd.c.Send(&Event{Command: NICK, Params: []string{name}})
	return nil
}

// Join attempts to enter a list of IRC channels, at bulk if possible to
// prevent sending extensive JOIN commands.
func (cmd *Commands) Join(channels ...string) error {
	// We can join multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// JoinKey attempts to enter an IRC channel with a password.
func (cmd *Commands) JoinKey(channel, password string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}})
	return nil
}

// Part leaves an IRC channel.
func (cmd *Commands) Part(channel string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: PART, Params: []string{channel}})
	return nil
}

// PartMessage leaves an IRC channel with a specified leave message.
func (cmd *Commands) PartMessage(channel, message string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: PART, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
	return nil
}

// SendCTCP sends a CTCP request to target. Note that this method uses
// PRIVMSG specifically.
func (cmd *Commands) SendCTCP(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Message(target, out)
}

// SendCTCPf sends a CTCP request to target using a specific format. Note that
// this method uses PRIVMSG specifically.
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReplyf sends a CTCP response to target using a specific format.
// Note that this method uses NOTICE specifically.
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReply sends a CTCP response to target. Note that this method uses
// NOTICE specifically.
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Notice(target, out)
}

// Message sends a PRIVMSG to target (either channel, service, or user).
func (cmd *Commands) Message(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message, EmptyTrailing: true})
	return nil
}

// Messagef sends a formated PRIVMSG to target (either channel, service, or
// user).
func (cmd *Commands) Messagef(target, format string, a ...interface{}) error {
	return cmd.Message(target, fmt.Sprintf(format, a...))
}

// ErrInvalidSource is returned when a method needs to know the origin of an
// event, however Event.Source is unknown (e.g. sent by the user, not the
// server.)
var ErrInvalidSource = errors.New("event has nil or invalid source address")

// Reply sends a reply to channel or user, based on where the supplied event
// originated from. See also ReplyTo().
func (cmd *Commands) Reply(event Event, message string) error {
	if event.Source == nil {
		return ErrInvalidSource
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		return cmd.Message(event.Params[0], message)
	}

	return cmd.Message(event.Source.Name, message)
}

// Replyf sends a reply to channel or user with a format string, based on
// where the supplied event originated from. See also ReplyTof().
func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) error {
	return cmd.Reply(event, fmt.Sprintf(format, a...))
}

// ReplyTo sends a reply to a channel or user, based on where the supplied
// event originated from. ReplyTo(), when originating from a channel will
// default to replying with "<user>, <message>". See also Reply().
func (cmd *Commands) ReplyTo(event Event, message string) error {
	if event.Source == nil {
		return ErrInvalidSource
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		return cmd.Message(event.Params[0], event.Source.Name+", "+message)
	}

	return cmd.Message(event.Source.Name, message)
}

// ReplyTof sends a reply to a channel or user with a format string, based
// on where the supplied event originated from. ReplyTo(), when originating
// from a channel will default to replying with "<user>, <message>". See
// also Replyf().
func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) error {
	return cmd.ReplyTo(event, fmt.Sprintf(format, a...))
}

// Action sends a PRIVMSG ACTION (/me) to target (either channel, service,
// or user).
func (cmd *Commands) Action(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{
		Command:  PRIVMSG,
		Params:   []string{target},
		Trailing: fmt.Sprintf("\001ACTION %s\001", message),
	})
	return nil
}

// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel,
// service, or user).
func (cmd *Commands) Actionf(target, format string, a ...interface{}) error {
	return cmd.Action(target, fmt.Sprintf(format, a...))
}

// Notice sends a NOTICE to target (either channel, service, or user).
func (cmd *Commands) Notice(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message, EmptyTrailing: true})
	return nil
}

// Noticef sends a formated NOTICE to target (either channel, service, or
// user).
func (cmd *Commands) Noticef(target, format string, a ...interface{}) error {
	return cmd.Notice(target, fmt.Sprintf(format, a...))
}

// SendRaw sends a raw string back to the server, without carriage returns
// or newlines.
func (cmd *Commands) SendRaw(raw string) error {
	e := ParseEvent(raw)
	if e == nil {
		return errors.New("invalid event: " + raw)
	}

	cmd.c.Send(e)
	return nil
}

// SendRawf sends a formated string back to the server, without carriage
// returns or newlines.
func (cmd *Commands) SendRawf(format string, a ...interface{}) error {
	return cmd.SendRaw(fmt.Sprintf(format, a...))
}

// Topic sets the topic of channel to message. Does not verify the length
// of the topic.
func (cmd *Commands) Topic(channel, message string) {
	cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
}

// Who sends a WHO query to the server, which will attempt WHOX by default.
// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This
// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with
// girc's builtin tracking functionality.
func (cmd *Commands) Who(target string) error {
	if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}})
	return nil
}

// Whois sends a WHOIS query to the server, targeted at a specific user.
// as WHOIS is a bit slower, you may want to use WHO for brief user info.
func (cmd *Commands) Whois(nick string) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}})
	return nil
}

// Ping sends a PING query to the server, with a specific identifier that
// the server should respond with.
func (cmd *Commands) Ping(id string) {
	cmd.c.write(&Event{Command: PING, Params: []string{id}})
}

// Pong sends a PONG query to the server, with an identifier which was
// received from a previous PING query received by the client.
func (cmd *Commands) Pong(id string) {
	cmd.c.write(&Event{Command: PONG, Params: []string{id}})
}

// Oper sends a OPER authentication query to the server, with a username
// and password.
func (cmd *Commands) Oper(user, pass string) {
	cmd.c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true})
}

// Kick sends a KICK query to the server, attempting to kick nick from
// channel, with reason. If reason is blank, one will not be sent to the
// server.
func (cmd *Commands) Kick(channel, nick, reason string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	if reason != "" {
		cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason, EmptyTrailing: true})
		return nil
	}

	cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}})
	return nil
}

// Invite sends a INVITE query to the server, to invite nick to channel.
func (cmd *Commands) Invite(channel, nick string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}})
	return nil
}

// Away sends a AWAY query to the server, suggesting that the client is no
// longer active. If reason is blank, Client.Back() is called. Also see
// Client.Back().
func (cmd *Commands) Away(reason string) {
	if reason == "" {
		cmd.Back()
		return
	}

	cmd.c.Send(&Event{Command: AWAY, Params: []string{reason}})
}

// Back sends a AWAY query to the server, however the query is blank,
// suggesting that the client is active once again. Also see Client.Away().
func (cmd *Commands) Back() {
	cmd.c.Send(&Event{Command: AWAY})
}

// List sends a LIST query to the server, which will list channels and topics.
// Supports multiple channels at once, in hopes it will reduce extensive
// LIST queries to the server. Supply no channels to run a list against the
// entire server (warning, that may mean LOTS of channels!)
func (cmd *Commands) List(channels ...string) error {
	if len(channels) == 0 {
		cmd.c.Send(&Event{Command: LIST})
		return nil
	}

	// We can LIST multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// Whowas sends a WHOWAS query to the server. amount is the amount of results
// you want back.
func (cmd *Commands) Whowas(nick string, amount int) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}})
	return nil
}
<commit_msg>remove enforcement of rfc-valid users/nicks/channels<commit_after>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"errors"
	"fmt"
)

// Commands holds a large list of useful methods to interact with the server,
// and wrappers for common events.
type Commands struct {
	c *Client
}

// Nick changes the client nickname.
func (cmd *Commands) Nick(name string) {
	cmd.c.Send(&Event{Command: NICK, Params: []string{name}})
}

// Join attempts to enter a list of IRC channels, at bulk if possible to
// prevent sending extensive JOIN commands.
func (cmd *Commands) Join(channels ...string) {
	// We can join multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			return
		}
	}
}

// JoinKey attempts to enter an IRC channel with a password.
func (cmd *Commands) JoinKey(channel, password string) {
	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}})
}

// Part leaves an IRC channel.
func (cmd *Commands) Part(channel string) {
	cmd.c.Send(&Event{Command: PART, Params: []string{channel}})
}

// PartMessage leaves an IRC channel with a specified leave message.
func (cmd *Commands) PartMessage(channel, message string) {
	cmd.c.Send(&Event{Command: PART, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
}

// SendCTCP sends a CTCP request to target. Note that this method uses
// PRIVMSG specifically. ctcpType is the CTCP command, e.g. "FINGER", "TIME",
// "VERSION", etc.
func (cmd *Commands) SendCTCP(target, ctcpType, message string) {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
	}

	cmd.Message(target, out)
}

// SendCTCPf sends a CTCP request to target using a specific format. Note that
// this method uses PRIVMSG specifically. ctcpType is the CTCP command, e.g.
// "FINGER", "TIME", "VERSION", etc.
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) {
	cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReplyf sends a CTCP response to target using a specific format.
// Note that this method uses NOTICE specifically. ctcpType is the CTCP
// command, e.g. "FINGER", "TIME", "VERSION", etc.
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) {
	cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReply sends a CTCP response to target. Note that this method uses
// NOTICE specifically.
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
	}

	cmd.Notice(target, out)
}

// Message sends a PRIVMSG to target (either channel, service, or user).
func (cmd *Commands) Message(target, message string) {
	cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message, EmptyTrailing: true})
}

// Messagef sends a formated PRIVMSG to target (either channel, service, or
// user).
func (cmd *Commands) Messagef(target, format string, a ...interface{}) {
	cmd.Message(target, fmt.Sprintf(format, a...))
}

// ErrInvalidSource is returned when a method needs to know the origin of an
// event, however Event.Source is unknown (e.g. sent by the user, not the
// server.)
var ErrInvalidSource = errors.New("event has nil or invalid source address")

// Reply sends a reply to channel or user, based on where the supplied event
// originated from. See also ReplyTo(). Panics if the incoming event has no
// source.
func (cmd *Commands) Reply(event Event, message string) {
	if event.Source == nil {
		panic(ErrInvalidSource)
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		cmd.Message(event.Params[0], message)
		return
	}

	cmd.Message(event.Source.Name, message)
}

// Replyf sends a reply to channel or user with a format string, based on
// where the supplied event originated from. See also ReplyTof(). Panics if
// the incoming event has no source.
func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) {
	cmd.Reply(event, fmt.Sprintf(format, a...))
}

// ReplyTo sends a reply to a channel or user, based on where the supplied
// event originated from. ReplyTo(), when originating from a channel will
// default to replying with "<user>, <message>". See also Reply(). Panics if
// the incoming event has no source.
func (cmd *Commands) ReplyTo(event Event, message string) {
	if event.Source == nil {
		panic(ErrInvalidSource)
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		cmd.Message(event.Params[0], event.Source.Name+", "+message)
		return
	}

	cmd.Message(event.Source.Name, message)
}

// ReplyTof sends a reply to a channel or user with a format string, based
// on where the supplied event originated from. ReplyTo(), when originating
// from a channel will default to replying with "<user>, <message>". See
// also Replyf(). Panics if the incoming event has no source.
func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) {
	cmd.ReplyTo(event, fmt.Sprintf(format, a...))
}

// Action sends a PRIVMSG ACTION (/me) to target (either channel, service,
// or user).
func (cmd *Commands) Action(target, message string) {
	cmd.c.Send(&Event{
		Command:  PRIVMSG,
		Params:   []string{target},
		Trailing: fmt.Sprintf("\001ACTION %s\001", message),
	})
}

// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel,
// service, or user).
func (cmd *Commands) Actionf(target, format string, a ...interface{}) {
	cmd.Action(target, fmt.Sprintf(format, a...))
}

// Notice sends a NOTICE to target (either channel, service, or user).
func (cmd *Commands) Notice(target, message string) {
	cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message, EmptyTrailing: true})
}

// Noticef sends a formated NOTICE to target (either channel, service, or
// user).
func (cmd *Commands) Noticef(target, format string, a ...interface{}) {
	cmd.Notice(target, fmt.Sprintf(format, a...))
}

// SendRaw sends a raw string back to the server, without carriage returns
// or newlines.
func (cmd *Commands) SendRaw(raw string) error {
	e := ParseEvent(raw)
	if e == nil {
		return errors.New("invalid event: " + raw)
	}

	cmd.c.Send(e)
	return nil
}

// SendRawf sends a formated string back to the server, without carriage
// returns or newlines.
func (cmd *Commands) SendRawf(format string, a ...interface{}) error {
	return cmd.SendRaw(fmt.Sprintf(format, a...))
}

// Topic sets the topic of channel to message. Does not verify the length
// of the topic.
func (cmd *Commands) Topic(channel, message string) {
	cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message, EmptyTrailing: true})
}

// Who sends a WHO query to the server, which will attempt WHOX by default.
// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This
// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with
// girc's builtin tracking functionality.
func (cmd *Commands) Who(target string) {
	cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}})
}

// Whois sends a WHOIS query to the server, targeted at a specific user.
// as WHOIS is a bit slower, you may want to use WHO for brief user info.
func (cmd *Commands) Whois(nick string) {
	cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}})
}

// Ping sends a PING query to the server, with a specific identifier that
// the server should respond with.
func (cmd *Commands) Ping(id string) {
	cmd.c.write(&Event{Command: PING, Params: []string{id}})
}

// Pong sends a PONG query to the server, with an identifier which was
// received from a previous PING query received by the client.
func (cmd *Commands) Pong(id string) {
	cmd.c.write(&Event{Command: PONG, Params: []string{id}})
}

// Oper sends a OPER authentication query to the server, with a username
// and password.
func (cmd *Commands) Oper(user, pass string) {
	cmd.c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true})
}

// Kick sends a KICK query to the server, attempting to kick nick from
// channel, with reason. If reason is blank, one will not be sent to the
// server.
func (cmd *Commands) Kick(channel, nick, reason string) {
	if reason != "" {
		cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason, EmptyTrailing: true})
	}

	cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}})
}

// Invite sends a INVITE query to the server, to invite nick to channel.
func (cmd *Commands) Invite(channel, nick string) {
	cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}})
}

// Away sends a AWAY query to the server, suggesting that the client is no
// longer active. If reason is blank, Client.Back() is called. Also see
// Client.Back().
func (cmd *Commands) Away(reason string) {
	if reason == "" {
		cmd.Back()
		return
	}

	cmd.c.Send(&Event{Command: AWAY, Params: []string{reason}})
}

// Back sends a AWAY query to the server, however the query is blank,
// suggesting that the client is active once again. Also see Client.Away().
func (cmd *Commands) Back() {
	cmd.c.Send(&Event{Command: AWAY})
}

// List sends a LIST query to the server, which will list channels and topics.
// Supports multiple channels at once, in hopes it will reduce extensive
// LIST queries to the server. Supply no channels to run a list against the
// entire server (warning, that may mean LOTS of channels!)
func (cmd *Commands) List(channels ...string) {
	if len(channels) == 0 {
		cmd.c.Send(&Event{Command: LIST})
		return
	}

	// We can LIST multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			return
		}
	}
}

// Whowas sends a WHOWAS query to the server. amount is the amount of results
// you want back.
func (cmd *Commands) Whowas(nick string, amount int) {
	cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}})
}
<|endoftext|>


<commit_before>package grim

// Copyright 2015 MediaMath <http://www.mediamath.com>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import "fmt"

// Instance models the state of a configured Grim instance.
type Instance struct {
	configRoot *string
	queue      *sqsQueue
}

// SetConfigRoot sets the base path of the configuration directory and clears any previously read config values from memory.
func (i *Instance) SetConfigRoot(path string) {
	i.configRoot = &path
	i.queue = nil
}

// PrepareGrimQueue creates or reuses the Amazon SQS queue named in the config.
func (i *Instance) PrepareGrimQueue() error {
	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	queue, err := prepareSQSQueue(config.awsKey, config.awsSecret, config.awsRegion, config.grimQueueName)
	if err != nil {
		return fatalGrimErrorf("error preparing queue: %v", err)
	}

	i.queue = queue

	return nil
}

// PrepareRepos discovers all repos that are configured then sets up SNS and GitHub.
// It is an error to call this without calling PrepareGrimQueue first.
func (i *Instance) PrepareRepos() error {
	if err := i.checkGrimQueue(); err != nil {
		return err
	}

	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	repos := getAllConfiguredRepos(configRoot)

	var topicARNs []string
	for _, repo := range repos {
		localConfig, err := getEffectiveConfig(configRoot, repo.owner, repo.name)

		snsTopicName := fmt.Sprintf("grim-%v-%v-repo-topic", repo.owner, repo.name)

		snsTopicARN, err := prepareSNSTopic(config.awsKey, config.awsSecret, config.awsRegion, snsTopicName)
		if err != nil {
			return fatalGrimErrorf("error creating SNS topic: %v", err)
		}

		err = prepareSubscription(config.awsKey, config.awsSecret, config.awsRegion, snsTopicARN, i.queue.ARN)
		if err != nil {
			return fatalGrimErrorf("error subscribing Grim queue %q to SNS topic %q: %v", i.queue.ARN, snsTopicARN, err)
		}

		err = prepareAmazonSNSService(localConfig.gitHubToken, repo.owner, repo.name, snsTopicARN, config.awsKey, config.awsSecret, config.awsRegion)
		if err != nil {
			return fatalGrimErrorf("error creating configuring GitHub AmazonSNS service: %v", err)
		}
		topicARNs = append(topicARNs, snsTopicARN)
	}

	err = setPolicy(config.awsKey, config.awsSecret, config.awsRegion, i.queue.ARN, i.queue.URL, topicARNs)
	if err != nil {
		return fatalGrimErrorf("error setting policy for Grim queue: %v", err)
	}

	return nil
}

// BuildNextInGrimQueue creates or reuses an SQS queue as a source of work.
func (i *Instance) BuildNextInGrimQueue() error {
	if err := i.checkGrimQueue(); err != nil {
		return err
	}

	configRoot := getEffectiveConfigRoot(i.configRoot)

	globalConfig, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return grimErrorf("error while reading config: %v", err)
	}

	message, err := getNextMessage(globalConfig.awsKey, globalConfig.awsSecret, globalConfig.awsRegion, i.queue.URL)
	if err != nil {
		return grimErrorf("error retrieving message from Grim queue %q: %v", i.queue.URL, err)
	}

	if message != "" {
		hook, err := extractHookEvent(message)
		if err != nil {
			return grimErrorf("error extracting hook from message: %v", err)
		}

		if !(hook.eventName == "push" || hook.eventName == "pull_request" && (hook.action == "opened" || hook.action == "reopened" || hook.action == "synchronize")) {
			return nil
		}

		if hook.eventName == "pull_request" {
			sha, err := pollForMergeCommitSha(globalConfig.gitHubToken, hook.owner, hook.repo, hook.prNumber)
			if err != nil {
				return grimErrorf("error getting merge commit sha: %v", err)
			} else if sha == "" {
				return grimErrorf("error getting merge commit sha: field empty")
			}
			hook.ref = sha
		}

		localConfig, err := getEffectiveConfig(configRoot, hook.owner, hook.repo)
		if err != nil {
			return grimErrorf("error while reading config: %v", err)
		}

		return buildForHook(configRoot, localConfig, *hook)
	}

	return nil
}

// BuildRef builds a git ref immediately.
func (i *Instance) BuildRef(owner, repo, ref string) error {
	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveConfig(configRoot, owner, repo)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	return buildForHook(configRoot, config, hookEvent{
		owner: owner,
		repo:  repo,
		ref:   ref,
	})
}

func buildForHook(configRoot string, config *effectiveConfig, hook hookEvent) error {
	extraEnv := hook.env()

	// TODO: do something with the err
	notifyPending(config, hook)

	result, err := build(configRoot, config.workspaceRoot, config.pathToCloneIn, hook.owner, hook.repo, extraEnv)
	if err != nil {
		notifyError(config, hook)
		return fatalGrimErrorf("error during %v: %v", describeHook(hook), err)
	}

	var notifyError error
	if result.ExitCode == 0 {
		notifyError = notifySuccess(config, hook)
	} else {
		notifyError = notifyFailure(config, hook)
	}

	err = appendResult(config.resultRoot, hook.owner, hook.repo, *result)
	if err != nil {
		return fatalGrimErrorf("error while storing result: %v", err)
	}

	return notifyError
}

func describeHook(hook hookEvent) string {
	return fmt.Sprintf("build of %v/%v initiated by a %q to %q by %q", hook.owner, hook.repo, hook.eventName, hook.target, hook.userName)
}

func notifyPending(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSPending, fmt.Sprintf("Starting %v", describeHook(hook)), ColorYellow)
}

func notifyError(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSError, fmt.Sprintf("Error during %v", describeHook(hook)), ColorGray)
}

func notifyFailure(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSFailure, fmt.Sprintf("Failure during %v", describeHook(hook)), ColorRed)
}

func notifySuccess(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSSuccess, fmt.Sprintf("Success after %v", describeHook(hook)), ColorGreen)
}

func notify(config *effectiveConfig, hook hookEvent, state refStatus, message string, color messageColor) error {
	if hook.eventName != "push" && hook.eventName != "pull_request" {
		return nil
	}

	//add grimServerID/grimQueueName to hipchat message test
	message += "ServerID:" + config.grimServerID

	ghErr := setRefStatus(config.gitHubToken, hook.owner, hook.repo, hook.statusRef, state, "", message)

	if config.hipChatToken != "" && config.hipChatRoom != "" {
		err := sendMessageToRoom(config.hipChatToken, config.hipChatRoom, "Grim", message, color)
		if err != nil {
			return err
		}
	}

	return ghErr
}

func (i *Instance) checkGrimQueue() error {
	if i.queue == nil {
		return fatalGrimErrorf("the Grim queue must be prepared first")
	}

	return nil
}
<commit_msg>grim local test<commit_after>package grim

// Copyright 2015 MediaMath <http://www.mediamath.com>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import "fmt"

// Instance models the state of a configured Grim instance.
type Instance struct {
	configRoot *string
	queue      *sqsQueue
}

// SetConfigRoot sets the base path of the configuration directory and clears any previously read config values from memory.
func (i *Instance) SetConfigRoot(path string) {
	i.configRoot = &path
	i.queue = nil
}

// PrepareGrimQueue creates or reuses the Amazon SQS queue named in the config.
func (i *Instance) PrepareGrimQueue() error {
	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	queue, err := prepareSQSQueue(config.awsKey, config.awsSecret, config.awsRegion, config.grimQueueName)
	if err != nil {
		return fatalGrimErrorf("error preparing queue: %v", err)
	}

	i.queue = queue

	return nil
}

// PrepareRepos discovers all repos that are configured then sets up SNS and GitHub.
// It is an error to call this without calling PrepareGrimQueue first.
func (i *Instance) PrepareRepos() error {
	if err := i.checkGrimQueue(); err != nil {
		return err
	}

	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	repos := getAllConfiguredRepos(configRoot)

	var topicARNs []string
	for _, repo := range repos {
		localConfig, err := getEffectiveConfig(configRoot, repo.owner, repo.name)

		snsTopicName := fmt.Sprintf("grim-%v-%v-repo-topic", repo.owner, repo.name)

		snsTopicARN, err := prepareSNSTopic(config.awsKey, config.awsSecret, config.awsRegion, snsTopicName)
		if err != nil {
			return fatalGrimErrorf("error creating SNS topic: %v", err)
		}

		err = prepareSubscription(config.awsKey, config.awsSecret, config.awsRegion, snsTopicARN, i.queue.ARN)
		if err != nil {
			return fatalGrimErrorf("error subscribing Grim queue %q to SNS topic %q: %v", i.queue.ARN, snsTopicARN, err)
		}

		err = prepareAmazonSNSService(localConfig.gitHubToken, repo.owner, repo.name, snsTopicARN, config.awsKey, config.awsSecret, config.awsRegion)
		if err != nil {
			return fatalGrimErrorf("error creating configuring GitHub AmazonSNS service: %v", err)
		}
		topicARNs = append(topicARNs, snsTopicARN)
	}

	err = setPolicy(config.awsKey, config.awsSecret, config.awsRegion, i.queue.ARN, i.queue.URL, topicARNs)
	if err != nil {
		return fatalGrimErrorf("error setting policy for Grim queue: %v", err)
	}

	return nil
}

// BuildNextInGrimQueue creates or reuses an SQS queue as a source of work.
func (i *Instance) BuildNextInGrimQueue() error {
	if err := i.checkGrimQueue(); err != nil {
		return err
	}

	configRoot := getEffectiveConfigRoot(i.configRoot)

	globalConfig, err := getEffectiveGlobalConfig(configRoot)
	if err != nil {
		return grimErrorf("error while reading config: %v", err)
	}

	message, err := getNextMessage(globalConfig.awsKey, globalConfig.awsSecret, globalConfig.awsRegion, i.queue.URL)
	if err != nil {
		return grimErrorf("error retrieving message from Grim queue %q: %v", i.queue.URL, err)
	}

	if message != "" {
		hook, err := extractHookEvent(message)
		if err != nil {
			return grimErrorf("error extracting hook from message: %v", err)
		}

		if !(hook.eventName == "push" || hook.eventName == "pull_request" && (hook.action == "opened" || hook.action == "reopened" || hook.action == "synchronize")) {
			return nil
		}

		if hook.eventName == "pull_request" {
			sha, err := pollForMergeCommitSha(globalConfig.gitHubToken, hook.owner, hook.repo, hook.prNumber)
			if err != nil {
				return grimErrorf("error getting merge commit sha: %v", err)
			} else if sha == "" {
				return grimErrorf("error getting merge commit sha: field empty")
			}
			hook.ref = sha
		}

		localConfig, err := getEffectiveConfig(configRoot, hook.owner, hook.repo)
		if err != nil {
			return grimErrorf("error while reading config: %v", err)
		}

		return buildForHook(configRoot, localConfig, *hook)
	}

	return nil
}

// BuildRef builds a git ref immediately.
func (i *Instance) BuildRef(owner, repo, ref string) error {
	configRoot := getEffectiveConfigRoot(i.configRoot)

	config, err := getEffectiveConfig(configRoot, owner, repo)
	if err != nil {
		return fatalGrimErrorf("error while reading config: %v", err)
	}

	return buildForHook(configRoot, config, hookEvent{
		owner: owner,
		repo:  repo,
		ref:   ref,
	})
}

func buildForHook(configRoot string, config *effectiveConfig, hook hookEvent) error {
	extraEnv := hook.env()

	// TODO: do something with the err
	notifyPending(config, hook)

	result, err := build(configRoot, config.workspaceRoot, config.pathToCloneIn, hook.owner, hook.repo, extraEnv)
	if err != nil {
		notifyError(config, hook)
		return fatalGrimErrorf("error during %v: %v", describeHook(hook), err)
	}

	var notifyError error
	if result.ExitCode == 0 {
		notifyError = notifySuccess(config, hook)
	} else {
		notifyError = notifyFailure(config, hook)
	}

	err = appendResult(config.resultRoot, hook.owner, hook.repo, *result)
	if err != nil {
		return fatalGrimErrorf("error while storing result: %v", err)
	}

	return notifyError
}

func describeHook(hook hookEvent) string {
	return fmt.Sprintf("build of %v/%v initiated by a %q to %q by %q", hook.owner, hook.repo, hook.eventName, hook.target, hook.userName)
}

func notifyPending(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSPending, fmt.Sprintf("Starting %v", describeHook(hook)), ColorYellow)
}

func notifyError(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSError, fmt.Sprintf("Error during %v", describeHook(hook)), ColorGray)
}

func notifyFailure(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSFailure, fmt.Sprintf("Failure during %v", describeHook(hook)), ColorRed)
}

func notifySuccess(config *effectiveConfig, hook hookEvent) error {
	return notify(config, hook, RSSuccess, fmt.Sprintf("Success after %v", describeHook(hook)), ColorGreen)
}

func notify(config *effectiveConfig, hook hookEvent, state refStatus, message string, color messageColor) error {
	if hook.eventName != "push" && hook.eventName != "pull_request" {
		return nil
	}

	//add grimServerID/grimQueueName to hipchat message
	message += "ServerID:" + config.grimServerID

	ghErr := setRefStatus(config.gitHubToken, hook.owner, hook.repo, hook.statusRef, state, "", message)

	if config.hipChatToken != "" && config.hipChatRoom != "" {
		err := sendMessageToRoom(config.hipChatToken, config.hipChatRoom, "Grim", message, color)
		if err != nil {
			return err
		}
	}

	return ghErr
}

func (i *Instance) checkGrimQueue() error {
	if i.queue == nil {
		return fatalGrimErrorf("the Grim queue must be prepared first")
	}

	return nil
}
<|endoftext|>


<commit_before>package gwyf

import (
	"appengine"
	"appengine/urlfetch"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"text/template"
	"time"
)

const (
	wapUrl = "http://wap.ratp.fr/siv/schedule?service=next" +
		`&reseau={{.Mode}}&lineid={{.Line}}&directionsens={{.Direction}}&stationname={{.Station}}"`
	schedResultPattern = `> ([^<]+)</div>.*>(\w+)</a>.*<div class="schmsg."><b>([^<]+)</b>`
)

var (
	delegateUrlTemplate = template.Must(template.New("schedUrl").Parse(wapUrl))
	schedResultRegexp   = regexp.MustCompile(schedResultPattern)
)

type jsonResult struct {
	DelegateDuration float64 `json:"duration_s"`
	IncomingTrains   []train `json:"incoming_trains"`
}

type train struct {
	Destination string `json:"destination"`
	Mission     string `json:"mission"`
	Stop        string `json:"stop"`
}

func init() {
	http.HandleFunc("/", errorHandler(worker))
}

func errorHandler(f func(http.ResponseWriter, appengine.Context, *http.Request) error) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		context := appengine.NewContext(request)
		err := f(writer, context, request)
		if err != nil {
			http.Error(writer, err.Error(), http.StatusInternalServerError)
			context.Errorf(err.Error())
		}
	}
}

func worker(writer http.ResponseWriter, context appengine.Context, request *http.Request) error {
	delegateURL, err := buildDelegateURL(request)
	if err != nil {
		return fmt.Errorf("Failed to build delegate URL: %v", err)
	}
	startTime := time.Now()
	delegateResult, err := queryDelegateBackend(context, delegateURL)
	if err != nil {
		return fmt.Errorf("Failed to query RATP service: %v", err)
	}
	delegateDuration := time.Since(startTime).Seconds()
	trains, err := parseDelegateResult(delegateResult)
	if err != nil {
		return fmt.Errorf("Failed to parse output of RATP service: %v", err)
	}
	marshal := mkMarshalFunc(request.URL.Query()["pretty"] != nil)
	jsonTrains, err := marshal(jsonResult{IncomingTrains: trains, DelegateDuration: delegateDuration})
	if err != nil {
		return fmt.Errorf("Failed to encode result to JSON: %v", err)
	}
	writer.Header().Set("Content-Type", "application/json")
	fmt.Fprint(writer, string(jsonTrains))
	return nil
}

func buildDelegateURL(request *http.Request) (string, error) {
	schedUrl := new(bytes.Buffer)
	schedParameters, err := buildSchedQuery(request.URL.Query())
	if err != nil {
		return "", err
	}
	err = delegateUrlTemplate.Execute(schedUrl, schedParameters)
	if err != nil {
		return "", err
	}
	return schedUrl.String(), nil
}

func queryDelegateBackend(context appengine.Context, delegateURL string) (string, error) {
	resp, err := urlfetch.Client(context).Get(delegateURL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func parseDelegateResult(schedResult string) ([]train, error) {
	trains := make([]train, 0, 10)
	matches := schedResultRegexp.FindAllStringSubmatch(schedResult, -1)
	for _, v := range matches {
		t := train{Destination: v[1], Mission: v[2], Stop: v[3]}
		trains = append(trains, t)
	}
	return trains, nil
}

func mkMarshalFunc(pretty bool) func(interface{}) ([]byte, error) {
	if pretty {
		return func(input interface{}) ([]byte, error) {
			return json.MarshalIndent(input, "", "  ")
		}
	} else {
		return json.Marshal
	}
}

func buildSchedQuery(q map[string][]string) (schedQuery, error) {
	mandatoryParameters := [...]string{"line", "direction", "station"}
	for _, p := range mandatoryParameters {
		if q[p] == nil {
			return schedQuery{}, errors.New("Missing mandatory parameter '" + p + "'")
		}
	}
	return schedQuery{Mode: "rer", Line: q["line"][0], Direction: q["direction"][0], Station: q["station"][0]}, nil
}

type schedQuery struct {
	Mode, Line, Direction, Station string
}
<commit_msg>Added response header to allow cross-site requests (CORS)<commit_after>package gwyf

import (
	"appengine"
	"appengine/urlfetch"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"text/template"
	"time"
)

const (
	wapUrl = "http://wap.ratp.fr/siv/schedule?service=next" +
		`&reseau={{.Mode}}&lineid={{.Line}}&directionsens={{.Direction}}&stationname={{.Station}}"`
	schedResultPattern = `> ([^<]+)</div>.*>(\w+)</a>.*<div class="schmsg."><b>([^<]+)</b>`
)

var (
	delegateUrlTemplate = template.Must(template.New("schedUrl").Parse(wapUrl))
	schedResultRegexp   = regexp.MustCompile(schedResultPattern)
)

type jsonResult struct {
	DelegateDuration float64 `json:"duration_s"`
	IncomingTrains   []train `json:"incoming_trains"`
}

type train struct {
	Destination string `json:"destination"`
	Mission     string `json:"mission"`
	Stop        string `json:"stop"`
}

func init() {
	http.HandleFunc("/", errorHandler(worker))
}

func errorHandler(f func(http.ResponseWriter, appengine.Context, *http.Request) error) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		context := appengine.NewContext(request)
		err := f(writer, context, request)
		if err != nil {
			http.Error(writer, err.Error(), http.StatusInternalServerError)
			context.Errorf(err.Error())
		}
	}
}

func worker(writer http.ResponseWriter, context appengine.Context, request *http.Request) error {
	delegateURL, err := buildDelegateURL(request)
	if err != nil {
		return fmt.Errorf("Failed to build delegate URL: %v", err)
	}
	startTime := time.Now()
	delegateResult, err := queryDelegateBackend(context, delegateURL)
	if err != nil {
		return fmt.Errorf("Failed to query RATP service: %v", err)
	}
	delegateDuration := time.Since(startTime).Seconds()
	trains, err := parseDelegateResult(delegateResult)
	if err != nil {
		return fmt.Errorf("Failed to parse output of RATP service: %v", err)
	}
	marshal := mkMarshalFunc(request.URL.Query()["pretty"] != nil)
	jsonTrains, err := marshal(jsonResult{IncomingTrains: trains, DelegateDuration: delegateDuration})
	if err != nil {
		return fmt.Errorf("Failed to encode result to JSON: %v", err)
	}
	writer.Header().Set("Content-Type", "application/json")
	writer.Header().Set("Access-Control-Allow-Origin", "*")
	fmt.Fprint(writer, string(jsonTrains))
	return nil
}

func buildDelegateURL(request *http.Request) (string, error) {
	schedUrl := new(bytes.Buffer)
	schedParameters, err := buildSchedQuery(request.URL.Query())
	if err != nil {
		return "", err
	}
	err = delegateUrlTemplate.Execute(schedUrl, schedParameters)
	if err != nil {
		return "", err
	}
	return schedUrl.String(), nil
}

func queryDelegateBackend(context appengine.Context, delegateURL string) (string, error) {
	resp, err := urlfetch.Client(context).Get(delegateURL)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func parseDelegateResult(schedResult string) ([]train, error) {
	trains := make([]train, 0, 10)
	matches := schedResultRegexp.FindAllStringSubmatch(schedResult, -1)
	for _, v := range matches {
		t := train{Destination: v[1], Mission: v[2], Stop: v[3]}
		trains = append(trains, t)
	}
	return trains, nil
}

func mkMarshalFunc(pretty bool) func(interface{}) ([]byte, error) {
	if pretty {
		return func(input interface{}) ([]byte, error) {
			return json.MarshalIndent(input, "", "  ")
		}
	} else {
		return json.Marshal
	}
}

func buildSchedQuery(q map[string][]string) (schedQuery, error) {
	mandatoryParameters := [...]string{"line", "direction", "station"}
	for _, p := range mandatoryParameters {
		if q[p] == nil {
			return schedQuery{}, errors.New("Missing mandatory parameter '" + p + "'")
		}
	}
	return schedQuery{Mode: "rer", Line: q["line"][0], Direction: q["direction"][0], Station: q["station"][0]}, nil
}

type schedQuery struct {
	Mode, Line, Direction, Station string
}
<|endoftext|>


<commit_before>package disgo

import (
	"github.com/disintegration/imaging"
	"image"
)

type PHash uint64

func (p1 PHash) Distance(p2 PHash) (distance uint) {
	hamming := p1 ^ p2

	for hamming != 0 {
		distance += 1
		hamming &= hamming - 1
	}
	return
}

func intensity(img *image.NRGBA, row, column int) uint8 {
	offset := (row-img.Rect.Min.Y)*img.Stride + (column-img.Rect.Min.X)*4
	return uint8((uint16(img.Pix[offset]) + uint16(img.Pix[offset+1]) + uint16(img.Pix[offset+2])) / 3)
}

func HashFile(filename string) (PHash, error) {
	img, err := imaging.Open(filename)
	if err != nil {
		return 0, err
	}

	return Hash(img)
}

func Hash(img image.Image) (PHash, error) {
	rows := 8
	columns := 9
	var hash PHash

	grayscale := imaging.Grayscale(img)
	grayscale = imaging.Resize(grayscale, columns, rows, imaging.Box)

	for row := 0; row < rows; row++ {
		for column := 0; column < columns-1; column++ {
			avg1 := intensity(grayscale, row, column)
			avg2 := intensity(grayscale, row, column+1)
			hash = hash << 1
			if avg1 > avg2 {
				hash = hash | 0x01
			}
		}
	}
	return hash, nil
}
<commit_msg>golint suggested +=1 should be changed to ++<commit_after>package disgo

import (
	"github.com/disintegration/imaging"
	"image"
)

type PHash uint64

func (p1 PHash) Distance(p2 PHash) (distance uint) {
	hamming := p1 ^ p2

	for hamming != 0 {
		distance++
		hamming &= hamming - 1
	}
	return
}

func intensity(img *image.NRGBA, row, column int) uint8 {
	offset := (row-img.Rect.Min.Y)*img.Stride + (column-img.Rect.Min.X)*4
	return uint8((uint16(img.Pix[offset]) + uint16(img.Pix[offset+1]) + uint16(img.Pix[offset+2])) / 3)
}

func HashFile(filename string) (PHash, error) {
	img, err := imaging.Open(filename)
	if err != nil {
		return 0, err
	}

	return Hash(img)
}

func Hash(img image.Image) (PHash, error) {
	rows := 8
	columns := 9
	var hash PHash

	grayscale := imaging.Grayscale(img)
	grayscale = imaging.Resize(grayscale, columns, rows, imaging.Box)

	for row := 0; row < rows; row++ {
		for column := 0; column < columns-1; column++ {
			avg1 := intensity(grayscale, row, column)
			avg2 := intensity(grayscale, row, column+1)
			hash = hash << 1
			if avg1 > avg2 {
				hash = hash | 0x01
			}
		}
	}
	return hash, nil
}
<|endoftext|>


<commit_before>package agent

// the HTTP interface

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/pprof"
	"os"
	"runtime"
	runtime_pprof "runtime/pprof"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/bakins/logrus-middleware"
	"github.com/bakins/net-http-recover"
	"github.com/gorilla/context"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
)

type (
	// HTTPRequest is a container for an http request and its context
	HTTPRequest struct {
		ResponseWriter http.ResponseWriter
		Request        *http.Request
		Context        *Context
		vars           map[string]string
		Guest          *client.Guest
		GuestRunner    *GuestRunner
	}

	// HTTPResponse is a wrapper for http.ResponseWriter which provides access
	// to several convenience methods
	HTTPResponse struct {
		http.ResponseWriter
	}

	// HTTPErrorMessage is an enhanced error struct for http error responses
	HTTPErrorMessage struct {
		Message string   `json:"message"`
		Code    int      `json:"code"`
		Stack   []string `json:"stack"`
	}

	// Chain is a middleware chain
	Chain struct {
		alice.Chain
		ctx *Context
	}
)

const ctxKey string = "agentContext"

var (
	// ErrNotFound is the error for a resouce not found
	ErrNotFound = errors.New("not found")
)

// AttachProfiler enables debug profiling exposed on http api endpoints
func AttachProfiler(router *mux.Router) {
	router.HandleFunc("/debug/pprof/", pprof.Index)
	for _, profile := range runtime_pprof.Profiles() {
		router.Handle(fmt.Sprintf("/debug/pprof/%s", profile.Name()), pprof.Handler(profile.Name()))
	}
	router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
}

// Run prepares and runs the http server
func Run(ctx *Context, address string) error {
	r := mux.NewRouter()
	r.StrictSlash(true)

	AttachProfiler(r)

	logrusMiddleware := logrusmiddleware.Middleware{
		Name: "agent",
	}
	commonMiddleware := alice.New(
		func(h http.Handler) http.Handler {
			return logrusMiddleware.Handler(h, "")
		},
		handlers.CompressHandler,
		func(h http.Handler) http.Handler {
			return recovery.Handler(os.Stderr, h, true)
		},
		func(h http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				context.Set(r, ctxKey, ctx)
				h.ServeHTTP(w, r)
			})
		},
	)

	chain := Chain{
		ctx:   ctx,
		Chain: alice.New(),
	}

	// General
	r.HandleFunc("/metadata", chain.RequestWrapper(getMetadata)).Methods("GET")
	r.HandleFunc("/metadata", chain.RequestWrapper(setMetadata)).Methods("PATCH")

	r.HandleFunc("/images", chain.RequestWrapper(listImages)).Queries("type", "{type:[a-zA-Z]+}").Methods("GET")
	r.HandleFunc("/images", chain.RequestWrapper(listImages)).Methods("GET")
	r.HandleFunc("/images", chain.RequestWrapper(fetchImage)).Methods("POST")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(getImage)).Queries("type", "{type:[a-zA-Z]+}").Methods("GET")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(getImage)).Methods("GET")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(deleteImage)).Queries("type", "{type:[a-zA-Z]+}").Methods("DELETE")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(deleteImage)).Methods("DELETE")

	r.HandleFunc("/jobs", chain.RequestWrapper(getLatestJobs)).Methods("GET").Queries("limit", "{limit:[0-9]+}").Methods("GET")
	r.HandleFunc("/jobs", chain.RequestWrapper(getLatestJobs)).Methods("GET")
	r.HandleFunc("/jobs/{jobID}", chain.RequestWrapper(getJobStatus)).Methods("GET")

	r.HandleFunc("/guests", chain.RequestWrapper(listGuests)).Methods("GET")

	// Specific Guest
	r.HandleFunc("/guests", chain.RequestWrapper(createGuest)).Methods("POST") // Special setup
	r.HandleFunc("/guests/{id}", chain.GuestRunnerWrapper(getGuest)).Methods("GET")
	r.HandleFunc("/guests/{id}/jobs", chain.GuestRunnerWrapper(getLatestGuestJobs)).Queries("limit", "{limit:[0-9]+}").Methods("GET")
	r.HandleFunc("/guests/{id}/jobs", chain.GuestRunnerWrapper(getLatestGuestJobs)).Methods("GET")
	r.HandleFunc("/guests/{id}/jobs/{jobID}", chain.GuestRunnerWrapper(getJobStatus)).Methods("GET")
	//r.HandleFunc("/guests/{id}", chain.GuestRunnerWrapper(deleteGuest)).Methods("DELETE")
	r.HandleFunc("/guests/{id}/metadata", chain.GuestRunnerWrapper(getGuestMetadata)).Methods("GET")
	r.HandleFunc("/guests/{id}/metadata", chain.GuestRunnerWrapper(setGuestMetadata)).Methods("PATCH")

	r.HandleFunc("/guests/{id}/metrics/cpu", chain.GuestRunnerWrapper(getCPUMetrics)).Methods("GET")
	r.HandleFunc("/guests/{id}/metrics/disk", chain.GuestRunnerWrapper(getDiskMetrics)).Methods("GET")
	r.HandleFunc("/guests/{id}/metrics/nic", chain.GuestRunnerWrapper(getNicMetrics)).Methods("GET")

	for _, action := range []string{"shutdown", "reboot", "restart", "poweroff", "start", "suspend", "delete"} {
		r.HandleFunc(fmt.Sprintf("/guests/{id}/%s", action), chain.GuestActionWrapper(action)).Methods("POST")
	}

	for _, prefix := range []string{"/guests/{id}", "/guests/{id}/disks/{disk}"} {
		r.HandleFunc(fmt.Sprintf("%s/snapshots", prefix), chain.GuestRunnerWrapper(listSnapshots)).Methods("GET")
		r.HandleFunc(fmt.Sprintf("%s/snapshots", prefix), chain.GuestRunnerWrapper(createSnapshot)).Methods("POST")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}", prefix), chain.GuestRunnerWrapper(getSnapshot)).Methods("GET")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}", prefix), chain.GuestRunnerWrapper(deleteSnapshot)).Methods("DELETE")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}/rollback", prefix), chain.GuestRunnerWrapper(rollbackSnapshot)).Methods("POST")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}/download", prefix), chain.GuestRunnerWrapper(downloadSnapshot)).Methods("GET")
	}

	/*
		guest := guests.PathPrefix("/{id}").Subrouter()
		guest.HandleFunc("/vnc", RequestWrapper(ctx, vncGuest))
		guest.HandleFunc("/history", RequestWrapper(ctx, getGuestHistory)).Methods("GET")

	*/

	s := &http.Server{
		Addr:           address,
		Handler:        commonMiddleware.Then(r),
		MaxHeaderBytes: 1 << 20,
	}
	return s.ListenAndServe()
}

// RequestWrapper turns a basic http request into an HTTPRequest
func (c *Chain) RequestWrapper(fn func(*HTTPRequest) *HTTPErrorMessage) http.HandlerFunc {
	return c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req := HTTPRequest{
			Context:        c.ctx,
			ResponseWriter: w,
			Request:        r,
		}
		if err := fn(&req); err != nil {
			log.WithFields(log.Fields{
				"error": err,
				"func":  "agent.RequestWrapper",
				"stack": strings.Join(err.Stack, "\t\n\t"),
				"path":  r.URL.Path,
			}).Error(err)
			req.JSON(err.Code, err)
		}
	})).ServeHTTP
}

// Parameter retrieves request parameter
func (r *HTTPRequest) Parameter(key string) string {
	vars := r.vars

	if vars == nil {
		vars = mux.Vars(r.Request)
		r.vars = vars
	}

	if vars == nil {
		return ""
	}

	return vars[key]
}

// SetHeader sets an http response header
func (r *HTTPRequest) SetHeader(key, val string) {
	r.ResponseWriter.Header().Set(key, val)
}

// JSON sends an http response with a json body
func (r *HTTPRequest) JSON(code int, obj interface{}) *HTTPErrorMessage {
	r.SetHeader("Content-Type", "application/json")
	r.ResponseWriter.WriteHeader(code)
	encoder := json.NewEncoder(r.ResponseWriter)
	if err := encoder.Encode(obj); err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return nil
}

// NewError creates an HTTPErrorMessage
func (r *HTTPRequest) NewError(err error, code int) *HTTPErrorMessage {
	if code <= 0 {
		code = http.StatusInternalServerError
	}
	msg := HTTPErrorMessage{
		Message: err.Error(),
		Code:    code,
		Stack:   make([]string, 0, 4),
	}
	for i := 1; ; i++ { //
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		// Print this much at least.  If we can't find the source, it won't show.
		msg.Stack = append(msg.Stack, fmt.Sprintf("%s:%d (0x%x)", file, line, pc))
	}
	return &msg
}

func getMetadata(r *HTTPRequest) *HTTPErrorMessage {
	metadata := make(map[string]string)

	err := r.Context.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("hypervisor-metadata")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			metadata[string(k)] = string(v)
			return nil
		})
	})

	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	return r.JSON(http.StatusOK, metadata)
}

func setMetadata(r *HTTPRequest) *HTTPErrorMessage {
	var metadata map[string]string

	err := json.NewDecoder(r.Request.Body).Decode(&metadata)
	if err != nil {
		return r.NewError(err, http.StatusBadRequest)
	}

	err = r.Context.db.Transaction(func(tx *kvite.Tx) error {
		for key, value := range metadata {
			b, err := tx.Bucket("hypervisor-metadata")
			if err != nil {
				return err
			}
			if value == "" {
				if err := b.Delete(key); err != nil {
					return err
				}
			} else {
				if err := b.Put(key, []byte(value)); err != nil {
					return err
				}
			}
		}
		return nil
	})

	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	return getMetadata(r)
}

// JSON writes appropriate headers and JSON body to the http response
func (hr *HTTPResponse) JSON(code int, obj interface{}) {
	hr.Header().Set("Content-Type", "application/json")
	hr.WriteHeader(code)
	encoder := json.NewEncoder(hr)
	if err := encoder.Encode(obj); err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
	}
}

// JSONError prepares an HTTPError with a stack trace and writes it with
// HTTPResponse.JSON
func (hr *HTTPResponse) JSONError(code int, err error) {
	httpError := &HTTPErrorMessage{
		Message: err.Error(),
		Code:    code,
		Stack:   make([]string, 0, 4),
	}
	for i := 1; ; i++ { //
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		// Print this much at least.  If we can't find the source, it won't show.
		httpError.Stack = append(httpError.Stack, fmt.Sprintf("%s:%d (0x%x)", file, line, pc))
	}
	hr.JSON(code, httpError)
}

// JSONMsg is a convenience method to write a JSON response with just a message
// string
func (hr *HTTPResponse) JSONMsg(code int, msg string) {
	msgObj := map[string]string{
		"message": msg,
	}
	hr.JSON(code, msgObj)
}

// GetContext retrieves a Context value for a request
func GetContext(r *http.Request) *Context {
	if value := context.Get(r, ctxKey); value != nil {
		return value.(*Context)
	}
	return nil
}
<commit_msg>Update metadata to http.HandlerFunc signatures<commit_after>package agent

// the HTTP interface

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/pprof"
	"os"
	"runtime"
	runtime_pprof "runtime/pprof"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/bakins/logrus-middleware"
	"github.com/bakins/net-http-recover"
	"github.com/gorilla/context"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
)

type (
	// HTTPRequest is a container for an http request and its context
	HTTPRequest struct {
		ResponseWriter http.ResponseWriter
		Request        *http.Request
		Context        *Context
		vars           map[string]string
		Guest          *client.Guest
		GuestRunner    *GuestRunner
	}

	// HTTPResponse is a wrapper for http.ResponseWriter which provides access
	// to several convenience methods
	HTTPResponse struct {
		http.ResponseWriter
	}

	// HTTPErrorMessage is an enhanced error struct for http error responses
	HTTPErrorMessage struct {
		Message string   `json:"message"`
		Code    int      `json:"code"`
		Stack   []string `json:"stack"`
	}

	// Chain is a middleware chain
	Chain struct {
		alice.Chain
		ctx *Context
	}
)

const ctxKey string = "agentContext"

var (
	// ErrNotFound is the error for a resouce not found
	ErrNotFound = errors.New("not found")
)

// AttachProfiler enables debug profiling exposed on http api endpoints
func AttachProfiler(router *mux.Router) {
	router.HandleFunc("/debug/pprof/", pprof.Index)
	for _, profile := range runtime_pprof.Profiles() {
		router.Handle(fmt.Sprintf("/debug/pprof/%s", profile.Name()), pprof.Handler(profile.Name()))
	}
	router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
}

// Run prepares and runs the http server
func Run(ctx *Context, address string) error {
	r := mux.NewRouter()
	r.StrictSlash(true)

	AttachProfiler(r)

	logrusMiddleware := logrusmiddleware.Middleware{
		Name: "agent",
	}
	commonMiddleware := alice.New(
		func(h http.Handler) http.Handler {
			return logrusMiddleware.Handler(h, "")
		},
		handlers.CompressHandler,
		func(h http.Handler) http.Handler {
			return recovery.Handler(os.Stderr, h, true)
		},
		func(h http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				context.Set(r, ctxKey, ctx)
				h.ServeHTTP(w, r)
			})
		},
	)

	chain := Chain{
		ctx:   ctx,
		Chain: alice.New(),
	}

	// General
	r.HandleFunc("/metadata", getMetadata).Methods("GET")
	r.HandleFunc("/metadata", setMetadata).Methods("PATCH")

	r.HandleFunc("/images", chain.RequestWrapper(listImages)).Queries("type", "{type:[a-zA-Z]+}").Methods("GET")
	r.HandleFunc("/images", chain.RequestWrapper(listImages)).Methods("GET")
	r.HandleFunc("/images", chain.RequestWrapper(fetchImage)).Methods("POST")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(getImage)).Queries("type", "{type:[a-zA-Z]+}").Methods("GET")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(getImage)).Methods("GET")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(deleteImage)).Queries("type", "{type:[a-zA-Z]+}").Methods("DELETE")
	r.HandleFunc("/images/{id}", chain.RequestWrapper(deleteImage)).Methods("DELETE")

	r.HandleFunc("/jobs", chain.RequestWrapper(getLatestJobs)).Methods("GET").Queries("limit", "{limit:[0-9]+}").Methods("GET")
	r.HandleFunc("/jobs", chain.RequestWrapper(getLatestJobs)).Methods("GET")
	r.HandleFunc("/jobs/{jobID}", chain.RequestWrapper(getJobStatus)).Methods("GET")

	r.HandleFunc("/guests", chain.RequestWrapper(listGuests)).Methods("GET")

	// Specific Guest
	r.HandleFunc("/guests", chain.RequestWrapper(createGuest)).Methods("POST") // Special setup
	r.HandleFunc("/guests/{id}", chain.GuestRunnerWrapper(getGuest)).Methods("GET")
	r.HandleFunc("/guests/{id}/jobs", chain.GuestRunnerWrapper(getLatestGuestJobs)).Queries("limit", "{limit:[0-9]+}").Methods("GET")
	r.HandleFunc("/guests/{id}/jobs", chain.GuestRunnerWrapper(getLatestGuestJobs)).Methods("GET")
	r.HandleFunc("/guests/{id}/jobs/{jobID}", chain.GuestRunnerWrapper(getJobStatus)).Methods("GET")
	//r.HandleFunc("/guests/{id}", chain.GuestRunnerWrapper(deleteGuest)).Methods("DELETE")
	r.HandleFunc("/guests/{id}/metadata", chain.GuestRunnerWrapper(getGuestMetadata)).Methods("GET")
	r.HandleFunc("/guests/{id}/metadata", chain.GuestRunnerWrapper(setGuestMetadata)).Methods("PATCH")

	r.HandleFunc("/guests/{id}/metrics/cpu", chain.GuestRunnerWrapper(getCPUMetrics)).Methods("GET")
	r.HandleFunc("/guests/{id}/metrics/disk", chain.GuestRunnerWrapper(getDiskMetrics)).Methods("GET")
	r.HandleFunc("/guests/{id}/metrics/nic", chain.GuestRunnerWrapper(getNicMetrics)).Methods("GET")

	for _, action := range []string{"shutdown", "reboot", "restart", "poweroff", "start", "suspend", "delete"} {
		r.HandleFunc(fmt.Sprintf("/guests/{id}/%s", action), chain.GuestActionWrapper(action)).Methods("POST")
	}

	for _, prefix := range []string{"/guests/{id}", "/guests/{id}/disks/{disk}"} {
		r.HandleFunc(fmt.Sprintf("%s/snapshots", prefix), chain.GuestRunnerWrapper(listSnapshots)).Methods("GET")
		r.HandleFunc(fmt.Sprintf("%s/snapshots", prefix), chain.GuestRunnerWrapper(createSnapshot)).Methods("POST")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}", prefix), chain.GuestRunnerWrapper(getSnapshot)).Methods("GET")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}", prefix), chain.GuestRunnerWrapper(deleteSnapshot)).Methods("DELETE")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}/rollback", prefix), chain.GuestRunnerWrapper(rollbackSnapshot)).Methods("POST")
		r.HandleFunc(fmt.Sprintf("%s/snapshots/{name}/download", prefix), chain.GuestRunnerWrapper(downloadSnapshot)).Methods("GET")
	}

	/*
		guest := guests.PathPrefix("/{id}").Subrouter()
		guest.HandleFunc("/vnc", RequestWrapper(ctx, vncGuest))
		guest.HandleFunc("/history", RequestWrapper(ctx, getGuestHistory)).Methods("GET")

	*/

	s := &http.Server{
		Addr:           address,
		Handler:        commonMiddleware.Then(r),
		MaxHeaderBytes: 1 << 20,
	}
	return s.ListenAndServe()
}

// RequestWrapper turns a basic http request into an HTTPRequest
func (c *Chain) RequestWrapper(fn func(*HTTPRequest) *HTTPErrorMessage) http.HandlerFunc {
	return c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req := HTTPRequest{
			Context:        c.ctx,
			ResponseWriter: w,
			Request:        r,
		}
		if err := fn(&req); err != nil {
			log.WithFields(log.Fields{
				"error": err,
				"func":  "agent.RequestWrapper",
				"stack": strings.Join(err.Stack, "\t\n\t"),
				"path":  r.URL.Path,
			}).Error(err)
			req.JSON(err.Code, err)
		}
	})).ServeHTTP
}

// Parameter retrieves request parameter
func (r *HTTPRequest) Parameter(key string) string {
	vars := r.vars

	if vars == nil {
		vars = mux.Vars(r.Request)
		r.vars = vars
	}

	if vars == nil {
		return ""
	}

	return vars[key]
}

// SetHeader sets an http response header
func (r *HTTPRequest) SetHeader(key, val string) {
	r.ResponseWriter.Header().Set(key, val)
}

// JSON sends an http response with a json body
func (r *HTTPRequest) JSON(code int, obj interface{}) *HTTPErrorMessage {
	r.SetHeader("Content-Type", "application/json")
	r.ResponseWriter.WriteHeader(code)
	encoder := json.NewEncoder(r.ResponseWriter)
	if err := encoder.Encode(obj); err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return nil
}

// NewError creates an HTTPErrorMessage
func (r *HTTPRequest) NewError(err error, code int) *HTTPErrorMessage {
	if code <= 0 {
		code = http.StatusInternalServerError
	}
	msg := HTTPErrorMessage{
		Message: err.Error(),
		Code:    code,
		Stack:   make([]string, 0, 4),
	}
	for i := 1; ; i++ { //
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		// Print this much at least.  If we can't find the source, it won't show.
		msg.Stack = append(msg.Stack, fmt.Sprintf("%s:%d (0x%x)", file, line, pc))
	}
	return &msg
}

func getMetadata(w http.ResponseWriter, r *http.Request) {
	hr := HTTPResponse{w}
	ctx := GetContext(r)

	metadata := make(map[string]string)

	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("hypervisor-metadata")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			metadata[string(k)] = string(v)
			return nil
		})
	})

	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	hr.JSON(http.StatusOK, metadata)
}

func setMetadata(w http.ResponseWriter, r *http.Request) {
	hr := HTTPResponse{w}
	ctx := GetContext(r)

	var metadata map[string]string

	err := json.NewDecoder(r.Body).Decode(&metadata)
	if err != nil {
		hr.JSONError(http.StatusBadRequest, err)
		return
	}

	err = ctx.db.Transaction(func(tx *kvite.Tx) error {
		for key, value := range metadata {
			b, err := tx.Bucket("hypervisor-metadata")
			if err != nil {
				return err
			}
			if value == "" {
				if err := b.Delete(key); err != nil {
					return err
				}
			} else {
				if err := b.Put(key, []byte(value)); err != nil {
					return err
				}
			}
		}
		return nil
	})

	if err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
		return
	}

	getMetadata(w, r)
}

// JSON writes appropriate headers and JSON body to the http response
func (hr *HTTPResponse) JSON(code int, obj interface{}) {
	hr.Header().Set("Content-Type", "application/json")
	hr.WriteHeader(code)
	encoder := json.NewEncoder(hr)
	if err := encoder.Encode(obj); err != nil {
		hr.JSONError(http.StatusInternalServerError, err)
	}
}

// JSONError prepares an HTTPError with a stack trace and writes it with
// HTTPResponse.JSON
func (hr *HTTPResponse) JSONError(code int, err error) {
	httpError := &HTTPErrorMessage{
		Message: err.Error(),
		Code:    code,
		Stack:   make([]string, 0, 4),
	}
	for i := 1; ; i++ { //
		pc, file, line, ok := runtime.Caller(i)
		if !ok {
			break
		}
		// Print this much at least.  If we can't find the source, it won't show.
		httpError.Stack = append(httpError.Stack, fmt.Sprintf("%s:%d (0x%x)", file, line, pc))
	}
	hr.JSON(code, httpError)
}

// JSONMsg is a convenience method to write a JSON response with just a message
// string
func (hr *HTTPResponse) JSONMsg(code int, msg string) {
	msgObj := map[string]string{
		"message": msg,
	}
	hr.JSON(code, msgObj)
}

// GetContext retrieves a Context value for a request
func GetContext(r *http.Request) *Context {
	if value := context.Get(r, ctxKey); value != nil {
		return value.(*Context)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import "net/http"

// http://stackoverflow.com/questions/13263492/set-useragent-in-http-request
// https://godoc.org/net/http

func NewClient(url string, ip string) (*http.Request, error) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)

	if err != nil {
		return nil, err
	}

	// spoof useragent, as there are going to be sites/servers that are
	// setup to deny by a specific useragent string (or lack there of)
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36")

	return req, nil
}
<commit_msg>add host spoofing; return resp. rather than req.<commit_after>package main

import "net/http"

// Get wraps the standard net/http library, allowing us to spoof hostnames and IP addresses
func Get(url string, ip string) (*http.Response, error) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)

	if err != nil {
		return nil, err
	}

	if len(ip) > 0 {
		req.Host = ip
	}

	// spoof useragent, as there are going to be sites/servers that are
	// setup to deny by a specific useragent string (or lack there of)
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36")

	resp, err := client.Do(req)

	return resp, err
}
<|endoftext|>