<commit_before>/*
 * 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 config helps plugins extensions to easily set config as key/value by reading from configuration file
// Package config also provides setter and getter functionality

package config

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mesos "github.com/mesos/mesos-go/mesosproto"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"

	"github.com/paypal/dce-go/types"
)

// Define default values
const (
	CONFIG_File                          = "config"
	FOLDER_NAME                          = "foldername"
	HEALTH_CHECK                         = "healthcheck"
	POD_MONITOR_INTERVAL                 = "launchtask.podmonitorinterval"
	TIMEOUT                              = "launchtask.timeout"
	INFRA_CONTAINER                      = "infracontainer"
	PULL_RETRY                           = "launchtask.pullretry"
	MAX_RETRY                            = "launchtask.maxretry"
	RETRY_INTERVAL                       = "launchtask.retryinterval"
	NETWORKS                             = "networks"
	PRE_EXIST                            = "pre_existing"
	NETWORK_NAME                         = "name"
	NETWORK_DRIVER                       = "driver"
	CLEANPOD                             = "cleanpod"
	CLEAN_CONTAINER_VOLUME_ON_MESOS_KILL = "cleanpod.cleanvolumeandcontaineronmesoskill"
	CLEAN_IMAGE_ON_MESOS_KILL            = "cleanpod.cleanimageonmesoskill"
	CLEAN_FAIL_TASK                      = "cleanpod.cleanfailtask"
	DOCKER_COMPOSE_VERBOSE               = "dockercomposeverbose"
	SKIP_PULL_IMAGES                     = "launchtask.skippull"
	COMPOSE_TRACE                        = "launchtask.composetrace"
	DEBUG_MODE                           = "launchtask.debug"
	COMPOSE_HTTP_TIMEOUT                 = "launchtask.composehttptimeout"
	HTTP_TIMEOUT                         = "launchtask.httptimeout"
	COMPOSE_STOP_TIMEOUT                 = "cleanpod.timeout"
	CONFIG_OVERRIDE_PREFIX               = "config."
	monitorName                          = "podMonitor.monitorName"
)

// Read from default configuration file and set config as key/values
func init() {
	err := getConfigFromFile(CONFIG_File)

	if err != nil {
		log.Fatalf("Fail to retrieve data from file, err: %s\n", err.Error())
	}
}

// Plugin extensions could merge configuration using ConfigInit with configuration file
func ConfigInit(pluginConfig string) {
	log.Printf("Plugin Merge Config : %s", pluginConfig)

	file, err := os.Open(pluginConfig)
	if err != nil {
		log.Fatalf("Fail to open file, err: %s\n", err.Error())
	}

	err = viper.MergeConfig(file)
	if err != nil {
		log.Fatalf("Fail to merge config, err: %s\n", err.Error())
	}

	setDefaultConfig(GetConfig())
}

func getConfigFromFile(cfgFile string) error {
	// Set config name
	viper.SetConfigName(cfgFile)

	// Set config type
	viper.SetConfigType("yaml")

	// Add path for searching config files including plugins'
	viper.AddConfigPath(".")
	abs_path, _ := filepath.Abs(".")
	viper.AddConfigPath(filepath.Join(filepath.Dir(filepath.Dir(abs_path)), "config"))
	dirs, _ := ioutil.ReadDir("./")
	for _, f := range dirs {
		if f.IsDir() {
			viper.AddConfigPath(f.Name())
		}
	}

	err := viper.ReadInConfig()
	if err != nil {
		log.Errorf("No config file loaded, err: %s\n", err.Error())
		return err
	}
	return nil
}

func SetConfig(key string, value interface{}) {
	log.Printf("Set config : %s = %v", key, value)
	GetConfig().Set(key, value)
}

func GetConfigSection(section string) map[string]string {
	return viper.GetStringMapString(section)
}

func GetConfig() *viper.Viper {
	return viper.GetViper()
}

func setDefaultConfig(conf *viper.Viper) {
	conf.SetDefault(POD_MONITOR_INTERVAL, "10s")
	conf.SetDefault(COMPOSE_HTTP_TIMEOUT, 300)
	conf.SetDefault(MAX_RETRY, 3)
	conf.SetDefault(PULL_RETRY, 3)
	conf.SetDefault(RETRY_INTERVAL, "10s")
	conf.SetDefault(TIMEOUT, "500s")
	conf.SetDefault(COMPOSE_STOP_TIMEOUT, "20")
	conf.SetDefault(HTTP_TIMEOUT, "20s")
	conf.SetDefault(monitorName, "default")
}

func GetAppFolder() string {
	folder := GetConfig().GetString(FOLDER_NAME)
	if folder == "" {
		return types.DEFAULT_FOLDER
	}
	return folder
}

func GetPullRetryCount() int {
	return GetConfig().GetInt(PULL_RETRY)
}

// GetLaunchTimeout returns maximum time to wait until a pod becomes healthy.
// Support value type as duration string.
// A duration string is a possibly signed sequence of
// decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
func GetLaunchTimeout() time.Duration {
	// Making backward compatible change to support config value of `launchtask.timeout` as duration (e.g. 500s),
	// as well as integer which is existing value type
	valStr := GetConfig().GetString(TIMEOUT)
	if valInt, err := strconv.Atoi(valStr); err == nil {
		return time.Duration(valInt) * time.Millisecond
	}
	duration, err := time.ParseDuration(valStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.timeout from %s to duration, using 500s as default value",
			valStr)
		return 500 * time.Second
	}
	return duration
}

// GetStopTimeout returns the grace period time for a pod to die
// Returns time in seconds as an integer
func GetStopTimeout() int {
	valStr := GetConfig().GetString(COMPOSE_STOP_TIMEOUT)

	// value from the config file is an integer (seconds)
	valInt, err := strconv.Atoi(valStr)
	if err == nil {
		return valInt
	}

	// the overridden config via mesos label comes as duration
	duration, err := time.ParseDuration(valStr)
	if err != nil {
		log.Warningf("unable to parse cleanpod.timeout from %s to int, using 20s as the default value", valStr)
		// 20s is the default cleanpod.timeout present in the dce config file
		return 20
	}

	// float64 to int; a practical timeout value won't overflow
	return int(duration.Seconds())
}

func GetRetryInterval() time.Duration {
	intervalStr := GetConfig().GetString(RETRY_INTERVAL)
	duration, err := time.ParseDuration(intervalStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.retryinterval from %s to duration, using 10s as default value",
			intervalStr)
		return 10 * time.Second
	}
	return duration
}

func GetMaxRetry() int {
	return GetConfig().GetInt(MAX_RETRY)
}

func GetNetwork() (types.Network, bool) {
	nmap, ok := GetConfig().GetStringMap(INFRA_CONTAINER)[NETWORKS].(map[string]interface{})
	if !ok {
		log.Println("networks section missing in config")
		return types.Network{}, false
	}

	if nmap[PRE_EXIST] == nil {
		nmap[PRE_EXIST] = false
	}

	if nmap[NETWORK_NAME] == nil {
		nmap[NETWORK_NAME] = ""
	}

	if nmap[NETWORK_DRIVER] == nil {
		nmap[NETWORK_DRIVER] = ""
	}

	network := types.Network{
		PreExist: nmap[PRE_EXIST].(bool),
		Name:     nmap[NETWORK_NAME].(string),
		Driver:   nmap[NETWORK_DRIVER].(string),
	}
	return network, true
}

func EnableVerbose() bool {
	return GetConfig().GetBool(DOCKER_COMPOSE_VERBOSE)
}

func SkipPullImages() bool {
	return GetConfig().GetBool(SKIP_PULL_IMAGES)
}

func EnableComposeTrace() bool {
	return GetConfig().GetBool(COMPOSE_TRACE)
}

func GetPollInterval() time.Duration {
	intervalStr := GetConfig().GetString(POD_MONITOR_INTERVAL)
	duration, err := time.ParseDuration(intervalStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.podmonitorinterval %s to duration, using 10s as default value",
			intervalStr)
		return 10 * time.Second
	}
	return duration
}

func GetHttpTimeout() time.Duration {
	timeoutStr := GetConfig().GetString(HTTP_TIMEOUT)
	duration, err := time.ParseDuration(timeoutStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.httptimeout %s to duration, using 20s as default value", timeoutStr)
		return 20 * time.Second
	}
	return duration
}

func GetComposeHttpTimeout() int {
	return GetConfig().GetInt(COMPOSE_HTTP_TIMEOUT)
}

func EnableDebugMode() bool {
	return GetConfig().GetBool(DEBUG_MODE)
}

func IsService() bool {
	return GetConfig().GetBool(types.IS_SERVICE)
}

func CreateFileAppendMode(filename string) *os.File {

	File, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		log.Errorf("Error in creating %v file, err: %v", filename, err)
		return os.Stdout
	}
	return File
}

// OverrideConfig gets labels with override prefix "config." and override configs with value of label
// Checking labels contain key word "config." instead of prefix since different framework will add different prefix for labels
func OverrideConfig(taskInfo *mesos.TaskInfo) {
	labelsList := taskInfo.GetLabels().GetLabels()

	for _, label := range labelsList {
		if strings.Contains(label.GetKey(), CONFIG_OVERRIDE_PREFIX) {
			parts := strings.SplitAfterN(label.GetKey(), CONFIG_OVERRIDE_PREFIX, 2)
			if len(parts) == 2 && GetConfig().IsSet(parts[1]) {
				GetConfig().Set(parts[1], label.GetValue())
				log.Infof("override config %s with %s", parts[1], label.GetValue())
			}
		}
	}
}
<commit_msg>setting default<commit_after>/*
 * 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 config helps plugins extensions to easily set config as key/value by reading from configuration file
// Package config also provides setter and getter functionality

package config

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mesos "github.com/mesos/mesos-go/mesosproto"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"

	"github.com/paypal/dce-go/types"
)

// Define default values
const (
	CONFIG_File                          = "config"
	FOLDER_NAME                          = "foldername"
	HEALTH_CHECK                         = "healthcheck"
	POD_MONITOR_INTERVAL                 = "launchtask.podmonitorinterval"
	TIMEOUT                              = "launchtask.timeout"
	INFRA_CONTAINER                      = "infracontainer"
	PULL_RETRY                           = "launchtask.pullretry"
	MAX_RETRY                            = "launchtask.maxretry"
	RETRY_INTERVAL                       = "launchtask.retryinterval"
	NETWORKS                             = "networks"
	PRE_EXIST                            = "pre_existing"
	NETWORK_NAME                         = "name"
	NETWORK_DRIVER                       = "driver"
	CLEANPOD                             = "cleanpod"
	CLEAN_CONTAINER_VOLUME_ON_MESOS_KILL = "cleanpod.cleanvolumeandcontaineronmesoskill"
	CLEAN_IMAGE_ON_MESOS_KILL            = "cleanpod.cleanimageonmesoskill"
	CLEAN_FAIL_TASK                      = "cleanpod.cleanfailtask"
	DOCKER_COMPOSE_VERBOSE               = "dockercomposeverbose"
	SKIP_PULL_IMAGES                     = "launchtask.skippull"
	COMPOSE_TRACE                        = "launchtask.composetrace"
	DEBUG_MODE                           = "launchtask.debug"
	COMPOSE_HTTP_TIMEOUT                 = "launchtask.composehttptimeout"
	HTTP_TIMEOUT                         = "launchtask.httptimeout"
	COMPOSE_STOP_TIMEOUT                 = "cleanpod.timeout"
	DEFAULT_COMPOSE_STOP_TIMEOUT         = 20
	CONFIG_OVERRIDE_PREFIX               = "config."
	monitorName                          = "podMonitor.monitorName"
)

// Read from default configuration file and set config as key/values
func init() {
	err := getConfigFromFile(CONFIG_File)

	if err != nil {
		log.Fatalf("Fail to retrieve data from file, err: %s\n", err.Error())
	}
}

// Plugin extensions could merge configuration using ConfigInit with configuration file
func ConfigInit(pluginConfig string) {
	log.Printf("Plugin Merge Config : %s", pluginConfig)

	file, err := os.Open(pluginConfig)
	if err != nil {
		log.Fatalf("Fail to open file, err: %s\n", err.Error())
	}

	err = viper.MergeConfig(file)
	if err != nil {
		log.Fatalf("Fail to merge config, err: %s\n", err.Error())
	}

	setDefaultConfig(GetConfig())
}

func getConfigFromFile(cfgFile string) error {
	// Set config name
	viper.SetConfigName(cfgFile)

	// Set config type
	viper.SetConfigType("yaml")

	// Add path for searching config files including plugins'
	viper.AddConfigPath(".")
	abs_path, _ := filepath.Abs(".")
	viper.AddConfigPath(filepath.Join(filepath.Dir(filepath.Dir(abs_path)), "config"))
	dirs, _ := ioutil.ReadDir("./")
	for _, f := range dirs {
		if f.IsDir() {
			viper.AddConfigPath(f.Name())
		}
	}

	err := viper.ReadInConfig()
	if err != nil {
		log.Errorf("No config file loaded, err: %s\n", err.Error())
		return err
	}
	return nil
}

func SetConfig(key string, value interface{}) {
	log.Printf("Set config : %s = %v", key, value)
	GetConfig().Set(key, value)
}

func GetConfigSection(section string) map[string]string {
	return viper.GetStringMapString(section)
}

func GetConfig() *viper.Viper {
	return viper.GetViper()
}

func setDefaultConfig(conf *viper.Viper) {
	conf.SetDefault(POD_MONITOR_INTERVAL, "10s")
	conf.SetDefault(COMPOSE_HTTP_TIMEOUT, 300)
	conf.SetDefault(MAX_RETRY, 3)
	conf.SetDefault(PULL_RETRY, 3)
	conf.SetDefault(RETRY_INTERVAL, "10s")
	conf.SetDefault(TIMEOUT, "500s")
	conf.SetDefault(COMPOSE_STOP_TIMEOUT, DEFAULT_COMPOSE_STOP_TIMEOUT)
	conf.SetDefault(HTTP_TIMEOUT, "20s")
	conf.SetDefault(monitorName, "default")
}

func GetAppFolder() string {
	folder := GetConfig().GetString(FOLDER_NAME)
	if folder == "" {
		return types.DEFAULT_FOLDER
	}
	return folder
}

func GetPullRetryCount() int {
	return GetConfig().GetInt(PULL_RETRY)
}

// GetLaunchTimeout returns maximum time to wait until a pod becomes healthy.
// Support value type as duration string.
// A duration string is a possibly signed sequence of
// decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
func GetLaunchTimeout() time.Duration {
	// Making backward compatible change to support config value of `launchtask.timeout` as duration (e.g. 500s),
	// as well as integer which is existing value type
	valStr := GetConfig().GetString(TIMEOUT)
	if valInt, err := strconv.Atoi(valStr); err == nil {
		return time.Duration(valInt) * time.Millisecond
	}
	duration, err := time.ParseDuration(valStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.timeout from %s to duration, using 500s as default value",
			valStr)
		return 500 * time.Second
	}
	return duration
}

// GetStopTimeout returns the grace period time for a pod to die
// Returns time in seconds as an integer
func GetStopTimeout() int {
	// value from the config file expressed as seconds in integer format
	// Viper implicitly casts string to int too
	if valInt := GetConfig().GetInt(COMPOSE_STOP_TIMEOUT); valInt != 0 {
		return valInt
	}

	valStr := GetConfig().GetString(COMPOSE_STOP_TIMEOUT)
	if valStr == "" {
		log.Warningf("unable to find cleanpod.timeout, using %ds as the default value", DEFAULT_COMPOSE_STOP_TIMEOUT)
		return DEFAULT_COMPOSE_STOP_TIMEOUT
	}

	// the overridden config via mesos label is expressed as duration in string format
	duration, err := time.ParseDuration(valStr)
	if err != nil {
		log.Warningf("unable to parse cleanpod.timeout from %s to int, using %ds as the default value",
			valStr, DEFAULT_COMPOSE_STOP_TIMEOUT)
		return DEFAULT_COMPOSE_STOP_TIMEOUT
	}

	// float64 to int; a practical timeout value won't overflow
	return int(duration.Seconds())
}

func GetRetryInterval() time.Duration {
	intervalStr := GetConfig().GetString(RETRY_INTERVAL)
	duration, err := time.ParseDuration(intervalStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.retryinterval from %s to duration, using 10s as default value",
			intervalStr)
		return 10 * time.Second
	}
	return duration
}

func GetMaxRetry() int {
	return GetConfig().GetInt(MAX_RETRY)
}

func GetNetwork() (types.Network, bool) {
	nmap, ok := GetConfig().GetStringMap(INFRA_CONTAINER)[NETWORKS].(map[string]interface{})
	if !ok {
		log.Println("networks section missing in config")
		return types.Network{}, false
	}

	if nmap[PRE_EXIST] == nil {
		nmap[PRE_EXIST] = false
	}

	if nmap[NETWORK_NAME] == nil {
		nmap[NETWORK_NAME] = ""
	}

	if nmap[NETWORK_DRIVER] == nil {
		nmap[NETWORK_DRIVER] = ""
	}

	network := types.Network{
		PreExist: nmap[PRE_EXIST].(bool),
		Name:     nmap[NETWORK_NAME].(string),
		Driver:   nmap[NETWORK_DRIVER].(string),
	}
	return network, true
}

func EnableVerbose() bool {
	return GetConfig().GetBool(DOCKER_COMPOSE_VERBOSE)
}

func SkipPullImages() bool {
	return GetConfig().GetBool(SKIP_PULL_IMAGES)
}

func EnableComposeTrace() bool {
	return GetConfig().GetBool(COMPOSE_TRACE)
}

func GetPollInterval() time.Duration {
	intervalStr := GetConfig().GetString(POD_MONITOR_INTERVAL)
	duration, err := time.ParseDuration(intervalStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.podmonitorinterval %s to duration, using 10s as default value",
			intervalStr)
		return 10 * time.Second
	}
	return duration
}

func GetHttpTimeout() time.Duration {
	timeoutStr := GetConfig().GetString(HTTP_TIMEOUT)
	duration, err := time.ParseDuration(timeoutStr)
	if err != nil {
		log.Warningf("unable to parse launchtask.httptimeout %s to duration, using 20s as default value", timeoutStr)
		return 20 * time.Second
	}
	return duration
}

func GetComposeHttpTimeout() int {
	return GetConfig().GetInt(COMPOSE_HTTP_TIMEOUT)
}

func EnableDebugMode() bool {
	return GetConfig().GetBool(DEBUG_MODE)
}

func IsService() bool {
	return GetConfig().GetBool(types.IS_SERVICE)
}

func CreateFileAppendMode(filename string) *os.File {

	File, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		log.Errorf("error in creating %v file, err: %v", filename, err)
		return os.Stdout
	}
	return File
}

// OverrideConfig gets labels with override prefix "config." and override configs with value of label
// Checking labels contain key word "config." instead of prefix since different framework will add different prefix for labels
func OverrideConfig(taskInfo *mesos.TaskInfo) {
	labelsList := taskInfo.GetLabels().GetLabels()

	for _, label := range labelsList {
		if strings.Contains(label.GetKey(), CONFIG_OVERRIDE_PREFIX) {
			parts := strings.SplitAfterN(label.GetKey(), CONFIG_OVERRIDE_PREFIX, 2)
			if len(parts) == 2 && GetConfig().IsSet(parts[1]) {
				GetConfig().Set(parts[1], label.GetValue())
				log.Infof("override config %s with %s", parts[1], label.GetValue())
			}
		}
	}
}
<|endoftext|>


<commit_before>package config

var GlobalConfig SystemConfig

type SystemConfig struct {
	HostName         string `toml:"host_name"`
	EndpointUser     string `toml:"endpoint_user"`
	EndpointName     string `toml:"endpoint_name"`
	EndpointURL      string `toml:"endpoint_url"`
	MaxCacheSize     int    `toml:"max_cache_size"`
	EndpointPassword string `toml:"endpoint_password"`
	JudgeRoot        string `toml:"judge_root"`
	DockerImage      string `toml:"docker_image"`
	DockerServer     string `toml:"docker_server"`
	CacheRoot        string `toml:"cache_root"`
	RootMemory       int64  `toml:"root_mem"`
}

type JudgeInfo struct {
	SubmitID      int64  `json:"submitid"`
	ContestID     int64  `json:"cid"`
	TeamID        int64  `json:"teamid"`
	JudgingID     int64  `json:"judgingid"`
	ProblemID     int64  `json:"probid"`
	Language      string `json:"langid"`
	TimeLimit     int64  `json:"maxruntime"`
	MemLimit      int64  `json:"memlimit"`
	OutputLimit   int64  `json:"output_limit"`
	BuildZip      string `json:"compile_script"`
	BuildZipMD5   string `json:"compile_script_md5sum"`
	RunZip        string `json:"run"`
	RunZipMD5     string `json:"run_md5sum"`
	CompareZip    string `json:"compare"`
	CompareZipMD5 string `json:"compare_md5sum"`
	CompareArgs   string `json:"compare_args"`
}

type TestcaseInfo struct {
	TestcaseID   int64  `json:"testcaseid"`
	Rank         int64  `json:"rank"`
	ProblemID    int64  `json:"probid"`
	MD5SumInput  string `json:"md5sum_input"`
	MD5SumOutput string `json:"md5sum_output"`
}

type SubmissionInfo struct {
	info []SubmissionFileInfo `json:""`
}

type SubmissionFileInfo struct {
	FileName string `json:"filename"`
	Content  string `json:"contetn"`
}
<commit_msg>Add Result Structure<commit_after>package config

var GlobalConfig SystemConfig

// Define Run results
const (
	ResTLE = "timelimit"
	ResWA  = "wrong-answer"
	ResAC  = "correct"
	ResCE  = "compiler-error"
	ResRE  = "run-error"
)

type SystemConfig struct {
	HostName         string `toml:"host_name"`
	EndpointUser     string `toml:"endpoint_user"`
	EndpointName     string `toml:"endpoint_name"`
	EndpointURL      string `toml:"endpoint_url"`
	MaxCacheSize     int    `toml:"max_cache_size"`
	EndpointPassword string `toml:"endpoint_password"`
	JudgeRoot        string `toml:"judge_root"`
	DockerImage      string `toml:"docker_image"`
	DockerServer     string `toml:"docker_server"`
	CacheRoot        string `toml:"cache_root"`
	RootMemory       int64  `toml:"root_mem"`
}

type JudgeInfo struct {
	SubmitID      int64  `json:"submitid"`
	ContestID     int64  `json:"cid"`
	TeamID        int64  `json:"teamid"`
	JudgingID     int64  `json:"judgingid"`
	ProblemID     int64  `json:"probid"`
	Language      string `json:"langid"`
	TimeLimit     int64  `json:"maxruntime"`
	MemLimit      int64  `json:"memlimit"`
	OutputLimit   int64  `json:"output_limit"`
	BuildZip      string `json:"compile_script"`
	BuildZipMD5   string `json:"compile_script_md5sum"`
	RunZip        string `json:"run"`
	RunZipMD5     string `json:"run_md5sum"`
	CompareZip    string `json:"compare"`
	CompareZipMD5 string `json:"compare_md5sum"`
	CompareArgs   string `json:"compare_args"`
}

type TestcaseInfo struct {
	TestcaseID   int64  `json:"testcaseid"`
	Rank         int64  `json:"rank"`
	ProblemID    int64  `json:"probid"`
	MD5SumInput  string `json:"md5sum_input"`
	MD5SumOutput string `json:"md5sum_output"`
}

type SubmissionInfo struct {
	info []SubmissionFileInfo `json:""`
}

type SubmissionFileInfo struct {
	FileName string `json:"filename"`
	Content  string `json:"contetn"`
}

type RunResult struct {
	JudgingID    int64
	TestcaseID   int64
	RunResult    string
	RunTime      float64
	OutputRun    string
	OutputError  string
	OutputSystem string
	OutputDiff   string
}
<|endoftext|>


<commit_before>package app

import (
	"cf/terminal"
	"github.com/codegangsta/cli"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"
)

var appHelpTemplate = `{{.Title "NAME:"}}
   {{.Name}} - {{.Usage}}

{{.Title "USAGE:"}}
   [environment variables] {{.Name}} [global options] command [arguments...] [command options]

{{.Title "VERSION:"}}
   {{.Version}}
   {{range .Commands}}
{{.SubTitle .Name}}{{range .CommandSubGroups}}
{{range .}}   {{.Name}} {{.Description}}
{{end}}{{end}}{{end}}
{{.Title "GLOBAL OPTIONS:"}}
   {{range .Flags}}{{.}}
   {{end}}
{{.Title "ENVIRONMENT VARIABLES:"}}
   CF_STAGING_TIMEOUT=15 max wait time for buildpack staging, in minutes
   CF_STARTUP_TIMEOUT=5 max wait time for app instance startup, in minutes
   CF_TRACE=true - print API request diagnostics to stdout
   CF_TRACE=path/to/trace.log - append API request diagnostics to a log file
   HTTP_PROXY=http://proxy.example.com:8080 - enable http proxying for API requests
`

type groupedCommands struct {
	Name             string
	CommandSubGroups [][]cmdPresenter
}

func (c groupedCommands) SubTitle(name string) string {
	return terminal.HeaderColor(name + ":")
}

type cmdPresenter struct {
	Name        string
	Description string
}

func newCmdPresenter(app *cli.App, maxNameLen int, cmdName string) (presenter cmdPresenter) {
	cmd := app.Command(cmdName)

	presenter.Name = presentCmdName(*cmd)
	padding := strings.Repeat(" ", maxNameLen-len(presenter.Name))
	presenter.Name = presenter.Name + padding

	presenter.Description = cmd.Description

	return
}

func presentCmdName(cmd cli.Command) (name string) {
	name = cmd.Name
	if cmd.ShortName != "" {
		name = name + ", " + cmd.ShortName
	}
	return
}

type appPresenter struct {
	cli.App
	Commands []groupedCommands
}

func (p appPresenter) Title(name string) string {
	return terminal.HeaderColor(name)
}

func getMaxCmdNameLength(app *cli.App) (length int) {
	for _, cmd := range app.Commands {
		name := presentCmdName(cmd)
		if len(name) > length {
			length = len(name)
		}
	}
	return
}

func newAppPresenter(app *cli.App) (presenter appPresenter) {
	maxNameLen := getMaxCmdNameLength(app)

	presenter.Name = app.Name
	presenter.Usage = app.Usage
	presenter.Version = app.Version
	presenter.Name = app.Name
	presenter.Flags = app.Flags

	presenter.Commands = []groupedCommands{
		{
			Name: "GETTING STARTED",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "login"),
					newCmdPresenter(app, maxNameLen, "logout"),
					newCmdPresenter(app, maxNameLen, "passwd"),
					newCmdPresenter(app, maxNameLen, "target"),
				}, {
					newCmdPresenter(app, maxNameLen, "api"),
					newCmdPresenter(app, maxNameLen, "auth"),
				},
			},
		}, {
			Name: "APPS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "apps"),
					newCmdPresenter(app, maxNameLen, "app"),
				}, {
					newCmdPresenter(app, maxNameLen, "push"),
					newCmdPresenter(app, maxNameLen, "scale"),
					newCmdPresenter(app, maxNameLen, "delete"),
					newCmdPresenter(app, maxNameLen, "rename"),
				}, {
					newCmdPresenter(app, maxNameLen, "start"),
					newCmdPresenter(app, maxNameLen, "stop"),
					newCmdPresenter(app, maxNameLen, "restart"),
				}, {
					newCmdPresenter(app, maxNameLen, "events"),
					newCmdPresenter(app, maxNameLen, "files"),
					newCmdPresenter(app, maxNameLen, "logs"),
				}, {
					newCmdPresenter(app, maxNameLen, "env"),
					newCmdPresenter(app, maxNameLen, "set-env"),
					newCmdPresenter(app, maxNameLen, "unset-env"),
				}, {
					newCmdPresenter(app, maxNameLen, "stacks"),
				},
			},
		}, {
			Name: "SERVICES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "marketplace"),
					newCmdPresenter(app, maxNameLen, "services"),
					newCmdPresenter(app, maxNameLen, "service"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-service"),
					newCmdPresenter(app, maxNameLen, "delete-service"),
					newCmdPresenter(app, maxNameLen, "rename-service"),
				}, {
					newCmdPresenter(app, maxNameLen, "bind-service"),
					newCmdPresenter(app, maxNameLen, "unbind-service"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-user-provided-service"),
					newCmdPresenter(app, maxNameLen, "update-user-provided-service"),
				},
			},
		}, {
			Name: "ORGS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "orgs"),
					newCmdPresenter(app, maxNameLen, "org"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-org"),
					newCmdPresenter(app, maxNameLen, "delete-org"),
					newCmdPresenter(app, maxNameLen, "rename-org"),
				},
			},
		}, {
			Name: "SPACES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "spaces"),
					newCmdPresenter(app, maxNameLen, "space"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-space"),
					newCmdPresenter(app, maxNameLen, "delete-space"),
					newCmdPresenter(app, maxNameLen, "rename-space"),
				},
			},
		}, {
			Name: "DOMAINS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "domains"),
					newCmdPresenter(app, maxNameLen, "create-domain"),
					newCmdPresenter(app, maxNameLen, "share-domain"),
					newCmdPresenter(app, maxNameLen, "map-domain"),
					newCmdPresenter(app, maxNameLen, "unmap-domain"),
					newCmdPresenter(app, maxNameLen, "delete-domain"),
				},
			},
		}, {
			Name: "ROUTES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "routes"),
					newCmdPresenter(app, maxNameLen, "create-route"),
					newCmdPresenter(app, maxNameLen, "map-route"),
					newCmdPresenter(app, maxNameLen, "unmap-route"),
					newCmdPresenter(app, maxNameLen, "delete-route"),
				},
			},
		}, {
			Name: "BUILDPACKS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "buildpacks"),
					newCmdPresenter(app, maxNameLen, "create-buildpack"),
					newCmdPresenter(app, maxNameLen, "update-buildpack"),
					newCmdPresenter(app, maxNameLen, "delete-buildpack"),
				},
			},
		}, {
			Name: "USER ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "create-user"),
					newCmdPresenter(app, maxNameLen, "delete-user"),
				}, {
					newCmdPresenter(app, maxNameLen, "org-users"),
					newCmdPresenter(app, maxNameLen, "set-org-role"),
					newCmdPresenter(app, maxNameLen, "unset-org-role"),
				}, {
					newCmdPresenter(app, maxNameLen, "space-users"),
					newCmdPresenter(app, maxNameLen, "set-space-role"),
					newCmdPresenter(app, maxNameLen, "unset-space-role"),
				},
			},
		}, {
			Name: "ORG ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "quotas"),
					newCmdPresenter(app, maxNameLen, "set-quota"),
				},
			},
		}, {
			Name: "SERVICE ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "service-auth-tokens"),
					newCmdPresenter(app, maxNameLen, "create-service-auth-token"),
					newCmdPresenter(app, maxNameLen, "update-service-auth-token"),
					newCmdPresenter(app, maxNameLen, "delete-service-auth-token"),
				}, {
					newCmdPresenter(app, maxNameLen, "service-brokers"),
					newCmdPresenter(app, maxNameLen, "create-service-broker"),
					newCmdPresenter(app, maxNameLen, "update-service-broker"),
					newCmdPresenter(app, maxNameLen, "delete-service-broker"),
					newCmdPresenter(app, maxNameLen, "rename-service-broker"),
				},
			},
		},
	}
	return
}

func showAppHelp(app *cli.App) {
	presenter := newAppPresenter(app)

	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
	t := template.Must(template.New("help").Parse(appHelpTemplate))
	t.Execute(w, presenter)
	w.Flush()
}
<commit_msg>Add curl to main help [finishes #57169686]<commit_after>package app

import (
	"cf/terminal"
	"github.com/codegangsta/cli"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"
)

var appHelpTemplate = `{{.Title "NAME:"}}
   {{.Name}} - {{.Usage}}

{{.Title "USAGE:"}}
   [environment variables] {{.Name}} [global options] command [arguments...] [command options]

{{.Title "VERSION:"}}
   {{.Version}}
   {{range .Commands}}
{{.SubTitle .Name}}{{range .CommandSubGroups}}
{{range .}}   {{.Name}} {{.Description}}
{{end}}{{end}}{{end}}
{{.Title "GLOBAL OPTIONS:"}}
   {{range .Flags}}{{.}}
   {{end}}
{{.Title "ENVIRONMENT VARIABLES:"}}
   CF_STAGING_TIMEOUT=15 max wait time for buildpack staging, in minutes
   CF_STARTUP_TIMEOUT=5 max wait time for app instance startup, in minutes
   CF_TRACE=true - print API request diagnostics to stdout
   CF_TRACE=path/to/trace.log - append API request diagnostics to a log file
   HTTP_PROXY=http://proxy.example.com:8080 - enable http proxying for API requests
`

type groupedCommands struct {
	Name             string
	CommandSubGroups [][]cmdPresenter
}

func (c groupedCommands) SubTitle(name string) string {
	return terminal.HeaderColor(name + ":")
}

type cmdPresenter struct {
	Name        string
	Description string
}

func newCmdPresenter(app *cli.App, maxNameLen int, cmdName string) (presenter cmdPresenter) {
	cmd := app.Command(cmdName)

	presenter.Name = presentCmdName(*cmd)
	padding := strings.Repeat(" ", maxNameLen-len(presenter.Name))
	presenter.Name = presenter.Name + padding

	presenter.Description = cmd.Description

	return
}

func presentCmdName(cmd cli.Command) (name string) {
	name = cmd.Name
	if cmd.ShortName != "" {
		name = name + ", " + cmd.ShortName
	}
	return
}

type appPresenter struct {
	cli.App
	Commands []groupedCommands
}

func (p appPresenter) Title(name string) string {
	return terminal.HeaderColor(name)
}

func getMaxCmdNameLength(app *cli.App) (length int) {
	for _, cmd := range app.Commands {
		name := presentCmdName(cmd)
		if len(name) > length {
			length = len(name)
		}
	}
	return
}

func newAppPresenter(app *cli.App) (presenter appPresenter) {
	maxNameLen := getMaxCmdNameLength(app)

	presenter.Name = app.Name
	presenter.Usage = app.Usage
	presenter.Version = app.Version
	presenter.Name = app.Name
	presenter.Flags = app.Flags

	presenter.Commands = []groupedCommands{
		{
			Name: "GETTING STARTED",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "login"),
					newCmdPresenter(app, maxNameLen, "logout"),
					newCmdPresenter(app, maxNameLen, "passwd"),
					newCmdPresenter(app, maxNameLen, "target"),
				}, {
					newCmdPresenter(app, maxNameLen, "api"),
					newCmdPresenter(app, maxNameLen, "auth"),
				},
			},
		}, {
			Name: "APPS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "apps"),
					newCmdPresenter(app, maxNameLen, "app"),
				}, {
					newCmdPresenter(app, maxNameLen, "push"),
					newCmdPresenter(app, maxNameLen, "scale"),
					newCmdPresenter(app, maxNameLen, "delete"),
					newCmdPresenter(app, maxNameLen, "rename"),
				}, {
					newCmdPresenter(app, maxNameLen, "start"),
					newCmdPresenter(app, maxNameLen, "stop"),
					newCmdPresenter(app, maxNameLen, "restart"),
				}, {
					newCmdPresenter(app, maxNameLen, "events"),
					newCmdPresenter(app, maxNameLen, "files"),
					newCmdPresenter(app, maxNameLen, "logs"),
				}, {
					newCmdPresenter(app, maxNameLen, "env"),
					newCmdPresenter(app, maxNameLen, "set-env"),
					newCmdPresenter(app, maxNameLen, "unset-env"),
				}, {
					newCmdPresenter(app, maxNameLen, "stacks"),
				},
			},
		}, {
			Name: "SERVICES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "marketplace"),
					newCmdPresenter(app, maxNameLen, "services"),
					newCmdPresenter(app, maxNameLen, "service"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-service"),
					newCmdPresenter(app, maxNameLen, "delete-service"),
					newCmdPresenter(app, maxNameLen, "rename-service"),
				}, {
					newCmdPresenter(app, maxNameLen, "bind-service"),
					newCmdPresenter(app, maxNameLen, "unbind-service"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-user-provided-service"),
					newCmdPresenter(app, maxNameLen, "update-user-provided-service"),
				},
			},
		}, {
			Name: "ORGS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "orgs"),
					newCmdPresenter(app, maxNameLen, "org"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-org"),
					newCmdPresenter(app, maxNameLen, "delete-org"),
					newCmdPresenter(app, maxNameLen, "rename-org"),
				},
			},
		}, {
			Name: "SPACES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "spaces"),
					newCmdPresenter(app, maxNameLen, "space"),
				}, {
					newCmdPresenter(app, maxNameLen, "create-space"),
					newCmdPresenter(app, maxNameLen, "delete-space"),
					newCmdPresenter(app, maxNameLen, "rename-space"),
				},
			},
		}, {
			Name: "DOMAINS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "domains"),
					newCmdPresenter(app, maxNameLen, "create-domain"),
					newCmdPresenter(app, maxNameLen, "share-domain"),
					newCmdPresenter(app, maxNameLen, "map-domain"),
					newCmdPresenter(app, maxNameLen, "unmap-domain"),
					newCmdPresenter(app, maxNameLen, "delete-domain"),
				},
			},
		}, {
			Name: "ROUTES",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "routes"),
					newCmdPresenter(app, maxNameLen, "create-route"),
					newCmdPresenter(app, maxNameLen, "map-route"),
					newCmdPresenter(app, maxNameLen, "unmap-route"),
					newCmdPresenter(app, maxNameLen, "delete-route"),
				},
			},
		}, {
			Name: "BUILDPACKS",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "buildpacks"),
					newCmdPresenter(app, maxNameLen, "create-buildpack"),
					newCmdPresenter(app, maxNameLen, "update-buildpack"),
					newCmdPresenter(app, maxNameLen, "delete-buildpack"),
				},
			},
		}, {
			Name: "USER ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "create-user"),
					newCmdPresenter(app, maxNameLen, "delete-user"),
				}, {
					newCmdPresenter(app, maxNameLen, "org-users"),
					newCmdPresenter(app, maxNameLen, "set-org-role"),
					newCmdPresenter(app, maxNameLen, "unset-org-role"),
				}, {
					newCmdPresenter(app, maxNameLen, "space-users"),
					newCmdPresenter(app, maxNameLen, "set-space-role"),
					newCmdPresenter(app, maxNameLen, "unset-space-role"),
				},
			},
		}, {
			Name: "ORG ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "quotas"),
					newCmdPresenter(app, maxNameLen, "set-quota"),
				},
			},
		}, {
			Name: "SERVICE ADMIN",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "service-auth-tokens"),
					newCmdPresenter(app, maxNameLen, "create-service-auth-token"),
					newCmdPresenter(app, maxNameLen, "update-service-auth-token"),
					newCmdPresenter(app, maxNameLen, "delete-service-auth-token"),
				}, {
					newCmdPresenter(app, maxNameLen, "service-brokers"),
					newCmdPresenter(app, maxNameLen, "create-service-broker"),
					newCmdPresenter(app, maxNameLen, "update-service-broker"),
					newCmdPresenter(app, maxNameLen, "delete-service-broker"),
					newCmdPresenter(app, maxNameLen, "rename-service-broker"),
				},
			},
		}, {
			Name: "ADVANCED",
			CommandSubGroups: [][]cmdPresenter{
				{
					newCmdPresenter(app, maxNameLen, "curl"),
				},
			},
		},
	}
	return
}

func showAppHelp(app *cli.App) {
	presenter := newAppPresenter(app)

	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
	t := template.Must(template.New("help").Parse(appHelpTemplate))
	t.Execute(w, presenter)
	w.Flush()
}
<|endoftext|>


<commit_before>/*

	record is a package to open, read, and save game records stored in
	filesystem's format.

	Note that because reading the files from disk is expensive, this library
	maintains a cache of records by filename that it returns, for a
	considerable performance boost. This means that changes in the filesystem
	while the storage layer is running that aren't mediated by this controller
	will cause undefined behavior.

*/
package record

