<commit_before>/*
 * itamae.go
 *
 * Copyright 2016 Krzysztof Wilczynski
 *
 * 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 itamae

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

	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/provisioner"
	"github.com/mitchellh/packer/template/interpolate"
)

const (
	DefaultCommand    = "itamae"
	DefaultStagingDir = "/tmp/packer-itamae"
)

type Config struct {
	common.PackerConfig `mapstructure:",squash"`

	Command         string
	Vars            []string `mapstructure:"environment_vars"`
	ExecuteCommand  string   `mapstructure:"execute_command"`
	ExtraArguments  []string `mapstructure:"extra_arguments"`
	PreventSudo     bool     `mapstructure:"prevent_sudo"`
	StagingDir      string   `mapstructure:"staging_directory"`
	CleanStagingDir bool     `mapstructure:"clean_staging_directory"`
	SourceDir       string   `mapstructure:"source_directory"`
	LogLevel        string   `mapstructure:"log_level"`
	Shell           string   `mapstructure:"shell"`
	JsonPath        string   `mapstructure:"json_path"`
	YamlPath        string   `mapstructure:"yaml_path"`
	Recipes         []string `mapstructure:"recipes"`
	IgnoreExitCodes bool     `mapstructure:"ignore_exit_codes"`

	ctx interpolate.Context
}

type Provisioner struct {
	config        Config
	guestCommands *provisioner.GuestCommands
}

type ExecuteTemplate struct {
	Command        string
	Vars           string
	StagingDir     string
	LogLevel       string
	Shell          string
	JsonPath       string
	YamlPath       string
	ExtraArguments string
	Recipes        string
	Sudo           bool
}

func (p *Provisioner) Prepare(raws ...interface{}) error {
	err := config.Decode(&p.config, &config.DecodeOpts{
		Interpolate:        true,
		InterpolateContext: &p.config.ctx,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"execute_command",
			},
		},
	}, raws...)
	if err != nil {
		return err
	}

	p.guestCommands, err = provisioner.NewGuestCommands(p.guestOStype(), !p.config.PreventSudo)
	if err != nil {
		return err
	}

	if p.config.Vars == nil {
		p.config.Vars = make([]string, 0)
	}

	if p.config.Command == "" {
		p.config.Command = DefaultCommand
	}

	if p.config.ExecuteCommand == "" {
		p.config.ExecuteCommand = "cd {{.StagingDir}} && " +
			"{{.Vars}} {{if .Sudo}}sudo -E {{end}}" +
			"{{.Command}} local --color='false' " +
			"{{if ne .LogLevel \"\"}}--log-level='{{.LogLevel}}' {{end}}" +
			"{{if ne .Shell \"\"}}--shell='{{.Shell}}' {{end}}" +
			"{{if ne .JsonPath \"\"}}--node-json='{{.JsonPath}}' {{end}}" +
			"{{if ne .YamlPath \"\"}}--node-yaml='{{.YamlPath}}' {{end}}" +
			"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
			"{{.Recipes}}"
	}

	if p.config.ExtraArguments == nil {
		p.config.ExtraArguments = make([]string, 0)
	}

	if p.config.StagingDir == "" {
		p.config.StagingDir = DefaultStagingDir
	}

	var errs *packer.MultiError

	if p.config.SourceDir != "" {
		if err := p.validateDirConfig(p.config.SourceDir, "source_directory"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	if p.config.Recipes == nil {
		errs = packer.MultiErrorAppend(errs,
			fmt.Errorf("A recipes must be specified."))
	} else {
		for idx, path := range p.config.Recipes {
			path = p.withDirPrefix(path, p.config.SourceDir)
			if err := p.validateFileConfig(path, fmt.Sprintf("recipes[%d]", idx)); err != nil {
				errs = packer.MultiErrorAppend(errs, err)
			}
		}
	}

	if p.config.JsonPath != "" {
		jsonPath := p.withDirPrefix(p.config.JsonPath, p.config.SourceDir)
		if err := p.validateFileConfig(jsonPath, "json_path"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	if p.config.YamlPath != "" {
		yamlPath := p.withDirPrefix(p.config.YamlPath, p.config.SourceDir)
		if err := p.validateFileConfig(yamlPath, "yaml_path"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	for idx, kv := range p.config.Vars {
		vs := strings.SplitN(kv, "=", 2)
		if len(vs) != 2 || vs[0] == "" {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
		} else {
			vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)
			p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
		}
	}

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}
	return nil
}

func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
	ui.Say("Provisioning with Itamae...")

	ui.Message("Creating staging directory...")
	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
		return fmt.Errorf("Error creating staging directory: %s", err)
	}

	if p.config.SourceDir != "" {
		ui.Message("Uploading source directory to staging directory...")
		if err := p.uploadDir(ui, comm, p.config.StagingDir, p.config.SourceDir); err != nil {
			return fmt.Errorf("Error uploading source directory: %s", err)
		}
	} else {
		ui.Message("Uploading recipes...")
		for _, src := range p.config.Recipes {
			dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, src))
			if err := p.uploadFile(ui, comm, dst, src); err != nil {
				return fmt.Errorf("Error uploading recipe: %s", err)
			}
		}
	}

	if err := p.executeItamae(ui, comm); err != nil {
		return fmt.Errorf("Error executing Itamae: %s", err)
	}

	if p.config.CleanStagingDir {
		ui.Message("Removing staging directory...")
		if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
			return fmt.Errorf("Error removing staging directory: %s", err)
		}
	}
	return nil
}

func (p *Provisioner) Cancel() {
	os.Exit(0)
}

func (p *Provisioner) guestOStype() string {
	unixes := map[string]bool{
		"darwin":  true,
		"freebsd": true,
		"linux":   true,
		"openbsd": true,
	}

	if unixes[runtime.GOOS] {
		return "unix"
	}
	return runtime.GOOS
}

func (p *Provisioner) withDirPrefix(path, prefix string) string {
	if prefix != "" {
		path = filepath.Join(prefix, path)
	}
	return filepath.ToSlash(path)
}

func (p *Provisioner) validateDirConfig(path, config string) error {
	fi, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
	} else if !fi.IsDir() {
		return fmt.Errorf("%s: %s must point to a directory", config, path)
	}
	return nil
}

func (p *Provisioner) validateFileConfig(path, config string) error {
	fi, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
	} else if fi.IsDir() {
		return fmt.Errorf("%s: %s must point to a file", config, path)
	}
	return nil
}

func (p *Provisioner) executeItamae(ui packer.Ui, comm packer.Communicator) error {
	envVars := make([]string, len(p.config.Vars)+2)
	envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
	envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
	copy(envVars[2:], p.config.Vars)

	p.config.ctx.Data = &ExecuteTemplate{
		Command:        p.config.Command,
		Vars:           strings.Join(envVars, " "),
		StagingDir:     p.config.StagingDir,
		LogLevel:       p.config.LogLevel,
		Shell:          p.config.Shell,
		JsonPath:       p.config.JsonPath,
		YamlPath:       p.config.YamlPath,
		ExtraArguments: strings.Join(p.config.ExtraArguments, " "),
		Recipes:        strings.Join(p.config.Recipes, " "),
		Sudo:           !p.config.PreventSudo,
	}

	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
	if err != nil {
		return err
	}

	cmd := &packer.RemoteCmd{
		Command: command,
	}

	ui.Message(fmt.Sprintf("Executing Itamae: %s", command))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}

	if cmd.ExitStatus != 0 && !p.config.IgnoreExitCodes {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
	cmd := &packer.RemoteCmd{
		Command: p.guestCommands.CreateDir(dir),
	}

	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}

	cmd = &packer.RemoteCmd{
		Command: p.guestCommands.Chmod(dir, "0777"),
	}
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
	cmd := &packer.RemoteCmd{
		Command: p.guestCommands.RemoveDir(dir),
	}

	ui.Message(fmt.Sprintf("Removing directory: %s", dir))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}

	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
	f, err := os.Open(src)
	if err != nil {
		return err
	}
	defer f.Close()

	ui.Message(fmt.Sprintf("Uploading file: %s", src))
	return comm.Upload(dst, f, nil)
}

func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error {
	ui.Message(fmt.Sprintf("Uploading directory: %s", src))
	if ok := strings.HasSuffix(src, "/"); !ok {
		src += "/"
	}
	return comm.UploadDir(dst, src, nil)
}
<commit_msg>Check if the list of recipes is empty.<commit_after>/*
 * provisioner.go
 *
 * Copyright 2016 Krzysztof Wilczynski
 *
 * 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 itamae

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

	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/provisioner"
	"github.com/mitchellh/packer/template/interpolate"
)

const (
	DefaultCommand    = "itamae"
	DefaultStagingDir = "/tmp/packer-itamae"
)

type Config struct {
	common.PackerConfig `mapstructure:",squash"`

	Command         string
	Vars            []string `mapstructure:"environment_vars"`
	ExecuteCommand  string   `mapstructure:"execute_command"`
	ExtraArguments  []string `mapstructure:"extra_arguments"`
	PreventSudo     bool     `mapstructure:"prevent_sudo"`
	StagingDir      string   `mapstructure:"staging_directory"`
	CleanStagingDir bool     `mapstructure:"clean_staging_directory"`
	SourceDir       string   `mapstructure:"source_directory"`
	LogLevel        string   `mapstructure:"log_level"`
	Shell           string   `mapstructure:"shell"`
	JsonPath        string   `mapstructure:"json_path"`
	YamlPath        string   `mapstructure:"yaml_path"`
	Recipes         []string `mapstructure:"recipes"`
	IgnoreExitCodes bool     `mapstructure:"ignore_exit_codes"`

	ctx interpolate.Context
}

type Provisioner struct {
	config        Config
	guestCommands *provisioner.GuestCommands
}

type ExecuteTemplate struct {
	Command        string
	Vars           string
	StagingDir     string
	LogLevel       string
	Shell          string
	JsonPath       string
	YamlPath       string
	ExtraArguments string
	Recipes        string
	Sudo           bool
}

func (p *Provisioner) Prepare(raws ...interface{}) error {
	err := config.Decode(&p.config, &config.DecodeOpts{
		Interpolate:        true,
		InterpolateContext: &p.config.ctx,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"execute_command",
			},
		},
	}, raws...)
	if err != nil {
		return err
	}

	p.guestCommands, err = provisioner.NewGuestCommands(p.guestOStype(), !p.config.PreventSudo)
	if err != nil {
		return err
	}

	if p.config.Vars == nil {
		p.config.Vars = make([]string, 0)
	}

	if p.config.Command == "" {
		p.config.Command = DefaultCommand
	}

	if p.config.ExecuteCommand == "" {
		p.config.ExecuteCommand = "cd {{.StagingDir}} && " +
			"{{.Vars}} {{if .Sudo}}sudo -E {{end}}" +
			"{{.Command}} local --color='false' " +
			"{{if ne .LogLevel \"\"}}--log-level='{{.LogLevel}}' {{end}}" +
			"{{if ne .Shell \"\"}}--shell='{{.Shell}}' {{end}}" +
			"{{if ne .JsonPath \"\"}}--node-json='{{.JsonPath}}' {{end}}" +
			"{{if ne .YamlPath \"\"}}--node-yaml='{{.YamlPath}}' {{end}}" +
			"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
			"{{.Recipes}}"
	}

	if p.config.ExtraArguments == nil {
		p.config.ExtraArguments = make([]string, 0)
	}

	if p.config.StagingDir == "" {
		p.config.StagingDir = DefaultStagingDir
	}

	var errs *packer.MultiError

	if p.config.SourceDir != "" {
		if err := p.validateDirConfig(p.config.SourceDir, "source_directory"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	if p.config.Recipes == nil {
		errs = packer.MultiErrorAppend(errs,
			fmt.Errorf("A list of recipes must be specified."))
	} else if len(p.config.Recipes) == 0 {
		errs = packer.MultiErrorAppend(errs,
			fmt.Errorf("A list of recipes cannot be empty."))
	} else {
		for idx, path := range p.config.Recipes {
			path = p.withDirPrefix(path, p.config.SourceDir)
			if err := p.validateFileConfig(path, fmt.Sprintf("recipes[%d]", idx)); err != nil {
				errs = packer.MultiErrorAppend(errs, err)
			}
		}
	}

	if p.config.JsonPath != "" {
		jsonPath := p.withDirPrefix(p.config.JsonPath, p.config.SourceDir)
		if err := p.validateFileConfig(jsonPath, "json_path"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	if p.config.YamlPath != "" {
		yamlPath := p.withDirPrefix(p.config.YamlPath, p.config.SourceDir)
		if err := p.validateFileConfig(yamlPath, "yaml_path"); err != nil {
			errs = packer.MultiErrorAppend(errs, err)
		}
	}

	for idx, kv := range p.config.Vars {
		vs := strings.SplitN(kv, "=", 2)
		if len(vs) != 2 || vs[0] == "" {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
		} else {
			vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)
			p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
		}
	}

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}
	return nil
}

func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
	ui.Say("Provisioning with Itamae...")

	ui.Message("Creating staging directory...")
	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
		return fmt.Errorf("Error creating staging directory: %s", err)
	}

	if p.config.SourceDir != "" {
		ui.Message("Uploading source directory to staging directory...")
		if err := p.uploadDir(ui, comm, p.config.StagingDir, p.config.SourceDir); err != nil {
			return fmt.Errorf("Error uploading source directory: %s", err)
		}
	} else {
		ui.Message("Uploading recipes...")
		for _, src := range p.config.Recipes {
			dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, src))
			if err := p.uploadFile(ui, comm, dst, src); err != nil {
				return fmt.Errorf("Error uploading recipe: %s", err)
			}
		}
	}

	if err := p.executeItamae(ui, comm); err != nil {
		return fmt.Errorf("Error executing Itamae: %s", err)
	}

	if p.config.CleanStagingDir {
		ui.Message("Removing staging directory...")
		if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil {
			return fmt.Errorf("Error removing staging directory: %s", err)
		}
	}
	return nil
}

func (p *Provisioner) Cancel() {
	os.Exit(0)
}

func (p *Provisioner) guestOStype() string {
	unixes := map[string]bool{
		"darwin":  true,
		"freebsd": true,
		"linux":   true,
		"openbsd": true,
	}

	if unixes[runtime.GOOS] {
		return "unix"
	}
	return runtime.GOOS
}

func (p *Provisioner) withDirPrefix(path, prefix string) string {
	if prefix != "" {
		path = filepath.Join(prefix, path)
	}
	return filepath.ToSlash(path)
}

func (p *Provisioner) validateDirConfig(path, config string) error {
	fi, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
	} else if !fi.IsDir() {
		return fmt.Errorf("%s: %s must point to a directory", config, path)
	}
	return nil
}

func (p *Provisioner) validateFileConfig(path, config string) error {
	fi, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
	} else if fi.IsDir() {
		return fmt.Errorf("%s: %s must point to a file", config, path)
	}
	return nil
}

func (p *Provisioner) executeItamae(ui packer.Ui, comm packer.Communicator) error {
	envVars := make([]string, len(p.config.Vars)+2)
	envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
	envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
	copy(envVars[2:], p.config.Vars)

	p.config.ctx.Data = &ExecuteTemplate{
		Command:        p.config.Command,
		Vars:           strings.Join(envVars, " "),
		StagingDir:     p.config.StagingDir,
		LogLevel:       p.config.LogLevel,
		Shell:          p.config.Shell,
		JsonPath:       p.config.JsonPath,
		YamlPath:       p.config.YamlPath,
		ExtraArguments: strings.Join(p.config.ExtraArguments, " "),
		Recipes:        strings.Join(p.config.Recipes, " "),
		Sudo:           !p.config.PreventSudo,
	}

	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
	if err != nil {
		return err
	}

	cmd := &packer.RemoteCmd{
		Command: command,
	}

	ui.Message(fmt.Sprintf("Executing Itamae: %s", command))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}

	if cmd.ExitStatus != 0 && !p.config.IgnoreExitCodes {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
	cmd := &packer.RemoteCmd{
		Command: p.guestCommands.CreateDir(dir),
	}

	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}

	cmd = &packer.RemoteCmd{
		Command: p.guestCommands.Chmod(dir, "0777"),
	}
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}
	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
	cmd := &packer.RemoteCmd{
		Command: p.guestCommands.RemoveDir(dir),
	}

	ui.Message(fmt.Sprintf("Removing directory: %s", dir))
	if err := cmd.StartWithUi(comm, ui); err != nil {
		return err
	}

	if cmd.ExitStatus != 0 {
		return fmt.Errorf("Non-zero exit status. See output above for more information.")
	}
	return nil
}

func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
	f, err := os.Open(src)
	if err != nil {
		return err
	}
	defer f.Close()

	ui.Message(fmt.Sprintf("Uploading file: %s", src))
	return comm.Upload(dst, f, nil)
}

func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error {
	ui.Message(fmt.Sprintf("Uploading directory: %s", src))
	if ok := strings.HasSuffix(src, "/"); !ok {
		src += "/"
	}
	return comm.UploadDir(dst, src, nil)
}
<|endoftext|>


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

// +build go1.8,!go1.9

// This stack trace data is Go 1.8.4.

package rollbar

import (
	api "github.com/zchee/go-rollbar/api/v1"
)

var (
	testTrace = &api.Trace{
		Frames: []*api.Frame{
			&api.Frame{
				Filename: "/usr/local/go/src/testing/testing.go",
				Lineno:   657,
				Method:   "testing.tRunner",
			},
			&api.Frame{
				Filename: "/usr/local/go/src/runtime/asm_amd64.s",
				Lineno:   2197,
				Method:   "runtime.goexit",
			},
		},
		Exception: &api.Exception{
			Class:   "{23d90530}",
			Message: "default error",
		},
	}
	testFingerprint = "fece3ad4"
)
<commit_msg>test/go18: fix build constants<commit_after>// Copyright 2017 The go-rollbar Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !go1.9

// This stack trace data is Go 1.8.4.

package rollbar

import (
	api "github.com/zchee/go-rollbar/api/v1"
)

var (
	testTrace = &api.Trace{
		Frames: []*api.Frame{
			&api.Frame{
				Filename: "/usr/local/go/src/testing/testing.go",
				Lineno:   657,
				Method:   "testing.tRunner",
			},
			&api.Frame{
				Filename: "/usr/local/go/src/runtime/asm_amd64.s",
				Lineno:   2197,
				Method:   "runtime.goexit",
			},
		},
		Exception: &api.Exception{
			Class:   "{23d90530}",
			Message: "default error",
		},
	}
	testFingerprint = "fece3ad4"
)
<|endoftext|>


<commit_before>package contractor

import (
	"errors"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errAllowanceNoHosts    = errors.New("hosts must be non-zero")
	errAllowanceZeroPeriod = errors.New("period must be non-zero")
	errAllowanceWindowSize = errors.New("renew window must be less than period")
	errAllowanceNotSynced  = errors.New("you must be synced to set an allowance")

	// ErrAllowanceZeroWindow is returned when the caller requests a
	// zero-length renewal window. This will happen if the caller sets the
	// period to 1 block, since RenewWindow := period / 2.
	ErrAllowanceZeroWindow = errors.New("renew window must be non-zero")
)

// SetAllowance sets the amount of money the Contractor is allowed to spend on
// contracts over a given time period, divided among the number of hosts
// specified. Note that Contractor can start forming contracts as soon as
// SetAllowance is called; that is, it may block.
//
// In most cases, SetAllowance will renew existing contracts instead of
// forming new ones. This preserves the data on those hosts. When this occurs,
// the renewed contracts will atomically replace their previous versions. If
// SetAllowance is interrupted, renewed contracts may be lost, though the
// allocated funds will eventually be returned.
//
// If a is the empty allowance, SetAllowance will archive the current contract
// set. The contracts cannot be used to create Editors or Downloads, and will
// not be renewed.
//
// TODO: can an Editor or Downloader be used across renewals?
// TODO: will hosts allow renewing the same contract twice?
//
// NOTE: At this time, transaction fees are not counted towards the allowance.
// This means the contractor may spend more than allowance.Funds.
func (c *Contractor) SetAllowance(a modules.Allowance) error {
	if a.Funds.IsZero() && a.Hosts == 0 && a.Period == 0 && a.RenewWindow == 0 {
		return c.managedCancelAllowance(a)
	}

	// sanity checks
	if a.Hosts == 0 {
		return errAllowanceNoHosts
	} else if a.Period == 0 {
		return errAllowanceZeroPeriod
	} else if a.RenewWindow == 0 {
		return ErrAllowanceZeroWindow
	} else if a.RenewWindow >= a.Period {
		return errAllowanceWindowSize
	} else if !c.cs.Synced() {
		return errAllowanceNotSynced
	}

	// calculate the maximum sectors this allowance can store
	max, err := maxSectors(a, c.hdb, c.tpool)
	if err != nil {
		return err
	}
	// Only allocate half as many sectors as the max. This leaves some leeway
	// for replacing contracts, transaction fees, etc.
	numSectors := max / 2
	// check that this is sufficient to store at least one sector
	if numSectors == 0 {
		return ErrInsufficientAllowance
	}

	c.log.Println("INFO: setting allowance to", a)
	c.mu.Lock()
	// set the current period to the blockheight if the existing allowance is
	// empty
	if c.allowance.Funds.IsZero() && c.allowance.Hosts == 0 && c.allowance.Period == 0 && c.allowance.RenewWindow == 0 {
		c.currentPeriod = c.blockHeight
	}
	c.allowance = a
	err = c.saveSync()
	c.mu.Unlock()
	if err != nil {
		c.log.Println("Unable to save contractor after setting allowance:", err)
	}

	// Initiate maintenance on the contracts, and then return.
	go c.threadedContractMaintenance()
	return nil
}

// managedCancelAllowance handles the special case where the allowance is empty.
func (c *Contractor) managedCancelAllowance(a modules.Allowance) error {
	c.log.Println("INFO: canceling allowance")
	// first need to invalidate any active editors/downloaders
	// NOTE: this code is the same as in managedRenewContracts
	var ids []types.FileContractID
	c.mu.Lock()
	for id := range c.contracts {
		ids = append(ids, id)
		// we aren't renewing, but we don't want new editors or downloaders to
		// be created
		c.renewing[id] = true
	}
	c.mu.Unlock()
	defer func() {
		c.mu.Lock()
		for _, id := range ids {
			delete(c.renewing, id)
		}
		c.mu.Unlock()
	}()
	for _, id := range ids {
		c.mu.RLock()
		e, eok := c.editors[id]
		d, dok := c.downloaders[id]
		c.mu.RUnlock()
		if eok {
			e.invalidate()
		}
		if dok {
			d.invalidate()
		}
	}

	// reset currentPeriod and archive all contracts
	c.mu.Lock()
	c.allowance = a
	c.currentPeriod = 0
	for id, contract := range c.contracts {
		c.oldContracts[id] = contract
	}
	c.contracts = make(map[types.FileContractID]modules.RenterContract)
	err := c.saveSync()
	c.mu.Unlock()
	return err
}
<commit_msg>use reflect for more robust checking<commit_after>package contractor

import (
	"errors"
	"reflect"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errAllowanceNoHosts    = errors.New("hosts must be non-zero")
	errAllowanceZeroPeriod = errors.New("period must be non-zero")
	errAllowanceWindowSize = errors.New("renew window must be less than period")
	errAllowanceNotSynced  = errors.New("you must be synced to set an allowance")

	// ErrAllowanceZeroWindow is returned when the caller requests a
	// zero-length renewal window. This will happen if the caller sets the
	// period to 1 block, since RenewWindow := period / 2.
	ErrAllowanceZeroWindow = errors.New("renew window must be non-zero")
)

// SetAllowance sets the amount of money the Contractor is allowed to spend on
// contracts over a given time period, divided among the number of hosts
// specified. Note that Contractor can start forming contracts as soon as
// SetAllowance is called; that is, it may block.
//
// In most cases, SetAllowance will renew existing contracts instead of
// forming new ones. This preserves the data on those hosts. When this occurs,
// the renewed contracts will atomically replace their previous versions. If
// SetAllowance is interrupted, renewed contracts may be lost, though the
// allocated funds will eventually be returned.
//
// If a is the empty allowance, SetAllowance will archive the current contract
// set. The contracts cannot be used to create Editors or Downloads, and will
// not be renewed.
//
// TODO: can an Editor or Downloader be used across renewals?
// TODO: will hosts allow renewing the same contract twice?
//
// NOTE: At this time, transaction fees are not counted towards the allowance.
// This means the contractor may spend more than allowance.Funds.
func (c *Contractor) SetAllowance(a modules.Allowance) error {
	if reflect.DeepEqual(a, modules.Allowance{}) {
		return c.managedCancelAllowance(a)
	}

	// sanity checks
	if a.Hosts == 0 {
		return errAllowanceNoHosts
	} else if a.Period == 0 {
		return errAllowanceZeroPeriod
	} else if a.RenewWindow == 0 {
		return ErrAllowanceZeroWindow
	} else if a.RenewWindow >= a.Period {
		return errAllowanceWindowSize
	} else if !c.cs.Synced() {
		return errAllowanceNotSynced
	}

	// calculate the maximum sectors this allowance can store
	max, err := maxSectors(a, c.hdb, c.tpool)
	if err != nil {
		return err
	}
	// Only allocate half as many sectors as the max. This leaves some leeway
	// for replacing contracts, transaction fees, etc.
	numSectors := max / 2
	// check that this is sufficient to store at least one sector
	if numSectors == 0 {
		return ErrInsufficientAllowance
	}

	c.log.Println("INFO: setting allowance to", a)
	c.mu.Lock()
	// set the current period to the blockheight if the existing allowance is
	// empty
	if reflect.DeepEqual(c.allowance, modules.Allowance{}) {
		c.currentPeriod = c.blockHeight
	}
	c.allowance = a
	err = c.saveSync()
	c.mu.Unlock()
	if err != nil {
		c.log.Println("Unable to save contractor after setting allowance:", err)
	}

	// Initiate maintenance on the contracts, and then return.
	go c.threadedContractMaintenance()
	return nil
}

// managedCancelAllowance handles the special case where the allowance is empty.
func (c *Contractor) managedCancelAllowance(a modules.Allowance) error {
	c.log.Println("INFO: canceling allowance")
	// first need to invalidate any active editors/downloaders
	// NOTE: this code is the same as in managedRenewContracts
	var ids []types.FileContractID
	c.mu.Lock()
	for id := range c.contracts {
		ids = append(ids, id)
		// we aren't renewing, but we don't want new editors or downloaders to
		// be created
		c.renewing[id] = true
	}
	c.mu.Unlock()
	defer func() {
		c.mu.Lock()
		for _, id := range ids {
			delete(c.renewing, id)
		}
		c.mu.Unlock()
	}()
	for _, id := range ids {
		c.mu.RLock()
		e, eok := c.editors[id]
		d, dok := c.downloaders[id]
		c.mu.RUnlock()
		if eok {
			e.invalidate()
		}
		if dok {
			d.invalidate()
		}
	}

	// reset currentPeriod and archive all contracts
	c.mu.Lock()
	c.allowance = a
	c.currentPeriod = 0
	for id, contract := range c.contracts {
		c.oldContracts[id] = contract
	}
	c.contracts = make(map[types.FileContractID]modules.RenterContract)
	err := c.saveSync()
	c.mu.Unlock()
	return err
}
<|endoftext|>


<commit_before>// Simple console progress bars
package pb

import (
	"fmt"
	"io"
	"math"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unicode/utf8"
)

// Current version
const Version = "1.0.4"

const (
	// Default refresh rate - 200ms
	DEFAULT_REFRESH_RATE = time.Millisecond * 200
	FORMAT               = "[=>-]"
)

// DEPRECATED
// variables for backward compatibility, from now do not work
// use pb.Format and pb.SetRefreshRate
var (
	DefaultRefreshRate                         = DEFAULT_REFRESH_RATE
	BarStart, BarEnd, Empty, Current, CurrentN string
)

// Create new progress bar object
func New(total int) *ProgressBar {
	return New64(int64(total))
}

// Create new progress bar object using int64 as total
func New64(total int64) *ProgressBar {
	pb := &ProgressBar{
		Total:         total,
		RefreshRate:   DEFAULT_REFRESH_RATE,
		ShowPercent:   true,
		ShowCounters:  true,
		ShowBar:       true,
		ShowTimeLeft:  true,
		ShowFinalTime: true,
		Units:         U_NO,
		ManualUpdate:  false,
		finish:        make(chan struct{}),
		currentValue:  -1,
		mu:            new(sync.Mutex),
	}
	return pb.Format(FORMAT)
}

// Create new object and start
func StartNew(total int) *ProgressBar {
	return New(total).Start()
}

// Callback for custom output
// For example:
// bar.Callback = func(s string) {
//     mySuperPrint(s)
// }
//
type Callback func(out string)

type ProgressBar struct {
	current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)

	Total                            int64
	RefreshRate                      time.Duration
	ShowPercent, ShowCounters        bool
	ShowSpeed, ShowTimeLeft, ShowBar bool
	ShowFinalTime                    bool
	Output                           io.Writer
	Callback                         Callback
	NotPrint                         bool
	Units                            Units
	Width                            int
	ForceWidth                       bool
	ManualUpdate                     bool
	AutoStat                         bool

	// Default width for the time box.
	UnitsWidth   int
	TimeBoxWidth int

	finishOnce sync.Once //Guards isFinish
	finish     chan struct{}
	isFinish   bool

	startTime    time.Time
	startValue   int64
	currentValue int64

	prefix, postfix string

	mu        *sync.Mutex
	lastPrint string

	BarStart string
	BarEnd   string
	Empty    string
	Current  string
	CurrentN string

	AlwaysUpdate bool
}

// Start print
func (pb *ProgressBar) Start() *ProgressBar {
	pb.startTime = time.Now()
	pb.startValue = pb.current
	if pb.Total == 0 {
		pb.ShowTimeLeft = false
		pb.ShowPercent = false
		pb.AutoStat = false
	}
	if !pb.ManualUpdate {
		pb.Update() // Initial printing of the bar before running the bar refresher.
		go pb.refresher()
	}
	return pb
}

// Increment current value
func (pb *ProgressBar) Increment() int {
	return pb.Add(1)
}

// Get current value
func (pb *ProgressBar) Get() int64 {
	c := atomic.LoadInt64(&pb.current)
	return c
}

// Set current value
func (pb *ProgressBar) Set(current int) *ProgressBar {
	return pb.Set64(int64(current))
}

// Set64 sets the current value as int64
func (pb *ProgressBar) Set64(current int64) *ProgressBar {
	atomic.StoreInt64(&pb.current, current)
	return pb
}

// Add to current value
func (pb *ProgressBar) Add(add int) int {
	return int(pb.Add64(int64(add)))
}

func (pb *ProgressBar) Add64(add int64) int64 {
	return atomic.AddInt64(&pb.current, add)
}

// Set prefix string
func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
	pb.prefix = prefix
	return pb
}

// Set postfix string
func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
	pb.postfix = postfix
	return pb
}

// Set custom format for bar
// Example: bar.Format("[=>_]")
// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
func (pb *ProgressBar) Format(format string) *ProgressBar {
	var formatEntries []string
	if len(format) == 5 {
		formatEntries = strings.Split(format, "")
	} else {
		formatEntries = strings.Split(format, "\x00")
	}
	if len(formatEntries) == 5 {
		pb.BarStart = formatEntries[0]
		pb.BarEnd = formatEntries[4]
		pb.Empty = formatEntries[3]
		pb.Current = formatEntries[1]
		pb.CurrentN = formatEntries[2]
	}
	return pb
}

// Set bar refresh rate
func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
	pb.RefreshRate = rate
	return pb
}

// Set units
// bar.SetUnits(U_NO) - by default
// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
	pb.Units = units
	return pb
}

// Set max width, if width is bigger than terminal width, will be ignored
func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
	pb.Width = width
	pb.ForceWidth = false
	return pb
}

// Set bar width
func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
	pb.Width = width
	pb.ForceWidth = true
	return pb
}

// End print
func (pb *ProgressBar) Finish() {
	//Protect multiple calls
	pb.finishOnce.Do(func() {
		close(pb.finish)
		pb.write(atomic.LoadInt64(&pb.current))
		if !pb.NotPrint {
			fmt.Println()
		}
		pb.isFinish = true
	})
}

// End print and write string 'str'
func (pb *ProgressBar) FinishPrint(str string) {
	pb.Finish()
	fmt.Println(str)
}

// implement io.Writer
func (pb *ProgressBar) Write(p []byte) (n int, err error) {
	n = len(p)
	pb.Add(n)
	return
}

// implement io.Reader
func (pb *ProgressBar) Read(p []byte) (n int, err error) {
	n = len(p)
	pb.Add(n)
	return
}

// Create new proxy reader over bar
// Takes io.Reader or io.ReadCloser
func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
	return &Reader{r, pb}
}

func (pb *ProgressBar) write(current int64) {
	width := pb.GetWidth()

	var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string

	// percents
	if pb.ShowPercent {
		var percent float64
		if pb.Total > 0 {
			percent = float64(current) / (float64(pb.Total) / float64(100))
		} else {
			percent = float64(current) / float64(100)
		}
		percentBox = fmt.Sprintf(" %6.02f%%", percent)
	}

	// counters
	if pb.ShowCounters {
		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
		if pb.Total > 0 {
			total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth)
			countersBox = fmt.Sprintf(" %s / %s ", current, total)
		} else {
			countersBox = fmt.Sprintf(" %s / ? ", current)
		}
	}

	// time left
	fromStart := time.Now().Sub(pb.startTime)
	currentFromStart := current - pb.startValue
	select {
	case <-pb.finish:
		if pb.ShowFinalTime {
			var left time.Duration
			if pb.Total > 0 {
				left = (fromStart / time.Second) * time.Second
			} else {
				left = (time.Duration(currentFromStart) / time.Second) * time.Second
			}
			timeLeftBox = fmt.Sprintf(" %s", left.String())
		}
	default:
		if pb.ShowTimeLeft && currentFromStart > 0 {
			perEntry := fromStart / time.Duration(currentFromStart)
			var left time.Duration
			if pb.Total > 0 {
				left = time.Duration(pb.Total-currentFromStart) * perEntry
				left = (left / time.Second) * time.Second
			} else {
				left = time.Duration(currentFromStart) * perEntry
				left = (left / time.Second) * time.Second
			}
			timeLeft := Format(int64(left)).To(U_DURATION).String()
			timeLeftBox = fmt.Sprintf(" %s", timeLeft)
		}
	}

	if len(timeLeftBox) < pb.TimeBoxWidth {
		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
	}

	// speed
	if pb.ShowSpeed && currentFromStart > 0 {
		fromStart := time.Now().Sub(pb.startTime)
		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
	}

	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
	// bar
	if pb.ShowBar {
		size := width - barWidth
		if size > 0 {
			if pb.Total > 0 {
				curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
				emptCount := size - curCount
				barBox = pb.BarStart
				if emptCount < 0 {
					emptCount = 0
				}
				if curCount > size {
					curCount = size
				}
				if emptCount <= 0 {
					barBox += strings.Repeat(pb.Current, curCount)
				} else if curCount > 0 {
					barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN
				}
				barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
			} else {
				barBox = pb.BarStart
				pos := size - int(current)%int(size)
				if pos-1 > 0 {
					barBox += strings.Repeat(pb.Empty, pos-1)
				}
				barBox += pb.Current
				if size-pos-1 > 0 {
					barBox += strings.Repeat(pb.Empty, size-pos-1)
				}
				barBox += pb.BarEnd
			}
		}
	}

	// check len
	out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
	if escapeAwareRuneCountInString(out) < width {
		end = strings.Repeat(" ", width-utf8.RuneCountInString(out))
	}

	// and print!
	pb.mu.Lock()
	pb.lastPrint = out + end
	pb.mu.Unlock()
	switch {
	case pb.isFinish:
		return
	case pb.Output != nil:
		fmt.Fprint(pb.Output, "\r"+out+end)
	case pb.Callback != nil:
		pb.Callback(out + end)
	case !pb.NotPrint:
		fmt.Print("\r" + out + end)
	}
}

// GetTerminalWidth - returns terminal width for all platforms.
func GetTerminalWidth() (int, error) {
	return terminalWidth()
}

func (pb *ProgressBar) GetWidth() int {
	if pb.ForceWidth {
		return pb.Width
	}

	width := pb.Width
	termWidth, _ := terminalWidth()
	if width == 0 || termWidth <= width {
		width = termWidth
	}

	return width
}

// Write the current state of the progressbar
func (pb *ProgressBar) Update() {
	c := atomic.LoadInt64(&pb.current)
	if pb.AlwaysUpdate || c != pb.currentValue {
		pb.write(c)
		pb.currentValue = c
	}
	if pb.AutoStat {
		if c == 0 {
			pb.startTime = time.Now()
			pb.startValue = 0
		} else if c >= pb.Total && pb.isFinish != true {
			pb.Finish()
		}
	}
}

func (pb *ProgressBar) String() string {
	return pb.lastPrint
}

// Internal loop for refreshing the progressbar
func (pb *ProgressBar) refresher() {
	for {
		select {
		case <-pb.finish:
			return
		case <-time.After(pb.RefreshRate):
			pb.Update()
		}
	}
}

type window struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}
<commit_msg>1.0.5<commit_after>// Simple console progress bars
package pb

import (
	"fmt"
	"io"
	"math"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unicode/utf8"
)

// Current version
const Version = "1.0.5"

const (
	// Default refresh rate - 200ms
	DEFAULT_REFRESH_RATE = time.Millisecond * 200
	FORMAT               = "[=>-]"
)

// DEPRECATED
// variables for backward compatibility, from now do not work
// use pb.Format and pb.SetRefreshRate
var (
	DefaultRefreshRate                         = DEFAULT_REFRESH_RATE
	BarStart, BarEnd, Empty, Current, CurrentN string
)

// Create new progress bar object
func New(total int) *ProgressBar {
	return New64(int64(total))
}

// Create new progress bar object using int64 as total
func New64(total int64) *ProgressBar {
	pb := &ProgressBar{
		Total:         total,
		RefreshRate:   DEFAULT_REFRESH_RATE,
		ShowPercent:   true,
		ShowCounters:  true,
		ShowBar:       true,
		ShowTimeLeft:  true,
		ShowFinalTime: true,
		Units:         U_NO,
		ManualUpdate:  false,
		finish:        make(chan struct{}),
		currentValue:  -1,
		mu:            new(sync.Mutex),
	}
	return pb.Format(FORMAT)
}

// Create new object and start
func StartNew(total int) *ProgressBar {
	return New(total).Start()
}

// Callback for custom output
// For example:
// bar.Callback = func(s string) {
//     mySuperPrint(s)
// }
//
type Callback func(out string)

type ProgressBar struct {
	current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)

	Total                            int64
	RefreshRate                      time.Duration
	ShowPercent, ShowCounters        bool
	ShowSpeed, ShowTimeLeft, ShowBar bool
	ShowFinalTime                    bool
	Output                           io.Writer
	Callback                         Callback
	NotPrint                         bool
	Units                            Units
	Width                            int
	ForceWidth                       bool
	ManualUpdate                     bool
	AutoStat                         bool

	// Default width for the time box.
	UnitsWidth   int
	TimeBoxWidth int

	finishOnce sync.Once //Guards isFinish
	finish     chan struct{}
	isFinish   bool

	startTime    time.Time
	startValue   int64
	currentValue int64

	prefix, postfix string

	mu        *sync.Mutex
	lastPrint string

	BarStart string
	BarEnd   string
	Empty    string
	Current  string
	CurrentN string

	AlwaysUpdate bool
}

// Start print
func (pb *ProgressBar) Start() *ProgressBar {
	pb.startTime = time.Now()
	pb.startValue = pb.current
	if pb.Total == 0 {
		pb.ShowTimeLeft = false
		pb.ShowPercent = false
		pb.AutoStat = false
	}
	if !pb.ManualUpdate {
		pb.Update() // Initial printing of the bar before running the bar refresher.
		go pb.refresher()
	}
	return pb
}

// Increment current value
func (pb *ProgressBar) Increment() int {
	return pb.Add(1)
}

// Get current value
func (pb *ProgressBar) Get() int64 {
	c := atomic.LoadInt64(&pb.current)
	return c
}

// Set current value
func (pb *ProgressBar) Set(current int) *ProgressBar {
	return pb.Set64(int64(current))
}

// Set64 sets the current value as int64
func (pb *ProgressBar) Set64(current int64) *ProgressBar {
	atomic.StoreInt64(&pb.current, current)
	return pb
}

// Add to current value
func (pb *ProgressBar) Add(add int) int {
	return int(pb.Add64(int64(add)))
}

func (pb *ProgressBar) Add64(add int64) int64 {
	return atomic.AddInt64(&pb.current, add)
}

// Set prefix string
func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
	pb.prefix = prefix
	return pb
}

// Set postfix string
func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
	pb.postfix = postfix
	return pb
}

// Set custom format for bar
// Example: bar.Format("[=>_]")
// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
func (pb *ProgressBar) Format(format string) *ProgressBar {
	var formatEntries []string
	if len(format) == 5 {
		formatEntries = strings.Split(format, "")
	} else {
		formatEntries = strings.Split(format, "\x00")
	}
	if len(formatEntries) == 5 {
		pb.BarStart = formatEntries[0]
		pb.BarEnd = formatEntries[4]
		pb.Empty = formatEntries[3]
		pb.Current = formatEntries[1]
		pb.CurrentN = formatEntries[2]
	}
	return pb
}

// Set bar refresh rate
func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
	pb.RefreshRate = rate
	return pb
}

// Set units
// bar.SetUnits(U_NO) - by default
// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
	pb.Units = units
	return pb
}

// Set max width, if width is bigger than terminal width, will be ignored
func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
	pb.Width = width
	pb.ForceWidth = false
	return pb
}

// Set bar width
func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
	pb.Width = width
	pb.ForceWidth = true
	return pb
}

// End print
func (pb *ProgressBar) Finish() {
	//Protect multiple calls
	pb.finishOnce.Do(func() {
		close(pb.finish)
		pb.write(atomic.LoadInt64(&pb.current))
		if !pb.NotPrint {
			fmt.Println()
		}
		pb.isFinish = true
	})
}

// End print and write string 'str'
func (pb *ProgressBar) FinishPrint(str string) {
	pb.Finish()
	fmt.Println(str)
}

// implement io.Writer
func (pb *ProgressBar) Write(p []byte) (n int, err error) {
	n = len(p)
	pb.Add(n)
	return
}

// implement io.Reader
func (pb *ProgressBar) Read(p []byte) (n int, err error) {
	n = len(p)
	pb.Add(n)
	return
}

// Create new proxy reader over bar
// Takes io.Reader or io.ReadCloser
func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
	return &Reader{r, pb}
}

func (pb *ProgressBar) write(current int64) {
	width := pb.GetWidth()

	var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string

	// percents
	if pb.ShowPercent {
		var percent float64
		if pb.Total > 0 {
			percent = float64(current) / (float64(pb.Total) / float64(100))
		} else {
			percent = float64(current) / float64(100)
		}
		percentBox = fmt.Sprintf(" %6.02f%%", percent)
	}

	// counters
	if pb.ShowCounters {
		current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
		if pb.Total > 0 {
			total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth)
			countersBox = fmt.Sprintf(" %s / %s ", current, total)
		} else {
			countersBox = fmt.Sprintf(" %s / ? ", current)
		}
	}

	// time left
	fromStart := time.Now().Sub(pb.startTime)
	currentFromStart := current - pb.startValue
	select {
	case <-pb.finish:
		if pb.ShowFinalTime {
			var left time.Duration
			if pb.Total > 0 {
				left = (fromStart / time.Second) * time.Second
			} else {
				left = (time.Duration(currentFromStart) / time.Second) * time.Second
			}
			timeLeftBox = fmt.Sprintf(" %s", left.String())
		}
	default:
		if pb.ShowTimeLeft && currentFromStart > 0 {
			perEntry := fromStart / time.Duration(currentFromStart)
			var left time.Duration
			if pb.Total > 0 {
				left = time.Duration(pb.Total-currentFromStart) * perEntry
				left = (left / time.Second) * time.Second
			} else {
				left = time.Duration(currentFromStart) * perEntry
				left = (left / time.Second) * time.Second
			}
			timeLeft := Format(int64(left)).To(U_DURATION).String()
			timeLeftBox = fmt.Sprintf(" %s", timeLeft)
		}
	}

	if len(timeLeftBox) < pb.TimeBoxWidth {
		timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
	}

	// speed
	if pb.ShowSpeed && currentFromStart > 0 {
		fromStart := time.Now().Sub(pb.startTime)
		speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
		speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
	}

	barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
	// bar
	if pb.ShowBar {
		size := width - barWidth
		if size > 0 {
			if pb.Total > 0 {
				curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
				emptCount := size - curCount
				barBox = pb.BarStart
				if emptCount < 0 {
					emptCount = 0
				}
				if curCount > size {
					curCount = size
				}
				if emptCount <= 0 {
					barBox += strings.Repeat(pb.Current, curCount)
				} else if curCount > 0 {
					barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN
				}
				barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
			} else {
				barBox = pb.BarStart
				pos := size - int(current)%int(size)
				if pos-1 > 0 {
					barBox += strings.Repeat(pb.Empty, pos-1)
				}
				barBox += pb.Current
				if size-pos-1 > 0 {
					barBox += strings.Repeat(pb.Empty, size-pos-1)
				}
				barBox += pb.BarEnd
			}
		}
	}

	// check len
	out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
	if escapeAwareRuneCountInString(out) < width {
		end = strings.Repeat(" ", width-utf8.RuneCountInString(out))
	}

	// and print!
	pb.mu.Lock()
	pb.lastPrint = out + end
	pb.mu.Unlock()
	switch {
	case pb.isFinish:
		return
	case pb.Output != nil:
		fmt.Fprint(pb.Output, "\r"+out+end)
	case pb.Callback != nil:
		pb.Callback(out + end)
	case !pb.NotPrint:
		fmt.Print("\r" + out + end)
	}
}

// GetTerminalWidth - returns terminal width for all platforms.
func GetTerminalWidth() (int, error) {
	return terminalWidth()
}

func (pb *ProgressBar) GetWidth() int {
	if pb.ForceWidth {
		return pb.Width
	}

	width := pb.Width
	termWidth, _ := terminalWidth()
	if width == 0 || termWidth <= width {
		width = termWidth
	}

	return width
}

// Write the current state of the progressbar
func (pb *ProgressBar) Update() {
	c := atomic.LoadInt64(&pb.current)
	if pb.AlwaysUpdate || c != pb.currentValue {
		pb.write(c)
		pb.currentValue = c
	}
	if pb.AutoStat {
		if c == 0 {
			pb.startTime = time.Now()
			pb.startValue = 0
		} else if c >= pb.Total && pb.isFinish != true {
			pb.Finish()
		}
	}
}

func (pb *ProgressBar) String() string {
	return pb.lastPrint
}

// Internal loop for refreshing the progressbar
func (pb *ProgressBar) refresher() {
	for {
		select {
		case <-pb.finish:
			return
		case <-time.After(pb.RefreshRate):
			pb.Update()
		}
	}
}

type window struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}
<|endoftext|>


<commit_before>/*package parse contains routines for parsing config files. To parse a config
file, the user creates a ConfigVars struct and registers every variable in the
config file with it. Registration requires 1) the variable name, 2) a
default value, and 3) a location to write the variable to.

Config files have two parse with this package have two parts, a title and a
body. The title specifies the type of config file an the body contains
Variable = Value pairs. The title ensures that when a project has multiple
config files the wrong one isn't read by mistake.

Here is an example configuration file that collects information about
my cat:

    # Title
    [cat_info]

    # Body:
	CatName = Bob
	FurColors = White, Black
	Age = 7.5 # Inline comments are okay, too.
	Paws = 4

Here is an example of using the parse package to parse this type of config
file.

	type CatInfo struct {
		CatName string
		FurColors []string
		Age float
		Paws, Tails int
	}

	info := new(CatInfo)

    vars := ConfigVars("cat_info")
    vars.String(&info.CatName, "CatName", "")
    vars.Strings(&info.FurColors, "FurColors", []string{})
    vars.Float(&info.Age, "Age", -1)
    vars.Int(&info.Paws, "Paws", 4)
    vars.Int(&info.Tail, "Tail", 1)

    // Then, once a file has been provided

    err := ReadConfig("my_cat.config", vars)
    if err != nil {
        // Handle error
    }

A careful read of the above example will show that the supplied config file
does not consider the config file missing one or more files an error. This will
be annoying in some cases, but is usually the desired behavior. You will need to
explicitly check for variables that have not been set.

For additional examples, see the usage in config_test.go
*/
package parse

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"
)

