<commit_before>package docker

import (
	"bufio"
	"context"
	"encoding/json"
	"github.com/ViBiOh/dashboard/auth"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
)

const ignoredByteLogSize = 8
const tailSize = `100`

var logWebsocketRequest = regexp.MustCompile(`containers/([^/]+)/logs`)
var statsWebsocketRequest = regexp.MustCompile(`containers/([^/]+)/stats`)
var eventsWebsocketRequest = regexp.MustCompile(`events`)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return hostCheck.MatchString(r.Host)
	},
}

func upgradeAndAuth(w http.ResponseWriter, r *http.Request) (*websocket.Conn, *auth.User, error) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		defer ws.Close()
		return nil, nil, err
	}

	_, basicAuth, err := ws.ReadMessage()
	if err != nil {
		defer ws.Close()
		return nil, nil, err
	}

	user, err := auth.IsAuthenticatedByAuth(string(basicAuth))
	if err != nil {
		ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))

		defer ws.Close()
		return nil, nil, err
	}

	return ws, user, nil
}

func logsContainerWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		log.Print(err)
		return
	}
	defer ws.Close()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	logs, err := docker.ContainerLogs(ctx, string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: tailSize})
	if err != nil {
		log.Print(err)
		return
	}
	defer logs.Close()

	go func() {
		defer cancel()

		scanner := bufio.NewScanner(logs)
		for scanner.Scan() {
			select {
			case <-ctx.Done():
				return

			default:
				logLine := scanner.Bytes()
				if len(logLine) > ignoredByteLogSize {
					if err = ws.WriteMessage(websocket.TextMessage, logLine[ignoredByteLogSize:]); err != nil {
						log.Printf(`[%s] Error while writing to logs socket: %v`, user.Username, err)
						return
					}
				}
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break

		default:
			messageType, _, err := ws.NextReader()
			if err != nil {
				log.Printf(`[%s] Error while reading from logs socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

func eventsWebsocketHandler(w http.ResponseWriter, r *http.Request) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		return
	}
	defer ws.Close()

	filtersArgs := filters.NewArgs()
	if labelFilters(&filtersArgs, user, nil) != nil {
		log.Printf(`[%s] Error while defining label filters: %v`, user.Username, err)
		return
	}
	if eventFilters(&filtersArgs) != nil {
		log.Printf(`[%s] Error while defining event filters: %v`, user.Username, err)
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	messages, errors := docker.Events(ctx, types.EventsOptions{Filters: filtersArgs})

	go func() {
		defer cancel()

		for {
			select {
			case <-ctx.Done():
				return

			case message := <-messages:
				messageJSON, err := json.Marshal(message)
				if err != nil {
					log.Printf(`[%s] Error while marshalling event: %v`, user.Username, err)
					return
				}

				if err = ws.WriteMessage(websocket.TextMessage, messageJSON); err != nil {
					log.Printf(`[%s] Error while writing to events socket: %v`, user.Username, err)
					return
				}

			case err := <-errors:
				log.Printf(`[%s] Error while reading events: %v`, user.Username, err)
				return
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break
		default:
			if messageType, _, err := ws.NextReader(); err != nil {
				log.Printf(`[%s] Error while reading from events socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

func statsWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		log.Print(err)
		return
	}
	defer ws.Close()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	stats, err := docker.ContainerStats(ctx, string(containerID), true)
	if err != nil {
		log.Print(err)
		return
	}
	defer stats.Body.Close()

	go func() {
		defer cancel()

		scanner := bufio.NewScanner(stats.Body)
		for scanner.Scan() {
			select {
			case <-ctx.Done():
				return

			default:
				if err = ws.WriteMessage(websocket.TextMessage, scanner.Bytes()); err != nil {
					log.Printf(`[%s] Error while writing to stats socket: %v`, user.Username, err)
					return
				}
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break

		default:
			if messageType, _, err := ws.NextReader(); err != nil {
				log.Printf(`[%s] Error while reading from stats socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

// WebsocketHandler for Docker Websocket request. Should be use with net/http
type WebsocketHandler struct {
}

func (handler WebsocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	urlPath := []byte(r.URL.Path)

	if logWebsocketRequest.Match(urlPath) {
		logsContainerWebsocketHandler(w, r, logWebsocketRequest.FindSubmatch(urlPath)[1])
	} else if eventsWebsocketRequest.Match((urlPath)) {
		eventsWebsocketHandler(w, r)
	} else if statsWebsocketRequest.Match(urlPath) {
		statsWebsocketHandler(w, r, statsWebsocketRequest.FindSubmatch(urlPath)[1])
	}
}
<commit_msg>Update websocket.go<commit_after>package docker

import (
	"bufio"
	"context"
	"encoding/json"
	"github.com/ViBiOh/dashboard/auth"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
)

const ignoredByteLogSize = 8
const tailSize = `100`

var logWebsocketRequest = regexp.MustCompile(`containers/([^/]+)/logs`)
var statsWebsocketRequest = regexp.MustCompile(`containers/([^/]+)/stats`)
var eventsWebsocketRequest = regexp.MustCompile(`events`)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return hostCheck.MatchString(r.Host)
	},
}

func upgradeAndAuth(w http.ResponseWriter, r *http.Request) (*websocket.Conn, *auth.User, error) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		defer ws.Close()
		return nil, nil, err
	}

	_, basicAuth, err := ws.ReadMessage()
	if err != nil {
		defer ws.Close()
		return nil, nil, err
	}

	user, err := auth.IsAuthenticatedByAuth(string(basicAuth))
	if err != nil {
		ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))

		defer ws.Close()
		return nil, nil, err
	}

	return ws, user, nil
}

func logsContainerWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		log.Print(err)
		return
	}
	defer ws.Close()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	logs, err := docker.ContainerLogs(ctx, string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: tailSize})
	if err != nil {
		log.Print(err)
		return
	}
	defer logs.Close()

	go func() {
		defer cancel()

		scanner := bufio.NewScanner(logs)
		for scanner.Scan() {
			select {
			case <-ctx.Done():
				return

			default:
				logLine := scanner.Bytes()
				if len(logLine) > ignoredByteLogSize {
					if err = ws.WriteMessage(websocket.TextMessage, logLine[ignoredByteLogSize:]); err != nil {
						log.Printf(`[%s] Error while writing to logs socket: %v`, user.Username, err)
						return
					}
				}
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break

		default:
			messageType, _, err := ws.NextReader()
			if err != nil {
				log.Printf(`[%s] Error while reading from logs socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

func eventsWebsocketHandler(w http.ResponseWriter, r *http.Request) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		return
	}
	defer ws.Close()

	filtersArgs := filters.NewArgs()
	if labelFilters(&filtersArgs, user, nil) != nil {
		log.Printf(`[%s] Error while defining label filters: %v`, user.Username, err)
		return
	}
	if eventFilters(&filtersArgs) != nil {
		log.Printf(`[%s] Error while defining event filters: %v`, user.Username, err)
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	messages, errors := docker.Events(ctx, types.EventsOptions{Filters: filtersArgs})

	go func() {
		defer cancel()

		for {
			select {
			case <-ctx.Done():
				return

			case message := <-messages:
				messageJSON, err := json.Marshal(message)
				if err != nil {
					log.Printf(`[%s] Error while marshalling event: %v`, user.Username, err)
					return
				}

				if err = ws.WriteMessage(websocket.TextMessage, messageJSON); err != nil {
					log.Printf(`[%s] Error while writing to events socket: %v`, user.Username, err)
					return
				}

			case err := <-errors:
				log.Printf(`[%s] Error while reading events: %v`, user.Username, err)
				return
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break
		default:
			messageType, _, err := ws.NextReader()
			if err != nil {
				log.Printf(`[%s] Error while reading from events socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

func statsWebsocketHandler(w http.ResponseWriter, r *http.Request, containerID []byte) {
	ws, user, err := upgradeAndAuth(w, r)
	if err != nil {
		log.Print(err)
		return
	}
	defer ws.Close()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	stats, err := docker.ContainerStats(ctx, string(containerID), true)
	if err != nil {
		log.Print(err)
		return
	}
	defer stats.Body.Close()

	go func() {
		defer cancel()

		scanner := bufio.NewScanner(stats.Body)
		for scanner.Scan() {
			select {
			case <-ctx.Done():
				return

			default:
				if err = ws.WriteMessage(websocket.TextMessage, scanner.Bytes()); err != nil {
					log.Printf(`[%s] Error while writing to stats socket: %v`, user.Username, err)
					return
				}
			}
		}
	}()

	for {
		select {
		case <-ctx.Done():
			break

		default:
			messageType, _, err := ws.NextReader()
			if err != nil {
				log.Printf(`[%s] Error while reading from stats socket: %v`, user.Username, err)
				return
			}
			if messageType == websocket.CloseMessage {
				break
			}
		}
	}
}

// WebsocketHandler for Docker Websocket request. Should be use with net/http
type WebsocketHandler struct {
}

func (handler WebsocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	urlPath := []byte(r.URL.Path)

	if logWebsocketRequest.Match(urlPath) {
		logsContainerWebsocketHandler(w, r, logWebsocketRequest.FindSubmatch(urlPath)[1])
	} else if eventsWebsocketRequest.Match((urlPath)) {
		eventsWebsocketHandler(w, r)
	} else if statsWebsocketRequest.Match(urlPath) {
		statsWebsocketHandler(w, r, statsWebsocketRequest.FindSubmatch(urlPath)[1])
	}
}
<|endoftext|>


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

package helpers

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strings"

	log "github.com/sirupsen/logrus"
)

//Vagrant helper struct
type Vagrant struct{}

//Create a new vagrant server. Receives and scope that it's the target server that need to be created.
// In case of any error on vagrant [provision|up|ssh-config] error will be returned.
func (vagrant *Vagrant) Create(scope string) error {
	createCMD := "vagrant up %s --provision"
	for _, v := range vagrant.Status(scope) {
		switch v {
		case "running":
			//Sometimes Jenkins is not deleting the servers. So we need to make
			//sure that we destroy before starts
			if !IsRunningOnJenkins() {
				createCMD = "vagrant provision %s"
			} else {
				vagrant.Destroy(scope)
			}
		case "not_created":
			createCMD = "vagrant up %s --provision"
		default:
			//Sometimes server are stoped and not destroyed. Destroy just in case
			vagrant.Destroy(scope)
		}
	}
	createCMD = fmt.Sprintf(createCMD, scope)
	log.Infof("Vagrant:Create: running '%s'", createCMD)
	cmd := vagrant.getCmd(createCMD)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	go func() {
		in := bufio.NewScanner(stdout)
		for in.Scan() {
			log.Infof(in.Text()) // write each line to your log
		}
	}()

	if err := cmd.Start(); err != nil {
		log.WithFields(log.Fields{
			"command": createCMD,
			"err":     err,
		}).Fatalf("Create error on start")
		return err
	}

	if err := cmd.Wait(); err != nil {
		return err
	}

	return nil
}

//GetSSHConfig returns an string with the ssh-cofnig for a target vm. REturn
//error if fails to run `vagrant ssh-config`
func (vagrant *Vagrant) GetSSHConfig(scope string) ([]byte, error) {
	cmd := vagrant.getCmd(fmt.Sprintf("vagrant ssh-config %s", scope))
	result, err := cmd.Output()
	if err != nil {
		return nil, err
	}
	return result, nil
}

//Destroy destroys all running Vagrant VMs in the provided scope. It returns an
//error if deletion of either the VMs fails
func (vagrant *Vagrant) Destroy(scope string) error {
	command := fmt.Sprintf("vagrant destroy -f %s ", scope)
	log.Infof("Vagrant:Destroy: running '%s'", command)
	cmd := vagrant.getCmd(command)
	_, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	return nil
}

func (vagrant *Vagrant) getCmd(op string) *exec.Cmd {
	cmd := exec.Command(vagrant.getPath("bash"), "-c", op)
	cmd.Dir = vagrant.getDir()
	return cmd
}

func (vagrant *Vagrant) getDir() string {
	dir, err := os.Getwd()
	if err != nil {
		return "/tmp/"
	}
	return fmt.Sprintf("%s/", dir)
}

func (vagrant *Vagrant) getPath(prog string) string {
	path, err := exec.LookPath(prog)
	if err != nil {
		return ""
	}
	return path
}

//Status returns a mapping of Vagrant VM name to its status
func (vagrant *Vagrant) Status(key string) map[string]string {
	result := map[string]string{}

	cmd := vagrant.getCmd(fmt.Sprintf("vagrant status %s --machine-readable", key))
	data, err := cmd.CombinedOutput()
	if err != nil {
		return result
	}
	for _, line := range strings.Split(string(data), "\n") {
		val := strings.Split(line, ",")
		if len(val) > 2 && val[2] == "state" {
			result[val[1]] = val[3]
		}
	}
	return result
}
<commit_msg>test/helpers: get stderr of Vagrant commands<commit_after>// Copyright 2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package helpers

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"strings"

	log "github.com/sirupsen/logrus"
)

//Vagrant helper struct
type Vagrant struct{}

//Create a new vagrant server. Receives and scope that it's the target server that need to be created.
// In case of any error on vagrant [provision|up|ssh-config] error will be returned.
func (vagrant *Vagrant) Create(scope string) error {
	createCMD := "vagrant up %s --provision"
	for _, v := range vagrant.Status(scope) {
		switch v {
		case "running":
			//Sometimes Jenkins is not deleting the servers. So we need to make
			//sure that we destroy before starts
			if !IsRunningOnJenkins() {
				createCMD = "vagrant provision %s"
			} else {
				vagrant.Destroy(scope)
			}
		case "not_created":
			createCMD = "vagrant up %s --provision"
		default:
			//Sometimes server are stoped and not destroyed. Destroy just in case
			vagrant.Destroy(scope)
		}
	}
	createCMD = fmt.Sprintf(createCMD, scope)
	log.Infof("Vagrant:Create: running '%s'", createCMD)
	cmd := vagrant.getCmd(createCMD)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("error getting stdout: %s", err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return fmt.Errorf("error getting stderr: %s", err)
	}

	go func() {
		in := bufio.NewScanner(stdout)
		for in.Scan() {
			log.Infof("stdout: %s", in.Text()) // write each line to your log
		}
	}()

	go func() {
		errIn := bufio.NewScanner(stderr)
		for errIn.Scan() {
			log.Infof("stderr: %s", errIn.Text())
		}
	}()

	if err := cmd.Start(); err != nil {
		log.WithFields(log.Fields{
			"command": createCMD,
			"err":     err,
		}).Fatalf("Create error on start")
		return err
	}

	if err := cmd.Wait(); err != nil {
		return err
	}

	return nil
}

//GetSSHConfig returns an string with the ssh-cofnig for a target vm. REturn
//error if fails to run `vagrant ssh-config`
func (vagrant *Vagrant) GetSSHConfig(scope string) ([]byte, error) {
	cmd := vagrant.getCmd(fmt.Sprintf("vagrant ssh-config %s", scope))
	result, err := cmd.Output()
	if err != nil {
		return nil, err
	}
	return result, nil
}

//Destroy destroys all running Vagrant VMs in the provided scope. It returns an
//error if deletion of either the VMs fails
func (vagrant *Vagrant) Destroy(scope string) error {
	command := fmt.Sprintf("vagrant destroy -f %s ", scope)
	log.Infof("Vagrant:Destroy: running '%s'", command)
	cmd := vagrant.getCmd(command)
	_, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	return nil
}

func (vagrant *Vagrant) getCmd(op string) *exec.Cmd {
	cmd := exec.Command(vagrant.getPath("bash"), "-c", op)
	cmd.Dir = vagrant.getDir()
	return cmd
}

func (vagrant *Vagrant) getDir() string {
	dir, err := os.Getwd()
	if err != nil {
		return "/tmp/"
	}
	return fmt.Sprintf("%s/", dir)
}

func (vagrant *Vagrant) getPath(prog string) string {
	path, err := exec.LookPath(prog)
	if err != nil {
		return ""
	}
	return path
}

//Status returns a mapping of Vagrant VM name to its status
func (vagrant *Vagrant) Status(key string) map[string]string {
	result := map[string]string{}

	cmd := vagrant.getCmd(fmt.Sprintf("vagrant status %s --machine-readable", key))
	data, err := cmd.CombinedOutput()
	if err != nil {
		return result
	}
	for _, line := range strings.Split(string(data), "\n") {
		val := strings.Split(line, ",")
		if len(val) > 2 && val[2] == "state" {
			result[val[1]] = val[3]
		}
	}
	return result
}
<|endoftext|>


<commit_before>package basiclist

import (
	"fmt"
	"testing"
)

func TestLinkedNode(t *testing.T) {
	headNode := BasicNote{key: 0, nextNode: nil}
	headNode.nextNode = &BasicNote{key: 1, val: "node1", nextNode: nil}
	headNode.nextNode.nextNode = &BasicNote{key: 2, val: "node2", nextNode: nil}

	fmt.Printf("head->")
	currentNode := &headNode
	for {
		fmt.Printf("[key:%d][val:%v]->", currentNode.key, currentNode.val)
		if currentNode.nextNode == nil {
			break
		}
		currentNode = currentNode.nextNode
	}
	fmt.Printf("nil\n")
}

func TestBasicList(t *testing.T) {
	bList := NewBasicList()
	bList.Insert(3, "string3")
	bList.Insert(4, "string4")
	bList.Insert(2, "string2")
	bList.Insert(5, "string5")
	bList.Insert(7, "string7")
	bList.Insert(9, "string9")
	bList.Insert(11, "string11")
	bList.DisplayAll()

	ret, err := bList.Search(5)
	if err != nil || ret != "string5" {
		t.Errorf("Search failed.\n")
	}

	bList.Remove(7)
	bList.DisplayAll()
}

func TestBasicListRemove(t *testing.T) {
	bList := NewBasicList()
	bList.Insert(4, "string4")
	bList.Insert(3, "string3")
	bList.DisplayAll()
	bList.Remove(3)
	bList.Remove(4)
	bList.DisplayAll()
	if bList.Remove(4) == nil {
		t.Errorf("Over remove\n")
	}
}
<commit_msg>fix basiclist test<commit_after>package basiclist

import (
	"fmt"
	"testing"
)

func TestLinkedNode(t *testing.T) {
	headNode := NewBasicNode(0, nil)
	headNode.next = NewBasicNode(1,  "node1")
	headNode.next.next = NewBasicNode(2,  "node2")

	fmt.Printf("head->")
	currentNode := headNode
	for {
		fmt.Printf("[key:%d][val:%v]->", currentNode.Key(), currentNode.Value())
		if currentNode.Next() == nil {
			break
		}
		currentNode = currentNode.Next()
	}
	fmt.Printf("nil\n")
}

func TestBasicList(t *testing.T) {
	bList := NewBasicList()
	bList.Insert(3, "string3")
	bList.Insert(4, "string4")
	bList.Insert(2, "string2")
	bList.Insert(5, "string5")
	bList.Insert(7, "string7")
	bList.Insert(9, "string9")
	bList.Insert(11, "string11")
	bList.DisplayAll()

	ret, err := bList.Search(5)
	if err != nil || ret != "string5" {
		t.Errorf("Search failed.\n")
	}

	bList.Remove(7)
	bList.DisplayAll()
}

func TestBasicListRemove(t *testing.T) {
	bList := NewBasicList()
	bList.Insert(4, "string4")
	bList.Insert(3, "string3")
	bList.DisplayAll()
	bList.Remove(3)
	bList.Remove(4)
	bList.DisplayAll()
	if bList.Remove(4) == nil {
		t.Errorf("Over remove\n")
	}
}
<|endoftext|>


<commit_before>package proc

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	tb "gopkg.in/tucnak/telebot.v2"

	"github.com/umputun/feed-master/app/feed"
)

func TestNewTelegramClientIfTokenEmpty(t *testing.T) {
	client, err := NewTelegramClient("", 0)
	assert.NoError(t, err)
	assert.Nil(t, client.Bot)
}

func TestNewTelegramClientCheckTimeout(t *testing.T) {
	tbl := []struct {
		timeout, expected time.Duration
	}{
		{0, time.Second * 60},
		{300, 300},
		{100500, 100500},
	}

	for i, tt := range tbl {
		i := i
		tt := tt
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			client, err := NewTelegramClient("", tt.timeout)
			assert.NoError(t, err)
			assert.Equal(t, tt.expected, client.Timeout)
		})
	}
}

func TestSendIfBotIsNil(t *testing.T) {
	client, err := NewTelegramClient("", 0)
	require.NoError(t, err)
	err = client.Send("@channel", feed.Item{})
	assert.NoError(t, err)
}

func TestSendIfChannelIDEmpty(t *testing.T) {
	client := TelegramClient{
		Bot: &tb.Bot{},
	}

	err := client.Send("", feed.Item{})
	assert.NoError(t, err)
}

func TestSendIfContentLengthZero(t *testing.T) {
	client := TelegramClient{
		Bot: &tb.Bot{},
	}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		w.Header().Set("Content-Length", string(0))
	}))
	defer ts.Close()

	err := client.Send("100500", feed.Item{
		Enclosure: feed.Enclosure{
			URL:    ts.URL,
			Length: 0,
		},
	})

	assert.Error(t, err)
	assert.EqualError(t, err, fmt.Sprintf("can't get length for %s: non-200 status, 500", ts.URL))
}

func TestTagLinkOnlySupport(t *testing.T) {
	html := `

Особое канадское искусство.

Результаты нашего странного эксперимента.

Теперь можно и в телеграмме

Саботаж на местах.

Их нравы: кумовство и коррупция.

Вопросы и ответы

аудио
`

	htmlExpected := `
Особое канадское искусство.
Результаты нашего странного эксперимента.
Теперь можно и в телеграмме
Саботаж на местах.
Их нравы: кумовство и коррупция.
Вопросы и ответы

аудио`

	client := TelegramClient{}
	got := client.tagLinkOnlySupport(html)
	assert.Equal(t, htmlExpected, got, "support only html tag a")
}

func TestGetMessageHTML(t *testing.T) {
	item := feed.Item{
		Title:       "\tPodcast\n\t",
		Description: "
News Podcast Link
\n",
		Enclosure: feed.Enclosure{
			URL: "https://example.com",
		},
		Link: "https://example.com/xyz",
	}

	expected := "Podcast\n\nNews Podcast Link\n\nhttps://example.com"

	client := TelegramClient{}
	msg := client.getMessageHTML(item, true)
	assert.Equal(t, expected, msg)
}

func TestRecipientChannelIDNotStartWithAt(t *testing.T) {
	cases := []string{"channel", "@channel"}
	expected := "@channel"

	for i, channelID := range cases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			got := recipient{chatID: channelID} // nolint
			assert.Equal(t, expected, got.Recipient())
		})
	}
}

func TestGetFilenameByURL(t *testing.T) {
	tbl := []struct {
		url, expected string
	}{
		{"https://example.com/100500/song.mp3", "song.mp3"},
		{"https://example.com//song.mp3", "song.mp3"},
		{"https://example.com/song.mp3", "song.mp3"},
		{"https://example.com/song.mp3/", ""},
		{"https://example.com/", ""},
	}

	for i, tt := range tbl {
		i := i
		tt := tt
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			client := TelegramClient{}
			fname := client.getFilenameByURL(tt.url)
			assert.Equal(t, tt.expected, fname)
		})
	}
}

func TestGetContentLengthNotFound(t *testing.T) {
	cases := []struct {
		statusCode     int
		contentLength  int
		expectedLength int
		expectedError  string
	}{
		{http.StatusInternalServerError, 100500, 0, "non-200 status, 500"},
		{http.StatusMethodNotAllowed, 100500, 0, "non-200 status, 405"},
		{http.StatusOK, 4, 4, ""},
	}

	for i, tc := range cases {
		i := i
		tc := tc
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tc.statusCode)
				w.Header().Set("Content-Length", string(tc.contentLength))
				if tc.contentLength > 0 {
					fmt.Fprint(w, "abcd")
				}
			}))

			defer ts.Close()

			length, err := getContentLength(ts.URL)

			assert.Equal(t, tc.expectedLength, length)
			if err != nil {
				assert.EqualError(t, err, tc.expectedError)
			}
		})
	}
}

func TestGetContentLengthIfErrorConnect(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ts.CloseClientConnections()
	}))

	defer ts.Close()

	length, err := getContentLength(ts.URL)

	assert.Equal(t, length, 0)
	assert.EqualError(t, err, fmt.Sprintf("can't HEAD %s: Head %s: EOF", ts.URL, ts.URL))
}

func TestDownloadAudioIfRequestError(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ts.CloseClientConnections()
	}))

	defer ts.Close()

	client := TelegramClient{}
	got, err := client.downloadAudio(ts.URL)

	assert.Nil(t, got)
	assert.EqualError(t, err, fmt.Sprintf("Get %s: EOF", ts.URL))
}

func TestDownloadAudio(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Length", string(4))
		fmt.Fprint(w, "abcd")
	}))
	defer ts.Close()

	client := TelegramClient{}
	got, err := client.downloadAudio(ts.URL)

	assert.NotNil(t, got)
	assert.Nil(t, err)
}
<commit_msg>fix test build error in telegram_test.go<commit_after>package proc

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	tb "gopkg.in/tucnak/telebot.v2"

	"github.com/umputun/feed-master/app/feed"
)

func TestNewTelegramClientIfTokenEmpty(t *testing.T) {
	client, err := NewTelegramClient("", 0)
	assert.NoError(t, err)
	assert.Nil(t, client.Bot)
}

func TestNewTelegramClientCheckTimeout(t *testing.T) {
	tbl := []struct {
		timeout, expected time.Duration
	}{
		{0, time.Second * 60},
		{300, 300},
		{100500, 100500},
	}

	for i, tt := range tbl {
		i := i
		tt := tt
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			client, err := NewTelegramClient("", tt.timeout)
			assert.NoError(t, err)
			assert.Equal(t, tt.expected, client.Timeout)
		})
	}
}

func TestSendIfBotIsNil(t *testing.T) {
	client, err := NewTelegramClient("", 0)
	require.NoError(t, err)
	err = client.Send("@channel", feed.Item{})
	assert.NoError(t, err)
}

func TestSendIfChannelIDEmpty(t *testing.T) {
	client := TelegramClient{
		Bot: &tb.Bot{},
	}

	err := client.Send("", feed.Item{})
	assert.NoError(t, err)
}

func TestSendIfContentLengthZero(t *testing.T) {
	client := TelegramClient{
		Bot: &tb.Bot{},
	}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		w.Header().Set("Content-Length", fmt.Sprint(0))
	}))
	defer ts.Close()

	err := client.Send("100500", feed.Item{
		Enclosure: feed.Enclosure{
			URL:    ts.URL,
			Length: 0,
		},
	})

	assert.Error(t, err)
	assert.EqualError(t, err, fmt.Sprintf("can't get length for %s: non-200 status, 500", ts.URL))
}

func TestTagLinkOnlySupport(t *testing.T) {
	html := `

Особое канадское искусство.

Результаты нашего странного эксперимента.

Теперь можно и в телеграмме

Саботаж на местах.

Их нравы: кумовство и коррупция.

Вопросы и ответы

аудио
`

	htmlExpected := `
Особое канадское искусство.
Результаты нашего странного эксперимента.
Теперь можно и в телеграмме
Саботаж на местах.
Их нравы: кумовство и коррупция.
Вопросы и ответы

аудио`

	client := TelegramClient{}
	got := client.tagLinkOnlySupport(html)
	assert.Equal(t, htmlExpected, got, "support only html tag a")
}

func TestGetMessageHTML(t *testing.T) {
	item := feed.Item{
		Title:       "\tPodcast\n\t",
		Description: "
News Podcast Link
\n",
		Enclosure: feed.Enclosure{
			URL: "https://example.com",
		},
		Link: "https://example.com/xyz",
	}

	expected := "Podcast\n\nNews Podcast Link\n\nhttps://example.com"

	client := TelegramClient{}
	msg := client.getMessageHTML(item, true)
	assert.Equal(t, expected, msg)
}

func TestRecipientChannelIDNotStartWithAt(t *testing.T) {
	cases := []string{"channel", "@channel"}
	expected := "@channel"

	for i, channelID := range cases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			got := recipient{chatID: channelID} // nolint
			assert.Equal(t, expected, got.Recipient())
		})
	}
}

func TestGetFilenameByURL(t *testing.T) {
	tbl := []struct {
		url, expected string
	}{
		{"https://example.com/100500/song.mp3", "song.mp3"},
		{"https://example.com//song.mp3", "song.mp3"},
		{"https://example.com/song.mp3", "song.mp3"},
		{"https://example.com/song.mp3/", ""},
		{"https://example.com/", ""},
	}

	for i, tt := range tbl {
		i := i
		tt := tt
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			client := TelegramClient{}
			fname := client.getFilenameByURL(tt.url)
			assert.Equal(t, tt.expected, fname)
		})
	}
}

func TestGetContentLengthNotFound(t *testing.T) {
	cases := []struct {
		statusCode     int
		contentLength  int
		expectedLength int
		expectedError  string
	}{
		{http.StatusInternalServerError, 100500, 0, "non-200 status, 500"},
		{http.StatusMethodNotAllowed, 100500, 0, "non-200 status, 405"},
		{http.StatusOK, 4, 4, ""},
	}

	for i, tc := range cases {
		i := i
		tc := tc
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tc.statusCode)
				w.Header().Set("Content-Length", fmt.Sprint(tc.contentLength))
				if tc.contentLength > 0 {
					fmt.Fprint(w, "abcd")
				}
			}))

			defer ts.Close()

			length, err := getContentLength(ts.URL)

			assert.Equal(t, tc.expectedLength, length)
			if err != nil {
				assert.EqualError(t, err, tc.expectedError)
			}
		})
	}
}

func TestGetContentLengthIfErrorConnect(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ts.CloseClientConnections()
	}))

	defer ts.Close()

	length, err := getContentLength(ts.URL)

	assert.Equal(t, length, 0)
	assert.EqualError(t, err, fmt.Sprintf("can't HEAD %s: Head %s: EOF", ts.URL, ts.URL))
}

func TestDownloadAudioIfRequestError(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ts.CloseClientConnections()
	}))

	defer ts.Close()

	client := TelegramClient{}
	got, err := client.downloadAudio(ts.URL)

	assert.Nil(t, got)
	assert.EqualError(t, err, fmt.Sprintf("Get %s: EOF", ts.URL))
}

func TestDownloadAudio(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Length", fmt.Sprint(4))
		fmt.Fprint(w, "abcd")
	}))
	defer ts.Close()

	client := TelegramClient{}
	got, err := client.downloadAudio(ts.URL)

	assert.NotNil(t, got)
	assert.Nil(t, err)
}
<|endoftext|>


<commit_before>package slack

import (
	"bytes"
	"log"
	"strconv"

	"github.com/dustin/go-humanize"
	"github.com/eveopsec/zk2s/app/config"
	"github.com/eveopsec/zk2s/app/filter"
	"github.com/eveopsec/zk2s/app/tmpl"
	slacklib "github.com/nlopes/slack"
	"github.com/vivace-io/evelib/crest"
	"github.com/vivace-io/evelib/redisq"
)

// TemplateData is passed to templates for defining how a slacklib post appears.
type TemplateData struct {
	// The raw CREST killmail model.
	Killmail crest.Killmail
	// The total value of the kill, as a formatted string with appropriate abbreviations.
	TotalValue string
	// IsLoss is true when the kill is a loss to the filter, false otherwise.
	IsLoss bool
	// IsSolo is true when the killmail was a solo kill/loss.
	IsSolo bool
}

// format loads the formatting template and applies formatting
// rules from the Configuration object.
func format(payload redisq.Payload, channel config.Channel) (messageParams slacklib.PostMessageParameters) {
	title := new(bytes.Buffer)
	body := new(bytes.Buffer)
	var err error

	// define post data for templates
	d := new(TemplateData)
	d.Killmail = payload.Killmail
	d.TotalValue = humanize.Comma(int64(payload.Zkb.TotalValue))
	d.IsLoss = filter.IsLoss(payload, channel)
	// Check if the kill was solo (NOTE: NPCs count)
	if len(payload.Killmail.Attackers) == 1 {
		d.IsSolo = true
	} else {
		d.IsLoss = false
	}

	// Execute templates
	err = tmpl.T.ExecuteTemplate(title, "kill-title", d)
	if err != nil {
		log.Println(err)
	}
	err = tmpl.T.ExecuteTemplate(body, "kill-body", d)
	if err != nil {
		log.Println(err)
	}

	attch := slacklib.Attachment{}
	attch.MarkdownIn = []string{"pretext", "text"}
	attch.Title = title.String()
	attch.TitleLink = "https://zkillboard.com/kill/" + strconv.Itoa(payload.KillID) + "/"
	attch.ThumbURL = "http://image.eveonline.com/render/" + strconv.Itoa(payload.Killmail.Victim.ShipType.ID) + "_64.png"
	attch.Text = body.String()
	//Color Coding
	if filter.IsLoss(payload, channel) {
		attch.Color = "danger"
	} else {
		attch.Color = "good"
	}
	messageParams.Attachments = []slacklib.Attachment{attch}
	messageParams.AsUser = true
	return
}
<commit_msg>removed unnecissary line<commit_after>package slack

import (
	"bytes"
	"log"
	"strconv"

	"github.com/dustin/go-humanize"
	"github.com/eveopsec/zk2s/app/config"
	"github.com/eveopsec/zk2s/app/filter"
	"github.com/eveopsec/zk2s/app/tmpl"
	slacklib "github.com/nlopes/slack"
	"github.com/vivace-io/evelib/crest"
	"github.com/vivace-io/evelib/redisq"
)

// TemplateData is passed to templates for defining how a slacklib post appears.
type TemplateData struct {
	// The raw CREST killmail model.
	Killmail crest.Killmail
	// The total value of the kill, as a formatted string with appropriate abbreviations.
	TotalValue string
	// IsLoss is true when the kill is a loss to the filter, false otherwise.
	IsLoss bool
	// IsSolo is true when the killmail was a solo kill/loss.
	IsSolo bool
}

// format loads the formatting template and applies formatting
// rules from the Configuration object.
func format(payload redisq.Payload, channel config.Channel) (messageParams slacklib.PostMessageParameters) {
	title := new(bytes.Buffer)
	body := new(bytes.Buffer)
	var err error

	// define post data for templates
	d := new(TemplateData)
	d.Killmail = payload.Killmail
	d.TotalValue = humanize.Comma(int64(payload.Zkb.TotalValue))
	d.IsLoss = filter.IsLoss(payload, channel)

	// Execute templates
	err = tmpl.T.ExecuteTemplate(title, "kill-title", d)
	if err != nil {
		log.Println(err)
	}
	err = tmpl.T.ExecuteTemplate(body, "kill-body", d)
	if err != nil {
		log.Println(err)
	}

	attch := slacklib.Attachment{}
	attch.MarkdownIn = []string{"pretext", "text"}
	attch.Title = title.String()
	attch.TitleLink = "https://zkillboard.com/kill/" + strconv.Itoa(payload.KillID) + "/"
	attch.ThumbURL = "http://image.eveonline.com/render/" + strconv.Itoa(payload.Killmail.Victim.ShipType.ID) + "_64.png"
	attch.Text = body.String()
	//Color Coding
	if filter.IsLoss(payload, channel) {
		attch.Color = "danger"
	} else {
		attch.Color = "good"
	}
	messageParams.Attachments = []slacklib.Attachment{attch}
	messageParams.AsUser = true
	return
}
<|endoftext|>


<commit_before>//+build smoke

package smoke

import (
	"fmt"
	"strings"
	"testing"

	"github.com/opentable/sous/util/filemap"
)

func dockerBuildAddArtifactInit(t *testing.T, f *testFixture, client *sousClient, flags *sousFlags, transforms ...ManifestTransform) (dockerRef string) {
	t.Helper()

	dockerRef = dockerBuildAddArtifact(t, f, client, flags)

	client.MustRun(t, "init", flags.SousInitFlags(), "-use-otpl-deploy")
	client.TransformManifest(t, flags, transforms...)

	return dockerRef
}

func dockerBuildAddArtifact(t *testing.T, f *testFixture, client *sousClient, flags *sousFlags) (dockerRef string) {
	t.Helper()
	tag := flags.tag
	if tag == "" {
		t.Fatalf("you must add a non-empty tag flag")
	}
	reg := client.Config.Docker.RegistryHost
	repo := flags.repo
	dockerTag := f.IsolatedVersionTag(t, tag)
	dockerRepo := fmt.Sprintf("%s/%s", reg, repo)
	dockerRef = fmt.Sprintf("%s:%s", dockerRepo, dockerTag)

	mustDoCMD(t, client.Dir, "docker", "build", "-t", dockerRef, ".")
	mustDoCMD(t, client.Dir, "docker", "push", dockerRef)

	client.MustRun(t, "artifact add", nil, "-docker-image", dockerRepo, "-repo", repo, "-tag", tag)

	return dockerRef
}

func TestOTPL(t *testing.T) {

	// FixedDimension is because otpl deploy can only work with simple dockerfile
	// projects, not split build projects.
	pf := pfs.newParallelTestFixture(t, Matrix().FixedDimension("project", "simple"))

	pf.RunMatrix(

		PTest{Name: "artifact-add", Test: func(t *testing.T, f *testFixture) {
			client := f.setupProject(t, f.Projects.HTTPServer())

			flags := &sousFlags{tag: "1.2.3", repo: "github.com/some-user/project1"}

			dockerRef := dockerBuildAddArtifact(t, f, client, flags)

			output := client.MustRun(t, "artifact get", flags)

			if !strings.Contains(output, dockerRef) {
				// TODO SS: Figure out how to do this assertion given that we do
				// not store the Docker tag sent, only the  digest.
				//t.Errorf("output did not contain %q; was:\n%s", dockerRef, output)
			} else {
				// TODO SS: Remove next line once we have the assertion above.
				t.Logf(output)
			}
		}},

		PTest{Name: "build-init-deploy", Test: func(t *testing.T, f *testFixture) {

			client := f.setupProject(t, f.Projects.HTTPServer().Merge(filemap.FileMap{
				"config/cluster1/singularity.json": `
				{
					"requestId": "request1",
					"resources": {
						"cpus": 0.01,
						"memoryMb": 1,
						"numPorts": 3
					}
				}`,
				"config/cluster1/singularity-request.json": `
				{
					"id": "request1",
					"requestType": "SERVICE",
					"owners": [
					    "test-user1@example.com"
					],
					"instances": 3
				}`,
			}))

			flags := &sousFlags{
				kind:    "http-service",
				repo:    "github.com/build-init-deploy-user/project1",
				tag:     "1.2.3",
				cluster: "cluster1",
			}

			dockerBuildAddArtifactInit(t, f, client, flags, setMinimalMemAndCPUNumInst1)

			client.MustRun(t, "deploy", flags.SousDeployFlags())

			reqID := f.DefaultSingReqID(t, flags)
			assertActiveStatus(t, f, reqID)
			assertSingularityRequestTypeService(t, f, reqID)
			assertNonNilHealthCheckOnLatestDeploy(t, f, reqID)
		}},

		PTest{Name: "fail-unknown-fields", Test: func(t *testing.T, f *testFixture) {

			t.Skipf("WIP")

			client := f.setupProject(t, f.Projects.HTTPServer().Merge(filemap.FileMap{
				"config/cluster1/singularity.json": `
				{
					"requestId": "request1",
					"resources": {
						"cpus": 0.01,
						"memoryMb": 1,
						"numPorts": 3
					}
				}`,
				"config/cluster1/singularity-request.json": `
				{
					id: "request1",
					"requestType": "WORKER",
					"owners": [
					    "test-user1@example.com"
					],
					"slavePlacement": "SEPARATE_BY_REQUEST",
					"instances": 3,
					"rackSensitive": false,
					"loadBalanced": false
				}`,
			}))
			client.MustRun(t, "version", nil)
		}},
	)
}
<commit_msg>test/smoke: otpl: failing test: unknown fields<commit_after>//+build smoke

package smoke

import (
	"fmt"
	"strings"
	"testing"

	"github.com/opentable/sous/util/filemap"
)

func dockerBuildAddArtifactInit(t *testing.T, f *testFixture, client *sousClient, flags *sousFlags, transforms ...ManifestTransform) (dockerRef string) {
	t.Helper()

	dockerRef = dockerBuildAddArtifact(t, f, client, flags)

	client.MustRun(t, "init", flags.SousInitFlags(), "-use-otpl-deploy")
	client.TransformManifest(t, flags, transforms...)

	return dockerRef
}

func dockerBuildAddArtifact(t *testing.T, f *testFixture, client *sousClient, flags *sousFlags) (dockerRef string) {
	t.Helper()
	tag := flags.tag
	if tag == "" {
		t.Fatalf("you must add a non-empty tag flag")
	}
	reg := client.Config.Docker.RegistryHost
	repo := flags.repo
	dockerTag := f.IsolatedVersionTag(t, tag)
	dockerRepo := fmt.Sprintf("%s/%s", reg, repo)
	dockerRef = fmt.Sprintf("%s:%s", dockerRepo, dockerTag)

	mustDoCMD(t, client.Dir, "docker", "build", "-t", dockerRef, ".")
	mustDoCMD(t, client.Dir, "docker", "push", dockerRef)

	client.MustRun(t, "artifact add", nil, "-docker-image", dockerRepo, "-repo", repo, "-tag", tag)

	return dockerRef
}

func TestOTPL(t *testing.T) {

	// FixedDimension is because otpl deploy can only work with simple dockerfile
	// projects, not split build projects.
	pf := pfs.newParallelTestFixture(t, Matrix().FixedDimension("project", "simple"))

	pf.RunMatrix(

		PTest{Name: "artifact-add", Test: func(t *testing.T, f *testFixture) {
			client := f.setupProject(t, f.Projects.HTTPServer())

			flags := &sousFlags{tag: "1.2.3", repo: "github.com/some-user/project1"}

			dockerRef := dockerBuildAddArtifact(t, f, client, flags)

			output := client.MustRun(t, "artifact get", flags)

			if !strings.Contains(output, dockerRef) {
				// TODO SS: Figure out how to do this assertion given that we do
				// not store the Docker tag sent, only the  digest.
				//t.Errorf("output did not contain %q; was:\n%s", dockerRef, output)
			} else {
				// TODO SS: Remove next line once we have the assertion above.
				t.Logf(output)
			}
		}},

		PTest{Name: "build-init-deploy", Test: func(t *testing.T, f *testFixture) {

			client := f.setupProject(t, f.Projects.HTTPServer().Merge(filemap.FileMap{
				"config/cluster1/singularity.json": `
				{
					"requestId": "request1",
					"resources": {
						"cpus": 0.01,
						"memoryMb": 1,
						"numPorts": 3
					}
				}`,
				"config/cluster1/singularity-request.json": `
				{
					"id": "request1",
					"requestType": "SERVICE",
					"owners": [
					    "test-user1@example.com"
					],
					"instances": 3
				}`,
			}))

			flags := &sousFlags{
				kind:    "http-service",
				repo:    "github.com/build-init-deploy-user/project1",
				tag:     "1.2.3",
				cluster: "cluster1",
			}

			dockerBuildAddArtifactInit(t, f, client, flags, setMinimalMemAndCPUNumInst1)

			client.MustRun(t, "deploy", flags.SousDeployFlags())

			reqID := f.DefaultSingReqID(t, flags)
			assertActiveStatus(t, f, reqID)
			assertSingularityRequestTypeService(t, f, reqID)
			assertNonNilHealthCheckOnLatestDeploy(t, f, reqID)
		}},

		PTest{Name: "fail-unknown-fields", Test: func(t *testing.T, f *testFixture) {

			client := f.setupProject(t, f.Projects.HTTPServer().Merge(filemap.FileMap{
				"config/cluster1/singularity.json": `
				{
					"requestId": "request1",
					"resources": {
						"cpus": 0.01,
						"memoryMb": 1,
						"numPorts": 3
					}
				}`,
				"config/cluster1/singularity-request.json": `
				{
					id: "request1",
					"requestType": "WORKER",
					"owners": [
					    "test-user1@example.com"
					],
					"slavePlacement": "SEPARATE_BY_REQUEST",
					"instances": 3,
					"rackSensitive": false,
					"loadBalanced": false
				}`,
			}))

			flags := &sousFlags{
				kind:    "http-service",
				repo:    "github.com/build-init-deploy-user/project1",
				tag:     "1.2.3",
				cluster: "cluster1",
			}

			client.MustFail(t, "init", flags.SousInitFlags(), "-use-otpl-deploy")
		}},
	)
}
<|endoftext|>


<commit_before>// +build !disable_driver_mysql

package sqlstorage

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/facette/maputil"
	"github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
)

const (
	defaultMysqlDriverHost = "localhost"
	defaultMysqlDriverPort = 3306
)

// mysqlDriver implements the database driver interface for MySQL.
type mysqlDriver struct {
	host     string
	port     int
	user     string
	password string
	dbName   string
}

func (d mysqlDriver) Name() string {
	return "mysql"
}

func (d mysqlDriver) Open() (*sql.DB, error) {
	dsn := fmt.Sprintf(
		"%s:%s@tcp(%s:%d)/%s?parseTime=true",
		d.user,
		d.password,
		d.host,
		d.port,
		d.dbName,
	)

	return sql.Open("mysql", dsn)
}

func (d mysqlDriver) Init(db *gorm.DB) error {
	return nil
}

func (d mysqlDriver) NormalizeError(err error) error {
	mysqlErr, ok := err.(*mysql.MySQLError)
	if !ok {
		return err
	}

	switch mysqlErr.Number {
	case 1062:
		return ErrItemConflict

	case 1216, 1217, 1451, 1452:
		return ErrUnknownReference

	case 1048, 1364:
		return ErrMissingField
	}

	return err
}

func (d mysqlDriver) WhereClause(column string, v interface{}) (string, interface{}) {
	operator := "="

	switch v.(type) {
	case GlobModifier:
		operator = "LIKE"

		s := string(v.(GlobModifier))
		s = strings.Replace(s, "%", "\\%", -1)
		s = strings.Replace(s, "_", "\\_", -1)
		s = strings.Replace(s, "*", "%", -1)

		v = strings.Replace(s, "?", "_", -1)

	case RegexpModifier:
		operator = "REGEXP"

	case string:
		if s := v.(string); s == "null" {
			operator = "IS"
			v = nil
		}
	}

	return fmt.Sprintf("%s %s ?", column, operator), v
}

func init() {
	drivers["mysql"] = func(name string, settings *maputil.Map) (sqlDriver, error) {
		var err error

		d := &mysqlDriver{}

		if d.host, err = settings.GetString("host", defaultMysqlDriverHost); err != nil {
			return nil, errors.Wrap(err, "invalid \"host\" setting")
		}

		if d.port, err = settings.GetInt("port", defaultMysqlDriverPort); err != nil {
			return nil, errors.Wrap(err, "invalid \"port\" setting")
		}

		if d.user, err = settings.GetString("user", name); err != nil {
			return nil, errors.Wrap(err, "invalid \"user\" setting")
		}

		if d.password, err = settings.GetString("password", ""); err != nil {
			return nil, errors.Wrap(err, "invalid \"password\" setting")
		}

		if d.dbName, err = settings.GetString("dbname", name); err != nil {
			return nil, errors.Wrap(err, "invalid \"dbname\" setting")
		}

		return d, nil
	}
}
<commit_msg>Add MySQL charset setting support<commit_after>// +build !disable_driver_mysql

package sqlstorage

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/facette/maputil"
	"github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
)

const (
	defaultMysqlDriverHost = "localhost"
	defaultMysqlDriverPort = 3306
)

// mysqlDriver implements the database driver interface for MySQL.
type mysqlDriver struct {
	host     string
	port     int
	user     string
	password string
	dbName   string
	charset  string
}

func (d mysqlDriver) Name() string {
	return "mysql"
}

func (d mysqlDriver) Open() (*sql.DB, error) {
	dsn := fmt.Sprintf(
		"%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true",
		d.user,
		d.password,
		d.host,
		d.port,
		d.dbName,
		d.charset,
	)

	return sql.Open("mysql", dsn)
}

func (d mysqlDriver) Init(db *gorm.DB) error {
	return nil
}

func (d mysqlDriver) NormalizeError(err error) error {
	mysqlErr, ok := err.(*mysql.MySQLError)
	if !ok {
		return err
	}

	switch mysqlErr.Number {
	case 1062:
		return ErrItemConflict

	case 1216, 1217, 1451, 1452:
		return ErrUnknownReference

	case 1048, 1364:
		return ErrMissingField
	}

	return err
}

func (d mysqlDriver) WhereClause(column string, v interface{}) (string, interface{}) {
	operator := "="

	switch v.(type) {
	case GlobModifier:
		operator = "LIKE"

		s := string(v.(GlobModifier))
		s = strings.Replace(s, "%", "\\%", -1)
		s = strings.Replace(s, "_", "\\_", -1)
		s = strings.Replace(s, "*", "%", -1)

		v = strings.Replace(s, "?", "_", -1)

	case RegexpModifier:
		operator = "REGEXP"

	case string:
		if s := v.(string); s == "null" {
			operator = "IS"
			v = nil
		}
	}

	return fmt.Sprintf("%s %s ?", column, operator), v
}

func init() {
	drivers["mysql"] = func(name string, settings *maputil.Map) (sqlDriver, error) {
		var err error

		d := &mysqlDriver{}

		if d.host, err = settings.GetString("host", defaultMysqlDriverHost); err != nil {
			return nil, errors.Wrap(err, "invalid \"host\" setting")
		}

		if d.port, err = settings.GetInt("port", defaultMysqlDriverPort); err != nil {
			return nil, errors.Wrap(err, "invalid \"port\" setting")
		}

		if d.user, err = settings.GetString("user", name); err != nil {
			return nil, errors.Wrap(err, "invalid \"user\" setting")
		}

		if d.password, err = settings.GetString("password", ""); err != nil {
			return nil, errors.Wrap(err, "invalid \"password\" setting")
		}

		if d.dbName, err = settings.GetString("dbname", name); err != nil {
			return nil, errors.Wrap(err, "invalid \"dbname\" setting")
		}

		if d.charset, err = settings.GetString("charset", "utf8"); err != nil {
			return nil, errors.Wrap(err, "invalid \"charset\" setting")
		}

		return d, nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	//bigv "bigv.io/client/lib"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// HelpForLocking provides usage information for locking and unlocking hardware
// profiles.
func (cmds *CommandSet) HelpForLocks() ExitCode {
	return E_SUCCESS
}

// HelpForSet provides usage information for the set command and its subcommands.
func (cmds *CommandSet) HelpForSet() ExitCode {
	return E_SUCCESS
}

// LockHWProfile implements the lock-hwprofile command
func (cmds *CommandSet) LockHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	e := cmds.bigv.SetVirtualMachineHardwareProfileLock(name, true)
	return processError(e)
}

// UnlockHWProfile implements the unlock-hwprofile command
func (cmds *CommandSet) UnlockHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	e := cmds.bigv.SetVirtualMachineHardwareProfileLock(name, false)
	return processError(e)
}

// SetCores implements the set-cores command
func (cmds *CommandSet) SetCores(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	if len(args) != 2 {
		fmt.Println("must specify a VM name and a number of CPUs")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	// decide on the number of cores to set now
	cores, err := strconv.Atoi(args[1])
	if err != nil || cores < 1 {
		fmt.Fprintf(os.Stderr, "Invalid number of cores \"%s\"\r\n", args[1])
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	// submit command to BigV
	err = cmds.bigv.SetVirtualMachineCores(name, cores)
	return processError(err)
}

// SetHWProfile implements the set-hwprofile command
func (cmds *CommandSet) SetHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	lock_hwp := flags.Bool("lock", false, "")
	unlock_hwp := flags.Bool("unlock", false, "")
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	// do nothing if --lock and --unlock are both specified
	if *lock_hwp && *unlock_hwp {
		fmt.Println("ambiguous command, both lock and unlock specified")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	// name and hardware profile required
	if len(args) != 2 {
		fmt.Println("must specify a VM name and a hardware profile")
		cmds.HelpForSet()
		return E_PEBKAC
	}
	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	// if lock_hwp or unlock_hwp are specified, account this into the call
	if *lock_hwp {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1], true)
	} else if *unlock_hwp {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1], false)
		// otherwise omit lock
	} else {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1])
	}

	// return
	return processError(err)
}

// SetMemory implements the set-memory command
func (cmds *CommandSet) SetMemory(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	if len(args) != 2 {
		fmt.Println("must specify a VM name and an amount of memory")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	// decide on the number of cores to set now
	m := 1 // decide if user means MB or GB
	if strings.HasSuffix(strings.ToUpper(args[1]), "G") {
		m = 1024
	}

	memory, err := strconv.Atoi(args[1])
	if err != nil || memory < 1 {
		fmt.Fprintf(os.Stderr, "Invalid amount of memory \"%s\"\r\n", args[1])
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	err = cmds.bigv.SetVirtualMachineMemory(name, memory*m)
	return processError(err)
}
<commit_msg>bugfix for the G suffix<commit_after>package main

import (
	//bigv "bigv.io/client/lib"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// HelpForLocking provides usage information for locking and unlocking hardware
// profiles.
func (cmds *CommandSet) HelpForLocks() ExitCode {
	return E_SUCCESS
}

// HelpForSet provides usage information for the set command and its subcommands.
func (cmds *CommandSet) HelpForSet() ExitCode {
	return E_SUCCESS
}

// LockHWProfile implements the lock-hwprofile command
func (cmds *CommandSet) LockHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	e := cmds.bigv.SetVirtualMachineHardwareProfileLock(name, true)
	return processError(e)
}

// UnlockHWProfile implements the unlock-hwprofile command
func (cmds *CommandSet) UnlockHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	e := cmds.bigv.SetVirtualMachineHardwareProfileLock(name, false)
	return processError(e)
}

// SetCores implements the set-cores command
func (cmds *CommandSet) SetCores(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	if len(args) != 2 {
		fmt.Println("must specify a VM name and a number of CPUs")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	// decide on the number of cores to set now
	cores, err := strconv.Atoi(args[1])
	if err != nil || cores < 1 {
		fmt.Fprintf(os.Stderr, "Invalid number of cores \"%s\"\r\n", args[1])
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	// submit command to BigV
	err = cmds.bigv.SetVirtualMachineCores(name, cores)
	return processError(err)
}

// SetHWProfile implements the set-hwprofile command
func (cmds *CommandSet) SetHWProfile(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	lock_hwp := flags.Bool("lock", false, "")
	unlock_hwp := flags.Bool("unlock", false, "")
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	// do nothing if --lock and --unlock are both specified
	if *lock_hwp && *unlock_hwp {
		fmt.Println("ambiguous command, both lock and unlock specified")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	// name and hardware profile required
	if len(args) != 2 {
		fmt.Println("must specify a VM name and a hardware profile")
		cmds.HelpForSet()
		return E_PEBKAC
	}
	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	// if lock_hwp or unlock_hwp are specified, account this into the call
	if *lock_hwp {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1], true)
	} else if *unlock_hwp {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1], false)
		// otherwise omit lock
	} else {
		err = cmds.bigv.SetVirtualMachineHardwareProfile(name, args[1])
	}

	// return
	return processError(err)
}

// SetMemory implements the set-memory command
func (cmds *CommandSet) SetMemory(args []string) ExitCode {
	flags := MakeCommonFlagSet()
	flags.Parse(args)
	args = cmds.config.ImportFlags(flags)

	if len(args) != 2 {
		fmt.Println("must specify a VM name and an amount of memory")
		cmds.HelpForSet()
		return E_PEBKAC
	}

	name, err := cmds.bigv.ParseVirtualMachineName(args[0])
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to parse VM name\r\n")
		return E_PEBKAC
	}

	// decide on the number of cores to set now
	m := 1 // decide if user means MB or GB
	if strings.HasSuffix(strings.ToUpper(args[1]), "G") {
		m = 1024
		args[1] = strings.TrimSuffix(strings.ToUpper(args[1]), "G")
	}

	memory, err := strconv.Atoi(args[1])
	if err != nil || memory < 1 {
		fmt.Fprintf(os.Stderr, "Invalid amount of memory \"%s\"\r\n", args[1])
		return E_PEBKAC
	}

	err = cmds.EnsureAuth()
	if err != nil {
		return processError(err)
	}

	err = cmds.bigv.SetVirtualMachineMemory(name, memory*m)
	return processError(err)
}
<|endoftext|>


<commit_before>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package gorilla/appengine/sessions implements session stores for Google App
Engine's datastore and memcache.

Usage is the same as described in gorilla/sessions documentation:

	http://gorilla-web.appspot.com/pkg/gorilla/sessions

...with a little preparation needed: the new stores must be registered and
their secret keys must be set before use. We can do it in a init() function:

	import (
		// ...
		appengineSessions "gorilla.googlecode.com/hg/gorilla/appengine/sessions"
		"gorilla.googlecode.com/hg/gorilla/sessions"
	)

	func init() {
		// Register the datastore and memcache session stores.
		sessions.SetStore("datastore", new(appengineSessions.DatastoreSessionStore))
		sessions.SetStore("memcache", new(appengineSessions.MemcacheSessionStore))

		// Set secret keys for the session stores.
		sessions.SetStoreKeys("datastore", []byte("my-secret-key"))
		sessions.SetStoreKeys("memcache", []byte("my-secret-key"))
	}

After this, to retrieve a session using datastore or memcache, pass a third
parameter to sessions.Session() with the key used to register the store.
For datastore:

	if session, err := sessions.Session(r, "", "datastore"); err == nil {
		session["foo"] = "bar"
		sessions.Save(r, w)
	}

Or for memcache:

	if session, err := sessions.Session(r, "", "memcache"); err == nil {
		session["foo"] = "bar"
		sessions.Save(r, w)
	}
*/
package sessions
<commit_msg>appengine/sessions: updated documentation to new API.<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package gorilla/appengine/sessions implements session stores for Google App
Engine's datastore and memcache.

Usage is the same as described in gorilla/sessions documentation:

	http://gorilla-web.appspot.com/pkg/gorilla/sessions

...but you'll use the DatastoreStore or MemcacheStore to load and save your
sessions. Let's initialize both:

	var dStore = sessions.NewDatastoreStore("", []byte("very-secret"))
	var mStore = sessions.NewMemcacheStore("", []byte("a-lot-secret"))

After this, call the appropriate store to retrieve a session, and then call
Save() on the session to save it:

	func MyHandler(w http.ResponseWriter, r *http.Request) {
		// Get a session. We're ignoring the error resulted from decoding an
		// existing session: Get() always returns a session, even if empty.
		session, _ := dStore.Get(r, "session-name")
		// Set some session values.
		session.Values["foo"] = "bar"
		session.Values[42] = 43
		// Save it.
		session.Save(r, w)
	}

Check the sessions package documentation for more details about other features.
*/
package sessions
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html/template"
	"log"
	"os"
	"strconv"

	"github.com/codegangsta/cli"
	"github.com/drone/drone-go/drone"
)

var deployCmd = cli.Command{
	Name:  "deploy",
	Usage: "deploy code",
	Action: func(c *cli.Context) {
		if err := deploy(c); err != nil {
			log.Fatalln(err)
		}
	},
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "format output",
			Value: tmplDeployInfo,
		},
	},
}

func deploy(c *cli.Context) error {
	repo := c.Args().First()
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}
	number, err := strconv.Atoi(c.Args().Get(1))
	if err != nil {
		return err
	}

	client, err := newClient(c)
	if err != nil {
		return err
	}

	build, err := client.Build(owner, name, number)
	if err != nil {
		return err
	}
	if build.Event == drone.EventPull {
		return fmt.Errorf("Cannot deploy a pull request")
	}
	env := c.Args().Get(2)
	if env == "" {
		return fmt.Errorf("Please specify the target environment (ie production)")
	}

	deploy, err := client.Deploy(owner, name, number, env)
	if err != nil {
		return err
	}

	tmpl, err := template.New("_").Parse(c.String("format"))
	if err != nil {
		return err
	}
	return tmpl.Execute(os.Stdout, deploy)
}

// template for deployment information
var tmplDeployInfo = `Number: {{ .Number }}
Status: {{ .Status }}
Commit: {{ .Commit }}
Branch: {{ .Branch }}
Ref: {{ .Ref }}
Message: {{ .Message }}
Author: {{ .Author }}
Target: {{ .Deploy }}
`
<commit_msg>remove referene to external package<commit_after>package main

import (
	"fmt"
	"html/template"
	"log"
	"os"
	"strconv"

	"github.com/codegangsta/cli"
	"github.com/drone/drone/model"
)

var deployCmd = cli.Command{
	Name:  "deploy",
	Usage: "deploy code",
	Action: func(c *cli.Context) {
		if err := deploy(c); err != nil {
			log.Fatalln(err)
		}
	},
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "format output",
			Value: tmplDeployInfo,
		},
	},
}

func deploy(c *cli.Context) error {
	repo := c.Args().First()
	owner, name, err := parseRepo(repo)
	if err != nil {
		return err
	}
	number, err := strconv.Atoi(c.Args().Get(1))
	if err != nil {
		return err
	}

	client, err := newClient(c)
	if err != nil {
		return err
	}

	build, err := client.Build(owner, name, number)
	if err != nil {
		return err
	}
	if build.Event == model.EventPull {
		return fmt.Errorf("Cannot deploy a pull request")
	}
	env := c.Args().Get(2)
	if env == "" {
		return fmt.Errorf("Please specify the target environment (ie production)")
	}

	deploy, err := client.Deploy(owner, name, number, env)
	if err != nil {
		return err
	}

	tmpl, err := template.New("_").Parse(c.String("format"))
	if err != nil {
		return err
	}
	return tmpl.Execute(os.Stdout, deploy)
}

// template for deployment information
var tmplDeployInfo = `Number: {{ .Number }}
Status: {{ .Status }}
Commit: {{ .Commit }}
Branch: {{ .Branch }}
Ref: {{ .Ref }}
Message: {{ .Message }}
Author: {{ .Author }}
Target: {{ .Deploy }}
`
<|endoftext|>


<commit_before>// entry.go
//
// Copyright (c) 2016, Ayke van Laethem
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package dtdiff

import (
	"bytes"
	"sort"
	"strconv"
	"time"

	"github.com/aykevl/dtsync/tree"
)

type revision struct {
	identity   string // random string as identifier for the replica
	generation int    // starts at 1, 0 means 'no generation'
}

// String for debug/test printing.
func (r revision) String() string {
	return r.identity + ":" + strconv.Itoa(r.generation)
}

// An Entry is one object (row) in a Replica. It belongs to one Replica.
type Entry struct {
	name     string
	revision // last content (hash) change
	fileType tree.Type
	modTime  time.Time
	size     int64
	mode     tree.Mode
	hasMode  tree.Mode
	hash     []byte
	children map[string]*Entry
	parent   *Entry
	replica  *Replica
	removed  time.Time
}

// String function, for debugging purposes
func (e *Entry) String() string {
	return "dtdiff.Entry(" + e.name + "," + e.revision.String() + ")"
}

// Name returns the name of this entry
func (e *Entry) Name() string {
	return e.name
}

// RelativePath returns the path relative to the root
func (e *Entry) RelativePath() []string {
	if e.parent == nil {
		return nil
	} else {
		return append(e.parent.RelativePath(), e.name)
	}
}

// Hash returns the hash of the status entry, if one is known.
func (e *Entry) Hash() []byte {
	return e.hash
}

// Type returns the tree.Type filetype.
func (e *Entry) Type() tree.Type {
	return e.fileType
}

// Mode returns the permission bits for this entry.
func (e *Entry) Mode() tree.Mode {
	return e.mode
}

// HasMode returns the permission bits this entry supports.
func (e *Entry) HasMode() tree.Mode {
	return e.hasMode
}

// ModTime returns the last modification time.
func (e *Entry) ModTime() time.Time {
	return e.modTime
}

// Size returns the filesize for regular files, or 0.
func (e *Entry) Size() int64 {
	return e.size
}

// Add new entry by recursively finding the parent
func (e *Entry) addRecursive(path []string, rev revision, fingerprint string, mode tree.Mode, hash []byte) (*Entry, error) {
	if path[0] == "" {
		return nil, errInvalidPath
	}
	if len(path) > 1 {
		child, ok := e.children[path[0]]
		if !ok {
			// child does not exist
			// or: the path has a parent that hasn't yet been scanned
			return nil, errInvalidPath
		}
		return child.addRecursive(path[1:], rev, fingerprint, mode, hash)
	} else {
		fileInfo, err := parseFingerprint(fingerprint)
		if err != nil {
			return nil, err
		}
		return e.addChild(path[0], rev, fileInfo, mode, hash)
	}
}

func (e *Entry) addChild(name string, rev revision, fileInfo fingerprintInfo, mode tree.Mode, hash []byte) (*Entry, error) {
	if _, ok := e.children[name]; ok {
		// duplicate path
		return nil, ErrExists
	}
	newEntry := &Entry{
		name:     name,
		revision: rev,
		fileType: fileInfo.fileType,
		modTime:  fileInfo.modTime,
		size:     fileInfo.size,
		mode:     mode,
		hasMode:  e.hasMode,
		hash:     hash,
		children: make(map[string]*Entry),
		parent:   e,
		replica:  e.replica,
	}
	e.children[newEntry.name] = newEntry
	return newEntry, nil
}

// Get returns the named child, or nil if it doesn't exist.
func (e *Entry) Get(name string) *Entry {
	return e.children[name]
}

// HasRevision returns true if this file (actually, this replica) includes the
// revision the other entry is at.
func (e *Entry) HasRevision(other *Entry) bool {
	return e.replica.knowledge[other.identity] >= other.generation
}

// Equal returns true if both entries are of the same revision (replica and
// generation). Not recursive.
func (e *Entry) Equal(e2 *Entry) bool {
	return e.HasRevision(e2) && e2.HasRevision(e)
}

// EqualContents returns true if the contents (fingerprint/hash) of these
// entries is the same.
func (e *Entry) EqualContents(e2 *Entry) bool {
	if tree.MatchFingerprint(e, e2) {
		return true
	}
	if len(e.hash) > 0 && bytes.Equal(e.hash, e2.hash) {
		return true
	}
	return false
}

// EqualMode compares the mode bits, noting the HasMode of both entries.
func (e *Entry) EqualMode(e2 *Entry) bool {
	hasMode := e.hasMode & e2.hasMode
	return e.mode&hasMode == e2.mode&hasMode
}

// After returns true if this entry was modified after the other.
func (e *Entry) After(e2 *Entry) bool {
	return e.generation > e2.replica.knowledge[e.identity]
}

// Before returns true if this entry is modified before the other.
func (e *Entry) Before(e2 *Entry) bool {
	return e2.After(e)
}

// Conflict returns true if both entries are modified.
func (e *Entry) Conflict(e2 *Entry) bool {
	return e.After(e2) && e2.After(e)
}

// Includes returns true if this entry includes all revisions from the other
// entry (recursively: children are also compared).
//
// FIXME: It does not always work when one file is removed. It does work however
// to check for equality, though.
func (e *Entry) Includes(e2 *Entry) bool {
	if e2.After(e) && !e.isRoot() {
		return false
	}

	for name, child2 := range e2.children {
		_, ok := e.children[name]
		if !ok {
			// if child2 has all revisions, that means it is deleted here, not
			// new there.
			if child2.HasRevision(e) {
				// their child was removed
				return false
			}
		}
	}
	for name, child := range e.children {
		child2, ok := e2.children[name]
		if !ok {
			if !child.HasRevision(e2) {
				// our child is outdated and to-be-removed
				return false
			}
		} else {
			if !child.Includes(child2) {
				return false
			}
		}
	}
	return true
}

func (e *Entry) isRoot() bool {
	return e.parent == nil
}

func (e *Entry) List() []*Entry {
	list := make([]*Entry, 0, len(e.children))
	for _, entry := range e.children {
		list = append(list, entry)
	}

	// Remove removed children from the list.
	p := 0
	for i, child := range list {
		if i != p {
			list[p] = list[i]
		}
		if child.removed.IsZero() {
			// go to next if not removed
			p++
		}
	}
	list = list[:p]

	sortEntries(list)
	return list
}

// Add a new status entry.
func (e *Entry) Add(info tree.FileInfo, source *Entry) (*Entry, error) {
	return e.add(info, source.revision)
}

func (e *Entry) add(info tree.FileInfo, rev revision) (*Entry, error) {
	e.replica.markChanged()
	fileInfo := fingerprintInfo{info.Type(), info.ModTime(), info.Size()}
	if fileInfo.fileType != tree.TYPE_REGULAR {
		// Maybe not necessary, but just to be safe...
		fileInfo.size = 0
	}
	return e.addChild(info.Name(), rev, fileInfo, info.Mode(), info.Hash())
}

// Update updates the revision if the file was changed. The file is not changed
// if the fingerprint but not the hash changed.
func (e *Entry) Update(info tree.FileInfo, hash []byte, source *Entry) {
	if !tree.MatchFingerprint(e, info) {
		e.fileType = info.Type()
		e.modTime = info.ModTime()
		e.size = info.Size()
		e.mode = info.Mode()
		e.hasMode = info.HasMode()
		if e.Type() == tree.TYPE_SYMLINK {
			// Changes in fingerprints of symbolic links must be tracked. For
			// regular files we look at the hash and for directories fingerprint
			// changes do not cause updates at all (but are still tracked).
			if source != nil {
				e.replica.markMetaChanged()
				e.revision = source.revision
			} else {
				e.replica.markChanged()
				e.revision = e.replica.revision
			}
		} else {
			e.replica.markMetaChanged()
		}
	}
	if info.HasMode()&^e.hasMode != 0 {
		// There are new permission bits. Mark as changed so permissions are
		// compared.
		e.replica.markChanged()
		e.revision = e.replica.revision
	}
	if info.HasMode() != e.hasMode {
		// This is a different filesystem or the support for filesystem
		// detection changed.
		e.replica.markMetaChanged()
		e.hasMode = info.HasMode()
	}
	hasMode := e.hasMode // same as info.HasMode()
	if info.Mode()&hasMode != e.mode&hasMode {
		// The permission bits got updated.
		e.mode = info.Mode() & hasMode
		if source != nil {
			e.revision = source.revision
		} else {
			e.replica.markChanged()
			e.revision = e.replica.revision
		}
	}
	e.UpdateHash(hash, source)
}

// UpdateHash sets the new hash from the parameter, marking this file as changed
// if it is different from the existing one.
func (e *Entry) UpdateHash(hash []byte, source *Entry) {
	if !bytes.Equal(e.hash, hash) {
		if source != nil {
			e.replica.markMetaChanged()
			e.revision = source.revision
		} else {
			e.replica.markChanged()
			e.revision = e.replica.revision
		}
		e.hash = hash
	}
}

// Remove this entry.
func (e *Entry) Remove() {
	e.replica.markChanged()
	// simply make it unreachable
	delete(e.parent.children, e.name)
}

type entrySlice []*Entry

func (es entrySlice) Len() int {
	return len(es)
}

func (es entrySlice) Less(i, j int) bool {
	return es[i].name < es[j].name
}

func (es entrySlice) Swap(i, j int) {
	es[i], es[j] = es[j], es[i]
}

func sortEntries(list []*Entry) {
	sort.Sort(entrySlice(list))
}

// IterateEntries returns a channel, reads from the channel will return each
// entry in the slice.
func IterateEntries(list []*Entry) chan *Entry {
	c := make(chan *Entry)
	go func() {
		for _, entry := range list {
			c <- entry
		}
		close(c)
	}()
	return c
}
<commit_msg>dtdiff: nicer Entry.String()<commit_after>// entry.go
//
// Copyright (c) 2016, Ayke van Laethem
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package dtdiff

import (
	"bytes"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/aykevl/dtsync/tree"
)

type revision struct {
	identity   string // random string as identifier for the replica
	generation int    // starts at 1, 0 means 'no generation'
}

// String for debug/test printing.
func (r revision) String() string {
	return r.identity + ":" + strconv.Itoa(r.generation)
}

// An Entry is one object (row) in a Replica. It belongs to one Replica.
type Entry struct {
	name     string
	revision // last content (hash) change
	fileType tree.Type
	modTime  time.Time
	size     int64
	mode     tree.Mode
	hasMode  tree.Mode
	hash     []byte
	children map[string]*Entry
	parent   *Entry
	replica  *Replica
	removed  time.Time
}

// String function, for debugging purposes
func (e *Entry) String() string {
	return "dtdiff.Entry(" + strings.Join(e.RelativePath(), "/") + "," + e.revision.String() + ")"
}

// Name returns the name of this entry
func (e *Entry) Name() string {
	return e.name
}

// RelativePath returns the path relative to the root
func (e *Entry) RelativePath() []string {
	if e.parent == nil {
		return nil
	} else {
		return append(e.parent.RelativePath(), e.name)
	}
}

// Hash returns the hash of the status entry, if one is known.
func (e *Entry) Hash() []byte {
	return e.hash
}

// Type returns the tree.Type filetype.
func (e *Entry) Type() tree.Type {
	return e.fileType
}

// Mode returns the permission bits for this entry.
func (e *Entry) Mode() tree.Mode {
	return e.mode
}

// HasMode returns the permission bits this entry supports.
func (e *Entry) HasMode() tree.Mode {
	return e.hasMode
}

// ModTime returns the last modification time.
func (e *Entry) ModTime() time.Time {
	return e.modTime
}

// Size returns the filesize for regular files, or 0.
func (e *Entry) Size() int64 {
	return e.size
}

// Add new entry by recursively finding the parent
func (e *Entry) addRecursive(path []string, rev revision, fingerprint string, mode tree.Mode, hash []byte) (*Entry, error) {
	if path[0] == "" {
		return nil, errInvalidPath
	}
	if len(path) > 1 {
		child, ok := e.children[path[0]]
		if !ok {
			// child does not exist
			// or: the path has a parent that hasn't yet been scanned
			return nil, errInvalidPath
		}
		return child.addRecursive(path[1:], rev, fingerprint, mode, hash)
	} else {
		fileInfo, err := parseFingerprint(fingerprint)
		if err != nil {
			return nil, err
		}
		return e.addChild(path[0], rev, fileInfo, mode, hash)
	}
}

func (e *Entry) addChild(name string, rev revision, fileInfo fingerprintInfo, mode tree.Mode, hash []byte) (*Entry, error) {
	if _, ok := e.children[name]; ok {
		// duplicate path
		return nil, ErrExists
	}
	newEntry := &Entry{
		name:     name,
		revision: rev,
		fileType: fileInfo.fileType,
		modTime:  fileInfo.modTime,
		size:     fileInfo.size,
		mode:     mode,
		hasMode:  e.hasMode,
		hash:     hash,
		children: make(map[string]*Entry),
		parent:   e,
		replica:  e.replica,
	}
	e.children[newEntry.name] = newEntry
	return newEntry, nil
}

// Get returns the named child, or nil if it doesn't exist.
func (e *Entry) Get(name string) *Entry {
	return e.children[name]
}

// HasRevision returns true if this file (actually, this replica) includes the
// revision the other entry is at.
func (e *Entry) HasRevision(other *Entry) bool {
	return e.replica.knowledge[other.identity] >= other.generation
}

// Equal returns true if both entries are of the same revision (replica and
// generation). Not recursive.
func (e *Entry) Equal(e2 *Entry) bool {
	return e.HasRevision(e2) && e2.HasRevision(e)
}

// EqualContents returns true if the contents (fingerprint/hash) of these
// entries is the same.
func (e *Entry) EqualContents(e2 *Entry) bool {
	if tree.MatchFingerprint(e, e2) {
		return true
	}
	if len(e.hash) > 0 && bytes.Equal(e.hash, e2.hash) {
		return true
	}
	return false
}

// EqualMode compares the mode bits, noting the HasMode of both entries.
func (e *Entry) EqualMode(e2 *Entry) bool {
	hasMode := e.hasMode & e2.hasMode
	return e.mode&hasMode == e2.mode&hasMode
}

// After returns true if this entry was modified after the other.
func (e *Entry) After(e2 *Entry) bool {
	return e.generation > e2.replica.knowledge[e.identity]
}

// Before returns true if this entry is modified before the other.
func (e *Entry) Before(e2 *Entry) bool {
	return e2.After(e)
}

// Conflict returns true if both entries are modified.
func (e *Entry) Conflict(e2 *Entry) bool {
	return e.After(e2) && e2.After(e)
}

// Includes returns true if this entry includes all revisions from the other
// entry (recursively: children are also compared).
//
// FIXME: It does not always work when one file is removed. It does work however
// to check for equality, though.
func (e *Entry) Includes(e2 *Entry) bool {
	if e2.After(e) && !e.isRoot() {
		return false
	}

	for name, child2 := range e2.children {
		_, ok := e.children[name]
		if !ok {
			// if child2 has all revisions, that means it is deleted here, not
			// new there.
			if child2.HasRevision(e) {
				// their child was removed
				return false
			}
		}
	}
	for name, child := range e.children {
		child2, ok := e2.children[name]
		if !ok {
			if !child.HasRevision(e2) {
				// our child is outdated and to-be-removed
				return false
			}
		} else {
			if !child.Includes(child2) {
				return false
			}
		}
	}
	return true
}

func (e *Entry) isRoot() bool {
	return e.parent == nil
}

func (e *Entry) List() []*Entry {
	list := make([]*Entry, 0, len(e.children))
	for _, entry := range e.children {
		list = append(list, entry)
	}

	// Remove removed children from the list.
	p := 0
	for i, child := range list {
		if i != p {
			list[p] = list[i]
		}
		if child.removed.IsZero() {
			// go to next if not removed
			p++
		}
	}
	list = list[:p]

	sortEntries(list)
	return list
}

// Add a new status entry.
func (e *Entry) Add(info tree.FileInfo, source *Entry) (*Entry, error) {
	return e.add(info, source.revision)
}

func (e *Entry) add(info tree.FileInfo, rev revision) (*Entry, error) {
	e.replica.markChanged()
	fileInfo := fingerprintInfo{info.Type(), info.ModTime(), info.Size()}
	if fileInfo.fileType != tree.TYPE_REGULAR {
		// Maybe not necessary, but just to be safe...
		fileInfo.size = 0
	}
	return e.addChild(info.Name(), rev, fileInfo, info.Mode(), info.Hash())
}

// Update updates the revision if the file was changed. The file is not changed
// if the fingerprint but not the hash changed.
func (e *Entry) Update(info tree.FileInfo, hash []byte, source *Entry) {
	if !tree.MatchFingerprint(e, info) {
		e.fileType = info.Type()
		e.modTime = info.ModTime()
		e.size = info.Size()
		e.mode = info.Mode()
		e.hasMode = info.HasMode()
		if e.Type() == tree.TYPE_SYMLINK {
			// Changes in fingerprints of symbolic links must be tracked. For
			// regular files we look at the hash and for directories fingerprint
			// changes do not cause updates at all (but are still tracked).
			if source != nil {
				e.replica.markMetaChanged()
				e.revision = source.revision
			} else {
				e.replica.markChanged()
				e.revision = e.replica.revision
			}
		} else {
			e.replica.markMetaChanged()
		}
	}
	if info.HasMode()&^e.hasMode != 0 {
		// There are new permission bits. Mark as changed so permissions are
		// compared.
		e.replica.markChanged()
		e.revision = e.replica.revision
	}
	if info.HasMode() != e.hasMode {
		// This is a different filesystem or the support for filesystem
		// detection changed.
		e.replica.markMetaChanged()
		e.hasMode = info.HasMode()
	}
	hasMode := e.hasMode // same as info.HasMode()
	if info.Mode()&hasMode != e.mode&hasMode {
		// The permission bits got updated.
		e.mode = info.Mode() & hasMode
		if source != nil {
			e.revision = source.revision
		} else {
			e.replica.markChanged()
			e.revision = e.replica.revision
		}
	}
	e.UpdateHash(hash, source)
}

// UpdateHash sets the new hash from the parameter, marking this file as changed
// if it is different from the existing one.
func (e *Entry) UpdateHash(hash []byte, source *Entry) {
	if !bytes.Equal(e.hash, hash) {
		if source != nil {
			e.replica.markMetaChanged()
			e.revision = source.revision
		} else {
			e.replica.markChanged()
			e.revision = e.replica.revision
		}
		e.hash = hash
	}
}

// Remove this entry.
func (e *Entry) Remove() {
	e.replica.markChanged()
	// simply make it unreachable
	delete(e.parent.children, e.name)
}

type entrySlice []*Entry

func (es entrySlice) Len() int {
	return len(es)
}

func (es entrySlice) Less(i, j int) bool {
	return es[i].name < es[j].name
}

func (es entrySlice) Swap(i, j int) {
	es[i], es[j] = es[j], es[i]
}

func sortEntries(list []*Entry) {
	sort.Sort(entrySlice(list))
}

// IterateEntries returns a channel, reads from the channel will return each
// entry in the slice.
func IterateEntries(list []*Entry) chan *Entry {
	c := make(chan *Entry)
	go func() {
		for _, entry := range list {
			c <- entry
		}
		close(c)
	}()
	return c
}
<|endoftext|>


<commit_before>package editor

import (
	"strings"
)

// basic represents a basic editor.
// Valid positions are in range [0, len(e.buf)].
type basic struct {
	pos int
	buf []rune
}

// move moves the position to the given position.
// Given a invalid position, move sets the position at the end of the buffer.
func (e *basic) move(to int) {
	switch {
	case to >= len(e.buf):
		e.pos = len(e.buf)
	case to <= 0:
		e.pos = 0
	default:
		e.pos = to
	}
}

// insert inserts s into the buffer at the given position.
// Given a invalid position, insert considers the position to be at the end of the buffer.
func (e *basic) insert(s []rune, at int) {
	switch {
	case at < 0:
		at = 0
	case at > len(e.buf):
		at = len(e.buf)
	}
	switch at {
	case 0:
		e.buf = append(s, e.buf...)
	case len(e.buf):
		e.buf = append(e.buf, s...)
	default:
		x := append(e.buf[:at:at], s...)
		e.buf = append(x, e.buf[at:]...)
	}
	if at <= e.pos {
		e.pos += len(s)
	}
}

func max(m, n int) int {
	if m > n {
		return m
	}
	return n
}

func min(m, n int) int {
	if m < n {
		return m
	}
	return n
}

func constrain(n, low, high int) int {
	if n < low {
		return low
	}
	if n > high {
		return high
	}
	return n
}

// delete deletes runes from the buffer [from, to].
// Given a invalid position, delete considers the position to be at the end of the buffer.
func (e *basic) delete(from, to int) {
	left := constrain(min(from, to), 0, len(e.buf))
	right := constrain(max(from, to), 0, len(e.buf))
	switch {
	case left == 0:
		e.buf = e.buf[right:]
	case right == len(e.buf):
		e.buf = e.buf[:left]
	default:
		e.buf = append(e.buf[:left], e.buf[right:]...)
	}
	switch {
	case e.pos < left:
	case right < e.pos:
		e.pos = e.pos - (right - left)
	default:
		e.pos = left
	}
}

// slice slices the buffer [from, to].
// Given a invalid position, slice considers the position to be at the end of the buffer.
func (e *basic) slice(from, to int) []rune {
	left := constrain(min(from, to), 0, len(e.buf))
	right := constrain(max(from, to), 0, len(e.buf))
	return e.buf[left:right]
}

func (e *basic) index(ch rune, start int) int {
	start = constrain(start, 0, len(e.buf))
	for i := start; i < len(e.buf); i++ {
		if e.buf[i] == ch {
			return i
		}
	}
	return -1
}

func (e *basic) lastIndex(ch rune, last int) int {
	last = constrain(last, 0, len(e.buf))
	for i := last - 1; i >= 0; i-- {
		if e.buf[i] == ch {
			return i
		}
	}
	return -1
}

// indexFunc(f, start, true) == indexFunc(func(r) bool { return !f(r) }, start, false)
func (e *basic) indexFunc(f func(rune) bool, start int, truth bool) int {
	start = constrain(start, 0, len(e.buf))
	for i := start; i < len(e.buf); i++ {
		if f(e.buf[i]) == truth {
			return i
		}
	}
	return -1
}

// lastIndexFunc(f, start, true) == lastIndexFunc(func(r) bool { return !f(r) }, start, false)
func (e *basic) lastIndexFunc(f func(rune) bool, last int, truth bool) int {
	last = constrain(last, 0, len(e.buf))
	for i := last - 1; i >= 0; i-- {
		if f(e.buf[i]) == truth {
			return i
		}
	}
	return -1
}

func (e *basic) replace(s []rune, at int) {
	if s == nil {
		return
	}
	switch {
	case len(e.buf) <= at:
		e.buf = append(e.buf, []rune(strings.Repeat(" ", at-len(e.buf)))...)
		e.buf = append(e.buf, s...)
	case at+len(s) <= 0:
		// no-op
	case at < 0:
		for i := 0; i < at+len(s); i++ {
			e.buf[i] = s[i-at]
		}
	case len(e.buf) < at+len(s):
		v := make([]rune, at+len(s))
		copy(v, e.buf)
		for i := at; i < at+len(s); i++ {
			v[i] = s[i-at]
		}
		e.buf = v
	default:
		for i := at; i < at+len(s); i++ {
			e.buf[i] = s[i-at]
		}
	}
}
<commit_msg>Fix editor.basic.slice()<commit_after>package editor

import (
	"strings"
)

// basic represents a basic editor.
// Valid positions are in range [0, len(e.buf)].
type basic struct {
	pos int
	buf []rune
}

// move moves the position to the given position.
// Given a invalid position, move sets the position at the end of the buffer.
func (e *basic) move(to int) {
	switch {
	case to >= len(e.buf):
		e.pos = len(e.buf)
	case to <= 0:
		e.pos = 0
	default:
		e.pos = to
	}
}

// insert inserts s into the buffer at the given position.
// Given a invalid position, insert considers the position to be at the end of the buffer.
func (e *basic) insert(s []rune, at int) {
	switch {
	case at < 0:
		at = 0
	case at > len(e.buf):
		at = len(e.buf)
	}
	switch at {
	case 0:
		e.buf = append(s, e.buf...)
	case len(e.buf):
		e.buf = append(e.buf, s...)
	default:
		x := append(e.buf[:at:at], s...)
		e.buf = append(x, e.buf[at:]...)
	}
	if at <= e.pos {
		e.pos += len(s)
	}
}

func max(m, n int) int {
	if m > n {
		return m
	}
	return n
}

func min(m, n int) int {
	if m < n {
		return m
	}
	return n
}

func constrain(n, low, high int) int {
	if n < low {
		return low
	}
	if n > high {
		return high
	}
	return n
}

// delete deletes runes from the buffer [from, to].
// Given a invalid position, delete considers the position to be at the end of the buffer.
func (e *basic) delete(from, to int) {
	left := constrain(min(from, to), 0, len(e.buf))
	right := constrain(max(from, to), 0, len(e.buf))
	switch {
	case left == 0:
		e.buf = e.buf[right:]
	case right == len(e.buf):
		e.buf = e.buf[:left]
	default:
		e.buf = append(e.buf[:left], e.buf[right:]...)
	}
	switch {
	case e.pos < left:
	case right < e.pos:
		e.pos = e.pos - (right - left)
	default:
		e.pos = left
	}
}

// slice slices the buffer [from, to].
// Given a invalid position, slice considers the position to be at the end of the buffer.
func (e *basic) slice(from, to int) []rune {
	left := constrain(min(from, to), 0, len(e.buf))
	right := constrain(max(from, to), 0, len(e.buf))
	s := make([]rune, right-left)
	copy(s, e.buf[left:right])
	return s
}

func (e *basic) index(ch rune, start int) int {
	start = constrain(start, 0, len(e.buf))
	for i := start; i < len(e.buf); i++ {
		if e.buf[i] == ch {
			return i
		}
	}
	return -1
}

func (e *basic) lastIndex(ch rune, last int) int {
	last = constrain(last, 0, len(e.buf))
	for i := last - 1; i >= 0; i-- {
		if e.buf[i] == ch {
			return i
		}
	}
	return -1
}

// indexFunc(f, start, true) == indexFunc(func(r) bool { return !f(r) }, start, false)
func (e *basic) indexFunc(f func(rune) bool, start int, truth bool) int {
	start = constrain(start, 0, len(e.buf))
	for i := start; i < len(e.buf); i++ {
		if f(e.buf[i]) == truth {
			return i
		}
	}
	return -1
}

// lastIndexFunc(f, start, true) == lastIndexFunc(func(r) bool { return !f(r) }, start, false)
func (e *basic) lastIndexFunc(f func(rune) bool, last int, truth bool) int {
	last = constrain(last, 0, len(e.buf))
	for i := last - 1; i >= 0; i-- {
		if f(e.buf[i]) == truth {
			return i
		}
	}
	return -1
}

func (e *basic) replace(s []rune, at int) {
	if s == nil {
		return
	}
	switch {
	case len(e.buf) <= at:
		e.buf = append(e.buf, []rune(strings.Repeat(" ", at-len(e.buf)))...)
		e.buf = append(e.buf, s...)
	case at+len(s) <= 0:
		// no-op
	case at < 0:
		for i := 0; i < at+len(s); i++ {
			e.buf[i] = s[i-at]
		}
	case len(e.buf) < at+len(s):
		v := make([]rune, at+len(s))
		copy(v, e.buf)
		for i := at; i < at+len(s); i++ {
			v[i] = s[i-at]
		}
		e.buf = v
	default:
		for i := at; i < at+len(s); i++ {
			e.buf[i] = s[i-at]
		}
	}
}
<|endoftext|>