import (
	"encoding/json"
	"errors"
	"github.com/jkomoros/boardgame"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
	"io/ioutil"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const randomStringChars = "ABCDEF0123456789"

var recCache map[string]*Record

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
	recCache = make(map[string]*Record, 16)
}

//Record is a record of moves, states, and game. Get a new one based on the
//contents of a file with New(). Instantiate directly for a blank one.
type Record struct {
	data   *storageRecord
	states []boardgame.StateStorageRecord
}

type storageRecord struct {
	Game  *boardgame.GameStorageRecord
	Moves []*boardgame.MoveStorageRecord
	//StatePatches are diffs from the state before. Get the actual state for a
	//version with State().
	StatePatches []json.RawMessage
}

//New returns a new record with the data encoded in the file. If you want an
//empty record, just instantiate a blank struct. If a record with that
//filename has already been saved, it will return that record.
func New(filename string) (*Record, error) {

	if cachedRec := recCache[filename]; cachedRec != nil {
		return cachedRec, nil
	}

	data, err := ioutil.ReadFile(filename)

	if err != nil {
		return nil, errors.New("Couldn't read file: " + err.Error())
	}

	var storageRec storageRecord

	if err := json.Unmarshal(data, &storageRec); err != nil {
		return nil, errors.New("Couldn't decode json: " + err.Error())
	}

	return &Record{
		data: &storageRec,
	}, nil
}

