<commit_before>package iso

import (
	"errors"
	"fmt"
	"log"
	"path"
	"strings"
	"time"

	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/communicator"
	hconfig "github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/template/interpolate"
	xsclient "github.com/xenserver/go-xenserver-client"
	xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common"
)

type config struct {
	common.PackerConfig   `mapstructure:",squash"`
	xscommon.CommonConfig `mapstructure:",squash"`

	VCPUsMax       uint              `mapstructure:"vcpus_max"`
	VCPUsAtStartup uint              `mapstructure:"vcpus_atstartup"`
	VMMemory       uint              `mapstructure:"vm_memory"`
	DiskSize       uint              `mapstructure:"disk_size"`
	CloneTemplate  string            `mapstructure:"clone_template"`
	VMOtherConfig  map[string]string `mapstructure:"vm_other_config"`

	ISOChecksum     string   `mapstructure:"iso_checksum"`
	ISOChecksumType string   `mapstructure:"iso_checksum_type"`
	ISOUrls         []string `mapstructure:"iso_urls"`
	ISOUrl          string   `mapstructure:"iso_url"`
	ISOName         string   `mapstructure:"iso_name"`

	PlatformArgs map[string]string `mapstructure:"platform_args"`

	RawInstallTimeout string        `mapstructure:"install_timeout"`
	InstallTimeout    time.Duration ``

	ctx interpolate.Context
}

type Builder struct {
	config config
	runner multistep.Runner
}

func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error) {

	var errs *packer.MultiError

	err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{
		Interpolate: true,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"boot_command",
			},
		},
	}, raws...)

	if err != nil {
		packer.MultiErrorAppend(errs, err)
	}

	errs = packer.MultiErrorAppend(
		errs, self.config.CommonConfig.Prepare(&self.config.ctx, &self.config.PackerConfig)...)
	errs = packer.MultiErrorAppend(errs, self.config.SSHConfig.Prepare(&self.config.ctx)...)

	// Set default values

	if self.config.RawInstallTimeout == "" {
		self.config.RawInstallTimeout = "200m"
	}

	if self.config.DiskSize == 0 {
		self.config.DiskSize = 40000
	}

	if self.config.VCPUsMax == 0 {
		self.config.VCPUsMax = 1
	}

	if self.config.VCPUsAtStartup == 0 {
		self.config.VCPUsAtStartup = 1
	}

	if self.config.VCPUsAtStartup > self.config.VCPUsMax {
		self.config.VCPUsAtStartup = self.config.VCPUsMax
	}

	if self.config.VMMemory == 0 {
		self.config.VMMemory = 1024
	}

	if self.config.CloneTemplate == "" {
		self.config.CloneTemplate = "Other install media"
	}

	if len(self.config.PlatformArgs) == 0 {
		pargs := make(map[string]string)
		pargs["viridian"] = "false"
		pargs["nx"] = "true"
		pargs["pae"] = "true"
		pargs["apic"] = "true"
		pargs["timeoffset"] = "0"
		pargs["acpi"] = "1"
		pargs["cores-per-socket"] = "1"
		self.config.PlatformArgs = pargs
	}

	// Template substitution

	templates := map[string]*string{
		"clone_template":    &self.config.CloneTemplate,
		"iso_checksum":      &self.config.ISOChecksum,
		"iso_checksum_type": &self.config.ISOChecksumType,
		"iso_url":           &self.config.ISOUrl,
		"iso_name":          &self.config.ISOName,
		"install_timeout":   &self.config.RawInstallTimeout,
	}
	for i := range self.config.ISOUrls {
		templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i]
	}

	// Validation

	self.config.InstallTimeout, err = time.ParseDuration(self.config.RawInstallTimeout)
	if err != nil {
		errs = packer.MultiErrorAppend(
			errs, fmt.Errorf("Failed to parse install_timeout: %s", err))
	}

	if self.config.ISOName == "" {

		// If ISO name is not specified, assume a URL and checksum has been provided.

		if self.config.ISOChecksumType == "" {
			errs = packer.MultiErrorAppend(
				errs, errors.New("The iso_checksum_type must be specified."))
		} else {
			self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
			if self.config.ISOChecksumType != "none" {
				if self.config.ISOChecksum == "" {
					errs = packer.MultiErrorAppend(
						errs, errors.New("Due to the file size being large, an iso_checksum is required."))
				} else {
					self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
				}

				if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
					errs = packer.MultiErrorAppend(
						errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
				}

			}
		}

		if len(self.config.ISOUrls) == 0 {
			if self.config.ISOUrl == "" {
				errs = packer.MultiErrorAppend(
					errs, errors.New("One of iso_url or iso_urls must be specified."))
			} else {
				self.config.ISOUrls = []string{self.config.ISOUrl}
			}
		} else if self.config.ISOUrl != "" {
			errs = packer.MultiErrorAppend(
				errs, errors.New("Only one of iso_url or iso_urls may be specified."))
		}

		for i, url := range self.config.ISOUrls {
			self.config.ISOUrls[i], err = common.DownloadableURL(url)
			if err != nil {
				errs = packer.MultiErrorAppend(
					errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err))
			}
		}
	} else {

		// An ISO name has been provided. It should be attached from an available SR.

	}

	if len(errs.Errors) > 0 {
		retErr = errors.New(errs.Error())
	}

	return nil, retErr

}

func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
	//Setup XAPI client
	client := xsclient.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)

	err := client.Login()
	if err != nil {
		return nil, err.(error)
	}
	ui.Say("XAPI client session established")

	client.GetHosts()

	//Share state between the other steps using a statebag
	state := new(multistep.BasicStateBag)
	state.Put("cache", cache)
	state.Put("client", client)
	state.Put("config", self.config)
	state.Put("commonconfig", self.config.CommonConfig)
	state.Put("hook", hook)
	state.Put("ui", ui)

	httpReqChan := make(chan string, 1)

	//Build the steps
	download_steps := []multistep.Step{
		&common.StepDownload{
			Checksum:     self.config.ISOChecksum,
			ChecksumType: self.config.ISOChecksumType,
			Description:  "ISO",
			ResultKey:    "iso_path",
			Url:          self.config.ISOUrls,
		},
	}

	steps := []multistep.Step{
		&xscommon.StepPrepareOutputDir{
			Force: self.config.PackerForce,
			Path:  self.config.OutputDir,
		},
		&common.StepCreateFloppy{
			Files: self.config.FloppyFiles,
		},
		&xscommon.StepHTTPServer{
			Chan: httpReqChan,
		},
		&xscommon.StepUploadVdi{
			VdiNameFunc: func() string {
				return "Packer-floppy-disk"
			},
			ImagePathFunc: func() string {
				if floppyPath, ok := state.GetOk("floppy_path"); ok {
					return floppyPath.(string)
				}
				return ""
			},
			VdiUuidKey: "floppy_vdi_uuid",
		},
		&xscommon.StepUploadVdi{
			VdiNameFunc: func() string {
				if len(self.config.ISOUrls) > 0 {
					return path.Base(self.config.ISOUrls[0])
				}
				return ""
			},
			ImagePathFunc: func() string {
				if isoPath, ok := state.GetOk("iso_path"); ok {
					return isoPath.(string)
				}
				return ""
			},
			VdiUuidKey: "iso_vdi_uuid",
		},
		&xscommon.StepFindVdi{
			VdiName:    self.config.ToolsIsoName,
			VdiUuidKey: "tools_vdi_uuid",
		},
		&xscommon.StepFindVdi{
			VdiName:    self.config.ISOName,
			VdiUuidKey: "isoname_vdi_uuid",
		},
		new(stepCreateInstance),
		&xscommon.StepAttachVdi{
			VdiUuidKey: "floppy_vdi_uuid",
			VdiType:    xsclient.Floppy,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "iso_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "isoname_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "tools_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		new(xscommon.StepStartVmPaused),
		new(xscommon.StepSetVmHostSshAddress),
		new(xscommon.StepGetVNCPort),
		&xscommon.StepForwardPortOverSSH{
			RemotePort:  xscommon.InstanceVNCPort,
			RemoteDest:  xscommon.InstanceVNCIP,
			HostPortMin: self.config.HostPortMin,
			HostPortMax: self.config.HostPortMax,
			ResultKey:   "local_vnc_port",
		},
		new(xscommon.StepBootWait),
		&xscommon.StepTypeBootCommand{
			Ctx: self.config.ctx,
		},
		&xscommon.StepWaitForIP{
			Chan:    httpReqChan,
			Timeout: self.config.InstallTimeout, // @todo change this
		},
		&xscommon.StepForwardPortOverSSH{
			RemotePort:  xscommon.InstanceSSHPort,
			RemoteDest:  xscommon.InstanceSSHIP,
			HostPortMin: self.config.HostPortMin,
			HostPortMax: self.config.HostPortMax,
			ResultKey:   "local_ssh_port",
		},
		&communicator.StepConnect{
			Config:    &self.config.SSHConfig.Comm,
			Host:      xscommon.CommHost,
			SSHConfig: xscommon.SSHConfigFunc(self.config.CommonConfig.SSHConfig),
			SSHPort:   xscommon.SSHPort,
		},
		new(common.StepProvision),
		new(xscommon.StepShutdown),
		&xscommon.StepDetachVdi{
			VdiUuidKey: "iso_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "isoname_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "tools_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "floppy_vdi_uuid",
		},
		new(xscommon.StepExport),
	}

	if self.config.ISOName == "" {
		steps = append(download_steps, steps...)
	}

	self.runner = &multistep.BasicRunner{Steps: steps}
	self.runner.Run(state)

	if rawErr, ok := state.GetOk("error"); ok {
		return nil, rawErr.(error)
	}

	// If we were interrupted or cancelled, then just exit.
	if _, ok := state.GetOk(multistep.StateCancelled); ok {
		return nil, errors.New("Build was cancelled.")
	}
	if _, ok := state.GetOk(multistep.StateHalted); ok {
		return nil, errors.New("Build was halted.")
	}

	artifact, _ := xscommon.NewArtifact(self.config.OutputDir)

	return artifact, nil
}

