<commit_before>package docker

import (
	"bytes"
	"fmt"
	"github.com/ActiveState/tail"
	"github.com/mitchellh/packer/packer"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"time"
)

type Communicator struct {
	ContainerId  string
	HostDir      string
	ContainerDir string

	lock sync.Mutex
}

func (c *Communicator) Start(remote *packer.RemoteCmd) error {
	// Create a temporary file to store the output. Because of a bug in
	// Docker, sometimes all the output doesn't properly show up. This
	// file will capture ALL of the output, and we'll read that.
	//
	// https://github.com/dotcloud/docker/issues/2625
	outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
	if err != nil {
		return err
	}
	outputFile.Close()

	// This file will store the exit code of the command once it is complete.
	exitCodePath := outputFile.Name() + "-exit"

	cmd := exec.Command("docker", "attach", c.ContainerId)
	stdin_w, err := cmd.StdinPipe()
	if err != nil {
		// We have to do some cleanup since run was never called
		os.Remove(outputFile.Name())
		os.Remove(exitCodePath)

		return err
	}

	// Run the actual command in a goroutine so that Start doesn't block
	go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)

	return nil
}

func (c *Communicator) Upload(dst string, src io.Reader) error {
	// Create a temporary file to store the upload
	tempfile, err := ioutil.TempFile(c.HostDir, "upload")
	if err != nil {
		return err
	}
	defer os.Remove(tempfile.Name())

	// Copy the contents to the temporary file
	_, err = io.Copy(tempfile, src)
	tempfile.Close()
	if err != nil {
		return err
	}

	// Copy the file into place by copying the temporary file we put
	// into the shared folder into the proper location in the container
	cmd := &packer.RemoteCmd{
		Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir,
			filepath.Base(tempfile.Name()), dst),
	}

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

	// Wait for the copy to complete
	cmd.Wait()
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
	}

	return nil
}

func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
	// Create the temporary directory that will store the contents of "src"
	// for copying into the container.
	td, err := ioutil.TempDir(c.HostDir, "dirupload")
	if err != nil {
		return err
	}
	defer os.RemoveAll(td)

	walkFn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relpath, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}
		hostpath := filepath.Join(td, relpath)

		// If it is a directory, just create it
		if info.IsDir() {
			return os.MkdirAll(hostpath, info.Mode())
		}

		// It is a file, copy it over, including mode.
		src, err := os.Open(path)
		if err != nil {
			return err
		}
		defer src.Close()

		dst, err := os.Create(hostpath)
		if err != nil {
			return err
		}
		defer dst.Close()

		if _, err := io.Copy(dst, src); err != nil {
			return err
		}

		si, err := src.Stat()
		if err != nil {
			return err
		}

		return dst.Chmod(si.Mode())
	}

	// Copy the entire directory tree to the temporary directory
	if err := filepath.Walk(src, walkFn); err != nil {
		return err
	}

	// Determine the destination directory
	containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
	containerDst := dst
	if src[len(src)-1] != '/' {
		containerDst = filepath.Join(dst, filepath.Base(src))
	}

	// Make the directory, then copy into it
	cmd := &packer.RemoteCmd{
		Command: fmt.Sprintf("set -e; mkdir -p %s; cp -R %s/* %s",
			containerDst, containerSrc, containerDst),
	}
	if err := c.Start(cmd); err != nil {
		return err
	}

	// Wait for the copy to complete
	cmd.Wait()
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
	}

	return nil
}

func (c *Communicator) Download(src string, dst io.Writer) error {
	panic("not implemented")
}

// Runs the given command and blocks until completion
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
	// For Docker, remote communication must be serialized since it
	// only supports single execution.
	c.lock.Lock()
	defer c.lock.Unlock()

	// Clean up after ourselves by removing our temporary files
	defer os.Remove(outputFile.Name())
	defer os.Remove(exitCodePath)

	// Tail the output file and send the data to the stdout listener
	tail, err := tail.TailFile(outputFile.Name(), tail.Config{
		Poll:   true,
		ReOpen: true,
		Follow: true,
	})
	if err != nil {
		log.Printf("Error tailing output file: %s", err)
		remote.SetExited(254)
		return
	}
	defer tail.Stop()

	// Modify the remote command so that all the output of the commands
	// go to a single file and so that the exit code is redirected to
	// a single file. This lets us determine both when the command
	// is truly complete (because the file will have data), what the
	// exit status is (because Docker loses it because of the pty, not
	// Docker's fault), and get the output (Docker bug).
	remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
		remote.Command,
		filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
		filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))

	// Start the command
	log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
	if err := cmd.Start(); err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}

	go func() {
		defer stdin_w.Close()

		// This sleep needs to be here because of the issue linked to below.
		// Basically, without it, Docker will hang on reading stdin forever,
		// and won't see what we write, for some reason.
		//
		// https://github.com/dotcloud/docker/issues/2628
		time.Sleep(2 * time.Second)

		stdin_w.Write([]byte(remoteCmd + "\n"))
	}()

	// Start a goroutine to read all the lines out of the logs
	go func() {
		for line := range tail.Lines {
			if remote.Stdout != nil {
				remote.Stdout.Write([]byte(line.Text + "\n"))
			} else {
				log.Printf("Command stdout: %#v", line.Text)
			}
		}
	}()

	err = cmd.Wait()
	if exitErr, ok := err.(*exec.ExitError); ok {
		exitStatus := 1

		// There is no process-independent way to get the REAL
		// exit status so we just try to go deeper.
		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
			exitStatus = status.ExitStatus()
		}

		// Say that we ended, since if Docker itself failed, then
		// the command must've not run, or so we assume
		remote.SetExited(exitStatus)
		return
	}

	// Wait for the exit code to appear in our file...
	log.Println("Waiting for exit code to appear for remote command...")
	for {
		fi, err := os.Stat(exitCodePath)
		if err == nil && fi.Size() > 0 {
			break
		}

		time.Sleep(1 * time.Second)
	}

	// Read the exit code
	exitRaw, err := ioutil.ReadFile(exitCodePath)
	if err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}

	exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
	if err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}
	log.Printf("Executed command exit status: %d", exitStatus)

	// Finally, we're done
	remote.SetExited(int(exitStatus))
}
<commit_msg>builder/docker: style<commit_after>package docker

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/ActiveState/tail"
	"github.com/mitchellh/packer/packer"
)

type Communicator struct {
	ContainerId  string
	HostDir      string
	ContainerDir string

	lock sync.Mutex
}

func (c *Communicator) Start(remote *packer.RemoteCmd) error {
	// Create a temporary file to store the output. Because of a bug in
	// Docker, sometimes all the output doesn't properly show up. This
	// file will capture ALL of the output, and we'll read that.
	//
	// https://github.com/dotcloud/docker/issues/2625
	outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
	if err != nil {
		return err
	}
	outputFile.Close()

	// This file will store the exit code of the command once it is complete.
	exitCodePath := outputFile.Name() + "-exit"

	cmd := exec.Command("docker", "attach", c.ContainerId)
	stdin_w, err := cmd.StdinPipe()
	if err != nil {
		// We have to do some cleanup since run was never called
		os.Remove(outputFile.Name())
		os.Remove(exitCodePath)

		return err
	}

	// Run the actual command in a goroutine so that Start doesn't block
	go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)

	return nil
}

func (c *Communicator) Upload(dst string, src io.Reader) error {
	// Create a temporary file to store the upload
	tempfile, err := ioutil.TempFile(c.HostDir, "upload")
	if err != nil {
		return err
	}
	defer os.Remove(tempfile.Name())

	// Copy the contents to the temporary file
	_, err = io.Copy(tempfile, src)
	tempfile.Close()
	if err != nil {
		return err
	}

	// Copy the file into place by copying the temporary file we put
	// into the shared folder into the proper location in the container
	cmd := &packer.RemoteCmd{
		Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir,
			filepath.Base(tempfile.Name()), dst),
	}

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

	// Wait for the copy to complete
	cmd.Wait()
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
	}

	return nil
}

func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
	// Create the temporary directory that will store the contents of "src"
	// for copying into the container.
	td, err := ioutil.TempDir(c.HostDir, "dirupload")
	if err != nil {
		return err
	}
	defer os.RemoveAll(td)

	walkFn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relpath, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}
		hostpath := filepath.Join(td, relpath)

		// If it is a directory, just create it
		if info.IsDir() {
			return os.MkdirAll(hostpath, info.Mode())
		}

		// It is a file, copy it over, including mode.
		src, err := os.Open(path)
		if err != nil {
			return err
		}
		defer src.Close()

		dst, err := os.Create(hostpath)
		if err != nil {
			return err
		}
		defer dst.Close()

		if _, err := io.Copy(dst, src); err != nil {
			return err
		}

		si, err := src.Stat()
		if err != nil {
			return err
		}

		return dst.Chmod(si.Mode())
	}

	// Copy the entire directory tree to the temporary directory
	if err := filepath.Walk(src, walkFn); err != nil {
		return err
	}

	// Determine the destination directory
	containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
	containerDst := dst
	if src[len(src)-1] != '/' {
		containerDst = filepath.Join(dst, filepath.Base(src))
	}

	// Make the directory, then copy into it
	cmd := &packer.RemoteCmd{
		Command: fmt.Sprintf("set -e; mkdir -p %s; cp -R %s/* %s",
			containerDst, containerSrc, containerDst),
	}
	if err := c.Start(cmd); err != nil {
		return err
	}

	// Wait for the copy to complete
	cmd.Wait()
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
	}

	return nil
}

func (c *Communicator) Download(src string, dst io.Writer) error {
	panic("not implemented")
}