func (r *Record) Game() *boardgame.GameStorageRecord {
	if r.data == nil {
		return nil
	}
	return r.data.Game
}

func (r *Record) Move(version int) (*boardgame.MoveStorageRecord, error) {
	if r.data == nil {
		return nil, errors.New("No data")
	}
	if version < 0 {
		return nil, errors.New("Version too low")
	}

	version -= 1
	//version is effectively 1-indexed, since we don't store a move for the
	//first version, but we store them in 0-indexed since we use the array
	//index. So convert to that.
	if len(r.data.Moves) < version {
		return nil, errors.New("Not enough moves")
	}

	return r.data.Moves[version], nil
}

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

func safeOvewritefile(path string, blob []byte) error {

	//Check for the easy case where the file doesn't exist yet
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return ioutil.WriteFile(path, blob, 0644)
	}

	dir, name := filepath.Split(path)

	ext := filepath.Ext(name)

	nameWithoutExt := strings.TrimSuffix(name, ext)

	tempFileName := filepath.Join(dir, nameWithoutExt+".TEMP."+randomString(6)+ext)

	if err := ioutil.WriteFile(tempFileName, blob, 0644); err != nil {
		return errors.New("Couldn't write temp file: " + err.Error())
	}

	if err := os.Remove(path); err != nil {
		return errors.New("Couldn't delete the original file: " + err.Error())
	}

	if err := os.Rename(tempFileName, path); err != nil {
		return errors.New("Couldn't rename the new file: " + err.Error())
	}

	return nil

}