func (self *Builder) Cancel() {
	if self.runner != nil {
		log.Println("Cancelling the step runner...")
		self.runner.Cancel()
	}
	fmt.Println("Cancelling the builder")
}
<commit_msg>Fix comments: remove redundant default setting of cores-per-socket.<commit_after>package iso

import (
	"errors"
	"fmt"
	"log"
	"path"
	"strings"
	"time"

	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/communicator"
	hconfig "github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/template/interpolate"
	xsclient "github.com/xenserver/go-xenserver-client"
	xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common"
)

type config struct {
	common.PackerConfig   `mapstructure:",squash"`
	xscommon.CommonConfig `mapstructure:",squash"`

	VCPUsMax       uint              `mapstructure:"vcpus_max"`
	VCPUsAtStartup uint              `mapstructure:"vcpus_atstartup"`
	VMMemory       uint              `mapstructure:"vm_memory"`
	DiskSize       uint              `mapstructure:"disk_size"`
	CloneTemplate  string            `mapstructure:"clone_template"`
	VMOtherConfig  map[string]string `mapstructure:"vm_other_config"`

	ISOChecksum     string   `mapstructure:"iso_checksum"`
	ISOChecksumType string   `mapstructure:"iso_checksum_type"`
	ISOUrls         []string `mapstructure:"iso_urls"`
	ISOUrl          string   `mapstructure:"iso_url"`
	ISOName         string   `mapstructure:"iso_name"`

	PlatformArgs map[string]string `mapstructure:"platform_args"`

	RawInstallTimeout string        `mapstructure:"install_timeout"`
	InstallTimeout    time.Duration ``

	ctx interpolate.Context
}

type Builder struct {
	config config
	runner multistep.Runner
}

func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error) {

	var errs *packer.MultiError

	err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{
		Interpolate: true,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"boot_command",
			},
		},
	}, raws...)

	if err != nil {
		packer.MultiErrorAppend(errs, err)
	}

	errs = packer.MultiErrorAppend(
		errs, self.config.CommonConfig.Prepare(&self.config.ctx, &self.config.PackerConfig)...)
	errs = packer.MultiErrorAppend(errs, self.config.SSHConfig.Prepare(&self.config.ctx)...)

	// Set default values

	if self.config.RawInstallTimeout == "" {
		self.config.RawInstallTimeout = "200m"
	}

	if self.config.DiskSize == 0 {
		self.config.DiskSize = 40000
	}

	if self.config.VCPUsMax == 0 {
		self.config.VCPUsMax = 1
	}

	if self.config.VCPUsAtStartup == 0 {
		self.config.VCPUsAtStartup = 1
	}

	if self.config.VCPUsAtStartup > self.config.VCPUsMax {
		self.config.VCPUsAtStartup = self.config.VCPUsMax
	}

	if self.config.VMMemory == 0 {
		self.config.VMMemory = 1024
	}

	if self.config.CloneTemplate == "" {
		self.config.CloneTemplate = "Other install media"
	}

	if len(self.config.PlatformArgs) == 0 {
		pargs := make(map[string]string)
		pargs["viridian"] = "false"
		pargs["nx"] = "true"
		pargs["pae"] = "true"
		pargs["apic"] = "true"
		pargs["timeoffset"] = "0"
		pargs["acpi"] = "1"
		self.config.PlatformArgs = pargs
	}

	// Template substitution

	templates := map[string]*string{
		"clone_template":    &self.config.CloneTemplate,
		"iso_checksum":      &self.config.ISOChecksum,
		"iso_checksum_type": &self.config.ISOChecksumType,
		"iso_url":           &self.config.ISOUrl,
		"iso_name":          &self.config.ISOName,
		"install_timeout":   &self.config.RawInstallTimeout,
	}
	for i := range self.config.ISOUrls {
		templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i]
	}

	// Validation

	self.config.InstallTimeout, err = time.ParseDuration(self.config.RawInstallTimeout)
	if err != nil {
		errs = packer.MultiErrorAppend(
			errs, fmt.Errorf("Failed to parse install_timeout: %s", err))
	}

	if self.config.ISOName == "" {

		// If ISO name is not specified, assume a URL and checksum has been provided.

		if self.config.ISOChecksumType == "" {
			errs = packer.MultiErrorAppend(
				errs, errors.New("The iso_checksum_type must be specified."))
		} else {
			self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
			if self.config.ISOChecksumType != "none" {
				if self.config.ISOChecksum == "" {
					errs = packer.MultiErrorAppend(
						errs, errors.New("Due to the file size being large, an iso_checksum is required."))
				} else {
					self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
				}

				if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
					errs = packer.MultiErrorAppend(
						errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
				}

			}
		}

		if len(self.config.ISOUrls) == 0 {
			if self.config.ISOUrl == "" {
				errs = packer.MultiErrorAppend(
					errs, errors.New("One of iso_url or iso_urls must be specified."))
			} else {
				self.config.ISOUrls = []string{self.config.ISOUrl}
			}
		} else if self.config.ISOUrl != "" {
			errs = packer.MultiErrorAppend(
				errs, errors.New("Only one of iso_url or iso_urls may be specified."))
		}

		for i, url := range self.config.ISOUrls {
			self.config.ISOUrls[i], err = common.DownloadableURL(url)
			if err != nil {
				errs = packer.MultiErrorAppend(
					errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err))
			}
		}
	} else {

		// An ISO name has been provided. It should be attached from an available SR.

	}

	if len(errs.Errors) > 0 {
		retErr = errors.New(errs.Error())
	}

	return nil, retErr

}

