<commit_before>package main

import (
	"encoding/base64"
	"encoding/json"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net"

	"golang.org/x/crypto/ssh"
)

type source int

const (
	client source = iota
	server
)

func (src source) String() string {
	switch src {
	case client:
		return "client"
	case server:
		return "server"
	default:
		return "unknown"
	}
}

func (src source) MarshalJSON() ([]byte, error) {
	return json.Marshal(src.String())
}

type channelLog struct {
	ChannelID int `json:"channel_id"`
}

type requestLog struct {
	Type      string `json:"type"`
	WantReply bool   `json:"want_reply"`
	Payload   string `json:"payload"`

	Accepted bool `json:"accepted"`
}

type logEntry interface {
	eventType() string
}

type globalRequestLog struct {
	requestLog

	Response string `json:"response"`
}

func (entry globalRequestLog) eventType() string {
	return "global_request"
}

type newChannelLog struct {
	Type      string `json:"type"`
	ExtraData string `json:"extra_data"`

	Accepted bool `json:"accepted"`
}

func (entry newChannelLog) eventType() string {
	return "new_channel"
}

type channelRequestLog struct {
	channelLog
	requestLog
}

func (entry channelRequestLog) eventType() string {
	return "channel_request"
}

type channelDataLog struct {
	channelLog
	Data string `json:"data"`
}

func (entry channelDataLog) eventType() string {
	return "channel_data"
}

type channelErrorLog struct {
	channelLog
	Data string `json:"data"`
}

func (entry channelErrorLog) eventType() string {
	return "channel_error"
}

type channelEOFLog struct {
	channelLog
}

func (entry channelEOFLog) eventType() string {
	return "channel_eof"
}

type channelCloseLog struct {
	channelLog
}

func (entry channelCloseLog) eventType() string {
	return "channel_close"
}

type connectionCloseLog struct{}

func (entry connectionCloseLog) eventType() string {
	return "connection_close"
}

func logEvent(entry logEntry, src source) {
	jsonBytes, err := json.Marshal(struct {
		Source    string   `json:"source"`
		EventType string   `json:"event_type"`
		Event     logEntry `json:"event"`
	}{
		Source:    src.String(),
		EventType: entry.eventType(),
		Event:     entry,
	})
	if err != nil {
		panic(err)
	}
	log.Printf("%s", jsonBytes)
}

func streamReader(reader io.Reader) <-chan string {
	input := make(chan string)
	go func() {
		defer close(input)
		buffer := make([]byte, 256)
		for {
			n, err := reader.Read(buffer)
			if n > 0 {
				input <- string(buffer[:n])
			}
			if err != nil {
				if err != io.EOF {
					panic(err)
				}
				return
			}
		}
	}()
	return input
}

func handleChannel(channelID int, clientChannel ssh.Channel, clientRequests <-chan *ssh.Request, serverChannel ssh.Channel, serverRequests <-chan *ssh.Request) {
	clientInputStream := streamReader(clientChannel)
	serverInputStream := streamReader(serverChannel)
	serverErrorStream := streamReader(serverChannel.Stderr())

	for clientInputStream != nil || clientRequests != nil || serverInputStream != nil || serverRequests != nil {
		select {
		case clientInput, ok := <-clientInputStream:
			if !ok {
				if serverInputStream != nil {
					logEvent(channelEOFLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, client)
					if err := serverChannel.CloseWrite(); err != nil {
						panic(err)
					}
				}
				clientInputStream = nil
				continue
			}
			logEvent(channelDataLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: clientInput,
			}, client)
			if _, err := serverChannel.Write([]byte(clientInput)); err != nil {
				panic(err)
			}
		case clientRequest, ok := <-clientRequests:
			if !ok {
				if serverRequests != nil {
					logEvent(channelCloseLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, client)
					if err := serverChannel.Close(); err != nil {
						panic(err)
					}
				}
				clientRequests = nil
				continue
			}
			accepted, err := serverChannel.SendRequest(clientRequest.Type, clientRequest.WantReply, clientRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(channelRequestLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				requestLog: requestLog{
					Type:      clientRequest.Type,
					WantReply: clientRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(clientRequest.Payload),
					Accepted:  accepted,
				},
			}, client)
			if clientRequest.WantReply {
				if err := clientRequest.Reply(accepted, nil); err != nil {
					panic(err)
				}
			}
		case serverInput, ok := <-serverInputStream:
			if !ok {
				if clientInputStream != nil {
					logEvent(channelEOFLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, server)
					if err := clientChannel.CloseWrite(); err != nil {
						panic(err)
					}
				}
				serverInputStream = nil
				continue
			}
			logEvent(channelDataLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: serverInput,
			}, server)
			if _, err := clientChannel.Write([]byte(serverInput)); err != nil {
				panic(err)
			}
		case serverError, ok := <-serverErrorStream:
			if !ok {
				serverErrorStream = nil
				continue
			}
			logEvent(channelErrorLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: serverError,
			}, server)
			if _, err := clientChannel.Stderr().Write([]byte(serverError)); err != nil {
				panic(err)
			}
		case serverRequest, ok := <-serverRequests:
			if !ok {
				if clientRequests != nil {
					logEvent(channelCloseLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, server)
					if err := clientChannel.Close(); err != nil {
						panic(err)
					}
				}
				serverRequests = nil
				continue
			}
			accepted, err := clientChannel.SendRequest(serverRequest.Type, serverRequest.WantReply, serverRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(channelRequestLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				requestLog: requestLog{
					Type:      serverRequest.Type,
					WantReply: serverRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(serverRequest.Payload),
					Accepted:  accepted,
				},
			}, server)
			if serverRequest.WantReply {
				if err := serverRequest.Reply(accepted, nil); err != nil {
					panic(err)
				}
			}
		}
	}
}

func handleConn(clientConn net.Conn, sshServerConfig *ssh.ServerConfig, serverAddress string, clientKey ssh.Signer) {
	clientSSHConn, clientNewChannels, clientRequests, err := ssh.NewServerConn(clientConn, sshServerConfig)
	if err != nil {
		panic(err)
	}

	serverConn, err := net.Dial("tcp", serverAddress)
	if err != nil {
		panic(err)
	}

	serverSSHConn, serverNewChannels, serverRequests, err := ssh.NewClientConn(serverConn, serverAddress, &ssh.ClientConfig{
		User:            clientSSHConn.User(),
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(clientKey),
		},
		ClientVersion: "SSH-2.0-OpenSSH_7.2",
	})
	if err != nil {
		panic(err)
	}

	channelID := 0

	for clientNewChannels != nil || clientRequests != nil || serverNewChannels != nil || serverRequests != nil {
		select {
		case clientNewChannel, ok := <-clientNewChannels:
			if !ok {
				clientNewChannels = nil
				if serverNewChannels != nil {
					logEvent(connectionCloseLog{}, client)
					if err := serverSSHConn.Close(); err != nil {
						panic(err)
					}
				}
				continue
			}
			serverChannel, serverChannelRequests, err := serverSSHConn.OpenChannel(clientNewChannel.ChannelType(), clientNewChannel.ExtraData())
			logEvent(newChannelLog{
				Type:      clientNewChannel.ChannelType(),
				ExtraData: base64.RawStdEncoding.EncodeToString(clientNewChannel.ExtraData()),
				Accepted:  err == nil,
			}, client)
			if err != nil {
				if err, ok := err.(*ssh.OpenChannelError); ok {
					if err := clientNewChannel.Reject(err.Reason, err.Message); err != nil {
						panic(err)
					}
					continue
				}
				panic(err)
			}
			clientChannel, clientChannelRequests, err := clientNewChannel.Accept()
			if err != nil {
				panic(err)
			}
			go handleChannel(channelID, clientChannel, clientChannelRequests, serverChannel, serverChannelRequests)
			channelID++
		case clientRequest, ok := <-clientRequests:
			if !ok {
				clientRequests = nil
				continue
			}
			if clientRequest.Type == "no-more-sessions@openssh.com" {
				logEvent(globalRequestLog{
					requestLog: requestLog{
						Type:      clientRequest.Type,
						WantReply: clientRequest.WantReply,
						Payload:   base64.RawStdEncoding.EncodeToString(clientRequest.Payload),
						Accepted:  clientRequest.WantReply,
					},
					Response: base64.RawStdEncoding.EncodeToString([]byte{}),
				}, client)
				continue
			}
			accepted, response, err := serverSSHConn.SendRequest(clientRequest.Type, clientRequest.WantReply, clientRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(globalRequestLog{
				requestLog: requestLog{
					Type:      clientRequest.Type,
					WantReply: clientRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(clientRequest.Payload),
					Accepted:  accepted,
				},
				Response: base64.RawStdEncoding.EncodeToString(response),
			}, client)
			if err := clientRequest.Reply(accepted, response); err != nil {
				panic(err)
			}
		case serverNewChannel, ok := <-serverNewChannels:
			if !ok {
				if clientNewChannels != nil {
					logEvent(connectionCloseLog{}, server)
					if err := clientSSHConn.Close(); err != nil {
						panic(err)
					}
				}
				serverNewChannels = nil
				continue
			}
			panic(serverNewChannel.ChannelType())
		case serverRequest, ok := <-serverRequests:
			if !ok {
				serverRequests = nil
				continue
			}
			accepted, response, err := clientSSHConn.SendRequest(serverRequest.Type, serverRequest.WantReply, serverRequest.Payload)
			logEvent(globalRequestLog{
				requestLog: requestLog{
					Type:      serverRequest.Type,
					WantReply: serverRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(serverRequest.Payload),
					Accepted:  accepted,
				},
				Response: base64.RawStdEncoding.EncodeToString(response),
			}, server)
			if err != nil {
				panic(err)
			}
			if err := serverRequest.Reply(accepted, response); err != nil {
				panic(err)
			}
		}
	}
}

func main() {
	listenAddress := flag.String("listen_address", "127.0.0.1:2022", "listen address")
	hostKeyFile := flag.String("host_key_file", "", "host key file")
	serverAddress := flag.String("server_address", "127.0.0.1:22", "server address")
	clientKeyFile := flag.String("client_key_file", "", "client key file")
	flag.Parse()
	if *listenAddress == "" {
		panic("listen address is required")
	}
	if *hostKeyFile == "" {
		panic("host key file is required")
	}
	if *serverAddress == "" {
		panic("server address is required")
	}
	if *clientKeyFile == "" {
		panic("client key file is required")
	}

	log.SetFlags(0)
	log.SetOutput(os.Stdout)

	serverConfig := &ssh.ServerConfig{
		NoClientAuth:  true,
		ServerVersion: "SSH-2.0-OpenSSH_7.2",
	}
	hostKeyBytes, err := ioutil.ReadFile(*hostKeyFile)
	if err != nil {
		panic(err)
	}
	hostKey, err := ssh.ParsePrivateKey(hostKeyBytes)
	if err != nil {
		panic(err)
	}
	serverConfig.AddHostKey(hostKey)

	clientKeyBytes, err := ioutil.ReadFile(*clientKeyFile)
	if err != nil {
		panic(err)
	}
	clientKey, err := ssh.ParsePrivateKey(clientKeyBytes)
	if err != nil {
		panic(err)
	}

	listener, err := net.Listen("tcp", *listenAddress)
	if err != nil {
		panic(err)
	}
	defer listener.Close()
	for {
		conn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		go handleConn(conn, serverConfig, *serverAddress, clientKey)
	}
}
<commit_msg>testproxy: forward no-more-sessions requests but only after a bit of time<commit_after>package main