//Save saves to the given path.
func (r *Record) Save(filename string) error {

	blob, err := json.MarshalIndent(r.data, "", "\t")

	if err != nil {
		return errors.New("Couldn't marshal blob: " + err.Error())
	}

	if err := safeOvewritefile(filename, blob); err != nil {
		return err
	}

	recCache[filename] = r

	return nil
}

//AddGameAndCurrentState adds the game, state, and move (if non-nil), ready
//for saving. Designed to be used in a SaveGameAndCurrentState method.
func (r *Record) AddGameAndCurrentState(game *boardgame.GameStorageRecord, state boardgame.StateStorageRecord, move *boardgame.MoveStorageRecord) error {

	if r.data == nil {
		r.data = &storageRecord{}
	}

	r.data.Game = game

	if move != nil {
		r.data.Moves = append(r.data.Moves, move)
	}

	lastState, err := r.State(len(r.data.StatePatches) - 1)

	if err != nil {
		return errors.New("Couldn't fetch last state: " + err.Error())
	}

	differ := gojsondiff.New()

	patch, err := differ.Compare(lastState, state)

	if err != nil {
		return err
	}

	f := formatter.NewDeltaFormatter()

	js, err := f.FormatAsJson(patch)

	if err != nil {
		return errors.New("Couldn't format patch as json: " + err.Error())
	}

	formattedPatch, err := json.Marshal(js)

	if err != nil {
		return errors.New("Couldn't format patch json to byte: " + err.Error())
	}

	r.states = append(r.states, state)
	r.data.StatePatches = append(r.data.StatePatches, formattedPatch)

	return nil

}

//State fetches the State object at that version. It can return an error
//because under the covers it has to apply serialized patches.
func (r *Record) State(version int) (boardgame.StateStorageRecord, error) {

	if r.data == nil {
		return nil, errors.New("No data")
	}

	if version < 0 {
		//The base object that version 0 is diffed against is the empty object
		return boardgame.StateStorageRecord(`{}`), nil
	}

	if len(r.states) > version {
		return r.states[version], nil
	}

	//Otherwise, derive forward, recursively.

	lastStateBlob, err := r.State(version - 1)

	if err != nil {
		//Don't decorate the error because it will likely stack
		return nil, err
	}

	unmarshaller := gojsondiff.NewUnmarshaller()

	patch, err := unmarshaller.UnmarshalBytes(r.data.StatePatches[version])

	if err != nil {
		return nil, err
	}

	differ := gojsondiff.New()

	var state map[string]interface{}

	if err := json.Unmarshal(lastStateBlob, &state); err != nil {
		return nil, errors.New("Couldn't unmarshal last blob: " + err.Error())
	}

	differ.ApplyPatch(state, patch)

	blob, err := json.MarshalIndent(state, "", "\t")

	if err != nil {
		return nil, errors.New("Couldn't marshal modified blob: " + err.Error())
	}

	r.states = append(r.states, blob)

	return blob, nil

}
<commit_msg>Add a Description field that persists if set in the json file. Part of #648.<commit_after>/*

	record is a package to open, read, and save game records stored in
	filesystem's format.

	Note that because reading the files from disk is expensive, this library
	maintains a cache of records by filename that it returns, for a
	considerable performance boost. This means that changes in the filesystem
	while the storage layer is running that aren't mediated by this controller
	will cause undefined behavior.

*/
package record

import (
	"encoding/json"
	"errors"
	"github.com/jkomoros/boardgame"
	"github.com/yudai/gojsondiff"
	"github.com/yudai/gojsondiff/formatter"
	"io/ioutil"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const randomStringChars = "ABCDEF0123456789"

var recCache map[string]*Record

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
	recCache = make(map[string]*Record, 16)
}

//Record is a record of moves, states, and game. Get a new one based on the
//contents of a file with New(). Instantiate directly for a blank one.
type Record struct {
	data   *storageRecord
	states []boardgame.StateStorageRecord
}

type storageRecord struct {
	Game  *boardgame.GameStorageRecord
	Moves []*boardgame.MoveStorageRecord
	//StatePatches are diffs from the state before. Get the actual state for a
	//version with State().
	StatePatches []json.RawMessage
	Description  string `json:"omitempty"`
}

//New returns a new record with the data encoded in the file. If you want an
//empty record, just instantiate a blank struct. If a record with that
//filename has already been saved, it will return that record.
func New(filename string) (*Record, error) {

	if cachedRec := recCache[filename]; cachedRec != nil {
		return cachedRec, nil
	}

	data, err := ioutil.ReadFile(filename)

	if err != nil {
		return nil, errors.New("Couldn't read file: " + err.Error())
	}

	var storageRec storageRecord

	if err := json.Unmarshal(data, &storageRec); err != nil {
		return nil, errors.New("Couldn't decode json: " + err.Error())
	}

	return &Record{
		data: &storageRec,
	}, nil
}

func (r *Record) Game() *boardgame.GameStorageRecord {
	if r.data == nil {
		return nil
	}
	return r.data.Game
}

//Description returns the top-level description string set in the json file.
//There's no way to set this except by modifying the JSON serialization
//directly, but it can be read from record.
func (r *Record) Description() string {
	if r.data == nil {
		return ""
	}
	return r.data.Description
}

func (r *Record) Move(version int) (*boardgame.MoveStorageRecord, error) {
	if r.data == nil {
		return nil, errors.New("No data")
	}
	if version < 0 {
		return nil, errors.New("Version too low")
	}

	version -= 1
	//version is effectively 1-indexed, since we don't store a move for the
	//first version, but we store them in 0-indexed since we use the array
	//index. So convert to that.
	if len(r.data.Moves) < version {
		return nil, errors.New("Not enough moves")
	}

	return r.data.Moves[version], nil
}

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

func safeOvewritefile(path string, blob []byte) error {

	//Check for the easy case where the file doesn't exist yet
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return ioutil.WriteFile(path, blob, 0644)
	}

	dir, name := filepath.Split(path)

	ext := filepath.Ext(name)

	nameWithoutExt := strings.TrimSuffix(name, ext)

	tempFileName := filepath.Join(dir, nameWithoutExt+".TEMP."+randomString(6)+ext)

	if err := ioutil.WriteFile(tempFileName, blob, 0644); err != nil {
		return errors.New("Couldn't write temp file: " + err.Error())
	}

	if err := os.Remove(path); err != nil {
		return errors.New("Couldn't delete the original file: " + err.Error())
	}

	if err := os.Rename(tempFileName, path); err != nil {
		return errors.New("Couldn't rename the new file: " + err.Error())
	}

	return nil

}

//Save saves to the given path.
func (r *Record) Save(filename string) error {

	blob, err := json.MarshalIndent(r.data, "", "\t")

	if err != nil {
		return errors.New("Couldn't marshal blob: " + err.Error())
	}

	if err := safeOvewritefile(filename, blob); err != nil {
		return err
	}

	recCache[filename] = r

	return nil
}

//AddGameAndCurrentState adds the game, state, and move (if non-nil), ready
//for saving. Designed to be used in a SaveGameAndCurrentState method.
func (r *Record) AddGameAndCurrentState(game *boardgame.GameStorageRecord, state boardgame.StateStorageRecord, move *boardgame.MoveStorageRecord) error {

	if r.data == nil {
		r.data = &storageRecord{}
	}

	r.data.Game = game

	if move != nil {
		r.data.Moves = append(r.data.Moves, move)
	}

	lastState, err := r.State(len(r.data.StatePatches) - 1)

	if err != nil {
		return errors.New("Couldn't fetch last state: " + err.Error())
	}

	differ := gojsondiff.New()

	patch, err := differ.Compare(lastState, state)

	if err != nil {
		return err
	}

	f := formatter.NewDeltaFormatter()

	js, err := f.FormatAsJson(patch)

	if err != nil {
		return errors.New("Couldn't format patch as json: " + err.Error())
	}

	formattedPatch, err := json.Marshal(js)

	if err != nil {
		return errors.New("Couldn't format patch json to byte: " + err.Error())
	}

	r.states = append(r.states, state)
	r.data.StatePatches = append(r.data.StatePatches, formattedPatch)

	return nil

}