func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
	//Setup XAPI client
	client := xsclient.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)

	err := client.Login()
	if err != nil {
		return nil, err.(error)
	}
	ui.Say("XAPI client session established")

	client.GetHosts()

	//Share state between the other steps using a statebag
	state := new(multistep.BasicStateBag)
	state.Put("cache", cache)
	state.Put("client", client)
	state.Put("config", self.config)
	state.Put("commonconfig", self.config.CommonConfig)
	state.Put("hook", hook)
	state.Put("ui", ui)

	httpReqChan := make(chan string, 1)

	//Build the steps
	download_steps := []multistep.Step{
		&common.StepDownload{
			Checksum:     self.config.ISOChecksum,
			ChecksumType: self.config.ISOChecksumType,
			Description:  "ISO",
			ResultKey:    "iso_path",
			Url:          self.config.ISOUrls,
		},
	}

	steps := []multistep.Step{
		&xscommon.StepPrepareOutputDir{
			Force: self.config.PackerForce,
			Path:  self.config.OutputDir,
		},
		&common.StepCreateFloppy{
			Files: self.config.FloppyFiles,
		},
		&xscommon.StepHTTPServer{
			Chan: httpReqChan,
		},
		&xscommon.StepUploadVdi{
			VdiNameFunc: func() string {
				return "Packer-floppy-disk"
			},
			ImagePathFunc: func() string {
				if floppyPath, ok := state.GetOk("floppy_path"); ok {
					return floppyPath.(string)
				}
				return ""
			},
			VdiUuidKey: "floppy_vdi_uuid",
		},
		&xscommon.StepUploadVdi{
			VdiNameFunc: func() string {
				if len(self.config.ISOUrls) > 0 {
					return path.Base(self.config.ISOUrls[0])
				}
				return ""
			},
			ImagePathFunc: func() string {
				if isoPath, ok := state.GetOk("iso_path"); ok {
					return isoPath.(string)
				}
				return ""
			},
			VdiUuidKey: "iso_vdi_uuid",
		},
		&xscommon.StepFindVdi{
			VdiName:    self.config.ToolsIsoName,
			VdiUuidKey: "tools_vdi_uuid",
		},
		&xscommon.StepFindVdi{
			VdiName:    self.config.ISOName,
			VdiUuidKey: "isoname_vdi_uuid",
		},
		new(stepCreateInstance),
		&xscommon.StepAttachVdi{
			VdiUuidKey: "floppy_vdi_uuid",
			VdiType:    xsclient.Floppy,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "iso_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "isoname_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		&xscommon.StepAttachVdi{
			VdiUuidKey: "tools_vdi_uuid",
			VdiType:    xsclient.CD,
		},
		new(xscommon.StepStartVmPaused),
		new(xscommon.StepSetVmHostSshAddress),
		new(xscommon.StepGetVNCPort),
		&xscommon.StepForwardPortOverSSH{
			RemotePort:  xscommon.InstanceVNCPort,
			RemoteDest:  xscommon.InstanceVNCIP,
			HostPortMin: self.config.HostPortMin,
			HostPortMax: self.config.HostPortMax,
			ResultKey:   "local_vnc_port",
		},
		new(xscommon.StepBootWait),
		&xscommon.StepTypeBootCommand{
			Ctx: self.config.ctx,
		},
		&xscommon.StepWaitForIP{
			Chan:    httpReqChan,
			Timeout: self.config.InstallTimeout, // @todo change this
		},
		&xscommon.StepForwardPortOverSSH{
			RemotePort:  xscommon.InstanceSSHPort,
			RemoteDest:  xscommon.InstanceSSHIP,
			HostPortMin: self.config.HostPortMin,
			HostPortMax: self.config.HostPortMax,
			ResultKey:   "local_ssh_port",
		},
		&communicator.StepConnect{
			Config:    &self.config.SSHConfig.Comm,
			Host:      xscommon.CommHost,
			SSHConfig: xscommon.SSHConfigFunc(self.config.CommonConfig.SSHConfig),
			SSHPort:   xscommon.SSHPort,
		},
		new(common.StepProvision),
		new(xscommon.StepShutdown),
		&xscommon.StepDetachVdi{
			VdiUuidKey: "iso_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "isoname_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "tools_vdi_uuid",
		},
		&xscommon.StepDetachVdi{
			VdiUuidKey: "floppy_vdi_uuid",
		},
		new(xscommon.StepExport),
	}

	if self.config.ISOName == "" {
		steps = append(download_steps, steps...)
	}

	self.runner = &multistep.BasicRunner{Steps: steps}
	self.runner.Run(state)

	if rawErr, ok := state.GetOk("error"); ok {
		return nil, rawErr.(error)
	}

	// If we were interrupted or cancelled, then just exit.
	if _, ok := state.GetOk(multistep.StateCancelled); ok {
		return nil, errors.New("Build was cancelled.")
	}
	if _, ok := state.GetOk(multistep.StateHalted); ok {
		return nil, errors.New("Build was halted.")
	}

	artifact, _ := xscommon.NewArtifact(self.config.OutputDir)

	return artifact, nil
}

func (self *Builder) Cancel() {
	if self.runner != nil {
		log.Println("Cancelling the step runner...")
		self.runner.Cancel()
	}
	fmt.Println("Cancelling the builder")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/mitchellh/packer/packer"
	"time"
)

type Builder struct {
	config config
	cancel chan int
	done   bool
	ui     packer.Ui
}

type config struct {
	period   float64
	duration float64
}

func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
	b.config.period = 1
	b.config.duration = 5

	for _, e := range raws {
		var m map[string]interface{} = *e.(*map[string]interface{})

		if v, ok := m["period"]; ok {
			b.config.period = v.(float64)
		}

		if v, ok := m["duration"]; ok {
			b.config.duration = v.(float64)
		}
	}

	b.cancel = make(chan int)

	return nil, nil
}

func (b *Builder) Run(ui packer.Ui, _ packer.Hook, _ packer.Cache) (packer.Artifact, error) {
	ui.Say(fmt.Sprintf("Running(%3.0f, %3.0f)...", b.config.period, b.config.duration))

	b.ui = ui

	tick := time.Tick(time.Duration(b.config.period) * time.Second)
	stop := time.After(time.Duration(b.config.duration) * time.Second)
	start := time.Now()

	for !b.done {
		select {
		case now := <-tick:
			ui.Say(fmt.Sprintf("Building... %s", now.Sub(start)))
		case <-stop:
			ui.Say("Done! Stopping...")
			b.done = true
		case <-b.cancel:
			ui.Say("Cancelled! Stopping...")
			b.done = true
		}
	}

	return nil, nil
}

func (b *Builder) Cancel() {
	if b.done {
		return
	}

	b.ui.Say("Cancelling...")
	b.cancel <- 1
}
<commit_msg>Update Run; eliminate dep on timer values<commit_after>package main

import (
	"fmt"
	"github.com/mitchellh/packer/packer"
	"time"
)

type Builder struct {
	config config
	cancel chan int
	done   bool
	ui     packer.Ui
}

type config struct {
	period   float64
	duration float64
}

func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
	b.config.period = 1
	b.config.duration = 5

	for _, e := range raws {
		var m map[string]interface{} = *e.(*map[string]interface{})

		if v, ok := m["period"]; ok {
			b.config.period = v.(float64)
		}

		if v, ok := m["duration"]; ok {
			b.config.duration = v.(float64)
		}
	}

	b.cancel = make(chan int)

	return nil, nil
}

func (b *Builder) Run(ui packer.Ui, _ packer.Hook, _ packer.Cache) (packer.Artifact, error) {
	ui.Say(fmt.Sprintf("Running(%3.0f, %3.0f)...", b.config.period, b.config.duration))

	b.ui = ui

	tick := time.Tick(time.Duration(b.config.period) * time.Second)
	stop := time.After(time.Duration(b.config.duration) * time.Second)
	start := time.Now()

	for !b.done {
		select {
		case <-tick:
			ui.Say(fmt.Sprintf("Building... %s", time.Since(start)))
		case <-stop:
			ui.Say("Done! Stopping...")
			b.done = true
		case <-b.cancel:
			ui.Say("Cancelled! Stopping...")
			b.done = true
		}
	}

	return nil, nil
}

