<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/Nitro/sidecar-executor/container"
	log "github.com/Sirupsen/logrus"
	"github.com/fsouza/go-dockerclient"
	"github.com/mesos/mesos-go/executor"
	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/relistan/go-director"
)

// Callbacks from the Mesos driver. These are required to implement
// the Executor interface.

func (exec *sidecarExecutor) Registered(driver executor.ExecutorDriver,
	execInfo *mesos.ExecutorInfo, fwinfo *mesos.FrameworkInfo, slaveInfo *mesos.SlaveInfo) {
	log.Info("Registered Executor on slave ", slaveInfo.GetHostname())
}

func (exec *sidecarExecutor) Reregistered(driver executor.ExecutorDriver, slaveInfo *mesos.SlaveInfo) {
	log.Info("Re-registered Executor on slave ", slaveInfo.GetHostname())
}

func (exec *sidecarExecutor) Disconnected(driver executor.ExecutorDriver) {
	log.Info("Executor disconnected.")
}

// Copy the Docker container logs to stdout and stderr so we can capture some
// failure information in the Mesos logs. Then tooling can fetch crash info
// from the Mesos API.
func (exec *sidecarExecutor) copyLogs(containerId string) {
	startTimeEpoch := time.Now().UTC().Add(0 - config.LogsSince).Unix()

	container.GetLogs(
		exec.client, containerId, startTimeEpoch, os.Stdout, os.Stderr,
	)
}

// monitorTask runs in a goroutine and hangs out, waiting for the watchLooper to
// complete. When it completes, it handles the Docker and Mesos interactions.
func (exec *sidecarExecutor) monitorTask(cntnrId string, taskInfo *mesos.TaskInfo) {
	log.Infof("Monitoring Mesos task %s for container %s",
		*taskInfo.TaskId.Value,
		cntnrId,
	)

	containerName := container.GetContainerName(taskInfo.TaskId)
	// Wait on the watchLooper to return a status
	err := exec.watchLooper.Wait()
	if err != nil {
		log.Errorf("Error! %s", err.Error())
		// Something went wrong, we better take this thing out!
		err := container.StopContainer(exec.client, containerName, config.KillTaskTimeout)
		if err != nil {
			log.Errorf("Error stopping container %s! %s", containerName, err.Error())
		}
		// Copy the failure logs (hopefully) to stdout/stderr so we can get them
		exec.copyLogs(containerName)
		// Notify Mesos
		exec.failTask(taskInfo)
		return
	}

	log.Info("Task completed: ", taskInfo.GetName())
	exec.finishTask(taskInfo)
	return
}

// Callback from Mesos driver to launch a new task in this executor
func (exec *sidecarExecutor) LaunchTask(driver executor.ExecutorDriver, taskInfo *mesos.TaskInfo) {
	log.Infof("Launching task %s with command '%s'", taskInfo.GetName(), taskInfo.Command.GetValue())
	log.Info("Task ID ", taskInfo.GetTaskId().GetValue())

	// We need to tell the scheduler that we started the task
	exec.sendStatus(TaskRunning, taskInfo.GetTaskId())

	log.Infof("Using image '%s'", *taskInfo.Container.Docker.Image)

	// TODO implement configurable pull timeout?
	if !container.CheckImage(exec.client, taskInfo) || *taskInfo.Container.Docker.ForcePullImage {
		err := container.PullImage(exec.client, taskInfo, exec.dockerAuth)
		if err != nil {
			log.Errorf("Failed to pull image: %s", err.Error())
			exec.failTask(taskInfo)
			return
		}
	} else {
		log.Info("Re-using existing image... already present")
	}

	// Additional environment variables we'll pass to the container
	var addEnvVars []string
	if exec.config.SeedSidecar {
		// Fetch the Mesos slave list and append the SIDECAR_SEEDS env var
		addEnvVars = exec.addSidecarSeeds(addEnvVars)
	}

	// Configure the container
	containerConfig := container.ConfigForTask(
		taskInfo,
		exec.config.ForceCpuLimit,
		exec.config.ForceMemoryLimit,
		addEnvVars,
	)

	// Log out what we're starting up with
	exec.logTaskEnv(taskInfo, container.LabelsForTask(taskInfo), addEnvVars)

	// Try to decrypt any existing Vault encoded env.
	decryptedEnv, err := exec.vault.DecryptAllEnv(containerConfig.Config.Env)
	if err != nil {
		log.Error(err.Error())
		exec.failTask(taskInfo)
		return
	}
	containerConfig.Config.Env = decryptedEnv

	// create the container
	cntnr, err := exec.client.CreateContainer(*containerConfig)
	if err != nil {
		log.Errorf("Failed to create Docker container: %s", err.Error())
		exec.failTask(taskInfo)
		return
	}

	// Start the container
	log.Info("Starting container with ID " + cntnr.ID[:12])
	err = exec.client.StartContainer(cntnr.ID, nil)
	if err != nil {
		log.Errorf("Failed to create Docker container: %s", err.Error())
		exec.failTask(taskInfo)
		return
	}

	// For debugging, set process title to contain container ID & image
	SetProcessName("sidecar-executor " + cntnr.ID[:12] + " (" + *taskInfo.Container.Docker.Image + ")")

	exec.watchLooper =
		director.NewImmediateTimedLooper(director.FOREVER, config.SidecarPollInterval, make(chan error))

	// We have to do this in a different goroutine or the scheduler
	// can't send us any further updates.
	go exec.watchContainer(cntnr.ID, shouldCheckSidecar(containerConfig))
	go exec.monitorTask(cntnr.ID[:12], taskInfo)
}

// getMasterHostname talks to the local worker endpoint and discovers the
// Mesos master hostname.
func (exec *sidecarExecutor) getMasterHostname() (string, error) {
	localEndpoint := "http://" + os.Getenv("MESOS_AGENT_ENDPOINT")
	if len(localEndpoint) < 8 { // Did we get anything in the env var?
		return "", fmt.Errorf("Can't get MESOS_AGENT_ENDPOINT from env! Won't provide Sidecar seeds.")
	}

	localStruct := struct {
		MasterHostname string `json:"master_hostname"`
	}{}

	// Let's find out the Mesos master's hostname
	resp, err := exec.fetcher.Get(localEndpoint)
	if err != nil {
		return "", fmt.Errorf("Unable to fetch Mesos master info from worker endpoint: %s", err)
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Error reading response body from Mesos worker! '%s'", err)
	}

	err = json.Unmarshal(body, &localStruct)
	if err != nil {
		return "", fmt.Errorf("Error parsing response body from Mesos worker! '%s'", err)
	}

	return localStruct.MasterHostname, nil
}

// getWorkerHostnames returns a slice of all the current worker hostnames
func (exec *sidecarExecutor) getWorkerHostnames(masterHostname string) ([]string, error) {
	masterEndpoint := "http://" + masterHostname + ":5050/slaves"

	type workersStruct struct {
		Hostname string `json:"hostname"`
	}

	masterStruct := struct {
		Slaves []workersStruct `json:"slaves"`
	}{}

	// Let's find out the Mesos master's hostname
	resp, err := exec.fetcher.Get(masterEndpoint)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch info from master endpoint: %s", err)
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Error reading response body from Mesos master! '%s'", err)
	}

	err = json.Unmarshal(body, &masterStruct)
	if err != nil {
		return nil, fmt.Errorf("Error parsing response body from Mesos master! '%s'", err)
	}

	var workers []string
	for _, worker := range masterStruct.Slaves {
		workers = append(workers, worker.Hostname)
	}

	return workers, nil
}

// addSidecarSeeds mutates the passed slice and inserts an env var formatted
// string (FOO=BAR_1) containing the list of Sidecar seeds that should be
// used to bootstrap a Sidecar instance.
func (exec *sidecarExecutor) addSidecarSeeds(envVars []string) []string {
	masterHostname, err := exec.getMasterHostname()
	if err != nil {
		log.Error(err.Error())
		return envVars
	}

	workerNames, err := exec.getWorkerHostnames(masterHostname)
	if err != nil {
		log.Error(err.Error())
		return envVars
	}

	return append(envVars, "SIDECAR_SEEDS="+strings.Join(workerNames, ","))
}

func (exec *sidecarExecutor) KillTask(driver executor.ExecutorDriver, taskID *mesos.TaskID) {
	log.Infof("Killing task: %s", *taskID.Value)

	// Stop watching the container so we don't send the wrong task status
	go func() { exec.watchLooper.Quit() }()

	containerName := container.GetContainerName(taskID)

	err := container.StopContainer(exec.client, containerName, config.KillTaskTimeout)
	if err != nil {
		log.Errorf("Error stopping container %s! %s", containerName, err.Error())
	}

	// Have to force this to be an int64
	var status int64 = TaskKilled // Default status is that we shot it in the head

	// Now we need to sort out whether the task finished nicely or not.
	// This driver callback is used both to shoot a task in the head, and when
	// a task is being replaced. The Mesos task status needs to reflect the
	// resulting container State.ExitCode.
	container, err := exec.client.InspectContainer(containerName)
	if err == nil {
		if container.State.ExitCode == 0 {
			status = TaskFinished // We exited cleanly when asked
		}
	} else {
		log.Errorf("Error inspecting container %s! %s", containerName, err.Error())
	}

	// Copy the failure logs (hopefully) to stdout/stderr so we can get them
	exec.copyLogs(containerName)
	// Notify Mesos
	exec.sendStatus(status, taskID)

	// We have to give the driver time to send the message
	time.Sleep(1 * time.Second)
	exec.driver.Stop()
}

func (exec *sidecarExecutor) FrameworkMessage(driver executor.ExecutorDriver, msg string) {
	log.Info("Got framework message: ", msg)
}

func (exec *sidecarExecutor) Shutdown(driver executor.ExecutorDriver) {
	log.Info("Shutting down the executor")
}

func (exec *sidecarExecutor) Error(driver executor.ExecutorDriver, err string) {
	log.Info("Got error message:", err)
}

// Check if it should check Sidecar status, assuming enabled by default
func shouldCheckSidecar(containerConfig *docker.CreateContainerOptions) bool {
	value, ok := containerConfig.Config.Labels["SidecarDiscover"]
	if !ok {
		return true
	}

	if enabled, err := strconv.ParseBool(value); err == nil {
		return enabled
	}

	return true
}
<commit_msg>Fix Mesos URL<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/Nitro/sidecar-executor/container"
	log "github.com/Sirupsen/logrus"
	"github.com/fsouza/go-dockerclient"
	"github.com/mesos/mesos-go/executor"
	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/relistan/go-director"
)

// Callbacks from the Mesos driver. These are required to implement
// the Executor interface.

func (exec *sidecarExecutor) Registered(driver executor.ExecutorDriver,
	execInfo *mesos.ExecutorInfo, fwinfo *mesos.FrameworkInfo, slaveInfo *mesos.SlaveInfo) {
	log.Info("Registered Executor on slave ", slaveInfo.GetHostname())
}

func (exec *sidecarExecutor) Reregistered(driver executor.ExecutorDriver, slaveInfo *mesos.SlaveInfo) {
	log.Info("Re-registered Executor on slave ", slaveInfo.GetHostname())
}

func (exec *sidecarExecutor) Disconnected(driver executor.ExecutorDriver) {
	log.Info("Executor disconnected.")
}

// Copy the Docker container logs to stdout and stderr so we can capture some
// failure information in the Mesos logs. Then tooling can fetch crash info
// from the Mesos API.
func (exec *sidecarExecutor) copyLogs(containerId string) {
	startTimeEpoch := time.Now().UTC().Add(0 - config.LogsSince).Unix()

	container.GetLogs(
		exec.client, containerId, startTimeEpoch, os.Stdout, os.Stderr,
	)
}

// monitorTask runs in a goroutine and hangs out, waiting for the watchLooper to
// complete. When it completes, it handles the Docker and Mesos interactions.
func (exec *sidecarExecutor) monitorTask(cntnrId string, taskInfo *mesos.TaskInfo) {
	log.Infof("Monitoring Mesos task %s for container %s",
		*taskInfo.TaskId.Value,
		cntnrId,
	)

	containerName := container.GetContainerName(taskInfo.TaskId)
	// Wait on the watchLooper to return a status
	err := exec.watchLooper.Wait()
	if err != nil {
		log.Errorf("Error! %s", err.Error())
		// Something went wrong, we better take this thing out!
		err := container.StopContainer(exec.client, containerName, config.KillTaskTimeout)
		if err != nil {
			log.Errorf("Error stopping container %s! %s", containerName, err.Error())
		}
		// Copy the failure logs (hopefully) to stdout/stderr so we can get them
		exec.copyLogs(containerName)
		// Notify Mesos
		exec.failTask(taskInfo)
		return
	}

	log.Info("Task completed: ", taskInfo.GetName())
	exec.finishTask(taskInfo)
	return
}