import (
	"encoding/base64"
	"encoding/json"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net"

	"golang.org/x/crypto/ssh"
)

type source int

const (
	client source = iota
	server
)

func (src source) String() string {
	switch src {
	case client:
		return "client"
	case server:
		return "server"
	default:
		return "unknown"
	}
}

func (src source) MarshalJSON() ([]byte, error) {
	return json.Marshal(src.String())
}

type channelLog struct {
	ChannelID int `json:"channel_id"`
}

type requestLog struct {
	Type      string `json:"type"`
	WantReply bool   `json:"want_reply"`
	Payload   string `json:"payload"`

	Accepted bool `json:"accepted"`
}

type logEntry interface {
	eventType() string
}

type globalRequestLog struct {
	requestLog

	Response string `json:"response"`
}

func (entry globalRequestLog) eventType() string {
	return "global_request"
}

type newChannelLog struct {
	Type      string `json:"type"`
	ExtraData string `json:"extra_data"`

	Accepted bool `json:"accepted"`
}

func (entry newChannelLog) eventType() string {
	return "new_channel"
}

type channelRequestLog struct {
	channelLog
	requestLog
}

func (entry channelRequestLog) eventType() string {
	return "channel_request"
}

type channelDataLog struct {
	channelLog
	Data string `json:"data"`
}

func (entry channelDataLog) eventType() string {
	return "channel_data"
}

type channelErrorLog struct {
	channelLog
	Data string `json:"data"`
}

func (entry channelErrorLog) eventType() string {
	return "channel_error"
}

type channelEOFLog struct {
	channelLog
}

func (entry channelEOFLog) eventType() string {
	return "channel_eof"
}

type channelCloseLog struct {
	channelLog
}

func (entry channelCloseLog) eventType() string {
	return "channel_close"
}

type connectionCloseLog struct{}

func (entry connectionCloseLog) eventType() string {
	return "connection_close"
}

func logEvent(entry logEntry, src source) {
	jsonBytes, err := json.Marshal(struct {
		Source    string   `json:"source"`
		EventType string   `json:"event_type"`
		Event     logEntry `json:"event"`
	}{
		Source:    src.String(),
		EventType: entry.eventType(),
		Event:     entry,
	})
	if err != nil {
		panic(err)
	}
	log.Printf("%s", jsonBytes)
}

func streamReader(reader io.Reader) <-chan string {
	input := make(chan string)
	go func() {
		defer close(input)
		buffer := make([]byte, 256)
		for {
			n, err := reader.Read(buffer)
			if n > 0 {
				input <- string(buffer[:n])
			}
			if err != nil {
				if err != io.EOF {
					panic(err)
				}
				return
			}
		}
	}()
	return input
}

func handleChannel(channelID int, clientChannel ssh.Channel, clientRequests <-chan *ssh.Request, serverChannel ssh.Channel, serverRequests <-chan *ssh.Request) {
	clientInputStream := streamReader(clientChannel)
	serverInputStream := streamReader(serverChannel)
	serverErrorStream := streamReader(serverChannel.Stderr())

	for clientInputStream != nil || clientRequests != nil || serverInputStream != nil || serverRequests != nil {
		select {
		case clientInput, ok := <-clientInputStream:
			if !ok {
				if serverInputStream != nil {
					logEvent(channelEOFLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, client)
					if err := serverChannel.CloseWrite(); err != nil {
						panic(err)
					}
				}
				clientInputStream = nil
				continue
			}
			logEvent(channelDataLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: clientInput,
			}, client)
			if _, err := serverChannel.Write([]byte(clientInput)); err != nil {
				panic(err)
			}
		case clientRequest, ok := <-clientRequests:
			if !ok {
				if serverRequests != nil {
					logEvent(channelCloseLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, client)
					if err := serverChannel.Close(); err != nil {
						panic(err)
					}
				}
				clientRequests = nil
				continue
			}
			accepted, err := serverChannel.SendRequest(clientRequest.Type, clientRequest.WantReply, clientRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(channelRequestLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				requestLog: requestLog{
					Type:      clientRequest.Type,
					WantReply: clientRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(clientRequest.Payload),
					Accepted:  accepted,
				},
			}, client)
			if clientRequest.WantReply {
				if err := clientRequest.Reply(accepted, nil); err != nil {
					panic(err)
				}
			}
		case serverInput, ok := <-serverInputStream:
			if !ok {
				if clientInputStream != nil {
					logEvent(channelEOFLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, server)
					if err := clientChannel.CloseWrite(); err != nil {
						panic(err)
					}
				}
				serverInputStream = nil
				continue
			}
			logEvent(channelDataLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: serverInput,
			}, server)
			if _, err := clientChannel.Write([]byte(serverInput)); err != nil {
				panic(err)
			}
		case serverError, ok := <-serverErrorStream:
			if !ok {
				serverErrorStream = nil
				continue
			}
			logEvent(channelErrorLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				Data: serverError,
			}, server)
			if _, err := clientChannel.Stderr().Write([]byte(serverError)); err != nil {
				panic(err)
			}
		case serverRequest, ok := <-serverRequests:
			if !ok {
				if clientRequests != nil {
					logEvent(channelCloseLog{
						channelLog: channelLog{
							ChannelID: channelID,
						},
					}, server)
					if err := clientChannel.Close(); err != nil {
						panic(err)
					}
				}
				serverRequests = nil
				continue
			}
			accepted, err := clientChannel.SendRequest(serverRequest.Type, serverRequest.WantReply, serverRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(channelRequestLog{
				channelLog: channelLog{
					ChannelID: channelID,
				},
				requestLog: requestLog{
					Type:      serverRequest.Type,
					WantReply: serverRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(serverRequest.Payload),
					Accepted:  accepted,
				},
			}, server)
			if serverRequest.WantReply {
				if err := serverRequest.Reply(accepted, nil); err != nil {
					panic(err)
				}
			}
		}
	}
}

func handleConn(clientConn net.Conn, sshServerConfig *ssh.ServerConfig, serverAddress string, clientKey ssh.Signer) {
	clientSSHConn, clientNewChannels, clientRequests, err := ssh.NewServerConn(clientConn, sshServerConfig)
	if err != nil {
		panic(err)
	}

	serverConn, err := net.Dial("tcp", serverAddress)
	if err != nil {
		panic(err)
	}

	serverSSHConn, serverNewChannels, serverRequests, err := ssh.NewClientConn(serverConn, serverAddress, &ssh.ClientConfig{
		User:            clientSSHConn.User(),
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(clientKey),
		},
		ClientVersion: "SSH-2.0-OpenSSH_7.2",
	})
	if err != nil {
		panic(err)
	}

	channelID := 0

	for clientNewChannels != nil || clientRequests != nil || serverNewChannels != nil || serverRequests != nil {
		select {
		case clientNewChannel, ok := <-clientNewChannels:
			if !ok {
				clientNewChannels = nil
				if serverNewChannels != nil {
					logEvent(connectionCloseLog{}, client)
					if err := serverSSHConn.Close(); err != nil {
						panic(err)
					}
				}
				continue
			}
			serverChannel, serverChannelRequests, err := serverSSHConn.OpenChannel(clientNewChannel.ChannelType(), clientNewChannel.ExtraData())
			logEvent(newChannelLog{
				Type:      clientNewChannel.ChannelType(),
				ExtraData: base64.RawStdEncoding.EncodeToString(clientNewChannel.ExtraData()),
				Accepted:  err == nil,
			}, client)
			if err != nil {
				if err, ok := err.(*ssh.OpenChannelError); ok {
					if err := clientNewChannel.Reject(err.Reason, err.Message); err != nil {
						panic(err)
					}
					continue
				}
				panic(err)
			}
			clientChannel, clientChannelRequests, err := clientNewChannel.Accept()
			if err != nil {
				panic(err)
			}
			go handleChannel(channelID, clientChannel, clientChannelRequests, serverChannel, serverChannelRequests)
			channelID++
		case clientRequest, ok := <-clientRequests:
			if !ok {
				clientRequests = nil
				continue
			}
			go func() {
			if clientRequest.Type == "no-more-sessions@openssh.com" {
					time.Sleep(100 * time.Millisecond)
			}
			accepted, response, err := serverSSHConn.SendRequest(clientRequest.Type, clientRequest.WantReply, clientRequest.Payload)
			if err != nil {
				panic(err)
			}
			logEvent(globalRequestLog{
				requestLog: requestLog{
					Type:      clientRequest.Type,
					WantReply: clientRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(clientRequest.Payload),
					Accepted:  accepted,
				},
				Response: base64.RawStdEncoding.EncodeToString(response),
			}, client)
			if err := clientRequest.Reply(accepted, response); err != nil {
				panic(err)
			}
			}()
		case serverNewChannel, ok := <-serverNewChannels:
			if !ok {
				if clientNewChannels != nil {
					logEvent(connectionCloseLog{}, server)
					if err := clientSSHConn.Close(); err != nil {
						panic(err)
					}
				}
				serverNewChannels = nil
				continue
			}
			panic(serverNewChannel.ChannelType())
		case serverRequest, ok := <-serverRequests:
			if !ok {
				serverRequests = nil
				continue
			}
			accepted, response, err := clientSSHConn.SendRequest(serverRequest.Type, serverRequest.WantReply, serverRequest.Payload)
			logEvent(globalRequestLog{
				requestLog: requestLog{
					Type:      serverRequest.Type,
					WantReply: serverRequest.WantReply,
					Payload:   base64.RawStdEncoding.EncodeToString(serverRequest.Payload),
					Accepted:  accepted,
				},
				Response: base64.RawStdEncoding.EncodeToString(response),
			}, server)
			if err != nil {
				panic(err)
			}
			if err := serverRequest.Reply(accepted, response); err != nil {
				panic(err)
			}
		}
	}
}

