<commit_before>package main

import (
    "os"
    "os/exec"

//    "log"

//    "fmt"
    "strings"
    "runtime"
//    "strconv" // For Itoa
//    "encoding/csv"
    "encoding/json"

    "github.com/go-martini/martini"
)

func main() {
    m := martini.Classic()

    // CPU count
    m.Get("/sh/numberofcores.php", func () ([]byte, error) {
        return json.Marshal(runtime.NumCPU())
    })

    // Server's hostname
    m.Get("/sh/hostname.php", func () ([]byte, error) {
        host, err := os.Hostname()

        if err != nil {
            return nil, err
        }

        return json.Marshal(host)
    })

    // PS
    m.Get("/sh/ps.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("ps", "aux").Output()

        // Convert output to a string (it's not binary data, so this is ok)
        output := string(rawOutput[:])

        if err != nil {
            return nil, err
        }

        // We'll add all the parsed lines here
        var entries [][]string

        // Lines of output
        lines := strings.Split(output, "\n")

        // Skip first and last line of output
        for _, str := range lines[1:len(lines)-1] {

            entries = append(entries, strings.Fields(str))
        }

        return json.Marshal(entries)
    })

    // Serve static files
    m.Get("/.*", martini.Static(""))

    m.Run()
}
<commit_msg>Refactor ps' output parsing to separate function<commit_after>package main

import (
    "os"
    "os/exec"

//    "log"

//    "fmt"
    "strings"
    "runtime"
//    "strconv" // For Itoa
//    "encoding/csv"
    "encoding/json"

    "github.com/go-martini/martini"
)

func main() {
    m := martini.Classic()

    // CPU count
    m.Get("/sh/numberofcores.php", func () ([]byte, error) {
        return json.Marshal(runtime.NumCPU())
    })

    // Server's hostname
    m.Get("/sh/hostname.php", func () ([]byte, error) {
        host, err := os.Hostname()

        if err != nil {
            return nil, err
        }

        return json.Marshal(host)
    })

    // PS
    m.Get("/sh/ps.php", func () ([]byte, error) {
        // Run uptime command
        rawOutput, err := exec.Command("ps", "aux").Output()

        if err != nil {
            return nil, err
        }

        return json.Marshal(parseCommandTable(rawOutput))
    })

    // Serve static files
    m.Get("/.*", martini.Static(""))

    m.Run()
}

func parseCommandTable(rawOutput []byte) [][]string {
    // Convert output to a string (it's not binary data, so this is ok)
    output := string(rawOutput[:])

    // We'll add all the parsed lines here
    var entries [][]string

    // Lines of output
    lines := strings.Split(output, "\n")

    // Skip first and last line of output
    for _, str := range lines[1:len(lines)-1] {
        entries = append(entries, strings.Fields(str))
    }

    return entries
}<|endoftext|>


<commit_before>package main

import (
        "flag"
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/davecgh/go-spew/spew"
)

var _ = fmt.Printf
var _ = spew.Dump

var config = &ZStackConfig{
	Hostname:    "localhost",
	OtasrvrPort:    2525,
	GatewayPort:    2541,
	NwkmgrPort:     2540,
	StableFlagFile: "/var/run/zigbee.stable",
}

func main() {

	flag.StringVar(&config.StableFlagFile, "zigbee-stable-file", "/var/run/zigbee.stable", "Location of zigbee.stable")
	flag.Parse()

	//spew.Dump(bus)

	driver := NewDriver()

	networkReady := make(chan bool)

	err := driver.Connect(config, networkReady)
	if err != nil {
		log.Fatalf("Failed to start ZigBee driver: %s", err)
	}

	//	driver.Reset(true)

	//driver.FetchDevices()

	/*err = driver.PermitJoin(120)
	if err != nil {
		log.Fatalf("Failed to enable joining: %s", err)
	}*/

	//	driver.Reset(true)

	//log.Println("Waiting!")

	//select {
	//case <-networkReady:
	log.Println("Driver starting!")
	driver.FetchDevices()
	err = driver.PermitJoin(120)
	if err != nil {
		log.Fatalf("Failed to enable joining: %s", err)
	}
	//case <-time.After(20 * time.Second):
	//log.Println("Timeout waiting for network ready. Soft-resetting cc2530")
	//driver.Reset(false)
	//os.Exit(0)
	//}

	/*

				toggleRequest := &gateway.DevSetOnOffStateReq{
					DstAddress: &gateway.GwAddressStructT{
						AddressType: gateway.GwAddressTypeT_UNICAST.Enum(),
						IeeeAddr:    device.IeeeAddress,
					},
					State: gateway.GwOnOffStateT_TOGGLE_STATE.Enum(),
				}

				confirmation := &gateway.GwZigbeeGenericCnf{}

				err = gatewayConn.SendCommand(toggleRequest, confirmation)
				if err != nil {
					log.Fatalf("Failed to toggle device: ", err)
				}
				log.Printf("Got on/off confirmation")
				if confirmation.Status.String() != "STATUS_SUCCESS" {
					log.Fatalf("Failed to request the device to toggle. Status:%s", confirmation.Status.String())
				}

				response := &gateway.GwZigbeeGenericRspInd{}
				err = gatewayConn.WaitForSequenceResponse(confirmation.SequenceNumber, response)
				if err != nil {
					log.Fatalf("Failed to get on/off response: ", err)
				}

				log.Printf("Got toggle response from device! Status: %s", response.Status.String())

				spew.Dump(response)

				powerRequest := &gateway.DevGetPowerReq{
					DstAddress: &gateway.GwAddressStructT{
						AddressType: gateway.GwAddressTypeT_UNICAST.Enum(),
						IeeeAddr:    device.IeeeAddress,
					},
				}

				confirmation = &gateway.GwZigbeeGenericCnf{}

				err = gatewayConn.SendCommand(powerRequest, confirmation)
				if err != nil {
					log.Fatalf("Failed to request power: ", err)
				}
				log.Printf("Got power request confirmation")
				if confirmation.Status.String() != "STATUS_SUCCESS" {
					log.Fatalf("Failed to request the power. Status:%s", confirmation.Status.String())
				}

				powerResponse := &gateway.DevGetPowerRspInd{}
				err = gatewayConn.WaitForSequenceResponse(confirmation.SequenceNumber, powerResponse)
				if err != nil {
					log.Fatalf("Failed to get power response: ", err)
				}

				spew.Dump(powerResponse)

	}*/

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}
<commit_msg>Tolerate parameters not in the flagset.<commit_after>package main

import (
        "flag"
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/davecgh/go-spew/spew"
)

var _ = fmt.Printf
var _ = spew.Dump

var config = &ZStackConfig{
	Hostname:    "localhost",
	OtasrvrPort:    2525,
	GatewayPort:    2541,
	NwkmgrPort:     2540,
	StableFlagFile: "/var/run/zigbee.stable",
}

func main() {

	flagset := flag.NewFlagSet("drive-go-zigbee", flag.ContinueOnError)
	flagset.StringVar(&config.StableFlagFile, "zigbee-stable-file", "/var/run/zigbee.stable", "Location of zigbee.stable")
	flagset.Parse(os.Args)

	//spew.Dump(bus)

	driver := NewDriver()

	networkReady := make(chan bool)

	err := driver.Connect(config, networkReady)
	if err != nil {
		log.Fatalf("Failed to start ZigBee driver: %s", err)
	}

	//	driver.Reset(true)

	//driver.FetchDevices()

	/*err = driver.PermitJoin(120)
	if err != nil {
		log.Fatalf("Failed to enable joining: %s", err)
	}*/

	//	driver.Reset(true)

	//log.Println("Waiting!")

	//select {
	//case <-networkReady:
	log.Println("Driver starting!")
	driver.FetchDevices()
	err = driver.PermitJoin(120)
	if err != nil {
		log.Fatalf("Failed to enable joining: %s", err)
	}
	//case <-time.After(20 * time.Second):
	//log.Println("Timeout waiting for network ready. Soft-resetting cc2530")
	//driver.Reset(false)
	//os.Exit(0)
	//}

	/*

				toggleRequest := &gateway.DevSetOnOffStateReq{
					DstAddress: &gateway.GwAddressStructT{
						AddressType: gateway.GwAddressTypeT_UNICAST.Enum(),
						IeeeAddr:    device.IeeeAddress,
					},
					State: gateway.GwOnOffStateT_TOGGLE_STATE.Enum(),
				}

				confirmation := &gateway.GwZigbeeGenericCnf{}

				err = gatewayConn.SendCommand(toggleRequest, confirmation)
				if err != nil {
					log.Fatalf("Failed to toggle device: ", err)
				}
				log.Printf("Got on/off confirmation")
				if confirmation.Status.String() != "STATUS_SUCCESS" {
					log.Fatalf("Failed to request the device to toggle. Status:%s", confirmation.Status.String())
				}

				response := &gateway.GwZigbeeGenericRspInd{}
				err = gatewayConn.WaitForSequenceResponse(confirmation.SequenceNumber, response)
				if err != nil {
					log.Fatalf("Failed to get on/off response: ", err)
				}

				log.Printf("Got toggle response from device! Status: %s", response.Status.String())

				spew.Dump(response)

				powerRequest := &gateway.DevGetPowerReq{
					DstAddress: &gateway.GwAddressStructT{
						AddressType: gateway.GwAddressTypeT_UNICAST.Enum(),
						IeeeAddr:    device.IeeeAddress,
					},
				}

				confirmation = &gateway.GwZigbeeGenericCnf{}

				err = gatewayConn.SendCommand(powerRequest, confirmation)
				if err != nil {
					log.Fatalf("Failed to request power: ", err)
				}
				log.Printf("Got power request confirmation")
				if confirmation.Status.String() != "STATUS_SUCCESS" {
					log.Fatalf("Failed to request the power. Status:%s", confirmation.Status.String())
				}

				powerResponse := &gateway.DevGetPowerRspInd{}
				err = gatewayConn.WaitForSequenceResponse(confirmation.SequenceNumber, powerResponse)
				if err != nil {
					log.Fatalf("Failed to get power response: ", err)
				}

				spew.Dump(powerResponse)

	}*/

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/linux/cmd"
	"github.com/stianeikeland/go-rpio"
)

var (
	current = 75
	desired = 75
	fanmode = "auto"
	sysmode = "off"

	fan  = rpio.Pin(17)
	cool = rpio.Pin(21)
	heat = rpio.Pin(22)

	exiting      = false
	disconnected = make(chan bool)
	peripheral   gatt.Peripheral

	DefaultClientOptions = []gatt.Option{
		gatt.LnxMaxConnections(1),
		gatt.LnxDeviceID(-1, true),
	}

	DefaultServerOptions = []gatt.Option{
		gatt.LnxMaxConnections(1),
		gatt.LnxDeviceID(-1, true),
		gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{
			AdvertisingIntervalMin: 0x00f4,
			AdvertisingIntervalMax: 0x00f4,
			AdvertisingChannelMap:  0x7,
		}),
	}
)

// API

type Thermostat struct {
	Current int    `json:"current"`
	Desired int    `json:"desired"`
	Sysmode string `json:"sysmode"`
	Fanmode string `json:"fanmode"`
}

func updateHandler(r *http.Request) int {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return http.StatusBadRequest
	}

	var t Thermostat
	err = json.Unmarshal(body, &t)
	if err != nil {
		return http.StatusBadRequest
	}

	if t.Desired > 60 && t.Desired < 85 {
		desired = t.Desired
	}
	if t.Current > 50 && t.Current < 90 {
		current = t.Current
	}
	sysmode = t.Sysmode
	fanmode = t.Fanmode
	updateState()
	return http.StatusOK
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	var code int

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

	switch r.Method {
	case "GET":
		code = http.StatusOK
	case "POST":
		code = updateHandler(r)
	case "OPTIONS":
		w.Header().Set("Allow", "GET,POST,OPTIONS")
		return
	default:
		code = http.StatusNotImplemented
	}

	response := Thermostat{current, desired, sysmode, fanmode}
	json, err := json.Marshal(response)
	if err != nil {
		http.Error(w, "Error marshalling JSON", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if code != 200 {
		http.Error(w, "", code)
	}
	w.Write(json)
	log.Printf("%s %s %s %d", r.RemoteAddr, r.Method, r.URL, 200)
}

// GPIO

func Start(p rpio.Pin) {
	p.Low()
}

func Stop(p rpio.Pin) {
	p.High()
}

// Sensor

func onStateChanged(d gatt.Device, s gatt.State) {
	log.Println("State:", s)
	switch s {
	case gatt.StatePoweredOn:
		log.Println("Scanning...")
		d.Scan([]gatt.UUID{}, false)
		return
	default:
		d.StopScanning()
	}
}

func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
	if !strings.Contains(a.LocalName, "SensorTag") {
		return
	}

	// Stop scanning once we've got the peripheral we're looking for.
	p.Device().StopScanning()

	log.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
	log.Println("  Local Name        =", a.LocalName)
	log.Println("  TX Power Level    =", a.TxPowerLevel)
	log.Println("  Manufacturer Data =", a.ManufacturerData)
	log.Println("  Service Data      =", a.ServiceData)
	log.Println("")

	p.Device().Connect(p)
}