// Callback from Mesos driver to launch a new task in this executor
func (exec *sidecarExecutor) LaunchTask(driver executor.ExecutorDriver, taskInfo *mesos.TaskInfo) {
	log.Infof("Launching task %s with command '%s'", taskInfo.GetName(), taskInfo.Command.GetValue())
	log.Info("Task ID ", taskInfo.GetTaskId().GetValue())

	// We need to tell the scheduler that we started the task
	exec.sendStatus(TaskRunning, taskInfo.GetTaskId())

	log.Infof("Using image '%s'", *taskInfo.Container.Docker.Image)

	// TODO implement configurable pull timeout?
	if !container.CheckImage(exec.client, taskInfo) || *taskInfo.Container.Docker.ForcePullImage {
		err := container.PullImage(exec.client, taskInfo, exec.dockerAuth)
		if err != nil {
			log.Errorf("Failed to pull image: %s", err.Error())
			exec.failTask(taskInfo)
			return
		}
	} else {
		log.Info("Re-using existing image... already present")
	}

	// Additional environment variables we'll pass to the container
	var addEnvVars []string
	if exec.config.SeedSidecar {
		// Fetch the Mesos slave list and append the SIDECAR_SEEDS env var
		addEnvVars = exec.addSidecarSeeds(addEnvVars)
	}

	// Configure the container
	containerConfig := container.ConfigForTask(
		taskInfo,
		exec.config.ForceCpuLimit,
		exec.config.ForceMemoryLimit,
		addEnvVars,
	)

	// Log out what we're starting up with
	exec.logTaskEnv(taskInfo, container.LabelsForTask(taskInfo), addEnvVars)

	// Try to decrypt any existing Vault encoded env.
	decryptedEnv, err := exec.vault.DecryptAllEnv(containerConfig.Config.Env)
	if err != nil {
		log.Error(err.Error())
		exec.failTask(taskInfo)
		return
	}
	containerConfig.Config.Env = decryptedEnv

	// create the container
	cntnr, err := exec.client.CreateContainer(*containerConfig)
	if err != nil {
		log.Errorf("Failed to create Docker container: %s", err.Error())
		exec.failTask(taskInfo)
		return
	}

	// Start the container
	log.Info("Starting container with ID " + cntnr.ID[:12])
	err = exec.client.StartContainer(cntnr.ID, nil)
	if err != nil {
		log.Errorf("Failed to create Docker container: %s", err.Error())
		exec.failTask(taskInfo)
		return
	}

	// For debugging, set process title to contain container ID & image
	SetProcessName("sidecar-executor " + cntnr.ID[:12] + " (" + *taskInfo.Container.Docker.Image + ")")

	exec.watchLooper =
		director.NewImmediateTimedLooper(director.FOREVER, config.SidecarPollInterval, make(chan error))

	// We have to do this in a different goroutine or the scheduler
	// can't send us any further updates.
	go exec.watchContainer(cntnr.ID, shouldCheckSidecar(containerConfig))
	go exec.monitorTask(cntnr.ID[:12], taskInfo)
}

// getMasterHostname talks to the local worker endpoint and discovers the
// Mesos master hostname.
func (exec *sidecarExecutor) getMasterHostname() (string, error) {
	localEndpoint := "http://" + os.Getenv("MESOS_AGENT_ENDPOINT") + "/state"
	if len(localEndpoint) < 8 { // Did we get anything in the env var?
		return "", fmt.Errorf("Can't get MESOS_AGENT_ENDPOINT from env! Won't provide Sidecar seeds.")
	}

	localStruct := struct {
		MasterHostname string `json:"master_hostname"`
	}{}

	// Let's find out the Mesos master's hostname
	resp, err := exec.fetcher.Get(localEndpoint)
	if err != nil {
		return "", fmt.Errorf("Unable to fetch Mesos master info from worker endpoint: %s", err)
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Error reading response body from Mesos worker! '%s'", err)
	}

	err = json.Unmarshal(body, &localStruct)
	if err != nil {
		return "", fmt.Errorf("Error parsing response body from Mesos worker! '%s'", err)
	}

	return localStruct.MasterHostname, nil
}

// getWorkerHostnames returns a slice of all the current worker hostnames
func (exec *sidecarExecutor) getWorkerHostnames(masterHostname string) ([]string, error) {
	masterEndpoint := "http://" + masterHostname + ":5050/slaves"

	type workersStruct struct {
		Hostname string `json:"hostname"`
	}

	masterStruct := struct {
		Slaves []workersStruct `json:"slaves"`
	}{}

	// Let's find out the Mesos master's hostname
	resp, err := exec.fetcher.Get(masterEndpoint)
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch info from master endpoint: %s", err)
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Error reading response body from Mesos master! '%s'", err)
	}

	err = json.Unmarshal(body, &masterStruct)
	if err != nil {
		return nil, fmt.Errorf("Error parsing response body from Mesos master! '%s'", err)
	}

	var workers []string
	for _, worker := range masterStruct.Slaves {
		workers = append(workers, worker.Hostname)
	}

	return workers, nil
}

// addSidecarSeeds mutates the passed slice and inserts an env var formatted
// string (FOO=BAR_1) containing the list of Sidecar seeds that should be
// used to bootstrap a Sidecar instance.
func (exec *sidecarExecutor) addSidecarSeeds(envVars []string) []string {
	masterHostname, err := exec.getMasterHostname()
	if err != nil {
		log.Error(err.Error())
		return envVars
	}

	workerNames, err := exec.getWorkerHostnames(masterHostname)
	if err != nil {
		log.Error(err.Error())
		return envVars
	}

	return append(envVars, "SIDECAR_SEEDS="+strings.Join(workerNames, ","))
}

func (exec *sidecarExecutor) KillTask(driver executor.ExecutorDriver, taskID *mesos.TaskID) {
	log.Infof("Killing task: %s", *taskID.Value)

	// Stop watching the container so we don't send the wrong task status
	go func() { exec.watchLooper.Quit() }()

	containerName := container.GetContainerName(taskID)

	err := container.StopContainer(exec.client, containerName, config.KillTaskTimeout)
	if err != nil {
		log.Errorf("Error stopping container %s! %s", containerName, err.Error())
	}

	// Have to force this to be an int64
	var status int64 = TaskKilled // Default status is that we shot it in the head

	// Now we need to sort out whether the task finished nicely or not.
	// This driver callback is used both to shoot a task in the head, and when
	// a task is being replaced. The Mesos task status needs to reflect the
	// resulting container State.ExitCode.
	container, err := exec.client.InspectContainer(containerName)
	if err == nil {
		if container.State.ExitCode == 0 {
			status = TaskFinished // We exited cleanly when asked
		}
	} else {
		log.Errorf("Error inspecting container %s! %s", containerName, err.Error())
	}

	// Copy the failure logs (hopefully) to stdout/stderr so we can get them
	exec.copyLogs(containerName)
	// Notify Mesos
	exec.sendStatus(status, taskID)

	// We have to give the driver time to send the message
	time.Sleep(1 * time.Second)
	exec.driver.Stop()
}

func (exec *sidecarExecutor) FrameworkMessage(driver executor.ExecutorDriver, msg string) {
	log.Info("Got framework message: ", msg)
}

func (exec *sidecarExecutor) Shutdown(driver executor.ExecutorDriver) {
	log.Info("Shutting down the executor")
}

func (exec *sidecarExecutor) Error(driver executor.ExecutorDriver, err string) {
	log.Info("Got error message:", err)
}

// Check if it should check Sidecar status, assuming enabled by default
func shouldCheckSidecar(containerConfig *docker.CreateContainerOptions) bool {
	value, ok := containerConfig.Config.Labels["SidecarDiscover"]
	if !ok {
		return true
	}

	if enabled, err := strconv.ParseBool(value); err == nil {
		return enabled
	}

	return true
}
<|endoftext|>


<commit_before>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"log"
	"reflect"
	"unsafe"
)

func (a *C.utp_callback_arguments) bufBytes() []byte {
	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{uintptr(unsafe.Pointer(a.buf)), int(a.len), int(a.len)}))
}

func (a *C.utp_callback_arguments) state() C.int {
	return *(*C.int)(unsafe.Pointer(&a.anon0))
}

func (a *C.utp_callback_arguments) error_code() C.int {
	return *(*C.int)(unsafe.Pointer(&a.anon0))
}

//export sendtoCallback
func sendtoCallback(a *C.utp_callback_arguments) (ret C.uint64) {
	s := getSocketForLibContext(a.context)
	sa := *(**C.struct_sockaddr)(unsafe.Pointer(&a.anon0[0]))
	b := a.bufBytes()
	addr := structSockaddrToUDPAddr(sa)
	n, err := s.pc.WriteTo(b, addr)
	if err != nil {
		log.Printf("error sending packet: %s", err)
		return
	}
	if n != len(b) {
		log.Printf("expected to send %d bytes but only sent %d", len(b), n)
	}
	return
}

//export errorCallback
func errorCallback(a *C.utp_callback_arguments) C.uint64 {
	codeName := libErrorCodeNames(a.error_code())
	// log.Printf("error callback: socket %p: %s", a.socket, codeName)
	libContextToSocket[a.context].conns[a.socket].onLibError(codeName)
	return 0
}

//export logCallback
func logCallback(a *C.utp_callback_arguments) C.uint64 {
	log.Printf("libutp: %s", C.GoString((*C.char)(unsafe.Pointer(a.buf))))
	return 0
}

//export stateChangeCallback
func stateChangeCallback(a *C.utp_callback_arguments) C.uint64 {
	// log.Printf("state changed: socket %p: %s", a.socket, libStateName(a.state()))
	s := libContextToSocket[a.context]
	c := s.conns[a.socket]
	switch a.state() {
	case C.UTP_STATE_CONNECT:
		c.setConnected()
	case C.UTP_STATE_WRITABLE:
		c.cond.Broadcast()
	case C.UTP_STATE_EOF:
		c.setGotEOF()
	case C.UTP_STATE_DESTROYING:
		c.onDestroyed()
		s.onLibSocketDestroyed(a.socket)
	default:
		panic(a.state)
	}
	return 0
}

//export readCallback
func readCallback(a *C.utp_callback_arguments) C.uint64 {
	s := libContextToSocket[a.context]
	c := s.conns[a.socket]
	c.readBuf = append(c.readBuf, a.bufBytes()...)
	c.cond.Broadcast()
	return 0
}

//export acceptCallback
func acceptCallback(a *C.utp_callback_arguments) C.uint64 {
	// log.Printf("accept callback: %#v", *a)
	s := getSocketForLibContext(a.context)
	s.pushBacklog(s.newConn(a.socket))
	return 0
}
<commit_msg>Fix for nettest RacyRead<commit_after>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"log"
	"reflect"
	"unsafe"
)

func (a *C.utp_callback_arguments) bufBytes() []byte {
	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{uintptr(unsafe.Pointer(a.buf)), int(a.len), int(a.len)}))
}

func (a *C.utp_callback_arguments) state() C.int {
	return *(*C.int)(unsafe.Pointer(&a.anon0))
}

func (a *C.utp_callback_arguments) error_code() C.int {
	return *(*C.int)(unsafe.Pointer(&a.anon0))
}

//export sendtoCallback
func sendtoCallback(a *C.utp_callback_arguments) (ret C.uint64) {
	s := getSocketForLibContext(a.context)
	sa := *(**C.struct_sockaddr)(unsafe.Pointer(&a.anon0[0]))
	b := a.bufBytes()
	addr := structSockaddrToUDPAddr(sa)
	n, err := s.pc.WriteTo(b, addr)
	if err != nil {
		log.Printf("error sending packet: %s", err)
		return
	}
	if n != len(b) {
		log.Printf("expected to send %d bytes but only sent %d", len(b), n)
	}
	return
}

//export errorCallback
func errorCallback(a *C.utp_callback_arguments) C.uint64 {
	codeName := libErrorCodeNames(a.error_code())
	// log.Printf("error callback: socket %p: %s", a.socket, codeName)
	libContextToSocket[a.context].conns[a.socket].onLibError(codeName)
	return 0
}

//export logCallback
func logCallback(a *C.utp_callback_arguments) C.uint64 {
	log.Printf("libutp: %s", C.GoString((*C.char)(unsafe.Pointer(a.buf))))
	return 0
}

//export stateChangeCallback
func stateChangeCallback(a *C.utp_callback_arguments) C.uint64 {
	// log.Printf("state changed: socket %p: %s", a.socket, libStateName(a.state()))
	s := libContextToSocket[a.context]
	c := s.conns[a.socket]
	switch a.state() {
	case C.UTP_STATE_CONNECT:
		c.setConnected()
		// A dialled connection will not tell the remote it's ready until it
		// writes. If the dialer has no intention of writing, this will stall
		// everything. We do an empty write to get things rolling again. This
		// circumstance occurs when c1 in the RacyRead nettest is the dialer.
		C.utp_write(a.socket, nil, 0)
	case C.UTP_STATE_WRITABLE:
		c.cond.Broadcast()
	case C.UTP_STATE_EOF:
		c.setGotEOF()
	case C.UTP_STATE_DESTROYING:
		c.onDestroyed()
		s.onLibSocketDestroyed(a.socket)
	default:
		panic(a.state)
	}
	return 0
}