// Runs the given command and blocks until completion
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
	// For Docker, remote communication must be serialized since it
	// only supports single execution.
	c.lock.Lock()
	defer c.lock.Unlock()

	// Clean up after ourselves by removing our temporary files
	defer os.Remove(outputFile.Name())
	defer os.Remove(exitCodePath)

	// Tail the output file and send the data to the stdout listener
	tail, err := tail.TailFile(outputFile.Name(), tail.Config{
		Poll:   true,
		ReOpen: true,
		Follow: true,
	})
	if err != nil {
		log.Printf("Error tailing output file: %s", err)
		remote.SetExited(254)
		return
	}
	defer tail.Stop()

	// Modify the remote command so that all the output of the commands
	// go to a single file and so that the exit code is redirected to
	// a single file. This lets us determine both when the command
	// is truly complete (because the file will have data), what the
	// exit status is (because Docker loses it because of the pty, not
	// Docker's fault), and get the output (Docker bug).
	remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
		remote.Command,
		filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
		filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))

	// Start the command
	log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
	if err := cmd.Start(); err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}

	go func() {
		defer stdin_w.Close()

		// This sleep needs to be here because of the issue linked to below.
		// Basically, without it, Docker will hang on reading stdin forever,
		// and won't see what we write, for some reason.
		//
		// https://github.com/dotcloud/docker/issues/2628
		time.Sleep(2 * time.Second)

		stdin_w.Write([]byte(remoteCmd + "\n"))
	}()

	// Start a goroutine to read all the lines out of the logs
	go func() {
		for line := range tail.Lines {
			if remote.Stdout != nil {
				remote.Stdout.Write([]byte(line.Text + "\n"))
			} else {
				log.Printf("Command stdout: %#v", line.Text)
			}
		}
	}()

	err = cmd.Wait()
	if exitErr, ok := err.(*exec.ExitError); ok {
		exitStatus := 1

		// There is no process-independent way to get the REAL
		// exit status so we just try to go deeper.
		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
			exitStatus = status.ExitStatus()
		}

		// Say that we ended, since if Docker itself failed, then
		// the command must've not run, or so we assume
		remote.SetExited(exitStatus)
		return
	}

	// Wait for the exit code to appear in our file...
	log.Println("Waiting for exit code to appear for remote command...")
	for {
		fi, err := os.Stat(exitCodePath)
		if err == nil && fi.Size() > 0 {
			break
		}

		time.Sleep(1 * time.Second)
	}

	// Read the exit code
	exitRaw, err := ioutil.ReadFile(exitCodePath)
	if err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}

	exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
	if err != nil {
		log.Printf("Error executing: %s", err)
		remote.SetExited(254)
		return
	}
	log.Printf("Executed command exit status: %d", exitStatus)

	// Finally, we're done
	remote.SetExited(int(exitStatus))
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

// Stackdriver tests cannot be run in parallel otherwise they will error out with:
// Error 503: Too many concurrent edits to the project configuration. Please try again.

func TestAccMonitoringAlertPolicy_basic(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	filter := `metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\"`

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, "ALIGN_RATE", filter),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccMonitoringAlertPolicy_update(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	filter1 := `metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\"`
	aligner1 := "ALIGN_RATE"
	filter2 := `metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.type=\"gce_instance\"`
	aligner2 := "ALIGN_MAX"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner1, filter1),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner2, filter2),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccMonitoringAlertPolicy_full(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName1 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName2 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_full(alertName, conditionName1, conditionName2),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.full",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckAlertPolicyDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "google_monitoring_alert_policy" {
			continue
		}

		name := rs.Primary.Attributes["name"]

		url := fmt.Sprintf("https://monitoring.googleapis.com/v3/%s", name)
		_, err := sendRequest(config, "GET", url, nil)

		if err == nil {
			return fmt.Errorf("Error, alert policy %s still exists", name)
		}
	}

	return nil
}

func testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner, filter string) string {
	return fmt.Sprintf(`
resource "google_monitoring_alert_policy" "basic" {
  display_name = "%s"
  enabled      = true
  combiner     = "OR"

  conditions {
    display_name = "%s"

    condition_threshold {
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "%s"
      }

      duration       = "60s"
      comparison     = "COMPARISON_GT"
      filter         = "%s"
      thresholdValue = "0.5"
    }
  }
}
`, alertName, conditionName, aligner, filter)
}

func testAccMonitoringAlertPolicy_full(alertName, conditionName1, conditionName2 string) string {
	return fmt.Sprintf(`
resource "google_monitoring_alert_policy" "full" {
  display_name = "%s"
  combiner     = "OR"
  enabled      = true

  conditions {
    display_name = "%s"

    condition_threshold {
      threshold_value = 50
      filter          = "metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\""
      duration        = "60s"
      comparison      = "COMPARISON_GT"

      aggregations {
        alignment_period     = "60s"
        per_series_aligner   = "ALIGN_RATE"
        cross_series_reducer = "REDUCE_MEAN"

        group_by_fields = [
          "metric.label.device_name",
          "project",
          "resource.label.instance_id",
          "resource.label.zone",
        ]
      }

      trigger {
        percent = 10
      }
    }
  }

  conditions {
    display_name = "%s"

    condition_absent {
      duration = "3600s"
      filter   = "metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.type=\"gce_instance\""

      aggregations {
        alignment_period     = "60s"
        cross_series_reducer = "REDUCE_MEAN"
        per_series_aligner   = "ALIGN_MEAN"

        group_by_fields = [
          "project",
          "resource.label.instance_id",
          "resource.label.zone",
        ]
      }

      trigger {
        count = 1
      }
    }
  }

  documentation {
    content   = "test content"
    mime_type = "text/markdown"
  }
}
`, alertName, conditionName1, conditionName2)
}
<commit_msg>Fix bad monitoring test data (#2767)<commit_after>package google

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

// Stackdriver tests cannot be run in parallel otherwise they will error out with:
// Error 503: Too many concurrent edits to the project configuration. Please try again.

func TestAccMonitoringAlertPolicy_basic(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	filter := `metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\"`

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, "ALIGN_RATE", filter),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccMonitoringAlertPolicy_update(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	filter1 := `metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\"`
	aligner1 := "ALIGN_RATE"
	filter2 := `metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.type=\"gce_instance\"`
	aligner2 := "ALIGN_MAX"

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner1, filter1),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner2, filter2),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccMonitoringAlertPolicy_full(t *testing.T) {

	alertName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName1 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	conditionName2 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAlertPolicyDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccMonitoringAlertPolicy_full(alertName, conditionName1, conditionName2),
			},
			{
				ResourceName:      "google_monitoring_alert_policy.full",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckAlertPolicyDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "google_monitoring_alert_policy" {
			continue
		}

		name := rs.Primary.Attributes["name"]

		url := fmt.Sprintf("https://monitoring.googleapis.com/v3/%s", name)
		_, err := sendRequest(config, "GET", url, nil)

		if err == nil {
			return fmt.Errorf("Error, alert policy %s still exists", name)
		}
	}

	return nil
}

func testAccMonitoringAlertPolicy_basic(alertName, conditionName, aligner, filter string) string {
	return fmt.Sprintf(`
resource "google_monitoring_alert_policy" "basic" {
  display_name = "%s"
  enabled      = true
  combiner     = "OR"

  conditions {
    display_name = "%s"

    condition_threshold {
      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "%s"
      }

      duration       = "60s"
      comparison     = "COMPARISON_GT"
      filter         = "%s"
      threshold_value = "0.5"
    }
  }
}
`, alertName, conditionName, aligner, filter)
}

func testAccMonitoringAlertPolicy_full(alertName, conditionName1, conditionName2 string) string {
	return fmt.Sprintf(`
resource "google_monitoring_alert_policy" "full" {
  display_name = "%s"
  combiner     = "OR"
  enabled      = true

  conditions {
    display_name = "%s"

    condition_threshold {
      threshold_value = 50
      filter          = "metric.type=\"compute.googleapis.com/instance/disk/write_bytes_count\" AND resource.type=\"gce_instance\""
      duration        = "60s"
      comparison      = "COMPARISON_GT"

      aggregations {
        alignment_period     = "60s"
        per_series_aligner   = "ALIGN_RATE"
        cross_series_reducer = "REDUCE_MEAN"

        group_by_fields = [
          "metric.label.device_name",
          "project",
          "resource.label.instance_id",
          "resource.label.zone",
        ]
      }

      trigger {
        percent = 10
      }
    }
  }

  conditions {
    display_name = "%s"

    condition_absent {
      duration = "3600s"
      filter   = "metric.type=\"compute.googleapis.com/instance/cpu/utilization\" AND resource.type=\"gce_instance\""

      aggregations {
        alignment_period     = "60s"
        cross_series_reducer = "REDUCE_MEAN"
        per_series_aligner   = "ALIGN_MEAN"

        group_by_fields = [
          "project",
          "resource.label.instance_id",
          "resource.label.zone",
        ]
      }

      trigger {
        count = 1
      }
    }
  }

  documentation {
    content   = "test content"
    mime_type = "text/markdown"
  }
}
`, alertName, conditionName1, conditionName2)
}
<|endoftext|>


<commit_before>package kloud

import (
	"fmt"

	"koding/kites/kloud/eventer"
	"koding/kites/kloud/kloud/machinestate"
	"koding/kites/kloud/kloud/protocol"

	"github.com/koding/kite"
)

type Controller struct {
	// Incoming arguments
	MachineId    string
	ImageName    string
	InstanceName string

	// Populated later
	CurrenState machinestate.State `json:"-"`
	Provider    protocol.Provider  `json:"-"`
	MachineData *MachineData       `json:"-"`
	Eventer     eventer.Eventer    `json:"-"`
}

type controlFunc func(*kite.Request, *Controller) (interface{}, error)

type statePair struct {
	initial machinestate.State
	final   machinestate.State
}

var states = map[string]*statePair{
	"start":   &statePair{initial: machinestate.Starting, final: machinestate.Running},
	"stop":    &statePair{initial: machinestate.Stopping, final: machinestate.Stopped},
	"destroy": &statePair{initial: machinestate.Terminating, final: machinestate.Terminated},
	"restart": &statePair{initial: machinestate.Rebooting, final: machinestate.Running},
}

func (k *Kloud) ControlFunc(method string, control controlFunc) {
	handler := func(r *kite.Request) (interface{}, error) {
		// calls with zero arguments causes args to be nil. Check it that we
		// don't get a beloved panic
		if r.Args == nil {
			return nil, NewError(ErrNoArguments)
		}

		k.Log.Info("[controller] got a request for method: '%s' with args: %v",
			method, string(r.Args.Raw))

		// this locks are important to prevent consecutive calls from the same
		// user
		k.idlock.Get(r.Username).Lock()
		c, err := k.controller(r)
		if err != nil {
			k.idlock.Get(r.Username).Unlock()
			return nil, err
		}
		k.idlock.Get(r.Username).Unlock()

		// now lock for machine-ids
		k.idlock.Get(c.MachineId).Lock()
		defer k.idlock.Get(c.MachineId).Unlock()

		// call no our kite handler with the the controller context
		return control(r, c)
	}

	k.Kite.HandleFunc(method, handler)
}