func convertTemp(b []byte) int {
	rawdata := binary.LittleEndian.Uint16(b)
	return int((float32(rawdata)/4.0)*float32(0.03125)*(9.0/5.0) + 32)
}

func handleTempNotification(c *gatt.Characteristic, b []byte, err error) {
	t := convertTemp(b[2:4])
	if t > 50 && t < 90 { // sanity range
		current = t
		updateState()
	}
}

func onPeriphConnected(p gatt.Peripheral, err error) {
	log.Println("Connected")
	peripheral = p

	err = p.SetMTU(500)
	if err != nil {
		log.Printf("Failed to set MTU, err: %s\n", err)
	}

	uuid, err := gatt.ParseUUID("f000aa00-0451-4000-b000-000000000000")
	if err != nil {
		log.Printf("Failed to create UUID, err: %s\n", err)
		return
	}

	ss, err := p.DiscoverServices(nil)
	if err != nil {
		log.Printf("Failed to discover IR sensor service, err: %s\n", err)
		return
	}

	var s *gatt.Service
	for _, s = range ss {
		if s.UUID().Equal(uuid) {
			break
		}
	}

	cs, err := p.DiscoverCharacteristics(nil, s)
	if err != nil {
		log.Printf("Failed to discover characteristics, err: %s\n", err)
		return
	}

	for _, c := range cs {
		_, err = p.DiscoverDescriptors(nil, c)
		if err != nil {
			log.Printf("Failed to discover descriptors, err: %s\n", err)
			return
		}
	}

	data := cs[0]
	config := cs[1]
	samplerate := cs[2]

	// Turn on notifications
	err = p.SetNotifyValue(data, handleTempNotification)
	if err != nil {
		log.Printf("Failed to write config characteristics, err: %s\n", err)
		return
	}

	// Set sample rate
	err = p.WriteCharacteristic(samplerate, []byte{0xFF}, false)
	if err != nil {
		log.Printf("Failed to write samplerate characteristics, err: %s\n", err)
		return
	}

	// Start sensors
	err = p.WriteCharacteristic(config, []byte{0x01}, false)
	if err != nil {
		log.Printf("Failed to write config characteristics, err: %s\n", err)
		return
	}
}

func onPeriphDisconnected(p gatt.Peripheral, err error) {
	if exiting {
		disconnected <- true
		return
	}
	current = 0
	updateState()
	peripheral = nil
	log.Println("Connection lost, resuming scanning...")
	p.Device().Scan([]gatt.UUID{}, false)
}

func updateState() {
	log.Printf("current: %d, desired: %d, sysmode: %s, fanmode: %s\n", current, desired, sysmode, fanmode)
	if current == 0 {
		Stop(fan)
		Stop(cool)
		Stop(heat)
	}
	switch sysmode {
	case "off":
		Stop(heat)
		Stop(cool)
		if fanmode == "auto" {
			Stop(fan)
		}
	case "cool":
		if current > desired {
			Start(fan)
			Start(cool)
		} else if current < desired {
			if fanmode == "auto" {
				Stop(fan)
			}
			Stop(cool)
		}
		Stop(heat)
	case "heat":
		if current < desired {
			Start(fan)
			Start(heat)
		} else if current > desired {
			if fanmode == "auto" {
				Stop(fan)
			}
			Stop(heat)
		}
		Stop(cool)
	}

	if fanmode == "on" {
		Start(fan)
	}
	go func() {
		led := rpio.Pin(16)
		led.Output()
		led.Low()
		<-time.After(100 * time.Millisecond)
		led.High()
	}()
}

func main() {
	// Sensor
	d, err := gatt.NewDevice(DefaultClientOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s\n", err)
		return
	}

	d.Handle(
		gatt.PeripheralDiscovered(onPeriphDiscovered),
		gatt.PeripheralConnected(onPeriphConnected),
		gatt.PeripheralDisconnected(onPeriphDisconnected),
	)

	d.Init(onStateChanged)

	// API
	http.HandleFunc("/", apiHandler)
	go http.ListenAndServe(":8000", nil)

	// GPIO
	err = rpio.Open()
	if err != nil {
		log.Printf("Failed to open controller, err: %s\n", err)
		return
	}
	defer func() {
		for _, p := range []rpio.Pin{fan, cool, heat} {
			Stop(p)
		}
		rpio.Close()
	}()

	for _, p := range []rpio.Pin{fan, cool, heat} {
		p.Output()
		Stop(p)
	}

	done := make(chan bool)
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt)
		<-c
		done <- true
	}()
	<-done

	exiting = true
	if peripheral != nil {
		d.CancelConnection(peripheral)
		<-disconnected
	}
}
<commit_msg>fix state change when sensor is lost<commit_after>package main

import (
	"encoding/binary"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/linux/cmd"
	"github.com/stianeikeland/go-rpio"
)

var (
	current = 75
	desired = 75
	fanmode = "auto"
	sysmode = "off"

	fan  = rpio.Pin(17)
	cool = rpio.Pin(21)
	heat = rpio.Pin(22)

	exiting      = false
	disconnected = make(chan bool)
	peripheral   gatt.Peripheral

	DefaultClientOptions = []gatt.Option{
		gatt.LnxMaxConnections(1),
		gatt.LnxDeviceID(-1, true),
	}

	DefaultServerOptions = []gatt.Option{
		gatt.LnxMaxConnections(1),
		gatt.LnxDeviceID(-1, true),
		gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{
			AdvertisingIntervalMin: 0x00f4,
			AdvertisingIntervalMax: 0x00f4,
			AdvertisingChannelMap:  0x7,
		}),
	}
)

// API

type Thermostat struct {
	Current int    `json:"current"`
	Desired int    `json:"desired"`
	Sysmode string `json:"sysmode"`
	Fanmode string `json:"fanmode"`
}

func updateHandler(r *http.Request) int {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return http.StatusBadRequest
	}

	var t Thermostat
	err = json.Unmarshal(body, &t)
	if err != nil {
		return http.StatusBadRequest
	}

	if t.Desired > 60 && t.Desired < 85 {
		desired = t.Desired
	}
	if t.Current > 50 && t.Current < 90 {
		current = t.Current
	}
	sysmode = t.Sysmode
	fanmode = t.Fanmode
	updateState()
	return http.StatusOK
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	var code int

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

	switch r.Method {
	case "GET":
		code = http.StatusOK
	case "POST":
		code = updateHandler(r)
	case "OPTIONS":
		w.Header().Set("Allow", "GET,POST,OPTIONS")
		return
	default:
		code = http.StatusNotImplemented
	}

	response := Thermostat{current, desired, sysmode, fanmode}
	json, err := json.Marshal(response)
	if err != nil {
		http.Error(w, "Error marshalling JSON", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if code != 200 {
		http.Error(w, "", code)
	}
	w.Write(json)
	log.Printf("%s %s %s %d", r.RemoteAddr, r.Method, r.URL, 200)
}

// GPIO

func Start(p rpio.Pin) {
	p.Low()
}

func Stop(p rpio.Pin) {
	p.High()
}

// Sensor

func onStateChanged(d gatt.Device, s gatt.State) {
	log.Println("State:", s)
	switch s {
	case gatt.StatePoweredOn:
		log.Println("Scanning...")
		d.Scan([]gatt.UUID{}, false)
		return
	default:
		d.StopScanning()
	}
}

func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
	if !strings.Contains(a.LocalName, "SensorTag") {
		return
	}

	// Stop scanning once we've got the peripheral we're looking for.
	p.Device().StopScanning()

	log.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
	log.Println("  Local Name        =", a.LocalName)
	log.Println("  TX Power Level    =", a.TxPowerLevel)
	log.Println("  Manufacturer Data =", a.ManufacturerData)
	log.Println("  Service Data      =", a.ServiceData)
	log.Println("")

	p.Device().Connect(p)
}

func convertTemp(b []byte) int {
	rawdata := binary.LittleEndian.Uint16(b)
	return int((float32(rawdata)/4.0)*float32(0.03125)*(9.0/5.0) + 32)
}

func handleTempNotification(c *gatt.Characteristic, b []byte, err error) {
	t := convertTemp(b[2:4])
	if t > 50 && t < 90 { // sanity range
		current = t
		updateState()
	}
}

func onPeriphConnected(p gatt.Peripheral, err error) {
	log.Println("Connected")
	peripheral = p

	err = p.SetMTU(500)
	if err != nil {
		log.Printf("Failed to set MTU, err: %s\n", err)
	}

	uuid, err := gatt.ParseUUID("f000aa00-0451-4000-b000-000000000000")
	if err != nil {
		log.Printf("Failed to create UUID, err: %s\n", err)
		return
	}

	ss, err := p.DiscoverServices(nil)
	if err != nil {
		log.Printf("Failed to discover IR sensor service, err: %s\n", err)
		return
	}

	var s *gatt.Service
	for _, s = range ss {
		if s.UUID().Equal(uuid) {
			break
		}
	}

	cs, err := p.DiscoverCharacteristics(nil, s)
	if err != nil {
		log.Printf("Failed to discover characteristics, err: %s\n", err)
		return
	}

	for _, c := range cs {
		_, err = p.DiscoverDescriptors(nil, c)
		if err != nil {
			log.Printf("Failed to discover descriptors, err: %s\n", err)
			return
		}
	}

	data := cs[0]
	config := cs[1]
	samplerate := cs[2]

	// Turn on notifications
	err = p.SetNotifyValue(data, handleTempNotification)
	if err != nil {
		log.Printf("Failed to write config characteristics, err: %s\n", err)
		return
	}

	// Set sample rate
	err = p.WriteCharacteristic(samplerate, []byte{0xFF}, false)
	if err != nil {
		log.Printf("Failed to write samplerate characteristics, err: %s\n", err)
		return
	}

	// Start sensors
	err = p.WriteCharacteristic(config, []byte{0x01}, false)
	if err != nil {
		log.Printf("Failed to write config characteristics, err: %s\n", err)
		return
	}
}

func onPeriphDisconnected(p gatt.Peripheral, err error) {
	if exiting {
		disconnected <- true
		return
	}
	current = 0
	updateState()
	peripheral = nil
	log.Println("Connection lost, resuming scanning...")
	p.Device().Scan([]gatt.UUID{}, false)
}

func updateState() {
	if current == 0 {
		Stop(fan)
		Stop(cool)
		Stop(heat)
		return
	}
	log.Printf("current: %d, desired: %d, sysmode: %s, fanmode: %s\n", current, desired, sysmode, fanmode)
	switch sysmode {
	case "off":
		Stop(heat)
		Stop(cool)
		if fanmode == "auto" {
			Stop(fan)
		}
	case "cool":
		if current > desired {
			Start(fan)
			Start(cool)
		} else if current < desired {
			if fanmode == "auto" {
				Stop(fan)
			}
			Stop(cool)
		}
		Stop(heat)
	case "heat":
		if current < desired {
			Start(fan)
			Start(heat)
		} else if current > desired {
			if fanmode == "auto" {
				Stop(fan)
			}
			Stop(heat)
		}
		Stop(cool)
	}

	if fanmode == "on" {
		Start(fan)
	}
	go func() {
		led := rpio.Pin(16)
		led.Output()
		led.Low()
		<-time.After(100 * time.Millisecond)
		led.High()
	}()
}