func main() {
	listenAddress := flag.String("listen_address", "127.0.0.1:2022", "listen address")
	hostKeyFile := flag.String("host_key_file", "", "host key file")
	serverAddress := flag.String("server_address", "127.0.0.1:22", "server address")
	clientKeyFile := flag.String("client_key_file", "", "client key file")
	flag.Parse()
	if *listenAddress == "" {
		panic("listen address is required")
	}
	if *hostKeyFile == "" {
		panic("host key file is required")
	}
	if *serverAddress == "" {
		panic("server address is required")
	}
	if *clientKeyFile == "" {
		panic("client key file is required")
	}

	log.SetFlags(0)
	log.SetOutput(os.Stdout)

	serverConfig := &ssh.ServerConfig{
		NoClientAuth:  true,
		ServerVersion: "SSH-2.0-OpenSSH_7.2",
	}
	hostKeyBytes, err := ioutil.ReadFile(*hostKeyFile)
	if err != nil {
		panic(err)
	}
	hostKey, err := ssh.ParsePrivateKey(hostKeyBytes)
	if err != nil {
		panic(err)
	}
	serverConfig.AddHostKey(hostKey)

	clientKeyBytes, err := ioutil.ReadFile(*clientKeyFile)
	if err != nil {
		panic(err)
	}
	clientKey, err := ssh.ParsePrivateKey(clientKeyBytes)
	if err != nil {
		panic(err)
	}

	listener, err := net.Listen("tcp", *listenAddress)
	if err != nil {
		panic(err)
	}
	defer listener.Close()
	for {
		conn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		go handleConn(conn, serverConfig, *serverAddress, clientKey)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/ActiveState/tail"
	"github.com/op/go-logging"
	"regexp"
	"strings"
)

func main() {

	// Setup logger https://github.com/op/go-logging/blob/master/examples/example.go
	var log = logging.MustGetLogger("example")

	var dss []string

	// Example format string. Everything except the message has a custom color
	// which is dependent on the log level. Many fields have a custom output
	// formatting too, eg. the time returns the hour down to the milli second.
	var format = "%{color}%{time:15:04:05.000000} [%{pid}] ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
	logging.SetFormatter(logging.MustStringFormatter(format))

	log.Info("Graphite News -- Showing which new metrics are available since 2014\n")
	log.Notice("Graphite News -- Showing which new metrics are available since 2014\n")
	log.Error("Graphite News -- Showing which new metrics are available since 2014\n")
	var dataPath = regexp.MustCompile(`\[creates\] creating (database) file .*/whisper/(.*)\.wsp`)

	t, err := tail.TailFile("./creates.log", tail.Config{Follow: true, ReOpen: true, MustExist: true})
	if err == nil {
		for line := range t.Lines {
			match := dataPath.FindStringSubmatch(line.Text)
			//	log.Info(match)
			if len(match) > 0 {
				log.Info(line.Text)
				ds := fmt.Sprintf("%v", strings.Replace(match[2], `/`, `.`, -1))
				dss = append(dss, ds)
				log.Notice(fmt.Sprintf("Found new datasource, total: %v, newly added: %s", len(dss), ds))
			}
		}
	}
	fmt.Println(err)
}
<commit_msg>Adding serving content<commit_after>package main

import (
	"fmt"
	"github.com/ActiveState/tail"
	"github.com/op/go-logging"
	"net/http"
	"regexp"
	"strings"
)

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fn(w, r, "sadfds")
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	var log = logging.MustGetLogger("example")
	log.Info(fmt.Sprintf("Request for %s\n", r.URL.Path))
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])

}

func main() {

	// Setup logger https://github.com/op/go-logging/blob/master/examples/example.go
	var log = logging.MustGetLogger("example")

	var dss []string

	// Example format string. Everything except the message has a custom color
	// which is dependent on the log level. Many fields have a custom output
	// formatting too, eg. the time returns the hour down to the milli second.
	var format = "%{color}%{time:15:04:05.000000} [%{pid}] ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
	logging.SetFormatter(logging.MustStringFormatter(format))

	log.Info("Graphite News -- Showing which new metrics are available since 2014\n")
	log.Notice("Graphite News -- Serving UI on: http://localhost:2934\n")

	http.HandleFunc("/view/", makeHandler(viewHandler))
	go http.ListenAndServe(":2934", nil)

	log.Notice("Graphite News -- Showing which new metrics are available since 2014\n")
	var dataPath = regexp.MustCompile(`\[creates\] creating (database) file .*/whisper/(.*)\.wsp`)

	t, err := tail.TailFile("./creates.log", tail.Config{Follow: true, ReOpen: true, MustExist: true})
	if err == nil {
		for line := range t.Lines {
			match := dataPath.FindStringSubmatch(line.Text)
			//	log.Info(match)
			if len(match) > 0 {
				log.Info(line.Text)
				ds := fmt.Sprintf("%v", strings.Replace(match[2], `/`, `.`, -1))
				dss = append(dss, ds)
				log.Notice(fmt.Sprintf("Found new datasource, total: %v, newly added: %s", len(dss), ds))
			}
		}
	}
	fmt.Println(err)
}
<|endoftext|>


<commit_before>package main

import (
    "log"
    "time"
    "github.com/jroimartin/gocui"
    "github.com/kr/beanstalk"
)

var (
    conn *beanstalk.Conn
    cTubes Tubes
    watch = false
    stop = make(chan bool)
)

func main() {
    var err error
    if conn, err = beanstalk.Dial("tcp", "127.0.0.1:11300"); err != nil {
        log.Fatal(err)
    }

    //Use all tubes by default
    cTubes.UseAll()

    g := gocui.NewGui()
    if err := g.Init(); err != nil {
        log.Fatal(err)
    }
    defer g.Close()

    g.SetLayout(listLayout)
    if err = SetMenuLayout(g); err != nil {
        log.Fatal(err)
    }
    go watchTubes(g)

    if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
        log.Fatal(err)
    }

    if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
        log.Fatal(err)
    }
}

func listLayout(g *gocui.Gui) error {
    maxX, maxY := g.Size()
    if v, err := g.SetView("tubes", 0, 0, maxX-1, maxY-3); err != nil {
        if err != gocui.ErrUnknownView {
            return err
        }

        PrintTubeList(v)
    }

    return nil
}

func SetMenuLayout(g *gocui.Gui) error {
    maxX, maxY := g.Size()
    if v, err := g.SetView("menu", 0, maxY-3, maxX-1, maxY-1); err != nil {
        if err != gocui.ErrUnknownView {
            return err
        }

        PrintMenu(v)
    }

    _, err := g.SetViewOnTop("menu")

    return err
}

func reloadMenu(g *gocui.Gui) error {
    v, err := g.View("menu")
    if err != nil {
        return err
    }

    v.Clear()
    PrintMenu(v)

    _, err = g.SetViewOnTop("menu")

    return err
}

func watchTubes(g *gocui.Gui) {
    for {
        select {
            case <-stop:
                watch = false
                return
            case <-time.After(1 * time.Second):
                watch = true
                //Refresh tube list
                g.Execute(func(g *gocui.Gui) error {
                    v, err := g.View("tubes")
                    if err != nil {
                        return err
                    }

                    v.Clear()
                    PrintTubeList(v)

                    return nil
                })

                _ = reloadMenu(g);
        }
    }
}

func quit(g *gocui.Gui, v *gocui.View) error {
    stop <- true

    return gocui.ErrQuit
}
<commit_msg>Adds menu to layout to fix window resize bug<commit_after>package main

import (
    "log"
    "time"
    "github.com/jroimartin/gocui"
    "github.com/kr/beanstalk"
)

var (
    conn *beanstalk.Conn
    cTubes Tubes
    watch = false
    stop = make(chan bool)
)

func main() {
    var err error
    if conn, err = beanstalk.Dial("tcp", "127.0.0.1:11300"); err != nil {
        log.Fatal(err)
    }

    //Use all tubes by default
    cTubes.UseAll()

    g := gocui.NewGui()
    if err := g.Init(); err != nil {
        log.Fatal(err)
    }
    defer g.Close()

    g.SetLayout(setLayout)
    go watchTubes(g)

    if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
        log.Fatal(err)
    }

    if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
        log.Fatal(err)
    }
}

func setLayout(g *gocui.Gui) error {
    maxX, maxY := g.Size()
    if v, err := g.SetView("tubes", 0, 0, maxX-1, maxY-3); err != nil {
        if err != gocui.ErrUnknownView {
            return err
        }

        PrintTubeList(v)
    }

    if v, err := g.SetView("menu", 0, maxY-3, maxX-1, maxY-1); err != nil {
        if err != gocui.ErrUnknownView {
            return err
        }

        PrintMenu(v)
    }

    return nil
}

func reloadMenu(g *gocui.Gui) error {
    v, err := g.View("menu")
    if err != nil {
        return err
    }

    v.Clear()
    PrintMenu(v)

    _, err = g.SetViewOnTop("menu")

    return err
}

func watchTubes(g *gocui.Gui) {
    for {
        select {
            case <-stop:
                watch = false
                return
            case <-time.After(1 * time.Second):
                watch = true
                //Refresh tube list
                g.Execute(func(g *gocui.Gui) error {
                    v, err := g.View("tubes")
                    if err != nil {
                        return err
                    }

                    v.Clear()
                    PrintTubeList(v)

                    return nil
                })

                _ = reloadMenu(g);
        }
    }
}