// controller returns the Controller struct with all necessary entities
// responsible for the given machine Id. It also calls provider.Prepare before
// returning.
func (k *Kloud) controller(r *kite.Request) (contr *Controller, err error) {
	args := &Controller{}
	if err := r.Args.One().Unmarshal(args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Geth all the data we need. It also sets the assignee for the given
	// machine id.
	m, err := k.Storage.Get(args.MachineId, &GetOption{
		IncludeMachine:    true,
		IncludeCredential: true,
	})
	if err != nil {
		return nil, err
	}

	// if something goes wrong reset the assigne which was set in previous step
	// by Storage.Get.
	defer func() {
		if err != nil {
			k.Storage.ResetAssignee(args.MachineId)
		}
	}()

	k.Log.Debug("[controller] got machine data with machineID (%s) : %#v",
		args.MachineId, m.Machine)

	// prevent request if the machine is terminated. However we want the user
	// to be able to build again
	if (m.Machine.State() == machinestate.Terminating || m.Machine.State() == machinestate.Terminated) &&
		r.Method != "build" {
		return nil, NewError(ErrMachineTerminating)
	}

	// now get the machine provider interface, it can DO, AWS, GCE, and so on..
	provider, err := k.GetProvider(m.Provider)
	if err != nil {
		return nil, err
	}

	return &Controller{
		MachineId:    args.MachineId,
		ImageName:    args.ImageName,
		InstanceName: args.InstanceName,
		Provider:     provider,
		MachineData:  m,
		CurrenState:  m.Machine.State(),
		Eventer:      k.NewEventer(r.Method + "-" + args.MachineId),
	}, nil
}

// coreMethods is running and returning the event id for the methods start,
// stop, restart and destroy. This method is used to avoid duplicate codes in
// start, stop, restart and destroy methods (because we do the same steps for
// each of them)
func (k *Kloud) coreMethods(
	r *kite.Request,
	c *Controller,
	fn func(*protocol.MachineOptions) error,
) (result interface{}, err error) {
	// if something goes wrong reset the assigne which was set in in
	// ControlFunc's Storage.Get method
	defer func() {
		if err != nil {
			k.Storage.ResetAssignee(c.MachineId)
		}
	}()

	// all core methods works only for machines that are initialized
	if c.CurrenState == machinestate.NotInitialized {
		return nil, NewError(ErrNotInitialized)
	}

	// get our state pair. A state pair defines the inital state and the final
	// state. For example, for "restart" method the initial state is
	// "rebooting" and the final "running.
	s, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}
	k.Storage.UpdateState(c.MachineId, s.initial)

	machOptions := &protocol.MachineOptions{
		MachineId:  c.MachineId,
		Username:   r.Username,
		Eventer:    c.Eventer,
		Credential: c.MachineData.Credential.Meta,
		Builder:    c.MachineData.Machine.Meta,
	}

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		k.idlock.Get(c.MachineId).Lock()
		defer k.idlock.Get(c.MachineId).Unlock()

		status := s.final
		msg := fmt.Sprintf("%s is finished successfully.", r.Method)

		k.Log.Info("[controller]: running method %s with mach options %v", r.Method, machOptions)
		err := fn(machOptions)
		if err != nil {
			k.Log.Error("[controller] %s failed: %s. Machine state is Unknown now.",
				r.Method, err.Error())

			status = s.initial
			msg = err.Error()
		} else {
			k.Log.Info("[%s] is successfull. State is now: %+v", r.Method, status)
		}

		k.Storage.UpdateState(c.MachineId, status)
		k.Storage.ResetAssignee(c.MachineId)
		c.Eventer.Push(&eventer.Event{
			Message:    msg,
			Status:     status,
			Percentage: 100,
		})
	}()

	return ControlResult{
		EventId: c.Eventer.Id(),
		State:   s.initial,
	}, nil
}