//export readCallback
func readCallback(a *C.utp_callback_arguments) C.uint64 {
	s := libContextToSocket[a.context]
	c := s.conns[a.socket]
	c.readBuf = append(c.readBuf, a.bufBytes()...)
	c.cond.Broadcast()
	return 0
}

//export acceptCallback
func acceptCallback(a *C.utp_callback_arguments) C.uint64 {
	// log.Printf("accept callback: %#v", *a)
	s := getSocketForLibContext(a.context)
	s.pushBacklog(s.newConn(a.socket))
	return 0
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"strings"
	"unicode/utf8"
)

const (
	_ = -iota
	EOF
	STRING
	IF
	THEN
	ELSE
	FI
	WHILE
	DO
	DONE
)

func parse(r io.Reader, name string) error {
	p := &parser{
		r:    bufio.NewReader(r),
		name: name,
		line: 1,
		col:  0,
	}
	p.next()
	p.program()
	return p.err
}

type parser struct {
	r   *bufio.Reader
	err error

	tok int32
	val string

	name string
	line int
	col  int
}

var reserved = map[rune]bool{
	'\n': true,
	'#':  true,
	'=':  true,
	'&':  true,
	'>':  true,
	'<':  true,
	'|':  true,
	';':  true,
	'(':  true,
	')':  true,
	'{':  true,
	'}':  true,
	'$':  true,
}

var quote = map[rune]bool{
	'"':  true,
	'\'': true,
}

var space = map[rune]bool{
	' ':  true,
	'\t': true,
}

var (
	ident = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
	num   = regexp.MustCompile(`^[1-9][0-9]*$`)
)

func (p *parser) next() {
	r := ' '
	var err error
	for space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			p.tok = EOF
			return
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
	}
	if r == '\\' {
		p.next()
		if p.got('\n') {
			return
		}
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
	}
	if reserved[r] {
		switch r {
		case '#':
			p.discardUpTo('\n')
			p.next()
			return
		case '$':
			p.next()
			if p.got('{') {
				p.discardUpTo('}')
				p.tok = STRING
				return
			}
		case '\n':
			p.line++
			p.col = 0
			p.tok = '\n'
			return
		default:
			p.tok = r
			return
		}
	}
	if quote[r] {
		p.strContent(byte(r))
		if p.tok == EOF {
			return
		}
		p.tok = STRING
		return
	}
	rs := []rune{r}
	for !reserved[r] && !quote[r] && !space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			break
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
		rs = append(rs, r)
	}
	if err != io.EOF && len(rs) > 1 {
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
		rs = rs[:len(rs)-1]
	}
	p.tok = STRING
	p.val = string(rs)
	return
}

func (p *parser) strContent(delim byte) {
	v := []string{string(delim)}
	for {
		s, err := p.r.ReadString(delim)
		if err == io.EOF {
			p.tok = EOF
			p.errWanted(rune(delim))
		} else if err != nil {
			p.errPass(err)
		}
		v = append(v, s)
		if delim == '\'' {
			break
		}
		if len(s) > 1 && s[len(s)-2] == '\\' && s[len(s)-1] == delim {
			continue
		}
		break
	}
	p.val = strings.Join(v, "")
	for _, r := range p.val {
		if r == '\n' {
			p.line++
			p.col = 0
		} else {
			p.col++
		}
	}
}

func (p *parser) discardUpTo(delim byte) {
	b, err := p.r.ReadBytes(delim)
	if err == io.EOF {
		p.tok = EOF
	} else if err != nil {
		p.errPass(err)
	}
	p.col += utf8.RuneCount(b)
}

func (p *parser) got(tok int32) bool {
	if p.tok == tok || (p.tok == STRING && p.val == tokStr(tok)) {
		p.next()
		return true
	}
	return false
}

func (p *parser) want(tok int32) {
	if p.tok != tok && !(p.tok == STRING && p.val == tokStr(tok)) {
		p.errWanted(tok)
		return
	}
	p.next()
}

var tokStrs = map[int32]string{
	EOF:    "EOF",
	STRING: "string",

	IF:    "if",
	THEN:  "then",
	ELSE:  "else",
	FI:    "fi",
	WHILE: "while",
	DO:    "do",
	DONE:  "done",
}

func tokStr(tok int32) string {
	if s, e := tokStrs[tok]; e {
		return s
	}
	return strconv.QuoteRune(tok)
}

func (p *parser) errPass(err error) {
	if p.err == nil {
		p.err = err
	}
	p.tok = EOF
}

func (p *parser) lineErr(format string, v ...interface{}) {
	var pos string
	if p.name != "" {
		pos = fmt.Sprintf("%s:%d:%d: ", p.name, p.line, p.col)
	} else {
		pos = fmt.Sprintf("%d:%d: ", p.line, p.col)
	}
	p.errPass(fmt.Errorf(pos+format, v...))
}

func (p *parser) errUnexpected() {
	p.lineErr("unexpected token %s", tokStr(p.tok))
}

func (p *parser) errWantedStr(s string) {
	p.lineErr("unexpected token %s, wanted %s", tokStr(p.tok), s)
}

func (p *parser) errWanted(tok int32) {
	p.errWantedStr(tokStr(tok))
}

func (p *parser) errAfterStr(s string) {
	p.lineErr("unexpected token %s after %s", tokStr(p.tok), s)
}

func (p *parser) program() {
	for p.tok != EOF {
		if p.got('\n') {
			continue
		}
		p.command()
	}
}

func (p *parser) command() {
	switch {
	case p.got('('):
		count := 0
		for p.tok != EOF && p.tok != ')' {
			if p.got('\n') {
				continue
			}
			p.command()
			count++
		}
		if count == 0 {
			p.errWantedStr("command")
		}
		p.want(')')
	case p.got(IF):
		p.command()
		p.want(THEN)
		for p.tok != EOF {
			if p.tok == STRING && (p.val == "fi" || p.val == "else") {
				break
			}
			if p.got('\n') {
				continue
			}
			p.command()
		}
		if p.got(ELSE) {
			for p.tok != EOF {
				if p.tok == STRING && p.val == "fi" {
					break
				}
				if p.got('\n') {
					continue
				}
				p.command()
			}
		}
		p.want(FI)
	case p.got(WHILE):
		p.command()
		p.want(DO)
		for p.tok != EOF {
			if p.tok == STRING && p.val == "done" {
				break
			}
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want(DONE)
	case p.got(STRING):
		for p.tok != EOF {
			lval := p.val
			switch {
			case p.got(STRING):
			case p.got('='):
				if !ident.MatchString(lval) {
					p.col -= utf8.RuneCountInString(lval)
					p.col--
					p.lineErr("invalid var name %q", lval)
					return
				}
				p.got(STRING)
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('('):
				if !ident.MatchString(lval) {
					p.col -= utf8.RuneCountInString(lval)
					p.col--
					p.lineErr("invalid func name %q", lval)
					return
				}
				p.want(')')
				p.command()
				return
			case p.got('>'):
				p.redirectDest()
			case p.got('<'):
				p.want(STRING)
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errAfterStr("command")
			}
		}
	case p.got('{'):
		for p.tok != EOF && p.tok != '}' {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want('}')
		switch {
		case p.got('&'):
			if p.got('&') {
				p.command()
			}
			return
		case p.got('|'):
			p.got('|')
			p.command()
			return
		case p.got('>'):
			p.redirectDest()
		case p.got('<'):
			p.want(STRING)
		case p.got(';'):
			return
		case p.got('\n'):
			return
		default:
			p.errAfterStr("block")
		}
	default:
		p.errWantedStr("command")
	}
}

func (p *parser) redirectDest() {
	switch {
	case p.got('&'):
		p.want(STRING)
		if !num.MatchString(p.val) {
			p.errWantedStr("number")
		}
		return
	case p.got('>'):
	}
	p.want(STRING)
}
<commit_msg>Simplify peeking into the next token<commit_after>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"strings"
	"unicode/utf8"
)

const (
	_ = -iota
	EOF
	STRING
	IF
	THEN
	ELSE
	FI
	WHILE
	DO
	DONE
)

func parse(r io.Reader, name string) error {
	p := &parser{
		r:    bufio.NewReader(r),
		name: name,
		line: 1,
		col:  0,
	}
	p.next()
	p.program()
	return p.err
}

type parser struct {
	r   *bufio.Reader
	err error

	tok int32
	val string

	name string
	line int
	col  int
}

var reserved = map[rune]bool{
	'\n': true,
	'#':  true,
	'=':  true,
	'&':  true,
	'>':  true,
	'<':  true,
	'|':  true,
	';':  true,
	'(':  true,
	')':  true,
	'{':  true,
	'}':  true,
	'$':  true,
}

var quote = map[rune]bool{
	'"':  true,
	'\'': true,
}

var space = map[rune]bool{
	' ':  true,
	'\t': true,
}

var (
	ident = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
	num   = regexp.MustCompile(`^[1-9][0-9]*$`)
)

func (p *parser) next() {
	r := ' '
	var err error
	for space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			p.tok = EOF
			return
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
	}
	if r == '\\' {
		p.next()
		if p.got('\n') {
			return
		}
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
	}
	if reserved[r] {
		switch r {
		case '#':
			p.discardUpTo('\n')
			p.next()
			return
		case '$':
			p.next()
			if p.got('{') {
				p.discardUpTo('}')
				p.tok = STRING
				return
			}
		case '\n':
			p.line++
			p.col = 0
			p.tok = '\n'
			return
		default:
			p.tok = r
			return
		}
	}
	if quote[r] {
		p.strContent(byte(r))
		if p.tok == EOF {
			return
		}
		p.tok = STRING
		return
	}
	rs := []rune{r}
	for !reserved[r] && !quote[r] && !space[r] {
		r, _, err = p.r.ReadRune()
		if err == io.EOF {
			break
		}
		if err != nil {
			p.errPass(err)
			return
		}
		p.col++
		rs = append(rs, r)
	}
	if err != io.EOF && len(rs) > 1 {
		if err := p.r.UnreadRune(); err != nil {
			p.errPass(err)
			return
		}
		p.col--
		rs = rs[:len(rs)-1]
	}
	p.tok = STRING
	p.val = string(rs)
	return
}

func (p *parser) strContent(delim byte) {
	v := []string{string(delim)}
	for {
		s, err := p.r.ReadString(delim)
		if err == io.EOF {
			p.tok = EOF
			p.errWanted(rune(delim))
		} else if err != nil {
			p.errPass(err)
		}
		v = append(v, s)
		if delim == '\'' {
			break
		}
		if len(s) > 1 && s[len(s)-2] == '\\' && s[len(s)-1] == delim {
			continue
		}
		break
	}
	p.val = strings.Join(v, "")
	for _, r := range p.val {
		if r == '\n' {
			p.line++
			p.col = 0
		} else {
			p.col++
		}
	}
}

func (p *parser) discardUpTo(delim byte) {
	b, err := p.r.ReadBytes(delim)
	if err == io.EOF {
		p.tok = EOF
	} else if err != nil {
		p.errPass(err)
	}
	p.col += utf8.RuneCount(b)
}

func (p *parser) peek(tok int32) bool {
	return p.tok == tok || (p.tok == STRING && p.val == tokStr(tok))
}

func (p *parser) got(tok int32) bool {
	if p.peek(tok) {
		p.next()
		return true
	}
	return false
}

func (p *parser) want(tok int32) {
	if !p.peek(tok) {
		p.errWanted(tok)
		return
	}
	p.next()
}

var tokStrs = map[int32]string{
	EOF:    "EOF",
	STRING: "string",

	IF:    "if",
	THEN:  "then",
	ELSE:  "else",
	FI:    "fi",
	WHILE: "while",
	DO:    "do",
	DONE:  "done",
}

func tokStr(tok int32) string {
	if s, e := tokStrs[tok]; e {
		return s
	}
	return strconv.QuoteRune(tok)
}

func (p *parser) errPass(err error) {
	if p.err == nil {
		p.err = err
	}
	p.tok = EOF
}

func (p *parser) lineErr(format string, v ...interface{}) {
	var pos string
	if p.name != "" {
		pos = fmt.Sprintf("%s:%d:%d: ", p.name, p.line, p.col)
	} else {
		pos = fmt.Sprintf("%d:%d: ", p.line, p.col)
	}
	p.errPass(fmt.Errorf(pos+format, v...))
}

func (p *parser) errUnexpected() {
	p.lineErr("unexpected token %s", tokStr(p.tok))
}

func (p *parser) errWantedStr(s string) {
	p.lineErr("unexpected token %s, wanted %s", tokStr(p.tok), s)
}

func (p *parser) errWanted(tok int32) {
	p.errWantedStr(tokStr(tok))
}

