<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/fsouza/go-dockerclient"
)

// Invoke a `command` in `workdir` with `args`, connecting up its Stdout and Stderr
func Command(workdir, command string, args ...string) *exec.Cmd {
	log.Printf("wd = %s cmd = %s, args = %q", workdir, command, append([]string{}, args...))
	cmd := exec.Command(command, args...)
	cmd.Dir = workdir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

type DockerClient struct {
	underlying *docker.Client
}

func (c *DockerClient) Build(checkoutPath, name, tag string) error {
	return c.underlying.BuildImage(docker.BuildImageOptions{
		Name:         fmt.Sprintf("%s:%s", name, tag),
		ContextDir:   checkoutPath,
		OutputStream: os.Stderr,
	})
}

func (c *DockerClient) Tag(name, what, tag string) error {
	cmd := Command(".", "docker", "tag", what, tag)
	return cmd.Run()
}

func (c *DockerClient) Push(repo, name, tag string) error {
	opts := docker.PushImageOptions{
		Name:         repo + "/" + name,
		Tag:          tag,
		Registry:     repo,
		OutputStream: os.Stderr,
	}
	auth := docker.AuthConfiguration{}
	return c.underlying.PushImage(opts, auth)
}

func RunDaemon() {
	DaemonRoutes()

	log.Println("Listening on :8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func DaemonRoutes() {

	underlying, err := dockerConnect()
	if err != nil {
		log.Fatal("Unable to connect to client")
	}

	client := &DockerClient{underlying}

	http.HandleFunc("/build/", func(w http.ResponseWriter, r *http.Request) {
		target := r.URL.Path[len("/build/"):]

		name := path.Base(target)

		remote := fmt.Sprintf("https://%v.git", target)

		ref := r.URL.Query().Get("ref")
		if ref == "" {
			ref = "HEAD"
		}

		if _, useSSH := r.URL.Query()["ssh"]; useSSH {
			split := strings.SplitN(target, "/", 2)
			domain, repo := split[0], split[1]
			remote = "git@" + domain + ":" + repo
		}

		path := "./src/" + target

		gitLocalMirror(remote, path, os.Stderr)

		rev, err := gitRevParse(path, ref)
		if err != nil {
			log.Printf("Unable to parse rev: %v", err)
			return
		}

		shortRev := rev[:10]

		checkoutPath := "c/" + shortRev

		err = gitCheckout(path, checkoutPath, rev)
		if err != nil {
			log.Printf("Failed to checkout: %v", err)
			return
		}

		tagName, err := gitDescribe(path, rev)
		if err != nil {
			log.Printf("Unable to describe %v: %v", rev, err)
			return
		}

		log.Println("Checked out")

		// dockerImage := name + "-" + shortRev

		repo := "localhost.localdomain:5000"

		fullName := repo + "/" + name

		err = client.Build(path+"/"+checkoutPath, fullName, tagName)
		if err != nil {
			log.Printf("Failed to build: %v", err)
			return
		}

		start := time.Now()

		err = client.Push(repo, name, tagName)
		log.Printf("Took %v to push", time.Since(start))

		if err != nil {
			log.Printf("Failed to push: %v", err)
			return
		}

		// var buf bytes.Buffer
		// start := time.Now()
		// dockerSave(dockerImage, &buf)
		// log.Printf("Took %v to save %v bytes", time.Since(start), buf.Len())

		fmt.Fprintln(w, "Success:", rev)
	})
}

// Creates or updates a mirror of `url` at `git_dir` using `git clone --mirror`
func gitLocalMirror(url, git_dir string, messages io.Writer) (err error) {

	err = os.MkdirAll(git_dir, 0777)
	if err != nil {
		return
	}

	cmd := Command(".", "git", "clone", "-q", "--mirror", url, git_dir)

	cmd.Stdout = messages
	cmd.Stderr = messages
	err = cmd.Run()

	if err == nil {
		log.Println("Cloned", url)

	} else if _, ok := err.(*exec.ExitError); ok {

		done := make(chan struct{})
		// Try "git remote update"

		cmd := Command(git_dir, "git", "fetch")
		cmd.Stdout = messages
		cmd.Stderr = messages

		go func() {
			err = cmd.Run()
			log.Printf("Normal completion %v %v", cmd.Args, cmd.Dir)
			close(done)
		}()

		const timeout = 20 * time.Second
		select {
		case <-done:
		case <-time.After(timeout):
			err = cmd.Process.Kill()
			log.Printf("Killing cmd %+v after %v, error returned: %v", cmd, timeout, err)
			err = fmt.Errorf("cmd %+v timed out", cmd)
		}

		if err != nil {
			// git fetch where there is no update is exit status 1.
			if err.Error() != "exit status 1" {
				return
			}
		}

		log.Println("Remote updated", url)

	} else {
		return
	}

	return
}

func gitHaveFile(git_dir, ref, path string) (ok bool, err error) {
	cmd := Command(git_dir, "git", "show", fmt.Sprintf("%s:%s", ref, path))
	cmd.Stdout = nil // don't want to see the contents
	err = cmd.Run()
	ok = true
	if err != nil {
		ok = false
		if err.Error() == "exit status 128" {
			// This happens if the file doesn't exist.
			err = nil
		}
	}
	return ok, err
}

func gitRevParse(git_dir, ref string) (sha string, err error) {
	cmd := Command(git_dir, "git", "rev-parse", ref)
	cmd.Stdout = nil // for cmd.Output

	var stdout []byte
	stdout, err = cmd.Output()
	if err != nil {
		return
	}

	sha = strings.TrimSpace(string(stdout))
	return
}

func gitDescribe(git_dir, ref string) (desc string, err error) {
	cmd := Command(git_dir, "git", "describe", "--all", "--tags", "--long", ref)
	cmd.Stdout = nil // for cmd.Output

	var stdout []byte
	stdout, err = cmd.Output()
	if err != nil {
		return
	}

	desc = strings.TrimSpace(string(stdout))
	desc = strings.TrimPrefix(desc, "heads/")
	return
}

func gitCheckout(git_dir, checkout_dir, ref string) error {

	// TODO(pwaller): this needs to be protected by a lock per git_dir,
	// since git-ls-files operates on the contents of the index :(

	err := os.MkdirAll(path.Join(git_dir, checkout_dir), 0777)
	if err != nil {
		return err
	}

	log.Println("Populating", checkout_dir)

	// Checkout without specifying a path to update the index
	// (required for ls-files to work correctly)
	args := []string{"--work-tree", checkout_dir, "checkout", ref}
	err = Command(git_dir, "git", args...).Run()
	if err != nil {
		return err
	}

	// Put the files physically in checkout_dir
	args = []string{"--work-tree", checkout_dir, "checkout", ref, "--", "."}
	err = Command(git_dir, "git", args...).Run()
	if err != nil {
		return err
	}

	wd := path.Join(git_dir, checkout_dir)

	args = []string{"--work-tree", ".", "submodule", "sync"}
	err = Command(wd, "git", args...).Run()
	if err != nil {
		return err
	}

	args = []string{"--work-tree", ".", "submodule", "update", "--init"}
	err = Command(wd, "git", args...).Run()
	if err != nil {
		return err
	}

	// Set mtimes to time file is most recently affected by a commit.
	// This is annoying but unfortunately git sets the timestamps to now,
	// and docker depends on the mtime for cache invalidation.
	err = gitSetMTimes(git_dir, git_dir+"/"+checkout_dir, ref)
	if err != nil {
		return err
	}

	return nil
}

func gitSetMTimes(git_dir, checkout_dir, ref string) error {
	// From https://github.com/rosylilly/git-set-mtime with tweaks
	// Copyright (c) 2014 Sho Kusano

	// MIT License

	// Permission is hereby granted, free of charge, to any person obtaining
	// a copy of this software and associated documentation files (the
	// "Software"), to deal in the Software without restriction, including
	// without limitation the rights to use, copy, modify, merge, publish,
	// distribute, sublicense, and/or sell copies of the Software, and to
	// permit persons to whom the Software is furnished to do so, subject to
	// the following conditions:

	// The above copyright notice and this permission notice shall be
	// included in all copies or substantial portions of the Software.

	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

	lsFiles := Command(git_dir, "git", "ls-tree", "-z", "-r", "--name-only", ref)
	lsFiles.Stdout = nil
	out, err := lsFiles.Output()
	if err != nil {
		return fmt.Errorf("git ls-files: %v", err)
	}

	dirMTimes := map[string]time.Time{}

	chtime := func(path string, atime, mtime time.Time) error {
		// https://github.com/torvalds/linux/blob/2decb2682f80759f631c8332f9a2a34a02150a03/include/uapi/linux/fcntl.h#L56
		const AT_FDCWD = -100

		var utimes [2]syscall.Timeval
		utimes[0] = syscall.NsecToTimeval(atime.UnixNano())
		utimes[1] = syscall.NsecToTimeval(mtime.UnixNano())

		if e := syscall.Futimesat(AT_FDCWD, path, utimes[0:]); e != nil {
			wd, err := os.Getwd()
			log.Println("WD:", wd, err)
			return &os.PathError{"futimesat", path, e}
		}
		return nil
	}

	files := strings.Split(strings.TrimRight(string(out), "\x00"), "\x00")
	for _, file := range files {
		gitLog := Command(git_dir, "git", "log", "-1", "--date=rfc2822",
			"--format=%cd", "--", file)
		gitLog.Stdout = nil

		out, err := gitLog.Output()
		if err != nil {
			return fmt.Errorf("git log: %v", err)
		}

		mStr := strings.TrimSpace(strings.TrimLeft(string(out), "Date:"))
		mTime, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", mStr)
		if err != nil {
			return fmt.Errorf("parse time: %v", err)
		}

		// Loop over each directory in the path to `file`, updating `dirMTimes`
		// to take the most recent time seen.
		dir := filepath.Dir(file)
		for {
			if other, ok := dirMTimes[dir]; ok {
				if mTime.After(other) {
					// file mTime is more recent than previous seen for 'dir'
					dirMTimes[dir] = mTime
				}
			} else {
				// first occurrence of dir
				dirMTimes[dir] = mTime
			}

			// Remove one directory from the path until it isn't changed anymore
			if dir == filepath.Dir(dir) {
				break
			}
			dir = filepath.Dir(dir)
		}

		err = chtime(checkout_dir+"/"+file, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}

		fmt.Printf("%s: %s\n", file, mTime)
	}

	for dir, mTime := range dirMTimes {
		err = chtime(checkout_dir+"/"+dir, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}
		fmt.Printf("%s: %s\n", dir, mTime)
	}
	return nil
}
<commit_msg>Eliminate index update<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/fsouza/go-dockerclient"
)

// Invoke a `command` in `workdir` with `args`, connecting up its Stdout and Stderr
func Command(workdir, command string, args ...string) *exec.Cmd {
	log.Printf("wd = %s cmd = %s, args = %q", workdir, command, append([]string{}, args...))
	cmd := exec.Command(command, args...)
	cmd.Dir = workdir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

type DockerClient struct {
	underlying *docker.Client
}

func (c *DockerClient) Build(checkoutPath, name, tag string) error {
	return c.underlying.BuildImage(docker.BuildImageOptions{
		Name:         fmt.Sprintf("%s:%s", name, tag),
		ContextDir:   checkoutPath,
		OutputStream: os.Stderr,
	})
}

func (c *DockerClient) Tag(name, what, tag string) error {
	cmd := Command(".", "docker", "tag", what, tag)
	return cmd.Run()
}

func (c *DockerClient) Push(repo, name, tag string) error {
	opts := docker.PushImageOptions{
		Name:         repo + "/" + name,
		Tag:          tag,
		Registry:     repo,
		OutputStream: os.Stderr,
	}
	auth := docker.AuthConfiguration{}
	return c.underlying.PushImage(opts, auth)
}

func RunDaemon() {
	DaemonRoutes()

	log.Println("Listening on :8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func DaemonRoutes() {

	underlying, err := dockerConnect()
	if err != nil {
		log.Fatal("Unable to connect to client")
	}

	client := &DockerClient{underlying}

	http.HandleFunc("/build/", func(w http.ResponseWriter, r *http.Request) {
		target := r.URL.Path[len("/build/"):]

		name := path.Base(target)

		remote := fmt.Sprintf("https://%v.git", target)

		ref := r.URL.Query().Get("ref")
		if ref == "" {
			ref = "HEAD"
		}

		if _, useSSH := r.URL.Query()["ssh"]; useSSH {
			split := strings.SplitN(target, "/", 2)
			domain, repo := split[0], split[1]
			remote = "git@" + domain + ":" + repo
		}

		path := "./src/" + target

		gitLocalMirror(remote, path, os.Stderr)

		rev, err := gitRevParse(path, ref)
		if err != nil {
			log.Printf("Unable to parse rev: %v", err)
			return
		}

		shortRev := rev[:10]

		checkoutPath := "c/" + shortRev

		err = gitCheckout(path, checkoutPath, rev)
		if err != nil {
			log.Printf("Failed to checkout: %v", err)
			return
		}

		tagName, err := gitDescribe(path, rev)
		if err != nil {
			log.Printf("Unable to describe %v: %v", rev, err)
			return
		}

		log.Println("Checked out")

		// dockerImage := name + "-" + shortRev

		repo := "localhost.localdomain:5000"

		fullName := repo + "/" + name

		err = client.Build(path+"/"+checkoutPath, fullName, tagName)
		if err != nil {
			log.Printf("Failed to build: %v", err)
			return
		}

		start := time.Now()

		err = client.Push(repo, name, tagName)
		log.Printf("Took %v to push", time.Since(start))

		if err != nil {
			log.Printf("Failed to push: %v", err)
			return
		}

		// var buf bytes.Buffer
		// start := time.Now()
		// dockerSave(dockerImage, &buf)
		// log.Printf("Took %v to save %v bytes", time.Since(start), buf.Len())

		fmt.Fprintln(w, "Success:", rev)
	})
}

// Creates or updates a mirror of `url` at `git_dir` using `git clone --mirror`
func gitLocalMirror(url, git_dir string, messages io.Writer) (err error) {

	err = os.MkdirAll(git_dir, 0777)
	if err != nil {
		return
	}

	cmd := Command(".", "git", "clone", "-q", "--mirror", url, git_dir)

	cmd.Stdout = messages
	cmd.Stderr = messages
	err = cmd.Run()

	if err == nil {
		log.Println("Cloned", url)

	} else if _, ok := err.(*exec.ExitError); ok {

		done := make(chan struct{})
		// Try "git remote update"

		cmd := Command(git_dir, "git", "fetch")
		cmd.Stdout = messages
		cmd.Stderr = messages

		go func() {
			err = cmd.Run()
			log.Printf("Normal completion %v %v", cmd.Args, cmd.Dir)
			close(done)
		}()

		const timeout = 20 * time.Second
		select {
		case <-done:
		case <-time.After(timeout):
			err = cmd.Process.Kill()
			log.Printf("Killing cmd %+v after %v, error returned: %v", cmd, timeout, err)
			err = fmt.Errorf("cmd %+v timed out", cmd)
		}

		if err != nil {
			// git fetch where there is no update is exit status 1.
			if err.Error() != "exit status 1" {
				return
			}
		}

		log.Println("Remote updated", url)

	} else {
		return
	}

	return
}

func gitHaveFile(git_dir, ref, path string) (ok bool, err error) {
	cmd := Command(git_dir, "git", "show", fmt.Sprintf("%s:%s", ref, path))
	cmd.Stdout = nil // don't want to see the contents
	err = cmd.Run()
	ok = true
	if err != nil {
		ok = false
		if err.Error() == "exit status 128" {
			// This happens if the file doesn't exist.
			err = nil
		}
	}
	return ok, err
}

func gitRevParse(git_dir, ref string) (sha string, err error) {
	cmd := Command(git_dir, "git", "rev-parse", ref)
	cmd.Stdout = nil // for cmd.Output

	var stdout []byte
	stdout, err = cmd.Output()
	if err != nil {
		return
	}

	sha = strings.TrimSpace(string(stdout))
	return
}

func gitDescribe(git_dir, ref string) (desc string, err error) {
	cmd := Command(git_dir, "git", "describe", "--all", "--tags", "--long", ref)
	cmd.Stdout = nil // for cmd.Output

	var stdout []byte
	stdout, err = cmd.Output()
	if err != nil {
		return
	}

	desc = strings.TrimSpace(string(stdout))
	desc = strings.TrimPrefix(desc, "heads/")
	return
}

func gitCheckout(git_dir, checkout_dir, ref string) error {

	// TODO(pwaller): this needs to be protected by a lock per git_dir,
	// since git-ls-files operates on the contents of the index :(

	err := os.MkdirAll(path.Join(git_dir, checkout_dir), 0777)
	if err != nil {
		return err
	}

	log.Println("Populating", checkout_dir)

	// Put the files physically in checkout_dir
	args := []string{"--work-tree", checkout_dir, "checkout", ref, "--", "."}
	err = Command(git_dir, "git", args...).Run()
	if err != nil {
		return err
	}

	wd := path.Join(git_dir, checkout_dir)

	args = []string{"--work-tree", ".", "submodule", "sync"}
	err = Command(wd, "git", args...).Run()
	if err != nil {
		return err
	}

	args = []string{"--work-tree", ".", "submodule", "update", "--init"}
	err = Command(wd, "git", args...).Run()
	if err != nil {
		return err
	}

	// Set mtimes to time file is most recently affected by a commit.
	// This is annoying but unfortunately git sets the timestamps to now,
	// and docker depends on the mtime for cache invalidation.
	err = gitSetMTimes(git_dir, git_dir+"/"+checkout_dir, ref)
	if err != nil {
		return err
	}

	return nil
}

func gitSetMTimes(git_dir, checkout_dir, ref string) error {
	// From https://github.com/rosylilly/git-set-mtime with tweaks
	// Copyright (c) 2014 Sho Kusano

	// MIT License

	// Permission is hereby granted, free of charge, to any person obtaining
	// a copy of this software and associated documentation files (the
	// "Software"), to deal in the Software without restriction, including
	// without limitation the rights to use, copy, modify, merge, publish,
	// distribute, sublicense, and/or sell copies of the Software, and to
	// permit persons to whom the Software is furnished to do so, subject to
	// the following conditions:

	// The above copyright notice and this permission notice shall be
	// included in all copies or substantial portions of the Software.

	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

	lsFiles := Command(git_dir, "git", "ls-tree", "-z", "-r", "--name-only", ref)
	lsFiles.Stdout = nil
	out, err := lsFiles.Output()
	if err != nil {
		return fmt.Errorf("git ls-files: %v", err)
	}

	dirMTimes := map[string]time.Time{}

	chtime := func(path string, atime, mtime time.Time) error {
		// https://github.com/torvalds/linux/blob/2decb2682f80759f631c8332f9a2a34a02150a03/include/uapi/linux/fcntl.h#L56
		const AT_FDCWD = -100

		var utimes [2]syscall.Timeval
		utimes[0] = syscall.NsecToTimeval(atime.UnixNano())
		utimes[1] = syscall.NsecToTimeval(mtime.UnixNano())

		if e := syscall.Futimesat(AT_FDCWD, path, utimes[0:]); e != nil {
			wd, err := os.Getwd()
			log.Println("WD:", wd, err)
			return &os.PathError{"futimesat", path, e}
		}
		return nil
	}

	files := strings.Split(strings.TrimRight(string(out), "\x00"), "\x00")
	for _, file := range files {
		gitLog := Command(git_dir, "git", "log", "-1", "--date=rfc2822",
			"--format=%cd", "--", file)
		gitLog.Stdout = nil

		out, err := gitLog.Output()
		if err != nil {
			return fmt.Errorf("git log: %v", err)
		}

		mStr := strings.TrimSpace(strings.TrimLeft(string(out), "Date:"))
		mTime, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", mStr)
		if err != nil {
			return fmt.Errorf("parse time: %v", err)
		}

		// Loop over each directory in the path to `file`, updating `dirMTimes`
		// to take the most recent time seen.
		dir := filepath.Dir(file)
		for {
			if other, ok := dirMTimes[dir]; ok {
				if mTime.After(other) {
					// file mTime is more recent than previous seen for 'dir'
					dirMTimes[dir] = mTime
				}
			} else {
				// first occurrence of dir
				dirMTimes[dir] = mTime
			}

			// Remove one directory from the path until it isn't changed anymore
			if dir == filepath.Dir(dir) {
				break
			}
			dir = filepath.Dir(dir)
		}

		err = chtime(checkout_dir+"/"+file, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}

		fmt.Printf("%s: %s\n", file, mTime)
	}

	for dir, mTime := range dirMTimes {
		err = chtime(checkout_dir+"/"+dir, mTime, mTime)
		if err != nil {
			return fmt.Errorf("chtimes: %v", err)
		}
		fmt.Printf("%s: %s\n", dir, mTime)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package awstasks

import (
	"encoding/base64"

	"github.com/aws/aws-sdk-go/aws"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
)

type cloudformationLaunchTemplateNetworkInterface struct {
	// AssociatePublicIPAddress associates a public ip address with the network interface. Boolean value.
	AssociatePublicIPAddress *bool `json:"AssociatePublicIpAddress,omitempty"`
	// DeleteOnTermination indicates whether the network interface should be destroyed on instance termination.
	DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
	// DeviceIndex is the device index for the network interface attachment.
	DeviceIndex *int `json:"DeviceIndex,omitempty"`
	// SecurityGroups is a list of security group ids.
	SecurityGroups []*cloudformation.Literal `json:"Groups,omitempty"`
}

type cloudformationLaunchTemplateMonitoring struct {
	// Enabled indicates that monitoring is enabled
	Enabled *bool `json:"Enabled,omitempty"`
}

type cloudformationLaunchTemplatePlacement struct {
	// Affinity is he affinity setting for an instance on a Dedicated Host.
	Affinity *string `json:"Affinity,omitempty"`
	// AvailabilityZone is the Availability Zone for the instance.
	AvailabilityZone *string `json:"AvailabilityZone,omitempty"`
	// GroupName is the name of the placement group for the instance.
	GroupName *string `json:"GroupName,omitempty"`
	// HostID is the ID of the Dedicated Host for the instance.
	HostID *string `json:"HostId,omitempty"`
	// SpreadDomain are reserved for future use.
	SpreadDomain *string `json:"SpreadDomain,omitempty"`
	// Tenancy ist he tenancy of the instance. Can be default, dedicated, or host.
	Tenancy *string `json:"Tenancy,omitempty"`
}

type cloudformationLaunchTemplateIAMProfile struct {
	// Name is the name of the profile
	Name *cloudformation.Literal `json:"Name,omitempty"`
}

type cloudformationLaunchTemplateMarketOptionsSpotOptions struct {
	// BlockDurationMinutes is required duration in minutes. This value must be a multiple of 60.
	BlockDurationMinutes *int64 `json:"BlockDurationMinutes,omitempty"`
	// InstanceInterruptionBehavior is the behavior when a Spot Instance is interrupted. Can be hibernate, stop, or terminate
	InstanceInterruptionBehavior *string `json:"InstanceInterruptionBehavior,omitempty"`
	// MaxPrice is the maximum hourly price you're willing to pay for the Spot Instances
	MaxPrice *string `json:"MaxPrice,omitempty"`
	// SpotInstanceType is the Spot Instance request type. Can be one-time, or persistent
	SpotInstanceType *string `json:"SpotInstanceType,omitempty"`
}

type cloudformationLaunchTemplateMarketOptions struct {
	// MarketType is the option type
	MarketType *string `json:"MarketType,omitempty"`
	// SpotOptions are the set of options
	SpotOptions *cloudformationLaunchTemplateMarketOptionsSpotOptions `json:"SpotOptions,omitempty"`
}

type cloudformationLaunchTemplateBlockDeviceEBS struct {
	// VolumeType is the ebs type to use
	VolumeType *string `json:"VolumeType,omitempty"`
	// VolumeSize is the volume size
	VolumeSize *int64 `json:"VolumeSize,omitempty"`
	// IOPS is the provisioned iops
	IOPS *int64 `json:"Iops,omitempty"`
	// Throughput is the gp3 volume throughput
	Throughput *int64 `json:"Throughput,omitempty"`
	// DeleteOnTermination indicates the volume should die with the instance
	DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
	// Encrypted indicates the device is encrypted
	Encrypted *bool `json:"Encrypted,omitempty"`
	// KmsKeyID is the encryption key identifier for the volume
	KmsKeyID *string `json:"KmsKeyId,omitempty"`
}

type cloudformationLaunchTemplateBlockDevice struct {
	// DeviceName is the name of the device
	DeviceName *string `json:"DeviceName,omitempty"`
	// VirtualName is used for the ephemeral devices
	VirtualName *string `json:"VirtualName,omitempty"`
	// EBS defines the ebs spec
	EBS *cloudformationLaunchTemplateBlockDeviceEBS `json:"Ebs,omitempty"`
}

type cloudformationLaunchTemplateTagSpecification struct {
	// ResourceType is the type of resource to tag.
	ResourceType *string `json:"ResourceType,omitempty"`
	// Tags are the tags to apply to the resource.
	Tags []cloudformationTag `json:"Tags,omitempty"`
}

type cloudformationLaunchTemplateInstanceMetadataOptions struct {
	// HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests.
	HTTPPutResponseHopLimit *int64 `json:"HttpPutResponseHopLimit,omitempty"`
	// HTTPTokens is the state of token usage for your instance metadata requests.
	HTTPTokens *string `json:"HttpTokens,omitempty"`
}

type cloudformationLaunchTemplateData struct {
	// BlockDeviceMappings is the device mappings
	BlockDeviceMappings []*cloudformationLaunchTemplateBlockDevice `json:"BlockDeviceMappings,omitempty"`
	// EBSOptimized indicates if the root device is ebs optimized
	EBSOptimized *bool `json:"EbsOptimized,omitempty"`
	// IAMInstanceProfile is the IAM profile to assign to the nodes
	IAMInstanceProfile *cloudformationLaunchTemplateIAMProfile `json:"IamInstanceProfile,omitempty"`
	// ImageID is the ami to use for the instances
	ImageID *string `json:"ImageId,omitempty"`
	// InstanceType is the type of instance
	InstanceType *string `json:"InstanceType,omitempty"`
	// KeyName is the ssh key to use
	KeyName *string `json:"KeyName,omitempty"`
	// MarketOptions are the spot pricing options
	MarketOptions *cloudformationLaunchTemplateMarketOptions `json:"InstanceMarketOptions,omitempty"`
	// MetadataOptions are the instance metadata options.
	MetadataOptions *cloudformationLaunchTemplateInstanceMetadataOptions `json:"MetadataOptions,omitempty"`
	// Monitoring are the instance monitoring options
	Monitoring *cloudformationLaunchTemplateMonitoring `json:"Monitoring,omitempty"`
	// NetworkInterfaces are the networking options
	NetworkInterfaces []*cloudformationLaunchTemplateNetworkInterface `json:"NetworkInterfaces,omitempty"`
	// Placement are the tenancy options
	Placement []*cloudformationLaunchTemplatePlacement `json:"Placement,omitempty"`
	// TagSpecifications are the tags to apply to a resource when it is created.
	TagSpecifications []*cloudformationLaunchTemplateTagSpecification `json:"TagSpecifications,omitempty"`
	// UserData is the user data for the instances
	UserData *string `json:"UserData,omitempty"`
}

type cloudformationLaunchTemplate struct {
	// LaunchTemplateName is the name of the launch template
	LaunchTemplateName *string `json:"LaunchTemplateName,omitempty"`
	// LaunchTemplateData is the data request
	LaunchTemplateData *cloudformationLaunchTemplateData `json:"LaunchTemplateData,omitempty"`
}

// CloudformationLink returns the cloudformation link for us
func (t *LaunchTemplate) CloudformationLink() *cloudformation.Literal {
	return cloudformation.Ref("AWS::EC2::LaunchTemplate", fi.StringValue(t.Name))
}

// CloudformationLink returns the cloudformation version.
func (t *LaunchTemplate) CloudformationVersion() *cloudformation.Literal {
	return cloudformation.GetAtt("AWS::EC2::LaunchTemplate", fi.StringValue(t.Name), "LatestVersionNumber")
}

// RenderCloudformation is responsible for rendering the cloudformation json
func (t *LaunchTemplate) RenderCloudformation(target *cloudformation.CloudformationTarget, a, e, changes *LaunchTemplate) error {
	var err error

	cloud := target.Cloud.(awsup.AWSCloud)

	var image *string
	if e.ImageID != nil {
		im, err := cloud.ResolveImage(fi.StringValue(e.ImageID))
		if err != nil {
			return err
		}
		image = im.ImageId
	}

	launchTemplateData := &cloudformationLaunchTemplateData{
		EBSOptimized: e.RootVolumeOptimization,
		ImageID:      image,
		InstanceType: e.InstanceType,
		MetadataOptions: &cloudformationLaunchTemplateInstanceMetadataOptions{
			HTTPTokens:              e.HTTPTokens,
			HTTPPutResponseHopLimit: e.HTTPPutResponseHopLimit,
		},
		NetworkInterfaces: []*cloudformationLaunchTemplateNetworkInterface{
			{
				AssociatePublicIPAddress: e.AssociatePublicIP,
				DeleteOnTermination:      fi.Bool(true),
				DeviceIndex:              fi.Int(0),
			},
		},
	}

	if fi.StringValue(e.SpotPrice) != "" {
		marketSpotOptions := cloudformationLaunchTemplateMarketOptionsSpotOptions{MaxPrice: e.SpotPrice}
		if e.SpotDurationInMinutes != nil {
			marketSpotOptions.BlockDurationMinutes = e.SpotDurationInMinutes
		}
		if e.InstanceInterruptionBehavior != nil {
			marketSpotOptions.InstanceInterruptionBehavior = e.InstanceInterruptionBehavior
		}
		launchTemplateData.MarketOptions = &cloudformationLaunchTemplateMarketOptions{MarketType: fi.String("spot"), SpotOptions: &marketSpotOptions}
	}

	cf := &cloudformationLaunchTemplate{
		LaunchTemplateName: fi.String(fi.StringValue(e.Name)),
		LaunchTemplateData: launchTemplateData,
	}
	data := cf.LaunchTemplateData

	for _, x := range e.SecurityGroups {
		data.NetworkInterfaces[0].SecurityGroups = append(data.NetworkInterfaces[0].SecurityGroups, x.CloudformationLink())
	}
	if e.SSHKey != nil {
		data.KeyName = e.SSHKey.Name
	}
	if e.Tenancy != nil {
		data.Placement = []*cloudformationLaunchTemplatePlacement{{Tenancy: e.Tenancy}}
	}
	if e.InstanceMonitoring != nil {
		data.Monitoring = &cloudformationLaunchTemplateMonitoring{
			Enabled: e.InstanceMonitoring,
		}
	}
	if e.IAMInstanceProfile != nil {
		data.IAMInstanceProfile = &cloudformationLaunchTemplateIAMProfile{
			Name: e.IAMInstanceProfile.CloudformationLink(),
		}
	}
	if e.UserData != nil {
		d, err := fi.ResourceAsBytes(e.UserData)
		if err != nil {
			return err
		}
		data.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
	}
	devices, err := e.buildRootDevice(cloud)
	if err != nil {
		return err
	}
	additionals, err := buildAdditionalDevices(e.BlockDeviceMappings)
	if err != nil {
		return err
	}
	for name, x := range devices {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			DeviceName: fi.String(name),
			EBS: &cloudformationLaunchTemplateBlockDeviceEBS{
				DeleteOnTermination: fi.Bool(true),
				IOPS:                x.EbsVolumeIops,
				Throughput:          x.EbsVolumeThroughput,
				VolumeSize:          x.EbsVolumeSize,
				VolumeType:          x.EbsVolumeType,
				Encrypted:           x.EbsEncrypted,
				KmsKeyID:            x.EbsKmsKey,
			},
		})
	}
	for name, x := range additionals {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			DeviceName: fi.String(name),
			EBS: &cloudformationLaunchTemplateBlockDeviceEBS{
				DeleteOnTermination: fi.Bool(true),
				IOPS:                x.EbsVolumeIops,
				VolumeSize:          x.EbsVolumeSize,
				VolumeType:          x.EbsVolumeType,
				Encrypted:           x.EbsEncrypted,
				KmsKeyID:            x.EbsKmsKey,
			},
		})
	}

	devices, err = buildEphemeralDevices(cloud, fi.StringValue(e.InstanceType))
	if err != nil {
		return err
	}
	for n, x := range devices {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			VirtualName: x.VirtualName,
			DeviceName:  fi.String(n),
		})
	}

	if e.Tags != nil {
		tags := buildCloudformationTags(t.Tags)
		data.TagSpecifications = append(data.TagSpecifications, &cloudformationLaunchTemplateTagSpecification{
			ResourceType: fi.String("instance"),
			Tags:         tags,
		})
		data.TagSpecifications = append(data.TagSpecifications, &cloudformationLaunchTemplateTagSpecification{
			ResourceType: fi.String("volume"),
			Tags:         tags,
		})
	}

	return target.RenderResource("AWS::EC2::LaunchTemplate", fi.StringValue(e.Name), cf)
}
<commit_msg>Add throughput field for gp3 volumes to additional volumes with CF<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package awstasks