/////////////////////
// Conversion Code //
/////////////////////

type varType int

const (
	intVar varType = iota
	intsVar
	floatVar
	floatsVar
	stringVar
	stringsVar
	boolVar
	boolsVar
)

func (v varType) String() string {
	switch v {
	case intVar:
		return "int"
	case intsVar:
		return "int list"
	case floatVar:
		return "float"
	case floatsVar:
		return "float list"
	case stringVar:
		return "string"
	case stringsVar:
		return "string list"
	case boolVar:
		return "bool"
	case boolsVar:
		return "bool list"
	}
	panic("Impossible")
}

type conversionFunc func(string) bool

type ConfigVars struct {
	name            string
	varNames        []string
	varTypes        []varType
	conversionFuncs []conversionFunc
}

func intConv(ptr *int64) conversionFunc {
	return func(s string) bool {
		i, err := strconv.Atoi(s)
		if err != nil {
			return false
		}
		*ptr = int64(i)
		return true
	}
}

func floatConv(ptr *float64) conversionFunc {
	return func(s string) bool {
		f, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return false
		}
		*ptr = f
		return true
	}
}

func stringConv(ptr *string) conversionFunc {
	return func(s string) bool {
		*ptr = strings.Trim(s, " ")
		return true
	}
}

func boolConv(ptr *bool) conversionFunc {
	return func(s string) bool {
		b, err := strconv.ParseBool(s)
		if err != nil {
			return false
		}
		*ptr = b
		return true
	}
}