func (p *parser) errAfterStr(s string) {
	p.lineErr("unexpected token %s after %s", tokStr(p.tok), s)
}

func (p *parser) program() {
	for p.tok != EOF {
		if p.got('\n') {
			continue
		}
		p.command()
	}
}

func (p *parser) command() {
	switch {
	case p.got('('):
		count := 0
		for p.tok != EOF && !p.peek(')') {
			if p.got('\n') {
				continue
			}
			p.command()
			count++
		}
		if count == 0 {
			p.errWantedStr("command")
		}
		p.want(')')
	case p.got(IF):
		p.command()
		p.want(THEN)
		for p.tok != EOF && !p.peek(FI) && !p.peek(ELSE) {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		if p.got(ELSE) {
			for p.tok != EOF && !p.peek(FI) {
				if p.got('\n') {
					continue
				}
				p.command()
			}
		}
		p.want(FI)
	case p.got(WHILE):
		p.command()
		p.want(DO)
		for p.tok != EOF && !p.peek(DONE) {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want(DONE)
	case p.got(STRING):
		for p.tok != EOF {
			lval := p.val
			switch {
			case p.got(STRING):
			case p.got('='):
				if !ident.MatchString(lval) {
					p.col -= utf8.RuneCountInString(lval)
					p.col--
					p.lineErr("invalid var name %q", lval)
					return
				}
				p.got(STRING)
			case p.got('&'):
				if p.got('&') {
					p.command()
				}
				return
			case p.got('|'):
				p.got('|')
				p.command()
				return
			case p.got('('):
				if !ident.MatchString(lval) {
					p.col -= utf8.RuneCountInString(lval)
					p.col--
					p.lineErr("invalid func name %q", lval)
					return
				}
				p.want(')')
				p.command()
				return
			case p.got('>'):
				p.redirectDest()
			case p.got('<'):
				p.want(STRING)
			case p.got(';'):
				return
			case p.got('\n'):
				return
			default:
				p.errAfterStr("command")
			}
		}
	case p.got('{'):
		for p.tok != EOF && !p.peek('}') {
			if p.got('\n') {
				continue
			}
			p.command()
		}
		p.want('}')
		switch {
		case p.got('&'):
			if p.got('&') {
				p.command()
			}
			return
		case p.got('|'):
			p.got('|')
			p.command()
			return
		case p.got('>'):
			p.redirectDest()
		case p.got('<'):
			p.want(STRING)
		case p.got(';'):
			return
		case p.got('\n'):
			return
		default:
			p.errAfterStr("block")
		}
	default:
		p.errWantedStr("command")
	}
}

func (p *parser) redirectDest() {
	switch {
	case p.got('&'):
		p.want(STRING)
		if !num.MatchString(p.val) {
			p.errWantedStr("number")
		}
		return
	case p.got('>'):
	}
	p.want(STRING)
}
<|endoftext|>


<commit_before>package ledger

import (
	"bufio"
	"fmt"
	"io"
	"math/big"
	"regexp"
	"strings"

	date "github.com/joyt/godate"
	"github.com/marcmak/calc/calc"
)

const (
	whitespace = " \t"
)

// ParseLedger parses a ledger file and returns a list of Transactions.
//
// Transactions are sorted by date.
func ParseLedger(ledgerReader io.Reader) (generalLedger []*Transaction, err error) {
	parseLedger(ledgerReader, func(t *Transaction, e error) (stop bool) {
		if e != nil {
			err = e
			stop = true
			return
		}

		generalLedger = append(generalLedger, t)
		return
	})

	return
}

// ParseLedgerAsync parses a ledger file and returns a Transaction and error channels .
//
func ParseLedgerAsync(ledgerReader io.Reader) (c chan *Transaction, e chan error) {
	c = make(chan *Transaction)
	e = make(chan error)

	go func() {
		parseLedger(ledgerReader, func(t *Transaction, err error) (stop bool) {
			if err != nil {
				e <- err
			} else {
				c <- t
			}
			return
		})

		e <- nil
	}()
	return c, e
}

var accountToAmountSpace = regexp.MustCompile(" {2,}|\t+")

func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error) (stop bool)) {
	scanner := bufio.NewScanner(ledgerReader)
	var line string
	var filename string
	var lineCount int
	var comments []string

	errorMsg := func(msg string) (stop bool) {
		return callback(nil, fmt.Errorf("%s:%d: %s", filename, lineCount, msg))
	}

	for scanner.Scan() {
		line = scanner.Text()

		// update filename/line if sentinel comment is found
		if strings.HasPrefix(line, markerPrefix) {
			filename, lineCount = parseMarker(line)
			continue
		}

		// remove heading and tailing space from the line
		trimmedLine := strings.Trim(line, whitespace)
		lineCount++

		// handle comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			comments = append(comments, trimmedLine[commentIdx:])
			trimmedLine = trimmedLine[:commentIdx]
			if len(trimmedLine) == 0 {
				continue
			}
		}

		// Skip empty lines
		if len(trimmedLine) == 0 {
			continue
		}

		lineSplit := strings.SplitN(trimmedLine, " ", 2)
		if len(lineSplit) != 2 {
			if errorMsg("Unable to parse payee line: " + line) {
				return
			}
			continue
		}
		commandDirective := lineSplit[0]
		switch commandDirective {
		case "account":
			_, lines, _ := parseAccount(scanner)
			lineCount += lines
		default:
			trans, lines, transErr := parseTransaction(scanner)
			lineCount += lines
			if transErr != nil {
				if errorMsg(fmt.Errorf("Unable to parse transaction: %w", transErr).Error()) {
					return
				}
				continue
			}
			trans.Comments = append(comments, trans.Comments...)
			callback(trans, nil)
			comments = nil
		}
	}
}

func parseAccount(scanner *bufio.Scanner) (accountName string, lines int, err error) {
	line := scanner.Text()
	// remove heading and tailing space from the line
	trimmedLine := strings.Trim(line, whitespace)

	lineSplit := strings.SplitN(trimmedLine, " ", 2)
	if len(lineSplit) != 2 {
		err = fmt.Errorf("Unable to parse account line: %s", line)
		return
	}
	accountName = lineSplit[1]

	for scanner.Scan() {
		// Read until blank line (ignore all sub-directives)
		line = scanner.Text()
		// remove heading and tailing space from the line
		trimmedLine = strings.Trim(line, whitespace)
		lines++

		// skip comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			trimmedLine = trimmedLine[:commentIdx]
		}

		// continue slurping up sub-directives until empty line
		if len(trimmedLine) == 0 {
			return
		}
	}

	return
}

func parseTransaction(scanner *bufio.Scanner) (trans *Transaction, lines int, err error) {
	var comments []string

	line := scanner.Text()
	trimmedLine := strings.Trim(line, whitespace)
	// handle comments (comment saved in calling function)
	if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
		trimmedLine = trimmedLine[:commentIdx]
	}
	trimmedLine = strings.Trim(trimmedLine, whitespace)

	// Parse Date-Payee line
	lineSplit := strings.SplitN(trimmedLine, " ", 2)
	if len(lineSplit) != 2 {
		err = fmt.Errorf("Unable to parse payee line: %s", line)
		return
	}
	dateString := lineSplit[0]
	transDate, dateErr := date.Parse(dateString)
	if dateErr != nil {
		err = fmt.Errorf("Unable to parse date: %s", dateString)
		return
	}
	payeeString := lineSplit[1]
	trans = &Transaction{Payee: payeeString, Date: transDate}

	for scanner.Scan() {
		line = scanner.Text()
		// remove heading and tailing space from the line
		trimmedLine = strings.Trim(line, whitespace)
		lines++

		// handle comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			comments = append(comments, trimmedLine[commentIdx:])
			trimmedLine = trimmedLine[:commentIdx]
			if len(trimmedLine) == 0 {
				continue
			}
		}

		if len(trimmedLine) == 0 {
			if trans != nil {
				transErr := balanceTransaction(trans)
				if transErr != nil {
					err = fmt.Errorf("Unable to balance transaction: %w", transErr)
					return
				}
				trans.Comments = comments
				return
			}
		} else {
			var accChange Account
			lineSplit := accountToAmountSpace.Split(trimmedLine, -1)
			var nonEmptyWords []string
			for _, word := range lineSplit {
				if len(word) > 0 {
					nonEmptyWords = append(nonEmptyWords, word)
				}
			}
			lastIndex := len(nonEmptyWords) - 1
			balErr, rationalNum := getBalance(strings.Trim(nonEmptyWords[lastIndex], whitespace))
			if !balErr {
				// Assuming no balance and whole line is account name
				accChange.Name = strings.Join(nonEmptyWords, " ")
			} else {
				accChange.Name = strings.Join(nonEmptyWords[:lastIndex], " ")
				accChange.Balance = rationalNum
			}
			trans.AccountChanges = append(trans.AccountChanges, accChange)
		}
	}
	// If the file does not end on empty line, we must attempt to balance last
	// transaction of the file.
	if trans != nil {
		transErr := balanceTransaction(trans)
		if transErr != nil {
			err = fmt.Errorf("Unable to balance transaction: %w", transErr)
			return
		}
		trans.Comments = comments
	}
	return
}

func getBalance(balance string) (bool, *big.Rat) {
	rationalNum := new(big.Rat)
	if strings.Contains(balance, "(") {
		rationalNum.SetFloat64(calc.Solve(balance))
		return true, rationalNum
	}
	_, isValid := rationalNum.SetString(balance)
	return isValid, rationalNum
}

// Takes a transaction and balances it. This is mainly to fill in the empty part
// with the remaining balance.
func balanceTransaction(input *Transaction) error {
	balance := new(big.Rat)
	var emptyFound bool
	var emptyAccIndex int
	for accIndex, accChange := range input.AccountChanges {
		if accChange.Balance == nil {
			if emptyFound {
				return fmt.Errorf("more than one account empty")
			}
			emptyAccIndex = accIndex
			emptyFound = true
		} else {
			balance = balance.Add(balance, accChange.Balance)
		}
	}
	if balance.Sign() != 0 {
		if !emptyFound {
			return fmt.Errorf("no empty account to place extra balance")
		}
	}
	if emptyFound {
		input.AccountChanges[emptyAccIndex].Balance = balance.Neg(balance)
	}

	return nil
}
<commit_msg>perf: use different strings funcs, remember date layout<commit_after>package ledger

import (
	"bufio"
	"fmt"
	"io"
	"math/big"
	"regexp"
	"strings"
	"time"
	"unicode"

	date "github.com/joyt/godate"
	"github.com/marcmak/calc/calc"
)

// ParseLedger parses a ledger file and returns a list of Transactions.
//
// Transactions are sorted by date.
func ParseLedger(ledgerReader io.Reader) (generalLedger []*Transaction, err error) {
	parseLedger(ledgerReader, func(t *Transaction, e error) (stop bool) {
		if e != nil {
			err = e
			stop = true
			return
		}

		generalLedger = append(generalLedger, t)
		return
	})

	return
}

// ParseLedgerAsync parses a ledger file and returns a Transaction and error channels .
//
func ParseLedgerAsync(ledgerReader io.Reader) (c chan *Transaction, e chan error) {
	c = make(chan *Transaction)
	e = make(chan error)

	go func() {
		parseLedger(ledgerReader, func(t *Transaction, err error) (stop bool) {
			if err != nil {
				e <- err
			} else {
				c <- t
			}
			return
		})

		e <- nil
	}()
	return c, e
}

// Calculation expressions are enclosed in parantheses
var calcExpr = regexp.MustCompile(`(?s) \((.*)\)`)

func parseLedger(ledgerReader io.Reader, callback func(t *Transaction, err error) (stop bool)) {
	scanner := bufio.NewScanner(ledgerReader)
	var line string
	var filename string
	var lineCount int
	var comments []string

	// default date layout
	dateLayout := "2006/01/02"

	errorMsg := func(msg string) (stop bool) {
		return callback(nil, fmt.Errorf("%s:%d: %s", filename, lineCount, msg))
	}

	for scanner.Scan() {
		line = scanner.Text()

		// update filename/line if sentinel comment is found
		if strings.HasPrefix(line, markerPrefix) {
			filename, lineCount = parseMarker(line)
			continue
		}

		// remove heading and tailing space from the line
		trimmedLine := strings.TrimSpace(line)
		lineCount++

		// handle comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			comments = append(comments, trimmedLine[commentIdx:])
			trimmedLine = trimmedLine[:commentIdx]
			if len(trimmedLine) == 0 {
				continue
			}
		}

		// Skip empty lines
		if len(trimmedLine) == 0 {
			continue
		}

		lineSplit := strings.SplitN(trimmedLine, " ", 2)
		if len(lineSplit) != 2 {
			if errorMsg("Unable to parse payee line: " + line) {
				return
			}
			continue
		}
		commandDirective := lineSplit[0]
		switch commandDirective {
		case "account":
			_, lines, _ := parseAccount(scanner)
			lineCount += lines
		default:
			trans, lines, layout, transErr := parseTransaction(dateLayout, scanner)
			dateLayout = layout
			lineCount += lines
			if transErr != nil {
				if errorMsg(fmt.Errorf("Unable to parse transaction: %w", transErr).Error()) {
					return
				}
				continue
			}
			trans.Comments = append(comments, trans.Comments...)
			callback(trans, nil)
			comments = nil
		}
	}
}