func main() {
	// Sensor
	d, err := gatt.NewDevice(DefaultClientOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s\n", err)
		return
	}

	d.Handle(
		gatt.PeripheralDiscovered(onPeriphDiscovered),
		gatt.PeripheralConnected(onPeriphConnected),
		gatt.PeripheralDisconnected(onPeriphDisconnected),
	)

	d.Init(onStateChanged)

	// API
	http.HandleFunc("/", apiHandler)
	go http.ListenAndServe(":8000", nil)

	// GPIO
	err = rpio.Open()
	if err != nil {
		log.Printf("Failed to open controller, err: %s\n", err)
		return
	}
	defer func() {
		for _, p := range []rpio.Pin{fan, cool, heat} {
			Stop(p)
		}
		rpio.Close()
	}()

	for _, p := range []rpio.Pin{fan, cool, heat} {
		p.Output()
		Stop(p)
	}

	done := make(chan bool)
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt)
		<-c
		done <- true
	}()
	<-done

	exiting = true
	if peripheral != nil {
		d.CancelConnection(peripheral)
		<-disconnected
	}
}
<|endoftext|>


<commit_before>package main

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

	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/rs/cors"
	log "github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"github.com/urfave/negroni"
)

var Version string

func router(config clientConfig) *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/", indexHandler(config))
	r.Handle("/favicon.ico", http.NotFoundHandler())

	authMiddleware := negroni.HandlerFunc(ShotgunAuthMiddleware(config))

	entityRoutes := mux.NewRouter()
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").HandlerFunc(entityGetHandler(config)).Methods("GET")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").HandlerFunc(entityUpdateHandler(config)).Methods("PATCH")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").
		HandlerFunc(entityDeleteHandler(config)).Methods("DELETE")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}/revive").
		HandlerFunc(entityReviveHandler(config)).Methods("POST")
	// entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers").
	// 	HandlerFunc(entityGetFollowersHandler(config)).Methods("GET")
	//entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers").
	//             HandlerFunc(entityAddFollowersHandler(config)).Methods("POST")
	//entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers/{user_type}/{user_id:[0-9]+}").
	//		       HandlerFunc(entityDeleteFollowersHandler(config)).Methods("DELETE")
	entityRoutes.Path("/{entity_type}").HandlerFunc(entityGetAllHandler(config)).Methods("GET")
	entityRoutes.Path("/{entity_type}").HandlerFunc(entityCreateHandler(config)).Methods("POST")

	// Adds auth on the sub router so that / can be accessed freely.
	r.PathPrefix("/{entity_type}").Handler(negroni.New(
		authMiddleware,
		negroni.Wrap(entityRoutes),
	))

	return r
}

func main() {
	f, err := os.OpenFile("sg-restful.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		fmt.Printf("error opening file: %v", err)
	}

	// don't forget to close it
	defer f.Close()

	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stdout)
	log.SetFormatter(&log.TextFormatter{})

	app := cli.NewApp()
	app.Name = "sg-restful"
	app.Version = Version

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "port, p",
			Value:  "8000",
			Usage:  "Port to listen on",
			EnvVar: "PORT",
		},
		cli.StringFlag{
			Name:   "shotgun-host, s",
			Value:  "",
			Usage:  "Shotgun host",
			EnvVar: "SG_HOST",
		},
	}

	app.Action = func(c *cli.Context) {
		log.Infof("sg-restful Version: %v", Version)
		if c.String("shotgun-host") == "" {
			log.Fatalln("Shotgun host not set.")
		}
		log.Infof("Shotgun Host: %v", c.String("shotgun-host"))
		config := newClientConfig(Version, c.String("shotgun-host"))

		r := router(config)
		corsMiddleware := cors.AllowAll()

		n := negroni.Classic()
		n.Use(negronilogrus.NewMiddleware())
		n.Use(corsMiddleware)
		n.UseHandler(r)
		n.Run(":" + c.String("port"))
	}
	app.Run(os.Args)
}
<commit_msg>Registered all 3 query formats as active until I refactor the query parser code.<commit_after>package main

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

	"github.com/gorilla/mux"
	"github.com/meatballhat/negroni-logrus"
	"github.com/rs/cors"
	log "github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"github.com/urfave/negroni"
)

var Version string

func router(config clientConfig) *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/", indexHandler(config))
	r.Handle("/favicon.ico", http.NotFoundHandler())

	authMiddleware := negroni.HandlerFunc(ShotgunAuthMiddleware(config))

	entityRoutes := mux.NewRouter()
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").HandlerFunc(entityGetHandler(config)).Methods("GET")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").HandlerFunc(entityUpdateHandler(config)).Methods("PATCH")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}").
		HandlerFunc(entityDeleteHandler(config)).Methods("DELETE")
	entityRoutes.Path("/{entity_type}/{id:[0-9]+}/revive").
		HandlerFunc(entityReviveHandler(config)).Methods("POST")
	// entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers").
	// 	HandlerFunc(entityGetFollowersHandler(config)).Methods("GET")
	//entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers").
	//             HandlerFunc(entityAddFollowersHandler(config)).Methods("POST")
	//entityRoutes.Path("/{entity_type}/{id:[0-9]+}/followers/{user_type}/{user_id:[0-9]+}").
	//		       HandlerFunc(entityDeleteFollowersHandler(config)).Methods("DELETE")
	entityRoutes.Path("/{entity_type}").HandlerFunc(entityGetAllHandler(config)).Methods("GET")
	entityRoutes.Path("/{entity_type}").HandlerFunc(entityCreateHandler(config)).Methods("POST")

	// Adds auth on the sub router so that / can be accessed freely.
	r.PathPrefix("/{entity_type}").Handler(negroni.New(
		authMiddleware,
		negroni.Wrap(entityRoutes),
	))

	return r
}

func main() {
	f, err := os.OpenFile("sg-restful.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		fmt.Printf("error opening file: %v", err)
	}

	// don't forget to close it
	defer f.Close()

	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stdout)
	log.SetFormatter(&log.TextFormatter{})

	app := cli.NewApp()
	app.Name = "sg-restful"
	app.Version = Version

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "port, p",
			Value:  "8000",
			Usage:  "Port to listen on",
			EnvVar: "PORT",
		},
		cli.StringFlag{
			Name:   "shotgun-host, s",
			Value:  "",
			Usage:  "Shotgun host",
			EnvVar: "SG_HOST",
		},
	}

	app.Action = func(c *cli.Context) {
		log.Infof("sg-restful Version: %v", Version)
		if c.String("shotgun-host") == "" {
			log.Fatalln("Shotgun host not set.")
		}
		log.Infof("Shotgun Host: %v", c.String("shotgun-host"))
		config := newClientConfig(Version, c.String("shotgun-host"))

		qpm := GetQPManager()
		qpm.SetActiveParsers("format1", "format2", "format3")

		r := router(config)
		corsMiddleware := cors.AllowAll()

		n := negroni.Classic()
		n.Use(negronilogrus.NewMiddleware())
		n.Use(corsMiddleware)
		n.UseHandler(r)
		n.Run(":" + c.String("port"))
	}
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecs"
)

var (
	clusterName = flag.String("c", "", "Cluster name to deploy to")
	repoName    = flag.String("i", "", "Container repo to pull from e.g. quay.io/username/reponame")
	appName     = flag.String("a", "", "Application name")
	environment = flag.String("e", "", "Application environment, e.g. production")
	sha         = flag.String("s", "", "Tag, usually short git SHA to deploy")
	region      = flag.String("r", "", "AWS region")
	webhook     = flag.String("w", "", "Webhook (slack) URL to post to")
	debug       = flag.Bool("d", false, "enable Debug output")
)

func fail(s string) {
	fmt.Printf(s)
	webhookFunc(s)
	os.Exit(2)
}

func webhookFunc(s string) {
	if *webhook == "" {
		return
	}

	json, _ := json.Marshal(
		struct {
			Text     string `json:"text"`
			Username string `json:"username"`
		}{
			s,
			"GO ECS Deploy",
		},
	)

	reader := bytes.NewReader(json)
	http.Post(*webhook, "application/json", reader)
}

func main() {
	flag.Parse()

	if *clusterName == "" || *repoName == "" || *appName == "" || *environment == "" || *sha == "" || *region == "" {
		flag.Usage()
		fail(fmt.Sprintf("Failed deployment %s \n`bad paramaters`", *appName))
	}

	serviceName := *appName + "-" + *environment

	svc := ecs.New(session.New(), &aws.Config{Region: aws.String(*region)})

	fmt.Printf("Request to deploy sha: %s to %s at %s \n", *sha, *environment, *region)
	fmt.Printf("Describing services for cluster %s and service %s \n", *clusterName, serviceName)

	serviceDesc, err :=
		svc.DescribeServices(
			&ecs.DescribeServicesInput{
				Cluster:  clusterName,
				Services: []*string{&serviceName},
			})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s \n`%s`", *appName, err.Error()))
	}

	if len(serviceDesc.Services) < 1 {
		msg := fmt.Sprintf("No service %s found on cluster %s", serviceName, *clusterName)
		fail("Failed: " + msg)
	}

	service := serviceDesc.Services[0]
	if serviceName != *service.ServiceName {
		msg := fmt.Sprintf("Found the wrong service when looking for %s found %s \n", serviceName, *service.ServiceName)
		fail("Failed: " + msg)
	}

	fmt.Printf("Found existing ARN %s for service %s \n", *service.ClusterArn, *service.ServiceName)

	taskDesc, err :=
		svc.DescribeTaskDefinition(
			&ecs.DescribeTaskDefinitionInput{
				TaskDefinition: service.TaskDefinition})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s \n`%s`", *appName, err.Error()))
	}

	if *debug {
		fmt.Printf("Current task description: \n%+v \n", taskDesc)
	}

	containerDef := taskDesc.TaskDefinition.ContainerDefinitions[0]
	oldImage := containerDef.Image
	{
		x := fmt.Sprintf("%s:%s", *repoName, *sha)
		containerDef.Image = &x
	}

	futureDef := &ecs.RegisterTaskDefinitionInput{
		ContainerDefinitions: taskDesc.TaskDefinition.ContainerDefinitions,
		Family:               taskDesc.TaskDefinition.Family,
		Volumes:              taskDesc.TaskDefinition.Volumes,
		NetworkMode:          taskDesc.TaskDefinition.NetworkMode,
		TaskRoleArn:          taskDesc.TaskDefinition.TaskRoleArn,
	}

	if *debug {
		fmt.Printf("Future task description: \n%+v \n", futureDef)
	}

	registerRes, err :=
		svc.RegisterTaskDefinition(futureDef)
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s for %s to %s \n`%s`", *containerDef.Image, *appName, *clusterName, err.Error()))
	}

	newArn := registerRes.TaskDefinition.TaskDefinitionArn

	fmt.Printf("Registered new task for %s:%s \n", *sha, *newArn)

	// update service to use new definition
	_, err = svc.UpdateService(
		&ecs.UpdateServiceInput{
			Cluster:        clusterName,
			Service:        &serviceName,
			DesiredCount:   service.DesiredCount,
			TaskDefinition: newArn,
		})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s for %s to %s as %s \n`%s`", *containerDef.Image, *serviceName, *clusterName, *newArn, err.Error()))
	}

	slackMsg := fmt.Sprintf("Deployed %s for *%s* to *%s* as `%s`", *containerDef.Image, *serviceName, *clusterName, *newArn)

	// extract old image sha, and use it to generate a git compare URL
	if *oldImage != "" {
		parts := strings.FieldsFunc(*oldImage, func(r rune) bool { return strings.ContainsRune(":-", r) })
		if len(parts) >= 2 {
			if gitURL, err := gitURL(parts[1], *sha); err == nil {
				slackMsg += " (<" + gitURL + "|diff>)"
			}
		}
	}
	webhookFunc(slackMsg)

	fmt.Printf("Updated %s service to use new ARN: %s \n", serviceName, *newArn)

}

// gitURL uses git since the program runs in many CI environments
func gitURL(startSHA string, endSHA string) (string, error) {
	var project string

	if travisSlug, ok := os.LookupEnv("TRAVIS_REPO_SLUG"); ok {
		project = travisSlug
	}

	if werckerOwner, ok := os.LookupEnv("WERCKER_GIT_OWNER"); ok {
		if werckerRepo, ok := os.LookupEnv("WERCKER_GIT_REPOSITORY"); ok {
			project = werckerOwner + "/" + werckerRepo
		}
	}

	if project == "" {
		return "", errors.New("nope")
	}

	url := "https://github.com/" + project + "/compare/" + startSHA + "..." + endSHA
	return url, nil
}
<commit_msg>Revert serviceName use on messages<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecs"
)