func (b *Builder) Cancel() {
	if b.done {
		return
	}

	b.ui.Say("Cancelling...")
	b.cancel <- 1
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

// Associate ClowdWatchEvent Rule and descripbed Rule(name based)
func AssociateRules(cweRules []*cloudwatchevents.Rule, describedRules []Rule) []Rule {
	if l := len(cweRules) - len(describedRules); l > 0 {
		r := make([]Rule, l)
		describedRules = append(describedRules, r...)
	}
	dupCWERules := make([]*cloudwatchevents.Rule, len(cweRules))
	copy(dupCWERules, cweRules)

	for i, rule := range describedRules {
		for j, cweRule := range dupCWERules {
			if *cweRule.Name == rule.Name {
				describedRules[i].ActualRule = *cweRule
				dupCWERules = DeleteRuleFromSlice(dupCWERules, j)
				break
			}
		}
	}
	if len(dupCWERules) > 0 {
		for _, dupRule := range dupCWERules {
			for j, rule := range describedRules {
				if rule.ActualRule.Arn == nil {
					describedRules[j].ActualRule = *dupRule
				}
			}
		}
	}
	return describedRules
}

func AssociateTargets(cweTargets []*cloudwatchevents.Target, describedTargets []Target) []Target {
	// if ClowdWatchEvents Targets is more than declareted targets, append number of lack target{}
	if l := len(cweTargets) - len(describedTargets); l > 0 {
		t := make([]Target, l)
		describedTargets = append(describedTargets, t...)
	}
	dupCWETargets := make([]*cloudwatchevents.Target, len(cweTargets))
	copy(dupCWETargets, cweTargets)

	for i, target := range describedTargets {
		for j, cweTarget := range dupCWETargets {
			if CompareString(&target.Arn, cweTarget.Arn) && CompareString(&target.Id, cweTarget.Id) {
				describedTargets[i].ActualTarget = *cweTarget
				dupCWETargets = DeleteTargetFromSlice(dupCWETargets, j)
				break
			}
		}
	}
	if len(dupCWETargets) > 0 {
		for _, dupTarget := range dupCWETargets {
			for j, target := range describedTargets {
				if target.ActualTarget.Arn == nil {
					describedTargets[j].ActualTarget = *dupTarget
				}
			}
		}
	}

	return describedTargets
}

// judge is rule need update
// compare rule and ActualRule
func JudgeRuleNeedUpdate(r *Rule) {
	if !CompareString(&r.Name, r.ActualRule.Name) ||
		!CompareString(&r.Description, r.ActualRule.Description) ||
		!CompareString(&r.EventPattern, r.ActualRule.EventPattern) ||
		!CompareString(&r.ScheduleExpression, r.ActualRule.ScheduleExpression) ||
		!CompareString(&r.State, r.ActualRule.State) {
		r.NeedUpdate = true
	}
}

func JudgeRuleNeedDelete(r *Rule) {
	if r.Name == "" &&
		r.Description == "" &&
		r.EventPattern == "" &&
		r.ScheduleExpression == "" &&
		r.RoleArn == "" &&
		r.State == "" &&
		r.ActualRule.Name != nil {
		r.NeedDelete = true
	}
}

// judge is target need update
// compare target and ActualTarget
func JudgeTargetNeedUpdate(t *Target) {
	if !CompareString(&t.Arn, t.ActualTarget.Arn) ||
		!CompareString(&t.Id, t.ActualTarget.Id) ||
		!CompareString(&t.Input, t.ActualTarget.Input) ||
		!CompareString(&t.InputPath, t.ActualTarget.InputPath) {
		t.NeedUpdate = true
	}
}

func JudgeTargetNeedDelete(t *Target) {
	if t.Arn == "" &&
		t.Id == "" &&
		t.ActualTarget.Arn != nil {
		t.NeedDelete = true
	}
}

// check all rules and targets, update "NeedUpdate" and "NeedDelete" field
func CheckIsNeedUpdateOrDelete(rules []Rule) {
	for i, _ := range rules {
		JudgeRuleNeedUpdate(&rules[i])
		JudgeRuleNeedDelete(&rules[i])
		for j, _ := range rules[i].Targets {
			JudgeTargetNeedUpdate(&rules[i].Targets[j])
			JudgeTargetNeedDelete(&rules[i].Targets[j])
		}
	}
}
<commit_msg>Associate to empty rule if actual rule is surplus<commit_after>package main

import (
	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

// Associate ClowdWatchEvent Rule and descripbed Rule(name based)
func AssociateRules(cweRules []*cloudwatchevents.Rule, describedRules []Rule) []Rule {
	dupCWERules := make([]*cloudwatchevents.Rule, len(cweRules))
	copy(dupCWERules, cweRules)

	for i, rule := range describedRules {
		for j, cweRule := range dupCWERules {
			if *cweRule.Name == rule.Name {
				describedRules[i].ActualRule = *cweRule
				dupCWERules = DeleteRuleFromSlice(dupCWERules, j)
				break
			}
		}
	}
	if len(dupCWERules) > 0 {
		for _, dupRule := range dupCWERules {
			describedRules = append(describedRules, Rule{
				ActualRule: *dupRule,
			})
		}
	}
	return describedRules
}

func AssociateTargets(cweTargets []*cloudwatchevents.Target, describedTargets []Target) []Target {
	// if ClowdWatchEvents Targets is more than declareted targets, append number of lack target{}
	if l := len(cweTargets) - len(describedTargets); l > 0 {
		t := make([]Target, l)
		describedTargets = append(describedTargets, t...)
	}
	dupCWETargets := make([]*cloudwatchevents.Target, len(cweTargets))
	copy(dupCWETargets, cweTargets)

	for i, target := range describedTargets {
		for j, cweTarget := range dupCWETargets {
			if CompareString(&target.Arn, cweTarget.Arn) && CompareString(&target.Id, cweTarget.Id) {
				describedTargets[i].ActualTarget = *cweTarget
				dupCWETargets = DeleteTargetFromSlice(dupCWETargets, j)
				break
			}
		}
	}
	if len(dupCWETargets) > 0 {
		for _, dupTarget := range dupCWETargets {
			for j, target := range describedTargets {
				if target.ActualTarget.Arn == nil {
					describedTargets[j].ActualTarget = *dupTarget
				}
			}
		}
	}

	return describedTargets
}

// judge is rule need update
// compare rule and ActualRule
func JudgeRuleNeedUpdate(r *Rule) {
	if !CompareString(&r.Name, r.ActualRule.Name) ||
		!CompareString(&r.Description, r.ActualRule.Description) ||
		!CompareString(&r.EventPattern, r.ActualRule.EventPattern) ||
		!CompareString(&r.ScheduleExpression, r.ActualRule.ScheduleExpression) ||
		!CompareString(&r.State, r.ActualRule.State) {
		r.NeedUpdate = true
	}
}

func JudgeRuleNeedDelete(r *Rule) {
	if r.Name == "" &&
		r.Description == "" &&
		r.EventPattern == "" &&
		r.ScheduleExpression == "" &&
		r.RoleArn == "" &&
		r.State == "" &&
		r.ActualRule.Name != nil {
		r.NeedDelete = true
	}
}

// judge is target need update
// compare target and ActualTarget
func JudgeTargetNeedUpdate(t *Target) {
	if !CompareString(&t.Arn, t.ActualTarget.Arn) ||
		!CompareString(&t.Id, t.ActualTarget.Id) ||
		!CompareString(&t.Input, t.ActualTarget.Input) ||
		!CompareString(&t.InputPath, t.ActualTarget.InputPath) {
		t.NeedUpdate = true
	}
}

func JudgeTargetNeedDelete(t *Target) {
	if t.Arn == "" &&
		t.Id == "" &&
		t.ActualTarget.Arn != nil {
		t.NeedDelete = true
	}
}

// check all rules and targets, update "NeedUpdate" and "NeedDelete" field
func CheckIsNeedUpdateOrDelete(rules []Rule) {
	for i, _ := range rules {
		JudgeRuleNeedUpdate(&rules[i])
		JudgeRuleNeedDelete(&rules[i])
		for j, _ := range rules[i].Targets {
			JudgeTargetNeedUpdate(&rules[i].Targets[j])
			JudgeTargetNeedDelete(&rules[i].Targets[j])
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ying32/govcl/vcl"
	"github.com/ying32/govcl/vcl/types"
	"github.com/ying32/govcl/vcl/win"

	"fmt"

	_ "github.com/ying32/govcl/pkgs/winappres"
)

type TMainForm struct {
	*vcl.TForm
	Button1 *vcl.TButton
}

type TForm1 struct {
	*vcl.TForm
	Button1 *vcl.TButton
}

var (
	mainForm *TMainForm
	form1    *TForm1
)

func main() {
	vcl.RunApp(&mainForm, &form1)
}

// --------------MainForm -----------------
func (f *TMainForm) OnFormCreate(sender vcl.IObject) {
	// Main窗口的会无效。。。。
	//f.SetOnCreateParams(f.OnCreateParams)
	f.SetCaption("Hello")
	f.EnabledMaximize(false)
	f.SetWidth(300)
	f.SetHeight(200)
	f.ScreenCenter()

	f.Button1 = vcl.NewButton(f)
	f.Button1.SetParent(f)
	f.Button1.SetCaption("窗口1")
	f.Button1.SetLeft(50)
	f.Button1.SetTop(50)
	f.Button1.SetOnClick(f.OnButton1Click)
}

func (f *TMainForm) OnButton1Click(object vcl.IObject) {
	form1.Show()
}

// 可以窗口创建前修改一些样式等操作，被动调用
func (f *TMainForm) CreateParams(params *types.TCreateParams) {
	fmt.Println("调用此过程  TMainForm.CreateParams")

}

// ---------- Form1 ----------------

func (f *TForm1) OnFormCreate(sender vcl.IObject) {

	fmt.Println("onCreate")
	f.Button1 = vcl.NewButton(f)
	fmt.Println("f.Button1:", f.Button1.Instance())
	f.Button1.SetParent(f)
	//f.Button1.SetName("Button1")
	f.Button1.SetCaption("我是按钮")
	f.Button1.SetOnClick(f.OnButton1Click)
}

func (f *TForm1) OnButton1Click(object vcl.IObject) {
	vcl.ShowMessage("Click")
}

// 可以窗口创建前修改一些样式等操作，被动调用
func (f *TForm1) CreateParams(params *types.TCreateParams) {
	fmt.Println("调用此过程 TForm1.CreateParams")
	params.WndParent = win.GetDesktopWindow()

}
<commit_msg>Update<commit_after>package main

import (
	"github.com/ying32/govcl/vcl"
	"github.com/ying32/govcl/vcl/types"
	"github.com/ying32/govcl/vcl/win"

	"fmt"

	_ "github.com/ying32/govcl/pkgs/winappres"
)

type TMainForm struct {
	*vcl.TForm
	Button1 *vcl.TButton
}

type TForm1 struct {
	*vcl.TForm
	Button1 *vcl.TButton
}

var (
	mainForm *TMainForm
	form1    *TForm1
)

func main() {
	vcl.RunApp(&mainForm, &form1)
}

// --------------MainForm -----------------
func (f *TMainForm) OnFormCreate(sender vcl.IObject) {

	f.SetCaption("Hello")
	f.EnabledMaximize(false)
	f.SetWidth(300)
	f.SetHeight(200)
	f.ScreenCenter()

	f.Button1 = vcl.NewButton(f)
	f.Button1.SetParent(f)
	f.Button1.SetCaption("窗口1")
	f.Button1.SetLeft(50)
	f.Button1.SetTop(50)
	f.Button1.SetOnClick(f.OnButton1Click)
}

func (f *TMainForm) OnButton1Click(object vcl.IObject) {
	form1.Show()
}

// 可以窗口创建前修改一些样式等操作，被动调用
func (f *TMainForm) CreateParams(params *types.TCreateParams) {
	fmt.Println("调用此过程  TMainForm.CreateParams")

}

// ---------- Form1 ----------------

func (f *TForm1) OnFormCreate(sender vcl.IObject) {

	fmt.Println("onCreate")
	f.Button1 = vcl.NewButton(f)
	fmt.Println("f.Button1:", f.Button1.Instance())
	f.Button1.SetParent(f)
	//f.Button1.SetName("Button1")
	f.Button1.SetCaption("我是按钮")
	f.Button1.SetOnClick(f.OnButton1Click)
}

func (f *TForm1) OnButton1Click(object vcl.IObject) {
	vcl.ShowMessage("Click")
}

// 可以窗口创建前修改一些样式等操作，被动调用
func (f *TForm1) CreateParams(params *types.TCreateParams) {
	fmt.Println("调用此过程 TForm1.CreateParams")
	params.WndParent = win.GetDesktopWindow()

}
<|endoftext|>


<commit_before>package noisey

/* Copyright 2014, Timothy Bogdala <tdb@animal-machine.com>
See the LICENSE file for more details. */

/* This module contains code to easily build 'maps' of random noise. */

// BuilderSource2D is an interface defining how the Builder* types get noise.
type BuilderSource2D interface {
	Get(float64, float64) float64
}

// Builder2DBounds is a simple rectangle type.
type Builder2DBounds struct {
	MinX, MinY, MaxX, MaxY float64
}

// Builder2D contains the parameters and data for the noise 'map' generated with Build().
type Builder2D struct {
	Source BuilderSource2D
	Width  int
	Height int
	Bounds Builder2DBounds
	Values []float64
}

// NewBuilder2D creates a new 2D noise 'map' builder of the given size
func NewBuilder2D(s BuilderSource2D, width int, height int) (b Builder2D) {
	b.Source = s
	b.Width = width
	b.Height = height
	b.Values = make([]float64, width*height)
	return
}

// Build gets noise from Source for each spot in the data array. These steps
// are real numbers so that Bounds does not have to match Width/Height.
func (b *Builder2D) Build() {
	// setup the initial parameters controlling how the noise is sampled
	xExtent := b.Bounds.MaxX - b.Bounds.MinX
	yExtent := b.Bounds.MaxY - b.Bounds.MinY
	xDelta := xExtent / float64(b.Width)
	yDelta := yExtent / float64(b.Height)
	xCur := b.Bounds.MinX
	yCur := b.Bounds.MinY

	for y := 0; y < b.Height; y++ {
		xCur = b.Bounds.MinX
		for x := 0; x < b.Width; x++ {
			value := b.Source.Get(xCur, yCur)
			b.Values[(y*b.Width)+x] = value
			xCur += xDelta
		}
		yCur += yDelta
	}
}
<commit_msg>Added Builder2D.GetMinMax<commit_after>package noisey

/* Copyright 2014, Timothy Bogdala <tdb@animal-machine.com>
See the LICENSE file for more details. */

/* This module contains code to easily build 'maps' of random noise. */

import "math"

// BuilderSource2D is an interface defining how the Builder* types get noise.
type BuilderSource2D interface {
	Get(float64, float64) float64
}

// Builder2DBounds is a simple rectangle type.
type Builder2DBounds struct {
	MinX, MinY, MaxX, MaxY float64
}

// Builder2D contains the parameters and data for the noise 'map' generated with Build().
type Builder2D struct {
	Source BuilderSource2D
	Width  int
	Height int
	Bounds Builder2DBounds
	Values []float64
}

// NewBuilder2D creates a new 2D noise 'map' builder of the given size
func NewBuilder2D(s BuilderSource2D, width int, height int) (b Builder2D) {
	b.Source = s
	b.Width = width
	b.Height = height
	b.Values = make([]float64, width*height)
	return
}

// Build gets noise from Source for each spot in the data array. These steps
// are real numbers so that Bounds does not have to match Width/Height.
func (b *Builder2D) Build() {
	// setup the initial parameters controlling how the noise is sampled
	xExtent := b.Bounds.MaxX - b.Bounds.MinX
	yExtent := b.Bounds.MaxY - b.Bounds.MinY
	xDelta := xExtent / float64(b.Width)
	yDelta := yExtent / float64(b.Height)
	xCur := b.Bounds.MinX
	yCur := b.Bounds.MinY

	for y := 0; y < b.Height; y++ {
		xCur = b.Bounds.MinX
		for x := 0; x < b.Width; x++ {
			value := b.Source.Get(xCur, yCur)
			b.Values[(y*b.Width)+x] = value
			xCur += xDelta
		}
		yCur += yDelta
	}
}

// GetMinMax returns the lowest and the highest Values
func (b *Builder2D) GetMinMax() (min float64, max float64) {
	var low float64 = math.MaxFloat64
	var high float64 = math.SmallestNonzeroFloat64

	totalIndex := b.Width*b.Height
	for i:=0; i<totalIndex; i++ {
		v := b.Values[i]
		if v < low {
			low = v
		}
		if v > high {
			high = v
		}
	}

	return low, high
}
<|endoftext|>


<commit_before>/*
Copyright 2017 WALLIX

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 inspectors

import (
	"fmt"
	"io"
	"text/tabwriter"

	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/graph"
)

type BucketSizer struct {
	total   float64
	buckets map[string]*bucket
}

type bucket struct {
	objects int
	size    float64
}

func (*BucketSizer) Name() string {
	return "bucket_sizer"
}

func (i *BucketSizer) Inspect(g *graph.Graph) error {
	i.buckets = make(map[string]*bucket)

	objects, err := g.GetAllResources(cloud.S3Object)
	if err != nil {
		return err
	}

	for _, obj := range objects {
		size := obj.Properties["Size"].(float64)
		i.total = i.total + size
		name := obj.Properties["BucketName"].(string)
		b := i.buckets[name]
		if b == nil {
			b = new(bucket)
			i.buckets[name] = b
		}
		b.size = b.size + size
		b.objects = b.objects + 1
	}

	return nil
}

func (i *BucketSizer) Print(w io.Writer) {
	tabw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0)

	fmt.Fprintln(tabw, "Bucket\tObject count\tS3 total storage\t")
	fmt.Fprintln(tabw, "--------\t----------\t-----------------\t")

	for name, bucket := range i.buckets {
		fmt.Fprintf(tabw, "%s\t%d\t%.4f Gb\t\n", name, bucket.objects, bucket.size/1e9)
	}

	fmt.Fprintf(tabw, "%s\t%s\t%.4f Gb\t\n", "", "", i.total/1e9)

	tabw.Flush()
}
<commit_msg>Issue #90: updating field datatype to reflect... ... current model<commit_after>/*
Copyright 2017 WALLIX

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 inspectors

import (
	"fmt"
	"io"
	"text/tabwriter"

	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/graph"
)

type BucketSizer struct {
	total   int
	buckets map[string]*bucket
}

type bucket struct {
	objects, size int
}

func (*BucketSizer) Name() string {
	return "bucket_sizer"
}

func (i *BucketSizer) Inspect(g *graph.Graph) error {
	i.buckets = make(map[string]*bucket)

	objects, err := g.GetAllResources(cloud.S3Object)
	if err != nil {
		return err
	}

	for _, obj := range objects {
		size := obj.Properties["Size"].(int)
		i.total = i.total + size
		name := obj.Properties["Bucket"].(string)
		b := i.buckets[name]
		if b == nil {
			b = new(bucket)
			i.buckets[name] = b
		}
		b.size = b.size + size
		b.objects = b.objects + 1
	}

	return nil
}

func (i *BucketSizer) Print(w io.Writer) {
	tabw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0)

	fmt.Fprintln(tabw, "Bucket\tObject count\tS3 total storage\t")
	fmt.Fprintln(tabw, "--------\t----------\t-----------------\t")

	for name, bucket := range i.buckets {
		fmt.Fprintf(tabw, "%s\t%d\t%0.6f Gb\t\n", name, bucket.objects, float64(bucket.size)/1e9)
	}

	fmt.Fprintf(tabw, "%s\t%s\t%0.6f Gb\t\n", "", "", float64(i.total)/1e9)

	tabw.Flush()
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.5
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Obtains next tide from database
// Tweets observation and tide prediction from @SFBuoy
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseUrl      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseUrl, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save latest observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide
	tideOutput := processTide(tide)

	// Format observation given Observation and tideOutput
	observationOutput := formatObservation(observation, tideOutput)

	// Tweet observation at 0000, 0600, 0900, 1200, 1500, 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 9 || t.Hour() == 12 || t.Hour() == 15 || t.Hour() == 18 {
		tweetCurrent(config, observationOutput)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println(observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	return o
}

// Given Observation returns formatted text for tweet
func formatObservation(o Observation, t string) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F\n", t)
	return output
}

// given degrees returns cardinal direction
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and returns Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a defined number of decimals given an Int and Float64, and returns a Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// getTide selects the next tide prediction from the database and return a Tide struct
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string give a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}
<commit_msg>Moved tide output before temperature.<commit_after>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.5
// Obtains latest observation for NBDC Station 46026
// Saves observation to database
// Obtains next tide from database
// Tweets observation and tide prediction from @SFBuoy
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// First two rows of text file, fixed width delimited, used for debugging
const header = "#YY  MM DD hh mm WDIR WSPD GST  WVHT   DPD   APD MWD   PRES  ATMP  WTMP  DEWP  VIS PTDY  TIDE\n#yr  mo dy hr mn degT m/s  m/s     m   sec   sec degT   hPa  degC  degC  degC  nmi  hPa    ft"

// URL for SF Buoy Observations
const noaaURL = "http://www.ndbc.noaa.gov/data/realtime2/46026.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseUrl      string `json:"DatabaseUrl"`
	DatabaseUser     string `json:"DatabaseUser"`
	DatabasePassword string `json:"DatabasePassword"`
	DatabaseName     string `json:"DatabaseName"`
}

// Tide stores a tide prediction from the database
type Tide struct {
	Date         string
	Day          string
	Time         string
	PredictionFt float64
	PredictionCm int64
	HighLow      string
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	dbinfo := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
		config.DatabaseUser, config.DatabasePassword, config.DatabaseUrl, config.DatabaseName)
	var err error
	db, err = sql.Open("postgres", dbinfo)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation()

	// Save latest observation in database
	saveObservation(observation)

	// Obtain next tide from database
	tide := getTide()

	// Format tide
	tideOutput := processTide(tide)

	// Format observation given Observation and tideOutput
	observationOutput := formatObservation(observation, tideOutput)

	// Tweet observation at 0000, 0600, 0900, 1200, 1500, 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 9 || t.Hour() == 12 || t.Hour() == 15 || t.Hour() == 18 {
		tweetCurrent(config, observationOutput)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println(observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation() Observation {
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	file, _ := os.Open(configpath)
	decoder := json.NewDecoder(file)
	err := decoder.Decode(&config)
	if err != nil {
		log.Fatal("Error loading config.json:", err)
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	return o
}

// Given Observation returns formatted text for tweet
func formatObservation(o Observation, tide string) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\n", tide, "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F")
	return output
}

// given degrees returns cardinal direction
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and returns Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a defined number of decimals given an Int and Float64, and returns a Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}

// getTide selects the next tide prediction from the database and return a Tide struct
func getTide() Tide {
	var tide Tide
	err := db.QueryRow("select date, day, time, predictionft, highlow from tidedata where datetime >= current_timestamp order by datetime limit 1").Scan(&tide.Date, &tide.Day, &tide.Time, &tide.PredictionFt, &tide.HighLow)
	if err != nil {
		log.Fatal("getTide function error:", err)
	}
	return tide
}

// processTide returns a formatted string give a Tide struct
func processTide(t Tide) string {
	if t.HighLow == "H" {
		t.HighLow = "High"
	} else {
		t.HighLow = "Low"
	}
	s := "Tide: " + t.HighLow + " " + strconv.FormatFloat(float64(t.PredictionFt), 'f', 1, 64) + "ft at " + t.Time
	// fmt.Println(s)
	return s
}
<|endoftext|>


<commit_before>package integration

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/zorkian/go-datadog-api"
)

func TestWidgetAlertValue(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.AlertValueWidget{
		X:            datadog.Int(1),
		Y:            datadog.Int(1),
		Width:        datadog.Int(5),
		Height:       datadog.Int(5),
		TitleText:    datadog.String("foo"),
		TitleAlign:   datadog.String("center"),
		TitleSize:    datadog.Int(1),
		Title:        datadog.Bool(true),
		TextSize:     datadog.String("auto"),
		Precision:    datadog.Int(2),
		AlertId:      datadog.Int(1),
		Type:         datadog.String("alert_value"),
		Unit:         datadog.String("auto"),
		AddTimeframe: datadog.Bool(false),
	}

	w := datadog.Widget{AlertValueWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].AlertValueWidget

	assert.Equal(t, actualWidget, expected)
}

func TestWidgetChange(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.ChangeWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Aggregator: datadog.String("min"),
		TileDef:    &datadog.TileDef{},
	}

	w := datadog.Widget{ChangeWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].ChangeWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetGraph(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.GraphWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Timeframe:  datadog.String("1d"),
		Type:       datadog.String("alert_graph"),
		Legend:     datadog.Bool(true),
		LegendSize: datadog.Int(5),
		TileDef:    &datadog.TileDef{},
	}

	w := datadog.Widget{GraphWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].GraphWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetEventTimeline(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.EventTimelineWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Query:      datadog.String("avg:system.load.1{foo} by {bar}"),
		Timeframe:  datadog.String("1d"),
		Type:       datadog.String("alert_graph"),
	}

	w := datadog.Widget{EventTimelineWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].EventTimelineWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestAlertWidgetGraph(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.AlertGraphWidget{
		X:            datadog.Int(1),
		Y:            datadog.Int(1),
		Width:        datadog.Int(5),
		Height:       datadog.Int(5),
		TitleText:    datadog.String("foo"),
		TitleAlign:   datadog.String("center"),
		TitleSize:    datadog.Int(1),
		Title:        datadog.Bool(true),
		VizType:      datadog.String(""),
		Timeframe:    datadog.String("1d"),
		AddTimeframe: datadog.Bool(false),
		AlertId:      datadog.Int(1),
		Type:         datadog.String("alert_graph"),
	}

	w := datadog.Widget{AlertGraphWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].AlertGraphWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetHostMap(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.HostMapWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Type:       datadog.String("check_status"),
		Query:      datadog.String("avg:system.load.1{foo} by {bar}"),
		Timeframe:  datadog.String("1d"),
		Legend:     datadog.Bool(true),
		LegendSize: datadog.Int(5),
		TileDef:    &datadog.TileDef{},
	}

	w := datadog.Widget{HostMapWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].HostMapWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetCheckStatus(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.CheckStatusWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Type:       datadog.String("check_status"),
		Tags:       datadog.String("foo"),
		Timeframe:  datadog.String("1d"),
		Check:      datadog.String("datadog.agent.up"),
		Group:      datadog.String("foo"),
		Grouping:   datadog.String("check"),
	}

	w := datadog.Widget{CheckStatusWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].CheckStatusWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetIFrame(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.IFrameWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize:  datadog.Int(1),
		Title:      datadog.Bool(true),
		Url:        datadog.String("http://www.example.com"),
		Type:       datadog.String("iframe"),
	}

	w := datadog.Widget{IFrameWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].IFrameWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetNote(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.NoteWidget{
		X:            datadog.Int(1),
		Y:            datadog.Int(1),
		Width:        datadog.Int(5),
		Height:       datadog.Int(5),
		TitleText:    datadog.String("foo"),
		TitleAlign:   datadog.String("center"),
		TitleSize:    datadog.Int(1),
		Title:        datadog.Bool(true),
		Color:        datadog.String("green"),
		FontSize:     datadog.Int(5),
		RefreshEvery: datadog.Int(60),
		TickPos:      datadog.String("foo"),
		TickEdge:     datadog.String("bar"),
		Html:         datadog.String("baz"),
		Tick:         datadog.Bool(false),
		Note:         datadog.String("quz"),
		AutoRefresh:  datadog.Bool(false),
	}

	w := datadog.Widget{NoteWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].NoteWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetToplist(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.ToplistWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize: &datadog.TextSize{
			Size: datadog.Int(1),
			Auto: datadog.Bool(true),
		},
		Title:      datadog.Bool(true),
		Timeframe:  datadog.String("5m"),
		Legend:     datadog.Bool(false),
		LegendSize: datadog.Int(5),
	}

	w := datadog.Widget{ToplistWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].ToplistWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetEventSteam(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.EventStreamWidget{
		EventSize:  datadog.String("1"),
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		TitleText:  datadog.String("foo"),
		TitleAlign: datadog.String("center"),
		TitleSize: &datadog.TextSize{
			Size: datadog.Int(1),
			Auto: datadog.Bool(true),
		},
		Title:     datadog.Bool(true),
		Timeframe: datadog.String("5m"),
		Query:     datadog.String("foo"),
		Type:      datadog.String("baz"),
	}

	w := datadog.Widget{EventStreamWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].EventStreamWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetImage(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.ImageWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(5),
		Height:     datadog.Int(5),
		Title:      datadog.Bool(false),
		TitleAlign: datadog.String("center"),
		TitleSize: &datadog.TextSize{
			Size: datadog.Int(1),
			Auto: datadog.Bool(true),
		},
		TitleText: datadog.String("bar"),
		Type:      datadog.String("baz"),
		Url:       datadog.String("qux"),
		Sizing:    datadog.String("quuz"),
	}

	w := datadog.Widget{ImageWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].ImageWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetFreeText(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.FreeTextWidget{
		X:         datadog.Int(1),
		Y:         datadog.Int(1),
		Width:     datadog.Int(5),
		Height:    datadog.Int(5),
		Text:      datadog.String("Test"),
		FontSize:  datadog.String("16"),
		TextAlign: datadog.String("center"),
		Type:      datadog.String("baz"),
	}

	w := datadog.Widget{FreeTextWidget: expected}

	board.Widgets = append(board.Widgets, w)

	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].FreeTextWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetTimeseries(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.TimeseriesWidget{
		X:          datadog.Int(1),
		Y:          datadog.Int(1),
		Width:      datadog.Int(20),
		Height:     datadog.Int(30),
		Title:      datadog.Bool(true),
		TitleAlign: datadog.String("centre"),
		TitleSize: &datadog.TextSize{
			Size: datadog.Int(16),
			Auto: datadog.Bool(true),
		},
		TitleText: datadog.String("Test"),
		Type:      datadog.String("baz"),
		Timeframe: datadog.String("1m"),
	}

	w := datadog.Widget{TimeseriesWidget: expected}

	board.Widgets = append(board.Widgets, w)
	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].TimeseriesWidget

	assert.Equal(t, *actualWidget, *expected)
}

func TestWidgetQueryValue(t *testing.T) {
	board := createTestScreenboard(t)
	defer cleanUpScreenboard(t, *board.Id)

	expected := &datadog.QueryValueWidget{
		X:                   datadog.Int(1),
		Y:                   datadog.Int(1),
		Width:               datadog.Int(20),
		Height:              datadog.Int(30),
		Title:               datadog.Bool(true),
		TitleAlign:          datadog.String("centre"),
		TitleSize:           &datadog.TextSize{Size: datadog.Int(16)},
		TitleText:           datadog.String("Test"),
		Timeframe:           datadog.String("1m"),
		TimeframeAggregator: datadog.String("sum"),
		Aggregator:          datadog.String("min"),
		Query:               datadog.String("docker.containers.running"),
		MetricType:          datadog.String("standard"),
		/* TODO: add test for conditional formats
		"conditional_formats": [{
			"comparator": ">",
			"color": "white_on_red",
			"custom_bg_color": null,
			"value": 1,
			"invert": false,
			"custom_fg_color": null}],
		*/
		IsValidQuery:   datadog.Bool(true),
		ResultCalcFunc: datadog.String("raw"),
		CalcFunc:       datadog.String("raw"),
	}

	w := datadog.Widget{QueryValueWidget: expected}

	board.Widgets = append(board.Widgets, w)
	if err := client.UpdateScreenboard(board); err != nil {
		t.Fatalf("Updating a screenboard failed: %s", err)
	}

	actual, err := client.GetScreenboard(*board.Id)
	if err != nil {
		t.Fatalf("Retrieving a screenboard failed: %s", err)
	}

	actualWidget := actual.Widgets[0].QueryValueWidget

	assert.Equal(t, *actualWidget, *expected)
}
<commit_msg>Remove screen widget tests<commit_after><|endoftext|>


<commit_before>package revel

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

	"github.com/revel/config"
)

const (
	CurrentLocaleRenderArg = "currentLocale" // The key for the current locale render arg value

	messageFilesDirectory  = "messages"
	messageFilePattern     = `^\w+\.[a-zA-Z]{2}$`
	defaultUnknownFormat   = "??? %s ???"
	unknownFormatConfigKey = "i18n.unknown_format"
	defaultLanguageOption  = "i18n.default_language"
	localeCookieConfigKey  = "i18n.cookie"
)

var (
	// All currently loaded message configs.
	messages map[string]*config.Config
)

// Setting MessageFunc allows you to override the translation interface.
//
// Set this to your own function that translates to the current locale.
// This allows you to set up your own loading and logging of translated texts.
//
// See Message(...) in i18n.go for example of function.
var MessageFunc = Message

// Return all currently loaded message languages.
func MessageLanguages() []string {
	languages := make([]string, len(messages))
	i := 0
	for language := range messages {
		languages[i] = language
		i++
	}
	return languages
}

// Perform a message look-up for the given locale and message using the given arguments.
//
// When either an unknown locale or message is detected, a specially formatted string is returned.
func Message(locale, message string, args ...interface{}) string {
	language, region := parseLocale(locale)
	unknownValueFormat := getUnknownValueFormat()

	messageConfig, knownLanguage := messages[language]
	if !knownLanguage {
		TRACE.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)

		if defaultLanguage, found := Config.String(defaultLanguageOption); found {
			TRACE.Printf("Using default language '%s'", defaultLanguage)

			messageConfig, knownLanguage = messages[defaultLanguage]
			if !knownLanguage {
				WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
				return fmt.Sprintf(unknownValueFormat, message)
			}
		} else {
			WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
			return fmt.Sprintf(unknownValueFormat, message)
		}
	}

	// This works because unlike the goconfig documentation suggests it will actually
	// try to resolve message in DEFAULT if it did not find it in the given section.
	value, error := messageConfig.String(region, message)
	if error != nil {
		WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
		return fmt.Sprintf(unknownValueFormat, message)
	}

	if len(args) > 0 {
		TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
		value = fmt.Sprintf(value, args...)
	}

	return value
}