func parseAccount(scanner *bufio.Scanner) (accountName string, lines int, err error) {
	line := scanner.Text()
	// remove heading and tailing space from the line
	trimmedLine := strings.TrimSpace(line)

	lineSplit := strings.SplitN(trimmedLine, " ", 2)
	if len(lineSplit) != 2 {
		err = fmt.Errorf("Unable to parse account line: %s", line)
		return
	}
	accountName = lineSplit[1]

	for scanner.Scan() {
		// Read until blank line (ignore all sub-directives)
		line = scanner.Text()
		// remove heading and tailing space from the line
		trimmedLine = strings.TrimSpace(line)
		lines++

		// skip comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			trimmedLine = trimmedLine[:commentIdx]
		}

		// continue slurping up sub-directives until empty line
		if len(trimmedLine) == 0 {
			return
		}
	}

	return
}

func parseTransaction(currentDateLayout string, scanner *bufio.Scanner) (trans *Transaction, lines int, layout string, err error) {
	var comments []string

	line := scanner.Text()
	trimmedLine := strings.TrimSpace(line)
	// handle comments (comment saved in calling function)
	if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
		trimmedLine = trimmedLine[:commentIdx]
	}
	trimmedLine = strings.TrimSpace(trimmedLine)

	// Parse Date-Payee line
	lineSplit := strings.SplitN(trimmedLine, " ", 2)
	if len(lineSplit) != 2 {
		err = fmt.Errorf("Unable to parse payee line: %s", line)
		return
	}
	dateString := lineSplit[0]

	// attempt currentDateLayout, hopefully file is consistent
	layout = currentDateLayout
	transDate, dateErr := time.Parse(layout, dateString)
	if dateErr != nil {
		// try to find new date layout
		transDate, layout, dateErr = date.ParseAndGetLayout(dateString)
	}
	if dateErr != nil {
		err = fmt.Errorf("Unable to parse date: %s", dateString)
		return
	}
	payeeString := lineSplit[1]
	trans = &Transaction{Payee: payeeString, Date: transDate}

	for scanner.Scan() {
		line = scanner.Text()
		// remove heading and tailing space from the line
		trimmedLine = strings.TrimSpace(line)
		lines++

		// handle comments
		if commentIdx := strings.Index(trimmedLine, ";"); commentIdx >= 0 {
			comments = append(comments, trimmedLine[commentIdx:])
			trimmedLine = trimmedLine[:commentIdx]
			if len(trimmedLine) == 0 {
				continue
			}
			trimmedLine = strings.TrimSpace(trimmedLine)
		}

		if len(trimmedLine) == 0 {
			if trans != nil {
				transErr := balanceTransaction(trans)
				if transErr != nil {
					err = fmt.Errorf("Unable to balance transaction: %w", transErr)
					return
				}
				trans.Comments = comments
				return
			}
		} else {
			// Check for expr
			trimmedLine = calcExpr.ReplaceAllStringFunc(trimmedLine, func(s string) string {
				return fmt.Sprintf("%f", calc.Solve(s))
			})

			var accChange Account
			accChange.Name = trimmedLine
			if i := strings.LastIndexFunc(trimmedLine, unicode.IsSpace); i >= 0 {
				acc := strings.TrimSpace(trimmedLine[:i])
				amt := trimmedLine[i+1:]
				if ratbal, valid := new(big.Rat).SetString(amt); valid {
					accChange.Name = acc
					accChange.Balance = ratbal
				}
			}
			trans.AccountChanges = append(trans.AccountChanges, accChange)
		}
	}
	// If the file does not end on empty line, we must attempt to balance last
	// transaction of the file.
	if trans != nil {
		transErr := balanceTransaction(trans)
		if transErr != nil {
			err = fmt.Errorf("Unable to balance transaction: %w", transErr)
			return
		}
		trans.Comments = comments
	}
	return
}

// Takes a transaction and balances it. This is mainly to fill in the empty part
// with the remaining balance.
func balanceTransaction(input *Transaction) error {
	balance := new(big.Rat)
	var emptyFound bool
	var emptyAccIndex int
	for accIndex, accChange := range input.AccountChanges {
		if accChange.Balance == nil {
			if emptyFound {
				return fmt.Errorf("more than one account empty")
			}
			emptyAccIndex = accIndex
			emptyFound = true
		} else {
			balance = balance.Add(balance, accChange.Balance)
		}
	}
	if balance.Sign() != 0 {
		if !emptyFound {
			return fmt.Errorf("no empty account to place extra balance")
		}
	}
	if emptyFound {
		input.AccountChanges[emptyAccIndex].Balance = balance.Neg(balance)
	}

	return nil
}
<|endoftext|>


<commit_before>// Package parse provides a client for the Parse API.
package parse

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/facebookgo/httperr"
)

const (
	userAgentHeader     = "User-Agent"
	userAgent           = "go-parse-2015-01-31"
	masterKeyHeader     = "X-Parse-Master-Key"
	restAPIKeyHeader    = "X-Parse-REST-API-Key"
	sessionTokenHeader  = "X-Parse-Session-Token"
	applicationIDHeader = "X-Parse-Application-Id"
)

var (
	errEmptyMasterKey    = errors.New("parse: cannot use empty MasterKey Credentials")
	errEmptyRestAPIKey   = errors.New("parse: cannot use empty RestAPIKey Credentials")
	errEmptySessionToken = errors.New("parse: cannot use empty SessionToken Credentials")

	// The default base URL for the API.
	defaultBaseURL = url.URL{
		Scheme: "https",
		Host:   "api.parse.com",
		Path:   "/1/",
	}
)

// Credentials allows for adding authentication information to a request.
type Credentials interface {
	Modify(r *http.Request) error
}

// MasterKey adds the Master Key to the request.
type MasterKey string

// Modify adds the Master Key header.
func (m MasterKey) Modify(r *http.Request) error {
	if m == "" {
		return errEmptyMasterKey
	}
	r.Header.Set(masterKeyHeader, string(m))
	return nil
}

// RestAPIKey adds the Rest API Key to the request.
type RestAPIKey string

// Modify adds the Rest API Key header.
func (k RestAPIKey) Modify(r *http.Request) error {
	if k == "" {
		return errEmptyRestAPIKey
	}
	r.Header.Set(restAPIKeyHeader, string(k))
	return nil
}

// SessionToken adds the Rest API Key and the Session Token to the request.
type SessionToken struct {
	RestAPIKey   string
	SessionToken string
}

// Modify adds the Session Token header.
func (t SessionToken) Modify(r *http.Request) error {
	if t.RestAPIKey == "" {
		return errEmptyRestAPIKey
	}
	if t.SessionToken == "" {
		return errEmptySessionToken
	}
	r.Header.Set(restAPIKeyHeader, string(t.RestAPIKey))
	r.Header.Set(sessionTokenHeader, string(t.SessionToken))
	return nil
}

// An Error from the Parse API.
type Error struct {
	// These are provided by the Parse API and may not always be available.
	Message string `json:"error"`
	Code    int    `json:"code"`

	// This is the HTTP StatusCode.
	StatusCode int `json:"-"`

	request  *http.Request
	response *http.Response
}

func (e *Error) Error() string {
	var buf bytes.Buffer
	if e.Code != 0 {
		fmt.Fprintf(&buf, "code %d", e.Code)
	}
	if e.Code != 0 && e.Message != "" {
		fmt.Fprint(&buf, " and ")
	}
	if e.Message != "" {
		fmt.Fprintf(&buf, "message %s", e.Message)
	}
	return httperr.NewError(
		errors.New(buf.String()),
		httperr.RedactNoOp(),
		e.request,
		e.response,
	).Error()
}

// Client provides access to the Parse API.
type Client struct {
	// The underlying http.RoundTripper to perform the individual requests. When
	// nil http.DefaultTransport will be used.
	Transport http.RoundTripper

	// The base URL to parse relative URLs off. If you pass absolute URLs to
	// Client functions they are used as-is. When nil, the production Parse URL
	// will be used.
	BaseURL *url.URL

	// Application ID will be included if not empty.
	ApplicationID string

	// Credentials if set, will be included on every request.
	Credentials Credentials
}

func (c *Client) transport() http.RoundTripper {
	if c.Transport == nil {
		return http.DefaultTransport
	}
	return c.Transport
}

// Get performs a GET method call on the given url and unmarshal response into
// result.
func (c *Client) Get(u *url.URL, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "GET", URL: u}, nil, result)
}

// Post performs a POST method call on the given url with the given body and
// unmarshal response into result.
func (c *Client) Post(u *url.URL, body, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "POST", URL: u}, body, result)
}

// Put performs a PUT method call on the given url with the given body and
// unmarshal response into result.
func (c *Client) Put(u *url.URL, body, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "PUT", URL: u}, body, result)
}

// Delete performs a DELETE method call on the given url and unmarshal response
// into result.
func (c *Client) Delete(u *url.URL, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "DELETE", URL: u}, nil, result)
}

// RoundTrip performs a RoundTrip ignoring the request and response bodies. It
// is up to the caller to close them. This method modifies the request.
func (c *Client) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Proto = "HTTP/1.1"
	req.ProtoMajor = 1
	req.ProtoMinor = 1

	if req.URL == nil {
		if c.BaseURL == nil {
			req.URL = &defaultBaseURL
		} else {
			req.URL = c.BaseURL
		}
	} else {
		if !req.URL.IsAbs() {
			if c.BaseURL == nil {
				req.URL = defaultBaseURL.ResolveReference(req.URL)
			} else {
				req.URL = c.BaseURL.ResolveReference(req.URL)
			}
		}
	}

	if req.Host == "" {
		req.Host = req.URL.Host
	}

	if req.Header == nil {
		req.Header = make(http.Header)
	}

	req.Header.Add(userAgentHeader, userAgent)
	if c.ApplicationID != "" {
		req.Header.Add(applicationIDHeader, c.ApplicationID)
	}

	if c.Credentials != nil {
		if err := c.Credentials.Modify(req); err != nil {
			return nil, err
		}
	}

	res, err := c.transport().RoundTrip(req)
	if err != nil {
		return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
	}

	if res.StatusCode > 399 || res.StatusCode < 200 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
		}

		apiErr := &Error{
			StatusCode: res.StatusCode,
			request:    req,
			response:   res,
		}
		if len(body) > 0 {
			err = json.Unmarshal(body, apiErr)
			if err != nil {
				return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
			}
		}
		return res, apiErr
	}

	return res, nil
}

// Do performs a Parse API call. This method modifies the request and adds the
// Authentication headers. The body is JSON encoded and for responses in the
// 2xx or 3xx range the response will be JSON decoded into result, for others
// an error of type Error will be returned.
func (c *Client) Do(req *http.Request, body, result interface{}) (*http.Response, error) {
	// we need to buffer as Parse requires a Content-Length
	if body != nil {
		bd, err := json.Marshal(body)
		if err != nil {
			return nil, httperr.NewError(err, httperr.RedactNoOp(), req, nil)
		}
		req.Body = ioutil.NopCloser(bytes.NewReader(bd))
		req.ContentLength = int64(len(bd))
	}

	res, err := c.RoundTrip(req)
	if err != nil {
		return res, err
	}
	defer res.Body.Close()

	if result != nil {
		if err := json.NewDecoder(res.Body).Decode(result); err != nil {
			return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
		}
	}
	return res, nil
}

// WithCredentials returns a new instance of the Client using the given
// Credentials. It discards the previous Credentials.
func (c *Client) WithCredentials(cr Credentials) *Client {
	var c2 Client
	c2 = *c
	c2.Credentials = cr
	return &c2
}
<commit_msg>dont use a date which wont be maintained<commit_after>// Package parse provides a client for the Parse API.
package parse

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/facebookgo/httperr"
)

const (
	userAgentHeader     = "User-Agent"
	userAgent           = "go-parse-1"
	masterKeyHeader     = "X-Parse-Master-Key"
	restAPIKeyHeader    = "X-Parse-REST-API-Key"
	sessionTokenHeader  = "X-Parse-Session-Token"
	applicationIDHeader = "X-Parse-Application-Id"
)

var (
	errEmptyMasterKey    = errors.New("parse: cannot use empty MasterKey Credentials")
	errEmptyRestAPIKey   = errors.New("parse: cannot use empty RestAPIKey Credentials")
	errEmptySessionToken = errors.New("parse: cannot use empty SessionToken Credentials")

	// The default base URL for the API.
	defaultBaseURL = url.URL{
		Scheme: "https",
		Host:   "api.parse.com",
		Path:   "/1/",
	}
)