var (
	clusterName = flag.String("c", "", "Cluster name to deploy to")
	repoName    = flag.String("i", "", "Container repo to pull from e.g. quay.io/username/reponame")
	appName     = flag.String("a", "", "Application name")
	environment = flag.String("e", "", "Application environment, e.g. production")
	sha         = flag.String("s", "", "Tag, usually short git SHA to deploy")
	region      = flag.String("r", "", "AWS region")
	webhook     = flag.String("w", "", "Webhook (slack) URL to post to")
	debug       = flag.Bool("d", false, "enable Debug output")
)

func fail(s string) {
	fmt.Printf(s)
	webhookFunc(s)
	os.Exit(2)
}

func webhookFunc(s string) {
	if *webhook == "" {
		return
	}

	json, _ := json.Marshal(
		struct {
			Text     string `json:"text"`
			Username string `json:"username"`
		}{
			s,
			"GO ECS Deploy",
		},
	)

	reader := bytes.NewReader(json)
	http.Post(*webhook, "application/json", reader)
}

func main() {
	flag.Parse()

	if *clusterName == "" || *repoName == "" || *appName == "" || *environment == "" || *sha == "" || *region == "" {
		flag.Usage()
		fail(fmt.Sprintf("Failed deployment %s \n`bad paramaters`", *appName))
	}

	serviceName := *appName + "-" + *environment

	svc := ecs.New(session.New(), &aws.Config{Region: aws.String(*region)})

	fmt.Printf("Request to deploy sha: %s to %s at %s \n", *sha, *environment, *region)
	fmt.Printf("Describing services for cluster %s and service %s \n", *clusterName, serviceName)

	serviceDesc, err :=
		svc.DescribeServices(
			&ecs.DescribeServicesInput{
				Cluster:  clusterName,
				Services: []*string{&serviceName},
			})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s \n`%s`", *appName, err.Error()))
	}

	if len(serviceDesc.Services) < 1 {
		msg := fmt.Sprintf("No service %s found on cluster %s", serviceName, *clusterName)
		fail("Failed: " + msg)
	}

	service := serviceDesc.Services[0]
	if serviceName != *service.ServiceName {
		msg := fmt.Sprintf("Found the wrong service when looking for %s found %s \n", serviceName, *service.ServiceName)
		fail("Failed: " + msg)
	}

	fmt.Printf("Found existing ARN %s for service %s \n", *service.ClusterArn, *service.ServiceName)

	taskDesc, err :=
		svc.DescribeTaskDefinition(
			&ecs.DescribeTaskDefinitionInput{
				TaskDefinition: service.TaskDefinition})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s \n`%s`", *appName, err.Error()))
	}

	if *debug {
		fmt.Printf("Current task description: \n%+v \n", taskDesc)
	}

	containerDef := taskDesc.TaskDefinition.ContainerDefinitions[0]
	oldImage := containerDef.Image
	{
		x := fmt.Sprintf("%s:%s", *repoName, *sha)
		containerDef.Image = &x
	}

	futureDef := &ecs.RegisterTaskDefinitionInput{
		ContainerDefinitions: taskDesc.TaskDefinition.ContainerDefinitions,
		Family:               taskDesc.TaskDefinition.Family,
		Volumes:              taskDesc.TaskDefinition.Volumes,
		NetworkMode:          taskDesc.TaskDefinition.NetworkMode,
		TaskRoleArn:          taskDesc.TaskDefinition.TaskRoleArn,
	}

	if *debug {
		fmt.Printf("Future task description: \n%+v \n", futureDef)
	}

	registerRes, err :=
		svc.RegisterTaskDefinition(futureDef)
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s for %s to %s \n`%s`", *containerDef.Image, *appName, *clusterName, err.Error()))
	}

	newArn := registerRes.TaskDefinition.TaskDefinitionArn

	fmt.Printf("Registered new task for %s:%s \n", *sha, *newArn)

	// update service to use new definition
	_, err = svc.UpdateService(
		&ecs.UpdateServiceInput{
			Cluster:        clusterName,
			Service:        &serviceName,
			DesiredCount:   service.DesiredCount,
			TaskDefinition: newArn,
		})
	if err != nil {
		fail(fmt.Sprintf("Failed: deployment %s for %s to %s as %s \n`%s`", *containerDef.Image, *appName, *clusterName, *newArn, err.Error()))
	}

	slackMsg := fmt.Sprintf("Deployed %s for *%s* to *%s* as `%s`", *containerDef.Image, *appName, *clusterName, *newArn)

	// extract old image sha, and use it to generate a git compare URL
	if *oldImage != "" {
		parts := strings.FieldsFunc(*oldImage, func(r rune) bool { return strings.ContainsRune(":-", r) })
		if len(parts) >= 2 {
			if gitURL, err := gitURL(parts[1], *sha); err == nil {
				slackMsg += " (<" + gitURL + "|diff>)"
			}
		}
	}
	webhookFunc(slackMsg)

	fmt.Printf("Updated %s service to use new ARN: %s \n", serviceName, *newArn)

}

// gitURL uses git since the program runs in many CI environments
func gitURL(startSHA string, endSHA string) (string, error) {
	var project string

	if travisSlug, ok := os.LookupEnv("TRAVIS_REPO_SLUG"); ok {
		project = travisSlug
	}

	if werckerOwner, ok := os.LookupEnv("WERCKER_GIT_OWNER"); ok {
		if werckerRepo, ok := os.LookupEnv("WERCKER_GIT_REPOSITORY"); ok {
			project = werckerOwner + "/" + werckerRepo
		}
	}

	if project == "" {
		return "", errors.New("nope")
	}

	url := "https://github.com/" + project + "/compare/" + startSHA + "..." + endSHA
	return url, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/log"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/go-xamarin/builder"
	"github.com/bitrise-tools/go-xamarin/constants"
	"github.com/bitrise-tools/go-xamarin/tools"
	shellquote "github.com/kballard/go-shellquote"
)

// ConfigsModel ...
type ConfigsModel struct {
	XamarinSolution      string
	XamarinConfiguration string
	XamarinPlatform      string
	ProjectTypeWhitelist string

	AndroidCustomOptions string
	IOSCustomOptions     string
	TvOSCustomOptions    string
	MacOSCustomOptions   string
	ForceMDTool          string

	DeployDir string
}

func createConfigsModelFromEnvs() ConfigsModel {
	return ConfigsModel{
		XamarinSolution:      os.Getenv("xamarin_solution"),
		XamarinConfiguration: os.Getenv("xamarin_configuration"),
		XamarinPlatform:      os.Getenv("xamarin_platform"),
		ProjectTypeWhitelist: os.Getenv("project_type_whitelist"),

		AndroidCustomOptions: os.Getenv("android_build_command_custom_options"),
		IOSCustomOptions:     os.Getenv("ios_build_command_custom_options"),
		TvOSCustomOptions:    os.Getenv("tvos_build_command_custom_options"),
		MacOSCustomOptions:   os.Getenv("macos_build_command_custom_options"),
		ForceMDTool:          os.Getenv("force_mdtool"),

		DeployDir: os.Getenv("BITRISE_DEPLOY_DIR"),
	}
}

func (configs ConfigsModel) print() {
	log.Info("Configs:")

	log.Detail("- XamarinSolution: %s", configs.XamarinSolution)
	log.Detail("- XamarinConfiguration: %s", configs.XamarinConfiguration)
	log.Detail("- XamarinPlatform: %s", configs.XamarinPlatform)
	log.Detail("- ProjectTypeWhitelist: %s", configs.ProjectTypeWhitelist)

	log.Info("Experimental Configs:")

	log.Detail("- AndroidCustomOptions: %s", configs.AndroidCustomOptions)
	log.Detail("- IOSCustomOptions: %s", configs.IOSCustomOptions)
	log.Detail("- TvOSCustomOptions: %s", configs.TvOSCustomOptions)
	log.Detail("- MacOSCustomOptions: %s", configs.MacOSCustomOptions)
	log.Detail("- ForceMDTool: %s", configs.ForceMDTool)

	log.Info("Other Configs:")

	log.Detail("- DeployDir: %s", configs.DeployDir)
}

func (configs ConfigsModel) validate() error {
	if configs.XamarinSolution == "" {
		return errors.New("No XamarinSolution parameter specified!")
	}
	if exist, err := pathutil.IsPathExists(configs.XamarinSolution); err != nil {
		return fmt.Errorf("Failed to check if XamarinSolution exist at: %s, error: %s", configs.XamarinSolution, err)
	} else if !exist {
		return fmt.Errorf("XamarinSolution not exist at: %s", configs.XamarinSolution)
	}

	if configs.XamarinConfiguration == "" {
		return errors.New("No XamarinConfiguration parameter specified!")
	}

	if configs.XamarinPlatform == "" {
		return errors.New("No XamarinPlatform parameter specified!")
	}

	return nil
}

func exportEnvironmentWithEnvman(keyStr, valueStr string) error {
	cmd := cmdex.NewCommand("envman", "add", "--key", keyStr)
	cmd.SetStdin(strings.NewReader(valueStr))
	return cmd.Run()
}

func exportZipedArtifactDir(pth, deployDir, envKey string) (string, error) {
	parentDir := filepath.Dir(pth)
	dirName := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, dirName+".zip")
	cmd := cmdex.NewCommand("/usr/bin/zip", "-rTy", deployPth, dirName)
	cmd.SetDir(parentDir)
	out, err := cmd.RunAndReturnTrimmedCombinedOutput()
	if err != nil {
		return "", fmt.Errorf("Failed to zip dir: %s, output: %s, error: %s", pth, out, err)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func exportArtifactDir(pth, deployDir, envKey string) (string, error) {
	base := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, base)

	if err := cmdex.CopyDir(pth, deployDir, false); err != nil {
		return "", fmt.Errorf("Failed to move artifact (%s) to (%s)", pth, deployDir)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func exportArtifactFile(pth, deployDir, envKey string) (string, error) {
	base := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, base)

	if err := cmdex.CopyFile(pth, deployPth); err != nil {
		return "", fmt.Errorf("Failed to move artifact (%s) to (%s)", pth, deployPth)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		fmt.Println()
		log.Error("Issue with input: %s", err)
		fmt.Println()

		os.Exit(1)
	}

	// parse project type filters
	projectTypeWhitelist := []constants.SDK{}
	if len(configs.ProjectTypeWhitelist) > 0 {
		split := strings.Split(configs.ProjectTypeWhitelist, ",")

		for _, item := range split {
			item := strings.TrimSpace(item)
			if item == "" {
				continue
			}

			projectType, err := constants.ParseSDK(item)
			if err != nil {
				log.Error("Failed to parse project type (%s), error: %s", item, err)
				os.Exit(1)
			}

			projectTypeWhitelist = append(projectTypeWhitelist, projectType)
		}
	}
	// ---

	// prepare custom options
	projectTypeCustomOptions := map[constants.SDK][]string{}
	projectTypeRawCustomOptions := map[constants.SDK]string{
		constants.SDKAndroid: configs.AndroidCustomOptions,
		constants.SDKIOS:     configs.IOSCustomOptions,
		constants.SDKTvOS:    configs.TvOSCustomOptions,
		constants.SDKMacOS:   configs.MacOSCustomOptions,
	}
	for projectType, rawOptions := range projectTypeRawCustomOptions {
		if rawOptions == "" {
			continue
		}

		split, err := shellquote.Split(rawOptions)
		if err != nil {
			log.Error("Failed to split options (%s), error: %s", err)
		}
		projectTypeCustomOptions[projectType] = split
	}
	// ---

	//
	// build
	fmt.Println()
	log.Info("Building all projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, projectTypeWhitelist, (configs.ForceMDTool == "yes"))
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)
		os.Exit(1)
	}

	prepareCallback := func(solutionName string, projectName string, sdk constants.SDK, testFramwork constants.TestFramework, command *tools.Editable) {
		options, ok := projectTypeCustomOptions[sdk]
		if ok {
			(*command).SetCustomOptions(options...)
		}
	}

	callback := func(solutionName string, projectName string, sdk constants.SDK, testFramwork constants.TestFramework, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		log.Info("Building project: %s", projectName)
		log.Done("$ %s", commandStr)
		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}
		fmt.Println()
	}

	warnings, err := builder.BuildAllProjects(configs.XamarinConfiguration, configs.XamarinPlatform, prepareCallback, callback)
	if len(warnings) > 0 {
		log.Warn("Build warnings:")
		for _, warning := range warnings {
			log.Warn(warning)
		}
	}
	if err != nil {
		log.Error("Build failed, error: %s", err)
		os.Exit(1)
	}

	output, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	if err != nil {
		log.Error("Failed to collect output, error: %s", err)
		os.Exit(1)
	}
	// ---

	// Export outputs
	fmt.Println()
	log.Info("Exporting generated outputs...")

	for projectName, projectOutput := range output {
		fmt.Println()
		log.Info("%s outputs:", projectName)

		for _, output := range projectOutput.Outputs {
			// Android outputs
			if projectOutput.ProjectType == constants.SDKAndroid && output.OutputType == constants.OutputTypeAPK {
				envKey := "BITRISE_APK_PATH"
				pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
				if err != nil {
					log.Error("Failed to export apk, error: %s", err)
					os.Exit(1)
				}
				log.Done("apk path (%s) is available in (%s) environment variable", pth, envKey)
			}

			// IOS outputs
			if projectOutput.ProjectType == constants.SDKIOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeIPA {
					envKey := "BITRISE_IPA_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export ipa, error: %s", err)
						os.Exit(1)
					}
					log.Done("ipa path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeDSYM {
					envKey := "BITRISE_DSYM_PATH"
					pth, err := exportZipedArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export dsym, error: %s", err)
						os.Exit(1)
					}
					log.Done("dsym path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}

			// TvOS outputs
			if projectOutput.ProjectType == constants.SDKTvOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_TVOS_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeIPA {
					envKey := "BITRISE_TVOS_IPA_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export ipa, error: %s", err)
						os.Exit(1)
					}
					log.Done("ipa path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeDSYM {
					envKey := "BITRISE_TVOS_DSYM_PATH"
					pth, err := exportZipedArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export dsym, error: %s", err)
						os.Exit(1)
					}
					log.Done("dsym path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}

			// MacOS outputs
			if projectOutput.ProjectType == constants.SDKMacOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_MACOS_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeAPP {
					envKey := "BITRISE_MACOS_APP_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("app path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypePKG {
					envKey := "BITRISE_MACOS_PKG_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export pkg, error: %s", err)
						os.Exit(1)
					}
					log.Done("pkg path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}
		}
	}
	// ---
}
<commit_msg>golint fixes<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/log"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-tools/go-xamarin/builder"
	"github.com/bitrise-tools/go-xamarin/constants"
	"github.com/bitrise-tools/go-xamarin/tools"
	shellquote "github.com/kballard/go-shellquote"
)

