<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"strconv"
	"unsafe"

	log "github.com/cihub/seelog"
	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/protocol"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection *basenode.Connection

func main() {
	// Load logger
	//logger, err := log.LoggerFromConfigAsFile("../logconfig.xml")
	//if err != nil {
	//panic(err)
	//}
	//log.ReplaceLogger(logger)

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)

	// Load flags
	//var host string
	//var port string
	//flag.StringVar(&host, "host", "localhost", "Stampzilla server hostname")
	//flag.StringVar(&port, "port", "8282", "Stampzilla server port")
	flag.Parse()

	log.Info("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.SetState(state)

	// Describe available actions
	node.AddAction("set", "Set", []string{"Devices.Id"})
	node.AddAction("toggle", "Toggle", []string{"Devices.Id"})
	node.AddAction("dim", "Dim", []string{"Devices.Id", "value"})

	// Describe available layouts
	//node.AddLayout("1", "switch", "toggle", "Devices", []string{"on"}, "Switches")
	//node.AddLayout("2", "slider", "dim", "Devices", []string{"dim"}, "Dimmers")
	//node.AddLayout("3", "slider", "dim", "Devices", []string{"dim"}, "Specials")

	// Add devices
	cnt := C.updateDevices()
	log.Info("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection *basenode.Connection) {
	for s := range connection.State {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send <- node.Node()
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection *basenode.Connection) {
	for d := range connection.Receive {
		if err := processCommand(d); err != nil {
			log.Error(err)
		}
	}
}

func processCommand(cmd protocol.Command) error {
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on":
		result = C.tdTurnOn(id)
	case "off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Info("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		return errors.New(C.GoString(errorString))
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Info(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	log.Debugf("SensorEVENT %s,\t%s,\t%d -> ", C.GoString(protocol), C.GoString(model), sensorId)

	if dataType == C.TELLSTICK_TEMPERATURE {
		log.Debugf("Temperature:\t%s\n", C.GoString(value))
	} else if dataType == C.TELLSTICK_HUMIDITY {
		log.Debugf("Humidity:\t%s%%\n", C.GoString(value))
	}
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	log.Debugf("DeviceEVENT %d\t%d\t:%s\n", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send <- node.Node()
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send <- node.Node()
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Error(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send <- node.Node()
	}
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	log.Debugf("DeviceChangeEVENT %d\t%d\t%d\n", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	log.Debugf("rawDeviceEVENT (%d):%s\n", controllerId, C.GoString(data))
}
<commit_msg>try to fix threadleak<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"strconv"
	"unsafe"

	log "github.com/cihub/seelog"
	"github.com/stampzilla/stampzilla-go/nodes/basenode"
	"github.com/stampzilla/stampzilla-go/protocol"
)

/*
#cgo LDFLAGS: -ltelldus-core

#include <telldus-core.h>

extern void registerCallbacks();
extern void unregisterCallbacks();
extern int updateDevices();

*/
import "C"

var node *protocol.Node
var state *State = &State{make(map[string]*Device), make(map[string]*Sensor, 0)}
var serverConnection *basenode.Connection

func main() {
	// Load logger
	//logger, err := log.LoggerFromConfigAsFile("../logconfig.xml")
	//if err != nil {
	//panic(err)
	//}
	//log.ReplaceLogger(logger)

	//Get a config with the correct parameters
	config := basenode.NewConfig()
	basenode.SetConfig(config)

	// Load flags
	//var host string
	//var port string
	//flag.StringVar(&host, "host", "localhost", "Stampzilla server hostname")
	//flag.StringVar(&port, "port", "8282", "Stampzilla server port")
	flag.Parse()

	log.Info("Starting TELLDUS-events node")

	C.registerCallbacks()
	defer C.unregisterCallbacks()

	// Create new node description
	node = protocol.NewNode("telldus-events")
	node.SetState(state)

	// Describe available actions
	node.AddAction("set", "Set", []string{"Devices.Id"})
	node.AddAction("toggle", "Toggle", []string{"Devices.Id"})
	node.AddAction("dim", "Dim", []string{"Devices.Id", "value"})

	// Describe available layouts
	//node.AddLayout("1", "switch", "toggle", "Devices", []string{"on"}, "Switches")
	//node.AddLayout("2", "slider", "dim", "Devices", []string{"dim"}, "Dimmers")
	//node.AddLayout("3", "slider", "dim", "Devices", []string{"dim"}, "Specials")

	// Add devices
	cnt := C.updateDevices()
	log.Info("Updated devices (", cnt, " in total)")

	for _, dev := range state.Devices {
		node.AddElement(&protocol.Element{
			Type: protocol.ElementTypeToggle,
			Name: dev.Name,
			Command: &protocol.Command{
				Cmd:  "toggle",
				Args: []string{dev.Id},
			},
			Feedback: `Devices[` + dev.Id + `].State.On`,
		})
	}

	// Start the connection
	//go connection(host, port, node)

	serverConnection = basenode.Connect()
	go monitorState(serverConnection)

	// This worker recives all incomming commands
	go serverRecv(serverConnection)

	select {}
}

// WORKER that monitors the current connection state
func monitorState(connection *basenode.Connection) {
	for s := range connection.State {
		switch s {
		case basenode.ConnectionStateConnected:
			connection.Send <- node.Node()
		case basenode.ConnectionStateDisconnected:
		}
	}
}

// WORKER that recives all incomming commands
func serverRecv(connection *basenode.Connection) {
	for d := range connection.Receive {
		if err := processCommand(d); err != nil {
			log.Error(err)
		}
	}
}

func processCommand(cmd protocol.Command) error {
	var result C.int = C.TELLSTICK_ERROR_UNKNOWN
	var id C.int = 0

	i, err := strconv.Atoi(cmd.Args[0])
	if err != nil {
		return fmt.Errorf("Failed to decode arg[0] to int %s %s", err, cmd.Args[0])
	}

	id = C.int(i)

	switch cmd.Cmd {
	case "on":
		result = C.tdTurnOn(id)
	case "off":
		result = C.tdTurnOff(id)
	case "toggle":
		s := C.tdLastSentCommand(id, C.TELLSTICK_TURNON|C.TELLSTICK_TURNOFF|C.TELLSTICK_DIM)
		switch {
		case s&C.TELLSTICK_DIM != 0:
			var state *C.char = C.tdLastSentValue(id)
			log.Info("DIM: ", C.GoString(state))
			if C.GoString(state) == "0" {
				result = C.tdTurnOn(id)
			} else {
				result = C.tdTurnOff(id)
			}
			C.tdReleaseString(state)
		case s&C.TELLSTICK_TURNON != 0:
			result = C.tdTurnOff(id)
		case s&C.TELLSTICK_TURNOFF != 0:
			result = C.tdTurnOn(id)
		}
	}

	if result != C.TELLSTICK_SUCCESS {
		var errorString *C.char = C.tdGetErrorString(result)
		C.tdReleaseString(errorString)
		return errors.New(C.GoString(errorString))
	}

	return nil
}

//export newDevice
func newDevice(id int, name *C.char, methods, s int, value *C.char) {
	//log.Info(id, C.GoString(name))

	features := []string{}
	if methods&C.TELLSTICK_TURNON != 0 {
		features = append(features, "on")
	}
	if methods&C.TELLSTICK_TURNOFF != 0 {
		features = append(features, "off")
	}
	if methods&C.TELLSTICK_BELL != 0 {
		features = append(features, "bell")
	}
	if methods&C.TELLSTICK_TOGGLE != 0 {
		features = append(features, "toggle")
	}
	if methods&C.TELLSTICK_DIM != 0 {
		features = append(features, "dim")
	}
	if methods&C.TELLSTICK_EXECUTE != 0 {
		features = append(features, "execute")
	}
	if methods&C.TELLSTICK_UP != 0 {
		features = append(features, "up")
	}
	if methods&C.TELLSTICK_DOWN != 0 {
		features = append(features, "down")
	}
	if methods&C.TELLSTICK_STOP != 0 {
		features = append(features, "stop")
	}

	if s&C.TELLSTICK_TURNON != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: true, Dim: 100})
	}
	if s&C.TELLSTICK_TURNOFF != 0 {
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: false})
	}
	if s&C.TELLSTICK_DIM != 0 {
		var currentState = C.GoString(value)
		level, _ := strconv.ParseUint(currentState, 10, 16)
		state.AddDevice(strconv.Itoa(id), C.GoString(name), features, DeviceState{On: level > 0, Dim: int(level)})
	}

}

//export sensorEvent
func sensorEvent(protocol, model *C.char, sensorId, dataType int, value *C.char) {
	log.Debugf("SensorEVENT %s,\t%s,\t%d -> ", C.GoString(protocol), C.GoString(model), sensorId)

	if dataType == C.TELLSTICK_TEMPERATURE {
		log.Debugf("Temperature:\t%s\n", C.GoString(value))
	} else if dataType == C.TELLSTICK_HUMIDITY {
		log.Debugf("Humidity:\t%s%%\n", C.GoString(value))
	}
	C.tdReleaseString(model)
}

//export deviceEvent
func deviceEvent(deviceId, method int, data *C.char, callbackId int, context unsafe.Pointer) {
	log.Debugf("DeviceEVENT %d\t%d\t:%s\n", deviceId, method, C.GoString(data))
	device := state.GetDevice(strconv.Itoa(deviceId))
	if method&C.TELLSTICK_TURNON != 0 {
		device.State.On = true
		serverConnection.Send <- node.Node()
	}
	if method&C.TELLSTICK_TURNOFF != 0 {
		device.State.On = false
		serverConnection.Send <- node.Node()
	}
	if method&C.TELLSTICK_DIM != 0 {
		level, err := strconv.ParseUint(C.GoString(data), 10, 16)
		if err != nil {
			log.Error(err)
			return
		}
		if level == 0 {
			device.State.On = false
		}
		if level > 0 {
			device.State.On = true
		}
		device.State.Dim = int(level)
		serverConnection.Send <- node.Node()
	}
	C.tdReleaseString(data)
}

//export deviceChangeEvent
func deviceChangeEvent(deviceId, changeEvent, changeType, callbackId int, context unsafe.Pointer) {
	log.Debugf("DeviceChangeEVENT %d\t%d\t%d\n", deviceId, changeEvent, changeType)
}

//export rawDeviceEvent
func rawDeviceEvent(data *C.char, controllerId, callbackId int, context unsafe.Pointer) {
	log.Debugf("rawDeviceEVENT (%d):%s\n", controllerId, C.GoString(data))
	C.tdReleaseString(data)
}
<|endoftext|>


<commit_before>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package broker

import (
	"sync"

	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/readwriter"
	dbutil "github.com/TheThingsNetwork/ttn/utils/storage"
	"github.com/brocaar/lorawan"
)

// NetworkController gives a facade for manipulating the broker databases and devices
type NetworkController interface {
	UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error
	LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error)
	LookupApplication(appEUI lorawan.EUI64) (appEntry, error)
	StoreDevice(reg core.BRegistration) error
	StoreApplication(reg core.ARegistration) error
	Close() error
}

type devEntry struct {
	Recipient []byte
	AppEUI    lorawan.EUI64
	DevEUI    lorawan.EUI64
	NwkSKey   lorawan.AES128Key
	FCntUp    uint32
	FCntDown  uint32
}