func strToList(a string) []string {
	strs := strings.Split(a, ",")
	for i := range strs {
		strs[i] = strings.Trim(strs[i], " ")
	}
	return strs
}

func intsConv(ptr *[]int64) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		*ptr = (*ptr)[:0]
		for j := range toks {
			i, err := strconv.Atoi(toks[j])
			if err != nil {
				return false
			}
			*ptr = append(*ptr, int64(i))
		}
		return true
	}
}

func floatsConv(ptr *[]float64) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		for j := range toks {
			f, err := strconv.ParseFloat(toks[j], 64)
			if err != nil {
				return false
			}
			*ptr = append(*ptr, f)
		}
		return true
	}
}

func stringsConv(ptr *[]string) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		for j := range toks {
			*ptr = append(*ptr, toks[j])
		}
		return true
	}
}

func boolsConv(ptr *[]bool) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		for j := range toks {
			b, err := strconv.ParseBool(toks[j])
			if err != nil {
				return false
			}
			*ptr = append(*ptr, b)
		}
		return true
	}
}

func NewConfigVars(name string) *ConfigVars {
	return &ConfigVars{name: name}
}

func (vars *ConfigVars) Int(ptr *int64, name string, value int64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, intConv(ptr))
	vars.varTypes = append(vars.varTypes, intVar)
}