func (k *Kloud) start(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Start(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) stop(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Stop(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) destroy(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Destroy(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) restart(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Restart(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) info(r *kite.Request, c *Controller) (interface{}, error) {
	defer k.Storage.ResetAssignee(c.MachineId)

	if c.CurrenState == machinestate.NotInitialized {
		return nil, NewError(ErrNotInitialized)
	}

	machOptions := &protocol.MachineOptions{
		MachineId:  c.MachineId,
		Username:   r.Username,
		Eventer:    c.Eventer,
		Credential: c.MachineData.Credential.Meta,
		Builder:    c.MachineData.Machine.Meta,
	}

	info, err := c.Provider.Info(machOptions)
	if err != nil {
		return nil, err
	}

	response := &protocol.InfoResponse{State: info.State}
	if info.State == machinestate.Unknown {
		response.State = c.CurrenState
	}

	k.Storage.UpdateState(c.MachineId, response.State)

	k.Log.Info("[info] returning response %+v", response)
	return response, nil
}
<commit_msg>kloud/controller: fix info to be called for other states<commit_after>package kloud

import (
	"fmt"

	"koding/kites/kloud/eventer"
	"koding/kites/kloud/kloud/machinestate"
	"koding/kites/kloud/kloud/protocol"

	"github.com/koding/kite"
)

type Controller struct {
	// Incoming arguments
	MachineId    string
	ImageName    string
	InstanceName string

	// Populated later
	CurrenState machinestate.State `json:"-"`
	Provider    protocol.Provider  `json:"-"`
	MachineData *MachineData       `json:"-"`
	Eventer     eventer.Eventer    `json:"-"`
}

type controlFunc func(*kite.Request, *Controller) (interface{}, error)

type statePair struct {
	initial machinestate.State
	final   machinestate.State
}

var states = map[string]*statePair{
	"start":   &statePair{initial: machinestate.Starting, final: machinestate.Running},
	"stop":    &statePair{initial: machinestate.Stopping, final: machinestate.Stopped},
	"destroy": &statePair{initial: machinestate.Terminating, final: machinestate.Terminated},
	"restart": &statePair{initial: machinestate.Rebooting, final: machinestate.Running},
}

func (k *Kloud) ControlFunc(method string, control controlFunc) {
	handler := func(r *kite.Request) (interface{}, error) {
		// calls with zero arguments causes args to be nil. Check it that we
		// don't get a beloved panic
		if r.Args == nil {
			return nil, NewError(ErrNoArguments)
		}

		k.Log.Info("[controller] got a request for method: '%s' with args: %v",
			method, string(r.Args.Raw))

		// this locks are important to prevent consecutive calls from the same
		// user
		k.idlock.Get(r.Username).Lock()
		c, err := k.controller(r)
		if err != nil {
			k.idlock.Get(r.Username).Unlock()
			return nil, err
		}
		k.idlock.Get(r.Username).Unlock()

		// now lock for machine-ids
		k.idlock.Get(c.MachineId).Lock()
		defer k.idlock.Get(c.MachineId).Unlock()

		// call no our kite handler with the the controller context
		return control(r, c)
	}

	k.Kite.HandleFunc(method, handler)
}

// controller returns the Controller struct with all necessary entities
// responsible for the given machine Id. It also calls provider.Prepare before
// returning.
func (k *Kloud) controller(r *kite.Request) (contr *Controller, err error) {
	args := &Controller{}
	if err := r.Args.One().Unmarshal(args); err != nil {
		return nil, err
	}

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Geth all the data we need. It also sets the assignee for the given
	// machine id.
	m, err := k.Storage.Get(args.MachineId, &GetOption{
		IncludeMachine:    true,
		IncludeCredential: true,
	})
	if err != nil {
		return nil, err
	}

	// if something goes wrong reset the assigne which was set in previous step
	// by Storage.Get.
	defer func() {
		if err != nil {
			k.Storage.ResetAssignee(args.MachineId)
		}
	}()

	k.Log.Debug("[controller] got machine data with machineID (%s) : %#v",
		args.MachineId, m.Machine)

	// prevent request if the machine is terminated. However we want the user
	// to be able to build again or get information, therefore build and info
	// are not permitted.
	if (m.Machine.State() == machinestate.Terminating || m.Machine.State() == machinestate.Terminated) &&
		(r.Method != "build" || r.Method != "info") {
		return nil, NewError(ErrMachineTerminating)
	}

	// now get the machine provider interface, it can DO, AWS, GCE, and so on..
	provider, err := k.GetProvider(m.Provider)
	if err != nil {
		return nil, err
	}

	return &Controller{
		MachineId:    args.MachineId,
		ImageName:    args.ImageName,
		InstanceName: args.InstanceName,
		Provider:     provider,
		MachineData:  m,
		CurrenState:  m.Machine.State(),
		Eventer:      k.NewEventer(r.Method + "-" + args.MachineId),
	}, nil
}

// coreMethods is running and returning the event id for the methods start,
// stop, restart and destroy. This method is used to avoid duplicate codes in
// start, stop, restart and destroy methods (because we do the same steps for
// each of them)
func (k *Kloud) coreMethods(
	r *kite.Request,
	c *Controller,
	fn func(*protocol.MachineOptions) error,
) (result interface{}, err error) {
	// if something goes wrong reset the assigne which was set in in
	// ControlFunc's Storage.Get method
	defer func() {
		if err != nil {
			k.Storage.ResetAssignee(c.MachineId)
		}
	}()

	// all core methods works only for machines that are initialized
	if c.CurrenState == machinestate.NotInitialized {
		return nil, NewError(ErrNotInitialized)
	}

	// get our state pair. A state pair defines the inital state and the final
	// state. For example, for "restart" method the initial state is
	// "rebooting" and the final "running.
	s, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}
	k.Storage.UpdateState(c.MachineId, s.initial)

	machOptions := &protocol.MachineOptions{
		MachineId:  c.MachineId,
		Username:   r.Username,
		Eventer:    c.Eventer,
		Credential: c.MachineData.Credential.Meta,
		Builder:    c.MachineData.Machine.Meta,
	}

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		k.idlock.Get(c.MachineId).Lock()
		defer k.idlock.Get(c.MachineId).Unlock()

		status := s.final
		msg := fmt.Sprintf("%s is finished successfully.", r.Method)

		k.Log.Info("[controller]: running method %s with mach options %v", r.Method, machOptions)
		err := fn(machOptions)
		if err != nil {
			k.Log.Error("[controller] %s failed: %s. Machine state is Unknown now.",
				r.Method, err.Error())

			status = s.initial
			msg = err.Error()
		} else {
			k.Log.Info("[%s] is successfull. State is now: %+v", r.Method, status)
		}

		k.Storage.UpdateState(c.MachineId, status)
		k.Storage.ResetAssignee(c.MachineId)
		c.Eventer.Push(&eventer.Event{
			Message:    msg,
			Status:     status,
			Percentage: 100,
		})
	}()

	return ControlResult{
		EventId: c.Eventer.Id(),
		State:   s.initial,
	}, nil
}

func (k *Kloud) start(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Start(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) stop(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Stop(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) destroy(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Destroy(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) restart(r *kite.Request, c *Controller) (interface{}, error) {
	fn := func(m *protocol.MachineOptions) error {
		return c.Provider.Restart(m)
	}

	return k.coreMethods(r, c, fn)
}

func (k *Kloud) info(r *kite.Request, c *Controller) (interface{}, error) {
	defer k.Storage.ResetAssignee(c.MachineId)

	if c.CurrenState == machinestate.NotInitialized {
		return nil, NewError(ErrNotInitialized)
	}

	machOptions := &protocol.MachineOptions{
		MachineId:  c.MachineId,
		Username:   r.Username,
		Eventer:    c.Eventer,
		Credential: c.MachineData.Credential.Meta,
		Builder:    c.MachineData.Machine.Meta,
	}

	info, err := c.Provider.Info(machOptions)
	if err != nil {
		return nil, err
	}

	response := &protocol.InfoResponse{State: info.State}
	if info.State == machinestate.Unknown {
		response.State = c.CurrenState
	}

	k.Storage.UpdateState(c.MachineId, response.State)

	k.Log.Info("[info] returning response %+v", response)
	return response, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"time"

	latest "github.com/tcnksm/go-latest"
)

const (
	// AppName is the cli name
	AppName = "gist"

	defaultCheckTimeout = 2 * time.Second
)

// GitCommit is cli current git commit hash
var GitCommit string

// Config.
var version = "master"

// Version show the cli's current version
func Version() {
	version := fmt.Sprintf("\n%s %s", AppName, version)
	if len(GitCommit) != 0 {
		version += fmt.Sprintf(" (%s)", GitCommit)
	}
	version += "\nCopyright (c) 2017, zcong1993."
	fmt.Println(version)
	var buf bytes.Buffer
	verCheckCh := make(chan *latest.CheckResponse)
	go func() {
		fixFunc := latest.DeleteFrontV()
		githubTag := &latest.GithubTag{
			Owner:             "zcong1993",
			Repository:        "gist",
			FixVersionStrFunc: fixFunc,
		}

		res, err := latest.Check(githubTag, fixFunc(version))
		if err != nil {
			// Don't return error
			return
		}
		verCheckCh <- res
	}()

	select {
	case <-time.After(defaultCheckTimeout):
	case res := <-verCheckCh:
		if res.Outdated {
			fmt.Fprintf(&buf,
				"Latest version of gist is v%s, please upgrade!\n",
				res.Current)
		}
	}
	fmt.Print(buf.String())
}
<commit_msg>update check update<commit_after>package main

import (
	"bytes"
	"fmt"
	"time"

	latest "github.com/tcnksm/go-latest"
)

const (
	// AppName is the cli name
	AppName = "gist"

	defaultCheckTimeout = 2 * time.Second
)

// GitCommit is cli current git commit hash
var GitCommit string

// Config.
var version = "master"

// Version show the cli's current version
func Version() {
	version := fmt.Sprintf("\n%s %s", AppName, version)
	if len(GitCommit) != 0 {
		version += fmt.Sprintf(" (%s)", GitCommit)
	}
	version += "\nCopyright (c) 2017, zcong1993."
	fmt.Println(version)
	var buf bytes.Buffer
	verCheckCh := make(chan *latest.CheckResponse)
	go func() {
		fixFunc := latest.DeleteFrontV()
		githubTag := &latest.GithubTag{
			Owner:             "zcong1993",
			Repository:        "gist",
			FixVersionStrFunc: fixFunc,
		}

		res, err := latest.Check(githubTag, version)
		if err != nil {
			// Don't return error
			return
		}
		verCheckCh <- res
	}()

	select {
	case <-time.After(defaultCheckTimeout):
	case res := <-verCheckCh:
		if res.Outdated {
			fmt.Fprintf(&buf,
				"Latest version of gist is v%s, please upgrade!\n",
				res.Current)
		}
	}
	fmt.Print(buf.String())
}
<|endoftext|>


<commit_before>package dita

import (
	"encoding/xml"
	"fmt"
	"html"
	"path"
	"strings"

	"github.com/raintreeinc/ditaconvert"
	"github.com/raintreeinc/knowledgebase/kb"
)

type PageConversion struct {
	*Conversion
	Mapping *TitleMapping
	Slug    kb.Slug
	Index   *ditaconvert.Index
	Topic   *ditaconvert.Topic
	Context *ditaconvert.Context
}

func (conversion *PageConversion) Convert() (page *kb.Page, errs []error, fatal error) {
	conversion.Context = ditaconvert.NewConversion(conversion.Index, conversion.Topic)
	conversion.Context.Encoder.RewriteID = "data-id"

	context, topic := conversion.Context, conversion.Topic

	page = &kb.Page{
		Slug:     conversion.Slug,
		Title:    topic.Title,
		Modified: topic.Modified,
		Synopsis: topic.Synopsis,
	}

	context.Rules.Custom["a"] = conversion.ToSlug
	context.Rules.Custom["img"] = conversion.InlineImage
	context.Rules.Custom["imagemap"] = conversion.ConvertImageMap

	if err := context.Run(); err != nil {
		return page, nil, err
	}

	if tags := conversion.ConvertTags(); len(tags) > 0 {
		page.Story.Append(kb.Tags(tags...))
	}

	page.Story.Append(kb.HTML(context.Output.String()))
	page.Story.Append(kb.HTML(conversion.RelatedLinksAsHTML()))

	return page, context.Errors, nil
}

func (conversion *PageConversion) ConvertTags() []string {
	raw := conversion.Topic.Original.Prolog.Keywords.Terms()
	for _, key := range conversion.Topic.Original.Prolog.ResourceID {
		raw = append(raw, "id/"+key.Name)
	}

	tags := []string{}
	for _, tag := range raw {
		slug := string(kb.Slugify(tag))
		if slug == "" || slug == "-" {
			continue
		}
		tags = append(tags, tag)
	}

	return tags
}

func (conversion *PageConversion) ToSlug(context *ditaconvert.Context, dec *xml.Decoder, start xml.StartElement) error {
	var href, title, desc string
	var internal bool

	href = getAttr(&start, "href")
	if href != "" {
		href, title, desc, internal = conversion.ResolveLinkInfo(href)
		setAttr(&start, "href", href)
	}

	if desc != "" && getAttr(&start, "title") == "" {
		setAttr(&start, "title", desc)
	}

	setAttr(&start, "scope", "")
	if internal && href != "" {
		setAttr(&start, "data-link", href)
	}

	if !internal {
		if class := getAttr(&start, "class"); class != "" {
			setAttr(&start, "class", class+" external-link")
		} else {
			setAttr(&start, "class", "external-link")
		}
	}

	if getAttr(&start, "format") != "" && href != "" {
		setAttr(&start, "format", "")
		ext := strings.ToLower(path.Ext(href))
		if ext == ".doc" || ext == ".xml" || ext == ".rtf" || ext == ".zip" || ext == ".exe" {
			setAttr(&start, "download", path.Base(href))
		} else {
			setAttr(&start, "target", "_blank")
		}
	}
	// encode starting tag and attributes
	if err := context.Encoder.WriteStart("a", start.Attr...); err != nil {
		return err
	}

	// recurse on child tokens
	err, count := context.RecurseChildCount(dec)
	if err != nil {
		return err
	}
	if count == 0 {
		context.Encoder.WriteRaw(html.EscapeString(title))
	}
	return context.Encoder.WriteEnd("a")
}

func (conversion *PageConversion) InlineImage(context *ditaconvert.Context, dec *xml.Decoder, start xml.StartElement) error {
	href := getAttr(&start, "href")
	setAttr(&start, "src", context.InlinedImageURL(href))
	setAttr(&start, "href", "")

	placement := getAttr(&start, "placement")
	setAttr(&start, "placement", "")
	if placement == "break" {
		context.Encoder.WriteStart("p",
			xml.Attr{Name: xml.Name{Local: "class"}, Value: "image"})
	}

	err := context.EmitWithChildren(dec, start)

	if placement == "break" {
		context.Encoder.WriteEnd("p")
	}

	return err
}

func (conversion *PageConversion) ResolveLinkInfo(url string) (href, title, synopsis string, internal bool) {
	if strings.HasPrefix(url, "http:") || strings.HasPrefix(url, "https:") || strings.HasPrefix(url, "mailto:") {
		return url, "", "", false
	}
	context := conversion.Context

	var selector, hash string
	url, selector = ditaconvert.SplitLink(url)
	if selector != "" {
		hash = "#" + selector
	}

	if url == "" {
		return hash, "", "", true
	}

	name := context.DecodingPath
	if url != "" {
		name = path.Join(path.Dir(context.DecodingPath), url)
	}

	topic, ok := context.Index.Topics[ditaconvert.CanonicalPath(name)]
	if !ok {
		context.Errors = append(context.Errors,
			fmt.Errorf("did not find topic %v [%v%v]", name, url, selector))
		return "", "", "", false
	}

	if selector != "" {
		var err error
		title, err = ditaconvert.ExtractTitle(topic.Raw, selector)
		if err != nil {
			context.Errors = append(context.Errors,
				fmt.Errorf("unable to extract title from %v [%v%v]: %v", name, url, selector, err))
		}
	}

	if title == "" && topic.Original != nil {
		title = topic.Title
		if selector == "" {
			synopsis, _ = topic.Original.ShortDesc.Text()
		}
	}

	slug, ok := conversion.Mapping.ByTopic[topic]
	if !ok {
		return href, title, synopsis, false
	}

	return string(slug) + hash, title, synopsis, true
}
<commit_msg>Fix local xref titles/links.<commit_after>package dita

import (
	"encoding/xml"
	"fmt"
	"html"
	"path"
	"strings"

	"github.com/raintreeinc/ditaconvert"
	"github.com/raintreeinc/knowledgebase/kb"
)

type PageConversion struct {
	*Conversion
	Mapping *TitleMapping
	Slug    kb.Slug
	Index   *ditaconvert.Index
	Topic   *ditaconvert.Topic
	Context *ditaconvert.Context
}

func (conversion *PageConversion) Convert() (page *kb.Page, errs []error, fatal error) {
	conversion.Context = ditaconvert.NewConversion(conversion.Index, conversion.Topic)
	conversion.Context.Encoder.RewriteID = "data-id"

	context, topic := conversion.Context, conversion.Topic

	page = &kb.Page{
		Slug:     conversion.Slug,
		Title:    topic.Title,
		Modified: topic.Modified,
		Synopsis: topic.Synopsis,
	}

	context.Rules.Custom["a"] = conversion.ToSlug
	context.Rules.Custom["img"] = conversion.InlineImage
	context.Rules.Custom["imagemap"] = conversion.ConvertImageMap

	if err := context.Run(); err != nil {
		return page, nil, err
	}

	if tags := conversion.ConvertTags(); len(tags) > 0 {
		page.Story.Append(kb.Tags(tags...))
	}

	page.Story.Append(kb.HTML(context.Output.String()))
	page.Story.Append(kb.HTML(conversion.RelatedLinksAsHTML()))

	return page, context.Errors, nil
}

func (conversion *PageConversion) ConvertTags() []string {
	raw := conversion.Topic.Original.Prolog.Keywords.Terms()
	for _, key := range conversion.Topic.Original.Prolog.ResourceID {
		raw = append(raw, "id/"+key.Name)
	}

	tags := []string{}
	for _, tag := range raw {
		slug := string(kb.Slugify(tag))
		if slug == "" || slug == "-" {
			continue
		}
		tags = append(tags, tag)
	}

	return tags
}

func (conversion *PageConversion) ToSlug(context *ditaconvert.Context, dec *xml.Decoder, start xml.StartElement) error {
	var href, title, desc string
	var internal bool

	href = getAttr(&start, "href")
	if href != "" {
		href, title, desc, internal = conversion.ResolveLinkInfo(href)
		setAttr(&start, "href", href)
	}

	if desc != "" && getAttr(&start, "title") == "" {
		setAttr(&start, "title", desc)
	}

	setAttr(&start, "scope", "")
	if internal && href != "" {
		setAttr(&start, "data-link", href)
	}

	if !internal {
		if class := getAttr(&start, "class"); class != "" {
			setAttr(&start, "class", class+" external-link")
		} else {
			setAttr(&start, "class", "external-link")
		}
	}

	if getAttr(&start, "format") != "" && href != "" {
		setAttr(&start, "format", "")
		ext := strings.ToLower(path.Ext(href))
		if ext == ".doc" || ext == ".xml" || ext == ".rtf" || ext == ".zip" || ext == ".exe" {
			setAttr(&start, "download", path.Base(href))
		} else {
			setAttr(&start, "target", "_blank")
		}
	}
	// encode starting tag and attributes
	if err := context.Encoder.WriteStart("a", start.Attr...); err != nil {
		return err
	}

	// recurse on child tokens
	err, count := context.RecurseChildCount(dec)
	if err != nil {
		return err
	}
	if count == 0 {
		if title == "" {
			context.Errors = append(context.Errors, fmt.Errorf("unable to find title for %v", href))
		}
		context.Encoder.WriteRaw(html.EscapeString(title))
	}
	return context.Encoder.WriteEnd("a")
}

func (conversion *PageConversion) InlineImage(context *ditaconvert.Context, dec *xml.Decoder, start xml.StartElement) error {
	href := getAttr(&start, "href")
	setAttr(&start, "src", context.InlinedImageURL(href))
	setAttr(&start, "href", "")

	placement := getAttr(&start, "placement")
	setAttr(&start, "placement", "")
	if placement == "break" {
		context.Encoder.WriteStart("p",
			xml.Attr{Name: xml.Name{Local: "class"}, Value: "image"})
	}

	err := context.EmitWithChildren(dec, start)

	if placement == "break" {
		context.Encoder.WriteEnd("p")
	}

	return err
}

func (conversion *PageConversion) ResolveLinkInfo(url string) (href, title, synopsis string, internal bool) {
	if strings.HasPrefix(url, "http:") || strings.HasPrefix(url, "https:") || strings.HasPrefix(url, "mailto:") {
		return url, "", "", false
	}
	context := conversion.Context

	var selector, hash string
	url, selector = ditaconvert.SplitLink(url)
	if selector != "" {
		hash = "#" + selector
	}

	name := context.DecodingPath
	if url != "" {
		name = path.Join(path.Dir(context.DecodingPath), url)
	}

	topic, ok := context.Index.Topics[ditaconvert.CanonicalPath(name)]
	if !ok {
		context.Errors = append(context.Errors,
			fmt.Errorf("did not find topic %v [%v%v]", name, url, selector))
		return "", "", "", false
	}

	if selector != "" {
		var err error
		title, err = ditaconvert.ExtractTitle(topic.Raw, selector)
		if err != nil {
			context.Errors = append(context.Errors,
				fmt.Errorf("unable to extract title from %v [%v%v]: %v", name, url, selector, err))
		}
	}

	if title == "" && topic.Original != nil {
		title = topic.Title
		if selector == "" {
			synopsis, _ = topic.Original.ShortDesc.Text()
		}
	}

	slug, ok := conversion.Mapping.ByTopic[topic]
	if !ok {
		return href, title, synopsis, false
	}

	return string(slug) + hash, title, synopsis, true
}
<|endoftext|>


<commit_before>package tty

import (
	"fmt"
	"os"
	"time"

	"github.com/elves/elvish/edit/ui"
)

var (
	// EscSequenceTimeout is the amount of time within which runes that make up
	// an escape sequence are supposed to follow each other. Modern terminal
	// emulators send escape sequences very fast, so 10ms is more than
	// sufficient. SSH connections on a slow link might be problematic though.
	EscSequenceTimeout = 10 * time.Millisecond
)

// Special rune values used in the return value of (*Reader).ReadRune.
const (
	// No rune received before specified time.
	runeTimeout rune = -1 - iota
	// Error occurred in AsyncReader. The error is left at the readError field.
	runeReadError
)

// Reader converts a stream of events on separate channels.
type Reader struct {
	ar  *AsyncReader
	raw bool

	unitChan chan ReadUnit
	/*
		rawRuneChan chan rune
		keyChan     chan ui.Key
		cprChan     chan Pos
		mouseChan   chan MouseEvent
		pasteChan   chan bool
	*/
	errChan chan error
	quit    chan struct{}
}

type MouseEvent struct {
	Pos
	Down bool
	// Number of the Button, 0-based. -1 for unknown.
	Button int
	Mod    ui.Mod
}

// NewReader creates a new Reader on the given terminal file.
func NewReader(f *os.File) *Reader {
	rd := &Reader{
		NewAsyncReader(f),
		false,
		make(chan ReadUnit),
		/*
			make(chan rune),
			make(chan ui.Key),
			make(chan Pos),
			make(chan MouseEvent),
			make(chan bool),
		*/
		make(chan error),
		nil,
	}
	return rd
}

// SetRaw turns the raw option on or off. If the reader is in the middle of
// reading one event, it takes effect after this event is fully read.
func (rd *Reader) SetRaw(raw bool) {
	rd.raw = raw
}

// UnitChan returns the channel onto which the Reader writes what it has read.
func (rd *Reader) UnitChan() <-chan ReadUnit {
	return rd.unitChan
}

// ErrorChan returns the channel onto which the Reader writes errors it came
// across during the reading process.
func (rd *Reader) ErrorChan() <-chan error {
	return rd.errChan
}

// Run runs the Reader. It blocks until Quit is called and should be called in
// a separate goroutine.
func (rd *Reader) Run() {
	runes := rd.ar.Chan()
	quit := make(chan struct{})
	rd.quit = quit
	go rd.ar.Run()

	for {
		select {
		case r := <-runes:
			if rd.raw {
				rd.unitChan <- RawRune(r)
			} else {
				rd.readOne(r)
			}
		case <-quit:
			return
		}
	}
}

// Quit terminates the loop of Run.
func (rd *Reader) Quit() {
	rd.ar.Quit()
	close(rd.quit)
}

// Close releases files associated with the Reader. It does not close the file
// used to create it.
func (rd *Reader) Close() {
	rd.ar.Close()
}

// readOne attempts to read one key or CPR, led by a rune already read.
func (rd *Reader) readOne(r rune) {
	var unit ReadUnit
	var err error
	currentSeq := string(r)

	badSeq := func(msg string) {
		err = fmt.Errorf("%s: %q", msg, currentSeq)
	}

	// readRune attempts to read a rune within EscSequenceTimeout. It writes to
	// the err and currentSeq variable in the outer scope.
	readRune :=
		func() rune {
			select {
			case r := <-rd.ar.Chan():
				currentSeq += string(r)
				return r
			case err = <-rd.ar.ErrorChan():
				return runeReadError
			case <-time.After(EscSequenceTimeout):
				return runeTimeout
			}
		}

	defer func() {
		if unit != nil {
			select {
			case rd.unitChan <- unit:
			case <-rd.quit:
			}
		}
		if err != nil {
			select {
			case rd.errChan <- err:
			case <-rd.quit:
			}
		}
	}()

	switch r {
	case 0x1b: // ^[ Escape
		r2 := readRune()
		if r2 == runeTimeout || r2 == runeReadError {
			// Nothing follows. Taken as a lone Escape.
			unit = Key{'[', ui.Ctrl}
			break
		}
		switch r2 {
		case '[':
			// A '[' follows. CSI style function key sequence.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				unit = Key{'[', ui.Alt}
				return
			}

			nums := make([]int, 0, 2)
			var starter rune

			// Read an optional starter.
			switch r {
			case '<':
				starter = r
				r = readRune()
			case 'M':
				// Mouse event.
				cb := readRune()
				if cb == runeTimeout || cb == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cx := readRune()
				if cx == runeTimeout || cx == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cy := readRune()
				if cy == runeTimeout || cy == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				down := true
				button := int(cb & 3)
				if button == 3 {
					down = false
					button = -1
				}
				mod := mouseModify(int(cb))
				unit = MouseEvent{
					Pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
				return
			}
		CSISeq:
			for {
				switch {
				case r == ';':
					nums = append(nums, 0)
				case '0' <= r && r <= '9':
					if len(nums) == 0 {
						nums = append(nums, 0)
					}
					cur := len(nums) - 1
					nums[cur] = nums[cur]*10 + int(r-'0')
				case r == runeTimeout:
					// Incomplete CSI.
					badSeq("Incomplete CSI")
					return
				case r == runeReadError:
					// TODO Also complain about incomplte CSI.
					return
				default: // Treat as a terminator.
					break CSISeq
				}

				r = readRune()
			}
			if starter == 0 && r == 'R' {
				// Cursor position report.
				if len(nums) != 2 {
					badSeq("bad CPR")
					return
				}
				unit = CursorPosition{nums[0], nums[1]}
			} else if starter == '<' && (r == 'm' || r == 'M') {
				// SGR-style mouse event.
				if len(nums) != 3 {
					badSeq("bad SGR mouse event")
					return
				}
				down := r == 'M'
				button := nums[0] & 3
				mod := mouseModify(nums[0])
				unit = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod}
			} else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) {
				b := nums[0] == 200
				unit = PasteSetting(b)
			} else {
				k := parseCSI(nums, r, currentSeq)
				if k == (ui.Key{}) {
					badSeq("bad CSI")
				} else {
					unit = Key(k)
				}
			}
		case 'O':
			// An 'O' follows. G3 style function key sequence: read one rune.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				// Nothing follows after 'O'. Taken as ui.Alt-o.
				unit = Key{'o', ui.Alt}
				return
			}
			r, ok := g3Seq[r]
			if ok {
				unit = Key{r, 0}
			} else {
				badSeq("bad G3")
			}
		default:
			// Something other than '[' or 'O' follows. Taken as an
			// ui.Alt-modified key, possibly also modified by ui.Ctrl.
			k := ctrlModify(r2)
			k.Mod |= ui.Alt
			unit = Key(k)
		}
	default:
		k := ctrlModify(r)
		unit = Key(k)
	}
}

// ctrlModify determines whether a rune corresponds to a ui.Ctrl-modified key and
// returns the ui.Key the rune represents.
func ctrlModify(r rune) ui.Key {
	switch r {
	case 0x0:
		return ui.Key{'`', ui.Ctrl} // ^@
	case 0x1e:
		return ui.Key{'6', ui.Ctrl} // ^^
	case 0x1f:
		return ui.Key{'/', ui.Ctrl} // ^_
	case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?
		return ui.Key{r, 0}
	default:
		// Regular ui.Ctrl sequences.
		if 0x1 <= r && r <= 0x1d {
			return ui.Key{r + 0x40, ui.Ctrl}
		}
	}
	return ui.Key{r, 0}
}

// G3-style key sequences: \eO followed by exactly one character. For instance,
// \eOP is ui.F1.
var g3Seq = map[rune]rune{
	'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left,

	// ui.F1-ui.F4: xterm, libvte and tmux
	'P': ui.F1, 'Q': ui.F2,
	'R': ui.F3, 'S': ui.F4,

	// ui.Home and ui.End: libvte
	'H': ui.Home, 'F': ui.End,
}

// Tables for CSI-style key sequences, which are \e[ followed by a list of
// semicolon-delimited numeric arguments, before being concluded by a
// non-numeric, non-semicolon rune.

// CSI-style key sequences that can be identified based on the ending rune. For
// instance, \e[A is ui.Up.
var keyByLast = map[rune]ui.Key{
	'A': {ui.Up, 0}, 'B': {ui.Down, 0},
	'C': {ui.Right, 0}, 'D': {ui.Left, 0},
	'H': {ui.Home, 0}, 'F': {ui.End, 0},
	'Z': {ui.Tab, ui.Shift},
}

// CSI-style key sequences ending with '~' and can be identified based on the
// only number argument. For instance, \e[~ is ui.Home. When they are
// modified, they take two arguments, first being 1 and second identifying the
// modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home.
var keyByNum0 = map[int]rune{
	1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End,
	5: ui.PageUp, 6: ui.PageDown,
	11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,
	15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,
	20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,
}

// CSI-style key sequences ending with '~', with 27 as the first numeric
// argument. For instance, \e[27;9~ is ui.Tab.
//
// The list is taken blindly from tmux source xterm-keys.c. I don't have a
// keyboard-terminal combination that generate such sequences, but assumably
// some PC keyboard with a numpad can.
var keyByNum2 = map[int]rune{
	9: '\t', 13: '\r',
	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
	46: '.',
	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
	56: '8', 57: '9',
	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
}

// parseCSI parses a CSI-style key sequence.
func parseCSI(nums []int, last rune, seq string) ui.Key {
	if k, ok := keyByLast[last]; ok {
		if len(nums) == 0 {
			// Unmodified: \e[A (ui.Up)
			return k
		} else if len(nums) == 2 && nums[0] == 1 {
			// Modified: \e[1;5A (ui.Ctrl-ui.Up)
			return xtermModify(k, nums[1], seq)
		} else {
			return ui.Key{}
		}
	}

	if last == '~' {
		if len(nums) == 1 || len(nums) == 2 {
			if r, ok := keyByNum0[nums[0]]; ok {
				k := ui.Key{r, 0}
				if len(nums) == 1 {
					// Unmodified: \e[5~ (ui.PageUp)
					return k
				}
				// Modified: \e[5;5~ (ui.Ctrl-ui.PageUp)
				return xtermModify(k, nums[1], seq)
			}
		} else if len(nums) == 3 && nums[0] == 27 {
			if r, ok := keyByNum2[nums[2]]; ok {
				k := ui.Key{r, 0}
				return xtermModify(k, nums[1], seq)
			}
		}
	}

	return ui.Key{}
}

func xtermModify(k ui.Key, mod int, seq string) ui.Key {
	switch mod {
	case 0:
		// do nothing
	case 2:
		k.Mod |= ui.Shift
	case 3:
		k.Mod |= ui.Alt
	case 4:
		k.Mod |= ui.Shift | ui.Alt
	case 5:
		k.Mod |= ui.Ctrl
	case 6:
		k.Mod |= ui.Shift | ui.Ctrl
	case 7:
		k.Mod |= ui.Alt | ui.Ctrl
	case 8:
		k.Mod |= ui.Shift | ui.Alt | ui.Ctrl
	default:
		return ui.Key{}
	}
	return k
}

func mouseModify(n int) ui.Mod {
	var mod ui.Mod
	if n&4 != 0 {
		mod |= ui.Shift
	}
	if n&8 != 0 {
		mod |= ui.Alt
	}
	if n&16 != 0 {
		mod |= ui.Ctrl
	}
	return mod
}
<commit_msg>edit/tty: In the loop of Reader.Run, pass error from asyncReader.<commit_after>package tty

import (
	"fmt"
	"os"
	"time"

	"github.com/elves/elvish/edit/ui"
)

var (
	// EscSequenceTimeout is the amount of time within which runes that make up
	// an escape sequence are supposed to follow each other. Modern terminal
	// emulators send escape sequences very fast, so 10ms is more than
	// sufficient. SSH connections on a slow link might be problematic though.
	EscSequenceTimeout = 10 * time.Millisecond
)

// Special rune values used in the return value of (*Reader).ReadRune.
const (
	// No rune received before specified time.
	runeTimeout rune = -1 - iota
	// Error occurred in AsyncReader. The error is left at the readError field.
	runeReadError
)

// Reader converts a stream of events on separate channels.
type Reader struct {
	ar  *AsyncReader
	raw bool

	unitChan chan ReadUnit
	errChan  chan error
	quit     chan struct{}
}

type MouseEvent struct {
	Pos
	Down bool
	// Number of the Button, 0-based. -1 for unknown.
	Button int
	Mod    ui.Mod
}

// NewReader creates a new Reader on the given terminal file.
func NewReader(f *os.File) *Reader {
	rd := &Reader{
		NewAsyncReader(f),
		false,
		make(chan ReadUnit),
		make(chan error),
		nil,
	}
	return rd
}

// SetRaw turns the raw option on or off. If the reader is in the middle of
// reading one event, it takes effect after this event is fully read.
func (rd *Reader) SetRaw(raw bool) {
	rd.raw = raw
}

// UnitChan returns the channel onto which the Reader writes what it has read.
func (rd *Reader) UnitChan() <-chan ReadUnit {
	return rd.unitChan
}

// ErrorChan returns the channel onto which the Reader writes errors it came
// across during the reading process.
func (rd *Reader) ErrorChan() <-chan error {
	return rd.errChan
}

// Run runs the Reader. It blocks until Quit is called and should be called in
// a separate goroutine.
func (rd *Reader) Run() {
	quit := make(chan struct{})
	rd.quit = quit
	go rd.ar.Run()

	for {
		select {
		case r := <-rd.ar.Chan():
			if rd.raw {
				rd.unitChan <- RawRune(r)
			} else {
				rd.readOne(r)
			}
		case err := <-rd.ar.ErrorChan():
			rd.errChan <- err
		case <-quit:
			return
		}
	}
}

// Quit terminates the loop of Run.
func (rd *Reader) Quit() {
	rd.ar.Quit()
	close(rd.quit)
}

// Close releases files associated with the Reader. It does not close the file
// used to create it.
func (rd *Reader) Close() {
	rd.ar.Close()
}

// readOne attempts to read one key or CPR, led by a rune already read.
func (rd *Reader) readOne(r rune) {
	var unit ReadUnit
	var err error
	currentSeq := string(r)

	badSeq := func(msg string) {
		err = fmt.Errorf("%s: %q", msg, currentSeq)
	}

	// readRune attempts to read a rune within EscSequenceTimeout. It writes to
	// the err and currentSeq variable in the outer scope.
	readRune :=
		func() rune {
			select {
			case r := <-rd.ar.Chan():
				currentSeq += string(r)
				return r
			case err = <-rd.ar.ErrorChan():
				return runeReadError
			case <-time.After(EscSequenceTimeout):
				return runeTimeout
			}
		}

	defer func() {
		if unit != nil {
			select {
			case rd.unitChan <- unit:
			case <-rd.quit:
			}
		}
		if err != nil {
			select {
			case rd.errChan <- err:
			case <-rd.quit:
			}
		}
	}()

	switch r {
	case 0x1b: // ^[ Escape
		r2 := readRune()
		if r2 == runeTimeout || r2 == runeReadError {
			// Nothing follows. Taken as a lone Escape.
			unit = Key{'[', ui.Ctrl}
			break
		}
		switch r2 {
		case '[':
			// A '[' follows. CSI style function key sequence.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				unit = Key{'[', ui.Alt}
				return
			}

			nums := make([]int, 0, 2)
			var starter rune

			// Read an optional starter.
			switch r {
			case '<':
				starter = r
				r = readRune()
			case 'M':
				// Mouse event.
				cb := readRune()
				if cb == runeTimeout || cb == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cx := readRune()
				if cx == runeTimeout || cx == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cy := readRune()
				if cy == runeTimeout || cy == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				down := true
				button := int(cb & 3)
				if button == 3 {
					down = false
					button = -1
				}
				mod := mouseModify(int(cb))
				unit = MouseEvent{
					Pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
				return
			}
		CSISeq:
			for {
				switch {
				case r == ';':
					nums = append(nums, 0)
				case '0' <= r && r <= '9':
					if len(nums) == 0 {
						nums = append(nums, 0)
					}
					cur := len(nums) - 1
					nums[cur] = nums[cur]*10 + int(r-'0')
				case r == runeTimeout:
					// Incomplete CSI.
					badSeq("Incomplete CSI")
					return
				case r == runeReadError:
					// TODO Also complain about incomplte CSI.
					return
				default: // Treat as a terminator.
					break CSISeq
				}

				r = readRune()
			}
			if starter == 0 && r == 'R' {
				// Cursor position report.
				if len(nums) != 2 {
					badSeq("bad CPR")
					return
				}
				unit = CursorPosition{nums[0], nums[1]}
			} else if starter == '<' && (r == 'm' || r == 'M') {
				// SGR-style mouse event.
				if len(nums) != 3 {
					badSeq("bad SGR mouse event")
					return
				}
				down := r == 'M'
				button := nums[0] & 3
				mod := mouseModify(nums[0])
				unit = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod}
			} else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) {
				b := nums[0] == 200
				unit = PasteSetting(b)
			} else {
				k := parseCSI(nums, r, currentSeq)
				if k == (ui.Key{}) {
					badSeq("bad CSI")
				} else {
					unit = Key(k)
				}
			}
		case 'O':
			// An 'O' follows. G3 style function key sequence: read one rune.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				// Nothing follows after 'O'. Taken as ui.Alt-o.
				unit = Key{'o', ui.Alt}
				return
			}
			r, ok := g3Seq[r]
			if ok {
				unit = Key{r, 0}
			} else {
				badSeq("bad G3")
			}
		default:
			// Something other than '[' or 'O' follows. Taken as an
			// ui.Alt-modified key, possibly also modified by ui.Ctrl.
			k := ctrlModify(r2)
			k.Mod |= ui.Alt
			unit = Key(k)
		}
	default:
		k := ctrlModify(r)
		unit = Key(k)
	}
}