import (
	"encoding/base64"

	"github.com/aws/aws-sdk-go/aws"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
	"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
)

type cloudformationLaunchTemplateNetworkInterface struct {
	// AssociatePublicIPAddress associates a public ip address with the network interface. Boolean value.
	AssociatePublicIPAddress *bool `json:"AssociatePublicIpAddress,omitempty"`
	// DeleteOnTermination indicates whether the network interface should be destroyed on instance termination.
	DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
	// DeviceIndex is the device index for the network interface attachment.
	DeviceIndex *int `json:"DeviceIndex,omitempty"`
	// SecurityGroups is a list of security group ids.
	SecurityGroups []*cloudformation.Literal `json:"Groups,omitempty"`
}

type cloudformationLaunchTemplateMonitoring struct {
	// Enabled indicates that monitoring is enabled
	Enabled *bool `json:"Enabled,omitempty"`
}

type cloudformationLaunchTemplatePlacement struct {
	// Affinity is he affinity setting for an instance on a Dedicated Host.
	Affinity *string `json:"Affinity,omitempty"`
	// AvailabilityZone is the Availability Zone for the instance.
	AvailabilityZone *string `json:"AvailabilityZone,omitempty"`
	// GroupName is the name of the placement group for the instance.
	GroupName *string `json:"GroupName,omitempty"`
	// HostID is the ID of the Dedicated Host for the instance.
	HostID *string `json:"HostId,omitempty"`
	// SpreadDomain are reserved for future use.
	SpreadDomain *string `json:"SpreadDomain,omitempty"`
	// Tenancy ist he tenancy of the instance. Can be default, dedicated, or host.
	Tenancy *string `json:"Tenancy,omitempty"`
}