// Credentials allows for adding authentication information to a request.
type Credentials interface {
	Modify(r *http.Request) error
}

// MasterKey adds the Master Key to the request.
type MasterKey string

// Modify adds the Master Key header.
func (m MasterKey) Modify(r *http.Request) error {
	if m == "" {
		return errEmptyMasterKey
	}
	r.Header.Set(masterKeyHeader, string(m))
	return nil
}

// RestAPIKey adds the Rest API Key to the request.
type RestAPIKey string

// Modify adds the Rest API Key header.
func (k RestAPIKey) Modify(r *http.Request) error {
	if k == "" {
		return errEmptyRestAPIKey
	}
	r.Header.Set(restAPIKeyHeader, string(k))
	return nil
}

// SessionToken adds the Rest API Key and the Session Token to the request.
type SessionToken struct {
	RestAPIKey   string
	SessionToken string
}

// Modify adds the Session Token header.
func (t SessionToken) Modify(r *http.Request) error {
	if t.RestAPIKey == "" {
		return errEmptyRestAPIKey
	}
	if t.SessionToken == "" {
		return errEmptySessionToken
	}
	r.Header.Set(restAPIKeyHeader, string(t.RestAPIKey))
	r.Header.Set(sessionTokenHeader, string(t.SessionToken))
	return nil
}

// An Error from the Parse API.
type Error struct {
	// These are provided by the Parse API and may not always be available.
	Message string `json:"error"`
	Code    int    `json:"code"`

	// This is the HTTP StatusCode.
	StatusCode int `json:"-"`

	request  *http.Request
	response *http.Response
}

func (e *Error) Error() string {
	var buf bytes.Buffer
	if e.Code != 0 {
		fmt.Fprintf(&buf, "code %d", e.Code)
	}
	if e.Code != 0 && e.Message != "" {
		fmt.Fprint(&buf, " and ")
	}
	if e.Message != "" {
		fmt.Fprintf(&buf, "message %s", e.Message)
	}
	return httperr.NewError(
		errors.New(buf.String()),
		httperr.RedactNoOp(),
		e.request,
		e.response,
	).Error()
}

// Client provides access to the Parse API.
type Client struct {
	// The underlying http.RoundTripper to perform the individual requests. When
	// nil http.DefaultTransport will be used.
	Transport http.RoundTripper

	// The base URL to parse relative URLs off. If you pass absolute URLs to
	// Client functions they are used as-is. When nil, the production Parse URL
	// will be used.
	BaseURL *url.URL

	// Application ID will be included if not empty.
	ApplicationID string

	// Credentials if set, will be included on every request.
	Credentials Credentials
}

func (c *Client) transport() http.RoundTripper {
	if c.Transport == nil {
		return http.DefaultTransport
	}
	return c.Transport
}

// Get performs a GET method call on the given url and unmarshal response into
// result.
func (c *Client) Get(u *url.URL, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "GET", URL: u}, nil, result)
}

// Post performs a POST method call on the given url with the given body and
// unmarshal response into result.
func (c *Client) Post(u *url.URL, body, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "POST", URL: u}, body, result)
}

// Put performs a PUT method call on the given url with the given body and
// unmarshal response into result.
func (c *Client) Put(u *url.URL, body, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "PUT", URL: u}, body, result)
}

// Delete performs a DELETE method call on the given url and unmarshal response
// into result.
func (c *Client) Delete(u *url.URL, result interface{}) (*http.Response, error) {
	return c.Do(&http.Request{Method: "DELETE", URL: u}, nil, result)
}

// RoundTrip performs a RoundTrip ignoring the request and response bodies. It
// is up to the caller to close them. This method modifies the request.
func (c *Client) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Proto = "HTTP/1.1"
	req.ProtoMajor = 1
	req.ProtoMinor = 1

	if req.URL == nil {
		if c.BaseURL == nil {
			req.URL = &defaultBaseURL
		} else {
			req.URL = c.BaseURL
		}
	} else {
		if !req.URL.IsAbs() {
			if c.BaseURL == nil {
				req.URL = defaultBaseURL.ResolveReference(req.URL)
			} else {
				req.URL = c.BaseURL.ResolveReference(req.URL)
			}
		}
	}

	if req.Host == "" {
		req.Host = req.URL.Host
	}

	if req.Header == nil {
		req.Header = make(http.Header)
	}

	req.Header.Add(userAgentHeader, userAgent)
	if c.ApplicationID != "" {
		req.Header.Add(applicationIDHeader, c.ApplicationID)
	}

	if c.Credentials != nil {
		if err := c.Credentials.Modify(req); err != nil {
			return nil, err
		}
	}

	res, err := c.transport().RoundTrip(req)
	if err != nil {
		return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
	}

	if res.StatusCode > 399 || res.StatusCode < 200 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
		}

		apiErr := &Error{
			StatusCode: res.StatusCode,
			request:    req,
			response:   res,
		}
		if len(body) > 0 {
			err = json.Unmarshal(body, apiErr)
			if err != nil {
				return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
			}
		}
		return res, apiErr
	}

	return res, nil
}

// Do performs a Parse API call. This method modifies the request and adds the
// Authentication headers. The body is JSON encoded and for responses in the
// 2xx or 3xx range the response will be JSON decoded into result, for others
// an error of type Error will be returned.
func (c *Client) Do(req *http.Request, body, result interface{}) (*http.Response, error) {
	// we need to buffer as Parse requires a Content-Length
	if body != nil {
		bd, err := json.Marshal(body)
		if err != nil {
			return nil, httperr.NewError(err, httperr.RedactNoOp(), req, nil)
		}
		req.Body = ioutil.NopCloser(bytes.NewReader(bd))
		req.ContentLength = int64(len(bd))
	}

	res, err := c.RoundTrip(req)
	if err != nil {
		return res, err
	}
	defer res.Body.Close()

	if result != nil {
		if err := json.NewDecoder(res.Body).Decode(result); err != nil {
			return res, httperr.NewError(err, httperr.RedactNoOp(), req, res)
		}
	}
	return res, nil
}

// WithCredentials returns a new instance of the Client using the given
// Credentials. It discards the previous Credentials.
func (c *Client) WithCredentials(cr Credentials) *Client {
	var c2 Client
	c2 = *c
	c2.Credentials = cr
	return &c2
}
<|endoftext|>


<commit_before>package main;

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
)

type plcProjectInfo struct {
	ProjectInfo projectInfo
	RoutingInfo routringInfo
	DataTypes dataTypes
	Functions functions
	Programs programs
	Symbols symbols
}

type projectInfo struct {
	Path string
	ChangeDate string
}

type routringInfo struct {
	AdsInfo adsInfo
}

type adsInfo struct {
	NetId string
	Port string
}

type dataTypes struct {
	DataType []dataType
}

type dataType struct {
	Name string
	BitSize string
	SubItems []dataTypeSubItem
}

type dataTypeSubItem struct {
	Name string
	Type string
	BitSize string
	BitOffs string
}

type functions struct {
	Function []function
}

type function struct {
	Name string
}

type programs struct {
	Program []program
}

type program struct {
	Name string
	PrgInfo prgInfo
}

type prgInfo struct {
	Symbol []prgInfoSymbol
}

type prgInfoSymbol struct {
	Name string
}

type symbols struct {
	Symbol []symbol
}

type symbol struct {
	Name string
	Type string
	IGroup string
	IOffset string
	BitSize string
}

func main() {
	v := plcProjectInfo{}

	data, err := ioutil.ReadFile("/beckhoff/Hanaslöv.tpy")
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	err = xml.Unmarshal([]byte(data), &v)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	text,_ := json.MarshalIndent(v, "", "    ");
	fmt.Print(string(text));
}
<commit_msg>Added a start of tpy parsing<commit_after>package goADS;

import (
	//"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	log "github.com/cihub/seelog"
	"strconv"
)

type plcProjectInfo struct {
	ProjectInfo struct {
		Path string
		ChangeDate string
	}
	RoutingInfo struct {
		AdsInfo	struct {
			NetId string
			Port string
		}
	}
	DataTypes struct {
		DataType []dataType
	}
	Functions struct {
		Function []function
	}
	Programs struct {
		Program []program
	}
	Symbols struct {
		Symbol []symbol
	}
}

type dataType struct {
	Name string
	Type string
	BitSize uint32
	SubItem []dataTypeSubItem
	ArrayInfo struct {
		LBound string
		Elements string
		nLBound int
		nElements int
	}
}

type dataTypeSubItem struct {
	Name string
	Type string
	BitSize string
	BitOffs string
	nBitSize int
	nBitOffs int
}

type function struct {
	Name string
}

type program struct {
	Name string
	PrgInfo struct {
		Symbol []prgInfoSymbol
	}
}

type prgInfoSymbol struct {
	Name string
}

type symbol struct {
	Name string
	Type string
	IGroup uint32
	IOffset uint32
	BitSize uint32
}

func (conn *Connection) ParseTPY (path string) (symbols map[string]ADSSymbol) {
	v := plcProjectInfo{}

	data, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	err = xml.Unmarshal([]byte(data), &v)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

    log.Warn("DAtatypes: ",len(v.DataTypes.DataType));
    log.Warn("Symbols: ",len(v.Symbols.Symbol));

	// Loop thru data types/*{{{*/
	if conn.symbols == nil {
		conn.datatypes = map[string]ADSSymbolUploadDataType{}
	}
	for _, val := range(v.DataTypes.DataType) {
		dt := ADSSymbolUploadDataType{}

		dt.Name = val.Name
		dt.DataType = val.Type
		//dt.Comment
		//dt.Size
		dt.Offset = val.BitSize;

		if len(dt.DataType) > 6 {
			if dt.DataType[:6] == "STRING" {
				dt.DataType = "STRING"
			}
		}

		if val.ArrayInfo.LBound != "" || val.ArrayInfo.Elements != "" {

		}

		if val.SubItem != nil {
			if dt.Childs == nil {
				dt.Childs = map[string]ADSSymbolUploadDataType{}
			}

			for _, val2 := range(val.SubItem) {
				val2.nBitSize, _ = strconv.Atoi(val2.BitSize);
				val2.nBitOffs, _ = strconv.Atoi(val2.BitOffs);

				child := ADSSymbolUploadDataType{}
				child.Name = val2.Name
				child.DataType = val2.Type
				child.Size = uint32(val2.nBitSize)
				child.Offset = uint32(val2.nBitOffs)

				dt.Childs[child.Name] = child
			}
		}

		//if val.Name == "dtFlash" {
			//text,_ := json.MarshalIndent(val, "", "    ");
			//log.Info(string(text));
			//text,_ = json.MarshalIndent(dt, "", "    ");
			//log.Info(string(text));
		//}

		//if header.ArrayLevels > 0 {
			//// Childs is an array
			//var result arrayInfo
			//arrayLevels := []arrayInfo{}

			//for i := 0; i < int(header.ArrayLevels); i++ {
				//binary.Read(buff, binary.LittleEndian, &result)

				//arrayLevels = append(arrayLevels,result)
			//}

			////log.Warn(arrayLevels)

			//header.Childs = makeArrayChilds(arrayLevels,header.DataType,header.Size);

		//} else {
			//// Childs is standard variables
			//for buff.Len() > 0 {
				//if header.Childs == nil {
					//header.Childs = map[string]ADSSymbolUploadDataType{}
				//}

				//child := decodeSymbolUploadDataType(buff, header.Name)
				//header.Childs[child.Name] = child
			//}
		//}

		dt.conn = conn
		conn.datatypes[dt.Name] = dt
	}/*}}}*/

	// Loop thru all symbols/*{{{*/
	if conn.symbols == nil {
		conn.symbols = map[string]ADSSymbol{}
	}
	for _, val := range(v.Symbols.Symbol) {

		var item ADSSymbolUploadSymbol
		item.conn = conn
		item.Name = val.Name
		item.DataType = val.Type
		//item.Comment = ""
		item.Area = val.IGroup
		item.Offset = val.IOffset
		item.Length = val.BitSize
		//item.Extra1 = ""
		//item.Extra2 = ""

		if len(item.DataType) > 6 {
			if item.DataType[:6] == "STRING" {
				item.DataType = "STRING"
			}
		}

		conn.addSymbol(item)
	}/*}}}*/

	//ADSSymbolUploadDataType
	//conn.datatypes[header.Name] = header

	return conn.symbols
}
<|endoftext|>


<commit_before>package cli

import (
	"flag"
	"strings"
)

type iterativeParser interface {
	newFlagSet() (*flag.FlagSet, error)
	useShortOptionHandling() bool
}

// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
// Pass `shellComplete` to continue parsing options on failure during shell
// completion when, the user-supplied options may be incomplete.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
	for {
		err := set.Parse(args)
		if !ip.useShortOptionHandling() || err == nil {
			if shellComplete {
				return nil
			}
			return err
		}

		errStr := err.Error()
		trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
		if errStr == trimmed {
			return err
		}

		// regenerate the initial args with the split short opts
		argsWereSplit := false
		for i, arg := range args {
			// skip args that are not part of the error message
			if name := strings.TrimLeft(arg, "-"); name != trimmed {
				continue
			}

			// if we can't split, the error was accurate
			shortOpts := splitShortOptions(set, arg)
			if len(shortOpts) == 1 {
				return err
			}

			// Start processing only from failed argument and not
			// from beginning
			args = append(shortOpts, args[i+1:]...)
			argsWereSplit = true
			break
		}

		// This should be an impossible to reach code path, but in case the arg
		// splitting failed to happen, this will prevent infinite loops
		if !argsWereSplit {
			return err
		}

		// Since custom parsing failed, replace the flag set before retrying
		newSet, err := ip.newFlagSet()
		if err != nil {
			return err
		}
		*set = *newSet
	}
}

func splitShortOptions(set *flag.FlagSet, arg string) []string {
	shortFlagsExist := func(s string) bool {
		for _, c := range s[1:] {
			if f := set.Lookup(string(c)); f == nil {
				return false
			}
		}
		return true
	}

	if !isSplittable(arg) || !shortFlagsExist(arg) {
		return []string{arg}
	}

	separated := make([]string, 0, len(arg)-1)
	for _, flagChar := range arg[1:] {
		separated = append(separated, "-"+string(flagChar))
	}

	return separated
}

func isSplittable(flagArg string) bool {
	return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
}
<commit_msg>Rebase from master<commit_after>package cli

import (
	"flag"
	"strings"
)

type iterativeParser interface {
	newFlagSet() (*flag.FlagSet, error)
	useShortOptionHandling() bool
}

// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
// Pass `shellComplete` to continue parsing options on failure during shell
// completion when, the user-supplied options may be incomplete.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
	for {
		err := set.Parse(args)
		if !ip.useShortOptionHandling() || err == nil {
			if shellComplete {
				return nil
			}
			return err
		}

		errStr := err.Error()
		trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
		if errStr == trimmed {
			return err
		}

		// regenerate the initial args with the split short opts
		argsWereSplit := false
		for i, arg := range args {
			// skip args that are not part of the error message
			if name := strings.TrimLeft(arg, "-"); name != trimmed {
				continue
			}

			// if we can't split, the error was accurate
			shortOpts := splitShortOptions(set, arg)
			if len(shortOpts) == 1 {
				return err
			}

			// swap current argument with the split version
			args = append(args[:i], append(shortOpts, args[i+1:]...)...)
			argsWereSplit = true
			break
		}

		// This should be an impossible to reach code path, but in case the arg
		// splitting failed to happen, this will prevent infinite loops
		if !argsWereSplit {
			return err
		}

		// Since custom parsing failed, replace the flag set before retrying
		newSet, err := ip.newFlagSet()
		if err != nil {
			return err
		}
		*set = *newSet
	}
}

func splitShortOptions(set *flag.FlagSet, arg string) []string {
	shortFlagsExist := func(s string) bool {
		for _, c := range s[1:] {
			if f := set.Lookup(string(c)); f == nil {
				return false
			}
		}
		return true
	}

	if !isSplittable(arg) || !shortFlagsExist(arg) {
		return []string{arg}
	}

	separated := make([]string, 0, len(arg)-1)
	for _, flagChar := range arg[1:] {
		separated = append(separated, "-"+string(flagChar))
	}

	return separated
}

func isSplittable(flagArg string) bool {
	return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package networkinterface

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/r3labs/terraform/helper/hashcode"
	"github.com/r3labs/terraform/helper/schema"

	aes "github.com/ernestio/crypto/aes"
	"github.com/ernestio/ernestprovider/event"
	"github.com/ernestio/ernestprovider/providers/azure"
	"github.com/r3labs/terraform/builtin/providers/azurerm"
)

// Event : This is the Ernest representation of an azure networkinterface
type Event struct {
	event.Base
	ID                     string            `json:"id"`
	Name                   string            `json:"name" validate:"required"`
	ResourceGroupName      string            `json:"resource_group_name" validate:"required"`
	Location               string            `json:"location"`
	NetworkSecurityGroup   string            `json:"network_security_group"`
	NetworkSecurityGroupID string            `json:"network_security_group_id"`
	MacAddress             string            `json:"mac_address"`
	PrivateIPAddress       string            `json:"private_ip_address"`
	VirtualMachineID       string            `json:"virtual_machine_id"`
	IPConfigurations       []IPConfiguration `json:"ip_configuration"` // validate:"min=1,dive"`
	DNSServers             []string          `json:"dns_servers" validate:"dive,ip"`
	InternalDNSNameLabel   string            `json:"internal_dns_name_label"`
	AppliedDNSServers      []string          `json:"applied_dns_servers"`
	InternalFQDN           string            `json:"internal_fqdn"`
	EnableIPForwarding     bool              `json:"enable_ip_forwarding"`
	Tags                   map[string]string `json:"tags"`
	ClientID               string            `json:"azure_client_id"`
	ClientSecret           string            `json:"azure_client_secret"`
	TenantID               string            `json:"azure_tenant_id"`
	SubscriptionID         string            `json:"azure_subscription_id"`
	Environment            string            `json:"environment"`
	ErrorMessage           string            `json:"error,omitempty"`
	Components             []json.RawMessage `json:"components"`
	CryptoKey              string            `json:"-"`
	Validator              *event.Validator  `json:"-"`
	GenericEvent           event.Event       `json:"-" validate:"-"`
}

// IPConfiguration : ...
type IPConfiguration struct {
	Name                              string   `json:"name" validate:"required"`
	Subnet                            string   `json:"subnet" validate:"required"`
	SubnetID                          string   `json:"subnet_id" validate:"required"`
	PublicIPAddress                   string   `json:"public_ip_address"`
	PrivateIPAddress                  string   `json:"private_ip_address"`
	PrivateIPAddressAllocation        string   `json:"private_ip_address_allocation" validate:"required"`
	PublicIPAddressID                 string   `json:"public_ip_address_id"`
	LoadbalancerBackendAddressPools   []string `json:"load_balancer_backend_address_pools"`
	LoadBalancerBackendAddressPoolIDs []string `json:"load_balancer_backend_address_pools_ids"`
	LoadBalancerInboundNatRules       []string `json:"load_balancer_inbound_nat_rules_ids"`
}

// New : Constructor
func New(subject, cryptoKey string, body []byte, val *event.Validator) (event.Event, error) {
	// var ev event.Resource
	ev := &Event{CryptoKey: cryptoKey, Validator: val}
	body = []byte(strings.Replace(string(body), `"_component":"network_interfaces"`, `"_component":"network_interface"`, 1))
	if err := json.Unmarshal(body, &ev); err != nil {
		err := fmt.Errorf("Error on input message : %s", err)
		return nil, err
	}

	ev.GenericEvent, _ = azure.New(subject, "azurerm_network_interface", body, val, ev)
	return ev.GenericEvent, nil
}

// SetComponents : ....
func (ev *Event) SetComponents(components []event.Event) {
	for _, v := range components {
		ev.Components = append(ev.Components, v.GetBody())
	}
}

// ValidateID : determines if the given id is valid for this resource type
func (ev *Event) ValidateID(id string) bool {
	parts := strings.Split(strings.ToLower(id), "/")
	if len(parts) != 9 {
		return false
	}
	if parts[6] != "microsoft.network" {
		return false
	}
	if parts[7] != "networkinterfaces" {
		return false
	}
	return true
}

// SetID : id setter
func (ev *Event) SetID(id string) {
	ev.ID = id
}

// GetID : id getter
func (ev *Event) GetID() string {
	return ev.ID
}

// SetState : state setter
func (ev *Event) SetState(state string) {
	ev.State = state
}

// ResourceDataToEvent : Translates a ResourceData on a valid Ernest Event
func (ev *Event) ResourceDataToEvent(d *schema.ResourceData) error {

	ev.ID = d.Id()
	if ev.ID == "" {
		ev.Name = d.Get("name").(string)
	} else {
		parts := strings.Split(ev.ID, "/")
		ev.Name = parts[8]
	}
	ev.ComponentID = "network_interface::" + ev.Name
	ev.ResourceGroupName = d.Get("resource_group_name").(string)
	ev.Location = d.Get("location").(string)
	ev.NetworkSecurityGroupID = d.Get("network_security_group_id").(string)
	ev.MacAddress = d.Get("mac_address").(string)
	ev.PrivateIPAddress = d.Get("private_ip_address").(string)
	ev.VirtualMachineID = d.Get("virtual_machine_id").(string)

	configs := []IPConfiguration{}

	x := d.Get("ip_configuration").(*schema.Set)
	fmt.Println("%w", x)

	cli, _ := ev.GenericEvent.Client()
	list := cli.ListNetworkInterfaceConfigurations(ev.ResourceGroupName, ev.Name)

	for _, mo := range list {
		configs = append(configs, IPConfiguration{
			Name:                              mo["name"],
			SubnetID:                          mo["subnet_id"],
			PrivateIPAddress:                  mo["private_ip_address"],
			PrivateIPAddressAllocation:        mo["private_ip_address_allocation"],
			PublicIPAddressID:                 mo["public_ip_address_id"],
			LoadBalancerBackendAddressPoolIDs: strings.Split(mo["load_balancer_backend_address_pools_ids"], ","),
			LoadBalancerInboundNatRules:       strings.Split(mo["load_balancer_inbound_nat_rules_ids"], ","),
		})
	}
	ev.IPConfigurations = configs
	ev.DNSServers = make([]string, 0)
	for _, v := range d.Get("dns_servers").(*schema.Set).List() {
		ev.DNSServers = append(ev.DNSServers, v.(string))
	}

	ev.InternalDNSNameLabel = d.Get("internal_dns_name_label").(string)
	ev.AppliedDNSServers = make([]string, 0)
	for _, v := range d.Get("applied_dns_servers").(*schema.Set).List() {
		ev.AppliedDNSServers = append(ev.AppliedDNSServers, v.(string))
	}

	ev.InternalFQDN = d.Get("internal_fqdn").(string)
	ev.EnableIPForwarding = d.Get("enable_ip_forwarding").(bool)

	tags := d.Get("tags").(map[string]interface{})
	ev.Tags = make(map[string]string, 0)
	for k, v := range tags {
		ev.Tags[k] = v.(string)
	}

	return nil
}

// EventToResourceData : Translates the current event on a valid ResourceData
func (ev *Event) EventToResourceData(d *schema.ResourceData) error {
	crypto := aes.New()

	encFields := make(map[string]string)
	encFields["subscription_id"] = ev.SubscriptionID
	encFields["client_id"] = ev.ClientID
	encFields["client_secret"] = ev.ClientSecret
	encFields["tenant_id"] = ev.TenantID
	encFields["environment"] = ev.Environment
	for k, v := range encFields {
		dec, err := crypto.Decrypt(v, ev.CryptoKey)
		if err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
		if err := d.Set(k, dec); err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
	}

	fields := make(map[string]interface{})
	fields["name"] = ev.Name
	fields["resource_group_name"] = ev.ResourceGroupName
	fields["location"] = ev.Location
	fields["network_security_group_id"] = ev.NetworkSecurityGroupID
	fields["mac_address"] = ev.MacAddress
	fields["private_ip_address"] = ev.PrivateIPAddress
	fields["virtual_machine_id"] = ev.VirtualMachineID
	fields["ip_configuration"] = ev.mapIPConfigurations()
	fields["dns_servers"] = ev.DNSServers
	fields["internal_dns_name_label"] = ev.InternalDNSNameLabel
	fields["applied_dns_servers"] = ev.AppliedDNSServers
	fields["internal_fqdn"] = ev.InternalFQDN
	fields["enable_ip_forwarding"] = ev.EnableIPForwarding
	fields["tags"] = ev.Tags
	for k, v := range fields {
		if err := d.Set(k, v); err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
	}

	return nil
}

func (ev *Event) mapIPConfigurations() *schema.Set {
	list := &schema.Set{
		F: resourceArmNetworkInterfaceIPConfigurationHash,
	}
	for _, c := range ev.IPConfigurations {
		conf := map[string]interface{}{}
		conf["name"] = c.Name
		conf["subnet_id"] = c.SubnetID
		conf["private_ip_address"] = c.PrivateIPAddress
		conf["private_ip_address_allocation"] = c.PrivateIPAddressAllocation
		conf["public_ip_address_id"] = c.PublicIPAddressID
		l1 := schema.Set{}
		for _, v := range c.LoadBalancerBackendAddressPoolIDs {
			l1.Add(v)
		}
		conf["load_balancer_backend_address_pools_ids"] = &l1
		l2 := schema.Set{}
		for _, v := range c.LoadBalancerInboundNatRules {
			l2.Add(v)
		}
		conf["load_balancer_inbound_nat_rules_ids"] = &l2
		list.Add(conf)
	}
	return list
}