func quit(g *gocui.Gui, v *gocui.View) error {
    stop <- true

    return gocui.ErrQuit
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var versionStr = "0.1.0"

func main() {
	var dirArgs = []string{}
	var cmdArgs = []string{}

	var hasDash bool = false
	var nArgs = len(os.Args)
	for n := 1; n < nArgs; n++ {
		arg := os.Args[n]
		if arg == "--" {
			hasDash = true
			continue
		}
		tokens := strings.SplitN(arg, "=", 2)
		flag, value := "", ""
		switch len(tokens) {
		case 1:
			flag = tokens[0]
			if n < nArgs - 1 && !options.IsBool(flag) {
				value = os.Args[n+1]
				n++
			}
		case 2:
			flag = tokens[0]
			value = tokens[1]
		default:
			continue
		}

		// everything after the dash, should be the command arguments
		if !hasDash && flag[0] == '-' {
			option := options.Get(flag[1:])
			if option == nil {
				log.Fatalf("Invalid option: '%v'\n", flag)
			} else {
				if _, ok := option.value.(string); ok {
					option.value = value
				} else {
					option.value = true
				}
			}
		} else {
			if !hasDash {
				if exists, _ := FileExists(arg); exists {
					dirArgs = append(dirArgs, arg)
				} else {
					log.Printf("Invalid path: '%v'", arg)
				}
			} else {
				cmdArgs = append(cmdArgs, arg)
			}
		}
	}

	if options.Bool("h") {
		fmt.Println("Usage: gomon [options] [dir] [-- command]")
		for _, option := range options {
			if _, ok := option.value.(string); ok {
				fmt.Printf("  -%s=%s: %s\n", option.flag, option.value, option.description)
			} else {
				fmt.Printf("  -%s: %s\n", option.flag, option.description)
			}
		}
		os.Exit(0)
	}
	if options.Bool("v") {
		fmt.Printf("gomon %s\n", versionStr)
		os.Exit(0)
	}

	if options.Bool("install-growl-icons") {
		installGrowlIcons()
		os.Exit(0)
	}

	var cmds = CommandSet{}
	var cmd = Command(cmdArgs)

	_ = cmds

	if len(cmd) == 0 {
		if options.Bool("t") {
			cmd = goCommands["test"]
		} else if options.Bool("i") {
			cmd = goCommands["install"]
		} else if options.Bool("f") {
			cmd = goCommands["fmt"]
		} else if options.Bool("r") {
			cmd = goCommands["run"]
		} else if options.Bool("b") {
			cmd = goCommands["build"]
		} else {
			// default behavior
			cmd = goCommands["build"]
		}
		if options.Bool("x") && len(cmd) > 0 {
			cmd = append(cmd, "-x")
		}
	}

	if len(cmd) == 0 {
		fmt.Println("No command specified")
		os.Exit(2)
	}

	if len(dirArgs) == 0 {
		var cwd, err = os.Getwd()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		dirArgs = []string{cwd}
	}

	fmt.Println("Watching", dirArgs, "for", cmd)

	watcher, err := fsnotify.NewWatcher()

	if err != nil {
		log.Fatal(err)
	}

	for _, dir := range dirArgs {
		subfolders := Subfolders(dir)
		for _, f := range subfolders {
			err = watcher.WatchFlags(f, fsnotify.FSN_ALL)
			if err != nil {
				log.Fatal(err)
			}
		}
	}

	var wasFailed bool = false
	var task *exec.Cmd

	runCommand := func(task *exec.Cmd) {
		var err error
		err = task.Start()
		if err != nil {
			log.Println(err)
			if options.Bool("growl") {
				notifyFail(options.String("gntp"), err.Error(), "")
			}
			failed("Failed!")
			wasFailed = true
			return
		}
		err = task.Wait()
		if err != nil {
			log.Println(err)
			if options.Bool("growl") {
				notifyFail(options.String("gntp"), err.Error(), "")
			}
			failed("Failed!")
			wasFailed = true
			return
		}

		// fixed
		if wasFailed {
			wasFailed = false
			if options.Bool("growl") {
				notifyFixed(options.String("gntp"), "Congratulations! It's fixed!", "")
			}
			success("Congratulations! It's fixed!")
		}
	}

	var fired bool = false
	for {
		select {
		case e := <-watcher.Event:
			matched, err := regexp.MatchString("\\.(go|c|h)$", e.Name)
			if err != nil {
				log.Println(err)
			}

			if !matched {
				if options.Bool("d") {
					log.Println("Ignore:", e)
				}
				continue
			}

			if options.Bool("d") {
				log.Println("Event:", e)
			} else {
				log.Println(e.Name)
			}

			if !fired {
				fired = true
				go func(dir string) {
					// duration to avoid to run commands frequency at once
					select {
					case <-time.After(100 * time.Millisecond):
						fired = false
						if task != nil && task.ProcessState != nil && !task.ProcessState.Exited() {
							fmt.Println("Stopping Task...")
							err := task.Process.Kill()
							if err != nil {
								log.Println(err)
							}
						}
						fmt.Println("Running Task:", cmd)
						task = exec.Command(cmd[0], cmd[1:]...)
						task.Stdout = os.Stdout
						task.Stderr = os.Stderr
						if options.Bool("chdir") {
							task.Dir = dir
						}
						runCommand(task)
					}
				}(filepath.Dir(e.Name))
			}

		case err := <-watcher.Error:
			log.Println("Error:", err)
		}
	}

	watcher.Close()
}
<commit_msg>Fixed an argument for options.IsBool<commit_after>package main

import (
	"fmt"
	"github.com/howeyc/fsnotify"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var versionStr = "0.1.0"

func main() {
	var dirArgs = []string{}
	var cmdArgs = []string{}

	var hasDash bool = false
	var nArgs = len(os.Args)
	for n := 1; n < nArgs; n++ {
		arg := os.Args[n]
		if arg == "--" {
			hasDash = true
			continue
		}
		tokens := strings.SplitN(arg, "=", 2)
		flag, value := "", ""
		switch len(tokens) {
		case 1:
			flag = tokens[0]
			if n < nArgs - 1 && !options.IsBool(flag[1:]) {
				value = os.Args[n+1]
				n++
			}
		case 2:
			flag = tokens[0]
			value = tokens[1]
		default:
			continue
		}

		// everything after the dash, should be the command arguments
		if !hasDash && flag[0] == '-' {
			option := options.Get(flag[1:])
			if option == nil {
				log.Fatalf("Invalid option: '%v'\n", flag)
			} else {
				if _, ok := option.value.(string); ok {
					option.value = value
				} else {
					option.value = true
				}
			}
		} else {
			if !hasDash {
				if exists, _ := FileExists(arg); exists {
					dirArgs = append(dirArgs, arg)
				} else {
					log.Printf("Invalid path: '%v'", arg)
				}
			} else {
				cmdArgs = append(cmdArgs, arg)
			}
		}
	}

	if options.Bool("h") {
		fmt.Println("Usage: gomon [options] [dir] [-- command]")
		for _, option := range options {
			if _, ok := option.value.(string); ok {
				fmt.Printf("  -%s=%s: %s\n", option.flag, option.value, option.description)
			} else {
				fmt.Printf("  -%s: %s\n", option.flag, option.description)
			}
		}
		os.Exit(0)
	}
	if options.Bool("v") {
		fmt.Printf("gomon %s\n", versionStr)
		os.Exit(0)
	}

	if options.Bool("install-growl-icons") {
		installGrowlIcons()
		os.Exit(0)
	}

	var cmds = CommandSet{}
	var cmd = Command(cmdArgs)

	_ = cmds

	if len(cmd) == 0 {
		if options.Bool("t") {
			cmd = goCommands["test"]
		} else if options.Bool("i") {
			cmd = goCommands["install"]
		} else if options.Bool("f") {
			cmd = goCommands["fmt"]
		} else if options.Bool("r") {
			cmd = goCommands["run"]
		} else if options.Bool("b") {
			cmd = goCommands["build"]
		} else {
			// default behavior
			cmd = goCommands["build"]
		}
		if options.Bool("x") && len(cmd) > 0 {
			cmd = append(cmd, "-x")
		}
	}

	if len(cmd) == 0 {
		fmt.Println("No command specified")
		os.Exit(2)
	}

	if len(dirArgs) == 0 {
		var cwd, err = os.Getwd()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		dirArgs = []string{cwd}
	}

	fmt.Println("Watching", dirArgs, "for", cmd)

	watcher, err := fsnotify.NewWatcher()

	if err != nil {
		log.Fatal(err)
	}

	for _, dir := range dirArgs {
		subfolders := Subfolders(dir)
		for _, f := range subfolders {
			err = watcher.WatchFlags(f, fsnotify.FSN_ALL)
			if err != nil {
				log.Fatal(err)
			}
		}
	}

	var wasFailed bool = false
	var task *exec.Cmd

	runCommand := func(task *exec.Cmd) {
		var err error
		err = task.Start()
		if err != nil {
			log.Println(err)
			if options.Bool("growl") {
				notifyFail(options.String("gntp"), err.Error(), "")
			}
			failed("Failed!")
			wasFailed = true
			return
		}
		err = task.Wait()
		if err != nil {
			log.Println(err)
			if options.Bool("growl") {
				notifyFail(options.String("gntp"), err.Error(), "")
			}
			failed("Failed!")
			wasFailed = true
			return
		}

		// fixed
		if wasFailed {
			wasFailed = false
			if options.Bool("growl") {
				notifyFixed(options.String("gntp"), "Congratulations! It's fixed!", "")
			}
			success("Congratulations! It's fixed!")
		}
	}

	var fired bool = false
	for {
		select {
		case e := <-watcher.Event:
			matched, err := regexp.MatchString("\\.(go|c|h)$", e.Name)
			if err != nil {
				log.Println(err)
			}

			if !matched {
				if options.Bool("d") {
					log.Println("Ignore:", e)
				}
				continue
			}

			if options.Bool("d") {
				log.Println("Event:", e)
			} else {
				log.Println(e.Name)
			}

			if !fired {
				fired = true
				go func(dir string) {
					// duration to avoid to run commands frequency at once
					select {
					case <-time.After(100 * time.Millisecond):
						fired = false
						if task != nil && task.ProcessState != nil && !task.ProcessState.Exited() {
							fmt.Println("Stopping Task...")
							err := task.Process.Kill()
							if err != nil {
								log.Println(err)
							}
						}
						fmt.Println("Running Task:", cmd)
						task = exec.Command(cmd[0], cmd[1:]...)
						task.Stdout = os.Stdout
						task.Stderr = os.Stderr
						if options.Bool("chdir") {
							task.Dir = dir
						}
						runCommand(task)
					}
				}(filepath.Dir(e.Name))
			}

		case err := <-watcher.Error:
			log.Println("Error:", err)
		}
	}

	watcher.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	progname    = "UnityLinks"
	progversion = "0.5.3"
)

// Config holds the setup for the web server
type Config struct {
	Main struct {
		Address  string
		Port     int
		Sitename string
	}
}

var cfg Config

func init() {
	rand.Seed(time.Now().UnixNano())
	if !fexists("versions") {
		info("Creating data directory.")
		os.MkdirAll("versions", 0700)
	}
	if !fexists("updates") {
		info("Creating update directory.")
		os.MkdirAll("updates", 0700)
	}
}

func main() {
	var runupdates = flag.Bool("u", false, "Run updates instead of launching web server.")
	var address = flag.String("a", "0.0.0.0", "Address to bind to.")
	var port = flag.Int("p", 8000, "Port to bind to.")
	var display = flag.Bool("d", false, "Display versions and URLs available, then exit.")
	var version = flag.String("v", "", "Display a specific version. If not specified you get everything.")
	flag.Parse()

	if *runupdates {
		updateVersions()
		return
	}

	if *display {
		if *version == "" {
			displayVersions()
		} else {
			displayVersion(*version)
		}
		return
	}

	cfg.Main.Address = *address
	cfg.Main.Port = *port
	cfg.Main.Sitename = "Unity download links"
	initWeb()
	endWeb()
}

var baseurl1 = "http://download.unity3d.com/download_unity/"
var baseurl2 = "http://beta.unity3d.com/download/"

func updateVersions() {
	p("%s %s", progname, progversion)
	files, err := ioutil.ReadDir("./updates")
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}
	count := 0
	max := 0
	for _, fi := range files {
		if !fi.IsDir() {
			max++
			s, _ := ioutil.ReadFile("updates/" + fi.Name())
			if s != nil {
				hash := strings.Replace(string(s), "\n", "", -1)
				getMacIni(hash, fi.Name())
				getWinIni(hash, fi.Name())
				count++
			}
		}
	}
	info("Updated %d versions out of %d.", count, max)
}

func getVar(line string) string {
	return strings.TrimSpace(line[strings.IndexByte(line, '=')+1:])
}

func getMacIni(hash string, filename string) {
	url := baseurl1 + hash + "/unity-" + filename + "-osx.ini"
	url2 := baseurl2 + hash + "/unity-" + filename + "-osx.ini"

	mybase := baseurl1
	info("Downloading %s", url)
	response, _ := http.Get(url)
	p("Status: %d\n", response.StatusCode)
	if response.StatusCode == 404 {
		p("Trying again with %s\n", url2)
		response, _ = http.Get(url2)
		if response.StatusCode == 404 {
			p("Error downloading %s\n", url2)
			return
		}
		mybase = baseurl2
	}
	defer response.Body.Close()
	bs, err := ioutil.ReadAll(response.Body)
	if err != nil {
		p("Error fetching macOS version: %s\n", err.Error())
		return
	}
	contents := string(bs)
	sections := strings.Split(contents, "[")
	data := ""
	for _, s := range sections {
		if len(s) > 0 {
			lines := strings.Split(s, "\n")
			data += getVar(lines[1]) + "\n"
			data += getVar(lines[2]) + "\n"
			data += mybase + hash + "/" + getVar(lines[3]) + "\n"
			data += getVar(lines[4]) + "\n"
		}
	}
	p("Saving %s (macOS)\n", filename)
	saveVersion(filename, data)
}

//TODO: gcfg can't read the -win.ini properly. Make custom hack.
func getWinIni(hash string, filename string) {
	url := baseurl1 + hash + "/unity-" + filename + "-win.ini"
	url2 := baseurl2 + hash + "/unity-" + filename + "-win.ini"
	mybase := baseurl1

	info("Downloading %s", url)
	response, _ := http.Get(url)
	if response.StatusCode == 404 {
		p("Trying again with %s\n", url2)
		response, _ = http.Get(url2)
		if response.StatusCode == 404 {
			p("Error downloading %s", url2)
			return
		}
		mybase = baseurl2
	}
	defer response.Body.Close()
	bs, err := ioutil.ReadAll(response.Body)
	if err != nil {
		p("Error fetching Windows version: %s\n", err.Error())
		return
	}
	contents := string(bs)
	sections := strings.Split(contents, "[")
	data := ""
	for _, s := range sections {
		if len(s) > 0 {
			lines := strings.Split(s, "\n")
			data += getVar(lines[1]) + "\n"
			data += getVar(lines[2]) + "\n"
			l3 := mybase + hash + "/" + getVar(lines[3])
			l4 := mybase + hash + "/" + getVar(lines[4])
			data += l3 + "\n" + l4 + "\n"
		}
	}
	p("Saving %s (Windows)\n", filename)
	saveVersion(filename+".win", data)
}

func saveVersion(version string, data string) {
	file, err := os.OpenFile("versions/"+version, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}

	defer file.Close()
	file.WriteString(data)
	os.Remove("updates/" + version)
}

func displayVersions() {
	files, err := ioutil.ReadDir("versions")
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}
	for _, fi := range files {
		if !fi.IsDir() {
			displayVersion(fi.Name())
		}
	}
}