type appEntry struct {
	Recipient []byte
	AppEUI    lorawan.EUI64
}

type controller struct {
	sync.RWMutex
	db           dbutil.Interface
	Devices      string
	Applications string
}

// NewNetworkController constructs a new broker controller
func NewNetworkController(name string) (NetworkController, error) {
	itf, err := dbutil.New(name)
	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}

	return controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil
}

// LookupDevices implements the broker.NetworkController interface
func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) {
	s.RLock()
	defer s.RUnlock()
	entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{})
	if err != nil {
		return nil, err
	}
	return entries.([]devEntry), nil
}

// LookupApplication implements the broker.NetworkController interface
func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) {
	s.RLock()
	defer s.RUnlock()
	itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{})
	if err != nil {
		return appEntry{}, err
	}

	entries := itf.([]appEntry)
	if len(entries) != 1 {
		// NOTE Shall we reset the entry ?
		return appEntry{}, errors.New(errors.Structural, "Invalid application entries")
	}

	return entries[0], nil
}

// UpdateFCnt implements the broker.NetworkController interface
func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error {
	s.Lock()
	defer s.Unlock()
	itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{})
	if err != nil {
		return err
	}
	entries := itf.([]devEntry)

	var newEntries []dbutil.Entry
	for _, e := range entries {
		if e.AppEUI == appEUI {
			switch dir {
			case "up":
				e.FCntUp = fcnt
			case "down":
				e.FCntDown = fcnt
			default:
				return errors.New(errors.Implementation, "Unreckognized direction")
			}
		}
		newEntries = append(newEntries, &e)
	}

	return s.db.Replace(s.Devices, devEUI[:], newEntries)
}

// StoreDevice implements the broker.NetworkController interface
func (s controller) StoreDevice(reg core.BRegistration) error {
	s.Lock()
	defer s.Unlock()
	data, err := reg.Recipient().MarshalBinary()
	if err != nil {
		return errors.New(errors.Structural, err)
	}

	devEUI := reg.DevEUI()
	return s.db.Store(s.Devices, devEUI[:], []dbutil.Entry{
		&devEntry{
			Recipient: data,
			AppEUI:    reg.AppEUI(),
			DevEUI:    devEUI,
			NwkSKey:   reg.NwkSKey(),
		},
	})
}

// StoreApplication implements the broker.NetworkController interface
func (s controller) StoreApplication(reg core.ARegistration) error {
	s.Lock()
	defer s.Unlock()
	data, err := reg.Recipient().MarshalBinary()
	if err != nil {
		return errors.New(errors.Structural, err)
	}

	appEUI := reg.AppEUI()
	return s.db.Replace(s.Applications, appEUI[:], []dbutil.Entry{
		&appEntry{
			Recipient: data,
			AppEUI:    appEUI,
		},
	})
}

// Close implements the broker.NetworkController interface
func (s controller) Close() error {
	return s.db.Close()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (e devEntry) MarshalBinary() ([]byte, error) {
	rw := readwriter.New(nil)
	rw.Write(e.Recipient)
	rw.Write(e.AppEUI)
	rw.Write(e.DevEUI)
	rw.Write(e.NwkSKey)
	return rw.Bytes()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (e *devEntry) UnmarshalBinary(data []byte) error {
	rw := readwriter.New(data)
	rw.Read(func(data []byte) { e.Recipient = data })
	rw.Read(func(data []byte) { copy(e.AppEUI[:], data) })
	rw.Read(func(data []byte) { copy(e.DevEUI[:], data) })
	rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) })
	return rw.Err()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (e appEntry) MarshalBinary() ([]byte, error) {
	rw := readwriter.New(nil)
	rw.Write(e.Recipient)
	rw.Write(e.AppEUI)
	return rw.Bytes()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (e *appEntry) UnmarshalBinary(data []byte) error {
	rw := readwriter.New(data)
	rw.Read(func(data []byte) { e.Recipient = data })
	rw.Read(func(data []byte) { copy(e.AppEUI[:], data) })
	return rw.Err()
}
<commit_msg>[network-controller] Fix issues according to test results<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package broker

import (
	"bytes"
	"encoding/binary"
	"sync"

	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/readwriter"
	dbutil "github.com/TheThingsNetwork/ttn/utils/storage"
	"github.com/brocaar/lorawan"
)

// NetworkController gives a facade for manipulating the broker databases and devices
type NetworkController interface {
	UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error
	LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error)
	LookupApplication(appEUI lorawan.EUI64) (appEntry, error)
	StoreDevice(reg core.BRegistration) error
	StoreApplication(reg core.ARegistration) error
	Close() error
}

type devEntry struct {
	Recipient []byte
	AppEUI    lorawan.EUI64
	DevEUI    lorawan.EUI64
	NwkSKey   lorawan.AES128Key
	FCntUp    uint32
	FCntDown  uint32
}

type appEntry struct {
	Recipient []byte
	AppEUI    lorawan.EUI64
}

type controller struct {
	sync.RWMutex
	db           dbutil.Interface
	Devices      string
	Applications string
}

// NewNetworkController constructs a new broker controller
func NewNetworkController(name string) (NetworkController, error) {
	itf, err := dbutil.New(name)
	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}

	return controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil
}

// LookupDevices implements the broker.NetworkController interface
func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) {
	s.RLock()
	defer s.RUnlock()
	entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{})
	if err != nil {
		return nil, err
	}
	return entries.([]devEntry), nil
}

// LookupApplication implements the broker.NetworkController interface
func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) {
	s.RLock()
	defer s.RUnlock()
	itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{})
	if err != nil {
		return appEntry{}, err
	}

	entries := itf.([]appEntry)
	if len(entries) != 1 {
		// NOTE Shall we reset the entry ?
		return appEntry{}, errors.New(errors.Structural, "Invalid application entries")
	}

	return entries[0], nil
}

// UpdateFCnt implements the broker.NetworkController interface
func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error {
	s.Lock()
	defer s.Unlock()
	itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{})
	if err != nil {
		return err
	}
	entries := itf.([]devEntry)

	var newEntries []dbutil.Entry
	for _, e := range entries {
		entry := new(devEntry)
		*entry = e
		if entry.AppEUI == appEUI {
			switch dir {
			case "up":
				entry.FCntUp = fcnt
			case "down":
				entry.FCntDown = fcnt
			default:
				return errors.New(errors.Implementation, "Unreckognized direction")
			}
		}
		newEntries = append(newEntries, entry)
	}

	return s.db.Replace(s.Devices, devEUI[:], newEntries)
}

// StoreDevice implements the broker.NetworkController interface
func (s controller) StoreDevice(reg core.BRegistration) error {
	s.Lock()
	defer s.Unlock()
	data, err := reg.Recipient().MarshalBinary()
	if err != nil {
		return errors.New(errors.Structural, err)
	}

	devEUI := reg.DevEUI()
	return s.db.Store(s.Devices, devEUI[:], []dbutil.Entry{
		&devEntry{
			Recipient: data,
			AppEUI:    reg.AppEUI(),
			DevEUI:    devEUI,
			NwkSKey:   reg.NwkSKey(),
		},
	})
}

// StoreApplication implements the broker.NetworkController interface
func (s controller) StoreApplication(reg core.ARegistration) error {
	s.Lock()
	defer s.Unlock()
	data, err := reg.Recipient().MarshalBinary()
	if err != nil {
		return errors.New(errors.Structural, err)
	}

	appEUI := reg.AppEUI()
	return s.db.Replace(s.Applications, appEUI[:], []dbutil.Entry{
		&appEntry{
			Recipient: data,
			AppEUI:    appEUI,
		},
	})
}

// Close implements the broker.NetworkController interface
func (s controller) Close() error {
	return s.db.Close()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (e devEntry) MarshalBinary() ([]byte, error) {
	rw := readwriter.New(nil)
	rw.Write(e.Recipient)
	rw.Write(e.AppEUI)
	rw.Write(e.DevEUI)
	rw.Write(e.NwkSKey)
	rw.Write(e.FCntUp)
	rw.Write(e.FCntDown)
	return rw.Bytes()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (e *devEntry) UnmarshalBinary(data []byte) error {
	rw := readwriter.New(data)
	rw.Read(func(data []byte) { e.Recipient = data })
	rw.Read(func(data []byte) { copy(e.AppEUI[:], data) })
	rw.Read(func(data []byte) { copy(e.DevEUI[:], data) })
	rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) })
	rw.TryRead(func(data []byte) error {
		buf := new(bytes.Buffer)
		buf.Write(data)
		fcnt := new(uint32)
		if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil {
			return err
		}
		e.FCntUp = *fcnt
		return nil
	})
	rw.TryRead(func(data []byte) error {
		buf := new(bytes.Buffer)
		buf.Write(data)
		fcnt := new(uint32)
		if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil {
			return err
		}
		e.FCntDown = *fcnt
		return nil
	})
	return rw.Err()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface
func (e appEntry) MarshalBinary() ([]byte, error) {
	rw := readwriter.New(nil)
	rw.Write(e.Recipient)
	rw.Write(e.AppEUI)
	return rw.Bytes()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (e *appEntry) UnmarshalBinary(data []byte) error {
	rw := readwriter.New(data)
	rw.Read(func(data []byte) { e.Recipient = data })
	rw.Read(func(data []byte) { copy(e.AppEUI[:], data) })
	return rw.Err()
}
<|endoftext|>


<commit_before>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prpc

import (
	"fmt"
	"net/http"
	"sort"
	"strings"
	"sync"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/retry/transient"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/server/router"
)

var (
	// Describe the permitted Access Control requests.
	allowHeaders = strings.Join([]string{"Origin", "Content-Type", "Accept", "Authorization"}, ", ")
	allowMethods = strings.Join([]string{"OPTIONS", "POST"}, ", ")

	// allowPreflightCacheAgeSecs is the amount of time to enable the browser to
	// cache the preflight access control response, in seconds.
	//
	// 600 seconds is 10 minutes.
	allowPreflightCacheAgeSecs = "600"

	// exposeHeaders lists the whitelisted non-standard response headers that the
	// client may accept.
	exposeHeaders = strings.Join([]string{HeaderGRPCCode}, ", ")

	// NoAuthentication can be used in place of an Authenticator to explicitly
	// specify that your Server will skip authentication.
	//
	// Use it with Server.Authenticator or RegisterDefaultAuth.
	NoAuthentication Authenticator = nullAuthenticator{}
)

// Server is a pRPC server to serve RPC requests.
// Zero value is valid.
type Server struct {
	// Authenticator, if not nil, specifies how to authenticate requests.
	//
	// If nil, the default authenticator set by RegisterDefaultAuth will be used.
	// If the default authenticator is also nil, all request handlers will panic.
	//
	// If you want to disable the authentication (e.g for tests), explicitly set
	// Authenticator to NoAuthentication.
	Authenticator Authenticator

	// AccessControl, if not nil, is a callback that is invoked per request to
	// determine if permissive access control headers should be added to the
	// response.
	//
	// This callback includes the request Context and the origin header supplied
	// by the client. If nil, or if it returns false, no headers will be written.
	// Otherwise, access control headers for the specified origin will be
	// included in the response.
	AccessControl func(c context.Context, origin string) bool

	// UnaryServerInterceptor provides a hook to intercept the execution of
	// a unary RPC on the server. It is the responsibility of the interceptor to
	// invoke handler to complete the RPC.
	UnaryServerInterceptor grpc.UnaryServerInterceptor

	mu       sync.Mutex
	services map[string]*service
}

type service struct {
	methods map[string]grpc.MethodDesc
	impl    interface{}
}

// RegisterService registers a service implementation.
// Called from the generated code.
//
// desc must contain description of the service, its message types
// and all transitive dependencies.
//
// Panics if a service of the same name is already registered.
func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
	serv := &service{
		impl:    impl,
		methods: make(map[string]grpc.MethodDesc, len(desc.Methods)),
	}
	for _, m := range desc.Methods {
		serv.methods[m.MethodName] = m
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	if s.services == nil {
		s.services = map[string]*service{}
	} else if _, ok := s.services[desc.ServiceName]; ok {
		panic(fmt.Errorf("service %q is already registered", desc.ServiceName))
	}

	s.services[desc.ServiceName] = serv
}

// authenticate forces authentication set by RegisterDefaultAuth.
func (s *Server) authenticate() router.Middleware {
	a := s.Authenticator
	if a == nil {
		a = GetDefaultAuth()
		if a == nil {
			panic("prpc: no custom Authenticator was provided and default authenticator was not registered.\n" +
				"Either explicitly set `Server.Authenticator = NoAuthentication`, or use RegisterDefaultAuth()")
		}
	}

	return func(c *router.Context, next router.Handler) {
		switch ctx, err := a.Authenticate(c.Context, c.Request); {
		case transient.Tag.In(err):
			writeError(c.Context, c.Writer, withCode(err, codes.Internal))
		case err != nil:
			writeError(c.Context, c.Writer, withCode(err, codes.Unauthenticated))
		default:
			c.Context = ctx
			next(c)
		}
	}
}

// InstallHandlers installs HTTP handlers at /prpc/:service/:method.
//
// See https://godoc.org/go.chromium.org/luci/grpc/prpc#hdr-Protocol
// for pRPC protocol.
//
// The authenticator in 'base' is always replaced by pRPC specific one. For more
// details about the authentication see Server.Authenticator doc.
func (s *Server) InstallHandlers(r *router.Router, base router.MiddlewareChain) {
	s.mu.Lock()
	defer s.mu.Unlock()

	rr := r.Subrouter("/prpc/:service/:method")
	rr.Use(base.Extend(s.authenticate()))

	rr.POST("", router.MiddlewareChain{}, s.handlePOST)
	rr.OPTIONS("", router.MiddlewareChain{}, s.handleOPTIONS)
}

// handle handles RPCs.
// See https://godoc.org/go.chromium.org/luci/grpc/prpc#hdr-Protocol
// for pRPC protocol.
func (s *Server) handlePOST(c *router.Context) {
	serviceName := c.Params.ByName("service")
	methodName := c.Params.ByName("method")
	s.setAccessControlHeaders(c, false)
	out, format, err := s.call(c, serviceName, methodName)
	if err != nil {
		writeError(c.Context, c.Writer, err)
		return
	}
	writeMessage(c.Context, c.Writer, out, format)
}

func (s *Server) handleOPTIONS(c *router.Context) {
	s.setAccessControlHeaders(c, true)
	c.Writer.WriteHeader(http.StatusOK)
}

func (s *Server) call(c *router.Context, serviceName, methodName string) (proto.Message, Format, error) {
	service := s.services[serviceName]
	if service == nil {
		return nil, 0, status.Errorf(
			codes.Unimplemented,
			"service %q is not implemented",
			serviceName)
	}

	method, ok := service.methods[methodName]
	if !ok {
		return nil, 0, status.Errorf(
			codes.Unimplemented,
			"method %q in service %q is not implemented",
			methodName, serviceName)
	}

	format, perr := responseFormat(c.Request.Header.Get(headerAccept))
	if perr != nil {
		return nil, 0, perr
	}

	methodCtx, err := parseHeader(c.Context, c.Request.Header)
	if err != nil {
		return nil, 0, withStatus(err, http.StatusBadRequest)
	}

	out, err := method.Handler(service.impl, methodCtx, func(in interface{}) error {
		if in == nil {
			return grpcutil.Errf(codes.Internal, "input message is nil")
		}
		// Do not collapse it to one line. There is implicit err type conversion.
		if perr := readMessage(c.Request, in.(proto.Message)); perr != nil {
			return perr
		}
		return nil
	}, s.UnaryServerInterceptor)
	if err != nil {
		return nil, 0, err
	}

	if out == nil {
		return nil, 0, status.Error(codes.Internal, "service returned nil message")
	}
	return out.(proto.Message), format, nil
}

func (s *Server) setAccessControlHeaders(c *router.Context, preflight bool) {
	// Don't write out access control headers if the origin is unspecified.
	const originHeader = "Origin"
	origin := c.Request.Header.Get(originHeader)
	if origin == "" || s.AccessControl == nil || !s.AccessControl(c.Context, origin) {
		return
	}

	h := c.Writer.Header()
	h.Add("Access-Control-Allow-Origin", origin)
	h.Add("Vary", originHeader)
	h.Add("Access-Control-Allow-Credentials", "true")

	if preflight {
		h.Add("Access-Control-Allow-Headers", allowHeaders)
		h.Add("Access-Control-Allow-Methods", allowMethods)
		h.Add("Access-Control-Max-Age", allowPreflightCacheAgeSecs)
	} else {
		h.Add("Access-Control-Expose-Headers", exposeHeaders)
	}
}

// ServiceNames returns a sorted list of full names of all registered services.
func (s *Server) ServiceNames() []string {
	s.mu.Lock()
	defer s.mu.Unlock()

	names := make([]string, 0, len(s.services))
	for name := range s.services {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}
<commit_msg>[prpc] add response struct<commit_after>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prpc

import (
	"fmt"
	"net/http"
	"sort"
	"strings"
	"sync"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/retry/transient"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/server/router"
)

var (
	// Describe the permitted Access Control requests.
	allowHeaders = strings.Join([]string{"Origin", "Content-Type", "Accept", "Authorization"}, ", ")
	allowMethods = strings.Join([]string{"OPTIONS", "POST"}, ", ")

	// allowPreflightCacheAgeSecs is the amount of time to enable the browser to
	// cache the preflight access control response, in seconds.
	//
	// 600 seconds is 10 minutes.
	allowPreflightCacheAgeSecs = "600"

	// exposeHeaders lists the whitelisted non-standard response headers that the
	// client may accept.
	exposeHeaders = strings.Join([]string{HeaderGRPCCode}, ", ")

	// NoAuthentication can be used in place of an Authenticator to explicitly
	// specify that your Server will skip authentication.
	//
	// Use it with Server.Authenticator or RegisterDefaultAuth.
	NoAuthentication Authenticator = nullAuthenticator{}
)

// Server is a pRPC server to serve RPC requests.
// Zero value is valid.
type Server struct {
	// Authenticator, if not nil, specifies how to authenticate requests.
	//
	// If nil, the default authenticator set by RegisterDefaultAuth will be used.
	// If the default authenticator is also nil, all request handlers will panic.
	//
	// If you want to disable the authentication (e.g for tests), explicitly set
	// Authenticator to NoAuthentication.
	Authenticator Authenticator

	// AccessControl, if not nil, is a callback that is invoked per request to
	// determine if permissive access control headers should be added to the
	// response.
	//
	// This callback includes the request Context and the origin header supplied
	// by the client. If nil, or if it returns false, no headers will be written.
	// Otherwise, access control headers for the specified origin will be
	// included in the response.
	AccessControl func(c context.Context, origin string) bool

	// UnaryServerInterceptor provides a hook to intercept the execution of
	// a unary RPC on the server. It is the responsibility of the interceptor to
	// invoke handler to complete the RPC.
	UnaryServerInterceptor grpc.UnaryServerInterceptor

	mu       sync.Mutex
	services map[string]*service
}

type service struct {
	methods map[string]grpc.MethodDesc
	impl    interface{}
}

// RegisterService registers a service implementation.
// Called from the generated code.
//
// desc must contain description of the service, its message types
// and all transitive dependencies.
//
// Panics if a service of the same name is already registered.
func (s *Server) RegisterService(desc *grpc.ServiceDesc, impl interface{}) {
	serv := &service{
		impl:    impl,
		methods: make(map[string]grpc.MethodDesc, len(desc.Methods)),
	}
	for _, m := range desc.Methods {
		serv.methods[m.MethodName] = m
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	if s.services == nil {
		s.services = map[string]*service{}
	} else if _, ok := s.services[desc.ServiceName]; ok {
		panic(fmt.Errorf("service %q is already registered", desc.ServiceName))
	}

	s.services[desc.ServiceName] = serv
}

// authenticate forces authentication set by RegisterDefaultAuth.
func (s *Server) authenticate() router.Middleware {
	a := s.Authenticator
	if a == nil {
		a = GetDefaultAuth()
		if a == nil {
			panic("prpc: no custom Authenticator was provided and default authenticator was not registered.\n" +
				"Either explicitly set `Server.Authenticator = NoAuthentication`, or use RegisterDefaultAuth()")
		}
	}

	return func(c *router.Context, next router.Handler) {
		switch ctx, err := a.Authenticate(c.Context, c.Request); {
		case transient.Tag.In(err):
			writeError(c.Context, c.Writer, withCode(err, codes.Internal))
		case err != nil:
			writeError(c.Context, c.Writer, withCode(err, codes.Unauthenticated))
		default:
			c.Context = ctx
			next(c)
		}
	}
}

// InstallHandlers installs HTTP handlers at /prpc/:service/:method.
//
// See https://godoc.org/go.chromium.org/luci/grpc/prpc#hdr-Protocol
// for pRPC protocol.
//
// The authenticator in 'base' is always replaced by pRPC specific one. For more
// details about the authentication see Server.Authenticator doc.
func (s *Server) InstallHandlers(r *router.Router, base router.MiddlewareChain) {
	s.mu.Lock()
	defer s.mu.Unlock()

	rr := r.Subrouter("/prpc/:service/:method")
	rr.Use(base.Extend(s.authenticate()))

	rr.POST("", router.MiddlewareChain{}, s.handlePOST)
	rr.OPTIONS("", router.MiddlewareChain{}, s.handleOPTIONS)
}

// handle handles RPCs.
// See https://godoc.org/go.chromium.org/luci/grpc/prpc#hdr-Protocol
// for pRPC protocol.
func (s *Server) handlePOST(c *router.Context) {
	serviceName := c.Params.ByName("service")
	methodName := c.Params.ByName("method")
	s.setAccessControlHeaders(c, false)
	res := s.call(c, serviceName, methodName)
	if res.err != nil {
		writeError(c.Context, c.Writer, res.err)
		return
	}
	writeMessage(c.Context, c.Writer, res.out, res.fmt)
}

func (s *Server) handleOPTIONS(c *router.Context) {
	s.setAccessControlHeaders(c, true)
	c.Writer.WriteHeader(http.StatusOK)
}

type response struct {
	out proto.Message
	fmt Format
	err error
}

func (s *Server) call(c *router.Context, serviceName, methodName string) (r response) {
	service := s.services[serviceName]
	if service == nil {
		r.err = status.Errorf(
			codes.Unimplemented,
			"service %q is not implemented",
			serviceName)
		return
	}

	method, ok := service.methods[methodName]
	if !ok {
		r.err = status.Errorf(
			codes.Unimplemented,
			"method %q in service %q is not implemented",
			methodName, serviceName)
		return
	}

	var perr *protocolError
	r.fmt, perr = responseFormat(c.Request.Header.Get(headerAccept))
	if perr != nil {
		r.err = perr
		return
	}

	methodCtx, err := parseHeader(c.Context, c.Request.Header)
	if err != nil {
		r.err = withStatus(err, http.StatusBadRequest)
		return
	}

	out, err := method.Handler(service.impl, methodCtx, func(in interface{}) error {
		if in == nil {
			return grpcutil.Errf(codes.Internal, "input message is nil")
		}
		// Do not collapse it to one line. There is implicit err type conversion.
		if perr := readMessage(c.Request, in.(proto.Message)); perr != nil {
			return perr
		}
		return nil
	}, s.UnaryServerInterceptor)

	switch {
	case err != nil:
		r.err = err
	case out == nil:
		r.err = status.Error(codes.Internal, "service returned nil message")
	default:
		r.out = out.(proto.Message)
	}
	return
}

func (s *Server) setAccessControlHeaders(c *router.Context, preflight bool) {
	// Don't write out access control headers if the origin is unspecified.
	const originHeader = "Origin"
	origin := c.Request.Header.Get(originHeader)
	if origin == "" || s.AccessControl == nil || !s.AccessControl(c.Context, origin) {
		return
	}

	h := c.Writer.Header()
	h.Add("Access-Control-Allow-Origin", origin)
	h.Add("Vary", originHeader)
	h.Add("Access-Control-Allow-Credentials", "true")

	if preflight {
		h.Add("Access-Control-Allow-Headers", allowHeaders)
		h.Add("Access-Control-Allow-Methods", allowMethods)
		h.Add("Access-Control-Max-Age", allowPreflightCacheAgeSecs)
	} else {
		h.Add("Access-Control-Expose-Headers", exposeHeaders)
	}
}

// ServiceNames returns a sorted list of full names of all registered services.
func (s *Server) ServiceNames() []string {
	s.mu.Lock()
	defer s.mu.Unlock()

	names := make([]string, 0, len(s.services))
	for name := range s.services {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}
<|endoftext|>


<commit_before>// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

// Package frameworktestutil contains utilities for testing functions written using the framework.
package frameworktestutil

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

	"github.com/spf13/cobra"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/kio"
)

// CommandResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type CommandResultsChecker struct {
	// TestDataDirectory is the directory containing the testdata subdirectories.
	// CommandResultsChecker will recurse into each test directory and run the Command
	// if the directory contains both the ConfigInputFilename and at least one
	// of ExpectedOutputFilname or ExpectedErrorFilename.
	// Defaults to "testdata"
	TestDataDirectory string

	// ConfigInputFilename is the name of the config file provided as the first
	// argument to the function.  Directories without this file will be skipped.
	// Defaults to "config.yaml"
	ConfigInputFilename string

	// InputFilenameGlob matches function inputs
	// Defaults to "input*.yaml"
	InputFilenameGlob string

	// ExpectedOutputFilename is the file with the expected output of the function
	// Defaults to "expected.yaml".  Directories containing neither this file
	// nor ExpectedErrorFilename will be skipped.
	ExpectedOutputFilename string

	// ExpectedErrorFilename is the file containing part of an expected error message
	// Defaults to "error.yaml".  Directories containing neither this file
	// nor ExpectedOutputFilename will be skipped.
	ExpectedErrorFilename string

	// Command provides the function to run.
	Command func() *cobra.Command

	// UpdateExpectedFromActual if set to true will write the actual results to the
	// expected testdata files.  This is useful for updating test data.
	UpdateExpectedFromActual bool

	testsCasesRun int
}

// Assert asserts the results for functions
func (rc *CommandResultsChecker) Assert(t *testing.T) bool {
	if rc.TestDataDirectory == "" {
		rc.TestDataDirectory = "testdata"
	}
	if rc.ConfigInputFilename == "" {
		rc.ConfigInputFilename = "config.yaml"
	}
	if rc.ExpectedOutputFilename == "" {
		rc.ExpectedOutputFilename = "expected.yaml"
	}
	if rc.ExpectedErrorFilename == "" {
		rc.ExpectedErrorFilename = "error.yaml"
	}
	if rc.InputFilenameGlob == "" {
		rc.InputFilenameGlob = "input*.yaml"
	}

	err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
		require.NoError(t, err)
		if !info.IsDir() {
			// skip non-directories
			return nil
		}
		rc.compare(t, path)
		return nil
	})
	require.NoError(t, err)

	require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)

	return true
}