func (vars *ConfigVars) Float(ptr *float64, name string, value float64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, floatConv(ptr))
	vars.varTypes = append(vars.varTypes, floatVar)
}

func (vars *ConfigVars) String(ptr *string, name string, value string) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, stringConv(ptr))
	vars.varTypes = append(vars.varTypes, stringVar)
}

func (vars *ConfigVars) Bool(ptr *bool, name string, value bool) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, boolConv(ptr))
	vars.varTypes = append(vars.varTypes, boolVar)
}

func (vars *ConfigVars) Ints(ptr *[]int64, name string, value []int64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, intsConv(ptr))
	vars.varTypes = append(vars.varTypes, intsVar)
}

func (vars *ConfigVars) Floats(ptr *[]float64, name string, value []float64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, floatsConv(ptr))
	vars.varTypes = append(vars.varTypes, floatsVar)
}

func (vars *ConfigVars) Strings(ptr *[]string, name string, value []string) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, stringsConv(ptr))
	vars.varTypes = append(vars.varTypes, stringsVar)
}

func (vars *ConfigVars) Bools(ptr *[]bool, name string, value []bool) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, boolsConv(ptr))
	vars.varTypes = append(vars.varTypes, boolsVar)
}

//////////////////
// Parsing Code //
//////////////////