func displayVersion(version string) {
	name := "versions/" + version
	s, _ := ioutil.ReadFile(name)
	p("%s\n", s)
}
<commit_msg>Slightly stylish update tagged.<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"
)

const (
	progname    = "UnityLinks"
	progversion = "0.5.4"
)

// Config holds the setup for the web server
type Config struct {
	Main struct {
		Address  string
		Port     int
		Sitename string
	}
}

var cfg Config

func init() {
	rand.Seed(time.Now().UnixNano())
	if !fexists("versions") {
		info("Creating data directory.")
		os.MkdirAll("versions", 0700)
	}
	if !fexists("updates") {
		info("Creating update directory.")
		os.MkdirAll("updates", 0700)
	}
}

func main() {
	var runupdates = flag.Bool("u", false, "Run updates instead of launching web server.")
	var address = flag.String("a", "0.0.0.0", "Address to bind to.")
	var port = flag.Int("p", 8000, "Port to bind to.")
	var display = flag.Bool("d", false, "Display versions and URLs available, then exit.")
	var version = flag.String("v", "", "Display a specific version. If not specified you get everything.")
	flag.Parse()

	if *runupdates {
		updateVersions()
		return
	}

	if *display {
		if *version == "" {
			displayVersions()
		} else {
			displayVersion(*version)
		}
		return
	}

	cfg.Main.Address = *address
	cfg.Main.Port = *port
	cfg.Main.Sitename = "Unity download links"
	initWeb()
	endWeb()
}

var baseurl1 = "http://download.unity3d.com/download_unity/"
var baseurl2 = "http://beta.unity3d.com/download/"

func updateVersions() {
	p("%s %s", progname, progversion)
	files, err := ioutil.ReadDir("./updates")
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}
	count := 0
	max := 0
	for _, fi := range files {
		if !fi.IsDir() {
			max++
			s, _ := ioutil.ReadFile("updates/" + fi.Name())
			if s != nil {
				hash := strings.Replace(string(s), "\n", "", -1)
				getMacIni(hash, fi.Name())
				getWinIni(hash, fi.Name())
				count++
			}
		}
	}
	info("Updated %d versions out of %d.", count, max)
}

func getVar(line string) string {
	return strings.TrimSpace(line[strings.IndexByte(line, '=')+1:])
}

func getMacIni(hash string, filename string) {
	url := baseurl1 + hash + "/unity-" + filename + "-osx.ini"
	url2 := baseurl2 + hash + "/unity-" + filename + "-osx.ini"

	mybase := baseurl1
	info("Downloading %s", url)
	response, _ := http.Get(url)
	p("Status: %d\n", response.StatusCode)
	if response.StatusCode == 404 {
		p("Trying again with %s\n", url2)
		response, _ = http.Get(url2)
		if response.StatusCode == 404 {
			p("Error downloading %s\n", url2)
			return
		}
		mybase = baseurl2
	}
	defer response.Body.Close()
	bs, err := ioutil.ReadAll(response.Body)
	if err != nil {
		p("Error fetching macOS version: %s\n", err.Error())
		return
	}
	contents := string(bs)
	sections := strings.Split(contents, "[")
	data := ""
	for _, s := range sections {
		if len(s) > 0 {
			lines := strings.Split(s, "\n")
			data += getVar(lines[1]) + "\n"
			data += getVar(lines[2]) + "\n"
			data += mybase + hash + "/" + getVar(lines[3]) + "\n"
			data += getVar(lines[4]) + "\n"
		}
	}
	p("Saving %s (macOS)\n", filename)
	saveVersion(filename, data)
}

//TODO: gcfg can't read the -win.ini properly. Make custom hack.
func getWinIni(hash string, filename string) {
	url := baseurl1 + hash + "/unity-" + filename + "-win.ini"
	url2 := baseurl2 + hash + "/unity-" + filename + "-win.ini"
	mybase := baseurl1

	info("Downloading %s", url)
	response, _ := http.Get(url)
	if response.StatusCode == 404 {
		p("Trying again with %s\n", url2)
		response, _ = http.Get(url2)
		if response.StatusCode == 404 {
			p("Error downloading %s", url2)
			return
		}
		mybase = baseurl2
	}
	defer response.Body.Close()
	bs, err := ioutil.ReadAll(response.Body)
	if err != nil {
		p("Error fetching Windows version: %s\n", err.Error())
		return
	}
	contents := string(bs)
	sections := strings.Split(contents, "[")
	data := ""
	for _, s := range sections {
		if len(s) > 0 {
			lines := strings.Split(s, "\n")
			data += getVar(lines[1]) + "\n"
			data += getVar(lines[2]) + "\n"
			l3 := mybase + hash + "/" + getVar(lines[3])
			l4 := mybase + hash + "/" + getVar(lines[4])
			data += l3 + "\n" + l4 + "\n"
		}
	}
	p("Saving %s (Windows)\n", filename)
	saveVersion(filename+".win", data)
}

func saveVersion(version string, data string) {
	file, err := os.OpenFile("versions/"+version, os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}

	defer file.Close()
	file.WriteString(data)
	os.Remove("updates/" + version)
}

func displayVersions() {
	files, err := ioutil.ReadDir("versions")
	if err != nil {
		p("Error: %s.", err.Error())
		return
	}
	for _, fi := range files {
		if !fi.IsDir() {
			displayVersion(fi.Name())
		}
	}
}

func displayVersion(version string) {
	name := "versions/" + version
	s, _ := ioutil.ReadFile(name)
	p("%s\n", s)
}
<|endoftext|>


<commit_before>package truncate_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"errors"

	"github.com/tidepool-org/platform/data"
	"github.com/tidepool-org/platform/data/deduplicator"
	"github.com/tidepool-org/platform/data/deduplicator/truncate"
	"github.com/tidepool-org/platform/data/types/base/upload"
	"github.com/tidepool-org/platform/log/test"
)

type CreateDatasetDataInput struct {
	dataset     *upload.Upload
	datasetData []data.Datum
}

type TestDataStoreSession struct {
	UpdateDatasetInputs              []*upload.Upload
	UpdateDatasetOutputs             []error
	CreateDatasetDataInputs          []CreateDatasetDataInput
	CreateDatasetDataOutputs         []error
	ActivateAllDatasetDataInputs     []*upload.Upload
	ActivateAllDatasetDataOutputs    []error
	RemoveAllOtherDatasetDataInputs  []*upload.Upload
	RemoveAllOtherDatasetDataOutputs []error
}

func (t *TestDataStoreSession) IsClosed() bool {
	panic("unexpected")
}

func (t *TestDataStoreSession) Close() {
	panic("unexpected")
}

func (t *TestDataStoreSession) GetDataset(datasetID string) (*upload.Upload, error) {
	panic("unexpected")
}

func (t *TestDataStoreSession) CreateDataset(dataset *upload.Upload) error {
	panic("unexpected")
}

func (t *TestDataStoreSession) UpdateDataset(dataset *upload.Upload) error {
	t.UpdateDatasetInputs = append(t.UpdateDatasetInputs, dataset)
	output := t.UpdateDatasetOutputs[0]
	t.UpdateDatasetOutputs = t.UpdateDatasetOutputs[1:]
	return output
}