type cloudformationLaunchTemplateIAMProfile struct {
	// Name is the name of the profile
	Name *cloudformation.Literal `json:"Name,omitempty"`
}

type cloudformationLaunchTemplateMarketOptionsSpotOptions struct {
	// BlockDurationMinutes is required duration in minutes. This value must be a multiple of 60.
	BlockDurationMinutes *int64 `json:"BlockDurationMinutes,omitempty"`
	// InstanceInterruptionBehavior is the behavior when a Spot Instance is interrupted. Can be hibernate, stop, or terminate
	InstanceInterruptionBehavior *string `json:"InstanceInterruptionBehavior,omitempty"`
	// MaxPrice is the maximum hourly price you're willing to pay for the Spot Instances
	MaxPrice *string `json:"MaxPrice,omitempty"`
	// SpotInstanceType is the Spot Instance request type. Can be one-time, or persistent
	SpotInstanceType *string `json:"SpotInstanceType,omitempty"`
}

type cloudformationLaunchTemplateMarketOptions struct {
	// MarketType is the option type
	MarketType *string `json:"MarketType,omitempty"`
	// SpotOptions are the set of options
	SpotOptions *cloudformationLaunchTemplateMarketOptionsSpotOptions `json:"SpotOptions,omitempty"`
}

type cloudformationLaunchTemplateBlockDeviceEBS struct {
	// VolumeType is the ebs type to use
	VolumeType *string `json:"VolumeType,omitempty"`
	// VolumeSize is the volume size
	VolumeSize *int64 `json:"VolumeSize,omitempty"`
	// IOPS is the provisioned iops
	IOPS *int64 `json:"Iops,omitempty"`
	// Throughput is the gp3 volume throughput
	Throughput *int64 `json:"Throughput,omitempty"`
	// DeleteOnTermination indicates the volume should die with the instance
	DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
	// Encrypted indicates the device is encrypted
	Encrypted *bool `json:"Encrypted,omitempty"`
	// KmsKeyID is the encryption key identifier for the volume
	KmsKeyID *string `json:"KmsKeyId,omitempty"`
}