func (rc *CommandResultsChecker) compare(t *testing.T, path string) {
	// cd into the directory so we can test functions that refer
	// local files by relative paths
	d, err := os.Getwd()
	require.NoError(t, err)

	defer func() { require.NoError(t, os.Chdir(d)) }()
	require.NoError(t, os.Chdir(path))

	// make sure this directory contains test data
	_, err = os.Stat(rc.ConfigInputFilename)
	if os.IsNotExist(err) {
		// missing input
		return
	}
	args := []string{rc.ConfigInputFilename}

	expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
	if expectedError == "" && expectedOutput == "" {
		// missing expected
		return
	}
	require.NoError(t, err)

	// run the test
	t.Run(path, func(t *testing.T) {
		rc.testsCasesRun += 1
		if rc.InputFilenameGlob != "" {
			inputs, err := filepath.Glob(rc.InputFilenameGlob)
			require.NoError(t, err)
			args = append(args, inputs...)
		}

		var actualOutput, actualError bytes.Buffer
		cmd := rc.Command()
		cmd.SetArgs(args)
		cmd.SetOut(&actualOutput)
		cmd.SetErr(&actualError)

		err = cmd.Execute()

		// Update the fixtures if configured to
		if rc.UpdateExpectedFromActual {
			if actualError.String() != "" {
				assert.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, actualError.Bytes(), 0600))
			}
			if actualOutput.String() != "" {
				assert.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
			}
			return
		}

		// Compare the results
		if expectedError != "" {
			// We expected an error, so make sure there was one and it matches
			require.Error(t, err, actualOutput.String())
			require.Contains(t,
				standardizeSpacing(actualError.String()),
				standardizeSpacing(expectedError), actualOutput.String())
		} else {
			// We didn't expect an error, and the output should match
			require.NoError(t, err, actualError.String())
			require.Equal(t,
				standardizeSpacing(expectedOutput),
				standardizeSpacing(actualOutput.String()), actualError.String())
		}
	})
}

func standardizeSpacing(s string) string {
	// remove extra whitespace and convert Windows line endings
	return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}

// ProcessorResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type ProcessorResultsChecker struct {
	// TestDataDirectory is the directory containing the testdata subdirectories.
	// CommandResultsChecker will recurse into each test directory and run the Processor
	// if the directory contains both the InputFilename and at least one
	// of ExpectedOutputFilename or ExpectedErrorFilename.
	// Defaults to "testdata"
	TestDataDirectory string

	// InputFilename is the name of the file containing the ResourceList input.
	// Directories without this file will be skipped.
	// Defaults to "input.yaml"
	InputFilename string

	// ExpectedOutputFilename is the file with the expected output of the function
	// Defaults to "expected.yaml".  Directories containing neither this file
	// nor ExpectedErrorFilename will be skipped.
	ExpectedOutputFilename string

	// ExpectedErrorFilename is the file containing part of an expected error message
	// Defaults to "error.yaml".  Directories containing neither this file
	// nor ExpectedOutputFilename will be skipped.
	ExpectedErrorFilename string

	// Processor returns a ResourceListProcessor to run.
	Processor func() framework.ResourceListProcessor

	// UpdateExpectedFromActual if set to true will write the actual results to the
	// expected testdata files.  This is useful for updating test data.
	UpdateExpectedFromActual bool

	testsCasesRun int
}

// Assert asserts the results for functions
func (rc *ProcessorResultsChecker) Assert(t *testing.T) bool {
	if rc.TestDataDirectory == "" {
		rc.TestDataDirectory = "testdata"
	}
	if rc.InputFilename == "" {
		rc.InputFilename = "input.yaml"
	}
	if rc.ExpectedOutputFilename == "" {
		rc.ExpectedOutputFilename = "expected.yaml"
	}
	if rc.ExpectedErrorFilename == "" {
		rc.ExpectedErrorFilename = "error.yaml"
	}

	err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
		require.NoError(t, err)
		if !info.IsDir() {
			// skip non-directories
			return nil
		}
		rc.compare(t, path)
		return nil
	})
	require.NoError(t, err)

	require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)

	return true
}

func (rc *ProcessorResultsChecker) compare(t *testing.T, path string) {
	// cd into the directory so we can test functions that refer
	// local files by relative paths
	d, err := os.Getwd()
	require.NoError(t, err)

	defer func() { require.NoError(t, os.Chdir(d)) }()
	require.NoError(t, os.Chdir(path))

	// make sure this directory contains test data
	_, err = os.Stat(rc.InputFilename)
	if os.IsNotExist(err) {
		// missing input
		return
	}
	require.NoError(t, err)

	expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
	if expectedError == "" && expectedOutput == "" {
		// missing expected
		return
	}

	// run the test
	t.Run(path, func(t *testing.T) {
		rc.testsCasesRun += 1
		actualOutput := bytes.NewBuffer([]byte{})
		rlBytes, err := ioutil.ReadFile(rc.InputFilename)
		require.NoError(t, err)

		rw := kio.ByteReadWriter{
			Reader: bytes.NewBuffer(rlBytes),
			Writer: actualOutput,
		}

		err = framework.Execute(rc.Processor(), &rw)

		// Update the fixtures if configured to
		if rc.UpdateExpectedFromActual {
			if err != nil {
				require.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, []byte(err.Error()), 0600))
			}
			if len(actualOutput.String()) > 0 {
				require.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
			}
			return
		}

		// Compare the results
		if expectedError != "" {
			// We expected an error, so make sure there was one and it matches
			require.Error(t, err, actualOutput.String())
			require.Contains(t,
				standardizeSpacing(err.Error()),
				standardizeSpacing(expectedError), actualOutput.String())
		} else {
			// We didn't expect an error, and the output should match
			require.NoError(t, err)
			require.Equal(t,
				standardizeSpacing(expectedOutput),
				standardizeSpacing(actualOutput.String()))
		}
	})
}

// getExpected reads the expected results and error files
func getExpected(t *testing.T, expectedOutFilename, expectedErrFilename string) (string, string) {
	// read the expected results
	var expectedOutput, expectedError string
	if expectedOutFilename != "" {
		_, err := os.Stat(expectedOutFilename)
		if !os.IsNotExist(err) && err != nil {
			t.FailNow()
		}
		if err == nil {
			// only read the file if it exists
			b, err := ioutil.ReadFile(expectedOutFilename)
			if !assert.NoError(t, err) {
				t.FailNow()
			}
			expectedOutput = string(b)
		}
	}
	if expectedErrFilename != "" {
		_, err := os.Stat(expectedErrFilename)
		if !os.IsNotExist(err) && err != nil {
			t.FailNow()
		}
		if err == nil {
			// only read the file if it exists
			b, err := ioutil.ReadFile(expectedErrFilename)
			if !assert.NoError(t, err) {
				t.FailNow()
			}
			expectedError = string(b)
		}
	}
	return expectedOutput, expectedError
}
<commit_msg>Add check of UpdateExpectedFromActual before skipping test<commit_after>// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