func (t *TestDataStoreSession) CreateDatasetData(dataset *upload.Upload, datasetData []data.Datum) error {
	t.CreateDatasetDataInputs = append(t.CreateDatasetDataInputs, CreateDatasetDataInput{dataset, datasetData})
	output := t.CreateDatasetDataOutputs[0]
	t.CreateDatasetDataOutputs = t.CreateDatasetDataOutputs[1:]
	return output
}

func (t *TestDataStoreSession) ActivateAllDatasetData(dataset *upload.Upload) error {
	t.ActivateAllDatasetDataInputs = append(t.ActivateAllDatasetDataInputs, dataset)
	output := t.ActivateAllDatasetDataOutputs[0]
	t.ActivateAllDatasetDataOutputs = t.ActivateAllDatasetDataOutputs[1:]
	return output
}

func (t *TestDataStoreSession) RemoveAllOtherDatasetData(dataset *upload.Upload) error {
	t.RemoveAllOtherDatasetDataInputs = append(t.RemoveAllOtherDatasetDataInputs, dataset)
	output := t.RemoveAllOtherDatasetDataOutputs[0]
	t.RemoveAllOtherDatasetDataOutputs = t.RemoveAllOtherDatasetDataOutputs[1:]
	return output
}

func StringPtr(str string) *string { return &str }

var _ = Describe("Truncate", func() {
	Context("NewFactory", func() {
		It("returns a new factory", func() {
			factory, err := truncate.NewFactory()
			Expect(factory).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
		})
	})

	Context("with a new factory", func() {
		var factory deduplicator.Factory
		var dataset *upload.Upload

		BeforeEach(func() {
			var err error
			factory, err = truncate.NewFactory()
			Expect(factory).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
			dataset, err = upload.New()
			Expect(dataset).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
			dataset.UserID = "user-id"
			dataset.GroupID = "group-id"
			dataset.DeviceID = StringPtr("device-id")
		})

		Context("CanDeduplicateDataset", func() {
			It("returns an error if the dataset is missing", func() {
				can, err := factory.CanDeduplicateDataset(nil)
				Expect(can).To(Equal(false))
				Expect(err).To(MatchError("truncate: dataset is missing"))
			})

			It("returns false if the dataset id is missing", func() {
				dataset.UploadID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the user id is missing", func() {
				dataset.UserID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the group id is missing", func() {
				dataset.GroupID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the device id is missing", func() {
				dataset.DeviceID = nil
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the device id is empty", func() {
				dataset.DeviceID = StringPtr("")
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns true if the device id is specified", func() {
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(true))
				Expect(err).ToNot(HaveOccurred())
			})

			Context("with deduplicator", func() {
				BeforeEach(func() {
					dataset.Deduplicator = &upload.Deduplicator{}
				})

				It("returns false if the deduplicator name is missing", func() {
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(false))
					Expect(err).ToNot(HaveOccurred())
				})

				It("returns true if the deduplicator name is not truncate", func() {
					dataset.Deduplicator.Name = "not-truncate"
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(false))
					Expect(err).ToNot(HaveOccurred())
				})

				It("returns true if the deduplicator name is truncate", func() {
					dataset.Deduplicator.Name = "truncate"
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(true))
					Expect(err).ToNot(HaveOccurred())
				})
			})
		})

		Context("NewDeduplicator", func() {
			It("returns an error if the logger is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(nil, &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: logger is missing"))
			})

			It("returns an error if the data store session is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), nil, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: data store session is missing"))
			})

			It("returns an error if the dataset is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, nil)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset is missing"))
			})

			It("returns an error if the dataset id is missing", func() {
				dataset.UploadID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset id is missing"))
			})

			It("returns an error if the dataset user id is missing", func() {
				dataset.UserID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset user id is missing"))
			})

			It("returns an error if the dataset group id is missing", func() {
				dataset.GroupID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset group id is missing"))
			})

			It("returns an error if the dataset device id is missing", func() {
				dataset.DeviceID = nil
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset device id is missing"))
			})

			It("returns an error if the dataset device id is empty", func() {
				dataset.DeviceID = StringPtr("")
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset device id is missing"))
			})

			It("returns a new deduplicator upon success", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).ToNot(BeNil())
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("with a new deduplicator", func() {
			var testDataStoreSession *TestDataStoreSession
			var truncateDeduplicator deduplicator.Deduplicator

			BeforeEach(func() {
				var err error
				testDataStoreSession = &TestDataStoreSession{}
				truncateDeduplicator, err = factory.NewDeduplicator(test.NewLogger(), testDataStoreSession, dataset)
				Expect(truncateDeduplicator).ToNot(BeNil())
				Expect(err).ToNot(HaveOccurred())
			})

			Context("InitializeDataset", func() {
				It("returns an error if there is an error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).To(MatchError("truncate: unable to initialize dataset; test error"))
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{nil}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})

				It("sets the dataset deduplicator if there is no error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{nil}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(dataset.Deduplicator).To(Equal(&upload.Deduplicator{Name: "truncate"}))
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})
			})

			Context("AddDataToDataset", func() {
				It("returns an error if the dataset is missing", func() {
					err := truncateDeduplicator.AddDataToDataset(nil)
					Expect(err).To(MatchError("truncate: dataset data is missing"))
				})

				It("returns an error if there is an error", func() {
					testDataStoreSession.CreateDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.AddDataToDataset([]data.Datum{})
					Expect(err).To(MatchError("truncate: unable to add data to dataset; test error"))
					Expect(testDataStoreSession.CreateDatasetDataInputs).To(ConsistOf(CreateDatasetDataInput{dataset, []data.Datum{}}))
					Expect(testDataStoreSession.CreateDatasetDataOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.CreateDatasetDataOutputs = []error{nil}
					err := truncateDeduplicator.AddDataToDataset([]data.Datum{})
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.CreateDatasetDataInputs).To(ConsistOf(CreateDatasetDataInput{dataset, []data.Datum{}}))
					Expect(testDataStoreSession.CreateDatasetDataOutputs).To(BeEmpty())
				})
			})

			Context("FinalizeDataset", func() {
				It("returns an error if there is an error on activate", func() {
					dataset.UploadID = "upload-id"
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).To(MatchError("truncate: unable to activate data in dataset with id \"upload-id\"; test error"))
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
				})

				It("returns an error if there is an error on remove", func() {
					dataset.UploadID = "upload-id"
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{nil}
					testDataStoreSession.RemoveAllOtherDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).To(MatchError("truncate: unable to remove all other data except dataset with id \"upload-id\"; test error"))
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{nil}
					testDataStoreSession.RemoveAllOtherDatasetDataOutputs = []error{nil}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataOutputs).To(BeEmpty())
				})
			})
		})
	})
})
<commit_msg>Rename StringPtr to StringAsPointer<commit_after>package truncate_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"errors"

	"github.com/tidepool-org/platform/data"
	"github.com/tidepool-org/platform/data/deduplicator"
	"github.com/tidepool-org/platform/data/deduplicator/truncate"
	"github.com/tidepool-org/platform/data/types/base/upload"
	"github.com/tidepool-org/platform/log/test"
)

type CreateDatasetDataInput struct {
	dataset     *upload.Upload
	datasetData []data.Datum
}

type TestDataStoreSession struct {
	UpdateDatasetInputs              []*upload.Upload
	UpdateDatasetOutputs             []error
	CreateDatasetDataInputs          []CreateDatasetDataInput
	CreateDatasetDataOutputs         []error
	ActivateAllDatasetDataInputs     []*upload.Upload
	ActivateAllDatasetDataOutputs    []error
	RemoveAllOtherDatasetDataInputs  []*upload.Upload
	RemoveAllOtherDatasetDataOutputs []error
}

func (t *TestDataStoreSession) IsClosed() bool {
	panic("unexpected")
}

func (t *TestDataStoreSession) Close() {
	panic("unexpected")
}

func (t *TestDataStoreSession) GetDataset(datasetID string) (*upload.Upload, error) {
	panic("unexpected")
}

func (t *TestDataStoreSession) CreateDataset(dataset *upload.Upload) error {
	panic("unexpected")
}

func (t *TestDataStoreSession) UpdateDataset(dataset *upload.Upload) error {
	t.UpdateDatasetInputs = append(t.UpdateDatasetInputs, dataset)
	output := t.UpdateDatasetOutputs[0]
	t.UpdateDatasetOutputs = t.UpdateDatasetOutputs[1:]
	return output
}

func (t *TestDataStoreSession) CreateDatasetData(dataset *upload.Upload, datasetData []data.Datum) error {
	t.CreateDatasetDataInputs = append(t.CreateDatasetDataInputs, CreateDatasetDataInput{dataset, datasetData})
	output := t.CreateDatasetDataOutputs[0]
	t.CreateDatasetDataOutputs = t.CreateDatasetDataOutputs[1:]
	return output
}

func (t *TestDataStoreSession) ActivateAllDatasetData(dataset *upload.Upload) error {
	t.ActivateAllDatasetDataInputs = append(t.ActivateAllDatasetDataInputs, dataset)
	output := t.ActivateAllDatasetDataOutputs[0]
	t.ActivateAllDatasetDataOutputs = t.ActivateAllDatasetDataOutputs[1:]
	return output
}

func (t *TestDataStoreSession) RemoveAllOtherDatasetData(dataset *upload.Upload) error {
	t.RemoveAllOtherDatasetDataInputs = append(t.RemoveAllOtherDatasetDataInputs, dataset)
	output := t.RemoveAllOtherDatasetDataOutputs[0]
	t.RemoveAllOtherDatasetDataOutputs = t.RemoveAllOtherDatasetDataOutputs[1:]
	return output
}

func StringAsPointer(sourceString string) *string { return &sourceString }