// ReadConfig parses the config file specified by fname using the set of
// variables vars. If successful nil is returned, otherwise an error is
// returned.
func ReadConfig(fname string, vars *ConfigVars) error {
	for i := range vars.varNames {
		vars.varNames[i] = strings.ToLower(vars.varNames[i])
	}

	// I/O

	bs, err := ioutil.ReadFile(fname)
	if err != nil {
		return err
	}

	// Begin tokenization; remember line numbers for better errors.

	lines := strings.Split(string(bs), "\n")
	lines, lineNums := removeComments(lines)
	for i := range lineNums {
		lineNums[i]++
	}

	if len(lines) == 0 || lines[0] != fmt.Sprintf("[%s]", vars.name) {
		return fmt.Errorf(
			"I expected the config file %s to have the header "+
				"[%s] at the top, but didn't find it.", fname, vars.name,
		)
	}
	lines = lines[1:]

	// Create associate list and check for name-based errors

	names, vals, errLine := associationList(lines)
	if errLine != -1 {
		return fmt.Errorf(
			"I could not parse line %d of the config file %s because it "+
				"did not take the form of a variable assignment.",
			lineNums[errLine+1], fname,
		)
	}

	if errLine = checkValidNames(names, vars); errLine != -1 {
		return fmt.Errorf(
			"Line %d of the config file %s assigns a value to the "+
				"variable '%s', but config files of type %s don't have that "+
				"variable.", lineNums[errLine+1], fname, names[errLine], vars.name,
		)
	}

	if errLine1, errLine2 := checkDuplicateNames(names); errLine1 != -1 {
		return fmt.Errorf(
			"Lines %d and %d of the config file %s both assign a value to "+
				"the variable '%s'.", lineNums[errLine1+1], lineNums[errLine2+1],
			fname, names[errLine1],
		)
	}

	// Convert every variable in the associate list.

	if errLine = convertAssoc(names, vals, vars); errLine != -1 {
		j := 0
		for ; j < len(vars.varNames); j++ {
			if vars.varNames[j] == names[errLine] {
				break
			}
		}
		typeName := vars.varTypes[j].String()
		a := "a"
		if typeName[0] == 'i' {
			a = "an"
		}
		return fmt.Errorf(
			"I could not parse line %d of the config file %s because '%s' "+
				"expects values of type %s and '%s' cannnot be converted to "+
				"%s %s.", lineNums[errLine+1], fname, vars.varNames[j], typeName,
			vals[j], a, typeName,
		)
	}

	return nil
}

// These functions are self-explanatory.

func removeComments(lines []string) ([]string, []int) {
	tmp := make([]string, len(lines))
	copy(tmp, lines)
	lines = tmp

	for i := range lines {
		comment := strings.Index(lines[i], "#")
		if comment == -1 {
			continue
		}
		lines[i] = lines[i][:comment]
	}

	out, lineNums := []string{}, []int{}
	for i := range lines {
		line := strings.Trim(lines[i], " ")
		if len(line) == 0 {
			continue
		}
		out = append(out, line)
		lineNums = append(lineNums, i)
	}

	return out, lineNums
}

func associationList(lines []string) ([]string, []string, int) {
	names, vals := []string{}, []string{}
	for i := range lines {
		eq := strings.Index(lines[i], "=")
		if eq == -1 {
			return nil, nil, i
		}
		name := lines[i][:eq]
		val := ""
		if len(lines[i])-1 > eq {
			val = lines[i][eq+1:]
		}
		names = append(names, strings.ToLower(strings.Trim(name, " ")))
		if len(names[len(names)-1]) == 0 {
			return nil, nil, i
		}
		vals = append(vals, strings.Trim(val, " "))
	}
	return names, vals, -1
}

func checkValidNames(names []string, vars *ConfigVars) int {
	for i := range names {
		found := false
		for j := range vars.varNames {
			if vars.varNames[j] == names[i] {
				found = true
				break
			}
		}
		if !found {
			return i
		}
	}
	return -1
}

func checkDuplicateNames(names []string) (int, int) {
	for i := range names {
		for j := i + 1; j < len(names); j++ {
			if names[i] == names[j] {
				return i, j
			}
		}
	}
	return -1, -1
}

func convertAssoc(names, vals []string, vars *ConfigVars) int {
	for i := range names {
		j := 0
		for ; j < len(vars.varNames); j++ {
			if vars.varNames[j] == names[i] {
				break
			}
		}

		ok := vars.conversionFuncs[j](vals[i])
		if !ok {
			return i
		}
	}
	return -1
}
<commit_msg>Fixed bug in config file parser.<commit_after>/*package parse contains routines for parsing config files. To parse a config
file, the user creates a ConfigVars struct and registers every variable in the
config file with it. Registration requires 1) the variable name, 2) a
default value, and 3) a location to write the variable to.

Config files have two parse with this package have two parts, a title and a
body. The title specifies the type of config file an the body contains
Variable = Value pairs. The title ensures that when a project has multiple
config files the wrong one isn't read by mistake.

Here is an example configuration file that collects information about
my cat:

    # Title
    [cat_info]

    # Body:
	CatName = Bob
	FurColors = White, Black
	Age = 7.5 # Inline comments are okay, too.
	Paws = 4

Here is an example of using the parse package to parse this type of config
file.

	type CatInfo struct {
		CatName string
		FurColors []string
		Age float
		Paws, Tails int
	}

	info := new(CatInfo)

    vars := ConfigVars("cat_info")
    vars.String(&info.CatName, "CatName", "")
    vars.Strings(&info.FurColors, "FurColors", []string{})
    vars.Float(&info.Age, "Age", -1)
    vars.Int(&info.Paws, "Paws", 4)
    vars.Int(&info.Tail, "Tail", 1)

    // Then, once a file has been provided

    err := ReadConfig("my_cat.config", vars)
    if err != nil {
        // Handle error
    }

A careful read of the above example will show that the supplied config file
does not consider the config file missing one or more files an error. This will
be annoying in some cases, but is usually the desired behavior. You will need to
explicitly check for variables that have not been set.

For additional examples, see the usage in config_test.go
*/
package parse

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"
)

/////////////////////
// Conversion Code //
/////////////////////

type varType int

const (
	intVar varType = iota
	intsVar
	floatVar
	floatsVar
	stringVar
	stringsVar
	boolVar
	boolsVar
)

func (v varType) String() string {
	switch v {
	case intVar:
		return "int"
	case intsVar:
		return "int list"
	case floatVar:
		return "float"
	case floatsVar:
		return "float list"
	case stringVar:
		return "string"
	case stringsVar:
		return "string list"
	case boolVar:
		return "bool"
	case boolsVar:
		return "bool list"
	}
	panic("Impossible")
}