// ConfigsModel ...
type ConfigsModel struct {
	XamarinSolution      string
	XamarinConfiguration string
	XamarinPlatform      string
	ProjectTypeWhitelist string

	AndroidCustomOptions string
	IOSCustomOptions     string
	TvOSCustomOptions    string
	MacOSCustomOptions   string
	ForceMDTool          string

	DeployDir string
}

func createConfigsModelFromEnvs() ConfigsModel {
	return ConfigsModel{
		XamarinSolution:      os.Getenv("xamarin_solution"),
		XamarinConfiguration: os.Getenv("xamarin_configuration"),
		XamarinPlatform:      os.Getenv("xamarin_platform"),
		ProjectTypeWhitelist: os.Getenv("project_type_whitelist"),

		AndroidCustomOptions: os.Getenv("android_build_command_custom_options"),
		IOSCustomOptions:     os.Getenv("ios_build_command_custom_options"),
		TvOSCustomOptions:    os.Getenv("tvos_build_command_custom_options"),
		MacOSCustomOptions:   os.Getenv("macos_build_command_custom_options"),
		ForceMDTool:          os.Getenv("force_mdtool"),

		DeployDir: os.Getenv("BITRISE_DEPLOY_DIR"),
	}
}

func (configs ConfigsModel) print() {
	log.Info("Configs:")

	log.Detail("- XamarinSolution: %s", configs.XamarinSolution)
	log.Detail("- XamarinConfiguration: %s", configs.XamarinConfiguration)
	log.Detail("- XamarinPlatform: %s", configs.XamarinPlatform)
	log.Detail("- ProjectTypeWhitelist: %s", configs.ProjectTypeWhitelist)

	log.Info("Experimental Configs:")

	log.Detail("- AndroidCustomOptions: %s", configs.AndroidCustomOptions)
	log.Detail("- IOSCustomOptions: %s", configs.IOSCustomOptions)
	log.Detail("- TvOSCustomOptions: %s", configs.TvOSCustomOptions)
	log.Detail("- MacOSCustomOptions: %s", configs.MacOSCustomOptions)
	log.Detail("- ForceMDTool: %s", configs.ForceMDTool)

	log.Info("Other Configs:")

	log.Detail("- DeployDir: %s", configs.DeployDir)
}

func (configs ConfigsModel) validate() error {
	if configs.XamarinSolution == "" {
		return errors.New("no XamarinSolution parameter specified")
	}
	if exist, err := pathutil.IsPathExists(configs.XamarinSolution); err != nil {
		return fmt.Errorf("failed to check if XamarinSolution exist at: %s, error: %s", configs.XamarinSolution, err)
	} else if !exist {
		return fmt.Errorf("XamarinSolution not exist at: %s", configs.XamarinSolution)
	}

	if configs.XamarinConfiguration == "" {
		return errors.New("no XamarinConfiguration parameter specified")
	}

	if configs.XamarinPlatform == "" {
		return errors.New("no XamarinPlatform parameter specified")
	}

	return nil
}

func exportEnvironmentWithEnvman(keyStr, valueStr string) error {
	cmd := cmdex.NewCommand("envman", "add", "--key", keyStr)
	cmd.SetStdin(strings.NewReader(valueStr))
	return cmd.Run()
}

func exportZipedArtifactDir(pth, deployDir, envKey string) (string, error) {
	parentDir := filepath.Dir(pth)
	dirName := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, dirName+".zip")
	cmd := cmdex.NewCommand("/usr/bin/zip", "-rTy", deployPth, dirName)
	cmd.SetDir(parentDir)
	out, err := cmd.RunAndReturnTrimmedCombinedOutput()
	if err != nil {
		return "", fmt.Errorf("Failed to zip dir: %s, output: %s, error: %s", pth, out, err)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func exportArtifactDir(pth, deployDir, envKey string) (string, error) {
	base := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, base)

	if err := cmdex.CopyDir(pth, deployDir, false); err != nil {
		return "", fmt.Errorf("Failed to move artifact (%s) to (%s)", pth, deployDir)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func exportArtifactFile(pth, deployDir, envKey string) (string, error) {
	base := filepath.Base(pth)
	deployPth := filepath.Join(deployDir, base)

	if err := cmdex.CopyFile(pth, deployPth); err != nil {
		return "", fmt.Errorf("Failed to move artifact (%s) to (%s)", pth, deployPth)
	}

	if err := exportEnvironmentWithEnvman(envKey, deployPth); err != nil {
		return "", fmt.Errorf("Failed to export artifact path (%s) into (%s)", deployPth, envKey)
	}

	return deployPth, nil
}

func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		fmt.Println()
		log.Error("Issue with input: %s", err)
		fmt.Println()

		os.Exit(1)
	}

	// parse project type filters
	projectTypeWhitelist := []constants.SDK{}
	if len(configs.ProjectTypeWhitelist) > 0 {
		split := strings.Split(configs.ProjectTypeWhitelist, ",")

		for _, item := range split {
			item := strings.TrimSpace(item)
			if item == "" {
				continue
			}

			projectType, err := constants.ParseSDK(item)
			if err != nil {
				log.Error("Failed to parse project type (%s), error: %s", item, err)
				os.Exit(1)
			}

			projectTypeWhitelist = append(projectTypeWhitelist, projectType)
		}
	}
	// ---

	// prepare custom options
	projectTypeCustomOptions := map[constants.SDK][]string{}
	projectTypeRawCustomOptions := map[constants.SDK]string{
		constants.SDKAndroid: configs.AndroidCustomOptions,
		constants.SDKIOS:     configs.IOSCustomOptions,
		constants.SDKTvOS:    configs.TvOSCustomOptions,
		constants.SDKMacOS:   configs.MacOSCustomOptions,
	}
	for projectType, rawOptions := range projectTypeRawCustomOptions {
		if rawOptions == "" {
			continue
		}

		split, err := shellquote.Split(rawOptions)
		if err != nil {
			log.Error("Failed to split options (%s), error: %s", err)
		}
		projectTypeCustomOptions[projectType] = split
	}
	// ---

	//
	// build
	fmt.Println()
	log.Info("Building all projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, projectTypeWhitelist, (configs.ForceMDTool == "yes"))
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)
		os.Exit(1)
	}

	prepareCallback := func(solutionName string, projectName string, sdk constants.SDK, testFramwork constants.TestFramework, command *tools.Editable) {
		options, ok := projectTypeCustomOptions[sdk]
		if ok {
			(*command).SetCustomOptions(options...)
		}
	}

	callback := func(solutionName string, projectName string, sdk constants.SDK, testFramwork constants.TestFramework, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		log.Info("Building project: %s", projectName)
		log.Done("$ %s", commandStr)
		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}
		fmt.Println()
	}

	warnings, err := builder.BuildAllProjects(configs.XamarinConfiguration, configs.XamarinPlatform, prepareCallback, callback)
	if len(warnings) > 0 {
		log.Warn("Build warnings:")
		for _, warning := range warnings {
			log.Warn(warning)
		}
	}
	if err != nil {
		log.Error("Build failed, error: %s", err)
		os.Exit(1)
	}

	output, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	if err != nil {
		log.Error("Failed to collect output, error: %s", err)
		os.Exit(1)
	}
	// ---

	// Export outputs
	fmt.Println()
	log.Info("Exporting generated outputs...")

	for projectName, projectOutput := range output {
		fmt.Println()
		log.Info("%s outputs:", projectName)

		for _, output := range projectOutput.Outputs {
			// Android outputs
			if projectOutput.ProjectType == constants.SDKAndroid && output.OutputType == constants.OutputTypeAPK {
				envKey := "BITRISE_APK_PATH"
				pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
				if err != nil {
					log.Error("Failed to export apk, error: %s", err)
					os.Exit(1)
				}
				log.Done("apk path (%s) is available in (%s) environment variable", pth, envKey)
			}

			// IOS outputs
			if projectOutput.ProjectType == constants.SDKIOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeIPA {
					envKey := "BITRISE_IPA_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export ipa, error: %s", err)
						os.Exit(1)
					}
					log.Done("ipa path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeDSYM {
					envKey := "BITRISE_DSYM_PATH"
					pth, err := exportZipedArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export dsym, error: %s", err)
						os.Exit(1)
					}
					log.Done("dsym path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}

			// TvOS outputs
			if projectOutput.ProjectType == constants.SDKTvOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_TVOS_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeIPA {
					envKey := "BITRISE_TVOS_IPA_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export ipa, error: %s", err)
						os.Exit(1)
					}
					log.Done("ipa path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeDSYM {
					envKey := "BITRISE_TVOS_DSYM_PATH"
					pth, err := exportZipedArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export dsym, error: %s", err)
						os.Exit(1)
					}
					log.Done("dsym path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}

			// MacOS outputs
			if projectOutput.ProjectType == constants.SDKMacOS {
				if output.OutputType == constants.OutputTypeXCArchive {
					envKey := "BITRISE_MACOS_XCARCHIVE_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("xcarchive path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypeAPP {
					envKey := "BITRISE_MACOS_APP_PATH"
					pth, err := exportArtifactDir(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export xcarchive, error: %s", err)
						os.Exit(1)
					}
					log.Done("app path (%s) is available in (%s) environment variable", pth, envKey)
				}

				if output.OutputType == constants.OutputTypePKG {
					envKey := "BITRISE_MACOS_PKG_PATH"
					pth, err := exportArtifactFile(output.Pth, configs.DeployDir, envKey)
					if err != nil {
						log.Error("Failed to export pkg, error: %s", err)
						os.Exit(1)
					}
					log.Done("pkg path (%s) is available in (%s) environment variable", pth, envKey)
				}
			}
		}
	}
	// ---
}
<|endoftext|>