var _ = Describe("Truncate", func() {
	Context("NewFactory", func() {
		It("returns a new factory", func() {
			factory, err := truncate.NewFactory()
			Expect(factory).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
		})
	})

	Context("with a new factory", func() {
		var factory deduplicator.Factory
		var dataset *upload.Upload

		BeforeEach(func() {
			var err error
			factory, err = truncate.NewFactory()
			Expect(factory).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
			dataset, err = upload.New()
			Expect(dataset).ToNot(BeNil())
			Expect(err).ToNot(HaveOccurred())
			dataset.UserID = "user-id"
			dataset.GroupID = "group-id"
			dataset.DeviceID = StringAsPointer("device-id")
		})

		Context("CanDeduplicateDataset", func() {
			It("returns an error if the dataset is missing", func() {
				can, err := factory.CanDeduplicateDataset(nil)
				Expect(can).To(Equal(false))
				Expect(err).To(MatchError("truncate: dataset is missing"))
			})

			It("returns false if the dataset id is missing", func() {
				dataset.UploadID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the user id is missing", func() {
				dataset.UserID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the group id is missing", func() {
				dataset.GroupID = ""
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the device id is missing", func() {
				dataset.DeviceID = nil
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns false if the device id is empty", func() {
				dataset.DeviceID = StringAsPointer("")
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(false))
				Expect(err).ToNot(HaveOccurred())
			})

			It("returns true if the device id is specified", func() {
				can, err := factory.CanDeduplicateDataset(dataset)
				Expect(can).To(Equal(true))
				Expect(err).ToNot(HaveOccurred())
			})

			Context("with deduplicator", func() {
				BeforeEach(func() {
					dataset.Deduplicator = &upload.Deduplicator{}
				})

				It("returns false if the deduplicator name is missing", func() {
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(false))
					Expect(err).ToNot(HaveOccurred())
				})

				It("returns true if the deduplicator name is not truncate", func() {
					dataset.Deduplicator.Name = "not-truncate"
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(false))
					Expect(err).ToNot(HaveOccurred())
				})

				It("returns true if the deduplicator name is truncate", func() {
					dataset.Deduplicator.Name = "truncate"
					can, err := factory.CanDeduplicateDataset(dataset)
					Expect(can).To(Equal(true))
					Expect(err).ToNot(HaveOccurred())
				})
			})
		})

		Context("NewDeduplicator", func() {
			It("returns an error if the logger is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(nil, &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: logger is missing"))
			})

			It("returns an error if the data store session is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), nil, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: data store session is missing"))
			})

			It("returns an error if the dataset is missing", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, nil)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset is missing"))
			})

			It("returns an error if the dataset id is missing", func() {
				dataset.UploadID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset id is missing"))
			})

			It("returns an error if the dataset user id is missing", func() {
				dataset.UserID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset user id is missing"))
			})

			It("returns an error if the dataset group id is missing", func() {
				dataset.GroupID = ""
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset group id is missing"))
			})

			It("returns an error if the dataset device id is missing", func() {
				dataset.DeviceID = nil
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset device id is missing"))
			})

			It("returns an error if the dataset device id is empty", func() {
				dataset.DeviceID = StringAsPointer("")
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).To(BeNil())
				Expect(err).To(MatchError("truncate: dataset device id is missing"))
			})

			It("returns a new deduplicator upon success", func() {
				truncateDeduplicator, err := factory.NewDeduplicator(test.NewLogger(), &TestDataStoreSession{}, dataset)
				Expect(truncateDeduplicator).ToNot(BeNil())
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("with a new deduplicator", func() {
			var testDataStoreSession *TestDataStoreSession
			var truncateDeduplicator deduplicator.Deduplicator

			BeforeEach(func() {
				var err error
				testDataStoreSession = &TestDataStoreSession{}
				truncateDeduplicator, err = factory.NewDeduplicator(test.NewLogger(), testDataStoreSession, dataset)
				Expect(truncateDeduplicator).ToNot(BeNil())
				Expect(err).ToNot(HaveOccurred())
			})

			Context("InitializeDataset", func() {
				It("returns an error if there is an error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).To(MatchError("truncate: unable to initialize dataset; test error"))
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{nil}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})

				It("sets the dataset deduplicator if there is no error", func() {
					testDataStoreSession.UpdateDatasetOutputs = []error{nil}
					err := truncateDeduplicator.InitializeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(dataset.Deduplicator).To(Equal(&upload.Deduplicator{Name: "truncate"}))
					Expect(testDataStoreSession.UpdateDatasetInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.UpdateDatasetOutputs).To(BeEmpty())
				})
			})

			Context("AddDataToDataset", func() {
				It("returns an error if the dataset is missing", func() {
					err := truncateDeduplicator.AddDataToDataset(nil)
					Expect(err).To(MatchError("truncate: dataset data is missing"))
				})

				It("returns an error if there is an error", func() {
					testDataStoreSession.CreateDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.AddDataToDataset([]data.Datum{})
					Expect(err).To(MatchError("truncate: unable to add data to dataset; test error"))
					Expect(testDataStoreSession.CreateDatasetDataInputs).To(ConsistOf(CreateDatasetDataInput{dataset, []data.Datum{}}))
					Expect(testDataStoreSession.CreateDatasetDataOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.CreateDatasetDataOutputs = []error{nil}
					err := truncateDeduplicator.AddDataToDataset([]data.Datum{})
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.CreateDatasetDataInputs).To(ConsistOf(CreateDatasetDataInput{dataset, []data.Datum{}}))
					Expect(testDataStoreSession.CreateDatasetDataOutputs).To(BeEmpty())
				})
			})

			Context("FinalizeDataset", func() {
				It("returns an error if there is an error on activate", func() {
					dataset.UploadID = "upload-id"
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).To(MatchError("truncate: unable to activate data in dataset with id \"upload-id\"; test error"))
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
				})

				It("returns an error if there is an error on remove", func() {
					dataset.UploadID = "upload-id"
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{nil}
					testDataStoreSession.RemoveAllOtherDatasetDataOutputs = []error{errors.New("test error")}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).To(MatchError("truncate: unable to remove all other data except dataset with id \"upload-id\"; test error"))
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataOutputs).To(BeEmpty())
				})

				It("returns successfully if there is no error", func() {
					testDataStoreSession.ActivateAllDatasetDataOutputs = []error{nil}
					testDataStoreSession.RemoveAllOtherDatasetDataOutputs = []error{nil}
					err := truncateDeduplicator.FinalizeDataset()
					Expect(err).ToNot(HaveOccurred())
					Expect(testDataStoreSession.ActivateAllDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.ActivateAllDatasetDataOutputs).To(BeEmpty())
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataInputs).To(ConsistOf(dataset))
					Expect(testDataStoreSession.RemoveAllOtherDatasetDataOutputs).To(BeEmpty())
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>// Package c2go contains the main function for running the executable.
//
// Installation
//
//     go get -u github.com/elliotchance/c2go
//
// Usage
//
//     c2go myfile.c
//
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/transpiler"
	"reflect"
)

// Version can be requested through the command line with:
//
//     c2go -v
//
// See https://github.com/elliotchance/c2go/wiki/Release-Process
const Version = "0.13.7"

// ProgramArgs defines the options available when processing the program. There
// is no constructor since the zeroed out values are the appropriaye defaults -
// you need only set the options you need.
//
// TODO: Better separation on CLI modes
// https://github.com/elliotchance/c2go/issues/134
type ProgramArgs struct {
	verbose     bool
	ast         bool
	inputFile   string
	outputFile  string
	packageName string
}

func readAST(data []byte) []string {
	uncolored := regexp.MustCompile(`\x1b\[[\d;]+m`).ReplaceAll(data, []byte{})
	return strings.Split(string(uncolored), "\n")
}

type treeNode struct {
	indent int
	node   ast.Node
}

func convertLinesToNodes(lines []string) []treeNode {
	nodes := []treeNode{}
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		// It is tempting to discard null AST nodes, but these may
		// have semantic importance: for example, they represent omitted
		// for-loop conditions, as in for(;;).
		line = strings.Replace(line, "<<<NULL>>>", "NullStmt", 1)
		trimmed := strings.TrimLeft(line, "|\\- `")
		node := ast.Parse(trimmed)
		indentLevel := (len(line) - len(trimmed)) / 2
		nodes = append(nodes, treeNode{indentLevel, node})
	}

	return nodes
}

// buildTree convert an array of nodes, each prefixed with a depth into a tree.
func buildTree(nodes []treeNode, depth int) []ast.Node {
	if len(nodes) == 0 {
		return []ast.Node{}
	}

	// Split the list into sections, treat each section as a a tree with its own root.
	sections := [][]treeNode{}
	for _, node := range nodes {
		if node.indent == depth {
			sections = append(sections, []treeNode{node})
		} else {
			sections[len(sections)-1] = append(sections[len(sections)-1], node)
		}
	}

	results := []ast.Node{}
	for _, section := range sections {
		slice := []treeNode{}
		for _, n := range section {
			if n.indent > depth {
				slice = append(slice, n)
			}
		}

		children := buildTree(slice, depth+1)
		for _, child := range children {
			section[0].node.AddChild(child)
		}
		results = append(results, section[0].node)
	}

	return results
}

func toJSON(tree []interface{}) []map[string]interface{} {
	r := make([]map[string]interface{}, len(tree))

	for j, n := range tree {
		rn := reflect.ValueOf(n).Elem()
		r[j] = make(map[string]interface{})
		r[j]["node"] = rn.Type().Name()

		for i := 0; i < rn.NumField(); i++ {
			name := strings.ToLower(rn.Type().Field(i).Name)
			value := rn.Field(i).Interface()

			if name == "children" {
				v := value.([]interface{})

				if len(v) == 0 {
					continue
				}

				value = toJSON(v)
			}

			r[j][name] = value
		}
	}

	return r
}

func check(prefix string, e error) {
	if e != nil {
		panic(prefix + e.Error())
	}
}

// Start begins transpiling an input file.
func Start(args ProgramArgs) error {
	if os.Getenv("GOPATH") == "" {
		return fmt.Errorf("The $GOPATH must be set")
	}

	// 1. Compile it first (checking for errors)
	_, err := os.Stat(args.inputFile)
	if err != nil {
		return fmt.Errorf("Input file is not found")
	}

	// 2. Preprocess
	var pp []byte
	{
		// See : https://clang.llvm.org/docs/CommandGuide/clang.html
		// clang -E <file>    Run the preprocessor stage.
		cmd := exec.Command("clang", "-E", args.inputFile)
		var out bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &out
		cmd.Stderr = &stderr
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("preprocess failed: %v\nStdErr = %v", err, stderr.String())
		}
		pp = []byte(out.String())
	}

	ppFilePath := path.Join(os.TempDir(), "pp.c")
	err = ioutil.WriteFile(ppFilePath, pp, 0644)
	if err != nil {
		return fmt.Errorf("writing to /tmp/pp.c failed: %v", err)
	}

	// 3. Generate JSON from AST
	astPP, err := exec.Command("clang", "-Xclang", "-ast-dump", "-fsyntax-only", ppFilePath).Output()
	if err != nil {
		// If clang fails it still prints out the AST, so we have to run it
		// again to get the real error.
		errBody, _ := exec.Command("clang", ppFilePath).CombinedOutput()

		panic("clang failed: " + err.Error() + ":\n\n" + string(errBody))
	}

	lines := readAST(astPP)
	if args.ast {
		for _, l := range lines {
			fmt.Println(l)
		}
		fmt.Println()

		return nil
	}

	nodes := convertLinesToNodes(lines)
	tree := buildTree(nodes, 0)

	p := program.NewProgram()
	p.Verbose = args.verbose

	err = transpiler.TranspileAST(args.inputFile, args.packageName, p, tree[0].(ast.Node))
	if err != nil {
		panic(err)
	}

	outputFilePath := args.outputFile

	if outputFilePath == "" {
		cleanFileName := filepath.Clean(filepath.Base(args.inputFile))
		extension := filepath.Ext(args.inputFile)

		outputFilePath = cleanFileName[0:len(cleanFileName)-len(extension)] + ".go"
	}

	err = ioutil.WriteFile(outputFilePath, []byte(p.String()), 0755)
	if err != nil {
		return fmt.Errorf("writing C output file failed: %v", err)
	}

	return nil
}