type conversionFunc func(string) bool

type ConfigVars struct {
	name            string
	varNames        []string
	varTypes        []varType
	conversionFuncs []conversionFunc
}

func intConv(ptr *int64) conversionFunc {
	return func(s string) bool {
		i, err := strconv.Atoi(s)
		if err != nil {
			return false
		}
		*ptr = int64(i)
		return true
	}
}

func floatConv(ptr *float64) conversionFunc {
	return func(s string) bool {
		f, err := strconv.ParseFloat(s, 64)
		if err != nil {
			return false
		}
		*ptr = f
		return true
	}
}

func stringConv(ptr *string) conversionFunc {
	return func(s string) bool {
		*ptr = strings.Trim(s, " ")
		return true
	}
}

func boolConv(ptr *bool) conversionFunc {
	return func(s string) bool {
		b, err := strconv.ParseBool(s)
		if err != nil {
			return false
		}
		*ptr = b
		return true
	}
}

func strToList(a string) []string {
	strs := strings.Split(a, ",")
	for i := range strs {
		strs[i] = strings.Trim(strs[i], " ")
	}
	return strs
}

func intsConv(ptr *[]int64) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		*ptr = []int64{}
		for j := range toks {
			i, err := strconv.Atoi(toks[j])
			if err != nil {
				return false
			}
			*ptr = append(*ptr, int64(i))
		}
		return true
	}
}

func floatsConv(ptr *[]float64) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		*ptr = []float64{}
		for j := range toks {
			f, err := strconv.ParseFloat(toks[j], 64)
			if err != nil {
				return false
			}
			*ptr = append(*ptr, f)
		}
		return true
	}
}

func stringsConv(ptr *[]string) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		*ptr = []string{}
		for j := range toks {
			*ptr = append(*ptr, toks[j])
		}
		return true
	}
}

func boolsConv(ptr *[]bool) conversionFunc {
	return func(s string) bool {
		toks := strToList(s)
		*ptr = []bool{}
		for j := range toks {
			b, err := strconv.ParseBool(toks[j])
			if err != nil {
				return false
			}
			*ptr = append(*ptr, b)
		}
		return true
	}
}

func NewConfigVars(name string) *ConfigVars {
	return &ConfigVars{name: name}
}

func (vars *ConfigVars) Int(ptr *int64, name string, value int64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, intConv(ptr))
	vars.varTypes = append(vars.varTypes, intVar)
}

func (vars *ConfigVars) Float(ptr *float64, name string, value float64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, floatConv(ptr))
	vars.varTypes = append(vars.varTypes, floatVar)
}

func (vars *ConfigVars) String(ptr *string, name string, value string) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, stringConv(ptr))
	vars.varTypes = append(vars.varTypes, stringVar)
}

func (vars *ConfigVars) Bool(ptr *bool, name string, value bool) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, boolConv(ptr))
	vars.varTypes = append(vars.varTypes, boolVar)
}

func (vars *ConfigVars) Ints(ptr *[]int64, name string, value []int64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, intsConv(ptr))
	vars.varTypes = append(vars.varTypes, intsVar)
}

func (vars *ConfigVars) Floats(ptr *[]float64, name string, value []float64) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, floatsConv(ptr))
	vars.varTypes = append(vars.varTypes, floatsVar)
}

func (vars *ConfigVars) Strings(ptr *[]string, name string, value []string) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, stringsConv(ptr))
	vars.varTypes = append(vars.varTypes, stringsVar)
}

func (vars *ConfigVars) Bools(ptr *[]bool, name string, value []bool) {
	*ptr = value
	vars.varNames = append(vars.varNames, name)
	vars.conversionFuncs = append(vars.conversionFuncs, boolsConv(ptr))
	vars.varTypes = append(vars.varTypes, boolsVar)
}

//////////////////
// Parsing Code //
//////////////////

// ReadConfig parses the config file specified by fname using the set of
// variables vars. If successful nil is returned, otherwise an error is
// returned.
func ReadConfig(fname string, vars *ConfigVars) error {
	for i := range vars.varNames {
		vars.varNames[i] = strings.ToLower(vars.varNames[i])
	}

	// I/O

	bs, err := ioutil.ReadFile(fname)
	if err != nil {
		return err
	}

	// Begin tokenization; remember line numbers for better errors.

	lines := strings.Split(string(bs), "\n")
	lines, lineNums := removeComments(lines)
	for i := range lineNums {
		lineNums[i]++
	}

	if len(lines) == 0 || lines[0] != fmt.Sprintf("[%s]", vars.name) {
		return fmt.Errorf(
			"I expected the config file %s to have the header "+
				"[%s] at the top, but didn't find it.", fname, vars.name,
		)
	}
	lines = lines[1:]

	// Create associate list and check for name-based errors

	names, vals, errLine := associationList(lines)
	if errLine != -1 {
		return fmt.Errorf(
			"I could not parse line %d of the config file %s because it "+
				"did not take the form of a variable assignment.",
			lineNums[errLine+1], fname,
		)
	}

	if errLine = checkValidNames(names, vars); errLine != -1 {
		return fmt.Errorf(
			"Line %d of the config file %s assigns a value to the "+
				"variable '%s', but config files of type %s don't have that "+
				"variable.", lineNums[errLine+1], fname, names[errLine], vars.name,
		)
	}

	if errLine1, errLine2 := checkDuplicateNames(names); errLine1 != -1 {
		return fmt.Errorf(
			"Lines %d and %d of the config file %s both assign a value to "+
				"the variable '%s'.", lineNums[errLine1+1], lineNums[errLine2+1],
			fname, names[errLine1],
		)
	}

	// Convert every variable in the associate list.

	if errLine = convertAssoc(names, vals, vars); errLine != -1 {
		j := 0
		for ; j < len(vars.varNames); j++ {
			if vars.varNames[j] == names[errLine] {
				break
			}
		}
		typeName := vars.varTypes[j].String()
		a := "a"
		if typeName[0] == 'i' {
			a = "an"
		}
		return fmt.Errorf(
			"I could not parse line %d of the config file %s because '%s' "+
				"expects values of type %s and '%s' cannnot be converted to "+
				"%s %s.", lineNums[errLine+1], fname, vars.varNames[j], typeName,
			vals[j], a, typeName,
		)
	}

	return nil
}

// These functions are self-explanatory.

func removeComments(lines []string) ([]string, []int) {
	tmp := make([]string, len(lines))
	copy(tmp, lines)
	lines = tmp

	for i := range lines {
		comment := strings.Index(lines[i], "#")
		if comment == -1 {
			continue
		}
		lines[i] = lines[i][:comment]
	}

	out, lineNums := []string{}, []int{}
	for i := range lines {
		line := strings.Trim(lines[i], " ")
		if len(line) == 0 {
			continue
		}
		out = append(out, line)
		lineNums = append(lineNums, i)
	}

	return out, lineNums
}

func associationList(lines []string) ([]string, []string, int) {
	names, vals := []string{}, []string{}
	for i := range lines {
		eq := strings.Index(lines[i], "=")
		if eq == -1 {
			return nil, nil, i
		}
		name := lines[i][:eq]
		val := ""
		if len(lines[i])-1 > eq {
			val = lines[i][eq+1:]
		}
		names = append(names, strings.ToLower(strings.Trim(name, " ")))
		if len(names[len(names)-1]) == 0 {
			return nil, nil, i
		}
		vals = append(vals, strings.Trim(val, " "))
	}
	return names, vals, -1
}

func checkValidNames(names []string, vars *ConfigVars) int {
	for i := range names {
		found := false
		for j := range vars.varNames {
			if vars.varNames[j] == names[i] {
				found = true
				break
			}
		}
		if !found {
			return i
		}
	}
	return -1
}

func checkDuplicateNames(names []string) (int, int) {
	for i := range names {
		for j := i + 1; j < len(names); j++ {
			if names[i] == names[j] {
				return i, j
			}
		}
	}
	return -1, -1
}

func convertAssoc(names, vals []string, vars *ConfigVars) int {
	for i := range names {
		j := 0
		for ; j < len(vars.varNames); j++ {
			if vars.varNames[j] == names[i] {
				break
			}
		}

		ok := vars.conversionFuncs[j](vals[i])
		if !ok {
			return i
		}
	}
	return -1
}
<|endoftext|>


<commit_before>package process

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/buildkite/agent/logger"
)

type Process struct {
	Pid        int
	PTY        bool
	Timestamp  bool
	Script     []string
	Env        []string
	ExitStatus string

	buffer  outputBuffer
	command *exec.Cmd

	// This callback is called when the process offically starts
	StartCallback func()

	// For every line in the process output, this callback will be called
	// with the contents of the line if its filter returns true.
	LineCallback       func(string)
	LinePreProcessor   func(string) string
	LineCallbackFilter func(string) bool

	// Running is stored as an int32 so we can use atomic operations to
	// set/get it (it's accessed by multiple goroutines)
	running int32

	mu   sync.Mutex
	done chan struct{}
}

// If you change header parsing here make sure to change it in the
// buildkite.com frontend logic, too

var headerExpansionRegex = regexp.MustCompile("^(?:\\^\\^\\^\\s+\\+\\+\\+)\\s*$")