// Package frameworktestutil contains utilities for testing functions written using the framework.
package frameworktestutil

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

	"github.com/spf13/cobra"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/kio"
)

// CommandResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type CommandResultsChecker struct {
	// TestDataDirectory is the directory containing the testdata subdirectories.
	// CommandResultsChecker will recurse into each test directory and run the Command
	// if the directory contains both the ConfigInputFilename and at least one
	// of ExpectedOutputFilname or ExpectedErrorFilename.
	// Defaults to "testdata"
	TestDataDirectory string

	// ConfigInputFilename is the name of the config file provided as the first
	// argument to the function.  Directories without this file will be skipped.
	// Defaults to "config.yaml"
	ConfigInputFilename string

	// InputFilenameGlob matches function inputs
	// Defaults to "input*.yaml"
	InputFilenameGlob string

	// ExpectedOutputFilename is the file with the expected output of the function
	// Defaults to "expected.yaml".  Directories containing neither this file
	// nor ExpectedErrorFilename will be skipped.
	ExpectedOutputFilename string

	// ExpectedErrorFilename is the file containing part of an expected error message
	// Defaults to "error.yaml".  Directories containing neither this file
	// nor ExpectedOutputFilename will be skipped.
	ExpectedErrorFilename string

	// Command provides the function to run.
	Command func() *cobra.Command

	// UpdateExpectedFromActual if set to true will write the actual results to the
	// expected testdata files.  This is useful for updating test data.
	UpdateExpectedFromActual bool

	testsCasesRun int
}

// Assert asserts the results for functions
func (rc *CommandResultsChecker) Assert(t *testing.T) bool {
	if rc.TestDataDirectory == "" {
		rc.TestDataDirectory = "testdata"
	}
	if rc.ConfigInputFilename == "" {
		rc.ConfigInputFilename = "config.yaml"
	}
	if rc.ExpectedOutputFilename == "" {
		rc.ExpectedOutputFilename = "expected.yaml"
	}
	if rc.ExpectedErrorFilename == "" {
		rc.ExpectedErrorFilename = "error.yaml"
	}
	if rc.InputFilenameGlob == "" {
		rc.InputFilenameGlob = "input*.yaml"
	}

	err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
		require.NoError(t, err)
		if !info.IsDir() {
			// skip non-directories
			return nil
		}
		rc.compare(t, path)
		return nil
	})
	require.NoError(t, err)

	require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)

	return true
}

func (rc *CommandResultsChecker) compare(t *testing.T, path string) {
	// cd into the directory so we can test functions that refer
	// local files by relative paths
	d, err := os.Getwd()
	require.NoError(t, err)

	defer func() { require.NoError(t, os.Chdir(d)) }()
	require.NoError(t, os.Chdir(path))

	// make sure this directory contains test data
	_, err = os.Stat(rc.ConfigInputFilename)
	if os.IsNotExist(err) {
		// missing input
		return
	}
	args := []string{rc.ConfigInputFilename}

	expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
	if expectedError == "" && expectedOutput == "" && !rc.UpdateExpectedFromActual {
		// missing expected and UpdateExpectedFromActual == false, return and skip this test
		return
	}
	require.NoError(t, err)

	// run the test
	t.Run(path, func(t *testing.T) {
		rc.testsCasesRun += 1
		if rc.InputFilenameGlob != "" {
			inputs, err := filepath.Glob(rc.InputFilenameGlob)
			require.NoError(t, err)
			args = append(args, inputs...)
		}

		var actualOutput, actualError bytes.Buffer
		cmd := rc.Command()
		cmd.SetArgs(args)
		cmd.SetOut(&actualOutput)
		cmd.SetErr(&actualError)

		err = cmd.Execute()

		// Update the fixtures if configured to
		if rc.UpdateExpectedFromActual {
			if actualError.String() != "" {
				assert.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, actualError.Bytes(), 0600))
			}
			if actualOutput.String() != "" {
				assert.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
			}
			return
		}

		// Compare the results
		if expectedError != "" {
			// We expected an error, so make sure there was one and it matches
			require.Error(t, err, actualOutput.String())
			require.Contains(t,
				standardizeSpacing(actualError.String()),
				standardizeSpacing(expectedError), actualOutput.String())
		} else {
			// We didn't expect an error, and the output should match
			require.NoError(t, err, actualError.String())
			require.Equal(t,
				standardizeSpacing(expectedOutput),
				standardizeSpacing(actualOutput.String()), actualError.String())
		}
	})
}

func standardizeSpacing(s string) string {
	// remove extra whitespace and convert Windows line endings
	return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}

// ProcessorResultsChecker tests a function by running it with predefined inputs and comparing
// the outputs to expected results.
type ProcessorResultsChecker struct {
	// TestDataDirectory is the directory containing the testdata subdirectories.
	// CommandResultsChecker will recurse into each test directory and run the Processor
	// if the directory contains both the InputFilename and at least one
	// of ExpectedOutputFilename or ExpectedErrorFilename.
	// Defaults to "testdata"
	TestDataDirectory string

	// InputFilename is the name of the file containing the ResourceList input.
	// Directories without this file will be skipped.
	// Defaults to "input.yaml"
	InputFilename string

	// ExpectedOutputFilename is the file with the expected output of the function
	// Defaults to "expected.yaml".  Directories containing neither this file
	// nor ExpectedErrorFilename will be skipped.
	ExpectedOutputFilename string

	// ExpectedErrorFilename is the file containing part of an expected error message
	// Defaults to "error.yaml".  Directories containing neither this file
	// nor ExpectedOutputFilename will be skipped.
	ExpectedErrorFilename string

	// Processor returns a ResourceListProcessor to run.
	Processor func() framework.ResourceListProcessor

	// UpdateExpectedFromActual if set to true will write the actual results to the
	// expected testdata files.  This is useful for updating test data.
	UpdateExpectedFromActual bool

	testsCasesRun int
}

// Assert asserts the results for functions
func (rc *ProcessorResultsChecker) Assert(t *testing.T) bool {
	if rc.TestDataDirectory == "" {
		rc.TestDataDirectory = "testdata"
	}
	if rc.InputFilename == "" {
		rc.InputFilename = "input.yaml"
	}
	if rc.ExpectedOutputFilename == "" {
		rc.ExpectedOutputFilename = "expected.yaml"
	}
	if rc.ExpectedErrorFilename == "" {
		rc.ExpectedErrorFilename = "error.yaml"
	}

	err := filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error {
		require.NoError(t, err)
		if !info.IsDir() {
			// skip non-directories
			return nil
		}
		rc.compare(t, path)
		return nil
	})
	require.NoError(t, err)

	require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.TestDataDirectory)

	return true
}

func (rc *ProcessorResultsChecker) compare(t *testing.T, path string) {
	// cd into the directory so we can test functions that refer
	// local files by relative paths
	d, err := os.Getwd()
	require.NoError(t, err)

	defer func() { require.NoError(t, os.Chdir(d)) }()
	require.NoError(t, os.Chdir(path))

	// make sure this directory contains test data
	_, err = os.Stat(rc.InputFilename)
	if os.IsNotExist(err) {
		// missing input
		return
	}
	require.NoError(t, err)

	expectedOutput, expectedError := getExpected(t, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename)
	if expectedError == "" && expectedOutput == "" && !rc.UpdateExpectedFromActual {
		// missing expected and UpdateExpectedFromActual == false, return and skip this test
		return
	}

	// run the test
	t.Run(path, func(t *testing.T) {
		rc.testsCasesRun += 1
		actualOutput := bytes.NewBuffer([]byte{})
		rlBytes, err := ioutil.ReadFile(rc.InputFilename)
		require.NoError(t, err)

		rw := kio.ByteReadWriter{
			Reader: bytes.NewBuffer(rlBytes),
			Writer: actualOutput,
		}

		err = framework.Execute(rc.Processor(), &rw)

		// Update the fixtures if configured to
		if rc.UpdateExpectedFromActual {
			if err != nil {
				require.NoError(t, ioutil.WriteFile(rc.ExpectedErrorFilename, []byte(err.Error()), 0600))
			}
			if len(actualOutput.String()) > 0 {
				require.NoError(t, ioutil.WriteFile(rc.ExpectedOutputFilename, actualOutput.Bytes(), 0600))
			}
			return
		}

		// Compare the results
		if expectedError != "" {
			// We expected an error, so make sure there was one and it matches
			require.Error(t, err, actualOutput.String())
			require.Contains(t,
				standardizeSpacing(err.Error()),
				standardizeSpacing(expectedError), actualOutput.String())
		} else {
			// We didn't expect an error, and the output should match
			require.NoError(t, err)
			require.Equal(t,
				standardizeSpacing(expectedOutput),
				standardizeSpacing(actualOutput.String()))
		}
	})
}

// getExpected reads the expected results and error files
func getExpected(t *testing.T, expectedOutFilename, expectedErrFilename string) (string, string) {
	// read the expected results
	var expectedOutput, expectedError string
	if expectedOutFilename != "" {
		_, err := os.Stat(expectedOutFilename)
		if !os.IsNotExist(err) && err != nil {
			t.FailNow()
		}
		if err == nil {
			// only read the file if it exists
			b, err := ioutil.ReadFile(expectedOutFilename)
			if !assert.NoError(t, err) {
				t.FailNow()
			}
			expectedOutput = string(b)
		}
	}
	if expectedErrFilename != "" {
		_, err := os.Stat(expectedErrFilename)
		if !os.IsNotExist(err) && err != nil {
			t.FailNow()
		}
		if err == nil {
			// only read the file if it exists
			b, err := ioutil.ReadFile(expectedErrFilename)
			if !assert.NoError(t, err) {
				t.FailNow()
			}
			expectedError = string(b)
		}
	}
	return expectedOutput, expectedError
}
<|endoftext|>


<commit_before>package server

import "testing"

func TestStringIsCommand(t *testing.T) {

	if stringIsCommand("!foo") {
		// do nothing
	}

	if stringIsCommand("bar") {
		t.Errorf("string is not a command.")
	}

	if stringIsCommand("f!gate") {
		t.Errorf("string is not a command.");
	}

	if stringIsCommand("! ") {
		// do nothing
	}

	if stringIsCommand("") {
		t.Errorf("string is not a command.");
	}
}

func TestCommandString(t *testing.T) {

	if getCommandFromString("!help") != "help" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!foo bars") != "foo" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!z bars") != "z" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!foo  !bars ") != "foo" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("foo") != "" {
		t.Errorf("String is not a command.")
	}

	if getCommandFromString("!") != "" {
		t.Errorf("Command string is incorrect.")
	}

}<commit_msg>Add tests for getting command from string<commit_after>package server

import "testing"

func TestStringIsCommand(t *testing.T) {

	if stringIsCommand("!foo") {
		// do nothing
	}

	if stringIsCommand("bar") {
		t.Errorf("string is not a command.")
	}

	if stringIsCommand("f!gate") {
		t.Errorf("string is not a command.");
	}

	if stringIsCommand("! ") {
		// do nothing
	}

	if stringIsCommand("") {
		t.Errorf("string is not a command.");
	}
}