func resourceArmNetworkInterfaceIPConfigurationHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string)))
	if m["private_ip_address"] != nil {
		buf.WriteString(fmt.Sprintf("%s-", m["private_ip_address"].(string)))
	}
	buf.WriteString(fmt.Sprintf("%s-", m["private_ip_address_allocation"].(string)))
	if m["public_ip_address_id"] != nil {
		buf.WriteString(fmt.Sprintf("%s-", m["public_ip_address_id"].(string)))
	}
	if m["load_balancer_backend_address_pools_ids"] != nil {
		str := fmt.Sprintf("*Set(%s)", m["load_balancer_backend_address_pools_ids"].(*schema.Set))
		buf.WriteString(fmt.Sprintf("%s-", str))
	}
	if m["load_balancer_inbound_nat_rules_ids"] != nil {
		str := fmt.Sprintf("*Set(%s)", m["load_balancer_inbound_nat_rules_ids"].(*schema.Set))
		buf.WriteString(fmt.Sprintf("%s-", str))
	}

	return hashcode.String(buf.String())
}

// Clone : will mark the event as errored
func (ev *Event) Clone() (event.Event, error) {
	body, _ := json.Marshal(ev)
	return New(ev.Subject, ev.CryptoKey, body, ev.Validator)
}

// Error : will mark the event as errored
func (ev *Event) Error(err error) {
	ev.ErrorMessage = err.Error()
	ev.Body, err = json.Marshal(ev)
}

// Client : not implemented
func (ev *Event) Client() (*azurerm.ArmClient, error) {
	return nil, errors.New("Not implemented")
}
<commit_msg>fixing Loadbalancer -> LoadBalancer<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package networkinterface

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/r3labs/terraform/helper/hashcode"
	"github.com/r3labs/terraform/helper/schema"

	aes "github.com/ernestio/crypto/aes"
	"github.com/ernestio/ernestprovider/event"
	"github.com/ernestio/ernestprovider/providers/azure"
	"github.com/r3labs/terraform/builtin/providers/azurerm"
)

// Event : This is the Ernest representation of an azure networkinterface
type Event struct {
	event.Base
	ID                     string            `json:"id"`
	Name                   string            `json:"name" validate:"required"`
	ResourceGroupName      string            `json:"resource_group_name" validate:"required"`
	Location               string            `json:"location"`
	NetworkSecurityGroup   string            `json:"network_security_group"`
	NetworkSecurityGroupID string            `json:"network_security_group_id"`
	MacAddress             string            `json:"mac_address"`
	PrivateIPAddress       string            `json:"private_ip_address"`
	VirtualMachineID       string            `json:"virtual_machine_id"`
	IPConfigurations       []IPConfiguration `json:"ip_configuration"` // validate:"min=1,dive"`
	DNSServers             []string          `json:"dns_servers" validate:"dive,ip"`
	InternalDNSNameLabel   string            `json:"internal_dns_name_label"`
	AppliedDNSServers      []string          `json:"applied_dns_servers"`
	InternalFQDN           string            `json:"internal_fqdn"`
	EnableIPForwarding     bool              `json:"enable_ip_forwarding"`
	Tags                   map[string]string `json:"tags"`
	ClientID               string            `json:"azure_client_id"`
	ClientSecret           string            `json:"azure_client_secret"`
	TenantID               string            `json:"azure_tenant_id"`
	SubscriptionID         string            `json:"azure_subscription_id"`
	Environment            string            `json:"environment"`
	ErrorMessage           string            `json:"error,omitempty"`
	Components             []json.RawMessage `json:"components"`
	CryptoKey              string            `json:"-"`
	Validator              *event.Validator  `json:"-"`
	GenericEvent           event.Event       `json:"-" validate:"-"`
}

// IPConfiguration : ...
type IPConfiguration struct {
	Name                              string   `json:"name" validate:"required"`
	Subnet                            string   `json:"subnet" validate:"required"`
	SubnetID                          string   `json:"subnet_id" validate:"required"`
	PublicIPAddress                   string   `json:"public_ip_address"`
	PrivateIPAddress                  string   `json:"private_ip_address"`
	PrivateIPAddressAllocation        string   `json:"private_ip_address_allocation" validate:"required"`
	PublicIPAddressID                 string   `json:"public_ip_address_id"`
	LoadBalancerBackendAddressPools   []string `json:"load_balancer_backend_address_pools"`
	LoadBalancerBackendAddressPoolIDs []string `json:"load_balancer_backend_address_pools_ids"`
	LoadBalancerInboundNatRules       []string `json:"load_balancer_inbound_nat_rules_ids"`
}

// New : Constructor
func New(subject, cryptoKey string, body []byte, val *event.Validator) (event.Event, error) {
	// var ev event.Resource
	ev := &Event{CryptoKey: cryptoKey, Validator: val}
	body = []byte(strings.Replace(string(body), `"_component":"network_interfaces"`, `"_component":"network_interface"`, 1))
	if err := json.Unmarshal(body, &ev); err != nil {
		err := fmt.Errorf("Error on input message : %s", err)
		return nil, err
	}

	ev.GenericEvent, _ = azure.New(subject, "azurerm_network_interface", body, val, ev)
	return ev.GenericEvent, nil
}

// SetComponents : ....
func (ev *Event) SetComponents(components []event.Event) {
	for _, v := range components {
		ev.Components = append(ev.Components, v.GetBody())
	}
}

// ValidateID : determines if the given id is valid for this resource type
func (ev *Event) ValidateID(id string) bool {
	parts := strings.Split(strings.ToLower(id), "/")
	if len(parts) != 9 {
		return false
	}
	if parts[6] != "microsoft.network" {
		return false
	}
	if parts[7] != "networkinterfaces" {
		return false
	}
	return true
}

// SetID : id setter
func (ev *Event) SetID(id string) {
	ev.ID = id
}

// GetID : id getter
func (ev *Event) GetID() string {
	return ev.ID
}

// SetState : state setter
func (ev *Event) SetState(state string) {
	ev.State = state
}

// ResourceDataToEvent : Translates a ResourceData on a valid Ernest Event
func (ev *Event) ResourceDataToEvent(d *schema.ResourceData) error {

	ev.ID = d.Id()
	if ev.ID == "" {
		ev.Name = d.Get("name").(string)
	} else {
		parts := strings.Split(ev.ID, "/")
		ev.Name = parts[8]
	}
	ev.ComponentID = "network_interface::" + ev.Name
	ev.ResourceGroupName = d.Get("resource_group_name").(string)
	ev.Location = d.Get("location").(string)
	ev.NetworkSecurityGroupID = d.Get("network_security_group_id").(string)
	ev.MacAddress = d.Get("mac_address").(string)
	ev.PrivateIPAddress = d.Get("private_ip_address").(string)
	ev.VirtualMachineID = d.Get("virtual_machine_id").(string)

	configs := []IPConfiguration{}

	x := d.Get("ip_configuration").(*schema.Set)
	fmt.Println("%w", x)

	cli, _ := ev.GenericEvent.Client()
	list := cli.ListNetworkInterfaceConfigurations(ev.ResourceGroupName, ev.Name)

	for _, mo := range list {
		configs = append(configs, IPConfiguration{
			Name:                              mo["name"],
			SubnetID:                          mo["subnet_id"],
			PrivateIPAddress:                  mo["private_ip_address"],
			PrivateIPAddressAllocation:        mo["private_ip_address_allocation"],
			PublicIPAddressID:                 mo["public_ip_address_id"],
			LoadBalancerBackendAddressPoolIDs: strings.Split(mo["load_balancer_backend_address_pools_ids"], ","),
			LoadBalancerInboundNatRules:       strings.Split(mo["load_balancer_inbound_nat_rules_ids"], ","),
		})
	}
	ev.IPConfigurations = configs
	ev.DNSServers = make([]string, 0)
	for _, v := range d.Get("dns_servers").(*schema.Set).List() {
		ev.DNSServers = append(ev.DNSServers, v.(string))
	}

	ev.InternalDNSNameLabel = d.Get("internal_dns_name_label").(string)
	ev.AppliedDNSServers = make([]string, 0)
	for _, v := range d.Get("applied_dns_servers").(*schema.Set).List() {
		ev.AppliedDNSServers = append(ev.AppliedDNSServers, v.(string))
	}

	ev.InternalFQDN = d.Get("internal_fqdn").(string)
	ev.EnableIPForwarding = d.Get("enable_ip_forwarding").(bool)

	tags := d.Get("tags").(map[string]interface{})
	ev.Tags = make(map[string]string, 0)
	for k, v := range tags {
		ev.Tags[k] = v.(string)
	}

	return nil
}

// EventToResourceData : Translates the current event on a valid ResourceData
func (ev *Event) EventToResourceData(d *schema.ResourceData) error {
	crypto := aes.New()

	encFields := make(map[string]string)
	encFields["subscription_id"] = ev.SubscriptionID
	encFields["client_id"] = ev.ClientID
	encFields["client_secret"] = ev.ClientSecret
	encFields["tenant_id"] = ev.TenantID
	encFields["environment"] = ev.Environment
	for k, v := range encFields {
		dec, err := crypto.Decrypt(v, ev.CryptoKey)
		if err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
		if err := d.Set(k, dec); err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
	}

	fields := make(map[string]interface{})
	fields["name"] = ev.Name
	fields["resource_group_name"] = ev.ResourceGroupName
	fields["location"] = ev.Location
	fields["network_security_group_id"] = ev.NetworkSecurityGroupID
	fields["mac_address"] = ev.MacAddress
	fields["private_ip_address"] = ev.PrivateIPAddress
	fields["virtual_machine_id"] = ev.VirtualMachineID
	fields["ip_configuration"] = ev.mapIPConfigurations()
	fields["dns_servers"] = ev.DNSServers
	fields["internal_dns_name_label"] = ev.InternalDNSNameLabel
	fields["applied_dns_servers"] = ev.AppliedDNSServers
	fields["internal_fqdn"] = ev.InternalFQDN
	fields["enable_ip_forwarding"] = ev.EnableIPForwarding
	fields["tags"] = ev.Tags
	for k, v := range fields {
		if err := d.Set(k, v); err != nil {
			err := fmt.Errorf("Field '%s' not valid : %s", k, err)
			ev.Log("error", err.Error())
			return err
		}
	}

	return nil
}

func (ev *Event) mapIPConfigurations() *schema.Set {
	list := &schema.Set{
		F: resourceArmNetworkInterfaceIPConfigurationHash,
	}
	for _, c := range ev.IPConfigurations {
		conf := map[string]interface{}{}
		conf["name"] = c.Name
		conf["subnet_id"] = c.SubnetID
		conf["private_ip_address"] = c.PrivateIPAddress
		conf["private_ip_address_allocation"] = c.PrivateIPAddressAllocation
		conf["public_ip_address_id"] = c.PublicIPAddressID
		l1 := schema.Set{}
		for _, v := range c.LoadBalancerBackendAddressPoolIDs {
			l1.Add(v)
		}
		conf["load_balancer_backend_address_pools_ids"] = &l1
		l2 := schema.Set{}
		for _, v := range c.LoadBalancerInboundNatRules {
			l2.Add(v)
		}
		conf["load_balancer_inbound_nat_rules_ids"] = &l2
		list.Add(conf)
	}
	return list
}

func resourceArmNetworkInterfaceIPConfigurationHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string)))
	if m["private_ip_address"] != nil {
		buf.WriteString(fmt.Sprintf("%s-", m["private_ip_address"].(string)))
	}
	buf.WriteString(fmt.Sprintf("%s-", m["private_ip_address_allocation"].(string)))
	if m["public_ip_address_id"] != nil {
		buf.WriteString(fmt.Sprintf("%s-", m["public_ip_address_id"].(string)))
	}
	if m["load_balancer_backend_address_pools_ids"] != nil {
		str := fmt.Sprintf("*Set(%s)", m["load_balancer_backend_address_pools_ids"].(*schema.Set))
		buf.WriteString(fmt.Sprintf("%s-", str))
	}
	if m["load_balancer_inbound_nat_rules_ids"] != nil {
		str := fmt.Sprintf("*Set(%s)", m["load_balancer_inbound_nat_rules_ids"].(*schema.Set))
		buf.WriteString(fmt.Sprintf("%s-", str))
	}

	return hashcode.String(buf.String())
}

// Clone : will mark the event as errored
func (ev *Event) Clone() (event.Event, error) {
	body, _ := json.Marshal(ev)
	return New(ev.Subject, ev.CryptoKey, body, ev.Validator)
}

// Error : will mark the event as errored
func (ev *Event) Error(err error) {
	ev.ErrorMessage = err.Error()
	ev.Body, err = json.Marshal(ev)
}

// Client : not implemented
func (ev *Event) Client() (*azurerm.ArmClient, error) {
	return nil, errors.New("Not implemented")
}
<|endoftext|>