// ctrlModify determines whether a rune corresponds to a ui.Ctrl-modified key and
// returns the ui.Key the rune represents.
func ctrlModify(r rune) ui.Key {
	switch r {
	case 0x0:
		return ui.Key{'`', ui.Ctrl} // ^@
	case 0x1e:
		return ui.Key{'6', ui.Ctrl} // ^^
	case 0x1f:
		return ui.Key{'/', ui.Ctrl} // ^_
	case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?
		return ui.Key{r, 0}
	default:
		// Regular ui.Ctrl sequences.
		if 0x1 <= r && r <= 0x1d {
			return ui.Key{r + 0x40, ui.Ctrl}
		}
	}
	return ui.Key{r, 0}
}

// G3-style key sequences: \eO followed by exactly one character. For instance,
// \eOP is ui.F1.
var g3Seq = map[rune]rune{
	'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left,

	// ui.F1-ui.F4: xterm, libvte and tmux
	'P': ui.F1, 'Q': ui.F2,
	'R': ui.F3, 'S': ui.F4,

	// ui.Home and ui.End: libvte
	'H': ui.Home, 'F': ui.End,
}

// Tables for CSI-style key sequences, which are \e[ followed by a list of
// semicolon-delimited numeric arguments, before being concluded by a
// non-numeric, non-semicolon rune.