func TestCommandString(t *testing.T) {

	if getCommandFromString("!help") != "help" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!foo bars") != "foo" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!z bars") != "z" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("!foo  !bars ") != "foo" {
		t.Errorf("Command string is incorrect.")
	}

	if getCommandFromString("foo") != "" {
		t.Errorf("String is not a command.")
	}

	if getCommandFromString("!") != "" {
		t.Errorf("Command string is incorrect.")
	}

}

func TestGetCommandFromString(t *testing.T) {
	// Testing command with arguments
	if getCommandFromString("!foo bar") != "foo" {
		t.Errorf("Did not get foo when passing `!foo bar`")
	}
	if getCommandFromString("!foo") != "foo" {
		t.Errorf("Did not get foo when passing !foo")
	}
	if getCommandFromString("!") != "" {
		t.Errorf("Did not get blank string from passing bare `!`")
	}
}
<|endoftext|>


<commit_before>package gorp

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"
)

// The Dialect interface encapsulates behaviors that differ across
// SQL databases.  At present the Dialect is only used by CreateTables()
// but this could change in the future
type Dialect interface {

	// ToSqlType returns the SQL column type to use when creating a
	// table of the given Go Type.  maxsize can be used to switch based on
	// size.  For example, in MySQL []byte could map to BLOB, MEDIUMBLOB,
	// or LONGBLOB depending on the maxsize
	ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string

	// string to append to primary key column definitions
	AutoIncrStr() string

	// string to append to "create table" statement for vendor specific
	// table attributes
	CreateTableSuffix() string

	LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error)

	// bind variable string to use when forming SQL statements
	// in many dbs it is "?", but Postgres appears to use $1
	//
	// i is a zero based index of the bind variable in this statement
	//
	BindVar(i int) string

	// Handles quoting of a field name to ensure that it doesn't raise any
	// SQL parsing exceptions by using a reserved word as a field name.
	QuoteField(field string) string
}

///////////////////////////////////////////////////////
// sqlite3 //
/////////////

type SqliteDialect struct {
	suffix string
}

func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
		return "integer"
	case reflect.Float64, reflect.Float32:
		return "real"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "blob"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "integer"
	case "NullableFloat64":
		return "real"
	case "NullableBool":
		return "integer"
	case "NullableBytes":
		return "blob"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns autoincrement
func (d SqliteDialect) AutoIncrStr() string {
	return "autoincrement"
}

// Returns suffix
func (d SqliteDialect) CreateTableSuffix() string {
	return d.suffix
}

// Returns "?"
func (d SqliteDialect) BindVar(i int) string {
	return "?"
}

func (d SqliteDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	return (*res).LastInsertId()
}

func (d SqliteDialect) QuoteField(f string) string {
	return "'" + f + "'"
}

///////////////////////////////////////////////////////
// PostgreSQL //
////////////////

type PostgresDialect struct {
	suffix string
}

func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32:
		if isAutoIncr {
			return "serial"
		}
		return "integer"
	case reflect.Int64:
		if isAutoIncr {
			return "serial"
		}
		return "bigint"
	case reflect.Float64, reflect.Float32:
		return "real"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "bytea"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "bigint"
	case "NullableFloat64":
		return "double"
	case "NullableBool":
		return "smallint"
	case "NullableBytes":
		return "bytea"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns empty string
func (d PostgresDialect) AutoIncrStr() string {
	return ""
}

// Returns suffix
func (d PostgresDialect) CreateTableSuffix() string {
	return d.suffix
}

// Returns "$(i+1)"
func (d PostgresDialect) BindVar(i int) string {
	return fmt.Sprintf("$%d", i+1)
}

func (d PostgresDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	sql := fmt.Sprintf("select currval('%s_%s_seq')", table.TableName, table.keys[0].ColumnName)
	rows, err := exec.query(sql)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	if rows.Next() {
		var dest int64
		err = rows.Scan(&dest)
		return dest, nil
	}
	return 0, errors.New(fmt.Sprintf("PostgresDialect: %s did not return a row", sql))
}

func (d PostgresDialect) QuoteField(f string) string {
	return `"` + f + `"`
}

///////////////////////////////////////////////////////
// MySQL //
///////////

// Implementation of Dialect for MySQL databases.
type MySQLDialect struct {

	// Engine is the storage engine to use "InnoDB" vs "MyISAM" for example
	Engine string

	// Encoding is the character encoding to use for created tables
	Encoding string
}

func (m MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32:
		return "int"
	case reflect.Int64:
		return "bigint"
	case reflect.Float64, reflect.Float32:
		return "double"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "mediumblob"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "bigint"
	case "NullableFloat64":
		return "double"
	case "NullableBool":
		return "tinyint"
	case "NullableBytes":
		return "mediumblob"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns auto_increment
func (m MySQLDialect) AutoIncrStr() string {
	return "auto_increment"
}

// Returns engine=%s charset=%s  based on values stored on struct
func (m MySQLDialect) CreateTableSuffix() string {
	return fmt.Sprintf(" engine=%s charset=%s", m.Engine, m.Encoding)
}

// Returns "?"
func (m MySQLDialect) BindVar(i int) string {
	return "?"
}

func (m MySQLDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	return (*res).LastInsertId()
}

func (d MySQLDialect) QuoteField(f string) string {
	return "`" + f + "`"
}
<commit_msg>Correct SQLite quoting<commit_after>package gorp

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"
)

// The Dialect interface encapsulates behaviors that differ across
// SQL databases.  At present the Dialect is only used by CreateTables()
// but this could change in the future
type Dialect interface {

	// ToSqlType returns the SQL column type to use when creating a
	// table of the given Go Type.  maxsize can be used to switch based on
	// size.  For example, in MySQL []byte could map to BLOB, MEDIUMBLOB,
	// or LONGBLOB depending on the maxsize
	ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string

	// string to append to primary key column definitions
	AutoIncrStr() string

	// string to append to "create table" statement for vendor specific
	// table attributes
	CreateTableSuffix() string

	LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error)

	// bind variable string to use when forming SQL statements
	// in many dbs it is "?", but Postgres appears to use $1
	//
	// i is a zero based index of the bind variable in this statement
	//
	BindVar(i int) string

	// Handles quoting of a field name to ensure that it doesn't raise any
	// SQL parsing exceptions by using a reserved word as a field name.
	QuoteField(field string) string
}

///////////////////////////////////////////////////////
// sqlite3 //
/////////////

type SqliteDialect struct {
	suffix string
}

func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
		return "integer"
	case reflect.Float64, reflect.Float32:
		return "real"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "blob"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "integer"
	case "NullableFloat64":
		return "real"
	case "NullableBool":
		return "integer"
	case "NullableBytes":
		return "blob"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns autoincrement
func (d SqliteDialect) AutoIncrStr() string {
	return "autoincrement"
}

// Returns suffix
func (d SqliteDialect) CreateTableSuffix() string {
	return d.suffix
}

// Returns "?"
func (d SqliteDialect) BindVar(i int) string {
	return "?"
}

func (d SqliteDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	return (*res).LastInsertId()
}

func (d SqliteDialect) QuoteField(f string) string {
	return "`" + f + "`"
}

///////////////////////////////////////////////////////
// PostgreSQL //
////////////////

type PostgresDialect struct {
	suffix string
}

func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32:
		if isAutoIncr {
			return "serial"
		}
		return "integer"
	case reflect.Int64:
		if isAutoIncr {
			return "serial"
		}
		return "bigint"
	case reflect.Float64, reflect.Float32:
		return "real"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "bytea"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "bigint"
	case "NullableFloat64":
		return "double"
	case "NullableBool":
		return "smallint"
	case "NullableBytes":
		return "bytea"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns empty string
func (d PostgresDialect) AutoIncrStr() string {
	return ""
}

// Returns suffix
func (d PostgresDialect) CreateTableSuffix() string {
	return d.suffix
}

// Returns "$(i+1)"
func (d PostgresDialect) BindVar(i int) string {
	return fmt.Sprintf("$%d", i+1)
}

func (d PostgresDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	sql := fmt.Sprintf("select currval('%s_%s_seq')", table.TableName, table.keys[0].ColumnName)
	rows, err := exec.query(sql)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	if rows.Next() {
		var dest int64
		err = rows.Scan(&dest)
		return dest, nil
	}
	return 0, errors.New(fmt.Sprintf("PostgresDialect: %s did not return a row", sql))
}

func (d PostgresDialect) QuoteField(f string) string {
	return `"` + f + `"`
}

///////////////////////////////////////////////////////
// MySQL //
///////////

// Implementation of Dialect for MySQL databases.
type MySQLDialect struct {

	// Engine is the storage engine to use "InnoDB" vs "MyISAM" for example
	Engine string

	// Encoding is the character encoding to use for created tables
	Encoding string
}

func (m MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
	switch val.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32:
		return "int"
	case reflect.Int64:
		return "bigint"
	case reflect.Float64, reflect.Float32:
		return "double"
	case reflect.Slice:
		if val.Elem().Kind() == reflect.Uint8 {
			return "mediumblob"
		}
	}

	switch val.Name() {
	case "NullableInt64":
		return "bigint"
	case "NullableFloat64":
		return "double"
	case "NullableBool":
		return "tinyint"
	case "NullableBytes":
		return "mediumblob"
	}

	if maxsize < 1 {
		maxsize = 255
	}
	return fmt.Sprintf("varchar(%d)", maxsize)
}

// Returns auto_increment
func (m MySQLDialect) AutoIncrStr() string {
	return "auto_increment"
}

// Returns engine=%s charset=%s  based on values stored on struct
func (m MySQLDialect) CreateTableSuffix() string {
	return fmt.Sprintf(" engine=%s charset=%s", m.Engine, m.Encoding)
}

// Returns "?"
func (m MySQLDialect) BindVar(i int) string {
	return "?"
}

func (m MySQLDialect) LastInsertId(res *sql.Result, table *TableMap, exec SqlExecutor) (int64, error) {
	return (*res).LastInsertId()
}

func (d MySQLDialect) QuoteField(f string) string {
	return "`" + f + "`"
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"regexp"
	"strings"

	"github.com/bwmarrin/discordgo"
)

//discordConfig is a struct containing user configuration for discord connection
type discordConfig struct {
	Boss          string
	Name          string
	AnimeChannels []string
	Token         string
	Debug         bool
}

//Declare constants
const (
	//Help string lists all discord chat commands for this bot
	discordHelp string = "***LIST OF BOT COMMANDS***\n" +
		"Fields in [] are optional\n" +
		"Fields in <> are mandatory\n\n" +
		"```" +
		"!help [bot] (Lists all bot commands)\n" +
		"!uptime [bot](Prints current bot uptime)\n" +
		"!sub <id> (Subscribe to anime)\n" +
		"!unsub <id> (Unsubscribe from anime)\n" +
		"!w <location> (Prints current weather)\n" +
		"```\n" +
		"Find anime list at http://gazzy.space/anime"
)

//Declare variables
var (
	discord       *discordgo.Session           //Discord client
	discordCfg    *discordConfig               //Discord options
	relevantRegex = regexp.MustCompile(`^!\w`) //Discord regex msg parser
)