func parseLocale(locale string) (language, region string) {
	if strings.Contains(locale, "-") {
		languageAndRegion := strings.Split(locale, "-")
		return languageAndRegion[0], languageAndRegion[1]
	}

	return locale, ""
}

// Retrieve message format or default format when i18n message is missing.
func getUnknownValueFormat() string {
	return Config.StringDefault(unknownFormatConfigKey, defaultUnknownFormat)
}

// Recursively read and cache all available messages from all message files on the given path.
func loadMessages(path string) {
	messages = make(map[string]*config.Config)

	// Read in messages from the modules. Load the module messges first,
	// so that it can be override in parent application
	for _, module := range Modules {
		TRACE.Println("Importing messages from ", filepath.Join(module.Path, messageFilesDirectory))
		if err := Walk(filepath.Join(module.Path, messageFilesDirectory), loadMessageFile); err != nil &&
			!os.IsNotExist(err) {
			ERROR.Println("Error reading messages files from module:", err)
		}
	}

	if err := Walk(path, loadMessageFile); err != nil && !os.IsNotExist(err) {
		ERROR.Println("Error reading messages files:", err)
	}
}

// Load a single message file
func loadMessageFile(path string, info os.FileInfo, osError error) error {
	if osError != nil {
		return osError
	}
	if info.IsDir() {
		return nil
	}

	if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
		var config *config.Config
		config, err := parseMessagesFile(path)
		if err != nil {
			return err
		}
		locale := parseLocaleFromFileName(info.Name())

		// If we have already parsed a message file for this locale, merge both
		if _, exists := messages[locale]; exists {
			messages[locale].Merge(config)
			TRACE.Printf("Successfully merged messages for locale '%s'", locale)
		} else {
			messages[locale] = config
		}

		TRACE.Println("Successfully loaded messages from file", info.Name())
	} else {
		TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
	}

	return nil
}