// CSI-style key sequences that can be identified based on the ending rune. For
// instance, \e[A is ui.Up.
var keyByLast = map[rune]ui.Key{
	'A': {ui.Up, 0}, 'B': {ui.Down, 0},
	'C': {ui.Right, 0}, 'D': {ui.Left, 0},
	'H': {ui.Home, 0}, 'F': {ui.End, 0},
	'Z': {ui.Tab, ui.Shift},
}

// CSI-style key sequences ending with '~' and can be identified based on the
// only number argument. For instance, \e[~ is ui.Home. When they are
// modified, they take two arguments, first being 1 and second identifying the
// modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home.
var keyByNum0 = map[int]rune{
	1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End,
	5: ui.PageUp, 6: ui.PageDown,
	11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,
	15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,
	20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,
}

// CSI-style key sequences ending with '~', with 27 as the first numeric
// argument. For instance, \e[27;9~ is ui.Tab.
//
// The list is taken blindly from tmux source xterm-keys.c. I don't have a
// keyboard-terminal combination that generate such sequences, but assumably
// some PC keyboard with a numpad can.
var keyByNum2 = map[int]rune{
	9: '\t', 13: '\r',
	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
	46: '.',
	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
	56: '8', 57: '9',
	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
}

// parseCSI parses a CSI-style key sequence.
func parseCSI(nums []int, last rune, seq string) ui.Key {
	if k, ok := keyByLast[last]; ok {
		if len(nums) == 0 {
			// Unmodified: \e[A (ui.Up)
			return k
		} else if len(nums) == 2 && nums[0] == 1 {
			// Modified: \e[1;5A (ui.Ctrl-ui.Up)
			return xtermModify(k, nums[1], seq)
		} else {
			return ui.Key{}
		}
	}

	if last == '~' {
		if len(nums) == 1 || len(nums) == 2 {
			if r, ok := keyByNum0[nums[0]]; ok {
				k := ui.Key{r, 0}
				if len(nums) == 1 {
					// Unmodified: \e[5~ (ui.PageUp)
					return k
				}
				// Modified: \e[5;5~ (ui.Ctrl-ui.PageUp)
				return xtermModify(k, nums[1], seq)
			}
		} else if len(nums) == 3 && nums[0] == 27 {
			if r, ok := keyByNum2[nums[2]]; ok {
				k := ui.Key{r, 0}
				return xtermModify(k, nums[1], seq)
			}
		}
	}

	return ui.Key{}
}