//Starts discord connection and sets handlers and behavior
func discordStart(c *discordConfig) {
	//Updates discordCfg variable with c parameter data
	//For future use outside this function
	discordCfg = c

	//Connect to discord with token
	var err error
	discord, err = discordgo.New(c.Token)
	if err != nil {
		log.Fatal("Error initializing discord in discordStart() function!\n", err)
	}

	//Set behavior & assign handlers
	discord.Debug = c.Debug
	discord.AddHandler(discordMsgHandler)
	discord.AddHandler(discordReadyHandler)
	discord.Open() //Opens discord connection
}

//discordReadyHandler sets required data after a successful connection
func discordReadyHandler(s *discordgo.Session, r *discordgo.Ready) {
	//Sets this bots name in discordCfg.Name
	discordCfg.Name = strings.ToUpper(r.User.Username)

	//Iterates through guilds
	for _, guild := range r.Guilds {
		//Gets a list of channels for this guild
		channels, _ := s.GuildChannels(guild.ID)
		//Iterates through a list of channels for this guild
		for _, channel := range channels {
			//If it finds a channel with name 'anime'
			//it will add it to discordCfg.AnimeChannels array
			//which is used when sending new episode messages in discord
			if channel.Name == "anime" {
				discordCfg.AnimeChannels = appendUnique(discordCfg.AnimeChannels, channel.ID)
			}
		}
	}

	//Logs successful connection to discord was established
	log.Println("Connected to discord as", discordCfg.Name)
}

//discordMsgHandler is a handler function for incomming discord messages
func discordMsgHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
	//Check if author is admin
	boss := m.Author.ID == discordCfg.Boss
	//Check if message is relevant to the bot
	//i.e message starts with '!' followed by a word
	relevant := relevantRegex.MatchString(m.Content)

	//If message is relevant process it otherwise leave this function
	if relevant {
		args := strings.Fields(m.Content)
		switch strings.ToUpper(args[0]) {

		//!HELP [string]
		//Can optionally include bots name as second argument
		case "!HELP":
			if len(args) > 1 {
				if strings.ToUpper(args[1]) == discordCfg.Name {
					s.ChannelMessageSend(m.ChannelID, discordHelp)
				}
			} else {
				s.ChannelMessageSend(m.ChannelID, discordHelp)
			}

		//!SUB anime.id
		//anime.id is the id of the anime (string with length of 3 chars)
		case "!SUB":
			if len(args) >= 2 {
				newAnime := anime{ID: strings.ToLower(args[1])}
				if newAnime.Exists() {
					newAnime.AddSub(m.Author.ID)
					s.ChannelMessageSend(m.ChannelID,
						"Successfully subscribed to "+newAnime.Name)
				} else {
					s.ChannelMessageSend(m.ChannelID, "Invalid ID")
				}
			}

		//!UNSUB anime.id
		//anime.id is the id of the anime (string with length of 3 chars)
		case "!UNSUB":
			if len(args) >= 2 {
				newAnime := anime{ID: strings.ToLower(args[1])}
				if newAnime.Exists() {
					newAnime.RemoveSub(m.Author.ID)
					s.ChannelMessageSend(m.ChannelID,
						"Successfully unsubscribed from "+newAnime.Name)
				} else {
					s.ChannelMessageSend(m.ChannelID, "Invalid ID")
				}
			}

		//!UPTIME [string]
		//Can optionally include bots name as second argument
		case "!UPTIME":
			if len(args) > 1 {
				if strings.ToUpper(args[1]) == discordCfg.Name {
					s.ChannelMessageSend(m.ChannelID, "Current uptime is "+getUptime())
				}
			} else {
				s.ChannelMessageSend(m.ChannelID, "Current uptime is "+getUptime())
			}

		//!W string
		//Looks up weather at the the location string
		case "!W":
			if len(args) > 1 {
				var location string
				for i := 1; i < len(args); i++ {
					location += args[i] + " "
				}
				location = location[:len(location)-1]
				s.ChannelMessageSend(m.ChannelID, getWeather(location))
			}

		//!P string
		//Sets the 'currently playing' state of the bot
		case "!P":
			if boss {
				if len(args) > 1 {
					var game string
					for i := 1; i < len(args); i++ {
						game += args[i] + " "
					}
					game = game[:len(game)-1]
					discord.UpdateStatus(0, game)
				} else {
					discord.UpdateStatus(0, "")
				}
			}
		}
	}
}
<commit_msg>Added !INFO command<commit_after>package main

import (
	"log"
	"regexp"
	"strings"

	"github.com/bwmarrin/discordgo"
)

//discordConfig is a struct containing user configuration for discord connection
type discordConfig struct {
	Boss          string
	Name          string
	AnimeChannels []string
	Token         string
	Debug         bool
}

//Declare constants
const (
	//Help string lists all discord chat commands for this bot
	discordHelp string = "***LIST OF BOT COMMANDS***\n" +
		"Fields in [] are optional\n" +
		"Fields in <> are mandatory\n\n" +
		"```" +
		"!help [bot] (Lists all bot commands)\n" +
		"!uptime [bot](Prints current bot uptime)\n" +
		"!sub <id> (Subscribe to anime)\n" +
		"!unsub <id> (Unsubscribe from anime)\n" +
		"!w <location> (Prints current weather)\n" +
		"```\n" +
		"Find anime list at http://gazzy.space/anime"
)

//Declare variables
var (
	discord       *discordgo.Session           //Discord client
	discordCfg    *discordConfig               //Discord options
	relevantRegex = regexp.MustCompile(`^!\w`) //Discord regex msg parser
)

//Starts discord connection and sets handlers and behavior
func discordStart(c *discordConfig) {
	//Updates discordCfg variable with c parameter data
	//For future use outside this function
	discordCfg = c

	//Connect to discord with token
	var err error
	discord, err = discordgo.New(c.Token)
	if err != nil {
		log.Fatal("Error initializing discord in discordStart() function!\n", err)
	}

	//Set behavior & assign handlers
	discord.Debug = c.Debug
	discord.AddHandler(discordMsgHandler)
	discord.AddHandler(discordReadyHandler)
	discord.Open() //Opens discord connection
}

//discordReadyHandler sets required data after a successful connection
func discordReadyHandler(s *discordgo.Session, r *discordgo.Ready) {
	//Sets this bots name in discordCfg.Name
	discordCfg.Name = strings.ToUpper(r.User.Username)

	//Iterates through guilds
	for _, guild := range r.Guilds {
		//Gets a list of channels for this guild
		channels, _ := s.GuildChannels(guild.ID)
		//Iterates through a list of channels for this guild
		for _, channel := range channels {
			//If it finds a channel with name 'anime'
			//it will add it to discordCfg.AnimeChannels array
			//which is used when sending new episode messages in discord
			if channel.Name == "anime" {
				discordCfg.AnimeChannels = appendUnique(discordCfg.AnimeChannels, channel.ID)
			}
		}
	}

	//Logs successful connection to discord was established
	log.Println("Connected to discord as", discordCfg.Name)
}

//discordMsgHandler is a handler function for incomming discord messages
func discordMsgHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
	//Check if author is admin
	boss := m.Author.ID == discordCfg.Boss
	//Check if message is relevant to the bot
	//i.e message starts with '!' followed by a word
	relevant := relevantRegex.MatchString(m.Content)

	//If message is relevant process it otherwise leave this function
	if relevant {
		args := strings.Fields(m.Content)
		switch strings.ToUpper(args[0]) {

		//!HELP [string]
		//Can optionally include bots name as second argument
		case "!HELP":
			if len(args) > 1 {
				if strings.ToUpper(args[1]) == discordCfg.Name {
					s.ChannelMessageSend(m.ChannelID, discordHelp)
				}
			} else {
				s.ChannelMessageSend(m.ChannelID, discordHelp)
			}

		//!SUB anime.id
		//anime.id is the id of the anime (string with length of 3 chars)
		case "!SUB":
			if len(args) >= 2 {
				newAnime := anime{ID: strings.ToLower(args[1])}
				if newAnime.Exists() {
					newAnime.AddSub(m.Author.ID)
					s.ChannelMessageSend(m.ChannelID,
						"Successfully subscribed to "+newAnime.Name)
				} else {
					s.ChannelMessageSend(m.ChannelID, "Invalid ID")
				}
			}

		//!UNSUB anime.id
		//anime.id is the id of the anime (string with length of 3 chars)
		case "!UNSUB":
			if len(args) >= 2 {
				newAnime := anime{ID: strings.ToLower(args[1])}
				if newAnime.Exists() {
					newAnime.RemoveSub(m.Author.ID)
					s.ChannelMessageSend(m.ChannelID,
						"Successfully unsubscribed from "+newAnime.Name)
				} else {
					s.ChannelMessageSend(m.ChannelID, "Invalid ID")
				}
			}

		//!UPTIME [string]
		//Can optionally include bots name as second argument
		case "!UPTIME":
			if len(args) > 1 {
				if strings.ToUpper(args[1]) == discordCfg.Name {
					s.ChannelMessageSend(m.ChannelID, "Current uptime is "+getUptime())
				}
			} else {
				s.ChannelMessageSend(m.ChannelID, "Current uptime is "+getUptime())
			}

		//!W string
		//Looks up weather at the the location string
		case "!W":
			if len(args) > 1 {
				var location string
				for i := 1; i < len(args); i++ {
					location += args[i] + " "
				}
				location = location[:len(location)-1]
				s.ChannelMessageSend(m.ChannelID, getWeather(location))
			}

		//!P string
		//Sets the 'currently playing' state of the bot
		//will only work for admin of the bot
		case "!P":
			if boss {
				if len(args) > 1 {
					var game string
					for i := 1; i < len(args); i++ {
						game += args[i] + " "
					}
					game = game[:len(game)-1]
					discord.UpdateStatus(0, game)
				} else {
					discord.UpdateStatus(0, "")
				}
			}

		//!INFO [string]
		//Can optionally include bots name as second argument
		//Lists bot usage and general information
		case "!INFO":
			if len(args) > 1 {
				if strings.ToUpper(args[1]) == discordCfg.Name {
					s.ChannelMessageSend(m.ChannelID, getInfo())
				}
			} else {
				s.ChannelMessageSend(m.ChannelID, getInfo())
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"sort"
	"strings"
)

func DisplayForceSobjects(sobjects []ForceSobject) {
	names := make([]string, len(sobjects))
	for i, sobject := range sobjects {
		names[i] = sobject["name"].(string)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Println(name)
	}
}

func DisplayForceRecords(records []ForceRecord) {
	var keys []string
	if len(records) > 0 {
		for key, _ := range records[0] {
			if key != "attributes" {
				keys = append(keys, key)
			}
		}
		lengths := make([]int, len(keys))
		separators := make([]string, len(keys))
		for i, key := range keys {
			lengths[i] = 0
			for _, record := range records {
				l := len(record[key].(string))
				if l > lengths[i] {
					lengths[i] = l
				}
			}
			separators[i] = strings.Repeat("-", lengths[i]+2)
		}
		formatter_parts := make([]string, len(keys))
		for i, length := range lengths {
			formatter_parts[i] = fmt.Sprintf(" %%-%ds ", length)
		}
		formatter := strings.Join(formatter_parts, "|")
		fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(keys)...)
		fmt.Printf(strings.Join(separators, "+") + "\n")
		for _, record := range records {
			values := make([]string, len(keys))
			for i, key := range keys {
				values[i] = record[key].(string)
			}
			fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(values)...)
		}
		fmt.Printf(strings.Join(separators, "+") + "\n")
	}
	fmt.Printf("%d results returned\n", len(records))
}