func parseMessagesFile(path string) (messageConfig *config.Config, error error) {
	messageConfig, error = config.ReadDefault(path)
	return
}

func parseLocaleFromFileName(file string) string {
	extension := filepath.Ext(file)[1:]
	return strings.ToLower(extension)
}

func init() {
	OnAppStart(func() {
		loadMessages(filepath.Join(BasePath, messageFilesDirectory))
	})
}

func I18nFilter(c *Controller, fc []Filter) {
	if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
		TRACE.Printf("Found locale cookie value: %s", cookieValue)
		setCurrentLocaleControllerArguments(c, cookieValue)
	} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
		TRACE.Printf("Found Accept-Language header value: %s", headerValue)
		setCurrentLocaleControllerArguments(c, headerValue)
	} else {
		TRACE.Println("Unable to find locale in cookie or header, using empty string")
		setCurrentLocaleControllerArguments(c, "")
	}
	fc[0](c, fc[1:])
}

// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
func setCurrentLocaleControllerArguments(c *Controller, locale string) {
	c.Request.Locale = locale
	c.RenderArgs[CurrentLocaleRenderArg] = locale
}

// Determine whether the given request has valid Accept-Language value.
//
// Assumes that the accept languages stored in the request are sorted according to quality, with top
// quality first in the slice.
func hasAcceptLanguageHeader(request *Request) (bool, string) {
	if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
		return true, request.AcceptLanguages[0].Language
	}

	return false, ""
}