type cloudformationLaunchTemplateBlockDevice struct {
	// DeviceName is the name of the device
	DeviceName *string `json:"DeviceName,omitempty"`
	// VirtualName is used for the ephemeral devices
	VirtualName *string `json:"VirtualName,omitempty"`
	// EBS defines the ebs spec
	EBS *cloudformationLaunchTemplateBlockDeviceEBS `json:"Ebs,omitempty"`
}

type cloudformationLaunchTemplateTagSpecification struct {
	// ResourceType is the type of resource to tag.
	ResourceType *string `json:"ResourceType,omitempty"`
	// Tags are the tags to apply to the resource.
	Tags []cloudformationTag `json:"Tags,omitempty"`
}

type cloudformationLaunchTemplateInstanceMetadataOptions struct {
	// HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests.
	HTTPPutResponseHopLimit *int64 `json:"HttpPutResponseHopLimit,omitempty"`
	// HTTPTokens is the state of token usage for your instance metadata requests.
	HTTPTokens *string `json:"HttpTokens,omitempty"`
}

type cloudformationLaunchTemplateData struct {
	// BlockDeviceMappings is the device mappings
	BlockDeviceMappings []*cloudformationLaunchTemplateBlockDevice `json:"BlockDeviceMappings,omitempty"`
	// EBSOptimized indicates if the root device is ebs optimized
	EBSOptimized *bool `json:"EbsOptimized,omitempty"`
	// IAMInstanceProfile is the IAM profile to assign to the nodes
	IAMInstanceProfile *cloudformationLaunchTemplateIAMProfile `json:"IamInstanceProfile,omitempty"`
	// ImageID is the ami to use for the instances
	ImageID *string `json:"ImageId,omitempty"`
	// InstanceType is the type of instance
	InstanceType *string `json:"InstanceType,omitempty"`
	// KeyName is the ssh key to use
	KeyName *string `json:"KeyName,omitempty"`
	// MarketOptions are the spot pricing options
	MarketOptions *cloudformationLaunchTemplateMarketOptions `json:"InstanceMarketOptions,omitempty"`
	// MetadataOptions are the instance metadata options.
	MetadataOptions *cloudformationLaunchTemplateInstanceMetadataOptions `json:"MetadataOptions,omitempty"`
	// Monitoring are the instance monitoring options
	Monitoring *cloudformationLaunchTemplateMonitoring `json:"Monitoring,omitempty"`
	// NetworkInterfaces are the networking options
	NetworkInterfaces []*cloudformationLaunchTemplateNetworkInterface `json:"NetworkInterfaces,omitempty"`
	// Placement are the tenancy options
	Placement []*cloudformationLaunchTemplatePlacement `json:"Placement,omitempty"`
	// TagSpecifications are the tags to apply to a resource when it is created.
	TagSpecifications []*cloudformationLaunchTemplateTagSpecification `json:"TagSpecifications,omitempty"`
	// UserData is the user data for the instances
	UserData *string `json:"UserData,omitempty"`
}

type cloudformationLaunchTemplate struct {
	// LaunchTemplateName is the name of the launch template
	LaunchTemplateName *string `json:"LaunchTemplateName,omitempty"`
	// LaunchTemplateData is the data request
	LaunchTemplateData *cloudformationLaunchTemplateData `json:"LaunchTemplateData,omitempty"`
}

// CloudformationLink returns the cloudformation link for us
func (t *LaunchTemplate) CloudformationLink() *cloudformation.Literal {
	return cloudformation.Ref("AWS::EC2::LaunchTemplate", fi.StringValue(t.Name))
}

// CloudformationLink returns the cloudformation version.
func (t *LaunchTemplate) CloudformationVersion() *cloudformation.Literal {
	return cloudformation.GetAtt("AWS::EC2::LaunchTemplate", fi.StringValue(t.Name), "LatestVersionNumber")
}

// RenderCloudformation is responsible for rendering the cloudformation json
func (t *LaunchTemplate) RenderCloudformation(target *cloudformation.CloudformationTarget, a, e, changes *LaunchTemplate) error {
	var err error

	cloud := target.Cloud.(awsup.AWSCloud)

	var image *string
	if e.ImageID != nil {
		im, err := cloud.ResolveImage(fi.StringValue(e.ImageID))
		if err != nil {
			return err
		}
		image = im.ImageId
	}

	launchTemplateData := &cloudformationLaunchTemplateData{
		EBSOptimized: e.RootVolumeOptimization,
		ImageID:      image,
		InstanceType: e.InstanceType,
		MetadataOptions: &cloudformationLaunchTemplateInstanceMetadataOptions{
			HTTPTokens:              e.HTTPTokens,
			HTTPPutResponseHopLimit: e.HTTPPutResponseHopLimit,
		},
		NetworkInterfaces: []*cloudformationLaunchTemplateNetworkInterface{
			{
				AssociatePublicIPAddress: e.AssociatePublicIP,
				DeleteOnTermination:      fi.Bool(true),
				DeviceIndex:              fi.Int(0),
			},
		},
	}

	if fi.StringValue(e.SpotPrice) != "" {
		marketSpotOptions := cloudformationLaunchTemplateMarketOptionsSpotOptions{MaxPrice: e.SpotPrice}
		if e.SpotDurationInMinutes != nil {
			marketSpotOptions.BlockDurationMinutes = e.SpotDurationInMinutes
		}
		if e.InstanceInterruptionBehavior != nil {
			marketSpotOptions.InstanceInterruptionBehavior = e.InstanceInterruptionBehavior
		}
		launchTemplateData.MarketOptions = &cloudformationLaunchTemplateMarketOptions{MarketType: fi.String("spot"), SpotOptions: &marketSpotOptions}
	}

	cf := &cloudformationLaunchTemplate{
		LaunchTemplateName: fi.String(fi.StringValue(e.Name)),
		LaunchTemplateData: launchTemplateData,
	}
	data := cf.LaunchTemplateData

	for _, x := range e.SecurityGroups {
		data.NetworkInterfaces[0].SecurityGroups = append(data.NetworkInterfaces[0].SecurityGroups, x.CloudformationLink())
	}
	if e.SSHKey != nil {
		data.KeyName = e.SSHKey.Name
	}
	if e.Tenancy != nil {
		data.Placement = []*cloudformationLaunchTemplatePlacement{{Tenancy: e.Tenancy}}
	}
	if e.InstanceMonitoring != nil {
		data.Monitoring = &cloudformationLaunchTemplateMonitoring{
			Enabled: e.InstanceMonitoring,
		}
	}
	if e.IAMInstanceProfile != nil {
		data.IAMInstanceProfile = &cloudformationLaunchTemplateIAMProfile{
			Name: e.IAMInstanceProfile.CloudformationLink(),
		}
	}
	if e.UserData != nil {
		d, err := fi.ResourceAsBytes(e.UserData)
		if err != nil {
			return err
		}
		data.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
	}
	devices, err := e.buildRootDevice(cloud)
	if err != nil {
		return err
	}
	additionals, err := buildAdditionalDevices(e.BlockDeviceMappings)
	if err != nil {
		return err
	}
	for name, x := range devices {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			DeviceName: fi.String(name),
			EBS: &cloudformationLaunchTemplateBlockDeviceEBS{
				DeleteOnTermination: fi.Bool(true),
				IOPS:                x.EbsVolumeIops,
				Throughput:          x.EbsVolumeThroughput,
				VolumeSize:          x.EbsVolumeSize,
				VolumeType:          x.EbsVolumeType,
				Encrypted:           x.EbsEncrypted,
				KmsKeyID:            x.EbsKmsKey,
			},
		})
	}
	for name, x := range additionals {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			DeviceName: fi.String(name),
			EBS: &cloudformationLaunchTemplateBlockDeviceEBS{
				DeleteOnTermination: fi.Bool(true),
				IOPS:                x.EbsVolumeIops,
				VolumeSize:          x.EbsVolumeSize,
				Throughput:          x.EbsVolumeThroughput,
				VolumeType:          x.EbsVolumeType,
				Encrypted:           x.EbsEncrypted,
				KmsKeyID:            x.EbsKmsKey,
			},
		})
	}

	devices, err = buildEphemeralDevices(cloud, fi.StringValue(e.InstanceType))
	if err != nil {
		return err
	}
	for n, x := range devices {
		data.BlockDeviceMappings = append(data.BlockDeviceMappings, &cloudformationLaunchTemplateBlockDevice{
			VirtualName: x.VirtualName,
			DeviceName:  fi.String(n),
		})
	}

	if e.Tags != nil {
		tags := buildCloudformationTags(t.Tags)
		data.TagSpecifications = append(data.TagSpecifications, &cloudformationLaunchTemplateTagSpecification{
			ResourceType: fi.String("instance"),
			Tags:         tags,
		})
		data.TagSpecifications = append(data.TagSpecifications, &cloudformationLaunchTemplateTagSpecification{
			ResourceType: fi.String("volume"),
			Tags:         tags,
		})
	}

	return target.RenderResource("AWS::EC2::LaunchTemplate", fi.StringValue(e.Name), cf)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	lua "github.com/anaminus/gopher-lua"
	"github.com/anaminus/rbxmk"
	"github.com/anaminus/rbxmk/formats"
	"github.com/anaminus/rbxmk/library"
	"github.com/anaminus/rbxmk/sources"
	"github.com/anaminus/snek"
)