func xtermModify(k ui.Key, mod int, seq string) ui.Key {
	switch mod {
	case 0:
		// do nothing
	case 2:
		k.Mod |= ui.Shift
	case 3:
		k.Mod |= ui.Alt
	case 4:
		k.Mod |= ui.Shift | ui.Alt
	case 5:
		k.Mod |= ui.Ctrl
	case 6:
		k.Mod |= ui.Shift | ui.Ctrl
	case 7:
		k.Mod |= ui.Alt | ui.Ctrl
	case 8:
		k.Mod |= ui.Shift | ui.Alt | ui.Ctrl
	default:
		return ui.Key{}
	}
	return k
}

func mouseModify(n int) ui.Mod {
	var mod ui.Mod
	if n&4 != 0 {
		mod |= ui.Shift
	}
	if n&8 != 0 {
		mod |= ui.Alt
	}
	if n&16 != 0 {
		mod |= ui.Ctrl
	}
	return mod
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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 alloydb

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

	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
	htransport "google.golang.org/api/transport/http"
)

type InstanceGetResponse struct {
	ServerResponse googleapi.ServerResponse
	Name           string `json:"name"`
	State          string `json:"state"`
	IPAddress      string `json:"ipAddress"`
}

type GenerateClientCertificateRequest struct {
	PemCSR string `json:"pemCsr"`
}

type GenerateClientCertificateResponse struct {
	ServerResponse      googleapi.ServerResponse
	PemCertificate      string   `json:"pemCertificate"`
	PemCertificateChain []string `json:"pemCertificateChain"`
}

type Client struct {
	client *http.Client
	// endpoint is the base URL for the AlloyDB admin API (e.g. https://alloydb.googleapis.com/v1)
	endpoint string
}

func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
	var os []option.ClientOption
	os = append(opts, option.WithScopes(
		"https://www.googleapis.com/auth/cloud-platform",
	))
	client, endpoint, err := htransport.NewClient(ctx, os...)
	if err != nil {
		return nil, err
	}
	return &Client{client: client, endpoint: endpoint}, nil
}

func (c *Client) InstanceGet(ctx context.Context, project, region, cluster, instance string) (InstanceGetResponse, error) {
	u := fmt.Sprintf(
		"%s/v1alpha1/projects/%s/locations/%s/clusters/%s/instances/%s",
		c.endpoint, project, region, cluster, instance,
	)
	req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
	if err != nil {
		return InstanceGetResponse{}, err
	}
	res, err := c.client.Do(req)
	if err != nil {
		return InstanceGetResponse{}, err
	}
	if res != nil && res.StatusCode == http.StatusNotModified {
		var body []byte
		if res.Body != nil {
			defer res.Body.Close()
			body, err = ioutil.ReadAll(res.Body)
			if err != nil {
				return InstanceGetResponse{}, err
			}
		}

		return InstanceGetResponse{}, &googleapi.Error{
			Code:   res.StatusCode,
			Header: res.Header,
			Body:   string(body),
		}
	}
	if err != nil {
		return InstanceGetResponse{}, err
	}
	defer res.Body.Close()
	ret := InstanceGetResponse{
		ServerResponse: googleapi.ServerResponse{
			Header:         res.Header,
			HTTPStatusCode: res.StatusCode,
		},
	}
	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
		return InstanceGetResponse{}, err
	}
	return ret, nil
}

func (c *Client) GenerateClientCert(ctx context.Context, project, region, cluster string, csr []byte) (GenerateClientCertificateResponse, error) {
	u := fmt.Sprintf(
		"%s/v1alpha1/projects/%s/locations/%s/clusters/%s:generateClientCertificate",
		c.endpoint, project, region, cluster,
	)
	body, err := json.Marshal(GenerateClientCertificateRequest{PemCSR: string(csr)})
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(body))
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	res, err := c.client.Do(req.WithContext(ctx))
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	defer res.Body.Close()
	ret := GenerateClientCertificateResponse{
		ServerResponse: googleapi.ServerResponse{
			Header:         res.Header,
			HTTPStatusCode: res.StatusCode,
		},
	}
	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	return ret, nil
}
<commit_msg>Formatting<commit_after>// Copyright 2022 Google LLC
//
// 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 alloydb

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

	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
	htransport "google.golang.org/api/transport/http"
)

type InstanceGetResponse struct {
	ServerResponse googleapi.ServerResponse
	Name           string `json:"name"`
	State          string `json:"state"`
	IPAddress      string `json:"ipAddress"`
}

type GenerateClientCertificateRequest struct {
	PemCSR string `json:"pemCsr"`
}

type GenerateClientCertificateResponse struct {
	ServerResponse      googleapi.ServerResponse
	PemCertificate      string   `json:"pemCertificate"`
	PemCertificateChain []string `json:"pemCertificateChain"`
}

type Client struct {
	client *http.Client
	// endpoint is the base URL for the AlloyDB admin API (e.g.
	// https://alloydb.googleapis.com/v1)
	endpoint string
}

func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
	var os []option.ClientOption
	os = append(opts, option.WithScopes(
		"https://www.googleapis.com/auth/cloud-platform",
	))
	client, endpoint, err := htransport.NewClient(ctx, os...)
	if err != nil {
		return nil, err
	}
	return &Client{client: client, endpoint: endpoint}, nil
}

func (c *Client) InstanceGet(ctx context.Context, project, region, cluster, instance string) (InstanceGetResponse, error) {
	u := fmt.Sprintf(
		"%s/v1alpha1/projects/%s/locations/%s/clusters/%s/instances/%s",
		c.endpoint, project, region, cluster, instance,
	)
	req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
	if err != nil {
		return InstanceGetResponse{}, err
	}
	res, err := c.client.Do(req)
	if err != nil {
		return InstanceGetResponse{}, err
	}
	if res != nil && res.StatusCode == http.StatusNotModified {
		var body []byte
		if res.Body != nil {
			defer res.Body.Close()
			body, err = ioutil.ReadAll(res.Body)
			if err != nil {
				return InstanceGetResponse{}, err
			}
		}

		return InstanceGetResponse{}, &googleapi.Error{
			Code:   res.StatusCode,
			Header: res.Header,
			Body:   string(body),
		}
	}
	if err != nil {
		return InstanceGetResponse{}, err
	}
	defer res.Body.Close()
	ret := InstanceGetResponse{
		ServerResponse: googleapi.ServerResponse{
			Header:         res.Header,
			HTTPStatusCode: res.StatusCode,
		},
	}
	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
		return InstanceGetResponse{}, err
	}
	return ret, nil
}

func (c *Client) GenerateClientCert(ctx context.Context, project, region, cluster string, csr []byte) (GenerateClientCertificateResponse, error) {
	u := fmt.Sprintf(
		"%s/v1alpha1/projects/%s/locations/%s/clusters/%s:generateClientCertificate",
		c.endpoint, project, region, cluster,
	)
	body, err := json.Marshal(GenerateClientCertificateRequest{PemCSR: string(csr)})
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	req, err := http.NewRequestWithContext(ctx, "POST", u, bytes.NewReader(body))
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	res, err := c.client.Do(req.WithContext(ctx))
	if err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	defer res.Body.Close()
	ret := GenerateClientCertificateResponse{
		ServerResponse: googleapi.ServerResponse{
			Header:         res.Header,
			HTTPStatusCode: res.StatusCode,
		},
	}
	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
		return GenerateClientCertificateResponse{}, err
	}
	return ret, nil
}
<|endoftext|>


<commit_before>package httpexpect

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"

	"github.com/valyala/fasthttp"
)

// Binder implements networkless http.RoundTripper attached directly to
// http.Handler.
//
// Binder emulates network communication by invoking given http.Handler
// directly. It passes httptest.ResponseRecorder as http.ResponseWriter
// to the handler, and then constructs http.Response from recorded data.
type Binder struct {
	// HTTP handler invoked for every request.
	Handler http.Handler
	// TLS connection state used for https:// requests.
	TLS *tls.ConnectionState
}

// NewBinder returns a new Binder given a http.Handler.
//
// Example:
//   client := &http.Client{
//       Transport: NewBinder(handler),
//   }
func NewBinder(handler http.Handler) Binder {
	return Binder{Handler: handler}
}