<commit_before>// CLI testing package for the Go language.
//
// Developing a command line application? Wanna be able to test your app from the
// outside? If the answer is Yes to at least one of the questions, keep reading.
//
// `testcli` is a wrapper around os/exec to test CLI apps in Go lang,
// minimalistic, so you can do your tests with `testing` or any other testing
// framework.
package testcli

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

type output struct {
	content string
	mu      *sync.Mutex
}

// Cmd is typically constructed through the Command() call and provides state
// to the execution engine.
type Cmd struct {
	cmd       *exec.Cmd
	env       []string
	exitError error
	status    string
	stdout    *output
	stderr    *output
	stdin     io.Reader
}

// ErrUninitializedCmd is returned when members are accessed before a run, that
// can only be used after a command has been run.
var ErrUninitializedCmd = errors.New("You need to run this command first")

// ErrCmdNotFinished is returned when members are accessed before or during a run,
// that can only be used after a command has finished executing.
var ErrCmdNotFinished = errors.New("Command is still executing")

const (
	// INITIALIZED represents the state of Command before it's started with Run() or Start()
	INITIALIZED = "initialized"
	// RUNNING represents the state of Command while it's running
	RUNNING = "running"
	// FINISHED represents the state of Command after it has exited successfully or not
	FINISHED = "finished"
)

var pkgCmd = &Cmd{
	status: INITIALIZED,
	stdout: &output{mu: &sync.Mutex{}},
	stderr: &output{mu: &sync.Mutex{}},
}

// Command constructs a *Cmd. It is passed the command name and arguments.
func Command(name string, arg ...string) *Cmd {
	return &Cmd{
		cmd:    exec.Command(name, arg...),
		status: INITIALIZED,
		stdout: &output{mu: &sync.Mutex{}},
		stderr: &output{mu: &sync.Mutex{}},
	}
}

func (c *Cmd) validateIsFinished() {
	if c.status != FINISHED {
		log.Fatal(ErrCmdNotFinished)
	}
}

func (c *Cmd) validateHasStarted() {
	if c.status == INITIALIZED {
		log.Fatal(ErrUninitializedCmd)
	}
}

// SetEnv overwrites the environment with the provided one. Otherwise, the
// parent environment will be supplied.
func (c *Cmd) SetEnv(env []string) {
	c.env = env
}

// SetStdin sets the stdin stream. It makes no attempt to determine if the
// command accepts anything over stdin.
func (c *Cmd) SetStdin(stdin io.Reader) {
	c.stdin = stdin
}

// Run runs the command.
func (c *Cmd) Run() {
	if c.stdin != nil {
		c.cmd.Stdin = c.stdin
	}

	if c.env != nil {
		c.cmd.Env = c.env
	} else {
		c.cmd.Env = os.Environ()
	}

	var outBuf bytes.Buffer
	c.cmd.Stdout = &outBuf

	var errBuf bytes.Buffer
	c.cmd.Stderr = &errBuf

	if err := c.cmd.Run(); err != nil {
		c.exitError = err
	}
	c.stdout.content = string(outBuf.Bytes())
	c.stderr.content = string(errBuf.Bytes())
	c.status = FINISHED
}

// Start starts the command without waiting for it to complete
func (c *Cmd) Start() {
	if c.stdin != nil {
		c.cmd.Stdin = c.stdin
	}

	if c.env != nil {
		c.cmd.Env = c.env
	} else {
		c.cmd.Env = os.Environ()
	}

	stdoutPipe, err := c.cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}

	stderrPipe, err := c.cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := c.cmd.Start(); err != nil {
		c.exitError = err
	}

	go func() {
		scanner := bufio.NewScanner(stdoutPipe)
		for scanner.Scan() {
			c.stdout.mu.Lock()
			c.stdout.content += scanner.Text()
			c.stdout.mu.Unlock()
		}
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
	}()

	go func() {
		scanner := bufio.NewScanner(stderrPipe)
		for scanner.Scan() {
			c.stderr.mu.Lock()
			c.stderr.content += scanner.Text()
			c.stderr.mu.Unlock()
		}
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
	}()
	c.status = RUNNING
}

// Wait waits for the command to exit
func (c *Cmd) Wait() {
	c.validateHasStarted()
	if err := c.cmd.Wait(); err != nil {
		c.exitError = err
	}
	c.status = FINISHED
}

// Kill kills the process of the current command
func (c *Cmd) Kill() {
	c.validateHasStarted()
	err := c.cmd.Process.Kill()
	if err != nil {
		log.Fatal(err)
	}
	c.status = FINISHED
}

// Run runs a command with name and arguments. After this, package-level
// functions will return the data about the last command run.
func Run(name string, arg ...string) {
	pkgCmd = Command(name, arg...)
	pkgCmd.Run()
}

// Error is the command's error, if any.
func (c *Cmd) Error() error {
	c.validateIsFinished()
	return c.exitError
}

// Error is the command's error, if any.
func Error() error {
	return pkgCmd.Error()
}

// Stdout stream for the command
func (c *Cmd) Stdout() string {
	c.validateHasStarted()
	c.stdout.mu.Lock()
	defer c.stdout.mu.Unlock()
	return c.stdout.content
}

// Stdout stream for the command
func Stdout() string {
	return pkgCmd.Stdout()
}

// Stderr stream for the command
func (c *Cmd) Stderr() string {
	c.validateHasStarted()
	c.stderr.mu.Lock()
	defer c.stderr.mu.Unlock()
	return c.stderr.content
}

// Stderr stream for the command
func Stderr() string {
	return pkgCmd.Stderr()
}

// StdoutContains determines if command's STDOUT contains `str`, this operation
// is case insensitive.
func (c *Cmd) StdoutContains(str string) bool {
	c.validateHasStarted()
	str = strings.ToLower(str)
	return retryStringTest(strings.Contains, c.stdout, str)
}

// StdoutContains determines if command's STDOUT contains `str`, this operation
// is case insensitive.
func StdoutContains(str string) bool {
	return pkgCmd.StdoutContains(str)
}

// StderrContains determines if command's STDERR contains `str`, this operation
// is case insensitive.
func (c *Cmd) StderrContains(str string) bool {
	c.validateHasStarted()
	str = strings.ToLower(str)
	return retryStringTest(strings.Contains, c.stderr, str)
	// return strings.Contains(strings.ToLower(c.stderr.content), str)
}

// StderrContains determines if command's STDERR contains `str`, this operation
// is case insensitive.
func StderrContains(str string) bool {
	return pkgCmd.StderrContains(str)
}

// Success is a boolean status which indicates if the program exited non-zero
// or not.
func (c *Cmd) Success() bool {
	c.validateIsFinished()
	return c.exitError == nil
}

// Success is a boolean status which indicates if the program exited non-zero
// or not.
func Success() bool {
	return pkgCmd.Success()
}

// Failure is the inverse of Success().
func (c *Cmd) Failure() bool {
	c.validateIsFinished()
	return c.exitError != nil
}

// Failure is the inverse of Success().
func Failure() bool {
	return pkgCmd.Failure()
}

// StdoutMatches compares a regex to the stdout produced by the command.
func (c *Cmd) StdoutMatches(regex string) bool {
	c.validateHasStarted()
	re := regexp.MustCompile(regex)
	return retryStringTest(func(got, want string) bool {
		return re.MatchString(got)
	}, c.stdout, regex)
}

// StdoutMatches compares a regex to the stdout produced by the command.
func StdoutMatches(regex string) bool {
	return pkgCmd.StdoutMatches(regex)
}

// StderrMatches compares a regex to the stderr produced by the command.
func (c *Cmd) StderrMatches(regex string) bool {
	c.validateHasStarted()
	re := regexp.MustCompile(regex)
	return retryStringTest(func(got, want string) bool {
		return re.MatchString(got)
	}, c.stderr, regex)
}

// StderrMatches compares a regex to the stderr produced by the command.
func StderrMatches(regex string) bool {
	return pkgCmd.StderrMatches(regex)
}

// retryStringTest takes in a testFunc and will test output for the expected string until either it
// finds the expected string or times out (default 1 second)
func retryStringTest(testFunc func(string, string) bool, output *output, expected string) bool {
	ticker := time.NewTicker(100 * time.Millisecond)
	timeout := time.After(1 * time.Second)
	for {
		select {
		case <-ticker.C:
			output.mu.Lock()
			found := testFunc(strings.ToLower(output.content), expected)
			output.mu.Unlock()
			if found == true {
				return true
			}
		case <-timeout:
			return false
		}
	}
}
<commit_msg>Invert validateHasStarted conditional for readability<commit_after>// CLI testing package for the Go language.
//
// Developing a command line application? Wanna be able to test your app from the
// outside? If the answer is Yes to at least one of the questions, keep reading.
//
// `testcli` is a wrapper around os/exec to test CLI apps in Go lang,
// minimalistic, so you can do your tests with `testing` or any other testing
// framework.
package testcli

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

type output struct {
	content string
	mu      *sync.Mutex
}

// Cmd is typically constructed through the Command() call and provides state
// to the execution engine.
type Cmd struct {
	cmd       *exec.Cmd
	env       []string
	exitError error
	status    string
	stdout    *output
	stderr    *output
	stdin     io.Reader
}

// ErrUninitializedCmd is returned when members are accessed before a run, that
// can only be used after a command has been run.
var ErrUninitializedCmd = errors.New("You need to run this command first")

// ErrCmdNotFinished is returned when members are accessed before or during a run,
// that can only be used after a command has finished executing.
var ErrCmdNotFinished = errors.New("Command is still executing")

const (
	// INITIALIZED represents the state of Command before it's started with Run() or Start()
	INITIALIZED = "initialized"
	// RUNNING represents the state of Command while it's running
	RUNNING = "running"
	// FINISHED represents the state of Command after it has exited successfully or not
	FINISHED = "finished"
)

var pkgCmd = &Cmd{
	status: INITIALIZED,
	stdout: &output{mu: &sync.Mutex{}},
	stderr: &output{mu: &sync.Mutex{}},
}

// Command constructs a *Cmd. It is passed the command name and arguments.
func Command(name string, arg ...string) *Cmd {
	return &Cmd{
		cmd:    exec.Command(name, arg...),
		status: INITIALIZED,
		stdout: &output{mu: &sync.Mutex{}},
		stderr: &output{mu: &sync.Mutex{}},
	}
}

func (c *Cmd) validateIsFinished() {
	if c.status != FINISHED {
		log.Fatal(ErrCmdNotFinished)
	}
}

func (c *Cmd) validateHasStarted() {
	// After calling Start() status can either be running or finished
	if !(c.status == RUNNING || c.status == FINISHED) {
		log.Fatal(ErrUninitializedCmd)
	}
}

// SetEnv overwrites the environment with the provided one. Otherwise, the
// parent environment will be supplied.
func (c *Cmd) SetEnv(env []string) {
	c.env = env
}

// SetStdin sets the stdin stream. It makes no attempt to determine if the
// command accepts anything over stdin.
func (c *Cmd) SetStdin(stdin io.Reader) {
	c.stdin = stdin
}

// Run runs the command.
func (c *Cmd) Run() {
	if c.stdin != nil {
		c.cmd.Stdin = c.stdin
	}

	if c.env != nil {
		c.cmd.Env = c.env
	} else {
		c.cmd.Env = os.Environ()
	}

	var outBuf bytes.Buffer
	c.cmd.Stdout = &outBuf

	var errBuf bytes.Buffer
	c.cmd.Stderr = &errBuf

	if err := c.cmd.Run(); err != nil {
		c.exitError = err
	}
	c.stdout.content = string(outBuf.Bytes())
	c.stderr.content = string(errBuf.Bytes())
	c.status = FINISHED
}

// Start starts the command without waiting for it to complete
func (c *Cmd) Start() {
	if c.stdin != nil {
		c.cmd.Stdin = c.stdin
	}

	if c.env != nil {
		c.cmd.Env = c.env
	} else {
		c.cmd.Env = os.Environ()
	}

	stdoutPipe, err := c.cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}

	stderrPipe, err := c.cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := c.cmd.Start(); err != nil {
		c.exitError = err
	}

	go func() {
		scanner := bufio.NewScanner(stdoutPipe)
		for scanner.Scan() {
			c.stdout.mu.Lock()
			c.stdout.content += scanner.Text()
			c.stdout.mu.Unlock()
		}
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
	}()

	go func() {
		scanner := bufio.NewScanner(stderrPipe)
		for scanner.Scan() {
			c.stderr.mu.Lock()
			c.stderr.content += scanner.Text()
			c.stderr.mu.Unlock()
		}
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
	}()
	c.status = RUNNING
}