// Determine whether the given request has a valid language cookie value.
func hasLocaleCookie(request *Request) (bool, string) {
	if request != nil && request.Cookies() != nil {
		name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG")
		cookie, err := request.Cookie(name)
		if err == nil {
			return true, cookie.Value
		}
		TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, err.Error())
	}

	return false, ""
}
<commit_msg>Use conventional name for error variable<commit_after>package revel

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

	"github.com/revel/config"
)

const (
	CurrentLocaleRenderArg = "currentLocale" // The key for the current locale render arg value

	messageFilesDirectory  = "messages"
	messageFilePattern     = `^\w+\.[a-zA-Z]{2}$`
	defaultUnknownFormat   = "??? %s ???"
	unknownFormatConfigKey = "i18n.unknown_format"
	defaultLanguageOption  = "i18n.default_language"
	localeCookieConfigKey  = "i18n.cookie"
)

var (
	// All currently loaded message configs.
	messages map[string]*config.Config
)

// Setting MessageFunc allows you to override the translation interface.
//
// Set this to your own function that translates to the current locale.
// This allows you to set up your own loading and logging of translated texts.
//
// See Message(...) in i18n.go for example of function.
var MessageFunc = Message

// Return all currently loaded message languages.
func MessageLanguages() []string {
	languages := make([]string, len(messages))
	i := 0
	for language := range messages {
		languages[i] = language
		i++
	}
	return languages
}