// shortenPath transforms the given path so that it is relative to the working
// directory. Returns the original path if that fails.
func shortenPath(filename string) string {
	if wd, err := os.Getwd(); err == nil {
		if abs, err := filepath.Abs(filename); err == nil {
			if r, err := filepath.Rel(wd, abs); err == nil {
				filename = r
			}
		}
	}
	return filename
}

// ParseLuaValue parses a string into a Lua value. Numbers, bools, and nil are
// parsed into their respective types, and any other value is interpreted as a
// string.
func ParseLuaValue(s string) lua.LValue {
	switch s {
	case "true":
		return lua.LTrue
	case "false":
		return lua.LFalse
	case "nil":
		return lua.LNil
	}
	if number, err := strconv.ParseFloat(s, 64); err == nil {
		return lua.LNumber(number)
	}
	return lua.LString(s)
}

func init() {
	Program.Register(snek.Def{
		Name:      "run",
		Summary:   "Execute a script.",
		Arguments: `[ FILE ] [ ...VALUE ]`,
		Description: `
Receives a file to be executed as a Lua script. If "-" is given, then the script
will be read from stdin instead.

Remaining arguments are Lua values to be passed to the file. Numbers, bools, and
nil are parsed into their respective types in Lua, and any other value is
interpreted as a string. Within the script, these arguments can be received from
the ... operator.`,
		New: func() snek.Command { return &RunCommand{} },
	})
}

type RunCommand struct {
	Debug bool
	Init  func(rbxmk.State)
}

func (c *RunCommand) SetFlags(flags snek.FlagSet) {
	flags.BoolVar(&c.Debug, "debug", false, "Display stack traces when an error occurs.")
}

// Run is the entrypoint to the command for running scripts. init runs after the
// World envrionment is fully initialized and arguments have been pushed, and
// before the script runs.
func (c *RunCommand) Run(opt snek.Options) error {
	// Parse flags.
	if err := opt.ParseFlags(); err != nil {
		return err
	}
	args := opt.Args()
	if len(args) == 0 {
		opt.Usage()
		return nil
	}
	file := args[0]
	args = args[1:]

	// Initialize world.
	world := rbxmk.NewWorld(lua.NewState(lua.Options{
		SkipOpenLibs:        true,
		IncludeGoStackTrace: c.Debug,
	}))
	if wd, err := os.Getwd(); err == nil {
		// Working directory is an accessible root.
		world.FS.AddRoot(wd)
	}
	for _, f := range formats.All() {
		world.RegisterFormat(f())
	}
	for _, s := range sources.All() {
		world.RegisterSource(s())
	}
	for _, lib := range library.All() {
		if err := world.Open(lib); err != nil {
			return err
		}
	}

	world.State().SetGlobal("_RBXMK_VERSION", lua.LString(Version))

	// Add script arguments.
	for _, arg := range args {
		world.State().Push(ParseLuaValue(arg))
	}

	if c.Init != nil {
		c.Init(rbxmk.State{World: world, L: world.State()})
	}

	// Run stdin as script.
	if file == "-" {
		if opt.Stdin == nil {
			return fmt.Errorf("no file handle")
		}
		return world.DoFileHandle(opt.Stdin, "", len(args))
	}

	// Run file as script.
	filename := shortenPath(filepath.Clean(file))
	return world.DoFile(filename, len(args))
}
<commit_msg>Add flag to include root directories.<commit_after>package main

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

	lua "github.com/anaminus/gopher-lua"
	"github.com/anaminus/rbxmk"
	"github.com/anaminus/rbxmk/formats"
	"github.com/anaminus/rbxmk/library"
	"github.com/anaminus/rbxmk/sources"
	"github.com/anaminus/snek"
)

// shortenPath transforms the given path so that it is relative to the working
// directory. Returns the original path if that fails.
func shortenPath(filename string) string {
	if wd, err := os.Getwd(); err == nil {
		if abs, err := filepath.Abs(filename); err == nil {
			if r, err := filepath.Rel(wd, abs); err == nil {
				filename = r
			}
		}
	}
	return filename
}

// ParseLuaValue parses a string into a Lua value. Numbers, bools, and nil are
// parsed into their respective types, and any other value is interpreted as a
// string.
func ParseLuaValue(s string) lua.LValue {
	switch s {
	case "true":
		return lua.LTrue
	case "false":
		return lua.LFalse
	case "nil":
		return lua.LNil
	}
	if number, err := strconv.ParseFloat(s, 64); err == nil {
		return lua.LNumber(number)
	}
	return lua.LString(s)
}

func init() {
	Program.Register(snek.Def{
		Name:      "run",
		Summary:   "Execute a script.",
		Arguments: `[ FILE ] [ ...VALUE ]`,
		Description: `
Receives a file to be executed as a Lua script. If "-" is given, then the script
will be read from stdin instead.

Remaining arguments are Lua values to be passed to the file. Numbers, bools, and
nil are parsed into their respective types in Lua, and any other value is
interpreted as a string. Within the script, these arguments can be received from
the ... operator.`,
		New: func() snek.Command { return &RunCommand{} },
	})
}

// repeatedString is a string flag that can be specified multiple times.
type repeatedString []string

func (s repeatedString) String() string {
	return strings.Join(s, ",")
}

func (s *repeatedString) Set(v string) error {
	*s = append(*s, v)
	return nil
}

type RunCommand struct {
	IncludedRoots []string
	Debug         bool
	Init          func(rbxmk.State)
}

func (c *RunCommand) SetFlags(flags snek.FlagSet) {
	flags.Var((*repeatedString)(&c.IncludedRoots), "include-root", "Mark a path as an accessible root directory.")
	flags.BoolVar(&c.Debug, "debug", false, "Display stack traces when an error occurs.")
}

// Run is the entrypoint to the command for running scripts. init runs after the
// World envrionment is fully initialized and arguments have been pushed, and
// before the script runs.
func (c *RunCommand) Run(opt snek.Options) error {
	// Parse flags.
	if err := opt.ParseFlags(); err != nil {
		return err
	}
	args := opt.Args()
	if len(args) == 0 {
		opt.Usage()
		return nil
	}
	file := args[0]
	args = args[1:]

	// Initialize world.
	world := rbxmk.NewWorld(lua.NewState(lua.Options{
		SkipOpenLibs:        true,
		IncludeGoStackTrace: c.Debug,
	}))
	if wd, err := os.Getwd(); err == nil {
		// Working directory is an accessible root.
		world.FS.AddRoot(wd)
	}
	for _, root := range c.IncludedRoots {
		world.FS.AddRoot(root)
	}
	for _, f := range formats.All() {
		world.RegisterFormat(f())
	}
	for _, s := range sources.All() {
		world.RegisterSource(s())
	}
	for _, lib := range library.All() {
		if err := world.Open(lib); err != nil {
			return err
		}
	}

	world.State().SetGlobal("_RBXMK_VERSION", lua.LString(Version))

	// Add script arguments.
	for _, arg := range args {
		world.State().Push(ParseLuaValue(arg))
	}

	if c.Init != nil {
		c.Init(rbxmk.State{World: world, L: world.State()})
	}

	// Run stdin as script.
	if file == "-" {
		if opt.Stdin == nil {
			return fmt.Errorf("no file handle")
		}
		return world.DoFileHandle(opt.Stdin, "", len(args))
	}

	// Run file as script.
	filename := shortenPath(filepath.Clean(file))
	return world.DoFile(filename, len(args))
}
<|endoftext|>


<commit_before>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package rdl

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

// for client/server generated code support

// ResourceContext is the context for a handler callout, in case access to the underlying request information is needed.
// Because explicit arguments are declared in the API with RDL (include path, query, and header params), the
// need to access this is rare.
// Note that the map of name to value is not protected for concurrency - the app must do this itself if it plans
// on sharing ResourceContext across threads.
//
type ResourceContext struct {
	Writer    http.ResponseWriter
	Request   *http.Request
	Params    map[string]string
	Principal Principal
	Values    map[string]interface{}
}

//
// Get - returns an application data value on the context.
//
func (ctx *ResourceContext) Get(name string) interface{} {
	if ctx.Values != nil {
		if v, ok := ctx.Values[name]; ok {
			return v
		}
	}
	return nil
}

//
// Put - associates an application datum to the given name in the context.
// This call is not thread-safe, the app must arrange to allow multi-threaded access.
//
func (ctx *ResourceContext) Put(name string, value interface{}) *ResourceContext {
	if ctx.Values == nil {
		ctx.Values = make(map[string]interface{})
	}
	ctx.Values[name] = value
	return ctx
}

//
// ResourceError is the generic container for service errors.
//
type ResourceError struct {
	Code    int    `json:"code"`    //the http status code
	Message string `json:"message"` //a human readable message

}

func (e ResourceError) Error() string {
	return fmt.Sprintf("%d %s", e.Code, e.Message)
}

// JSONResponse provides response encoded as JSON.
func JSONResponse(w http.ResponseWriter, code int, data interface{}) {
	w.WriteHeader(code)
	switch code {
	case 204, 304:
		/* no body */
	default:
		if data == nil {
			data = ResourceError{code, "Server Error"}
		}
		b, e := json.MarshalIndent(data, "", "  ")
		if e != nil {
			code = http.StatusInternalServerError
			b, _ = json.MarshalIndent(ResourceError{500, "Server Error"}, "", "  ")
		}
		fmt.Fprintf(w, "%s\n", string(b))
	}
}

// OptionalStringParam parses and returns an optional parameter
// from the form body (multipart/form-data encoded).
func OptionalStringParam(r *http.Request, name string) string {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		return vs[0]
	}
	return ""
}

func StringParam(r *http.Request, name string, defaultValue string) (string, bool) {
	s := OptionalStringParam(r, name)
	if s == "" {
		if defaultValue == "" {
			return "", false
		}
		s = defaultValue
	}
	return s, true
}

func OptionalInt32Param(r *http.Request, name string) (*int32, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseInt(vs[0], 10, 32); err == nil {
			n := int32(i)
			return &n, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Int32: " + vs[0]}
	}
	return nil, nil
}

func Int32Param(r *http.Request, name string, defaultValue int32) (int32, error) {
	pi, err := OptionalInt32Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}

func OptionalInt64Param(r *http.Request, name string) (*int64, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseInt(vs[0], 10, 64); err == nil {
			return &i, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Int64: " + vs[0]}
	}
	return nil, nil
}

func Int64Param(r *http.Request, name string, defaultValue int64) (int64, error) {
	pi, err := OptionalInt64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}