// Wait waits for the command to exit
func (c *Cmd) Wait() {
	c.validateHasStarted()
	if err := c.cmd.Wait(); err != nil {
		c.exitError = err
	}
	c.status = FINISHED
}

// Kill kills the process of the current command
func (c *Cmd) Kill() {
	c.validateHasStarted()
	err := c.cmd.Process.Kill()
	if err != nil {
		log.Fatal(err)
	}
	c.status = FINISHED
}

// Run runs a command with name and arguments. After this, package-level
// functions will return the data about the last command run.
func Run(name string, arg ...string) {
	pkgCmd = Command(name, arg...)
	pkgCmd.Run()
}

// Error is the command's error, if any.
func (c *Cmd) Error() error {
	c.validateIsFinished()
	return c.exitError
}

// Error is the command's error, if any.
func Error() error {
	return pkgCmd.Error()
}

// Stdout stream for the command
func (c *Cmd) Stdout() string {
	c.validateHasStarted()
	c.stdout.mu.Lock()
	defer c.stdout.mu.Unlock()
	return c.stdout.content
}

// Stdout stream for the command
func Stdout() string {
	return pkgCmd.Stdout()
}

// Stderr stream for the command
func (c *Cmd) Stderr() string {
	c.validateHasStarted()
	c.stderr.mu.Lock()
	defer c.stderr.mu.Unlock()
	return c.stderr.content
}

// Stderr stream for the command
func Stderr() string {
	return pkgCmd.Stderr()
}

// StdoutContains determines if command's STDOUT contains `str`, this operation
// is case insensitive.
func (c *Cmd) StdoutContains(str string) bool {
	c.validateHasStarted()
	str = strings.ToLower(str)
	return retryStringTest(strings.Contains, c.stdout, str)
}

// StdoutContains determines if command's STDOUT contains `str`, this operation
// is case insensitive.
func StdoutContains(str string) bool {
	return pkgCmd.StdoutContains(str)
}

// StderrContains determines if command's STDERR contains `str`, this operation
// is case insensitive.
func (c *Cmd) StderrContains(str string) bool {
	c.validateHasStarted()
	str = strings.ToLower(str)
	return retryStringTest(strings.Contains, c.stderr, str)
	// return strings.Contains(strings.ToLower(c.stderr.content), str)
}

// StderrContains determines if command's STDERR contains `str`, this operation
// is case insensitive.
func StderrContains(str string) bool {
	return pkgCmd.StderrContains(str)
}

// Success is a boolean status which indicates if the program exited non-zero
// or not.
func (c *Cmd) Success() bool {
	c.validateIsFinished()
	return c.exitError == nil
}

// Success is a boolean status which indicates if the program exited non-zero
// or not.
func Success() bool {
	return pkgCmd.Success()
}

// Failure is the inverse of Success().
func (c *Cmd) Failure() bool {
	c.validateIsFinished()
	return c.exitError != nil
}

// Failure is the inverse of Success().
func Failure() bool {
	return pkgCmd.Failure()
}

// StdoutMatches compares a regex to the stdout produced by the command.
func (c *Cmd) StdoutMatches(regex string) bool {
	c.validateHasStarted()
	re := regexp.MustCompile(regex)
	return retryStringTest(func(got, want string) bool {
		return re.MatchString(got)
	}, c.stdout, regex)
}

// StdoutMatches compares a regex to the stdout produced by the command.
func StdoutMatches(regex string) bool {
	return pkgCmd.StdoutMatches(regex)
}

// StderrMatches compares a regex to the stderr produced by the command.
func (c *Cmd) StderrMatches(regex string) bool {
	c.validateHasStarted()
	re := regexp.MustCompile(regex)
	return retryStringTest(func(got, want string) bool {
		return re.MatchString(got)
	}, c.stderr, regex)
}

// StderrMatches compares a regex to the stderr produced by the command.
func StderrMatches(regex string) bool {
	return pkgCmd.StderrMatches(regex)
}

// retryStringTest takes in a testFunc and will test output for the expected string until either it
// finds the expected string or times out (default 1 second)
func retryStringTest(testFunc func(string, string) bool, output *output, expected string) bool {
	ticker := time.NewTicker(100 * time.Millisecond)
	timeout := time.After(1 * time.Second)
	for {
		select {
		case <-ticker.C:
			output.mu.Lock()
			found := testFunc(strings.ToLower(output.content), expected)
			output.mu.Unlock()
			if found == true {
				return true
			}
		case <-timeout:
			return false
		}
	}
}
<|endoftext|>


<commit_before>package main

import "os"

var name = "rexdep"
var version = "0.3.0"
var description = "Roughly extract dependency from source code"
var author = "itchyny"

func main() {
	os.Exit(run(os.Args))
}
<commit_msg>bump up version to 0.3.1<commit_after>package main

import "os"

var name = "rexdep"
var version = "0.3.1"
var description = "Roughly extract dependency from source code"
var author = "itchyny"

func main() {
	os.Exit(run(os.Args))
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	"github.com/jirwin/xpost-quadlek/pkg"

	"github.com/Bullpeen/infobot"
	"github.com/Bullpeen/slices"
	"github.com/Bullpeen/stox"
	log "github.com/Sirupsen/logrus"
	"github.com/jirwin/gifs-quadlek/src"
	"github.com/jirwin/quadlek/plugins/comics"
	"github.com/jirwin/quadlek/plugins/echo"
	"github.com/jirwin/quadlek/plugins/eslogs"
	"github.com/jirwin/quadlek/plugins/karma"
	"github.com/jirwin/quadlek/plugins/nextep"
	"github.com/jirwin/quadlek/plugins/random"
	"github.com/jirwin/quadlek/plugins/spotify"
	"github.com/jirwin/quadlek/plugins/twitter"
	"github.com/jirwin/quadlek/quadlek"
	"github.com/morgabra/cointip/quadlek"
	twitch "github.com/morgabra/libtwitch/quadlek"
	"github.com/urfave/cli"
)

const Version = "0.0.1"