//State fetches the State object at that version. It can return an error
//because under the covers it has to apply serialized patches.
func (r *Record) State(version int) (boardgame.StateStorageRecord, error) {

	if r.data == nil {
		return nil, errors.New("No data")
	}

	if version < 0 {
		//The base object that version 0 is diffed against is the empty object
		return boardgame.StateStorageRecord(`{}`), nil
	}

	if len(r.states) > version {
		return r.states[version], nil
	}

	//Otherwise, derive forward, recursively.

	lastStateBlob, err := r.State(version - 1)

	if err != nil {
		//Don't decorate the error because it will likely stack
		return nil, err
	}

	unmarshaller := gojsondiff.NewUnmarshaller()

	patch, err := unmarshaller.UnmarshalBytes(r.data.StatePatches[version])

	if err != nil {
		return nil, err
	}

	differ := gojsondiff.New()

	var state map[string]interface{}

	if err := json.Unmarshal(lastStateBlob, &state); err != nil {
		return nil, errors.New("Couldn't unmarshal last blob: " + err.Error())
	}

	differ.ApplyPatch(state, patch)

	blob, err := json.MarshalIndent(state, "", "\t")

	if err != nil {
		return nil, errors.New("Couldn't marshal modified blob: " + err.Error())
	}

	r.states = append(r.states, blob)

	return blob, nil

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"ieveapi/apicache"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

type apiReq struct {
	url    string
	params map[string]string
	data   []byte
	apiErr apicache.APIError

	expires time.Time

	httpCode int
	err      error
	respChan chan apiReq
}

var workChan chan apiReq

var apiClient apicache.Client
var activeWorkerCount, workerCount int32

func APIReq(url string, params map[string]string) ([]byte, int, error) {
	if atomic.LoadInt32(&workerCount) <= 0 {
		startWorkers()
	}

	respChan := make(chan apiReq)
	req := apiReq{url: url, params: params, respChan: respChan}
	workChan <- req

	resp := <-respChan
	close(respChan)

	return resp.data, resp.httpCode, resp.err
}

func worker(reqChan chan apiReq) {
	atomic.AddInt32(&workerCount, 1)
	for req := range reqChan {
		atomic.AddInt32(&activeWorkerCount, 1)
		apireq := apicache.NewRequest(req.url)
		for k, v := range req.params {
			apireq.Set(k, v)
		}

		resp, err := apireq.Do()
		req.data = resp.Data
		req.expires = resp.Expires
		req.apiErr = resp.Error
		req.httpCode = resp.HTTPCode
		req.err = err

		var errorStr string
		if resp.Error.ErrorCode != 0 {
			errorStr = fmt.Sprintf(" Error %d: %s", resp.Error.ErrorCode, resp.Error.ErrorText)
		}
		log.Printf("%s - %+v FromCache: %v HTTP: %d%s", req.url, req.params, resp.FromCache, resp.HTTPCode, errorStr)

		req.respChan <- req
		atomic.AddInt32(&activeWorkerCount, -1)
	}
	atomic.AddInt32(&workerCount, -1)
}

var startWorkersOnce = &sync.Once{}

func startWorkers() {
	startWorkersOnce.Do(realStartWorkers)
}

func realStartWorkers() {
	log.Printf("Starting %d Workers...", conf.Workers)
	workChan = make(chan apiReq)

	for i := 0; i < conf.Workers; i++ {
		go worker(workChan)
	}
}

func stopWorkers() {
	close(workChan)
	for atomic.LoadInt32(&activeWorkerCount) > 0 {
		time.Sleep(10 * time.Millisecond)
	}
	startWorkersOnce = &sync.Once{}
}

func PrintWorkerStats() {
	active, loaded := GetWorkerStats()
	log.Printf("%d workers idle, %d workers active.", loaded-active, active)
}

func GetWorkerStats() (int32, int32) {
	return atomic.LoadInt32(&activeWorkerCount), atomic.LoadInt32(&workerCount)
}
<commit_msg>added expiration to logging<commit_after>package main

import (
	"fmt"
	"ieveapi/apicache"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

type apiReq struct {
	url    string
	params map[string]string
	data   []byte
	apiErr apicache.APIError

	expires time.Time

	httpCode int
	err      error
	respChan chan apiReq
}

var workChan chan apiReq

var apiClient apicache.Client
var activeWorkerCount, workerCount int32

func APIReq(url string, params map[string]string) ([]byte, int, error) {
	if atomic.LoadInt32(&workerCount) <= 0 {
		startWorkers()
	}

	respChan := make(chan apiReq)
	req := apiReq{url: url, params: params, respChan: respChan}
	workChan <- req

	resp := <-respChan
	close(respChan)

	return resp.data, resp.httpCode, resp.err
}

func worker(reqChan chan apiReq) {
	atomic.AddInt32(&workerCount, 1)
	for req := range reqChan {
		atomic.AddInt32(&activeWorkerCount, 1)
		apireq := apicache.NewRequest(req.url)
		for k, v := range req.params {
			apireq.Set(k, v)
		}

		resp, err := apireq.Do()
		req.data = resp.Data
		req.expires = resp.Expires
		req.apiErr = resp.Error
		req.httpCode = resp.HTTPCode
		req.err = err

		var errorStr string
		if resp.Error.ErrorCode != 0 {
			errorStr = fmt.Sprintf(" Error %d: %s", resp.Error.ErrorCode, resp.Error.ErrorText)
		}
		log.Printf("%s - %+v FromCache: %v HTTP: %d Expires: %s%s", req.url, req.params, resp.FromCache, resp.HTTPCode, resp.Expires.Format("2006-01-02 15:04:05"), errorStr)

		req.respChan <- req
		atomic.AddInt32(&activeWorkerCount, -1)
	}
	atomic.AddInt32(&workerCount, -1)
}

var startWorkersOnce = &sync.Once{}

func startWorkers() {
	startWorkersOnce.Do(realStartWorkers)
}

func realStartWorkers() {
	log.Printf("Starting %d Workers...", conf.Workers)
	workChan = make(chan apiReq)

	for i := 0; i < conf.Workers; i++ {
		go worker(workChan)
	}
}

func stopWorkers() {
	close(workChan)
	for atomic.LoadInt32(&activeWorkerCount) > 0 {
		time.Sleep(10 * time.Millisecond)
	}
	startWorkersOnce = &sync.Once{}
}

func PrintWorkerStats() {
	active, loaded := GetWorkerStats()
	log.Printf("%d workers idle, %d workers active.", loaded-active, active)
}

func GetWorkerStats() (int32, int32) {
	return atomic.LoadInt32(&activeWorkerCount), atomic.LoadInt32(&workerCount)
}
<|endoftext|>


<commit_before>// +build !windows

package local

import (
	"os"
	"restic/fs"
	"syscall"
)

// set file to readonly
func setNewFileMode(f string, fi os.FileInfo) error {
	err := fs.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
	// ignore the error if the FS does not support setting this mode (e.g. CIFS with gvfs on Linux)
	if err == syscall.ENOTSUP {
		err = nil
	}
	return err
}
<commit_msg>Test error for os.PathError<commit_after>// +build !windows

package local

import (
	"os"
	"restic/fs"
	"syscall"
)

// set file to readonly
func setNewFileMode(f string, fi os.FileInfo) error {
	err := fs.Chmod(f, fi.Mode()&os.FileMode(^uint32(0222)))
	// ignore the error if the FS does not support setting this mode (e.g. CIFS with gvfs on Linux)
	if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ENOTSUP {
		err = nil
	}
	return err
}
<|endoftext|>


<commit_before>package config

// GetDeviceKey -- Returns the device's key (or an empty string if not defined)
func GetDeviceKey() string {
	rc, err := GetValue("device", "key")
	if err != nil {
		return ""
	}
	return rc
}
<commit_msg>added config.GetDeviceID()<commit_after>package config

// GetDeviceID -- Returns the devId if available (otherwise returns an empty string)
func GetDeviceID() string {
	rc, err := GetValue("device", "dev-id")
	if err != nil {
		return ""
	}
	return rc
}

// GetDeviceKey -- Returns the device's key (or an empty string if not defined)
func GetDeviceKey() string {
	rc, err := GetValue("device", "key")
	if err != nil {
		return ""
	}
	return rc
}
<|endoftext|>


<commit_before>package interp

import (
	"bufio"
	"fmt"
	"go/parser"
	"os"
)

func Run(env *Env, packages map[string]Package) {
	scanner := bufio.NewScanner(os.Stdin)
	for prompt("; ", scanner) {
		x, err := parser.ParseExpr(scanner.Text())
		if err != nil {
			fmt.Println(err)
			continue
		}
		a, err := Eval(x, env)
		if err != nil {
			fmt.Println(err)
			continue
		}
		for _, v := range a {
			fmt.Println(v.Interface())
		}
	}
	fmt.Println()
}
<commit_msg>print struct field names<commit_after>package interp

import (
	"bufio"
	"fmt"
	"go/parser"
	"os"
)

func Run(env *Env, packages map[string]Package) {
	scanner := bufio.NewScanner(os.Stdin)
	for prompt("; ", scanner) {
		x, err := parser.ParseExpr(scanner.Text())
		if err != nil {
			fmt.Println(err)
			continue
		}
		a, err := Eval(x, env)
		if err != nil {
			fmt.Println(err)
			continue
		}
		for _, v := range a {
			fmt.Printf("%+v\n", v.Interface())
		}
	}
	fmt.Println()
}
<|endoftext|>


<commit_before>package interp

import (
	"log"
	"reflect"
	"strconv"
)

// Cat defines interpreter type categories
type Cat uint

// Types for go language
const (
	Unset Cat = iota
	AliasT
	ArrayT
	BinT
	BinPkgT
	BoolT
	BuiltinT
	ByteT
	ChanT
	Complex64T
	Complex128T
	ErrorT
	Float32T
	Float64T
	FuncT
	InterfaceT
	IntT
	Int8T
	Int16T
	Int32T
	Int64T
	MapT
	PtrT
	RuneT
	SrcPkgT
	StringT
	StructT
	UintT
	Uint8T
	Uint16T
	Uint32T
	Uint64T
	UintptrT
	ValueT
)

var cats = [...]string{
	Unset:       "Unset",
	AliasT:      "AliasT",
	ArrayT:      "ArrayT",
	BinT:        "BinT",
	BinPkgT:     "BinPkgT",
	ByteT:       "ByteT",
	BoolT:       "BoolT",
	BuiltinT:    "BuiltinT",
	ChanT:       "ChanT",
	Complex64T:  "Complex64T",
	Complex128T: "Complex128T",
	ErrorT:      "ErrorT",
	Float32T:    "Float32",
	Float64T:    "Float64T",
	FuncT:       "FuncT",
	InterfaceT:  "InterfaceT",
	IntT:        "IntT",
	Int8T:       "Int8T",
	Int16T:      "Int16T",
	Int32T:      "Int32T",
	Int64T:      "Int64T",
	MapT:        "MapT",
	PtrT:        "PtrT",
	RuneT:       "RuneT",
	SrcPkgT:     "SrcPkgT",
	StringT:     "StringT",
	StructT:     "StructT",
	UintT:       "UintT",
	Uint8T:      "Uint8T",
	Uint16T:     "Uint16T",
	Uint32T:     "Uint32T",
	Uint64T:     "Uint64T",
	UintptrT:    "UintptrT",
	ValueT:      "ValueT",
}

func (c Cat) String() string {
	if c < Cat(len(cats)) {
		return cats[c]
	}
	return "Cat(" + strconv.Itoa(int(c)) + ")"
}

// StructField type defines a field in a struct
type StructField struct {
	name string
	typ  *Type
}

// Type defines the internal representation of types in the interpreter
type Type struct {
	cat      Cat           // Type category
	field    []StructField // Array of struct fields if StrucT or nil
	key      *Type         // Type of key element if MapT or nil
	val      *Type         // Type of value element if ChanT, MapT, PtrT, AliasT or ArrayT
	arg      []*Type       // Argument types if FuncT or nil
	ret      []*Type       // Return types if FuncT or nil
	method   []*Node       // Associated methods or nil
	rtype    reflect.Type  // Reflection type if ValueT, or nil
	rzero    reflect.Value // Reflection zero settable value, or nil
	variadic bool          // true if type is variadic
	nindex   int           // node index (for debug only)
}

// TypeMap defines a map of Types indexed by type names
type TypeMap map[string]*Type

var defaultTypes TypeMap = map[string]*Type{
	"bool":       &Type{cat: BoolT},
	"byte":       &Type{cat: ByteT},
	"complex64":  &Type{cat: Complex64T},
	"complex128": &Type{cat: Complex128T},
	"error":      &Type{cat: ErrorT},
	"float32":    &Type{cat: Float32T},
	"float64":    &Type{cat: Float64T},
	"int":        &Type{cat: IntT},
	"int8":       &Type{cat: Int8T},
	"int16":      &Type{cat: Int16T},
	"int32":      &Type{cat: Int32T},
	"int64":      &Type{cat: Int64T},
	"rune":       &Type{cat: RuneT},
	"string":     &Type{cat: StringT},
	"uint":       &Type{cat: UintT},
	"uint8":      &Type{cat: Uint8T},
	"uint16":     &Type{cat: Uint16T},
	"uint32":     &Type{cat: Uint32T},
	"uint64":     &Type{cat: Uint64T},
	"uintptr":    &Type{cat: UintptrT},
}

// return a type definition for the corresponding AST subtree
func nodeType(interp *Interpreter, scope *Scope, n *Node) *Type {
	if n.typ != nil {
		return n.typ
	}
	var t = &Type{nindex: n.index}
	switch n.kind {
	case ArrayType:
		t.cat = ArrayT
		t.val = nodeType(interp, scope, n.child[0])
	case BasicLit:
		switch n.val.(type) {
		case bool:
			t.cat = BoolT
		case byte:
			t.cat = ByteT
		case float32:
			t.cat = Float32T
		case float64:
			t.cat = Float64T
		case int:
			t.cat = IntT
		case string:
			t.cat = StringT
		default:
			log.Panicf("Missing support for basic type %T, node %v\n", n.val, n.index)
		}
	case ChanType:
		t.cat = ChanT
		t.val = nodeType(interp, scope, n.child[0])
	case Ellipsis:
		t = nodeType(interp, scope, n.child[0])
		t.variadic = true
	case FuncType:
		t.cat = FuncT
		for _, arg := range n.child[0].child {
			t.arg = append(t.arg, nodeType(interp, scope, arg.child[len(arg.child)-1]))
		}
		if len(n.child) == 2 {
			for _, ret := range n.child[1].child {
				t.ret = append(t.ret, nodeType(interp, scope, ret.child[len(ret.child)-1]))
			}
		}
	case Ident:
		t = interp.types[n.ident]
	case InterfaceType:
		t.cat = InterfaceT
		//for _, method := range n.child[0].child {
		//	t.method = append(t.method, nodeType(interp, scope, method))
		//}
	case MapType:
		t.cat = MapT
		t.key = nodeType(interp, scope, n.child[0])
		t.val = nodeType(interp, scope, n.child[1])
	case SelectorExpr:
		pkgName, typeName := n.child[0].ident, n.child[1].ident
		if pkg, _, ok := scope.lookup(pkgName); ok {
			if typ, ok := (*pkg.pkgbin)[typeName]; ok {
				t.cat = ValueT
				t.rtype = reflect.TypeOf(typ).Elem()
			}
		}
	case StarExpr:
		t.cat = PtrT
		t.val = nodeType(interp, scope, n.child[0])
	case StructType:
		t.cat = StructT
		for _, c := range n.child[0].child {
			if len(c.child) == 1 {
				t.field = append(t.field, StructField{typ: nodeType(interp, scope, c.child[0])})
			} else {
				l := len(c.child)
				typ := nodeType(interp, scope, c.child[l-1])
				for _, d := range c.child[:l-1] {
					t.field = append(t.field, StructField{name: d.ident, typ: typ})
				}
			}
		}
	default:
		log.Panicln("type definition not implemented for node", n.index, n.kind)
	}
	return t
}

var zeroValues = [...]interface{}{
	BoolT:       false,
	ByteT:       byte(0),
	Complex64T:  complex64(0),
	Complex128T: complex128(0),
	ErrorT:      error(nil),
	Float32T:    float32(0),
	Float64T:    float64(0),
	IntT:        int(0),
	Int8T:       int8(0),
	Int16T:      int16(0),
	Int32T:      int32(0),
	Int64T:      int64(0),
	RuneT:       rune(0),
	StringT:     "",
	UintT:       uint(0),
	Uint8T:      uint8(0),
	Uint16T:     uint16(0),
	Uint32T:     uint32(0),
	Uint64T:     uint64(0),
	UintptrT:    uintptr(0),
	ValueT:      nil,
}

// zero instantiates and return a zero value object for the givent type t
func (t *Type) zero() interface{} {
	if t.cat >= Cat(len(zeroValues)) {
		return nil
	}
	switch t.cat {
	case AliasT:
		return t.val.zero()
	case StructT:
		z := make([]interface{}, len(t.field))
		for i, f := range t.field {
			z[i] = f.typ.zero()
		}
		return z
	case ValueT:
		return t.rzero
	default:
		return zeroValues[t.cat]
	}
}

// fieldType returns the field type of a struct or *struct type
func (t *Type) fieldType(index int) *Type {
	if t.cat == PtrT {
		return t.val.fieldType(index)
	}
	return t.field[index].typ
}

// fieldIndex returns the field index from name in a struct, or -1 if not found
func (t *Type) fieldIndex(name string) int {
	if t.cat == PtrT {
		return t.val.fieldIndex(name)
	}
	for i, field := range t.field {
		if name == field.name {
			return i
		}
	}
	return -1
}

// lookupField return a list of indices, i.e. a path to access a field in a struct object
func (t *Type) lookupField(name string) []int {
	var index []int
	if fi := t.fieldIndex(name); fi < 0 {
		for i, f := range t.field {
			if f.name == "" {
				if index2 := f.typ.lookupField(name); len(index2) > 0 {
					index = append([]int{i}, index2...)
					break
				}
			}
		}
	} else {
		index = append(index, fi)
	}
	return index
}

// getMethod returns a pointer to the method definition
func (t *Type) getMethod(name string) *Node {
	for _, m := range t.method {
		if name == m.ident {
			return m
		}
	}
	return nil
}

// lookupMethod returns a pointer to method definition associated to type t
// and the list of indices to access the right struct field, in case of a promoted method
func (t *Type) lookupMethod(name string) (*Node, []int) {
	if t.cat == PtrT {
		m, index := t.val.lookupMethod(name)
		return m, index
	}
	var index []int
	if m := t.getMethod(name); m == nil {
		for i, f := range t.field {
			if f.name == "" {
				if m, index2 := f.typ.lookupMethod(name); m != nil {
					index = append([]int{i}, index2...)
					return m, index
				}
			}
		}
	} else {
		return m, index
	}
	return nil, index
}

// ptrTo returns the pointer type with element t.
func ptrTo(t *Type) *Type {
	return &Type{cat: PtrT, val: t}
}

// TypeOf returns the reflection type of dynamic interpreter type t.
func (t *Type) TypeOf() reflect.Type {
	switch t.cat {
	case ArrayT:
		return reflect.SliceOf(t.val.TypeOf())
	case ChanT:
		return reflect.ChanOf(reflect.BothDir, t.val.TypeOf())
	case FuncT:
		in := make([]reflect.Type, len(t.arg))
		out := make([]reflect.Type, len(t.ret))
		for i, v := range t.arg {
			in[i] = v.TypeOf()
		}
		for i, v := range t.ret {
			out[i] = v.TypeOf()
		}
		return reflect.FuncOf(in, out, false)
	case MapT:
		return reflect.MapOf(t.key.TypeOf(), t.val.TypeOf())
	case PtrT:
		return reflect.PtrTo(t.val.TypeOf())
	case StructT:
		var fields = []reflect.StructField{}
		for _, f := range t.field {
			if !canExport(f.name) {
				continue
			}
			fields = append(fields, reflect.StructField{Name: f.name, Type: f.typ.TypeOf()})
		}
		return reflect.StructOf(fields)
	case ValueT:
		return t.rtype
	default:
		return reflect.TypeOf(t.zero())
	}
}
<commit_msg>Support exporting a function which returns error type<commit_after>package interp

import (
	"log"
	"reflect"
	"strconv"
)

// Cat defines interpreter type categories
type Cat uint

// Types for go language
const (
	Unset Cat = iota
	AliasT
	ArrayT
	BinT
	BinPkgT
	BoolT
	BuiltinT
	ByteT
	ChanT
	Complex64T
	Complex128T
	ErrorT
	Float32T
	Float64T
	FuncT
	InterfaceT
	IntT
	Int8T
	Int16T
	Int32T
	Int64T
	MapT
	PtrT
	RuneT
	SrcPkgT
	StringT
	StructT
	UintT
	Uint8T
	Uint16T
	Uint32T
	Uint64T
	UintptrT
	ValueT
)

var cats = [...]string{
	Unset:       "Unset",
	AliasT:      "AliasT",
	ArrayT:      "ArrayT",
	BinT:        "BinT",
	BinPkgT:     "BinPkgT",
	ByteT:       "ByteT",
	BoolT:       "BoolT",
	BuiltinT:    "BuiltinT",
	ChanT:       "ChanT",
	Complex64T:  "Complex64T",
	Complex128T: "Complex128T",
	ErrorT:      "ErrorT",
	Float32T:    "Float32",
	Float64T:    "Float64T",
	FuncT:       "FuncT",
	InterfaceT:  "InterfaceT",
	IntT:        "IntT",
	Int8T:       "Int8T",
	Int16T:      "Int16T",
	Int32T:      "Int32T",
	Int64T:      "Int64T",
	MapT:        "MapT",
	PtrT:        "PtrT",
	RuneT:       "RuneT",
	SrcPkgT:     "SrcPkgT",
	StringT:     "StringT",
	StructT:     "StructT",
	UintT:       "UintT",
	Uint8T:      "Uint8T",
	Uint16T:     "Uint16T",
	Uint32T:     "Uint32T",
	Uint64T:     "Uint64T",
	UintptrT:    "UintptrT",
	ValueT:      "ValueT",
}

func (c Cat) String() string {
	if c < Cat(len(cats)) {
		return cats[c]
	}
	return "Cat(" + strconv.Itoa(int(c)) + ")"
}

// StructField type defines a field in a struct
type StructField struct {
	name string
	typ  *Type
}

// Type defines the internal representation of types in the interpreter
type Type struct {
	cat      Cat           // Type category
	field    []StructField // Array of struct fields if StrucT or nil
	key      *Type         // Type of key element if MapT or nil
	val      *Type         // Type of value element if ChanT, MapT, PtrT, AliasT or ArrayT
	arg      []*Type       // Argument types if FuncT or nil
	ret      []*Type       // Return types if FuncT or nil
	method   []*Node       // Associated methods or nil
	rtype    reflect.Type  // Reflection type if ValueT, or nil
	rzero    reflect.Value // Reflection zero settable value, or nil
	variadic bool          // true if type is variadic
	nindex   int           // node index (for debug only)
}

// TypeMap defines a map of Types indexed by type names
type TypeMap map[string]*Type

var defaultTypes TypeMap = map[string]*Type{
	"bool":       &Type{cat: BoolT},
	"byte":       &Type{cat: ByteT},
	"complex64":  &Type{cat: Complex64T},
	"complex128": &Type{cat: Complex128T},
	"error":      &Type{cat: ErrorT},
	"float32":    &Type{cat: Float32T},
	"float64":    &Type{cat: Float64T},
	"int":        &Type{cat: IntT},
	"int8":       &Type{cat: Int8T},
	"int16":      &Type{cat: Int16T},
	"int32":      &Type{cat: Int32T},
	"int64":      &Type{cat: Int64T},
	"rune":       &Type{cat: RuneT},
	"string":     &Type{cat: StringT},
	"uint":       &Type{cat: UintT},
	"uint8":      &Type{cat: Uint8T},
	"uint16":     &Type{cat: Uint16T},
	"uint32":     &Type{cat: Uint32T},
	"uint64":     &Type{cat: Uint64T},
	"uintptr":    &Type{cat: UintptrT},
}

// return a type definition for the corresponding AST subtree
func nodeType(interp *Interpreter, scope *Scope, n *Node) *Type {
	if n.typ != nil {
		return n.typ
	}
	var t = &Type{nindex: n.index}
	switch n.kind {
	case ArrayType:
		t.cat = ArrayT
		t.val = nodeType(interp, scope, n.child[0])
	case BasicLit:
		switch n.val.(type) {
		case bool:
			t.cat = BoolT
		case byte:
			t.cat = ByteT
		case float32:
			t.cat = Float32T
		case float64:
			t.cat = Float64T
		case int:
			t.cat = IntT
		case string:
			t.cat = StringT
		default:
			log.Panicf("Missing support for basic type %T, node %v\n", n.val, n.index)
		}
	case ChanType:
		t.cat = ChanT
		t.val = nodeType(interp, scope, n.child[0])
	case Ellipsis:
		t = nodeType(interp, scope, n.child[0])
		t.variadic = true
	case FuncType:
		t.cat = FuncT
		for _, arg := range n.child[0].child {
			t.arg = append(t.arg, nodeType(interp, scope, arg.child[len(arg.child)-1]))
		}
		if len(n.child) == 2 {
			for _, ret := range n.child[1].child {
				t.ret = append(t.ret, nodeType(interp, scope, ret.child[len(ret.child)-1]))
			}
		}
	case Ident:
		t = interp.types[n.ident]
	case InterfaceType:
		t.cat = InterfaceT
		//for _, method := range n.child[0].child {
		//	t.method = append(t.method, nodeType(interp, scope, method))
		//}
	case MapType:
		t.cat = MapT
		t.key = nodeType(interp, scope, n.child[0])
		t.val = nodeType(interp, scope, n.child[1])
	case SelectorExpr:
		pkgName, typeName := n.child[0].ident, n.child[1].ident
		if pkg, _, ok := scope.lookup(pkgName); ok {
			if typ, ok := (*pkg.pkgbin)[typeName]; ok {
				t.cat = ValueT
				t.rtype = reflect.TypeOf(typ).Elem()
			}
		}
	case StarExpr:
		t.cat = PtrT
		t.val = nodeType(interp, scope, n.child[0])
	case StructType:
		t.cat = StructT
		for _, c := range n.child[0].child {
			if len(c.child) == 1 {
				t.field = append(t.field, StructField{typ: nodeType(interp, scope, c.child[0])})
			} else {
				l := len(c.child)
				typ := nodeType(interp, scope, c.child[l-1])
				for _, d := range c.child[:l-1] {
					t.field = append(t.field, StructField{name: d.ident, typ: typ})
				}
			}
		}
	default:
		log.Panicln("type definition not implemented for node", n.index, n.kind)
	}
	return t
}

var zeroValues = [...]interface{}{
	BoolT:       false,
	ByteT:       byte(0),
	Complex64T:  complex64(0),
	Complex128T: complex128(0),
	ErrorT:      error(nil),
	Float32T:    float32(0),
	Float64T:    float64(0),
	IntT:        int(0),
	Int8T:       int8(0),
	Int16T:      int16(0),
	Int32T:      int32(0),
	Int64T:      int64(0),
	RuneT:       rune(0),
	StringT:     "",
	UintT:       uint(0),
	Uint8T:      uint8(0),
	Uint16T:     uint16(0),
	Uint32T:     uint32(0),
	Uint64T:     uint64(0),
	UintptrT:    uintptr(0),
	ValueT:      nil,
}

// zero instantiates and return a zero value object for the givent type t
func (t *Type) zero() interface{} {
	if t.cat >= Cat(len(zeroValues)) {
		return nil
	}
	switch t.cat {
	case AliasT:
		return t.val.zero()
	case StructT:
		z := make([]interface{}, len(t.field))
		for i, f := range t.field {
			z[i] = f.typ.zero()
		}
		return z
	case ValueT:
		return t.rzero
	default:
		return zeroValues[t.cat]
	}
}

// fieldType returns the field type of a struct or *struct type
func (t *Type) fieldType(index int) *Type {
	if t.cat == PtrT {
		return t.val.fieldType(index)
	}
	return t.field[index].typ
}

// fieldIndex returns the field index from name in a struct, or -1 if not found
func (t *Type) fieldIndex(name string) int {
	if t.cat == PtrT {
		return t.val.fieldIndex(name)
	}
	for i, field := range t.field {
		if name == field.name {
			return i
		}
	}
	return -1
}

// lookupField return a list of indices, i.e. a path to access a field in a struct object
func (t *Type) lookupField(name string) []int {
	var index []int
	if fi := t.fieldIndex(name); fi < 0 {
		for i, f := range t.field {
			if f.name == "" {
				if index2 := f.typ.lookupField(name); len(index2) > 0 {
					index = append([]int{i}, index2...)
					break
				}
			}
		}
	} else {
		index = append(index, fi)
	}
	return index
}

// getMethod returns a pointer to the method definition
func (t *Type) getMethod(name string) *Node {
	for _, m := range t.method {
		if name == m.ident {
			return m
		}
	}
	return nil
}

// lookupMethod returns a pointer to method definition associated to type t
// and the list of indices to access the right struct field, in case of a promoted method
func (t *Type) lookupMethod(name string) (*Node, []int) {
	if t.cat == PtrT {
		m, index := t.val.lookupMethod(name)
		return m, index
	}
	var index []int
	if m := t.getMethod(name); m == nil {
		for i, f := range t.field {
			if f.name == "" {
				if m, index2 := f.typ.lookupMethod(name); m != nil {
					index = append([]int{i}, index2...)
					return m, index
				}
			}
		}
	} else {
		return m, index
	}
	return nil, index
}

// ptrTo returns the pointer type with element t.
func ptrTo(t *Type) *Type {
	return &Type{cat: PtrT, val: t}
}

// TypeOf returns the reflection type of dynamic interpreter type t.
func (t *Type) TypeOf() reflect.Type {
	switch t.cat {
	case ArrayT:
		return reflect.SliceOf(t.val.TypeOf())
	case ChanT:
		return reflect.ChanOf(reflect.BothDir, t.val.TypeOf())
	case ErrorT:
		var e = new(error)
		return reflect.TypeOf(e).Elem()
	case FuncT:
		in := make([]reflect.Type, len(t.arg))
		out := make([]reflect.Type, len(t.ret))
		for i, v := range t.arg {
			in[i] = v.TypeOf()
		}
		for i, v := range t.ret {
			out[i] = v.TypeOf()
		}
		return reflect.FuncOf(in, out, false)
	case MapT:
		return reflect.MapOf(t.key.TypeOf(), t.val.TypeOf())
	case PtrT:
		return reflect.PtrTo(t.val.TypeOf())
	case StructT:
		var fields = []reflect.StructField{}
		for _, f := range t.field {
			if !canExport(f.name) {
				continue
			}
			fields = append(fields, reflect.StructField{Name: f.name, Type: f.typ.TypeOf()})
		}
		return reflect.StructOf(fields)
	case ValueT:
		return t.rtype.Elem()
	default:
		return reflect.TypeOf(t.zero())
	}
}
<|endoftext|>


<commit_before>// Package ystocks implements a library for using the Yahoo! Finance stock
// market API.
package ystocks

import (
    "encoding/csv"
    "net/http"
    "strings"
)

// Constants with URL parts for API URL-building.
const (
    quotesBaseUrl = "http://download.finance.yahoo.com/d/quotes.csv?s="
    staticUrl     = "&e=.csv"
)

// Stock symbol type; id must be a valid stock market symbol.
type Stock struct {
    id string
}

// Data type to return stock properties that were queried for.
type StockProperties [][]string

// Named constants for various stock properties.
const (
    AfterHoursChangeRealtime    = "c8"
    AnnualizedGain              = "g3"
    Ask                         = "a0"
    AskRealtime                 = "b2"
    AskSize                     = "a5"
    AverageDailyVolume          = "a2"
    Bid                         = "b0"
    BidRealtime                 = "b3"
    BidSize                     = "b6"
    BookValuePerShare           = "b4"
    Change                      = "c1"
    ChangeChangeInPercent       = "c0"
    ChangeFromFiftyDayMovingAverage = "m7"
    ChangeFromTwoHundredDayMovingAverage = "m5"
    ChangeFromYearHigh          = "k4"
    ChangeFromYearLow           = "j5"
    ChangeInPercent             = "p2"
    ChangeInPercentRealtime     = "k2"
    ChangeRealtime              = "c6"
    Commission                  = "c3"
    Currency                    = "c4"
    DaysHigh                    = "h0"
    DaysLow                     = "g0"
    DaysRange                   = "m0"
    DaysRangeRealtime           = "m2"
    DaysValueChange             = "w1"
    DaysValueChangeRealtime     = "w4"
    DividendPayDate             = "r1"
    TrailingAnnualDividendYield = "d0"
    TrailingAnnualDividendYieldInPercent = "y0"
    DilutedEPS                  = "e0"
    EBITDA                      = "j4"
    EPSEstimateCurrentYear      = "e7"
    EPSEstimateNextQuarter      = "e9"
    EPSEstimateNextYear         = "e8"
    ExDividendDate              = "q0"
    FiftyDayMovingAverage       = "m3"
    SharesFloat                 = "f6"
    HighLimit                   = "l2"
    HoldingsGain                = "g4"
    HoldingsGainPercent         = "g1"
    HoldingsGainPercentRealtime = "g5"
    HoldingsGainRealtime        = "g6"
    HoldingsValue               = "v1"
    HoldingsValueRealtime       = "v7"
    LastTradeDate               = "d1"
    LastTradePriceOnly          = "l1"
    LastTradeRealtimeWithTime   = "k1"
    LastTradeSize               = "k3"
    LastTradeTime               = "t1"
    LastTradeWithTime           = "l0"
    LowLimit                    = "l3"
    MarketCapitalization        = "j1"
    MarketCapRealtime           = "j3"
    MoreInfo                    = "i0"
    Name                        = "n0"
    Notes                       = "n4"
    OneYearTargetPrice          = "t8"
    Open                        = "o0"
    OrderBookRealtime           = "i5"
    PEGRatio                    = "r5"
    PERatio                     = "r0"
    PERatioRealtime             = "r2"
    PercentChangeFromFiftyDayMovingAverage = "m8"
    PercentChangeFromTwoHundredDayMovingAverage = "m6"
    ChangeInPercentFromYearHigh = "k5"
    PercentChangeFromYearLow    = "j6"
    PreviousClose               = "p0"
    PriceBook                   = "p6"
    PriceEPSEstimateCurrentYear = "r6"
    PriceEPSEstimateNextYear    = "r7"
    PricePaid                   = "p1"
    PriceSales                  = "p5"
    Revenue                     = "s6"
    SharesOwned                 = "s1"
    SharesOutstanding           = "j2"
    ShortRatio                  = "s7"
    StockExchange               = "x0"
    Symbol                      = "s0"
    TickerTrend                 = "t7"
    TradeDate                   = "d2"
    TradeLinks                  = "t6"
    TradeLinksAdditional        = "f0"
    TwoHundredDayMovingAverage  = "m4"
    Volume                      = "v0"
    YearHigh                    = "k0"
    YearLow                     = "j0"
    YearRange                   = "w0"
)

// Get a single property for a given stock. See the named constants, for
// example: MarketCapitalization or YearHigh, for what to pass in as a
// property string.
func (s *Stock) getProperty(prop string) (string, error) {
    props, err := s.getProperties([]string{prop})

    // Flatten properties to a single string if no error was found
    if err == nil && len(props) == 1 && len(props[0]) == 1 {
        return props[0][0], nil
    }

    return "", err
}

// Similar to getProperty(), but accepts an array of property names.
func (s *Stock) getProperties(props []string) (StockProperties, error) {
    // Build up the Y! Finance API URL
    propsStr := strings.Join(props, "")
    url := quotesBaseUrl + s.id +
           "&f=" + propsStr +
           staticUrl

    // HTTP GET the CSV data
    resp, httpErr := http.Get(url)
    if httpErr != nil {
        return nil, httpErr
    }

    // Convert string CSV data to a usable data structure
    reader := csv.NewReader(resp.Body)
    records, parseErr := reader.ReadAll()
    if parseErr != nil {
        return nil, parseErr
    }

    return records, nil
}
<commit_msg>Removes 'x' file attr for ystocks.go (silly Windows)<commit_after>// Package ystocks implements a library for using the Yahoo! Finance stock
// market API.
package ystocks

import (
    "encoding/csv"
    "net/http"
    "strings"
)

// Constants with URL parts for API URL-building.
const (
    quotesBaseUrl = "http://download.finance.yahoo.com/d/quotes.csv?s="
    staticUrl     = "&e=.csv"
)

// Stock symbol type; id must be a valid stock market symbol.
type Stock struct {
    id string
}

// Data type to return stock properties that were queried for.
type StockProperties [][]string

// Named constants for various stock properties.
const (
    AfterHoursChangeRealtime    = "c8"
    AnnualizedGain              = "g3"
    Ask                         = "a0"
    AskRealtime                 = "b2"
    AskSize                     = "a5"
    AverageDailyVolume          = "a2"
    Bid                         = "b0"
    BidRealtime                 = "b3"
    BidSize                     = "b6"
    BookValuePerShare           = "b4"
    Change                      = "c1"
    ChangeChangeInPercent       = "c0"
    ChangeFromFiftyDayMovingAverage = "m7"
    ChangeFromTwoHundredDayMovingAverage = "m5"
    ChangeFromYearHigh          = "k4"
    ChangeFromYearLow           = "j5"
    ChangeInPercent             = "p2"
    ChangeInPercentRealtime     = "k2"
    ChangeRealtime              = "c6"
    Commission                  = "c3"
    Currency                    = "c4"
    DaysHigh                    = "h0"
    DaysLow                     = "g0"
    DaysRange                   = "m0"
    DaysRangeRealtime           = "m2"
    DaysValueChange             = "w1"
    DaysValueChangeRealtime     = "w4"
    DividendPayDate             = "r1"
    TrailingAnnualDividendYield = "d0"
    TrailingAnnualDividendYieldInPercent = "y0"
    DilutedEPS                  = "e0"
    EBITDA                      = "j4"
    EPSEstimateCurrentYear      = "e7"
    EPSEstimateNextQuarter      = "e9"
    EPSEstimateNextYear         = "e8"
    ExDividendDate              = "q0"
    FiftyDayMovingAverage       = "m3"
    SharesFloat                 = "f6"
    HighLimit                   = "l2"
    HoldingsGain                = "g4"
    HoldingsGainPercent         = "g1"
    HoldingsGainPercentRealtime = "g5"
    HoldingsGainRealtime        = "g6"
    HoldingsValue               = "v1"
    HoldingsValueRealtime       = "v7"
    LastTradeDate               = "d1"
    LastTradePriceOnly          = "l1"
    LastTradeRealtimeWithTime   = "k1"
    LastTradeSize               = "k3"
    LastTradeTime               = "t1"
    LastTradeWithTime           = "l0"
    LowLimit                    = "l3"
    MarketCapitalization        = "j1"
    MarketCapRealtime           = "j3"
    MoreInfo                    = "i0"
    Name                        = "n0"
    Notes                       = "n4"
    OneYearTargetPrice          = "t8"
    Open                        = "o0"
    OrderBookRealtime           = "i5"
    PEGRatio                    = "r5"
    PERatio                     = "r0"
    PERatioRealtime             = "r2"
    PercentChangeFromFiftyDayMovingAverage = "m8"
    PercentChangeFromTwoHundredDayMovingAverage = "m6"
    ChangeInPercentFromYearHigh = "k5"
    PercentChangeFromYearLow    = "j6"
    PreviousClose               = "p0"
    PriceBook                   = "p6"
    PriceEPSEstimateCurrentYear = "r6"
    PriceEPSEstimateNextYear    = "r7"
    PricePaid                   = "p1"
    PriceSales                  = "p5"
    Revenue                     = "s6"
    SharesOwned                 = "s1"
    SharesOutstanding           = "j2"
    ShortRatio                  = "s7"
    StockExchange               = "x0"
    Symbol                      = "s0"
    TickerTrend                 = "t7"
    TradeDate                   = "d2"
    TradeLinks                  = "t6"
    TradeLinksAdditional        = "f0"
    TwoHundredDayMovingAverage  = "m4"
    Volume                      = "v0"
    YearHigh                    = "k0"
    YearLow                     = "j0"
    YearRange                   = "w0"
)

// Get a single property for a given stock. See the named constants, for
// example: MarketCapitalization or YearHigh, for what to pass in as a
// property string.
func (s *Stock) getProperty(prop string) (string, error) {
    props, err := s.getProperties([]string{prop})

    // Flatten properties to a single string if no error was found
    if err == nil && len(props) == 1 && len(props[0]) == 1 {
        return props[0][0], nil
    }

    return "", err
}

// Similar to getProperty(), but accepts an array of property names.
func (s *Stock) getProperties(props []string) (StockProperties, error) {
    // Build up the Y! Finance API URL
    propsStr := strings.Join(props, "")
    url := quotesBaseUrl + s.id +
           "&f=" + propsStr +
           staticUrl

    // HTTP GET the CSV data
    resp, httpErr := http.Get(url)
    if httpErr != nil {
        return nil, httpErr
    }

    // Convert string CSV data to a usable data structure
    reader := csv.NewReader(resp.Body)
    records, parseErr := reader.ReadAll()
    if parseErr != nil {
        return nil, parseErr
    }

    return records, nil
}
<|endoftext|>


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

package common

import (
	"errors"
	"fmt"
	"time"

	envoyAdmin "github.com/envoyproxy/go-control-plane/envoy/admin/v2alpha"
	"github.com/gogo/protobuf/jsonpb"

	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/pkg/test/util/structpath"
)

const (
	// DefaultTimeout the default timeout for the entire retry operation
	defaultConfigTimeout = time.Second * 30

	// DefaultDelay the default delay between successive retry attempts
	defaultConfigDelay = time.Second * 2
)

// ConfigFetchFunc retrieves the config dump from Envoy.
type ConfigFetchFunc func() (*envoyAdmin.ConfigDump, error)

// ConfigAcceptFunc evaluates the Envoy config dump and either accept/reject it. This is used
// by WaitForConfig to control the retry loop. If an error is returned, a retry will be attempted.
// Otherwise the loop is immediately terminated with an error if rejected or none if accepted.
type ConfigAcceptFunc func(*envoyAdmin.ConfigDump) (bool, error)

func WaitForConfig(fetch ConfigFetchFunc, accept ConfigAcceptFunc, options ...retry.Option) error {
	options = append([]retry.Option{retry.Delay(defaultConfigDelay), retry.Timeout(defaultConfigTimeout)}, options...)

	var cfg *envoyAdmin.ConfigDump
	_, err := retry.Do(func() (result interface{}, completed bool, err error) {
		cfg, err = fetch()
		if err != nil {
			return nil, false, err
		}

		accepted, err := accept(cfg)
		if err != nil {
			// Accept returned an error - retry.
			return nil, false, err
		}

		if accepted {
			// The configuration was accepted.
			return nil, true, nil
		}

		// The configuration was rejected, don't try again.
		return nil, true, errors.New("envoy config rejected")
	}, options...)

	if err != nil {
		configDumpStr := "nil"
		if cfg != nil {
			m := jsonpb.Marshaler{
				Indent: "  ",
			}
			if out, err := m.MarshalToString(cfg); err == nil {
				configDumpStr = out
			}
		}

		return fmt.Errorf("failed waiting for Envoy configuration: %v. Last config_dump:\n%s", err, configDumpStr)
	}
	return nil
}

// OutboundConfigAcceptFunc returns a function that accepts Envoy configuration if it contains
// outbound configuration for all of the given instances.
func OutboundConfigAcceptFunc(outboundInstances ...echo.Instance) ConfigAcceptFunc {
	return func(cfg *envoyAdmin.ConfigDump) (bool, error) {
		validator := structpath.ForProto(cfg)

		for _, target := range outboundInstances {
			for _, port := range target.Config().Ports {
				// Ensure that we have an outbound configuration for the target port.
				if err := CheckOutboundConfig(target, port, validator); err != nil {
					return false, err
				}
			}
		}

		return true, nil
	}
}

// CheckOutboundConfig checks the Envoy config dump for outbound configuration to the given target.
func CheckOutboundConfig(target echo.Instance, port echo.Port, validator *structpath.Instance) error {
	// Verify that we have an outbound cluster for the target.
	clusterName := clusterName(target, port)
	if err := validator.Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.name == '%s')]}", clusterName).
		Check(); err != nil {
		if err := validator.Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.edsClusterConfig.serviceName == '%s')]}",
			clusterName).Check(); err != nil {
			return err
		}
	}

	// For HTTP endpoints, verify that we have a route configured.
	if port.Protocol.IsHTTP() {
		return validator.Exists("{.configs[*].dynamicRouteConfigs[*].routeConfig.virtualHosts[*].routes[?(@.route.cluster == '%s')]}",
			clusterName).Check()
	}

	if !target.Config().Headless {
		// TCP case: Make sure we have an outbound listener configured.
		listenerName := listenerName(target.Address(), port)
		return validator.Exists("{.configs[*].dynamicActiveListeners[?(@.listener.name == '%s')]}", listenerName).Check()
	}
	return nil
}