func DisplayForceRecord(record ForceRecord) {
	DisplayInterfaceMap(record, 0)
}

func DisplayInterfaceMap(object map[string]interface{}, indent int) {
	keys := make([]string, len(object))
	i := 0
	for key, _ := range object {
		keys[i] = key
		i++
	}
	sort.Strings(keys)
	for _, key := range keys {
		for i := 0; i < indent; i++ {
			fmt.Printf("  ")
		}
		fmt.Printf("%s: ", key)
		switch v := object[key].(type) {
		case map[string]interface{}:
			fmt.Printf("\n")
			DisplayInterfaceMap(v, indent+1)
		default:
			fmt.Printf("%v\n", v)
		}
	}
}

func StringSliceToInterfaceSlice(s []string) (i []interface{}) {
	for _, str := range s {
		i = append(i, interface{}(str))
	}
	return
}
<commit_msg>change footer<commit_after>package main

import (
	"fmt"
	"sort"
	"strings"
)

func DisplayForceSobjects(sobjects []ForceSobject) {
	names := make([]string, len(sobjects))
	for i, sobject := range sobjects {
		names[i] = sobject["name"].(string)
	}
	sort.Strings(names)
	for _, name := range names {
		fmt.Println(name)
	}
}

func DisplayForceRecords(records []ForceRecord) {
	var keys []string
	if len(records) > 0 {
		for key, _ := range records[0] {
			if key != "attributes" {
				keys = append(keys, key)
			}
		}
		lengths := make([]int, len(keys))
		separators := make([]string, len(keys))
		for i, key := range keys {
			lengths[i] = 0
			for _, record := range records {
				l := len(record[key].(string))
				if l > lengths[i] {
					lengths[i] = l
				}
			}
			separators[i] = strings.Repeat("-", lengths[i]+2)
		}
		formatter_parts := make([]string, len(keys))
		for i, length := range lengths {
			formatter_parts[i] = fmt.Sprintf(" %%-%ds ", length)
		}
		formatter := strings.Join(formatter_parts, "|")
		fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(keys)...)
		fmt.Printf(strings.Join(separators, "+") + "\n")
		for _, record := range records {
			values := make([]string, len(keys))
			for i, key := range keys {
				values[i] = record[key].(string)
			}
			fmt.Printf(formatter+"\n", StringSliceToInterfaceSlice(values)...)
		}
		fmt.Printf(strings.Join(separators, "+") + "\n")
	}
	fmt.Printf("(%d records)\n", len(records))
}

func DisplayForceRecord(record ForceRecord) {
	DisplayInterfaceMap(record, 0)
}

func DisplayInterfaceMap(object map[string]interface{}, indent int) {
	keys := make([]string, len(object))
	i := 0
	for key, _ := range object {
		keys[i] = key
		i++
	}
	sort.Strings(keys)
	for _, key := range keys {
		for i := 0; i < indent; i++ {
			fmt.Printf("  ")
		}
		fmt.Printf("%s: ", key)
		switch v := object[key].(type) {
		case map[string]interface{}:
			fmt.Printf("\n")
			DisplayInterfaceMap(v, indent+1)
		default:
			fmt.Printf("%v\n", v)
		}
	}
}

func StringSliceToInterfaceSlice(s []string) (i []interface{}) {
	for _, str := range s {
		i = append(i, interface{}(str))
	}
	return
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/nelsonleduc/calmanbot/cache"
	"github.com/nelsonleduc/calmanbot/handlers/models"
	"github.com/nelsonleduc/calmanbot/service"
)

const currentCalmanBotVersion string = "v2.9.0"

type builtinDescription struct {
	trigger     string
	description string
}

type builtInParams struct {
	bot     models.Bot
	cache   cache.QueryCache
	repo    models.Repo
	service service.Service
}

type builtin struct {
	builtinDescription
	handler func([]string, builtInParams) string
}

// Descriptions
var helpDescription = builtinDescription{
	"(help)",
	"See this",
}
var topDescription = builtinDescription{
	"(top)",
	"List top 10 liked posts",
}
var showDescription = builtinDescription{
	"show (10|[1-9])(?:$| )",
	"Repost nth top post",
}

var versionDescription = builtinDescription{
	"(version)",
	"Display current version",
}

var descriptions = []builtinDescription{
	helpDescription,
	topDescription,
	showDescription,
	versionDescription,
}
var builtins = []builtin{
	builtin{
		helpDescription,
		responseForHelp,
	},
	builtin{
		topDescription,
		responseForLeaderboard,
	},
	builtin{
		showDescription,
		responseForShow,
	},
	builtin{
		versionDescription,
		responseForVersion,
	},
}

// Handlers
func responseForLeaderboard(matched []string, params builtInParams) string {
	entries := params.cache.LeaderboardEntries(params.bot.GroupID, 10)
	leaderboardAccumulatr := "Top posts:"
	for _, e := range entries {
		leaderboardAccumulatr += "\n" + strconv.Itoa(e.LikeCount) + "    " + e.Query
	}

	return leaderboardAccumulatr
}

func responseForShow(matched []string, params builtInParams) string {
	entries := params.cache.LeaderboardEntries(params.bot.GroupID, 10)
	num, error := strconv.Atoi(matched[1])
	num--
	if len(entries) <= num || error != nil {
		return "There is nothing to display"
	}

	return entries[num].Result
}

func responseForVersion(matched []string, params builtInParams) string {
	return "I'm currently running " + currentCalmanBotVersion
}

func responseForHelp(matched []string, params builtInParams) string {
	_, e := params.service.ServiceTriggerWrangler()
	actions, _ := params.repo.FetchActions(true, e == nil)
	sort.Sort(models.ByPriority(actions))
	botName := params.bot.SanitizedBotNames()[0]

	helpAccumulator := "```Commands:"
	longest := 0
	for _, a := range actions {
		if a.Description == nil || a.Pattern == nil {
			continue
		}

		if len(*a.Pattern) > longest {
			longest = len(*a.Pattern)
		}
	}
	for _, b := range descriptions {
		length := len("@" + botName + " !" + b.trigger)
		if length > longest {
			longest = length
		}
	}
	paddingFmt := fmt.Sprintf("%%-%ds", longest+2)

	for _, a := range actions {
		if a.Description == nil || a.Pattern == nil {
			continue
		}
		printablePattern := *a.Pattern
		printablePattern = strings.Replace(printablePattern, "{_botname_}", botName, -1)
		re := regexp.MustCompile("^\\[(.)\\]")
		matched := re.FindStringSubmatch(printablePattern)
		thing := ""
		if len(matched) > 1 && matched[1] != "" {
			thing = matched[1]
		}
		printablePattern = re.ReplaceAllLiteralString(printablePattern, thing)

		helpAccumulator += "\n" + fmt.Sprintf(paddingFmt, printablePattern) + "\n\t" + *a.Description
	}
	for _, b := range descriptions {
		printablePattern := "@" + botName + " !" + b.trigger
		helpAccumulator += "\n" + fmt.Sprintf(paddingFmt, printablePattern) + "\n\t" + b.description
	}

	return helpAccumulator + "```"
}
<commit_msg>Update version to v2.9.1<commit_after>package handlers

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/nelsonleduc/calmanbot/cache"
	"github.com/nelsonleduc/calmanbot/handlers/models"
	"github.com/nelsonleduc/calmanbot/service"
)

const currentCalmanBotVersion string = "v2.9.1"

type builtinDescription struct {
	trigger     string
	description string
}

type builtInParams struct {
	bot     models.Bot
	cache   cache.QueryCache
	repo    models.Repo
	service service.Service
}

type builtin struct {
	builtinDescription
	handler func([]string, builtInParams) string
}

// Descriptions
var helpDescription = builtinDescription{
	"(help)",
	"See this",
}
var topDescription = builtinDescription{
	"(top)",
	"List top 10 liked posts",
}
var showDescription = builtinDescription{
	"show (10|[1-9])(?:$| )",
	"Repost nth top post",
}

var versionDescription = builtinDescription{
	"(version)",
	"Display current version",
}

var descriptions = []builtinDescription{
	helpDescription,
	topDescription,
	showDescription,
	versionDescription,
}
var builtins = []builtin{
	builtin{
		helpDescription,
		responseForHelp,
	},
	builtin{
		topDescription,
		responseForLeaderboard,
	},
	builtin{
		showDescription,
		responseForShow,
	},
	builtin{
		versionDescription,
		responseForVersion,
	},
}

// Handlers
func responseForLeaderboard(matched []string, params builtInParams) string {
	entries := params.cache.LeaderboardEntries(params.bot.GroupID, 10)
	leaderboardAccumulatr := "Top posts:"
	for _, e := range entries {
		leaderboardAccumulatr += "\n" + strconv.Itoa(e.LikeCount) + "    " + e.Query
	}

	return leaderboardAccumulatr
}

func responseForShow(matched []string, params builtInParams) string {
	entries := params.cache.LeaderboardEntries(params.bot.GroupID, 10)
	num, error := strconv.Atoi(matched[1])
	num--
	if len(entries) <= num || error != nil {
		return "There is nothing to display"
	}

	return entries[num].Result
}

func responseForVersion(matched []string, params builtInParams) string {
	return "I'm currently running " + currentCalmanBotVersion
}

func responseForHelp(matched []string, params builtInParams) string {
	_, e := params.service.ServiceTriggerWrangler()
	actions, _ := params.repo.FetchActions(true, e == nil)
	sort.Sort(models.ByPriority(actions))
	botName := params.bot.SanitizedBotNames()[0]

	helpAccumulator := "```Commands:"
	longest := 0
	for _, a := range actions {
		if a.Description == nil || a.Pattern == nil {
			continue
		}

		if len(*a.Pattern) > longest {
			longest = len(*a.Pattern)
		}
	}
	for _, b := range descriptions {
		length := len("@" + botName + " !" + b.trigger)
		if length > longest {
			longest = length
		}
	}
	paddingFmt := fmt.Sprintf("%%-%ds", longest+2)

	for _, a := range actions {
		if a.Description == nil || a.Pattern == nil {
			continue
		}
		printablePattern := *a.Pattern
		printablePattern = strings.Replace(printablePattern, "{_botname_}", botName, -1)
		re := regexp.MustCompile("^\\[(.)\\]")
		matched := re.FindStringSubmatch(printablePattern)
		thing := ""
		if len(matched) > 1 && matched[1] != "" {
			thing = matched[1]
		}
		printablePattern = re.ReplaceAllLiteralString(printablePattern, thing)

		helpAccumulator += "\n" + fmt.Sprintf(paddingFmt, printablePattern) + "\n\t" + *a.Description
	}
	for _, b := range descriptions {
		printablePattern := "@" + botName + " !" + b.trigger
		helpAccumulator += "\n" + fmt.Sprintf(paddingFmt, printablePattern) + "\n\t" + b.description
	}

	return helpAccumulator + "```"
}
<|endoftext|>