func (p *Process) Start() error {
	p.command = exec.Command(p.Script[0], p.Script[1:]...)

	// Copy the current processes ENV and merge in the new ones. We do this
	// so the sub process gets PATH and stuff. We merge our path in over
	// the top of the current one so the ENV from Buildkite and the agent
	// take precedence over the agent
	currentEnv := os.Environ()
	p.command.Env = append(currentEnv, p.Env...)

	var waitGroup sync.WaitGroup

	lineReaderPipe, lineWriterPipe := io.Pipe()

	var multiWriter io.Writer
	if p.Timestamp {
		multiWriter = io.MultiWriter(lineWriterPipe)
	} else {
		multiWriter = io.MultiWriter(&p.buffer, lineWriterPipe)
	}

	// Toggle between running in a pty
	if p.PTY {
		pty, err := StartPTY(p.command)
		if err != nil {
			p.ExitStatus = "1"
			return err
		}

		p.Pid = p.command.Process.Pid
		p.setRunning(true)

		p.mu.Lock()
		p.done = make(chan struct{})
		p.mu.Unlock()

		waitGroup.Add(1)

		go func() {
			logger.Debug("[Process] Starting to copy PTY to the buffer")

			// Copy the pty to our buffer. This will block until it
			// EOF's or something breaks.
			_, err = io.Copy(multiWriter, pty)
			if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
				// We can safely ignore this error, because
				// it's just the PTY telling us that it closed
				// successfully.  See:
				// https://github.com/buildkite/agent/pull/34#issuecomment-46080419
				err = nil
			}

			if err != nil {
				logger.Error("[Process] PTY output copy failed with error: %T: %v", err, err)
			} else {
				logger.Debug("[Process] PTY has finished being copied to the buffer")
			}

			waitGroup.Done()
		}()
	} else {
		p.command.Stdout = multiWriter
		p.command.Stderr = multiWriter
		p.command.Stdin = nil

		err := p.command.Start()
		if err != nil {
			p.ExitStatus = "1"
			return err
		}

		p.Pid = p.command.Process.Pid
		p.setRunning(true)

		p.mu.Lock()
		p.done = make(chan struct{})
		p.mu.Unlock()
	}

	logger.Info("[Process] Process is running with PID: %d", p.Pid)

	// Add the line callback routine to the waitGroup
	waitGroup.Add(1)

	go func() {
		logger.Debug("[LineScanner] Starting to read lines")

		reader := bufio.NewReader(lineReaderPipe)

		var appending []byte
		var lineCallbackWaitGroup sync.WaitGroup

		for {
			line, isPrefix, err := reader.ReadLine()
			if err != nil {
				if err == io.EOF {
					logger.Debug("[LineScanner] Encountered EOF")
					break
				}

				logger.Error("[LineScanner] Failed to read: (%T: %v)", err, err)
			}

			// If isPrefix is true, that means we've got a really
			// long line incoming, and we'll keep appending to it
			// until isPrefix is false (which means the long line
			// has ended.
			if isPrefix && appending == nil {
				logger.Debug("[LineScanner] Line is too long to read, going to buffer it until it finishes")
				// bufio.ReadLine returns a slice which is only valid until the next invocation
				// since it points to its own internal buffer array. To accumulate the entire
				// result we make a copy of the first prefix, and insure there is spare capacity
				// for future appends to minimize the need for resizing on append.
				appending = make([]byte, len(line), (cap(line))*2)
				copy(appending, line)

				continue
			}

			// Should we be appending?
			if appending != nil {
				appending = append(appending, line...)

				// No more isPrefix! Line is finished!
				if !isPrefix {
					logger.Debug("[LineScanner] Finished buffering long line")
					line = appending

					// Reset appending back to nil
					appending = nil
				} else {
					continue
				}
			}

			// If we're timestamping this main thread will take
			// the hit of running the regex so we can build up
			// the timestamped buffer without breaking headers,
			// otherwise we let the goroutines take the perf hit.

			checkedForCallback := false
			lineHasCallback := false
			lineString := p.LinePreProcessor(string(line))

			// Create the prefixed buffer
			if p.Timestamp {
				lineHasCallback = p.LineCallbackFilter(lineString)
				checkedForCallback = true
				if lineHasCallback || headerExpansionRegex.MatchString(lineString) {
					// Don't timestamp special lines (e.g. header)
					p.buffer.WriteString(fmt.Sprintf("%s\n", line))
				} else {
					currentTime := time.Now().UTC().Format(time.RFC3339)
					p.buffer.WriteString(fmt.Sprintf("[%s] %s\n", currentTime, line))
				}
			}

			if lineHasCallback || !checkedForCallback {
				lineCallbackWaitGroup.Add(1)
				go func(line string) {
					defer lineCallbackWaitGroup.Done()
					if (checkedForCallback && lineHasCallback) || p.LineCallbackFilter(lineString) {
						p.LineCallback(line)
					}
				}(lineString)
			}
		}

		// We need to make sure all the line callbacks have finish before
		// finish up the process
		logger.Debug("[LineScanner] Waiting for callbacks to finish")
		lineCallbackWaitGroup.Wait()

		logger.Debug("[LineScanner] Finished")
		waitGroup.Done()
	}()

	// Call the StartCallback
	go p.StartCallback()

	// Wait until the process has finished. The returned error is nil if the command runs,
	// has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.
	waitResult := p.command.Wait()

	// Close the line writer pipe
	lineWriterPipe.Close()

	// The process is no longer running at this point
	p.setRunning(false)
	close(p.done)

	// Find the exit status of the script
	p.ExitStatus = getExitStatus(waitResult)

	logger.Info("Process with PID: %d finished with Exit Status: %s", p.Pid, p.ExitStatus)

	// Sometimes (in docker containers) io.Copy never seems to finish. This is a mega
	// hack around it. If it doesn't finish after 1 second, just continue.
	logger.Debug("[Process] Waiting for routines to finish")
	err := timeoutWait(&waitGroup)
	if err != nil {
		logger.Debug("[Process] Timed out waiting for wait group: (%T: %v)", err, err)
	}

	// No error occurred so we can return nil
	return nil
}

func (p *Process) Output() string {
	return p.buffer.String()
}

// Done returns a channel that is closed when the process finishes
func (p *Process) Done() <-chan struct{} {
	p.mu.Lock()
	if p.done == nil {
		p.done = make(chan struct{})
	}
	d := p.done
	p.mu.Unlock()
	return d
}

// Kill terminates the process gracefully. Initially a SIGTERM is sent, and
// then 10 seconds later a SIGTERM is sent.
func (p *Process) Kill() error {
	var err error
	if runtime.GOOS == "windows" {
		// Sending Interrupt on Windows is not implemented.
		// https://golang.org/src/os/exec.go?s=3842:3884#L110
		err = exec.Command("CMD", "/C", "TASKKILL", "/F", "/T", "/PID", strconv.Itoa(p.Pid)).Run()
	} else {
		// Send a sigterm
		err = p.signal(syscall.SIGTERM)
	}
	if err != nil {
		return err
	}

	select {
	// Was successfully terminated
	case <-p.Done():
		logger.Debug("[Process] Process with PID: %d has exited.", p.Pid)

	// Forcefully kill the process after 10 seconds
	case <-time.After(10 * time.Second):
		if err = p.signal(syscall.SIGKILL); err != nil {
			return err
		}
	}

	return nil
}

func (p *Process) signal(sig os.Signal) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.command != nil && p.command.Process != nil {
		logger.Debug("[Process] Sending signal: %s to PID: %d", sig.String(), p.Pid)

		err := p.command.Process.Signal(sig)
		if err != nil {
			logger.Error("[Process] Failed to send signal: %s to PID: %d (%T: %v)", sig.String(), p.Pid, err, err)
			return err
		}
	} else {
		logger.Debug("[Process] No process to signal yet")
	}

	return nil
}

// Returns whether or not the process is running
// Deprecated: use Done() instead
func (p *Process) IsRunning() bool {
	return atomic.LoadInt32(&p.running) != 0
}

// Sets the running flag of the process
func (p *Process) setRunning(r bool) {
	// Use the atomic package to avoid race conditions when setting the
	// `running` value from multiple routines
	if r {
		atomic.StoreInt32(&p.running, 1)
	} else {
		atomic.StoreInt32(&p.running, 0)
	}
}

// https://github.com/hnakamur/commango/blob/fe42b1cf82bf536ce7e24dceaef6656002e03743/os/executil/executil.go#L29
// TODO: Can this be better?
func getExitStatus(waitResult error) string {
	exitStatus := -1

	if waitResult != nil {
		if err, ok := waitResult.(*exec.ExitError); ok {
			if s, ok := err.Sys().(syscall.WaitStatus); ok {
				exitStatus = s.ExitStatus()
			} else {
				logger.Error("[Process] Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
			}
		} else {
			logger.Error("[Process] Unexpected error type in getExitStatus: %#v", waitResult)
		}
	} else {
		exitStatus = 0
	}

	return fmt.Sprintf("%d", exitStatus)
}

func timeoutWait(waitGroup *sync.WaitGroup) error {
	// Make a chanel that we'll use as a timeout
	c := make(chan int, 1)

	// Start waiting for the routines to finish
	go func() {
		waitGroup.Wait()
		c <- 1
	}()

	select {
	case _ = <-c:
		return nil
	case <-time.After(10 * time.Second):
		return errors.New("Timeout")
	}
}

// outputBuffer is a goroutine safe bytes.Buffer
type outputBuffer struct {
	sync.RWMutex
	buf bytes.Buffer
}

// Write appends the contents of p to the buffer, growing the buffer as needed. It returns
// the number of bytes written.
func (ob *outputBuffer) Write(p []byte) (n int, err error) {
	ob.Lock()
	defer ob.Unlock()
	return ob.buf.Write(p)
}

// WriteString appends the contents of s to the buffer, growing the buffer as needed. It returns
// the number of bytes written.
func (ob *outputBuffer) WriteString(s string) (n int, err error) {
	return ob.Write([]byte(s))
}

// String returns the contents of the unread portion of the buffer
// as a string.  If the Buffer is a nil pointer, it returns "<nil>".
func (ob *outputBuffer) String() string {
	ob.RLock()
	defer ob.RUnlock()
	return ob.buf.String()
}
<commit_msg>Create done channel earlier<commit_after>package process

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/buildkite/agent/logger"
)

type Process struct {
	Pid        int
	PTY        bool
	Timestamp  bool
	Script     []string
	Env        []string
	ExitStatus string

	buffer  outputBuffer
	command *exec.Cmd

	// This callback is called when the process offically starts
	StartCallback func()

	// For every line in the process output, this callback will be called
	// with the contents of the line if its filter returns true.
	LineCallback       func(string)
	LinePreProcessor   func(string) string
	LineCallbackFilter func(string) bool

	// Running is stored as an int32 so we can use atomic operations to
	// set/get it (it's accessed by multiple goroutines)
	running int32

	mu   sync.Mutex
	done chan struct{}
}

// If you change header parsing here make sure to change it in the
// buildkite.com frontend logic, too

var headerExpansionRegex = regexp.MustCompile("^(?:\\^\\^\\^\\s+\\+\\+\\+)\\s*$")

// Start executes the command and blocks until it finishes
func (p *Process) Start() error {
	if p.IsRunning() {
		return fmt.Errorf("Process is already running")
	}

	p.command = exec.Command(p.Script[0], p.Script[1:]...)

	p.mu.Lock()
	if p.done == nil {
		p.done = make(chan struct{})
	}
	p.mu.Unlock()

	// Copy the current processes ENV and merge in the new ones. We do this
	// so the sub process gets PATH and stuff. We merge our path in over
	// the top of the current one so the ENV from Buildkite and the agent
	// take precedence over the agent
	currentEnv := os.Environ()
	p.command.Env = append(currentEnv, p.Env...)

	var waitGroup sync.WaitGroup

	lineReaderPipe, lineWriterPipe := io.Pipe()

	var multiWriter io.Writer
	if p.Timestamp {
		multiWriter = io.MultiWriter(lineWriterPipe)
	} else {
		multiWriter = io.MultiWriter(&p.buffer, lineWriterPipe)
	}

	// Toggle between running in a pty
	if p.PTY {
		pty, err := StartPTY(p.command)
		if err != nil {
			p.ExitStatus = "1"
			return err
		}

		p.Pid = p.command.Process.Pid
		p.setRunning(true)

		waitGroup.Add(1)

		go func() {
			logger.Debug("[Process] Starting to copy PTY to the buffer")

			// Copy the pty to our buffer. This will block until it
			// EOF's or something breaks.
			_, err = io.Copy(multiWriter, pty)
			if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
				// We can safely ignore this error, because
				// it's just the PTY telling us that it closed
				// successfully.  See:
				// https://github.com/buildkite/agent/pull/34#issuecomment-46080419
				err = nil
			}

			if err != nil {
				logger.Error("[Process] PTY output copy failed with error: %T: %v", err, err)
			} else {
				logger.Debug("[Process] PTY has finished being copied to the buffer")
			}

			waitGroup.Done()
		}()
	} else {
		p.command.Stdout = multiWriter
		p.command.Stderr = multiWriter
		p.command.Stdin = nil

		err := p.command.Start()
		if err != nil {
			p.ExitStatus = "1"
			return err
		}

		p.Pid = p.command.Process.Pid
		p.setRunning(true)
	}

	logger.Info("[Process] Process is running with PID: %d", p.Pid)

	// Add the line callback routine to the waitGroup
	waitGroup.Add(1)

	go func() {
		logger.Debug("[LineScanner] Starting to read lines")

		reader := bufio.NewReader(lineReaderPipe)

		var appending []byte
		var lineCallbackWaitGroup sync.WaitGroup

		for {
			line, isPrefix, err := reader.ReadLine()
			if err != nil {
				if err == io.EOF {
					logger.Debug("[LineScanner] Encountered EOF")
					break
				}

				logger.Error("[LineScanner] Failed to read: (%T: %v)", err, err)
			}

			// If isPrefix is true, that means we've got a really
			// long line incoming, and we'll keep appending to it
			// until isPrefix is false (which means the long line
			// has ended.
			if isPrefix && appending == nil {
				logger.Debug("[LineScanner] Line is too long to read, going to buffer it until it finishes")
				// bufio.ReadLine returns a slice which is only valid until the next invocation
				// since it points to its own internal buffer array. To accumulate the entire
				// result we make a copy of the first prefix, and insure there is spare capacity
				// for future appends to minimize the need for resizing on append.
				appending = make([]byte, len(line), (cap(line))*2)
				copy(appending, line)

				continue
			}

			// Should we be appending?
			if appending != nil {
				appending = append(appending, line...)

				// No more isPrefix! Line is finished!
				if !isPrefix {
					logger.Debug("[LineScanner] Finished buffering long line")
					line = appending

					// Reset appending back to nil
					appending = nil
				} else {
					continue
				}
			}

			// If we're timestamping this main thread will take
			// the hit of running the regex so we can build up
			// the timestamped buffer without breaking headers,
			// otherwise we let the goroutines take the perf hit.

			checkedForCallback := false
			lineHasCallback := false
			lineString := p.LinePreProcessor(string(line))

			// Create the prefixed buffer
			if p.Timestamp {
				lineHasCallback = p.LineCallbackFilter(lineString)
				checkedForCallback = true
				if lineHasCallback || headerExpansionRegex.MatchString(lineString) {
					// Don't timestamp special lines (e.g. header)
					p.buffer.WriteString(fmt.Sprintf("%s\n", line))
				} else {
					currentTime := time.Now().UTC().Format(time.RFC3339)
					p.buffer.WriteString(fmt.Sprintf("[%s] %s\n", currentTime, line))
				}
			}

			if lineHasCallback || !checkedForCallback {
				lineCallbackWaitGroup.Add(1)
				go func(line string) {
					defer lineCallbackWaitGroup.Done()
					if (checkedForCallback && lineHasCallback) || p.LineCallbackFilter(lineString) {
						p.LineCallback(line)
					}
				}(lineString)
			}
		}

		// We need to make sure all the line callbacks have finish before
		// finish up the process
		logger.Debug("[LineScanner] Waiting for callbacks to finish")
		lineCallbackWaitGroup.Wait()

		logger.Debug("[LineScanner] Finished")
		waitGroup.Done()
	}()

	// Call the StartCallback
	go p.StartCallback()

	// Wait until the process has finished. The returned error is nil if the command runs,
	// has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.
	waitResult := p.command.Wait()

	// Close the line writer pipe
	lineWriterPipe.Close()

	// The process is no longer running at this point
	p.setRunning(false)
	close(p.done)

	// Find the exit status of the script
	p.ExitStatus = getExitStatus(waitResult)

	logger.Info("Process with PID: %d finished with Exit Status: %s", p.Pid, p.ExitStatus)

	// Sometimes (in docker containers) io.Copy never seems to finish. This is a mega
	// hack around it. If it doesn't finish after 1 second, just continue.
	logger.Debug("[Process] Waiting for routines to finish")
	err := timeoutWait(&waitGroup)
	if err != nil {
		logger.Debug("[Process] Timed out waiting for wait group: (%T: %v)", err, err)
	}

	// No error occurred so we can return nil
	return nil
}