// newTempFile - returns temp file
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
	for index := 1; index < 10000; index++ {
		path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
		if _, err := os.Stat(path); err != nil {
			return os.Create(path)
		}
	}
	return nil, fmt.Errorf("could not create file: %s%03d%s", prefix, 1, suffix)
}

func main() {
	var (
		versionFlag       = flag.Bool("v", false, "print the version and exit")
		transpileCommand  = flag.NewFlagSet("transpile", flag.ContinueOnError)
		verboseFlag       = transpileCommand.Bool("V", false, "print progress as comments")
		outputFlag        = transpileCommand.String("o", "", "output Go generated code to the specified file")
		packageFlag       = transpileCommand.String("p", "main", "set the name of the generated package")
		transpileHelpFlag = transpileCommand.Bool("h", false, "print help information")
		astCommand        = flag.NewFlagSet("ast", flag.ContinueOnError)
		astHelpFlag       = astCommand.Bool("h", false, "print help information")
	)

	flag.Usage = func() {
		usage := "Usage: %s [-v] [<command>] [<flags>] file.c\n\n"
		usage += "Commands:\n"
		usage += "  transpile\ttranspile an input C source file to Go\n"
		usage += "  ast\t\tprint AST before translated Go code\n\n"

		usage += "Flags:\n"
		fmt.Fprintf(os.Stderr, usage, os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	if *versionFlag {
		// Simply print out the version and exit.
		fmt.Println(Version)
		return
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	args := ProgramArgs{verbose: *verboseFlag, ast: false}

	switch os.Args[1] {
	case "ast":
		err := astCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("ast command cannot parse: %v", err)
			os.Exit(1)
		}

		if *astHelpFlag || astCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s ast file.c\n", os.Args[0])
			astCommand.PrintDefaults()
			os.Exit(1)
		}

		args.ast = true
		args.inputFile = astCommand.Arg(0)
	case "transpile":
		err := transpileCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("transpile command cannot parse: %v", err)
			os.Exit(1)
		}

		if *transpileHelpFlag || transpileCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s transpile [-V] [-o file.go] [-p package] file.c\n", os.Args[0])
			transpileCommand.PrintDefaults()
			os.Exit(1)
		}

		args.inputFile = transpileCommand.Arg(0)
		args.outputFile = *outputFlag
		args.packageName = *packageFlag
	default:
		flag.Usage()
		os.Exit(1)
	}

	if err := Start(args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}
<commit_msg>Bump version<commit_after>// Package c2go contains the main function for running the executable.
//
// Installation
//
//     go get -u github.com/elliotchance/c2go
//
// Usage
//
//     c2go myfile.c
//
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/transpiler"
	"reflect"
)

// Version can be requested through the command line with:
//
//     c2go -v
//
// See https://github.com/elliotchance/c2go/wiki/Release-Process
const Version = "0.14.0"

// ProgramArgs defines the options available when processing the program. There
// is no constructor since the zeroed out values are the appropriaye defaults -
// you need only set the options you need.
//
// TODO: Better separation on CLI modes
// https://github.com/elliotchance/c2go/issues/134
type ProgramArgs struct {
	verbose     bool
	ast         bool
	inputFile   string
	outputFile  string
	packageName string
}

func readAST(data []byte) []string {
	uncolored := regexp.MustCompile(`\x1b\[[\d;]+m`).ReplaceAll(data, []byte{})
	return strings.Split(string(uncolored), "\n")
}

type treeNode struct {
	indent int
	node   ast.Node
}

func convertLinesToNodes(lines []string) []treeNode {
	nodes := []treeNode{}
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		// It is tempting to discard null AST nodes, but these may
		// have semantic importance: for example, they represent omitted
		// for-loop conditions, as in for(;;).
		line = strings.Replace(line, "<<<NULL>>>", "NullStmt", 1)
		trimmed := strings.TrimLeft(line, "|\\- `")
		node := ast.Parse(trimmed)
		indentLevel := (len(line) - len(trimmed)) / 2
		nodes = append(nodes, treeNode{indentLevel, node})
	}

	return nodes
}

// buildTree convert an array of nodes, each prefixed with a depth into a tree.
func buildTree(nodes []treeNode, depth int) []ast.Node {
	if len(nodes) == 0 {
		return []ast.Node{}
	}

	// Split the list into sections, treat each section as a a tree with its own root.
	sections := [][]treeNode{}
	for _, node := range nodes {
		if node.indent == depth {
			sections = append(sections, []treeNode{node})
		} else {
			sections[len(sections)-1] = append(sections[len(sections)-1], node)
		}
	}

	results := []ast.Node{}
	for _, section := range sections {
		slice := []treeNode{}
		for _, n := range section {
			if n.indent > depth {
				slice = append(slice, n)
			}
		}

		children := buildTree(slice, depth+1)
		for _, child := range children {
			section[0].node.AddChild(child)
		}
		results = append(results, section[0].node)
	}

	return results
}

func toJSON(tree []interface{}) []map[string]interface{} {
	r := make([]map[string]interface{}, len(tree))

	for j, n := range tree {
		rn := reflect.ValueOf(n).Elem()
		r[j] = make(map[string]interface{})
		r[j]["node"] = rn.Type().Name()

		for i := 0; i < rn.NumField(); i++ {
			name := strings.ToLower(rn.Type().Field(i).Name)
			value := rn.Field(i).Interface()

			if name == "children" {
				v := value.([]interface{})

				if len(v) == 0 {
					continue
				}

				value = toJSON(v)
			}

			r[j][name] = value
		}
	}

	return r
}

func check(prefix string, e error) {
	if e != nil {
		panic(prefix + e.Error())
	}
}

// Start begins transpiling an input file.
func Start(args ProgramArgs) error {
	if os.Getenv("GOPATH") == "" {
		return fmt.Errorf("The $GOPATH must be set")
	}

	// 1. Compile it first (checking for errors)
	_, err := os.Stat(args.inputFile)
	if err != nil {
		return fmt.Errorf("Input file is not found")
	}

	// 2. Preprocess
	var pp []byte
	{
		// See : https://clang.llvm.org/docs/CommandGuide/clang.html
		// clang -E <file>    Run the preprocessor stage.
		cmd := exec.Command("clang", "-E", args.inputFile)
		var out bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &out
		cmd.Stderr = &stderr
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("preprocess failed: %v\nStdErr = %v", err, stderr.String())
		}
		pp = []byte(out.String())
	}

	ppFilePath := path.Join(os.TempDir(), "pp.c")
	err = ioutil.WriteFile(ppFilePath, pp, 0644)
	if err != nil {
		return fmt.Errorf("writing to /tmp/pp.c failed: %v", err)
	}

	// 3. Generate JSON from AST
	astPP, err := exec.Command("clang", "-Xclang", "-ast-dump", "-fsyntax-only", ppFilePath).Output()
	if err != nil {
		// If clang fails it still prints out the AST, so we have to run it
		// again to get the real error.
		errBody, _ := exec.Command("clang", ppFilePath).CombinedOutput()

		panic("clang failed: " + err.Error() + ":\n\n" + string(errBody))
	}

	lines := readAST(astPP)
	if args.ast {
		for _, l := range lines {
			fmt.Println(l)
		}
		fmt.Println()

		return nil
	}

	nodes := convertLinesToNodes(lines)
	tree := buildTree(nodes, 0)

	p := program.NewProgram()
	p.Verbose = args.verbose

	err = transpiler.TranspileAST(args.inputFile, args.packageName, p, tree[0].(ast.Node))
	if err != nil {
		panic(err)
	}

	outputFilePath := args.outputFile

	if outputFilePath == "" {
		cleanFileName := filepath.Clean(filepath.Base(args.inputFile))
		extension := filepath.Ext(args.inputFile)

		outputFilePath = cleanFileName[0:len(cleanFileName)-len(extension)] + ".go"
	}

	err = ioutil.WriteFile(outputFilePath, []byte(p.String()), 0755)
	if err != nil {
		return fmt.Errorf("writing C output file failed: %v", err)
	}

	return nil
}

// newTempFile - returns temp file
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
	for index := 1; index < 10000; index++ {
		path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
		if _, err := os.Stat(path); err != nil {
			return os.Create(path)
		}
	}
	return nil, fmt.Errorf("could not create file: %s%03d%s", prefix, 1, suffix)
}

func main() {
	var (
		versionFlag       = flag.Bool("v", false, "print the version and exit")
		transpileCommand  = flag.NewFlagSet("transpile", flag.ContinueOnError)
		verboseFlag       = transpileCommand.Bool("V", false, "print progress as comments")
		outputFlag        = transpileCommand.String("o", "", "output Go generated code to the specified file")
		packageFlag       = transpileCommand.String("p", "main", "set the name of the generated package")
		transpileHelpFlag = transpileCommand.Bool("h", false, "print help information")
		astCommand        = flag.NewFlagSet("ast", flag.ContinueOnError)
		astHelpFlag       = astCommand.Bool("h", false, "print help information")
	)

	flag.Usage = func() {
		usage := "Usage: %s [-v] [<command>] [<flags>] file.c\n\n"
		usage += "Commands:\n"
		usage += "  transpile\ttranspile an input C source file to Go\n"
		usage += "  ast\t\tprint AST before translated Go code\n\n"

		usage += "Flags:\n"
		fmt.Fprintf(os.Stderr, usage, os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	if *versionFlag {
		// Simply print out the version and exit.
		fmt.Println(Version)
		return
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	args := ProgramArgs{verbose: *verboseFlag, ast: false}

	switch os.Args[1] {
	case "ast":
		err := astCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("ast command cannot parse: %v", err)
			os.Exit(1)
		}

		if *astHelpFlag || astCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s ast file.c\n", os.Args[0])
			astCommand.PrintDefaults()
			os.Exit(1)
		}

		args.ast = true
		args.inputFile = astCommand.Arg(0)
	case "transpile":
		err := transpileCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("transpile command cannot parse: %v", err)
			os.Exit(1)
		}

		if *transpileHelpFlag || transpileCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s transpile [-V] [-o file.go] [-p package] file.c\n", os.Args[0])
			transpileCommand.PrintDefaults()
			os.Exit(1)
		}

		args.inputFile = transpileCommand.Arg(0)
		args.outputFile = *outputFlag
		args.packageName = *packageFlag
	default:
		flag.Usage()
		os.Exit(1)
	}

	if err := Start(args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}
<|endoftext|>