func OptionalFloat64Param(r *http.Request, name string) (*float64, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseFloat(vs[0], 64); err == nil {
			return &i, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Float64: " + vs[0]}
	}
	return nil, nil
}

func Float64Param(r *http.Request, name string, defaultValue float64) (float64, error) {
	pi, err := OptionalFloat64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}
func Float32Param(r *http.Request, name string, defaultValue float32) (float32, error) {
	pi, err := OptionalFloat64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return float32(*pi), nil
	}
	return defaultValue, nil
}

func OptionalBoolParam(r *http.Request, name string) (*bool, error) {
	var b bool
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		switch vs[0] {
		case "true":
			b = true
		case "false":
			b = false
		default:
			return nil, &ResourceError{400, "Parameter '" + name + "' is not a Bool: " + vs[0]}
		}
	}
	return &b, nil
}

func BoolParam(r *http.Request, name string, defaultValue bool) (bool, error) {
	pb, err := OptionalBoolParam(r, name)
	if err != nil {
		return false, err
	}
	if pb != nil {
		return *pb, nil
	}
	return defaultValue, nil
}

func OptionalHeaderParam(r *http.Request, name string) string {
	if tmp, ok := r.Header[name]; ok {
		s := strings.Join(tmp, ",")
		return s
	}
	return ""
}

func HeaderParam(r *http.Request, name string, defaultValue string) string {
	val := OptionalHeaderParam(r, name)
	if val == "" {
		val = defaultValue
	}
	return val
}

// FoldHttpHeaderName adapts to the Go misfeature: all headers are
// canonicalized as Capslike-This (for a header "CapsLike-this").
func FoldHttpHeaderName(name string) string {
	return http.CanonicalHeaderKey(name)
}
<commit_msg>Add a unique method to ResourceError for easy type switch<commit_after>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package rdl

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

// for client/server generated code support

// ResourceContext is the context for a handler callout, in case access to the underlying request information is needed.
// Because explicit arguments are declared in the API with RDL (include path, query, and header params), the
// need to access this is rare.
// Note that the map of name to value is not protected for concurrency - the app must do this itself if it plans
// on sharing ResourceContext across threads.
//
type ResourceContext struct {
	Writer    http.ResponseWriter
	Request   *http.Request
	Params    map[string]string
	Principal Principal
	Values    map[string]interface{}
}

//
// Get - returns an application data value on the context.
//
func (ctx *ResourceContext) Get(name string) interface{} {
	if ctx.Values != nil {
		if v, ok := ctx.Values[name]; ok {
			return v
		}
	}
	return nil
}

//
// Put - associates an application datum to the given name in the context.
// This call is not thread-safe, the app must arrange to allow multi-threaded access.
//
func (ctx *ResourceContext) Put(name string, value interface{}) *ResourceContext {
	if ctx.Values == nil {
		ctx.Values = make(map[string]interface{})
	}
	ctx.Values[name] = value
	return ctx
}

//
// ResourceError is the generic container for service errors.
//
type ResourceError struct {
	Code    int    `json:"code"`    //the http status code
	Message string `json:"message"` //a human readable message

}

func (e ResourceError) StatusCode() int {
	return e.Code
}

func (e ResourceError) Error() string {
	return fmt.Sprintf("%d %s", e.Code, e.Message)
}

// JSONResponse provides response encoded as JSON.
func JSONResponse(w http.ResponseWriter, code int, data interface{}) {
	w.WriteHeader(code)
	switch code {
	case 204, 304:
		/* no body */
	default:
		if data == nil {
			data = ResourceError{code, "Server Error"}
		}
		b, e := json.MarshalIndent(data, "", "  ")
		if e != nil {
			code = http.StatusInternalServerError
			b, _ = json.MarshalIndent(ResourceError{500, "Server Error"}, "", "  ")
		}
		fmt.Fprintf(w, "%s\n", string(b))
	}
}

// OptionalStringParam parses and returns an optional parameter
// from the form body (multipart/form-data encoded).
func OptionalStringParam(r *http.Request, name string) string {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		return vs[0]
	}
	return ""
}

func StringParam(r *http.Request, name string, defaultValue string) (string, bool) {
	s := OptionalStringParam(r, name)
	if s == "" {
		if defaultValue == "" {
			return "", false
		}
		s = defaultValue
	}
	return s, true
}

func OptionalInt32Param(r *http.Request, name string) (*int32, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseInt(vs[0], 10, 32); err == nil {
			n := int32(i)
			return &n, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Int32: " + vs[0]}
	}
	return nil, nil
}

func Int32Param(r *http.Request, name string, defaultValue int32) (int32, error) {
	pi, err := OptionalInt32Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}

func OptionalInt64Param(r *http.Request, name string) (*int64, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseInt(vs[0], 10, 64); err == nil {
			return &i, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Int64: " + vs[0]}
	}
	return nil, nil
}

func Int64Param(r *http.Request, name string, defaultValue int64) (int64, error) {
	pi, err := OptionalInt64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}

func OptionalFloat64Param(r *http.Request, name string) (*float64, error) {
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		if i, err := strconv.ParseFloat(vs[0], 64); err == nil {
			return &i, nil
		}
		return nil, &ResourceError{400, "Parameter '" + name + "' is not an Float64: " + vs[0]}
	}
	return nil, nil
}

func Float64Param(r *http.Request, name string, defaultValue float64) (float64, error) {
	pi, err := OptionalFloat64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return *pi, nil
	}
	return defaultValue, nil
}
func Float32Param(r *http.Request, name string, defaultValue float32) (float32, error) {
	pi, err := OptionalFloat64Param(r, name)
	if err != nil {
		return 0, err
	}
	if pi != nil {
		return float32(*pi), nil
	}
	return defaultValue, nil
}

func OptionalBoolParam(r *http.Request, name string) (*bool, error) {
	var b bool
	if r.Form == nil {
		r.ParseMultipartForm(32 << 20)
	}
	if vs := r.Form[name]; len(vs) == 1 {
		switch vs[0] {
		case "true":
			b = true
		case "false":
			b = false
		default:
			return nil, &ResourceError{400, "Parameter '" + name + "' is not a Bool: " + vs[0]}
		}
	}
	return &b, nil
}

func BoolParam(r *http.Request, name string, defaultValue bool) (bool, error) {
	pb, err := OptionalBoolParam(r, name)
	if err != nil {
		return false, err
	}
	if pb != nil {
		return *pb, nil
	}
	return defaultValue, nil
}

func OptionalHeaderParam(r *http.Request, name string) string {
	if tmp, ok := r.Header[name]; ok {
		s := strings.Join(tmp, ",")
		return s
	}
	return ""
}

func HeaderParam(r *http.Request, name string, defaultValue string) string {
	val := OptionalHeaderParam(r, name)
	if val == "" {
		val = defaultValue
	}
	return val
}

// FoldHttpHeaderName adapts to the Go misfeature: all headers are
// canonicalized as Capslike-This (for a header "CapsLike-this").
func FoldHttpHeaderName(name string) string {
	return http.CanonicalHeaderKey(name)
}
<|endoftext|>


<commit_before>/* Copyright (C) 2013 CompleteDB LLC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with PubSubSQL.  If not, see <http://www.gnu.org/licenses/>.
 */

package pubsubsql

import "strconv"

type responseStatusType int8

const (
	responseStatusOk  responseStatusType = iota // ok.
	responseStatusErr                           // error.
)

// response
type response interface {
	getResponseStatus() responseStatusType
	toNetworkReadyJSON() ([]byte, bool)
	setRequestId(requestId uint32)
}

type requestIdResponse struct {
	response
	requestId uint32
}

func (this *requestIdResponse) setRequestId(requestId uint32) {
	this.requestId = requestId
}

// json helper functions
func ok(builder *JSONBuilder) {
	builder.nameValue("status", "ok")
}

func id(builder *JSONBuilder, id string) {
	builder.nameValue("id", id)
}

func action(builder *JSONBuilder, action string) {
	builder.nameValue("action", action)
}

// errorResponse
type errorResponse struct {
	requestIdResponse
	msg string
}

func newErrorResponse(msg string) *errorResponse {
	return &errorResponse{
		msg: msg,
	}
}

func (this *errorResponse) getResponsStatus() responseStatusType {
	return responseStatusErr
}

func (this *errorResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	builder.nameValue("status", "err")
	builder.valueSeparator()
	builder.nameValue("msg", this.msg)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// okResponse
type okResponse struct {
	requestIdResponse
	action string
}

func newOkResponse(action string) *okResponse {
	return &okResponse{action: action}
}

func (this *okResponse) getResponsStatus() responseStatusType {
	return responseStatusOk
}

func (this *okResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, this.action)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// cmdStatusResponse
type cmdStatusResponse struct {
	requestIdResponse
	connections int
}

func newCmdStatusResponse(connections int) *cmdStatusResponse {
	return &cmdStatusResponse{
		connections: connections,
	}
}

func (this *cmdStatusResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "status")
	builder.valueSeparator()
	builder.nameIntValue("connections", this.connections)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlInsertResponse is a response for sql insert statement
type sqlInsertResponse struct {
	requestIdResponse
	id string
}

func newSqlInsertResponse(id string) *sqlInsertResponse {
	return &sqlInsertResponse{
		id: id,
	}
}

func (this *sqlInsertResponse) getResponsStatus() responseStatusType {
	return responseStatusOk
}

func (this *sqlInsertResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "insert")
	builder.valueSeparator()
	id(builder, this.id)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlSelectResponse is a response for sql select statement
type sqlSelectResponse struct {
	requestIdResponse
	columns []*column
	records []*record
	//
	init    bool
	rows    int
	fromrow int
	torow   int
}

func row(builder *JSONBuilder, columns []*column, rec *record) {
	builder.beginObject()
	// columns and values
	for colIndex, col := range columns {
		if colIndex != 0 {
			builder.valueSeparator()
		}
		builder.nameValue(col.name, rec.getValue(colIndex))
	}
	builder.endObject()
}

func (this *sqlSelectResponse) data(builder *JSONBuilder) bool {
	// write the columns first
	builder.string("columns")
	builder.nameSeparator()
	builder.beginArray()
	for colIndex, col := range this.columns {
		// another row
		if colIndex != 0 {
			builder.valueSeparator()
		}
		builder.string(col.name)
	}
	builder.endArray()
	builder.objectSeparator()
	// now write data (records)
	if !this.init {
		this.init = true
		this.rows = len(this.records)
		this.fromrow = 0
		this.torow = 0
	}
	more := len(this.records) > config.DATA_BATCH_SIZE
	records := this.records
	if more {
		records = this.records[0:config.DATA_BATCH_SIZE]
		this.records = this.records[config.DATA_BATCH_SIZE:]
		this.fromrow = this.torow + 1
		this.torow = this.fromrow + config.DATA_BATCH_SIZE - 1
	} else {
		if this.rows > 0 {
			this.fromrow = this.torow + 1
			this.torow = this.rows
		}
	}
	// rows, fromrow, torow
	builder.nameIntValue("rows", this.rows)
	builder.valueSeparator()
	builder.nameIntValue("fromrow", this.fromrow)
	builder.valueSeparator()
	builder.nameIntValue("torow", this.torow)
	builder.valueSeparator()
	//
	builder.string("data")
	builder.nameSeparator()
	builder.beginArray()
	for recIndex, rec := range records {
		// another row
		if recIndex != 0 {
			builder.objectSeparator()
		}
		row(builder, this.columns, rec)
	}
	builder.endArray()
	return more
}

func (this *sqlSelectResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "select")
	builder.valueSeparator()
	more := this.data(builder)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), more
}