func run(c *cli.Context) error {
	var apiToken string
	if c.IsSet("api-key") {
		apiToken = c.String("api-key")
	} else {
		cli.ShowAppHelp(c)
		return cli.NewExitError("Missing --api-key arg.", 1)
	}

	var verificationToken string
	if c.IsSet("verification-token") {
		verificationToken = c.String("verification-token")
	} else {
		cli.ShowAppHelp(c)
		return cli.NewExitError("Missing --verification-token arg.", 1)
	}

	dbPath := c.String("db-path")

	bot, err := quadlek.NewBot(context.Background(), apiToken, verificationToken, dbPath)
	if err != nil {
		log.WithFields(log.Fields{
			"err": err,
		}).Error("error creating bot")
		return nil
	}

	err = bot.RegisterPlugin(echo.Register())
	if err != nil {
		fmt.Printf("error registering echo plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(karma.Register())
	if err != nil {
		fmt.Printf("error registering karma plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(random.Register())
	if err != nil {
		fmt.Printf("error registering random plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(spotify.Register())
	if err != nil {
		fmt.Printf("error registering spotify plugin: %s\n", err.Error())
		return nil
	}

	if c.IsSet("tvdb-key") {
		tvdbKey := c.String("tvdb-key")

		err = bot.RegisterPlugin(nextep.Register(tvdbKey))
		if err != nil {
			fmt.Printf("error registering nextep plugin: %s\n", err.Error())
			return nil
		}
	}

	err = bot.RegisterPlugin(infobot.Register())
	if err != nil {
		fmt.Printf("error registering infobot plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(twitter.Register(
		c.String("twitter-consumer-key"),
		c.String("twitter-consumer-secret"),
		c.String("twitter-access-token"),
		c.String("twitter-access-secret"),
		// These must be twitter user ids, not names. https://tweeterid.com/ for easy conversion between the two.
		map[string]string{
			"25073877":           "politics-feed", // @realDonaldTrump
			"830896623689547776": "politics-feed", // @PresVillain
			"934474767645724672": "politics-feed", // @realtrumpweathr
			"138203134":          "politics-feed", // @AOC
			"783792992":          "politics-feed", // @IlhanMN
			"976366106561490944": "artfolio",      // @DrawnDavidsOff
			"921111554371682304": "artfolio",      // @DrawnDavidson
			"1581511":            "wwdc",          //@macrumorslive
			//added for retweet testing
			"778682": "quadlek-chat", // @jirwin

		},
	))

	coinbasePlugin := cointip.Register(
		c.String("coinbase-key"),
		c.String("coinbase-secret"),
		c.String("coinbase-account"),
	)
	if coinbasePlugin != nil {
		err = bot.RegisterPlugin(coinbasePlugin)
		if err != nil {
			panic(err)
		}
	}

	err = bot.RegisterPlugin(comics.Register(
		c.String("imgur-client-id"),
		"/opt/quad-assets/Arial.ttf",
	))
	if err != nil {
		fmt.Printf("error registering comic plugin: %s\n", err.Error())
		return err
	}
	esPlugin, err := eslogs.Register(c.String("es-endpoint"), c.String("es-index"))
	if err != nil {
		fmt.Printf("Error creating eslogs plugin: %s\n", err.Error())
		return err
	}
	err = bot.RegisterPlugin(esPlugin)
	if err != nil {
		fmt.Printf("Error registering eslogs plugin: %s\n", err.Error())
		return err
	}

	xpostPlugin := xpost.Register()
	err = bot.RegisterPlugin(xpostPlugin)
	if err != nil {
		fmt.Printf("error registering xpost plugin: %s\n", err.Error())
		return err
	}

	stoxPlugin := stox.Register(c.String("av-key"))
	err = bot.RegisterPlugin(stoxPlugin)
	if err != nil {
		fmt.Printf("Error registering stox plugin: %s\n", err.Error())
		return err
	}

	gifPlugin := gifs.Register(c.String("giphy-api-key"))
	err = bot.RegisterPlugin(gifPlugin)
	if err != nil {
		fmt.Printf("Error registering gifs plugin: %s\n", err.Error())
		return err
	}

	twitchFollows := []*twitch.TwitchFollow{
		{
			SlackChannels: []string{"kitboga"},
			TwitchUser:    "kitboga",
			WatchStream:   true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "telewyn",
			SlackUser:     "telewyn",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "khryo72",
			SlackUser:     "morgabra",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "mekilek",
			SlackUser:     "jirwin",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "sonicdm",
			SlackUser:     "sonicdm",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "sakatana",
			SlackUser:     "saka",
			WatchStream:   true,
			WatchFollows:  true,
		},
	}

	twitchPlugin := twitch.Register(c.String("twitch-oauth-client-id"), "", "https://quadlek.jirw.in/slack/plugin/twitch", false, twitchFollows)
	if twitchPlugin != nil {
		err = bot.RegisterPlugin(twitchPlugin)
		if err != nil {
			fmt.Printf("error registering twitch plugin: %s\n", err.Error())
			return err
		}
	}

	err = bot.RegisterPlugin(slices.Register([]string {
		"76561197980107683", // sonicdm
		"76561197976367183", // morgabra
		"76561198057633471", // greenjeans
		"76561198002272597", // newsomr
		"76561197974723967", // purdyk
		//"76561197969022064", // schonstal?
	}))
	if err != nil {
		fmt.Printf("error registering slices plugin: %s\n", err.Error())
		return nil
	}

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)

	bot.Start()
	<-signals
	bot.Stop()

	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "quadlek"
	app.Version = Version
	app.Usage = "a slack bot"
	app.Action = run
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "api-key",
			Usage:  "The slack api token for the bot",
			EnvVar: "QUADLEK_API_TOKEN",
		},
		cli.StringFlag{
			Name:   "verification-token",
			Usage:  "The slack webhook verification token.",
			EnvVar: "QUADLEK_VERIFICATION_TOKEN",
		},
		cli.StringFlag{
			Name:   "db-path",
			Usage:  "The path where the database is stored.",
			Value:  "quadlek.db",
			EnvVar: "QUADLEK_DB_PATH",
		},
		cli.StringFlag{
			Name:   "tvdb-key",
			Usage:  "The TVDB api key for the bot, used by the nextep command",
			EnvVar: "QUADLEK_TVDB_TOKEN",
		},
		cli.StringFlag{
			Name:   "twitter-consumer-key",
			Usage:  "The consumer key for the twitter api",
			EnvVar: "QUADLEK_TWITTER_CONSUMER_KEY",
		},
		cli.StringFlag{
			Name:   "twitter-consumer-secret",
			Usage:  "The consumer secret for the twitter api",
			EnvVar: "QUADLEK_TWITTER_CONSUMER_SECRET",
		},
		cli.StringFlag{
			Name:   "twitter-access-token",
			Usage:  "The access key for the twitter api",
			EnvVar: "QUADLEK_TWITTER_ACCESS_TOKEN",
		},
		cli.StringFlag{
			Name:   "twitter-access-secret",
			Usage:  "The access secret for the twitter api",
			EnvVar: "QUADLEK_TWITTER_ACCESS_SECRET",
		},
		cli.StringFlag{
			Name:   "coinbase-key",
			Usage:  "The access key for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_KEY",
		},
		cli.StringFlag{
			Name:   "coinbase-secret",
			Usage:  "The access secret for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_SECRET",
		},
		cli.StringFlag{
			Name:   "coinbase-account",
			Usage:  "The bank account for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_BANK_ACCOUNT",
		},
		cli.StringFlag{
			Name:   "imgur-client-id",
			Usage:  "The imgur client id",
			EnvVar: "QUADLEK_IMGUR_CLIENT_ID",
		},
		cli.StringFlag{
			Name:   "es-endpoint",
			Usage:  "The elastic search endpoint",
			EnvVar: "QUADLEK_ES_ENDPOINT",
		},
		cli.StringFlag{
			Name:   "es-index",
			Usage:  "The elastic search index",
			EnvVar: "QUADLEK_ES_INDEX",
		},
		cli.StringFlag{
			Name:   "av-key",
			Usage:  "Alpha Vantage Api Key",
			EnvVar: "QUADLEK_AV_KEY",
		},
		cli.StringFlag{
			Name:   "giphy-api-key",
			Usage:  "Giphy API Key",
			EnvVar: "QUADLEK_GIPHY_KEY",
		},
		cli.StringFlag{
			Name:   "twitch-oauth-client-id",
			Usage:  "OAuth application client id for twitch API",
			EnvVar: "QUADLEK_TWITCH_OAUTH_CLIENT_ID",
		},
	}

	app.Run(os.Args)
}
<commit_msg>Add @bh to slices command.<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	"github.com/jirwin/xpost-quadlek/pkg"

	"github.com/Bullpeen/infobot"
	"github.com/Bullpeen/slices"
	"github.com/Bullpeen/stox"
	log "github.com/Sirupsen/logrus"
	"github.com/jirwin/gifs-quadlek/src"
	"github.com/jirwin/quadlek/plugins/comics"
	"github.com/jirwin/quadlek/plugins/echo"
	"github.com/jirwin/quadlek/plugins/eslogs"
	"github.com/jirwin/quadlek/plugins/karma"
	"github.com/jirwin/quadlek/plugins/nextep"
	"github.com/jirwin/quadlek/plugins/random"
	"github.com/jirwin/quadlek/plugins/spotify"
	"github.com/jirwin/quadlek/plugins/twitter"
	"github.com/jirwin/quadlek/quadlek"
	"github.com/morgabra/cointip/quadlek"
	twitch "github.com/morgabra/libtwitch/quadlek"
	"github.com/urfave/cli"
)

const Version = "0.0.1"

func run(c *cli.Context) error {
	var apiToken string
	if c.IsSet("api-key") {
		apiToken = c.String("api-key")
	} else {
		cli.ShowAppHelp(c)
		return cli.NewExitError("Missing --api-key arg.", 1)
	}

	var verificationToken string
	if c.IsSet("verification-token") {
		verificationToken = c.String("verification-token")
	} else {
		cli.ShowAppHelp(c)
		return cli.NewExitError("Missing --verification-token arg.", 1)
	}

	dbPath := c.String("db-path")

	bot, err := quadlek.NewBot(context.Background(), apiToken, verificationToken, dbPath)
	if err != nil {
		log.WithFields(log.Fields{
			"err": err,
		}).Error("error creating bot")
		return nil
	}

	err = bot.RegisterPlugin(echo.Register())
	if err != nil {
		fmt.Printf("error registering echo plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(karma.Register())
	if err != nil {
		fmt.Printf("error registering karma plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(random.Register())
	if err != nil {
		fmt.Printf("error registering random plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(spotify.Register())
	if err != nil {
		fmt.Printf("error registering spotify plugin: %s\n", err.Error())
		return nil
	}

	if c.IsSet("tvdb-key") {
		tvdbKey := c.String("tvdb-key")

		err = bot.RegisterPlugin(nextep.Register(tvdbKey))
		if err != nil {
			fmt.Printf("error registering nextep plugin: %s\n", err.Error())
			return nil
		}
	}

	err = bot.RegisterPlugin(infobot.Register())
	if err != nil {
		fmt.Printf("error registering infobot plugin: %s\n", err.Error())
		return nil
	}

	err = bot.RegisterPlugin(twitter.Register(
		c.String("twitter-consumer-key"),
		c.String("twitter-consumer-secret"),
		c.String("twitter-access-token"),
		c.String("twitter-access-secret"),
		// These must be twitter user ids, not names. https://tweeterid.com/ for easy conversion between the two.
		map[string]string{
			"25073877":           "politics-feed", // @realDonaldTrump
			"830896623689547776": "politics-feed", // @PresVillain
			"934474767645724672": "politics-feed", // @realtrumpweathr
			"138203134":          "politics-feed", // @AOC
			"783792992":          "politics-feed", // @IlhanMN
			"976366106561490944": "artfolio",      // @DrawnDavidsOff
			"921111554371682304": "artfolio",      // @DrawnDavidson
			"1581511":            "wwdc",          //@macrumorslive
			//added for retweet testing
			"778682": "quadlek-chat", // @jirwin

		},
	))

	coinbasePlugin := cointip.Register(
		c.String("coinbase-key"),
		c.String("coinbase-secret"),
		c.String("coinbase-account"),
	)
	if coinbasePlugin != nil {
		err = bot.RegisterPlugin(coinbasePlugin)
		if err != nil {
			panic(err)
		}
	}

	err = bot.RegisterPlugin(comics.Register(
		c.String("imgur-client-id"),
		"/opt/quad-assets/Arial.ttf",
	))
	if err != nil {
		fmt.Printf("error registering comic plugin: %s\n", err.Error())
		return err
	}
	esPlugin, err := eslogs.Register(c.String("es-endpoint"), c.String("es-index"))
	if err != nil {
		fmt.Printf("Error creating eslogs plugin: %s\n", err.Error())
		return err
	}
	err = bot.RegisterPlugin(esPlugin)
	if err != nil {
		fmt.Printf("Error registering eslogs plugin: %s\n", err.Error())
		return err
	}

	xpostPlugin := xpost.Register()
	err = bot.RegisterPlugin(xpostPlugin)
	if err != nil {
		fmt.Printf("error registering xpost plugin: %s\n", err.Error())
		return err
	}

	stoxPlugin := stox.Register(c.String("av-key"))
	err = bot.RegisterPlugin(stoxPlugin)
	if err != nil {
		fmt.Printf("Error registering stox plugin: %s\n", err.Error())
		return err
	}

	gifPlugin := gifs.Register(c.String("giphy-api-key"))
	err = bot.RegisterPlugin(gifPlugin)
	if err != nil {
		fmt.Printf("Error registering gifs plugin: %s\n", err.Error())
		return err
	}

	twitchFollows := []*twitch.TwitchFollow{
		{
			SlackChannels: []string{"kitboga"},
			TwitchUser:    "kitboga",
			WatchStream:   true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "telewyn",
			SlackUser:     "telewyn",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "khryo72",
			SlackUser:     "morgabra",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "mekilek",
			SlackUser:     "jirwin",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "sonicdm",
			SlackUser:     "sonicdm",
			WatchStream:   true,
			WatchFollows:  true,
		},
		{
			SlackChannels: []string{"vidyagames"},
			TwitchUser:    "sakatana",
			SlackUser:     "saka",
			WatchStream:   true,
			WatchFollows:  true,
		},
	}

	twitchPlugin := twitch.Register(c.String("twitch-oauth-client-id"), "", "https://quadlek.jirw.in/slack/plugin/twitch", false, twitchFollows)
	if twitchPlugin != nil {
		err = bot.RegisterPlugin(twitchPlugin)
		if err != nil {
			fmt.Printf("error registering twitch plugin: %s\n", err.Error())
			return err
		}
	}

	err = bot.RegisterPlugin(slices.Register([]string {
		"76561197980107683", // sonicdm
		"76561197976367183", // morgabra
		"76561198057633471", // greenjeans
		"76561198002272597", // newsomr
		"76561197974723967", // purdyk
		"1967806609958425", // bh
		//"76561197969022064", // schonstal?
	}))
	if err != nil {
		fmt.Printf("error registering slices plugin: %s\n", err.Error())
		return nil
	}

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)

	bot.Start()
	<-signals
	bot.Stop()

	return nil
}

func main() {
	app := cli.NewApp()
	app.Name = "quadlek"
	app.Version = Version
	app.Usage = "a slack bot"
	app.Action = run
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:   "api-key",
			Usage:  "The slack api token for the bot",
			EnvVar: "QUADLEK_API_TOKEN",
		},
		cli.StringFlag{
			Name:   "verification-token",
			Usage:  "The slack webhook verification token.",
			EnvVar: "QUADLEK_VERIFICATION_TOKEN",
		},
		cli.StringFlag{
			Name:   "db-path",
			Usage:  "The path where the database is stored.",
			Value:  "quadlek.db",
			EnvVar: "QUADLEK_DB_PATH",
		},
		cli.StringFlag{
			Name:   "tvdb-key",
			Usage:  "The TVDB api key for the bot, used by the nextep command",
			EnvVar: "QUADLEK_TVDB_TOKEN",
		},
		cli.StringFlag{
			Name:   "twitter-consumer-key",
			Usage:  "The consumer key for the twitter api",
			EnvVar: "QUADLEK_TWITTER_CONSUMER_KEY",
		},
		cli.StringFlag{
			Name:   "twitter-consumer-secret",
			Usage:  "The consumer secret for the twitter api",
			EnvVar: "QUADLEK_TWITTER_CONSUMER_SECRET",
		},
		cli.StringFlag{
			Name:   "twitter-access-token",
			Usage:  "The access key for the twitter api",
			EnvVar: "QUADLEK_TWITTER_ACCESS_TOKEN",
		},
		cli.StringFlag{
			Name:   "twitter-access-secret",
			Usage:  "The access secret for the twitter api",
			EnvVar: "QUADLEK_TWITTER_ACCESS_SECRET",
		},
		cli.StringFlag{
			Name:   "coinbase-key",
			Usage:  "The access key for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_KEY",
		},
		cli.StringFlag{
			Name:   "coinbase-secret",
			Usage:  "The access secret for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_SECRET",
		},
		cli.StringFlag{
			Name:   "coinbase-account",
			Usage:  "The bank account for the coinbase api",
			EnvVar: "QUADLEK_COINBASE_BANK_ACCOUNT",
		},
		cli.StringFlag{
			Name:   "imgur-client-id",
			Usage:  "The imgur client id",
			EnvVar: "QUADLEK_IMGUR_CLIENT_ID",
		},
		cli.StringFlag{
			Name:   "es-endpoint",
			Usage:  "The elastic search endpoint",
			EnvVar: "QUADLEK_ES_ENDPOINT",
		},
		cli.StringFlag{
			Name:   "es-index",
			Usage:  "The elastic search index",
			EnvVar: "QUADLEK_ES_INDEX",
		},
		cli.StringFlag{
			Name:   "av-key",
			Usage:  "Alpha Vantage Api Key",
			EnvVar: "QUADLEK_AV_KEY",
		},
		cli.StringFlag{
			Name:   "giphy-api-key",
			Usage:  "Giphy API Key",
			EnvVar: "QUADLEK_GIPHY_KEY",
		},
		cli.StringFlag{
			Name:   "twitch-oauth-client-id",
			Usage:  "OAuth application client id for twitch API",
			EnvVar: "QUADLEK_TWITCH_OAUTH_CLIENT_ID",
		},
	}

	app.Run(os.Args)
}
<|endoftext|>