func clusterName(target echo.Instance, port echo.Port) string {
	cfg := target.Config()
	return fmt.Sprintf("outbound|%d||%s.%s.%s", port.ServicePort, cfg.Service, cfg.Namespace.Name(), cfg.Domain)
}

func listenerName(address string, port echo.Port) string {
	return fmt.Sprintf("%s_%d", address, port.ServicePort)
}
<commit_msg>[Test Framework] Fix outbound check for Echo (#14166)<commit_after>// Copyright 2019 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
	"errors"
	"fmt"
	"time"

	envoyAdmin "github.com/envoyproxy/go-control-plane/envoy/admin/v2alpha"
	"github.com/gogo/protobuf/jsonpb"

	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/util/retry"
	"istio.io/istio/pkg/test/util/structpath"
)

const (
	// DefaultTimeout the default timeout for the entire retry operation
	defaultConfigTimeout = time.Second * 30

	// DefaultDelay the default delay between successive retry attempts
	defaultConfigDelay = time.Second * 2
)

// ConfigFetchFunc retrieves the config dump from Envoy.
type ConfigFetchFunc func() (*envoyAdmin.ConfigDump, error)

// ConfigAcceptFunc evaluates the Envoy config dump and either accept/reject it. This is used
// by WaitForConfig to control the retry loop. If an error is returned, a retry will be attempted.
// Otherwise the loop is immediately terminated with an error if rejected or none if accepted.
type ConfigAcceptFunc func(*envoyAdmin.ConfigDump) (bool, error)