func (this *sqlSelectResponse) copyRecordData(source *record) {
	l := len(this.columns)
	dest := &record{
		values: make([]string, l, l),
	}
	for idx, col := range this.columns {
		dest.setValue(idx, source.getValue(col.ordinal))
	}
	addRecordToSlice(&this.records, dest)
}

// sqlDeleteResponse
type sqlDeleteResponse struct {
	requestIdResponse
	deleted int
}

func (this *sqlDeleteResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "delete")
	builder.valueSeparator()
	builder.nameIntValue("rows", this.deleted)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlUpdateResponse
type sqlUpdateResponse struct {
	requestIdResponse
	updated int
}

func (this *sqlUpdateResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "update")
	builder.valueSeparator()
	builder.nameIntValue("rows", this.updated)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlSubscribeResponse
type sqlSubscribeResponse struct {
	requestIdResponse
	pubsubid uint64
}

func (this *sqlSubscribeResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "subscribe")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

func newSubscribeResponse(sub *subscription) response {
	return &sqlSubscribeResponse{
		pubsubid: sub.id,
	}
}

// sqlActionDataResponse
type sqlActionDataResponse struct {
	sqlSelectResponse
	pubsubid uint64
}

func (this *sqlActionDataResponse) toNetworkReadyJSONHelper(act string) ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, act)
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	more := this.data(builder)
	builder.endObject()
	return builder.getNetworkBytes(0), more
}

// sqlActionAddResponse
type sqlActionAddResponse struct {
	sqlActionDataResponse
}

func (this *sqlActionAddResponse) toNetworkReadyJSON() ([]byte, bool) {
	return this.toNetworkReadyJSONHelper("add")
}

// sqlActionInsertResponse
type sqlActionInsertResponse struct {
	sqlActionDataResponse
}

func (this *sqlActionInsertResponse) toNetworkReadyJSON() ([]byte, bool) {
	return this.toNetworkReadyJSONHelper("insert")
}

// sqlActonDeleteResponse
type sqlActionDeleteResponse struct {
	response
	id       string
	pubsubid uint64
}

func (this *sqlActionDeleteResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionDeleteResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "delete")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	builder.nameValue("id", this.id)
	builder.endObject()
	return builder.getNetworkBytes(0), false
}

// sqlActionRemoveResponse
type sqlActionRemoveResponse struct {
	response
	id       string
	pubsubid uint64
}

func (this *sqlActionRemoveResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionRemoveResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "remove")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	builder.nameValue("id", this.id)
	builder.endObject()
	return builder.getNetworkBytes(0), false
}

// sqlActionUpdateResponse
type sqlActionUpdateResponse struct {
	response
	pubsubid uint64
	cols     []*column
	rec      *record
}

func (this *sqlActionUpdateResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionUpdateResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "update")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()

	builder.string("data")
	builder.nameSeparator()
	builder.beginArray()
	row(builder, this.cols, this.rec)
	builder.endArray()

	builder.endObject()
	return builder.getNetworkBytes(0), false
}

func newSqlActionUpdateResponse(pubsubid uint64, cols []*column, rec *record) *sqlActionUpdateResponse {
	res := sqlActionUpdateResponse{
		pubsubid: pubsubid,
		cols:     cols,
	}
	// copy updated data
	l := len(cols)
	res.rec = &record{
		values: make([]string, l, l),
	}
	for idx, col := range cols {
		res.rec.setValue(idx, rec.getValue(col.ordinal))
	}
	return &res
}

// sqlUnsubscribeResponse
type sqlUnsubscribeResponse struct {
	requestIdResponse
	unsubscribed int
}

func (this *sqlUnsubscribeResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "unsubscribe")
	builder.valueSeparator()
	builder.nameIntValue("subscriptions", this.unsubscribed)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}
<commit_msg>response add unify pubsub update with pubsub add,insert<commit_after>/* Copyright (C) 2013 CompleteDB LLC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with PubSubSQL.  If not, see <http://www.gnu.org/licenses/>.
 */

package pubsubsql

import "strconv"

type responseStatusType int8

const (
	responseStatusOk  responseStatusType = iota // ok.
	responseStatusErr                           // error.
)

// response
type response interface {
	getResponseStatus() responseStatusType
	toNetworkReadyJSON() ([]byte, bool)
	setRequestId(requestId uint32)
}

type requestIdResponse struct {
	response
	requestId uint32
}

func (this *requestIdResponse) setRequestId(requestId uint32) {
	this.requestId = requestId
}

// json helper functions
func ok(builder *JSONBuilder) {
	builder.nameValue("status", "ok")
}

func id(builder *JSONBuilder, id string) {
	builder.nameValue("id", id)
}

func action(builder *JSONBuilder, action string) {
	builder.nameValue("action", action)
}

// errorResponse
type errorResponse struct {
	requestIdResponse
	msg string
}

func newErrorResponse(msg string) *errorResponse {
	return &errorResponse{
		msg: msg,
	}
}

func (this *errorResponse) getResponsStatus() responseStatusType {
	return responseStatusErr
}

func (this *errorResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	builder.nameValue("status", "err")
	builder.valueSeparator()
	builder.nameValue("msg", this.msg)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// okResponse
type okResponse struct {
	requestIdResponse
	action string
}

func newOkResponse(action string) *okResponse {
	return &okResponse{action: action}
}

func (this *okResponse) getResponsStatus() responseStatusType {
	return responseStatusOk
}

func (this *okResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, this.action)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// cmdStatusResponse
type cmdStatusResponse struct {
	requestIdResponse
	connections int
}

func newCmdStatusResponse(connections int) *cmdStatusResponse {
	return &cmdStatusResponse{
		connections: connections,
	}
}

func (this *cmdStatusResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "status")
	builder.valueSeparator()
	builder.nameIntValue("connections", this.connections)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlInsertResponse is a response for sql insert statement
type sqlInsertResponse struct {
	requestIdResponse
	id string
}

func newSqlInsertResponse(id string) *sqlInsertResponse {
	return &sqlInsertResponse{
		id: id,
	}
}

func (this *sqlInsertResponse) getResponsStatus() responseStatusType {
	return responseStatusOk
}

func (this *sqlInsertResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "insert")
	builder.valueSeparator()
	id(builder, this.id)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlSelectResponse is a response for sql select statement
type sqlSelectResponse struct {
	requestIdResponse
	columns []*column
	records []*record
	//
	init    bool
	rows    int
	fromrow int
	torow   int
}

func row(builder *JSONBuilder, columns []*column, rec *record) {
	builder.beginObject()
	// columns and values
	for colIndex, col := range columns {
		if colIndex != 0 {
			builder.valueSeparator()
		}
		builder.nameValue(col.name, rec.getValue(colIndex))
	}
	builder.endObject()
}

func (this *sqlSelectResponse) data(builder *JSONBuilder) bool {
	// write the columns first
	builder.string("columns")
	builder.nameSeparator()
	builder.beginArray()
	for colIndex, col := range this.columns {
		// another row
		if colIndex != 0 {
			builder.valueSeparator()
		}
		builder.string(col.name)
	}
	builder.endArray()
	builder.objectSeparator()
	// now write data (records)
	if !this.init {
		this.init = true
		this.rows = len(this.records)
		this.fromrow = 0
		this.torow = 0
	}
	more := len(this.records) > config.DATA_BATCH_SIZE
	records := this.records
	if more {
		records = this.records[0:config.DATA_BATCH_SIZE]
		this.records = this.records[config.DATA_BATCH_SIZE:]
		this.fromrow = this.torow + 1
		this.torow = this.fromrow + config.DATA_BATCH_SIZE - 1
	} else {
		if this.rows > 0 {
			this.fromrow = this.torow + 1
			this.torow = this.rows
		}
	}
	// rows, fromrow, torow
	builder.nameIntValue("rows", this.rows)
	builder.valueSeparator()
	builder.nameIntValue("fromrow", this.fromrow)
	builder.valueSeparator()
	builder.nameIntValue("torow", this.torow)
	builder.valueSeparator()
	//
	builder.string("data")
	builder.nameSeparator()
	builder.beginArray()
	for recIndex, rec := range records {
		// another row
		if recIndex != 0 {
			builder.objectSeparator()
		}
		row(builder, this.columns, rec)
	}
	builder.endArray()
	return more
}

func (this *sqlSelectResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "select")
	builder.valueSeparator()
	more := this.data(builder)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), more
}

func (this *sqlSelectResponse) copyRecordData(source *record) {
	l := len(this.columns)
	dest := &record{
		values: make([]string, l, l),
	}
	for idx, col := range this.columns {
		dest.setValue(idx, source.getValue(col.ordinal))
	}
	addRecordToSlice(&this.records, dest)
}

// sqlDeleteResponse
type sqlDeleteResponse struct {
	requestIdResponse
	deleted int
}

func (this *sqlDeleteResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "delete")
	builder.valueSeparator()
	builder.nameIntValue("rows", this.deleted)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlUpdateResponse
type sqlUpdateResponse struct {
	requestIdResponse
	updated int
}

func (this *sqlUpdateResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "update")
	builder.valueSeparator()
	builder.nameIntValue("rows", this.updated)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

// sqlSubscribeResponse
type sqlSubscribeResponse struct {
	requestIdResponse
	pubsubid uint64
}

func (this *sqlSubscribeResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "subscribe")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}

func newSubscribeResponse(sub *subscription) response {
	return &sqlSubscribeResponse{
		pubsubid: sub.id,
	}
}

// sqlActionDataResponse
type sqlActionDataResponse struct {
	sqlSelectResponse
	pubsubid uint64
}

func (this *sqlActionDataResponse) toNetworkReadyJSONHelper(act string) ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, act)
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	more := this.data(builder)
	builder.endObject()
	return builder.getNetworkBytes(0), more
}

// sqlActionAddResponse
type sqlActionAddResponse struct {
	sqlActionDataResponse
}

func (this *sqlActionAddResponse) toNetworkReadyJSON() ([]byte, bool) {
	return this.toNetworkReadyJSONHelper("add")
}

// sqlActionInsertResponse
type sqlActionInsertResponse struct {
	sqlActionDataResponse
}

func (this *sqlActionInsertResponse) toNetworkReadyJSON() ([]byte, bool) {
	return this.toNetworkReadyJSONHelper("insert")
}

// sqlActonDeleteResponse
type sqlActionDeleteResponse struct {
	response
	id       string
	pubsubid uint64
}

func (this *sqlActionDeleteResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionDeleteResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "delete")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	builder.nameValue("id", this.id)
	builder.endObject()
	return builder.getNetworkBytes(0), false
}

// sqlActionRemoveResponse
type sqlActionRemoveResponse struct {
	response
	id       string
	pubsubid uint64
}

func (this *sqlActionRemoveResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionRemoveResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "remove")
	builder.valueSeparator()
	builder.nameValue("pubsubid", strconv.FormatUint(this.pubsubid, 10))
	builder.valueSeparator()
	builder.nameValue("id", this.id)
	builder.endObject()
	return builder.getNetworkBytes(0), false
}

// sqlActionUpdateResponse
type sqlActionUpdateResponse struct {
	sqlActionDataResponse
}

func (this *sqlActionUpdateResponse) setRequestId(requestId uint32) {

}