// RoundTrip implements http.RoundTripper.RoundTrip.
func (binder Binder) RoundTrip(req *http.Request) (*http.Response, error) {
	if req.Proto == "" {
		req.Proto = fmt.Sprintf("HTTP/%d.%d", req.ProtoMajor, req.ProtoMinor)
	}

	if req.Body != nil {
		if req.ContentLength == -1 {
			req.TransferEncoding = []string{"chunked"}
		}
	} else {
		req.Body = ioutil.NopCloser(bytes.NewReader(nil))
	}

	if req.URL != nil && req.URL.Scheme == "https" && binder.TLS != nil {
		req.TLS = binder.TLS
	}

	if req.RequestURI == "" {
		req.RequestURI = req.URL.RequestURI()
	}

	recorder := httptest.NewRecorder()

	binder.Handler.ServeHTTP(recorder, req)

	resp := http.Response{
		Request:    req,
		StatusCode: recorder.Code,
		Status:     http.StatusText(recorder.Code),
		Header:     recorder.HeaderMap,
	}

	if recorder.Flushed {
		resp.TransferEncoding = []string{"chunked"}
	}

	if recorder.Body != nil {
		resp.Body = ioutil.NopCloser(recorder.Body)
	}

	return &resp, nil
}

// FastBinder implements networkless http.RoundTripper attached directly
// to fasthttp.RequestHandler.
//
// FastBinder emulates network communication by invoking given fasthttp.RequestHandler
// directly. It converts http.Request to fasthttp.Request, invokes handler, and then
// converts fasthttp.Response to http.Response.
type FastBinder struct {
	// FastHTTP handler invoked for every request.
	Handler fasthttp.RequestHandler
	// TLS connection state used for https:// requests.
	TLS *tls.ConnectionState
}

// NewFastBinder returns a new FastBinder given a fasthttp.RequestHandler.
//
// Example:
//   client := &http.Client{
//       Transport: NewFastBinder(fasthandler),
//   }
func NewFastBinder(handler fasthttp.RequestHandler) FastBinder {
	return FastBinder{Handler: handler}
}

// RoundTrip implements http.RoundTripper.RoundTrip.
func (binder FastBinder) RoundTrip(stdreq *http.Request) (*http.Response, error) {
	fastreq := std2fast(stdreq)

	var conn net.Conn
	if stdreq.URL != nil && stdreq.URL.Scheme == "https" && binder.TLS != nil {
		conn = connTLS{state: binder.TLS}
	} else {
		conn = connNonTLS{}
	}

	ctx := fasthttp.RequestCtx{}
	ctx.Init2(conn, fastLogger{}, true)
	fastreq.CopyTo(&ctx.Request)

	if stdreq.ContentLength >= 0 {
		ctx.Request.Header.SetContentLength(int(stdreq.ContentLength))
	} else {
		ctx.Request.Header.Add("Transfer-Encoding", "chunked")
	}

	if stdreq.Body != nil {
		b, err := ioutil.ReadAll(stdreq.Body)
		if err == nil {
			ctx.Request.SetBody(b)
		}
	}

	binder.Handler(&ctx)

	return fast2std(stdreq, &ctx.Response), nil
}

func std2fast(stdreq *http.Request) *fasthttp.Request {
	fastreq := &fasthttp.Request{}
	fastreq.SetRequestURI(stdreq.URL.String())

	fastreq.Header.SetMethod(stdreq.Method)

	for k, a := range stdreq.Header {
		for n, v := range a {
			if n == 0 {
				fastreq.Header.Set(k, v)
			} else {
				fastreq.Header.Add(k, v)
			}
		}
	}

	return fastreq
}

func fast2std(stdreq *http.Request, fastresp *fasthttp.Response) *http.Response {
	status := fastresp.Header.StatusCode()
	body := fastresp.Body()

	stdresp := &http.Response{
		Request:    stdreq,
		StatusCode: status,
		Status:     http.StatusText(status),
	}

	fastresp.Header.VisitAll(func(k, v []byte) {
		sk := string(k)
		sv := string(v)
		if stdresp.Header == nil {
			stdresp.Header = make(http.Header)
		}
		stdresp.Header.Add(sk, sv)
	})

	if fastresp.Header.ContentLength() == -1 {
		stdresp.TransferEncoding = []string{"chunked"}
	}

	if body != nil {
		stdresp.Body = ioutil.NopCloser(bytes.NewReader(body))
	} else {
		stdresp.Body = ioutil.NopCloser(bytes.NewReader(nil))
	}

	return stdresp
}

type fastLogger struct{}

func (fastLogger) Printf(format string, args ...interface{}) {
	_, _ = format, args
}

type connNonTLS struct {
	net.Conn
}

func (connNonTLS) RemoteAddr() net.Addr {
	return &net.TCPAddr{IP: net.IPv4zero}
}

func (connNonTLS) LocalAddr() net.Addr {
	return &net.TCPAddr{IP: net.IPv4zero}
}

type connTLS struct {
	connNonTLS
	state *tls.ConnectionState
}

func (c connTLS) ConnectionState() tls.ConnectionState {
	return *c.state
}
<commit_msg>Fix fasthttp TLS support<commit_after>package httpexpect

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"

	"github.com/valyala/fasthttp"
)

// Binder implements networkless http.RoundTripper attached directly to
// http.Handler.
//
// Binder emulates network communication by invoking given http.Handler
// directly. It passes httptest.ResponseRecorder as http.ResponseWriter
// to the handler, and then constructs http.Response from recorded data.
type Binder struct {
	// HTTP handler invoked for every request.
	Handler http.Handler
	// TLS connection state used for https:// requests.
	TLS *tls.ConnectionState
}

// NewBinder returns a new Binder given a http.Handler.
//
// Example:
//   client := &http.Client{
//       Transport: NewBinder(handler),
//   }
func NewBinder(handler http.Handler) Binder {
	return Binder{Handler: handler}
}

// RoundTrip implements http.RoundTripper.RoundTrip.
func (binder Binder) RoundTrip(req *http.Request) (*http.Response, error) {
	if req.Proto == "" {
		req.Proto = fmt.Sprintf("HTTP/%d.%d", req.ProtoMajor, req.ProtoMinor)
	}

	if req.Body != nil {
		if req.ContentLength == -1 {
			req.TransferEncoding = []string{"chunked"}
		}
	} else {
		req.Body = ioutil.NopCloser(bytes.NewReader(nil))
	}

	if req.URL != nil && req.URL.Scheme == "https" && binder.TLS != nil {
		req.TLS = binder.TLS
	}

	if req.RequestURI == "" {
		req.RequestURI = req.URL.RequestURI()
	}

	recorder := httptest.NewRecorder()

	binder.Handler.ServeHTTP(recorder, req)

	resp := http.Response{
		Request:    req,
		StatusCode: recorder.Code,
		Status:     http.StatusText(recorder.Code),
		Header:     recorder.HeaderMap,
	}

	if recorder.Flushed {
		resp.TransferEncoding = []string{"chunked"}
	}

	if recorder.Body != nil {
		resp.Body = ioutil.NopCloser(recorder.Body)
	}

	return &resp, nil
}

// FastBinder implements networkless http.RoundTripper attached directly
// to fasthttp.RequestHandler.
//
// FastBinder emulates network communication by invoking given fasthttp.RequestHandler
// directly. It converts http.Request to fasthttp.Request, invokes handler, and then
// converts fasthttp.Response to http.Response.
type FastBinder struct {
	// FastHTTP handler invoked for every request.
	Handler fasthttp.RequestHandler
	// TLS connection state used for https:// requests.
	TLS *tls.ConnectionState
}

// NewFastBinder returns a new FastBinder given a fasthttp.RequestHandler.
//
// Example:
//   client := &http.Client{
//       Transport: NewFastBinder(fasthandler),
//   }
func NewFastBinder(handler fasthttp.RequestHandler) FastBinder {
	return FastBinder{Handler: handler}
}

// RoundTrip implements http.RoundTripper.RoundTrip.
func (binder FastBinder) RoundTrip(stdreq *http.Request) (*http.Response, error) {
	fastreq := std2fast(stdreq)

	var conn net.Conn
	if stdreq.URL != nil && stdreq.URL.Scheme == "https" && binder.TLS != nil {
		conn = connTLS{state: binder.TLS}
	} else {
		conn = connNonTLS{}
	}

	ctx := fasthttp.RequestCtx{}
	ctx.Init2(conn, fastLogger{}, true)
	fastreq.CopyTo(&ctx.Request)

	if stdreq.ContentLength >= 0 {
		ctx.Request.Header.SetContentLength(int(stdreq.ContentLength))
	} else {
		ctx.Request.Header.Add("Transfer-Encoding", "chunked")
	}

	if stdreq.Body != nil {
		b, err := ioutil.ReadAll(stdreq.Body)
		if err == nil {
			ctx.Request.SetBody(b)
		}
	}

	binder.Handler(&ctx)

	return fast2std(stdreq, &ctx.Response), nil
}

func std2fast(stdreq *http.Request) *fasthttp.Request {
	fastreq := &fasthttp.Request{}
	fastreq.SetRequestURI(stdreq.URL.String())

	fastreq.Header.SetMethod(stdreq.Method)

	for k, a := range stdreq.Header {
		for n, v := range a {
			if n == 0 {
				fastreq.Header.Set(k, v)
			} else {
				fastreq.Header.Add(k, v)
			}
		}
	}

	return fastreq
}

func fast2std(stdreq *http.Request, fastresp *fasthttp.Response) *http.Response {
	status := fastresp.Header.StatusCode()
	body := fastresp.Body()

	stdresp := &http.Response{
		Request:    stdreq,
		StatusCode: status,
		Status:     http.StatusText(status),
	}

	fastresp.Header.VisitAll(func(k, v []byte) {
		sk := string(k)
		sv := string(v)
		if stdresp.Header == nil {
			stdresp.Header = make(http.Header)
		}
		stdresp.Header.Add(sk, sv)
	})

	if fastresp.Header.ContentLength() == -1 {
		stdresp.TransferEncoding = []string{"chunked"}
	}

	if body != nil {
		stdresp.Body = ioutil.NopCloser(bytes.NewReader(body))
	} else {
		stdresp.Body = ioutil.NopCloser(bytes.NewReader(nil))
	}

	return stdresp
}

type fastLogger struct{}

func (fastLogger) Printf(format string, args ...interface{}) {
	_, _ = format, args
}

type connNonTLS struct {
	net.Conn
}

func (connNonTLS) RemoteAddr() net.Addr {
	return &net.TCPAddr{IP: net.IPv4zero}
}

func (connNonTLS) LocalAddr() net.Addr {
	return &net.TCPAddr{IP: net.IPv4zero}
}

type connTLS struct {
	connNonTLS
	state *tls.ConnectionState
}

func (c connTLS) Handshake() error {
	return nil
}

func (c connTLS) ConnectionState() tls.ConnectionState {
	return *c.state
}
<|endoftext|>