func WaitForConfig(fetch ConfigFetchFunc, accept ConfigAcceptFunc, options ...retry.Option) error {
	options = append([]retry.Option{retry.Delay(defaultConfigDelay), retry.Timeout(defaultConfigTimeout)}, options...)

	var cfg *envoyAdmin.ConfigDump
	_, err := retry.Do(func() (result interface{}, completed bool, err error) {
		cfg, err = fetch()
		if err != nil {
			return nil, false, err
		}

		accepted, err := accept(cfg)
		if err != nil {
			// Accept returned an error - retry.
			return nil, false, err
		}

		if accepted {
			// The configuration was accepted.
			return nil, true, nil
		}

		// The configuration was rejected, don't try again.
		return nil, true, errors.New("envoy config rejected")
	}, options...)

	if err != nil {
		configDumpStr := "nil"
		if cfg != nil {
			m := jsonpb.Marshaler{
				Indent: "  ",
			}
			if out, err := m.MarshalToString(cfg); err == nil {
				configDumpStr = out
			}
		}

		return fmt.Errorf("failed waiting for Envoy configuration: %v. Last config_dump:\n%s", err, configDumpStr)
	}
	return nil
}

// OutboundConfigAcceptFunc returns a function that accepts Envoy configuration if it contains
// outbound configuration for all of the given instances.
func OutboundConfigAcceptFunc(outboundInstances ...echo.Instance) ConfigAcceptFunc {
	return func(cfg *envoyAdmin.ConfigDump) (bool, error) {
		validator := structpath.ForProto(cfg)

		for _, target := range outboundInstances {
			for _, port := range target.Config().Ports {
				// Ensure that we have an outbound configuration for the target port.
				if err := CheckOutboundConfig(target, port, validator); err != nil {
					return false, err
				}
			}
		}

		return true, nil
	}
}