// Output returns the current state of the output buffer and can be called incrementally
func (p *Process) Output() string {
	return p.buffer.String()
}

// Done returns a channel that is closed when the process finishes
func (p *Process) Done() <-chan struct{} {
	p.mu.Lock()
	if p.done == nil {
		p.done = make(chan struct{})
	}
	d := p.done
	p.mu.Unlock()
	return d
}

// Kill terminates the process gracefully. Initially a SIGTERM is sent, and
// then 10 seconds later a SIGTERM is sent.
func (p *Process) Kill() error {
	var err error
	if runtime.GOOS == "windows" {
		// Sending Interrupt on Windows is not implemented.
		// https://golang.org/src/os/exec.go?s=3842:3884#L110
		err = exec.Command("CMD", "/C", "TASKKILL", "/F", "/T", "/PID", strconv.Itoa(p.Pid)).Run()
	} else {
		// Send a sigterm
		err = p.signal(syscall.SIGTERM)
	}
	if err != nil {
		return err
	}

	select {
	// Was successfully terminated
	case <-p.Done():
		logger.Debug("[Process] Process with PID: %d has exited.", p.Pid)

	// Forcefully kill the process after 10 seconds
	case <-time.After(10 * time.Second):
		if err = p.signal(syscall.SIGKILL); err != nil {
			return err
		}
	}

	return nil
}

func (p *Process) signal(sig os.Signal) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.command != nil && p.command.Process != nil {
		logger.Debug("[Process] Sending signal: %s to PID: %d", sig.String(), p.Pid)

		err := p.command.Process.Signal(sig)
		if err != nil {
			logger.Error("[Process] Failed to send signal: %s to PID: %d (%T: %v)", sig.String(), p.Pid, err, err)
			return err
		}
	} else {
		logger.Debug("[Process] No process to signal yet")
	}

	return nil
}

// Returns whether or not the process is running
// Deprecated: use Done() instead
func (p *Process) IsRunning() bool {
	return atomic.LoadInt32(&p.running) != 0
}

// Sets the running flag of the process
func (p *Process) setRunning(r bool) {
	// Use the atomic package to avoid race conditions when setting the
	// `running` value from multiple routines
	if r {
		atomic.StoreInt32(&p.running, 1)
	} else {
		atomic.StoreInt32(&p.running, 0)
	}
}

// https://github.com/hnakamur/commango/blob/fe42b1cf82bf536ce7e24dceaef6656002e03743/os/executil/executil.go#L29
// TODO: Can this be better?
func getExitStatus(waitResult error) string {
	exitStatus := -1

	if waitResult != nil {
		if err, ok := waitResult.(*exec.ExitError); ok {
			if s, ok := err.Sys().(syscall.WaitStatus); ok {
				exitStatus = s.ExitStatus()
			} else {
				logger.Error("[Process] Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.")
			}
		} else {
			logger.Error("[Process] Unexpected error type in getExitStatus: %#v", waitResult)
		}
	} else {
		exitStatus = 0
	}

	return fmt.Sprintf("%d", exitStatus)
}

func timeoutWait(waitGroup *sync.WaitGroup) error {
	// Make a chanel that we'll use as a timeout
	c := make(chan int, 1)

	// Start waiting for the routines to finish
	go func() {
		waitGroup.Wait()
		c <- 1
	}()

	select {
	case _ = <-c:
		return nil
	case <-time.After(10 * time.Second):
		return errors.New("Timeout")
	}
}

// outputBuffer is a goroutine safe bytes.Buffer
type outputBuffer struct {
	sync.RWMutex
	buf bytes.Buffer
}

// Write appends the contents of p to the buffer, growing the buffer as needed. It returns
// the number of bytes written.
func (ob *outputBuffer) Write(p []byte) (n int, err error) {
	ob.Lock()
	defer ob.Unlock()
	return ob.buf.Write(p)
}

// WriteString appends the contents of s to the buffer, growing the buffer as needed. It returns
// the number of bytes written.
func (ob *outputBuffer) WriteString(s string) (n int, err error) {
	return ob.Write([]byte(s))
}

// String returns the contents of the unread portion of the buffer
// as a string.  If the Buffer is a nil pointer, it returns "<nil>".
func (ob *outputBuffer) String() string {
	ob.RLock()
	defer ob.RUnlock()
	return ob.buf.String()
}
<|endoftext|>


<commit_before>package main

/*

	This is an example OpenGL app. A Drawing tool based on http://mrdoob.com/projects/harmony/

*/

import "sdl"
import "gl"
import "math"

type Point struct {
	x int
	y int
}

func (p0 Point) distanceTo(p1 Point) float64 {
	dx := (p0.x - p1.x)
	dy := (p0.y - p1.y)
	return math.Sqrt(float64(dx*dx + dy*dy))
}

type Pen struct {
	pos    Point
	points [4096]Point
	n      int
}

func (pen *Pen) lineTo(p Point) {

	gl.Enable(gl.BLEND)
	gl.Enable(gl.POINT_SMOOTH)
	gl.Enable(gl.LINE_SMOOTH)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

	gl.Color4f(0.0, 0.0, 0.0, 0.1)

	gl.Begin(gl.LINES)

	for _, s := range pen.points {

		if s.x == 0 && s.y == 0 {
			continue
		}

		if p.distanceTo(s) < 20.0 {

			gl.Vertex2i(int(p.x), int(p.y))
			gl.Vertex2i(int(s.x), int(s.y))

		}

	}

	gl.End()

	pen.n = (pen.n + 1) % len(pen.points)
	pen.points[pen.n] = p

	pen.moveTo(p)

}

func (pen *Pen) moveTo(p Point) {
	pen.pos = p
}

func main() {

	sdl.Init(sdl.INIT_VIDEO)

	var screen = sdl.SetVideoMode(640, 480, 32, sdl.OPENGL)

	if screen == nil {
		panic("sdl error")
	}

	if gl.Init() != 0 {
		panic("glew error")
	}

	pen := Pen{}

	gl.MatrixMode(gl.PROJECTION)

	gl.Viewport(0, 0, int(screen.W), int(screen.H))
	gl.LoadIdentity()
	gl.Ortho(0, float64(screen.W), float64(screen.H), 0, -1.0, 1.0)

	gl.ClearColor(1, 1, 1, 0)
	gl.Clear(gl.COLOR_BUFFER_BIT)

	var running = true

	for running {

		e := &sdl.Event{}

		for e.Poll() {
			switch e.Type {
			case sdl.QUIT:
				running = false
				break
			case sdl.KEYDOWN:
				running = false
				break
			case sdl.MOUSEMOTION:
				me := e.MouseMotion()
				if me.State != 0 {
					pen.lineTo(Point{int(me.X), int(me.Y)})
				} else {
					pen.moveTo(Point{int(me.X), int(me.Y)})
				}
				break
			}
		}

		sdl.GL_SwapBuffers()
		sdl.Delay(25)
	}

	sdl.Quit()

}
<commit_msg>remove gl.Init<commit_after>package main

/*

	This is an example OpenGL app. A Drawing tool based on http://mrdoob.com/projects/harmony/

*/

import "sdl"
import "gl"
import "math"

type Point struct {
	x int
	y int
}

func (p0 Point) distanceTo(p1 Point) float64 {
	dx := (p0.x - p1.x)
	dy := (p0.y - p1.y)
	return math.Sqrt(float64(dx*dx + dy*dy))
}

type Pen struct {
	pos    Point
	points [4096]Point
	n      int
}

func (pen *Pen) lineTo(p Point) {

	gl.Enable(gl.BLEND)
	gl.Enable(gl.POINT_SMOOTH)
	gl.Enable(gl.LINE_SMOOTH)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

	gl.Color4f(0.0, 0.0, 0.0, 0.1)

	gl.Begin(gl.LINES)

	for _, s := range pen.points {

		if s.x == 0 && s.y == 0 {
			continue
		}

		if p.distanceTo(s) < 20.0 {

			gl.Vertex2i(int(p.x), int(p.y))
			gl.Vertex2i(int(s.x), int(s.y))

		}

	}

	gl.End()

	pen.n = (pen.n + 1) % len(pen.points)
	pen.points[pen.n] = p

	pen.moveTo(p)

}

func (pen *Pen) moveTo(p Point) {
	pen.pos = p
}

func main() {

	sdl.Init(sdl.INIT_VIDEO)

	var screen = sdl.SetVideoMode(640, 480, 32, sdl.OPENGL)

	if screen == nil {
		panic("sdl error")
	}

	pen := Pen{}

	gl.MatrixMode(gl.PROJECTION)

	gl.Viewport(0, 0, int(screen.W), int(screen.H))
	gl.LoadIdentity()
	gl.Ortho(0, float64(screen.W), float64(screen.H), 0, -1.0, 1.0)

	gl.ClearColor(1, 1, 1, 0)
	gl.Clear(gl.COLOR_BUFFER_BIT)

	var running = true

	for running {

		e := &sdl.Event{}

		for e.Poll() {
			switch e.Type {
			case sdl.QUIT:
				running = false
				break
			case sdl.KEYDOWN:
				running = false
				break
			case sdl.MOUSEMOTION:
				me := e.MouseMotion()
				if me.State != 0 {
					pen.lineTo(Point{int(me.X), int(me.Y)})
				} else {
					pen.moveTo(Point{int(me.X), int(me.Y)})
				}
				break
			}
		}

		sdl.GL_SwapBuffers()
		sdl.Delay(25)
	}

	sdl.Quit()

}
<|endoftext|>