func (this *sqlActionUpdateResponse) toNetworkReadyJSON() ([]byte, bool) {
	return this.toNetworkReadyJSONHelper("update")
}

func newSqlActionUpdateResponse(pubsubid uint64, cols []*column, rec *record) *sqlActionUpdateResponse {
	var res sqlActionUpdateResponse
	res.columns = cols
	res.pubsubid = pubsubid
	res.copyRecordData(rec)
	return &res
}

// sqlUnsubscribeResponse
type sqlUnsubscribeResponse struct {
	requestIdResponse
	unsubscribed int
}

func (this *sqlUnsubscribeResponse) toNetworkReadyJSON() ([]byte, bool) {
	builder := networkReadyJSONBuilder()
	builder.beginObject()
	ok(builder)
	builder.valueSeparator()
	action(builder, "unsubscribe")
	builder.valueSeparator()
	builder.nameIntValue("subscriptions", this.unsubscribed)
	builder.endObject()
	return builder.getNetworkBytes(this.requestId), false
}
<|endoftext|>


<commit_before>package peco

import (
	"fmt"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
	"github.com/nsf/termbox-go"
)

type View struct {
	*Ctx
}

type PagingRequest int

const (
	ToNextLine PagingRequest = iota
	ToNextPage
	ToPrevLine
	ToPrevPage
)

func (u *View) Loop() {
	u.AddWaitGroup()
	defer u.ReleaseWaitGroup()
	for {
		select {
		case <-u.LoopCh():
			return
		case r := <-u.PagingCh():
			u.movePage(r)
		case lines := <-u.DrawCh():
			u.drawScreen(lines)
		}
	}
}

func printTB(x, y int, fg, bg termbox.Attribute, msg string) {
	for len(msg) > 0 {
		c, w := utf8.DecodeRuneInString(msg)
		if c == utf8.RuneError {
			continue
		}
		msg = msg[w:]
		termbox.SetCell(x, y, c, fg, bg)
		x += runewidth.RuneWidth(c)
	}
}

func (v *View) movePage(p PagingRequest) {
	_, height := termbox.Size()
	perPage := height - 4

	switch p {
	case ToPrevLine:
		v.selectedLine--
	case ToNextLine:
		v.selectedLine++
	case ToPrevPage, ToNextPage:
		if p == ToPrevPage {
			v.selectedLine -= perPage
		} else {
			v.selectedLine += perPage
		}
	}

	if v.selectedLine < 1 {
		if v.current != nil && len(v.current) > perPage {
			// Go to last page, if possible
			v.selectedLine = len(v.current)
		} else {
			v.selectedLine = 1
		}
	} else if v.current != nil && v.selectedLine > len(v.current) {
		v.selectedLine = 1
	}
}

func (u *View) drawScreen(targets []Match) {
	u.mutex.Lock()
	defer u.mutex.Unlock()

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	if targets == nil {
		if current := u.Ctx.current; current != nil {
			targets = u.Ctx.current
		} else {
			targets = u.Ctx.lines
		}
	}

	width, height := termbox.Size()
	perPage := height - 4

CALCULATE_PAGE:
	currentPage := ((u.Ctx.selectedLine - 1) / perPage) + 1
	if currentPage <= 0 {
		currentPage = 1
	}
	offset := (currentPage - 1) * perPage
	var maxPage int
	if len(targets) == 0 {
		maxPage = 1
	} else {
		maxPage = ((len(targets) + perPage - 1) / perPage)
	}

	if maxPage < currentPage {
		u.Ctx.selectedLine = offset
		goto CALCULATE_PAGE
	}

	printTB(0, 0, termbox.ColorDefault, termbox.ColorDefault, "QUERY>")

	if u.caretPos <= 0 {
		u.caretPos = 0 // sanity
	}
	if u.caretPos > len(u.query) {
		u.caretPos = len(u.query)
	}

	if u.caretPos == len(u.query) {
		// the entire string + the caret after the string
		printTB(8, 0, termbox.ColorDefault, termbox.ColorDefault, string(u.query))
		termbox.SetCell(8+runewidth.StringWidth(string(u.query)), 0, ' ', termbox.ColorDefault|termbox.AttrReverse, termbox.ColorDefault|termbox.AttrReverse)
	} else {
		// the caret is in the middle of the string
		prev := 0
		for i, r := range u.query {
			fg := termbox.ColorDefault
			bg := termbox.ColorDefault
			if i == u.caretPos {
				fg |= termbox.AttrReverse
				bg |= termbox.AttrReverse
			}
			termbox.SetCell(8+prev, 0, r, fg, bg)
			prev += runewidth.RuneWidth(r)
		}
	}

	pmsg := fmt.Sprintf("%s [%d/%d]", u.Ctx.Matcher().String(), currentPage, maxPage)

	printTB(width-runewidth.StringWidth(pmsg), 0, termbox.ColorDefault, termbox.ColorDefault, pmsg)

	for n := 1; n <= perPage; n++ {
		fgAttr := termbox.ColorDefault
		bgAttr := termbox.ColorDefault
		if n == u.selectedLine-offset {
			fgAttr = termbox.AttrUnderline
			bgAttr = termbox.ColorMagenta
		}

		targetIdx := offset + n - 1
		if targetIdx >= len(targets) {
			break
		}
		target := targets[targetIdx]
		line := target.line
		if target.matches == nil {
			printTB(0, n, fgAttr, bgAttr, line)
		} else {
			prev := 0
			index := 0
			for _, m := range target.matches {
				if m[0] > index {
					c := line[index:m[0]]
					printTB(prev, n, fgAttr, bgAttr, c)
					prev += runewidth.StringWidth(c)
					index += len(c)
				}
				c := line[m[0]:m[1]]
				printTB(prev, n, fgAttr|termbox.ColorCyan, bgAttr, c)
				prev += runewidth.StringWidth(c)
				index += len(c)
			}

			m := target.matches[len(target.matches)-1]
			if m[0] > prev {
				printTB(prev, n, fgAttr|termbox.ColorCyan, bgAttr, line[m[0]:m[1]])
			} else if len(line) > m[1] {
				printTB(prev, n, fgAttr, bgAttr, line[m[1]:len(line)])
			}
		}
	}
	termbox.Flush()

	// FIXME
	u.current = targets
}
<commit_msg>Ignore unexpected bytes with displaying '?' instead<commit_after>package peco

import (
	"fmt"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
	"github.com/nsf/termbox-go"
)

type View struct {
	*Ctx
}

type PagingRequest int

const (
	ToNextLine PagingRequest = iota
	ToNextPage
	ToPrevLine
	ToPrevPage
)

func (u *View) Loop() {
	u.AddWaitGroup()
	defer u.ReleaseWaitGroup()
	for {
		select {
		case <-u.LoopCh():
			return
		case r := <-u.PagingCh():
			u.movePage(r)
		case lines := <-u.DrawCh():
			u.drawScreen(lines)
		}
	}
}

func printTB(x, y int, fg, bg termbox.Attribute, msg string) {
	for len(msg) > 0 {
		c, w := utf8.DecodeRuneInString(msg)
		if c == utf8.RuneError {
			c = '?'
			w = 1
		}
		msg = msg[w:]
		termbox.SetCell(x, y, c, fg, bg)
		x += runewidth.RuneWidth(c)
	}
}

func (v *View) movePage(p PagingRequest) {
	_, height := termbox.Size()
	perPage := height - 4

	switch p {
	case ToPrevLine:
		v.selectedLine--
	case ToNextLine:
		v.selectedLine++
	case ToPrevPage, ToNextPage:
		if p == ToPrevPage {
			v.selectedLine -= perPage
		} else {
			v.selectedLine += perPage
		}
	}

	if v.selectedLine < 1 {
		if v.current != nil && len(v.current) > perPage {
			// Go to last page, if possible
			v.selectedLine = len(v.current)
		} else {
			v.selectedLine = 1
		}
	} else if v.current != nil && v.selectedLine > len(v.current) {
		v.selectedLine = 1
	}
}

func (u *View) drawScreen(targets []Match) {
	u.mutex.Lock()
	defer u.mutex.Unlock()

	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

	if targets == nil {
		if current := u.Ctx.current; current != nil {
			targets = u.Ctx.current
		} else {
			targets = u.Ctx.lines
		}
	}

	width, height := termbox.Size()
	perPage := height - 4

CALCULATE_PAGE:
	currentPage := ((u.Ctx.selectedLine - 1) / perPage) + 1
	if currentPage <= 0 {
		currentPage = 1
	}
	offset := (currentPage - 1) * perPage
	var maxPage int
	if len(targets) == 0 {
		maxPage = 1
	} else {
		maxPage = ((len(targets) + perPage - 1) / perPage)
	}

	if maxPage < currentPage {
		u.Ctx.selectedLine = offset
		goto CALCULATE_PAGE
	}

	printTB(0, 0, termbox.ColorDefault, termbox.ColorDefault, "QUERY>")

	if u.caretPos <= 0 {
		u.caretPos = 0 // sanity
	}
	if u.caretPos > len(u.query) {
		u.caretPos = len(u.query)
	}

	if u.caretPos == len(u.query) {
		// the entire string + the caret after the string
		printTB(8, 0, termbox.ColorDefault, termbox.ColorDefault, string(u.query))
		termbox.SetCell(8+runewidth.StringWidth(string(u.query)), 0, ' ', termbox.ColorDefault|termbox.AttrReverse, termbox.ColorDefault|termbox.AttrReverse)
	} else {
		// the caret is in the middle of the string
		prev := 0
		for i, r := range u.query {
			fg := termbox.ColorDefault
			bg := termbox.ColorDefault
			if i == u.caretPos {
				fg |= termbox.AttrReverse
				bg |= termbox.AttrReverse
			}
			termbox.SetCell(8+prev, 0, r, fg, bg)
			prev += runewidth.RuneWidth(r)
		}
	}

	pmsg := fmt.Sprintf("%s [%d/%d]", u.Ctx.Matcher().String(), currentPage, maxPage)

	printTB(width-runewidth.StringWidth(pmsg), 0, termbox.ColorDefault, termbox.ColorDefault, pmsg)

	for n := 1; n <= perPage; n++ {
		fgAttr := termbox.ColorDefault
		bgAttr := termbox.ColorDefault
		if n == u.selectedLine-offset {
			fgAttr = termbox.AttrUnderline
			bgAttr = termbox.ColorMagenta
		}

		targetIdx := offset + n - 1
		if targetIdx >= len(targets) {
			break
		}
		target := targets[targetIdx]
		line := target.line
		if target.matches == nil {
			printTB(0, n, fgAttr, bgAttr, line)
		} else {
			prev := 0
			index := 0
			for _, m := range target.matches {
				if m[0] > index {
					c := line[index:m[0]]
					printTB(prev, n, fgAttr, bgAttr, c)
					prev += runewidth.StringWidth(c)
					index += len(c)
				}
				c := line[m[0]:m[1]]
				printTB(prev, n, fgAttr|termbox.ColorCyan, bgAttr, c)
				prev += runewidth.StringWidth(c)
				index += len(c)
			}

			m := target.matches[len(target.matches)-1]
			if m[0] > prev {
				printTB(prev, n, fgAttr|termbox.ColorCyan, bgAttr, line[m[0]:m[1]])
			} else if len(line) > m[1] {
				printTB(prev, n, fgAttr, bgAttr, line[m[1]:len(line)])
			}
		}
	}
	termbox.Flush()

	// FIXME
	u.current = targets
}
<|endoftext|>