// CheckOutboundConfig checks the Envoy config dump for outbound configuration to the given target.
func CheckOutboundConfig(target echo.Instance, port echo.Port, validator *structpath.Instance) error {
	// Verify that we have an outbound cluster for the target.
	clusterName := clusterName(target, port)
	if err := validator.
		Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.name == '%s')]}", clusterName).
		Check(); err != nil {
		if err := validator.
			Exists("{.configs[*].dynamicActiveClusters[?(@.cluster.edsClusterConfig.serviceName == '%s')]}", clusterName).
			Check(); err != nil {
			return err
		}
	}

	// For HTTP endpoints, verify that we have a route configured.
	if port.Protocol.IsHTTP() {
		rname := routeName(target, port)
		return validator.
			Select("{.configs[*].dynamicRouteConfigs[*].routeConfig.virtualHosts[?(@.name == '%s')]}", rname).
			Exists("{.routes[?(@.route.cluster == '%s')]}", clusterName).
			Check()
	}

	if !target.Config().Headless {
		// TCP case: Make sure we have an outbound listener configured.
		listenerName := listenerName(target.Address(), port)
		return validator.
			Exists("{.configs[*].dynamicActiveListeners[?(@.listener.name == '%s')]}", listenerName).
			Check()
	}
	return nil
}

func clusterName(target echo.Instance, port echo.Port) string {
	cfg := target.Config()
	return fmt.Sprintf("outbound|%d||%s.%s.%s", port.ServicePort, cfg.Service, cfg.Namespace.Name(), cfg.Domain)
}

func routeName(target echo.Instance, port echo.Port) string {
	cfg := target.Config()
	return fmt.Sprintf("%s:%d", cfg.FQDN(), port.ServicePort)
}

func listenerName(address string, port echo.Port) string {
	return fmt.Sprintf("%s_%d", address, port.ServicePort)
}
<|endoftext|>