// Perform a message look-up for the given locale and message using the given arguments.
//
// When either an unknown locale or message is detected, a specially formatted string is returned.
func Message(locale, message string, args ...interface{}) string {
	language, region := parseLocale(locale)
	unknownValueFormat := getUnknownValueFormat()

	messageConfig, knownLanguage := messages[language]
	if !knownLanguage {
		TRACE.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)

		if defaultLanguage, found := Config.String(defaultLanguageOption); found {
			TRACE.Printf("Using default language '%s'", defaultLanguage)

			messageConfig, knownLanguage = messages[defaultLanguage]
			if !knownLanguage {
				WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
				return fmt.Sprintf(unknownValueFormat, message)
			}
		} else {
			WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
			return fmt.Sprintf(unknownValueFormat, message)
		}
	}

	// This works because unlike the goconfig documentation suggests it will actually
	// try to resolve message in DEFAULT if it did not find it in the given section.
	value, err := messageConfig.String(region, message)
	if err != nil {
		WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
		return fmt.Sprintf(unknownValueFormat, message)
	}

	if len(args) > 0 {
		TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
		value = fmt.Sprintf(value, args...)
	}

	return value
}

func parseLocale(locale string) (language, region string) {
	if strings.Contains(locale, "-") {
		languageAndRegion := strings.Split(locale, "-")
		return languageAndRegion[0], languageAndRegion[1]
	}

	return locale, ""
}

// Retrieve message format or default format when i18n message is missing.
func getUnknownValueFormat() string {
	return Config.StringDefault(unknownFormatConfigKey, defaultUnknownFormat)
}

// Recursively read and cache all available messages from all message files on the given path.
func loadMessages(path string) {
	messages = make(map[string]*config.Config)

	// Read in messages from the modules. Load the module messges first,
	// so that it can be override in parent application
	for _, module := range Modules {
		TRACE.Println("Importing messages from ", filepath.Join(module.Path, messageFilesDirectory))
		if err := Walk(filepath.Join(module.Path, messageFilesDirectory), loadMessageFile); err != nil &&
			!os.IsNotExist(err) {
			ERROR.Println("Error reading messages files from module:", err)
		}
	}

	if err := Walk(path, loadMessageFile); err != nil && !os.IsNotExist(err) {
		ERROR.Println("Error reading messages files:", err)
	}
}

// Load a single message file
func loadMessageFile(path string, info os.FileInfo, osError error) error {
	if osError != nil {
		return osError
	}
	if info.IsDir() {
		return nil
	}

	if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
		var config *config.Config
		config, err := parseMessagesFile(path)
		if err != nil {
			return err
		}
		locale := parseLocaleFromFileName(info.Name())

		// If we have already parsed a message file for this locale, merge both
		if _, exists := messages[locale]; exists {
			messages[locale].Merge(config)
			TRACE.Printf("Successfully merged messages for locale '%s'", locale)
		} else {
			messages[locale] = config
		}

		TRACE.Println("Successfully loaded messages from file", info.Name())
	} else {
		TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
	}

	return nil
}

func parseMessagesFile(path string) (messageConfig *config.Config, err error) {
	messageConfig, err = config.ReadDefault(path)
	return
}

func parseLocaleFromFileName(file string) string {
	extension := filepath.Ext(file)[1:]
	return strings.ToLower(extension)
}

func init() {
	OnAppStart(func() {
		loadMessages(filepath.Join(BasePath, messageFilesDirectory))
	})
}

func I18nFilter(c *Controller, fc []Filter) {
	if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
		TRACE.Printf("Found locale cookie value: %s", cookieValue)
		setCurrentLocaleControllerArguments(c, cookieValue)
	} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
		TRACE.Printf("Found Accept-Language header value: %s", headerValue)
		setCurrentLocaleControllerArguments(c, headerValue)
	} else {
		TRACE.Println("Unable to find locale in cookie or header, using empty string")
		setCurrentLocaleControllerArguments(c, "")
	}
	fc[0](c, fc[1:])
}

// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
func setCurrentLocaleControllerArguments(c *Controller, locale string) {
	c.Request.Locale = locale
	c.RenderArgs[CurrentLocaleRenderArg] = locale
}

// Determine whether the given request has valid Accept-Language value.
//
// Assumes that the accept languages stored in the request are sorted according to quality, with top
// quality first in the slice.
func hasAcceptLanguageHeader(request *Request) (bool, string) {
	if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
		return true, request.AcceptLanguages[0].Language
	}

	return false, ""
}

// Determine whether the given request has a valid language cookie value.
func hasLocaleCookie(request *Request) (bool, string) {
	if request != nil && request.Cookies() != nil {
		name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG")
		cookie, err := request.Cookie(name)
		if err == nil {
			return true, cookie.Value
		}
		TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, err.Error())
	}

	return false, ""
}
<|endoftext|>